diff --git a/src/views/PrintStepView.vue b/src/views/PrintStepView.vue index 3972614..a73fa79 100644 --- a/src/views/PrintStepView.vue +++ b/src/views/PrintStepView.vue @@ -8,6 +8,7 @@ const showResult = ref(false) const photos = ref([]) const layout = ref<'1x4' | '2x2'>('1x4') const frame = ref(0) +const finalImage = ref(null) onMounted(() => { // โหลดข้อมูลจาก localStorage @@ -29,6 +30,9 @@ onMounted(() => { // เริ่มนับถอยหลัง startCountdown() + + // Load the final image from localStorage + finalImage.value = localStorage.getItem('photobooth-final-image'); }) const startCountdown = () => { @@ -53,7 +57,7 @@ const getGridStyle = () => { gridTemplateColumns: '1fr', gridTemplateRows: 'repeat(4, 1fr)', gap: '8px', - aspectRatio: '1/5' // ทำให้แต่ละภาพยาวขึ้น (1:2 aspect ratio รวม) + aspectRatio: '1/1' // ทำให้แต่ละภาพยาวขึ้น (1:2 aspect ratio รวม) } } else { return { @@ -65,6 +69,226 @@ const getGridStyle = () => { } } } + +const saveToLocalStorage = (key: string, data: any) => { + try { + localStorage.setItem(key, JSON.stringify(data)); + } catch (error) { + console.error('Error saving to localStorage:', error); + } +}; + +const drawFrameBackground = (context: CanvasRenderingContext2D, width: number, height: number, frameType: number) => { + context.clearRect(0, 0, width, height); + + switch (frameType) { + case 0: // Classic frame + context.fillStyle = '#000000'; + context.fillRect(0, 0, width, height); + break; + case 1: // Modern frame + const gradient1 = context.createLinearGradient(0, 0, width, height); + gradient1.addColorStop(0, '#667eea'); + gradient1.addColorStop(1, '#764ba2'); + context.fillStyle = gradient1; + context.fillRect(0, 0, width, height); + break; + case 2: // Vintage frame + const gradient2 = context.createLinearGradient(0, 0, width, height); + gradient2.addColorStop(0, '#f093fb'); + gradient2.addColorStop(1, '#f5576c'); + context.fillStyle = gradient2; + context.fillRect(0, 0, width, height); + break; + case 3: // Colorful frame + const gradient3 = context.createLinearGradient(0, 0, width, height); + gradient3.addColorStop(0, '#4facfe'); + gradient3.addColorStop(1, '#00f2fe'); + context.fillStyle = gradient3; + context.fillRect(0, 0, width, height); + break; + case 4: // Minimal frame + context.fillStyle = '#f8f9fa'; + context.fillRect(0, 0, width, height); + context.strokeStyle = '#dee2e6'; + context.lineWidth = 4; + context.strokeRect(0, 0, width, height); + break; + default: + console.warn('Unknown frame type:', frameType); + context.fillStyle = '#ffffff'; + context.fillRect(0, 0, width, height); + } +}; + +const drawHeaderFooterImage = async (context: CanvasRenderingContext2D, imagePath: string, x: number, y: number, width: number, height: number) => { + return new Promise((resolve) => { + const img = new Image() + img.onload = () => { + context.drawImage(img, x, y, width, height) + resolve() + } + img.onerror = () => { + console.warn(`Failed to load header/footer image: ${imagePath}, drawing placeholder`) + // วาด placeholder color แทน + if (imagePath.includes('header-1x4')) { + context.fillStyle = '#007bff' // น้ำเงินสำหรับ 1x4 header + } else if (imagePath.includes('footer-1x4')) { + context.fillStyle = '#dc3545' // แดงสำหรับ 1x4 footer + } else if (imagePath.includes('header-2x2')) { + context.fillStyle = '#28a745' // เขียวสำหรับ 2x2 header + } else if (imagePath.includes('footer-2x2')) { + context.fillStyle = '#ffc107' // เหลืองสำหรับ 2x2 footer + } + context.fillRect(x, y, width, height) + resolve() + } + img.src = imagePath + }) +} + + +const generateAndCacheImage = async () => { + try { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Could not get canvas context') + } + + // ขนาดรูปแต่ละภาพ (360x480 = 3:4) - ลดขนาดลงเพื่อป้องกันปัญหา canvas ใหญ่เกินไป + const photoWidth = 360 + const photoHeight = 480 + const gap = 20 // ระยะห่างระหว่างรูป + + let canvasWidth, canvasHeight + let headerHeight = 0, footerHeight = 0 + let sidePadding = 0 + + if (layout.value === '1x4') { + // 1x4: 1 คอลัมน์ 4 แถว + header/footer + headerHeight = 50 + footerHeight = 50 + sidePadding = 30 // padding ซ้ายขวาเพื่อให้ total width = 420 + canvasWidth = 420 + canvasHeight = headerHeight + (photoHeight * 4) + (gap * 3) + footerHeight + } else { + // 2x2: 2 คอลัมน์ 2 แถว + header/footer + headerHeight = 60 + footerHeight = 60 + sidePadding = 20 // padding ซ้ายขวาเพื่อให้ total width = 780 + canvasWidth = 780 + canvasHeight = headerHeight + (photoHeight * 2) + gap + footerHeight + } + + console.log('Canvas dimensions:', canvasWidth, 'x', canvasHeight) + canvas.width = canvasWidth + canvas.height = canvasHeight + + // วาด background ตาม frame + drawFrameBackground(context, canvasWidth, canvasHeight, frame.value) + + // วาด header image + const headerImagePath = layout.value === '1x4' ? '/assets/header-1x4.png' : '/assets/header-2x2.png' + await drawHeaderFooterImage(context, headerImagePath, 0, 0, canvasWidth, headerHeight) + + // วาด footer image + const footerImagePath = layout.value === '1x4' ? '/assets/footer-1x4.png' : '/assets/footer-2x2.png' + await drawHeaderFooterImage(context, footerImagePath, 0, canvasHeight - footerHeight, canvasWidth, footerHeight) + + // วาดรูปภาพ (มี padding ซ้ายขวา) + for (let i = 0; i < photos.value.length; i++) { + const photoSrc = photos.value[i] + if (!photoSrc) continue + + const img = new Image() + img.src = photoSrc + + await new Promise((resolve) => { + img.onload = () => { + let x, y + + if (layout.value === '1x4') { + // 1x4 layout - วาดรูปตรงกลาง (มี padding ซ้ายขวา) + x = sidePadding + y = headerHeight + i * (photoHeight + gap) + } else { + // 2x2 layout - วาดรูปตรงกลาง (มี padding ซ้ายขวา) + const col = i % 2 + const row = Math.floor(i / 2) + x = sidePadding + col * (photoWidth + gap) + y = headerHeight + row * (photoHeight + gap) + } + + // วาดรูปภาพ + context.drawImage(img, x, y, photoWidth, photoHeight) + resolve(void 0) + } + img.onerror = () => { + console.error(`Failed to load image ${i}`) + resolve(void 0) + } + }) + } + + // สร้าง PNG data URL และบันทึกใน localStorage + const dataUrl = canvas.toDataURL('image/png') + + try { + saveToLocalStorage('photobooth-final-image', dataUrl); + finalImage.value = dataUrl; + } catch (error) { + if (error instanceof DOMException && error.name === 'QuotaExceededError') { + console.error('localStorage quota exceeded for cached image') + alert('ไม่สามารถบันทึกภาพได้เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ') + } else { + console.error('Error saving cached image:', error) + throw error + } + } + } catch (error) { + console.error('Error in generateAndCacheImage:', error) + } +} + + + + +const compressImage = (dataUrl: string, quality: number): Promise => { + return new Promise((resolve) => { + const img = new Image(); + img.src = dataUrl; + + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + if (!ctx) { + console.error('Canvas context not available for compression'); + resolve(dataUrl); + return; + } + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + resolve(canvas.toDataURL('image/jpeg', quality)); + }; + + img.onerror = () => { + console.error('Failed to compress image'); + resolve(dataUrl); + }; + }); +}; + +onMounted(() => { + // ...existing code... + + generateAndCacheImage(); +}); @@ -232,6 +463,26 @@ const getGridStyle = () => { font-size: 1.1rem; } +.final-image-section { + margin-top: 2rem; + text-align: center; + animation: fadeIn 1s ease-out forwards; +} + +.final-image-container { + margin-top: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.final-image-container img { + max-width: 100%; + height: auto; + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; }