{"version":3,"file":"image-preview-worker.d.ts","sourceRoot":"","sources":["../../src/server/image-preview-worker.ts"],"names":[],"mappings":"AAiGA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG;IACxD,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,WAAW,GAAG,YAAY,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf,CAoCA","sourcesContent":["import { parentPort } from \"node:worker_threads\";\nimport * as photon from \"@silvia-odwyer/photon-node\";\nimport { MAX_PREVIEW_BYTES, MAX_PREVIEW_HEIGHT, MAX_PREVIEW_WIDTH } from \"./image-preview.js\";\n\ntype PhotonImage = ReturnType<typeof photon.PhotonImage.new_from_byteslice>;\n\ninterface PreviewRequest {\n\tid: number;\n\tbytes: ArrayBuffer;\n\tmimeType: string;\n}\n\nfunction readExifOrientation(bytes: Uint8Array): number {\n\tlet tiff = -1;\n\tif (bytes.length >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8) {\n\t\tlet offset = 2;\n\t\twhile (offset + 4 <= bytes.length && bytes[offset] === 0xff) {\n\t\t\tconst marker = bytes[offset + 1];\n\t\t\tconst length = (bytes[offset + 2] << 8) | bytes[offset + 3];\n\t\t\tif (marker === 0xe1 && offset + 10 <= bytes.length) {\n\t\t\t\tconst start = offset + 4;\n\t\t\t\tif (String.fromCharCode(...bytes.slice(start, start + 6)) === \"Exif\\0\\0\") tiff = start + 6;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (length < 2) break;\n\t\t\toffset += 2 + length;\n\t\t}\n\t} else if (\n\t\tbytes.length >= 12 &&\n\t\tString.fromCharCode(...bytes.slice(0, 4)) === \"RIFF\" &&\n\t\tString.fromCharCode(...bytes.slice(8, 12)) === \"WEBP\"\n\t) {\n\t\tlet offset = 12;\n\t\twhile (offset + 8 <= bytes.length) {\n\t\t\tconst chunk = String.fromCharCode(...bytes.slice(offset, offset + 4));\n\t\t\tconst size =\n\t\t\t\tbytes[offset + 4] | (bytes[offset + 5] << 8) | (bytes[offset + 6] << 16) | (bytes[offset + 7] << 24);\n\t\t\tconst start = offset + 8;\n\t\t\tif (chunk === \"EXIF\") {\n\t\t\t\ttiff = String.fromCharCode(...bytes.slice(start, start + 6)) === \"Exif\\0\\0\" ? start + 6 : start;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\toffset = start + size + (size % 2);\n\t\t}\n\t}\n\tif (tiff < 0 || tiff + 8 > bytes.length) return 1;\n\tconst little = bytes[tiff] === 0x49 && bytes[tiff + 1] === 0x49;\n\tconst read16 = (at: number) => (little ? bytes[at] | (bytes[at + 1] << 8) : (bytes[at] << 8) | bytes[at + 1]);\n\tconst read32 = (at: number) =>\n\t\tlittle\n\t\t\t? (bytes[at] | (bytes[at + 1] << 8) | (bytes[at + 2] << 16) | (bytes[at + 3] << 24)) >>> 0\n\t\t\t: ((bytes[at] << 24) | (bytes[at + 1] << 16) | (bytes[at + 2] << 8) | bytes[at + 3]) >>> 0;\n\tconst ifd = tiff + read32(tiff + 4);\n\tif (ifd + 2 > bytes.length) return 1;\n\tconst count = read16(ifd);\n\tfor (let index = 0; index < count; index++) {\n\t\tconst entry = ifd + 2 + index * 12;\n\t\tif (entry + 12 > bytes.length) break;\n\t\tif (read16(entry) === 0x0112) {\n\t\t\tconst value = read16(entry + 8);\n\t\t\treturn value >= 1 && value <= 8 ? value : 1;\n\t\t}\n\t}\n\treturn 1;\n}\n\nfunction rotate(image: PhotonImage, clockwise: boolean): PhotonImage {\n\tconst width = image.get_width();\n\tconst height = image.get_height();\n\tconst source = image.get_raw_pixels();\n\tconst target = new Uint8Array(source.length);\n\tfor (let y = 0; y < height; y++) {\n\t\tfor (let x = 0; x < width; x++) {\n\t\t\tconst sourceIndex = (y * width + x) * 4;\n\t\t\tconst destinationPixel = clockwise ? x * height + (height - 1 - y) : (width - 1 - x) * height + y;\n\t\t\ttarget.set(source.slice(sourceIndex, sourceIndex + 4), destinationPixel * 4);\n\t\t}\n\t}\n\treturn new photon.PhotonImage(target, height, width);\n}\n\nfunction orient(image: PhotonImage, bytes: Uint8Array): PhotonImage {\n\tconst orientation = readExifOrientation(bytes);\n\tif (orientation === 2) photon.fliph(image);\n\telse if (orientation === 3) {\n\t\tphoton.fliph(image);\n\t\tphoton.flipv(image);\n\t} else if (orientation === 4) photon.flipv(image);\n\telse if (orientation >= 5) {\n\t\tconst rotated = rotate(image, orientation === 5 || orientation === 6);\n\t\timage.free();\n\t\timage = rotated;\n\t\tif (orientation === 5 || orientation === 7) photon.fliph(image);\n\t}\n\treturn image;\n}\n\nexport function generateImagePreview(bytes: Uint8Array): {\n\tbytes: Uint8Array;\n\tmimeType: \"image/png\" | \"image/jpeg\";\n\twidth: number;\n\theight: number;\n} {\n\tlet image = photon.PhotonImage.new_from_byteslice(bytes);\n\ttry {\n\t\timage = orient(image, bytes);\n\t\tconst originalWidth = image.get_width();\n\t\tconst originalHeight = image.get_height();\n\t\tif (originalWidth < 1 || originalHeight < 1) throw new Error(\"Image has invalid dimensions\");\n\t\tconst scale = Math.min(1, MAX_PREVIEW_WIDTH / originalWidth, MAX_PREVIEW_HEIGHT / originalHeight);\n\t\tlet width = Math.max(1, Math.round(originalWidth * scale));\n\t\tlet height = Math.max(1, Math.round(originalHeight * scale));\n\t\twhile (true) {\n\t\t\tconst resized = photon.resize(image, width, height, photon.SamplingFilter.Lanczos3);\n\t\t\ttry {\n\t\t\t\tconst candidates: Array<{ bytes: Uint8Array; mimeType: \"image/png\" | \"image/jpeg\" }> = [\n\t\t\t\t\t{ bytes: resized.get_bytes(), mimeType: \"image/png\" },\n\t\t\t\t\t...([85, 70, 55, 40] as const).map((quality) => ({\n\t\t\t\t\t\tbytes: resized.get_bytes_jpeg(quality),\n\t\t\t\t\t\tmimeType: \"image/jpeg\" as const,\n\t\t\t\t\t})),\n\t\t\t\t];\n\t\t\t\tconst acceptable = candidates.filter((candidate) => candidate.bytes.byteLength <= MAX_PREVIEW_BYTES);\n\t\t\t\tif (acceptable.length > 0) {\n\t\t\t\t\tacceptable.sort((left, right) => left.bytes.byteLength - right.bytes.byteLength);\n\t\t\t\t\treturn { ...acceptable[0]!, width, height };\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tresized.free();\n\t\t\t}\n\t\t\tif (width === 1 && height === 1) break;\n\t\t\twidth = Math.max(1, Math.floor(width * 0.75));\n\t\t\theight = Math.max(1, Math.floor(height * 0.75));\n\t\t}\n\t\tthrow new Error(\"Could not encode image within the 256 KiB preview limit\");\n\t} finally {\n\t\timage.free();\n\t}\n}\n\nconst port = parentPort;\nif (port) {\n\tport.on(\"message\", (request: PreviewRequest) => {\n\t\ttry {\n\t\t\tconst preview = generateImagePreview(new Uint8Array(request.bytes));\n\t\t\tconst transferable = Uint8Array.from(preview.bytes);\n\t\t\tport.postMessage({ id: request.id, ok: true, ...preview, bytes: transferable.buffer }, [transferable.buffer]);\n\t\t} catch (error) {\n\t\t\tport.postMessage({\n\t\t\t\tid: request.id,\n\t\t\t\tok: false,\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t});\n}\n"]}