Skip to content

Commit f6e7ab3

Browse files
committed
fix: higher res on tcg
1 parent 0849af5 commit f6e7ab3

File tree

1 file changed

+89
-62
lines changed

1 file changed

+89
-62
lines changed

src/pages/apps/TcgCardGeneratorPage.js

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import React, {useState, useEffect, useRef} from 'react';
1+
import React, {useState, useEffect, useRef, useCallback} from 'react';
22
import {Link} from 'react-router-dom';
33
import {
4-
AcornIcon,
54
ArrowLeftIcon,
65
DownloadSimpleIcon,
76
UploadSimpleIcon,
87
} from '@phosphor-icons/react';
98
import useSeo from '../../hooks/useSeo';
9+
import { useToast } from '../../hooks/useToast';
1010
import CustomDropdown from '../../components/CustomDropdown';
1111
import BreadcrumbTitle from '../../components/BreadcrumbTitle';
1212

@@ -41,11 +41,15 @@ const TcgCardGeneratorPage = () => {
4141
],
4242
});
4343

44+
const { addToast } = useToast();
45+
4446
// --- State ---
4547
const [cardName, setCardName] = useState('Cyber Dragon');
4648
const [hp, setHp] = useState('250');
4749
const [background, setBackground] = useState('Techno 1');
4850
const [image, setImage] = useState(null);
51+
const [imageDimensions, setImageDimensions] = useState(null); // Stores { width, height }
52+
const [loadedImgElement, setLoadedImgElement] = useState(null); // Stores the actual Image object
4953

