- processImageWithPipeline for full pipeline processing - ZIP archive creation with JSZip - File download helpers - Output filename generation Co-Authored-By: Claude <noreply@anthropic.com>
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
// High-level pipeline API for image processing
|
|
|
|
import type { Device, ImageEntry, PipelineConfig } from '$lib/types';
|
|
import { getExtension } from '$lib/types';
|
|
import JSZip from 'jszip';
|
|
import { initPhoton } from './photon';
|
|
import { workerPool } from './workerPool';
|
|
|
|
let photonReady = false;
|
|
|
|
export async function ensurePhotonReady(): Promise<void> {
|
|
if (photonReady) return;
|
|
await initPhoton();
|
|
photonReady = true;
|
|
}
|
|
|
|
export async function processImage(file: File, device: Device): Promise<Blob> {
|
|
const { blob } = await processImageWithPipeline(file, device, {
|
|
crop: { enabled: true, mode: 'center' },
|
|
resize: { enabled: true, mode: 'cover' },
|
|
brightness: { enabled: false, value: 0 },
|
|
contrast: { enabled: false, value: 0 },
|
|
gamma: { enabled: false, value: 1.0 },
|
|
autoLevels: { enabled: false, clipPercent: 0.5 },
|
|
greyscale: { enabled: true, method: 'luminosity' },
|
|
sharpen: { enabled: false, amount: 0 },
|
|
quantize: { enabled: true, levels: device.greyLevels },
|
|
dither: { enabled: false, algorithm: 'floyd-steinberg' }
|
|
});
|
|
return blob;
|
|
}
|
|
|
|
export async function processImageWithPreview(
|
|
file: File,
|
|
device: Device
|
|
): Promise<{ blob: Blob; dataUrl: string }> {
|
|
return processImageWithPipeline(file, device, {
|
|
crop: { enabled: true, mode: 'center' },
|
|
resize: { enabled: true, mode: 'cover' },
|
|
brightness: { enabled: false, value: 0 },
|
|
contrast: { enabled: false, value: 0 },
|
|
gamma: { enabled: false, value: 1.0 },
|
|
autoLevels: { enabled: false, clipPercent: 0.5 },
|
|
greyscale: { enabled: true, method: 'luminosity' },
|
|
sharpen: { enabled: false, amount: 0 },
|
|
quantize: { enabled: true, levels: device.greyLevels },
|
|
dither: { enabled: false, algorithm: 'floyd-steinberg' }
|
|
});
|
|
}
|
|
|
|
export async function processImageWithPipeline(
|
|
file: File,
|
|
device: Device,
|
|
config: PipelineConfig
|
|
): Promise<{ blob: Blob; dataUrl: string }> {
|
|
return workerPool.process(file, device, config);
|
|
}
|
|
|
|
export function getOutputFilename(originalFilename: string, device: Device): string {
|
|
const lastDot = originalFilename.lastIndexOf('.');
|
|
const stem = lastDot > 0 ? originalFilename.substring(0, lastDot) : originalFilename;
|
|
const ext = getExtension(device.outputFormat);
|
|
return `${stem}_${device.id}.${ext}`;
|
|
}
|
|
|
|
export async function createZipArchive(images: ImageEntry[], device: Device): Promise<Blob> {
|
|
const zip = new JSZip();
|
|
|
|
for (const image of images) {
|
|
if (image.processedBlob) {
|
|
const filename = getOutputFilename(image.filename, device);
|
|
zip.file(filename, image.processedBlob);
|
|
}
|
|
}
|
|
|
|
return zip.generateAsync({ type: 'blob' });
|
|
}
|
|
|
|
export function downloadFile(blob: Blob, filename: string): void {
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
export function downloadImage(image: ImageEntry, device: Device): void {
|
|
if (!image.processedBlob) return;
|
|
const filename = getOutputFilename(image.filename, device);
|
|
downloadFile(image.processedBlob, filename);
|
|
}
|
|
|
|
export async function downloadAllAsZip(images: ImageEntry[], device: Device): Promise<void> {
|
|
const completedImages = images.filter((img) => img.status === 'complete' && img.processedBlob);
|
|
if (completedImages.length === 0) return;
|
|
|
|
const zipBlob = await createZipArchive(completedImages, device);
|
|
downloadFile(zipBlob, '2eink_covers.zip');
|
|
}
|