Previously, the ImageMagick service was just a placeholder that returned the input data unchanged. This caused PNG to WebP conversions to have the same file size. Changes: - Properly implement ImageMagick.read() with conversion logic - Apply imageQuality option to control compression - Apply imageWidth/imageHeight options with aspect ratio preservation - Use correct MagickFormat enum values for output formats - Fix write() method signature (format comes before callback) - Remove unnecessary initializeImageMagick() call Image conversion now properly applies: - Quality settings (1-100%) - Resolution/resize options - Format-specific compression This fixes the issue where quality settings had no effect on output file size. Users will now see proper file size reduction when using lower quality settings or converting to compressed formats like WebP. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
208 lines
4.9 KiB
TypeScript
208 lines
4.9 KiB
TypeScript
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<ConversionResult> {
|
|
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
|
|
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;
|
|
|
|
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 to output format
|
|
image.write(outputFormatEnum, (data) => {
|
|
result = data;
|
|
});
|
|
|
|
if (onProgress) onProgress(90);
|
|
});
|
|
|
|
// Create blob from result
|
|
const blob = new Blob([result! as BlobPart], { type: getMimeType(outputFormat) });
|
|
|
|
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<any> {
|
|
const { MagickFormat } = await import('@imagemagick/magick-wasm');
|
|
|
|
const formatMap: Record<string, any> = {
|
|
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<string, string> = {
|
|
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<ConversionResult> {
|
|
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<ConversionResult> {
|
|
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<ConversionResult[]> {
|
|
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;
|
|
}
|