5054
const [generation, setGeneration] = useState('Gen 5 - Neo Tokyo');
5155
const [type, setType] = useState('Machine / Dragon');
@@ -65,6 +69,17 @@ const TcgCardGeneratorPage = () => {
6569
const canvasRef = useRef(null);
6670
const fileInputRef = useRef(null);
6771

72+
// Preload image object when image string changes
73+
useEffect(() => {
74+
if (image) {
75+
const img = new Image();
76+
img.src = image;
77+
img.onload = () => setLoadedImgElement(img);
78+
} else {
79+
setLoadedImgElement(null);
80+
}
81+
}, [image]);
82+
6883
// --- Canvas Drawing Helper Functions ---
6984
const drawRoundedRect = (ctx, x, y, width, height, radius) => {
7085
ctx.beginPath();
@@ -101,22 +116,8 @@ const TcgCardGeneratorPage = () => {
101116
return currentY + lineHeight;
102117
};
103118

104-
// --- Main Draw Logic ---
105-
useEffect(() => {
106-
const canvas = canvasRef.current;
107-
if (!canvas) return;
108-
const ctx = canvas.getContext('2d');
109-
110-
// High DPI scaling
111-
const dpr = window.devicePixelRatio || 1;
112-
const logicalWidth = 420;
113-
const logicalHeight = 620; // Increased height slightly
114-
canvas.width = logicalWidth * dpr;
115-
canvas.height = logicalHeight * dpr;
116-
ctx.scale(dpr, dpr);
117-
canvas.style.width = `${logicalWidth}px`;
118-
canvas.style.height = `${logicalHeight}px`;
119-
119+
// --- Main Draw Logic (Memoized) ---
120+
const drawCard = useCallback((ctx, logicalWidth, logicalHeight) => {
120121
// --- Background ---
121122
const selectedBg = backgroundOptions.find(b => b.value === background) || backgroundOptions[0];
122123

@@ -189,7 +190,7 @@ const TcgCardGeneratorPage = () => {
189190

190191
// Text: Name
191192
ctx.fillStyle = '#00ffff';
192-
ctx.font = 'bold 22px "Courier New", monospace';
193+
ctx.font = 'bold 22px "Arvo", serif';
193194
ctx.textAlign = 'left';
194195
ctx.shadowColor = '#00ffff';
195196
ctx.shadowBlur = 8;
@@ -198,7 +199,7 @@ const TcgCardGeneratorPage = () => {
198199

199200
// Text: HP
200201
ctx.fillStyle = '#ff0055';
201-
ctx.font = 'bold 22px "Courier New", monospace';
202+
ctx.font = 'bold 22px "Arvo", serif';
202203
ctx.textAlign = 'right';
203204
ctx.shadowColor = '#ff0055';
204205
ctx.shadowBlur = 8;
@@ -216,42 +217,29 @@ const TcgCardGeneratorPage = () => {
216217
ctx.fillRect(imgX - 2, imgY - 2, imgW + 4, imgH + 4);
217218

218219
// Draw Image
219-
if (image) {
220-
const img = new Image();
221-
img.src = image;
222-
223-
const drawImageCover = (img) => {
224-
const srcRatio = img.width / img.height;
225-
const destRatio = imgW / imgH;
226-
let sx, sy, sWidth, sHeight;
227-
228-
if (srcRatio > destRatio) {
229-
// Image is wider than destination: crop width
230-
sHeight = img.height;
231-
sWidth = img.height * destRatio;
232-
sx = (img.width - sWidth) / 2;
233-
sy = 0;
234-
} else {
235-
// Image is taller than destination: crop height
236-
sWidth = img.width;
237-
sHeight = img.width / destRatio;
238-
sx = 0;
239-
sy = (img.height - sHeight) / 2;
240-
}
241-
242-
ctx.drawImage(img, sx, sy, sWidth, sHeight, imgX, imgY, imgW, imgH);
243-
};
244-
245-
if (img.complete) {
246-
drawImageCover(img);
220+
if (loadedImgElement) {
221+
// Object-fit: cover logic
222+
const srcRatio = loadedImgElement.width / loadedImgElement.height;
223+
const destRatio = imgW / imgH;
224+
let sx, sy, sWidth, sHeight;
225+
226+
if (srcRatio > destRatio) {
227+
sHeight = loadedImgElement.height;
228+
sWidth = loadedImgElement.height * destRatio;
229+
sx = (loadedImgElement.width - sWidth) / 2;
230+
sy = 0;
247231
} else {
248-
img.onload = () => drawImageCover(img);
232+
sWidth = loadedImgElement.width;
233+
sHeight = loadedImgElement.width / destRatio;
234+
sx = 0;
235+
sy = (loadedImgElement.height - sHeight) / 2;
249236
}
237+
ctx.drawImage(loadedImgElement, sx, sy, sWidth, sHeight, imgX, imgY, imgW, imgH);
250238
} else {
251239
ctx.fillStyle = '#222';
252240
ctx.fillRect(imgX, imgY, imgW, imgH);
253241
ctx.fillStyle = '#555';
254-
ctx.font = '16px "Courier New", monospace';
242+
ctx.font = '16px "Arvo", serif';
255243
ctx.textAlign = 'center';
256244
ctx.fillText('UPLOAD IMAGE', logicalWidth / 2, imgY + imgH / 2);
257245
}
@@ -288,7 +276,7 @@ const TcgCardGeneratorPage = () => {
288276
// --- Content Fields ---
289277
let currentY = imgY + imgH + 20;
290278
const labelWidth = 100;
291-
const valueX = 40 + labelWidth;
279+
// const valueX = 40 + labelWidth;
292280

293281
const drawField = (label, value, color = '#fff') => {
294282
// Label Box
@@ -351,15 +339,36 @@ const TcgCardGeneratorPage = () => {
351339

352340
// Illustrator (Bottom Left)
353341
ctx.fillStyle = '#fff';
354-
ctx.font = '10px "Courier New", monospace';
342+
ctx.font = '10px "Arvo", serif';
355343
ctx.textAlign = 'left';
356344
ctx.fillText(`ILLUS: ${illustrator.toUpperCase()}`, 25, footerY);
357345

358346
// Card Info (Bottom Right)
359347
const date = new Date().toLocaleDateString();
360348
ctx.textAlign = 'right';
361349
ctx.fillText(`${date} | FEZCODEX | ${cardNumber}/${totalCards}`, logicalWidth - 25, footerY);
362-
}, [cardName, hp, background, image, generation, type, attack, defense, cost, description, illustrator, cardNumber, totalCards]);
350+
}, [cardName, hp, background, loadedImgElement, generation, type, attack, defense, cost, description, illustrator, cardNumber, totalCards]);
351+
352+
// --- useEffect to update canvas ---
353+
useEffect(() => {
354+
const canvas = canvasRef.current;
355+
if (!canvas) return;
356+
const ctx = canvas.getContext('2d');
357+
358+
const dpr = window.devicePixelRatio || 1;
359+
const logicalWidth = 420;
360+
const logicalHeight = 620;
361+
362+
canvas.width = logicalWidth * dpr;
363+
canvas.height = logicalHeight * dpr;
364+
canvas.style.width = `${logicalWidth}px`;
365+
canvas.style.height = `${logicalHeight}px`;
366+
367+
ctx.save();
368+
ctx.scale(dpr, dpr);
369+
drawCard(ctx, logicalWidth, logicalHeight);
370+
ctx.restore();
371+
}, [drawCard]); // Depend only on drawCard which already depends on all state
363372

364373
const handleImageUpload = (e) => {
365374
const file = e.target.files[0];
@@ -368,6 +377,7 @@ const TcgCardGeneratorPage = () => {
368377
reader.onload = (event) => {
369378
const img = new Image();
370379
img.onload = () => {
380+
setImageDimensions({width: img.width, height: img.height});
371381
setImage(event.target.result);
372382
};
373383
img.src = event.target.result;
@@ -381,11 +391,23 @@ const TcgCardGeneratorPage = () => {
381391
};
382392

383393
const downloadCard = () => {
384-
const canvas = canvasRef.current;
385-
if (!canvas) return;
394+
addToast({ title: 'Downloading...', message: 'Generating high-resolution card.', duration: 3000 });
395+
// High Resolution Download
396+
const scaleFactor = 3; // 3x resolution (1260x1860)
397+
const width = 420;
398+
const height = 620;
399+
400+
const canvas = document.createElement('canvas');
401+
canvas.width = width * scaleFactor;
402+
canvas.height = height * scaleFactor;
403+
const ctx = canvas.getContext('2d');
404+
405+
ctx.scale(scaleFactor, scaleFactor);
406+
drawCard(ctx, width, height);
407+
386408
const link = document.createElement('a');
387-
link.download = `${cardName.replace(/\s+/g, '_')}_card.png`;
388-
link.href = canvas.toDataURL();
409+
link.download = `${cardName.replace(/\s+/g, '_')}_card_HD.png`;
410+
link.href = canvas.toDataURL('image/png');
389411
link.click();
390412
};
391413

@@ -403,13 +425,13 @@ const TcgCardGeneratorPage = () => {
403425
{/* Header */}
404426
<div className="mb-10 text-center">
405427
<Link to="/apps" className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4">
406-
<ArrowLeftIcon size={24} /> Back to Apps
428+
<ArrowLeftIcon size={24}/> Back to Apps
407429
</Link>
408430
<BreadcrumbTitle title="Techno TCG Maker" slug="tcg"/>
409431
<p className="text-gray-500 max-w-xl mx-auto mb-4">
410432
Design your own futuristic trading cards.
411433
</p>
412-
<hr className="border-gray-700" />
434+
<hr className="border-gray-700"/>
413435
</div>
414436
<div className="flex flex-col xl:flex-row gap-10 items-start justify-center">
415437
{/* --- Left Column: Editor --- */}
@@ -459,6 +481,11 @@ const TcgCardGeneratorPage = () => {
459481
<div className="flex items-center gap-4">
460482
<div className="flex-1">
461483
<p className="text-sm text-gray-400">Upload a cyberpunk or futuristic image for best results.</p>
484+
{imageDimensions && (
485+
<p className="text-xs text-gray-500 mt-1">
486+
Dimensions: {imageDimensions.width} x {imageDimensions.height} px
487+
</p>
488+
)}
462489
</div>
463490
<div className="flex-shrink-0">
464491
<input
@@ -597,13 +624,13 @@ const TcgCardGeneratorPage = () => {
597624
<div className="mt-8 flex gap-4 w-full max-w-[420px]">
598625
<button
599626
onClick={downloadCard}
600-
className="flex-1 flex items-center justify-center gap-2 px-6 py-4 rounded-xl text-lg font-bold bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white shadow-lg hover:shadow-cyan-900/30 transition-all transform hover:-translate-y-0.5"
627+
className="flex-1 flex items-center justify-center gap-2 px-6 py-4 rounded-md text-lg font-arvo transition-colors border bg-tb text-app border-app-alpha-50 hover:bg-app/15 "
601628
>
602-
<DownloadSimpleIcon size={24} weight="bold"/> Download Card
629+
<DownloadSimpleIcon size={24} weight="bold"/> Download HD
603630
</button>
604631
</div>
605632
<p className="mt-4 text-xs text-gray-500">
606-
High-resolution PNG output.
633+
High-resolution (3x) PNG output.
607634
</p>
608635
</div>
609636
</div>

0 commit comments

Comments
 (0)