import { onScopeDispose, ref, watch, type Ref } from 'vue' import sampleUrl from '@/assets/variant-sample.jpg' import { SAMPLE_SOURCE, type EstimateRequest } from '@/lib/variants/variantEstimate' import { outputSize } from '@/lib/variants/variantOutput' import { canRender, drawPlan, flattensTransparency, mimeFor } from '@/lib/variants/variantRender' const RENDER_DELAY_MS = 150 const PERCENT = 100 export interface RenderedVariant { url: string bytes: number } let sampleImage: Promise | null = null function loadSample() { sampleImage ??= new Promise((resolve) => { const image = new Image() image.onload = () => resolve(image) image.onerror = () => resolve(null) image.src = sampleUrl }) return sampleImage } function encode(canvas: HTMLCanvasElement, request: EstimateRequest) { const mime = mimeFor(request.format) const quality = mime === 'image/png' ? undefined : request.quality / PERCENT return new Promise((resolve) => canvas.toBlob(resolve, mime, quality)) } async function renderVariant(image: HTMLImageElement, request: EstimateRequest) { const size = outputSize(SAMPLE_SOURCE, request) if (size === null || !canRender(size)) { return null } const canvas = document.createElement('canvas') canvas.width = size.width canvas.height = size.height const context = canvas.getContext('2d') if (context === null) { return null } if (flattensTransparency(request.format)) { context.fillStyle = 'white' context.fillRect(0, 0, size.width, size.height) } const plan = drawPlan(SAMPLE_SOURCE, size, request.fit) context.drawImage(image, plan.sx, plan.sy, plan.sw, plan.sh, plan.dx, plan.dy, plan.dw, plan.dh) return encode(canvas, request) } export function useVariantRender(request: Ref) { const original = ref(null) const variant = ref(null) let timer: ReturnType | undefined let token = 0 function release() { if (variant.value) { URL.revokeObjectURL(variant.value.url) variant.value = null } } async function run(current: EstimateRequest | null, ticket: number) { const image = await loadSample() if (image === null || ticket !== token) { return } original.value = sampleUrl const blob = current === null ? null : await renderVariant(image, current) if (ticket !== token) { return } release() if (blob !== null) { variant.value = { url: URL.createObjectURL(blob), bytes: blob.size } } } function schedule(current: EstimateRequest | null) { clearTimeout(timer) const ticket = ++token timer = setTimeout(() => void run(current, ticket), RENDER_DELAY_MS) } watch(request, schedule, { immediate: true, deep: true }) onScopeDispose(() => { clearTimeout(timer) token++ release() }) return { original, variant } }