From 0d6ec682940991fbb2ea61221718b1bca11ceced Mon Sep 17 00:00:00 2001 From: patrick Date: Wed, 13 May 2026 18:11:09 -0400 Subject: [PATCH] feat: add pipeline API and download utilities - processImageWithPipeline for full pipeline processing - ZIP archive creation with JSZip - File download helpers - Output filename generation Co-Authored-By: Claude --- src/lib/processing/pipeline.ts | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/lib/processing/pipeline.ts diff --git a/src/lib/processing/pipeline.ts b/src/lib/processing/pipeline.ts new file mode 100644 index 0000000..f61c9dc --- /dev/null +++ b/src/lib/processing/pipeline.ts @@ -0,0 +1,102 @@ +// 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 { + if (photonReady) return; + await initPhoton(); + photonReady = true; +} + +export async function processImage(file: File, device: Device): Promise { + 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 { + 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 { + 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'); +}