Implement asset upload API and utility functions
- Add Asset and CardLayer model updates - Create asset upload API endpoints - Add AssetUploadDialog component - Create card layout algorithms - Implement symmetry generation utils - Add template configurations
This commit is contained in:
128
frontend/src/utils/cardLayout.js
Normal file
128
frontend/src/utils/cardLayout.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const LAYOUT_POSITIONS = {
|
||||
1: [
|
||||
{ x: 0.5, y: 0.5 }
|
||||
],
|
||||
2: [
|
||||
{ x: 0.5, y: 0.25 },
|
||||
{ x: 0.5, y: 0.75 }
|
||||
],
|
||||
3: [
|
||||
{ x: 0.5, y: 0.2 },
|
||||
{ x: 0.5, y: 0.5 },
|
||||
{ x: 0.5, y: 0.8 }
|
||||
],
|
||||
4: [
|
||||
{ x: 0.3, y: 0.25 },
|
||||
{ x: 0.7, y: 0.25 },
|
||||
{ x: 0.3, y: 0.75 },
|
||||
{ x: 0.7, y: 0.75 }
|
||||
],
|
||||
5: [
|
||||
{ x: 0.3, y: 0.2 },
|
||||
{ x: 0.7, y: 0.2 },
|
||||
{ x: 0.5, y: 0.5 },
|
||||
{ x: 0.3, y: 0.8 },
|
||||
{ x: 0.7, y: 0.8 }
|
||||
],
|
||||
6: [
|
||||
{ x: 0.3, y: 0.2 },
|
||||
{ x: 0.7, y: 0.2 },
|
||||
{ x: 0.3, y: 0.5 },
|
||||
{ x: 0.7, y: 0.5 },
|
||||
{ x: 0.3, y: 0.8 },
|
||||
{ x: 0.7, y: 0.8 }
|
||||
],
|
||||
7: [
|
||||
{ x: 0.3, y: 0.15 },
|
||||
{ x: 0.7, y: 0.15 },
|
||||
{ x: 0.5, y: 0.35 },
|
||||
{ x: 0.3, y: 0.55 },
|
||||
{ x: 0.7, y: 0.55 },
|
||||
{ x: 0.3, y: 0.85 },
|
||||
{ x: 0.7, y: 0.85 }
|
||||
],
|
||||
8: [
|
||||
{ x: 0.3, y: 0.15 },
|
||||
{ x: 0.7, y: 0.15 },
|
||||
{ x: 0.5, y: 0.35 },
|
||||
{ x: 0.3, y: 0.55 },
|
||||
{ x: 0.7, y: 0.55 },
|
||||
{ x: 0.5, y: 0.65 },
|
||||
{ x: 0.3, y: 0.85 },
|
||||
{ x: 0.7, y: 0.85 }
|
||||
],
|
||||
9: [
|
||||
{ x: 0.3, y: 0.15 },
|
||||
{ x: 0.7, y: 0.15 },
|
||||
{ x: 0.5, y: 0.35 },
|
||||
{ x: 0.2, y: 0.5 },
|
||||
{ x: 0.5, y: 0.5 },
|
||||
{ x: 0.8, y: 0.5 },
|
||||
{ x: 0.5, y: 0.65 },
|
||||
{ x: 0.3, y: 0.85 },
|
||||
{ x: 0.7, y: 0.85 }
|
||||
],
|
||||
10: [
|
||||
{ x: 0.3, y: 0.15 },
|
||||
{ x: 0.7, y: 0.15 },
|
||||
{ x: 0.3, y: 0.35 },
|
||||
{ x: 0.7, y: 0.35 },
|
||||
{ x: 0.5, y: 0.5 },
|
||||
{ x: 0.3, y: 0.65 },
|
||||
{ x: 0.7, y: 0.65 },
|
||||
{ x: 0.3, y: 0.85 },
|
||||
{ x: 0.7, y: 0.85 }
|
||||
]
|
||||
}
|
||||
|
||||
export function calculateSuitPositions(rank, cardWidth, cardHeight, symbolSize = 60) {
|
||||
const positions = LAYOUT_POSITIONS[rank] || LAYOUT_POSITIONS[1]
|
||||
|
||||
return positions.map(pos => ({
|
||||
x: pos.x * cardWidth - symbolSize / 2,
|
||||
y: pos.y * cardHeight - symbolSize / 2,
|
||||
width: symbolSize,
|
||||
height: symbolSize
|
||||
}))
|
||||
}
|
||||
|
||||
export function getCornerPositions(cardWidth, cardHeight) {
|
||||
return {
|
||||
topLeft: { x: 50, y: 50 },
|
||||
topRight: { x: cardWidth - 100, y: 50 },
|
||||
bottomLeft: { x: 50, y: cardHeight - 100 },
|
||||
bottomRight: { x: cardWidth - 100, y: cardHeight - 100 }
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuitSymbol(suit) {
|
||||
const symbols = {
|
||||
spade: '♠',
|
||||
heart: '♥',
|
||||
club: '♣',
|
||||
diamond: '♦'
|
||||
}
|
||||
return symbols[suit] || '♠'
|
||||
}
|
||||
|
||||
export function getSuitColor(suit, templateColors) {
|
||||
if (templateColors && templateColors[suit]) {
|
||||
return templateColors[suit]
|
||||
}
|
||||
|
||||
const colors = {
|
||||
spade: '#000000',
|
||||
heart: '#FF0000',
|
||||
club: '#000000',
|
||||
diamond: '#FF0000'
|
||||
}
|
||||
return colors[suit] || '#000000'
|
||||
}
|
||||
|
||||
export function isRedSuit(suit) {
|
||||
return suit === 'heart' || suit === 'diamond'
|
||||
}
|
||||
|
||||
export function isBlackSuit(suit) {
|
||||
return suit === 'spade' || suit === 'club'
|
||||
}
|
||||
81
frontend/src/utils/symmetry.js
Normal file
81
frontend/src/utils/symmetry.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import { fabric } from 'fabric'
|
||||
|
||||
export async function createSymmetricalImage(originalImage, canvasWidth, canvasHeight) {
|
||||
const imgWidth = originalImage.width
|
||||
const imgHeight = originalImage.height
|
||||
const halfHeight = imgHeight / 2
|
||||
|
||||
const topHalf = new fabric.Image(originalImage, {
|
||||
clipPath: new fabric.Rect({
|
||||
width: imgWidth,
|
||||
height: halfHeight,
|
||||
originX: 'left',
|
||||
originY: 'top'
|
||||
}),
|
||||
top: 0,
|
||||
scaleX: canvasWidth / imgWidth,
|
||||
scaleY: (canvasHeight / 2) / halfHeight
|
||||
})
|
||||
|
||||
const bottomHalf = new fabric.Image(originalImage, {
|
||||
clipPath: new fabric.Rect({
|
||||
width: imgWidth,
|
||||
height: halfHeight,
|
||||
originX: 'left',
|
||||
originY: 'top',
|
||||
top: halfHeight
|
||||
}),
|
||||
top: canvasHeight / 2,
|
||||
scaleX: canvasWidth / imgWidth,
|
||||
scaleY: -(canvasHeight / 2) / halfHeight,
|
||||
flipY: true
|
||||
})
|
||||
|
||||
const group = new fabric.Group([topHalf, bottomHalf], {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: canvasWidth,
|
||||
height: canvasHeight
|
||||
})
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
export async function loadAndProcessImage(imageUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(imageUrl, (img) => {
|
||||
if (!img) {
|
||||
reject(new Error('Failed to load image'))
|
||||
return
|
||||
}
|
||||
resolve(img)
|
||||
}, { crossOrigin: 'anonymous' })
|
||||
})
|
||||
}
|
||||
|
||||
export function applySymmetryToFaceCard(canvas, imageUrl, cardWidth, cardHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fabric.Image.fromURL(imageUrl, async (originalImage) => {
|
||||
if (!originalImage) {
|
||||
reject(new Error('Failed to load image'))
|
||||
return
|
||||
}
|
||||
|
||||
const symmetricalGroup = await createSymmetricalImage(
|
||||
originalImage.getElement(),
|
||||
cardWidth - 100,
|
||||
cardHeight - 100
|
||||
)
|
||||
|
||||
symmetricalGroup.set({
|
||||
left: 50,
|
||||
top: 50,
|
||||
selectable: true
|
||||
})
|
||||
|
||||
canvas.add(symmetricalGroup)
|
||||
canvas.renderAll()
|
||||
resolve(symmetricalGroup)
|
||||
}, { crossOrigin: 'anonymous' })
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user