import { saveAs } from 'file-saver'; import type { Layer } from '@/types'; import { loadImageFromFile } from './canvas-utils'; /** * Project file format */ export interface ProjectData { version: string; width: number; height: number; layers: { id: string; name: string; visible: boolean; opacity: number; blendMode: string; order: number; locked: boolean; width: number; height: number; x: number; y: number; imageData?: string; // Base64 encoded PNG }[]; metadata: { createdAt: number; modifiedAt: number; }; } /** * Open image file and return HTMLImageElement */ export async function openImageFile(file: File): Promise { return loadImageFromFile(file); } /** * Handle drag & drop or paste files */ export function extractImageFromDataTransfer( dataTransfer: DataTransfer ): File | null { const items = Array.from(dataTransfer.items); const files = Array.from(dataTransfer.files); // Check for image in items first for (const item of items) { if (item.type.startsWith('image/')) { const file = item.getAsFile(); if (file) return file; } } // Check files for (const file of files) { if (file.type.startsWith('image/')) { return file; } } return null; } /** * Export canvas as image file */ export async function exportCanvasAsImage( canvas: HTMLCanvasElement, format: 'png' | 'jpeg' | 'webp' = 'png', quality = 1, filename = 'image' ): Promise { const mimeType = `image/${format}`; const extension = format === 'jpeg' ? 'jpg' : format; const blob = await new Promise((resolve) => { canvas.toBlob((blob) => resolve(blob), mimeType, quality); }); if (blob) { saveAs(blob, `${filename}.${extension}`); } } /** * Export project as JSON with layer data */ export async function exportProject( layers: Layer[], width: number, height: number, filename = 'project' ): Promise { const projectData: ProjectData = { version: '1.0.0', width, height, layers: await Promise.all( layers.map(async (layer) => ({ id: layer.id, name: layer.name, visible: layer.visible, opacity: layer.opacity, blendMode: layer.blendMode, order: layer.order, locked: layer.locked, width: layer.width, height: layer.height, x: layer.x, y: layer.y, imageData: layer.canvas ? layer.canvas.toDataURL('image/png') : undefined, })) ), metadata: { createdAt: Date.now(), modifiedAt: Date.now(), }, }; const json = JSON.stringify(projectData, null, 2); const blob = new Blob([json], { type: 'application/json' }); saveAs(blob, `${filename}.paint`); } /** * Load project from JSON file */ export async function loadProject(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = (e) => { try { const json = e.target?.result as string; const data: ProjectData = JSON.parse(json); resolve(data); } catch (error) { reject(new Error('Invalid project file')); } }; reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsText(file); }); } /** * Create canvas from base64 image data */ export async function createCanvasFromDataURL( dataURL: string, width: number, height: number ): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (ctx) { ctx.drawImage(img, 0, 0); resolve(canvas); } else { reject(new Error('Failed to get 2D context')); } }; img.onerror = () => reject(new Error('Failed to load image')); img.src = dataURL; }); } /** * Get file extension from filename */ export function getFileExtension(filename: string): string { const parts = filename.split('.'); return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : ''; } /** * Validate file type */ export function isImageFile(file: File): boolean { return file.type.startsWith('image/'); } /** * Validate project file */ export function isProjectFile(file: File): boolean { return ( file.type === 'application/json' || getFileExtension(file.name) === 'paint' ); }