import { loadImageMagick } from '@/lib/wasm/wasmLoader'; import type { ConversionOptions, ProgressCallback, ConversionResult } from '@/types/conversion'; /** * Convert image using ImageMagick */ export async function convertWithImageMagick( file: File, outputFormat: string, options: ConversionOptions = {}, onProgress?: ProgressCallback ): Promise { const startTime = Date.now(); try { // Load ImageMagick instance await loadImageMagick(); // Report initial progress if (onProgress) onProgress(10); // Read input file as ArrayBuffer const arrayBuffer = await file.arrayBuffer(); const inputData = new Uint8Array(arrayBuffer); if (onProgress) onProgress(30); // Import ImageMagick functions (already initialized by loadImageMagick) const { ImageMagick } = await import('@imagemagick/magick-wasm'); if (onProgress) onProgress(40); // Get output format enum const outputFormatEnum = await getMagickFormatEnum(outputFormat); if (onProgress) onProgress(50); // Convert image using ImageMagick let result: Uint8Array | undefined; await ImageMagick.read(inputData, (image) => { // Apply quality setting if specified if (options.imageQuality !== undefined) { image.quality = options.imageQuality; } // Apply resize if specified if (options.imageWidth || options.imageHeight) { const width = options.imageWidth || 0; const height = options.imageHeight || 0; if (width > 0 && height > 0) { // Both dimensions specified image.resize(width, height); } else if (width > 0) { // Only width specified, maintain aspect ratio const aspectRatio = image.height / image.width; image.resize(width, Math.round(width * aspectRatio)); } else if (height > 0) { // Only height specified, maintain aspect ratio const aspectRatio = image.width / image.height; image.resize(Math.round(height * aspectRatio), height); } } if (onProgress) onProgress(70); // Write the image data with the specified format result = image.write(outputFormatEnum); if (onProgress) onProgress(90); }); // Verify we have a result if (!result || result.length === 0) { throw new Error('ImageMagick conversion produced empty result'); } console.log('[ImageMagick] Conversion complete:', { inputSize: inputData.length, outputSize: result.length, format: outputFormat, quality: options.imageQuality, }); // Verify the data looks like valid image data by checking magic bytes const first4Bytes = Array.from(result.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(' '); console.log('[ImageMagick] First 4 bytes:', first4Bytes); // Create blob from result const mimeType = getMimeType(outputFormat); const blob = new Blob([result as BlobPart], { type: mimeType }); console.log('[ImageMagick] Created blob:', { size: blob.size, type: blob.type, }); // Verify blob can be read try { const testReader = new FileReader(); const testPromise = new Promise((resolve) => { testReader.onloadend = () => { if (testReader.result instanceof ArrayBuffer) { const testArr = new Uint8Array(testReader.result); console.log('[ImageMagick] Blob verification - first 4 bytes:', Array.from(testArr.slice(0, 4)).map(b => b.toString(16).padStart(2, '0')).join(' ')); } resolve(true); }; }); testReader.readAsArrayBuffer(blob.slice(0, 4)); await testPromise; } catch (err) { console.error('[ImageMagick] Blob verification failed:', err); } if (onProgress) onProgress(100); const duration = Date.now() - startTime; return { success: true, blob, duration, }; } catch (error) { console.error('[ImageMagick] Conversion error:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown conversion error', duration: Date.now() - startTime, }; } } /** * Get ImageMagick format enum */ async function getMagickFormatEnum(format: string): Promise { const { MagickFormat } = await import('@imagemagick/magick-wasm'); const formatMap: Record = { png: MagickFormat.Png, jpg: MagickFormat.Jpg, jpeg: MagickFormat.Jpg, webp: MagickFormat.WebP, gif: MagickFormat.Gif, bmp: MagickFormat.Bmp, tiff: MagickFormat.Tiff, svg: MagickFormat.Svg, }; return formatMap[format.toLowerCase()] || MagickFormat.Png; } /** * Get MIME type for output format */ function getMimeType(format: string): string { const mimeTypes: Record = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif', bmp: 'image/bmp', tiff: 'image/tiff', svg: 'image/svg+xml', }; return mimeTypes[format.toLowerCase()] || 'application/octet-stream'; } /** * Resize image */ export async function resizeImage( file: File, width: number, height: number, outputFormat?: string, onProgress?: ProgressCallback ): Promise { const format = outputFormat || file.name.split('.').pop() || 'png'; return convertWithImageMagick( file, format, { imageWidth: width, imageHeight: height, }, onProgress ); } /** * Convert image to WebP */ export async function convertToWebP( file: File, quality: number = 85, onProgress?: ProgressCallback ): Promise { return convertWithImageMagick( file, 'webp', { imageQuality: quality, }, onProgress ); } /** * Batch convert images */ export async function batchConvertImages( files: File[], outputFormat: string, options: ConversionOptions = {}, onProgress?: (fileIndex: number, progress: number) => void ): Promise { const results: ConversionResult[] = []; for (let i = 0; i < files.length; i++) { const file = files[i]; const result = await convertWithImageMagick( file, outputFormat, options, (progress) => { if (onProgress) { onProgress(i, progress); } } ); results.push(result); } return results; }