feat: Initialize k-convert application with core structure, UI components, and conversion logic.
This commit is contained in:
113
src/adapters/heic-adapter.ts
Normal file
113
src/adapters/heic-adapter.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
// =============================================================================
|
||||
// HEIC Adapter
|
||||
// Converts HEIC/HEIF images to any image format
|
||||
// Uses heic2any for HEIC→JPEG/PNG, then ImageMagick worker for further conversion
|
||||
// =============================================================================
|
||||
|
||||
import * as Comlink from 'comlink';
|
||||
import heic2any from 'heic2any';
|
||||
import type {
|
||||
IFileConverter,
|
||||
SupportedFormat,
|
||||
ConversionRequest,
|
||||
ImageFormat,
|
||||
} from '../core/types';
|
||||
import type { ImageConverterWorker } from '../workers/image.worker';
|
||||
|
||||
// Formats heic2any can directly output
|
||||
const HEIC2ANY_OUTPUTS: ImageFormat[] = ['jpeg', 'png'];
|
||||
|
||||
// All image formats we support as final output
|
||||
const ALL_IMAGE_OUTPUTS: ImageFormat[] = ['jpeg', 'png', 'webp', 'gif', 'bmp', 'tiff', 'ico'];
|
||||
|
||||
type WorkerType = Comlink.Remote<ImageConverterWorker>;
|
||||
let workerInstance: WorkerType | null = null;
|
||||
|
||||
function getWorker(): WorkerType {
|
||||
if (!workerInstance) {
|
||||
const worker = new Worker(
|
||||
new URL('../workers/image.worker.ts', import.meta.url),
|
||||
{ type: 'module' }
|
||||
);
|
||||
workerInstance = Comlink.wrap<ImageConverterWorker>(worker);
|
||||
}
|
||||
return workerInstance;
|
||||
}
|
||||
|
||||
export class HeicAdapter implements IFileConverter {
|
||||
readonly name = 'heic-adapter';
|
||||
|
||||
supports(inputFormat: SupportedFormat, outputFormat: SupportedFormat): boolean {
|
||||
return (
|
||||
inputFormat === 'heic' &&
|
||||
ALL_IMAGE_OUTPUTS.includes(outputFormat as ImageFormat)
|
||||
);
|
||||
}
|
||||
|
||||
async convert(
|
||||
request: ConversionRequest,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<Blob> {
|
||||
const { file, targetFormat, options } = request;
|
||||
const quality = options?.quality ?? 85;
|
||||
|
||||
onProgress?.(5);
|
||||
|
||||
try {
|
||||
// Step 1: Convert HEIC to intermediate format using heic2any (main thread)
|
||||
// Use JPEG as intermediate for efficiency, PNG if target is PNG
|
||||
const intermediateFormat = targetFormat === 'png' ? 'png' : 'jpeg';
|
||||
const toType = intermediateFormat === 'png' ? 'image/png' : 'image/jpeg';
|
||||
|
||||
let intermediateBlob: Blob;
|
||||
try {
|
||||
const result = await heic2any({
|
||||
blob: file,
|
||||
toType,
|
||||
quality: quality / 100,
|
||||
});
|
||||
intermediateBlob = Array.isArray(result) ? result[0] : result;
|
||||
} catch (heicError) {
|
||||
// heic2any failed - provide helpful error message
|
||||
const errorMessage = heicError instanceof Error ? heicError.message : String(heicError);
|
||||
if (errorMessage.includes('LIBHEIF') || errorMessage.includes('format not supported')) {
|
||||
throw new Error(`This HEIC file format is not supported. Try using a different photo or convert it on your iPhone first.`);
|
||||
}
|
||||
throw new Error(`Failed to decode HEIC: ${JSON.stringify(errorMessage)}`);
|
||||
}
|
||||
|
||||
onProgress?.(50);
|
||||
|
||||
// Step 2: If target is JPEG or PNG, we're done
|
||||
if (HEIC2ANY_OUTPUTS.includes(targetFormat as ImageFormat)) {
|
||||
onProgress?.(100);
|
||||
return intermediateBlob;
|
||||
}
|
||||
|
||||
// Step 3: Convert intermediate to final format using ImageMagick worker
|
||||
const arrayBuffer = await intermediateBlob.arrayBuffer();
|
||||
onProgress?.(70);
|
||||
|
||||
const worker = getWorker();
|
||||
const result = await worker.convertImage(
|
||||
arrayBuffer,
|
||||
intermediateFormat,
|
||||
targetFormat as ImageFormat,
|
||||
quality
|
||||
);
|
||||
|
||||
onProgress?.(100);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
// Re-throw with context
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`HEIC conversion failed: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
export const heicAdapter = new HeicAdapter();
|
||||
Reference in New Issue
Block a user