@su-engineering/heic 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +23 -3
- package/dist/heic.global.js +1 -1
- package/dist/heic.global.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/{types-Bv9KPnri.d.ts → types-nCqKJJpp.d.ts} +11 -1
- package/dist/wasm.d.ts +2 -2
- package/dist/wasm.js +52 -44
- package/dist/wasm.js.map +1 -1
- package/docs/api.md +9 -1
- package/docs/benchmarks.md +66 -0
- package/package.json +10 -5
|
@@ -71,6 +71,16 @@ interface DecodedImage {
|
|
|
71
71
|
/** Unsupported features that were detected and skipped. Usually empty. */
|
|
72
72
|
warnings: HeicWarning[];
|
|
73
73
|
}
|
|
74
|
+
interface ConvertOptions extends DecodeOptions {
|
|
75
|
+
/** Output format. Default 'image/jpeg'. */
|
|
76
|
+
type?: 'image/jpeg' | 'image/png';
|
|
77
|
+
/** JPEG encoder quality, from 0 to 1. Default 0.92; ignored for PNG. */
|
|
78
|
+
quality?: number;
|
|
79
|
+
}
|
|
80
|
+
/** Encoded pixels and decode metadata; no bitmap needs to be closed by the caller. */
|
|
81
|
+
interface ConvertedImage extends Omit<DecodedImage, 'image'> {
|
|
82
|
+
blob: Blob;
|
|
83
|
+
}
|
|
74
84
|
interface SupportReport {
|
|
75
85
|
/** `createImageBitmap` decodes HEIC directly (Safari, some Chrome builds). */
|
|
76
86
|
native: boolean;
|
|
@@ -109,4 +119,4 @@ interface DecoderAdapter {
|
|
|
109
119
|
decode(request: AdapterRequest): Promise<AdapterResult>;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
export type { AdapterRequest as A, DecodeOptions as D, HeicWarning as H, OutputColorSpace as O, SourceColor as S, TransformsApplied as T, SupportReport as a,
|
|
122
|
+
export type { AdapterRequest as A, ConvertOptions as C, DecodeOptions as D, HeicWarning as H, OutputColorSpace as O, SourceColor as S, TransformsApplied as T, SupportReport as a, ConvertedImage as b, DecodedImage as c, DecoderAdapter as d, AdapterResult as e, Strategy as f };
|
package/dist/wasm.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as AdapterRequest,
|
|
1
|
+
import { d as DecoderAdapter } from './types-nCqKJJpp.js';
|
|
2
|
+
export { A as AdapterRequest, e as AdapterResult } from './types-nCqKJJpp.js';
|
|
3
3
|
|
|
4
4
|
interface WasmAdapterOptions {
|
|
5
5
|
/**
|
package/dist/wasm.js
CHANGED
|
@@ -17,54 +17,62 @@ function createWasmAdapter(options = {}) {
|
|
|
17
17
|
const libheif = await modulePromise;
|
|
18
18
|
if (request.signal?.aborted) throw new HeicAbortError();
|
|
19
19
|
const decoder = new libheif.HeifDecoder();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
throw new HeicDecodeError("Could not get a 2d context for libheif output", {
|
|
42
|
-
strategy: "wasm"
|
|
20
|
+
let images = [];
|
|
21
|
+
try {
|
|
22
|
+
images = decoder.decode(request.data);
|
|
23
|
+
if (!images || images.length === 0) {
|
|
24
|
+
throw new HeicDecodeError("libheif returned no images", { strategy: "wasm" });
|
|
25
|
+
}
|
|
26
|
+
const image = images[0];
|
|
27
|
+
const width = image.get_width();
|
|
28
|
+
const height = image.get_height();
|
|
29
|
+
if (width <= 0 || height <= 0) {
|
|
30
|
+
throw new HeicDecodeError(`libheif reported ${width}x${height}`, { strategy: "wasm" });
|
|
31
|
+
}
|
|
32
|
+
if (typeof OffscreenCanvas === "undefined") {
|
|
33
|
+
throw new HeicDecodeError("OffscreenCanvas is required to receive libheif output", {
|
|
34
|
+
strategy: "wasm"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
38
|
+
const ctx = canvas.getContext("2d", {
|
|
39
|
+
colorSpace: request.colorSpace,
|
|
40
|
+
alpha: false
|
|
43
41
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
image.display(imageData, (result) => {
|
|
49
|
-
if (!result) reject(new HeicDecodeError("libheif failed to render", { strategy: "wasm" }));
|
|
50
|
-
else resolve();
|
|
42
|
+
if (!ctx) {
|
|
43
|
+
throw new HeicDecodeError("Could not get a 2d context for libheif output", {
|
|
44
|
+
strategy: "wasm"
|
|
51
45
|
});
|
|
52
|
-
} catch (error) {
|
|
53
|
-
reject(
|
|
54
|
-
new HeicDecodeError(`libheif threw while rendering: ${String(error)}`, {
|
|
55
|
-
strategy: "wasm"
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
46
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
const imageData = ctx.createImageData(width, height, { colorSpace: request.colorSpace });
|
|
48
|
+
await new Promise((resolve, reject) => {
|
|
49
|
+
try {
|
|
50
|
+
image.display(imageData, (result) => {
|
|
51
|
+
if (!result) reject(new HeicDecodeError("libheif failed to render", { strategy: "wasm" }));
|
|
52
|
+
else resolve();
|
|
53
|
+
});
|
|
54
|
+
} catch (error) {
|
|
55
|
+
reject(
|
|
56
|
+
new HeicDecodeError(`libheif threw while rendering: ${String(error)}`, {
|
|
57
|
+
strategy: "wasm"
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
ctx.putImageData(imageData, 0, 0);
|
|
63
|
+
if (request.signal?.aborted) {
|
|
64
|
+
canvas.width = 0;
|
|
65
|
+
canvas.height = 0;
|
|
66
|
+
throw new HeicAbortError();
|
|
67
|
+
}
|
|
68
|
+
return { image: canvas, width, height };
|
|
69
|
+
} finally {
|
|
70
|
+
for (const image of images ?? []) image.free?.();
|
|
71
|
+
if (decoder.decoder && libheif.heif_context_free) {
|
|
72
|
+
libheif.heif_context_free(decoder.decoder);
|
|
73
|
+
decoder.decoder = null;
|
|
74
|
+
}
|
|
66
75
|
}
|
|
67
|
-
return { image: canvas, width, height };
|
|
68
76
|
}
|
|
69
77
|
};
|
|
70
78
|
}
|
package/dist/wasm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../wasm/index.ts"],"names":[],"mappings":";;;AAkDO,SAAS,iBAAA,CAAkB,OAAA,GAA8B,EAAC,EAAmB;AAClF,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,WAAA,KAAgB,MAAwB,OAAO,2BAA2B,CAAA,CAAA;AAE/F,EAAA,IAAI,aAAA;AAEJ,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMN,iBAAA,EAAmB,IAAA;AAAA,IAEnB,MAAM,OAAO,OAAA,EAAiD;AAC5D,MAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,MAAM,IAAI,cAAA,EAAe;AAEtD,MAAA,aAAA,KAAkB,QAAQ,OAAA,CAAQ,IAAA,EAAM,CAAA,CAAE,KAAK,gBAAgB,CAAA;AAC/D,MAAA,MAAM,UAAU,MAAM,aAAA;AACtB,MAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,MAAM,IAAI,cAAA,EAAe;AAEtD,MAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAQ,WAAA,EAAY;AACxC,MAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA;AAC1C,MAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG;AAClC,QAAA,MAAM,IAAI,eAAA,CAAgB,4BAAA,EAA8B,EAAE,QAAA,EAAU,QAAQ,CAAA;AAAA,MAC9E;AAIA,MAAA,MAAM,KAAA,GAAQ,OAAO,CAAC,CAAA;AACtB,MAAA,MAAM,KAAA,GAAQ,MAAM,SAAA,EAAU;AAC9B,MAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,MAAA,IAAI,KAAA,IAAS,CAAA,IAAK,MAAA,IAAU,CAAA,EAAG;AAC7B,QAAA,MAAM,IAAI,eAAA,CAAgB,CAAA,iBAAA,EAAoB,KAAK,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,EAAI,EAAE,QAAA,EAAU,MAAA,EAAQ,CAAA;AAAA,MACvF;AAEA,MAAA,IAAI,OAAO,oBAAoB,WAAA,EAAa;AAC1C,QAAA,MAAM,IAAI,gBAAgB,uDAAA,EAAyD;AAAA,UACjF,QAAA,EAAU;AAAA,SACX,CAAA;AAAA,MACH;AACA,MAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AAChD,MAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAA,EAAM;AAAA,QAClC,YAAY,OAAA,CAAQ,UAAA;AAAA,QACpB,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA,IAAI,CAAC,GAAA,EAAK;AACR,QAAA,MAAM,IAAI,gBAAgB,+CAAA,EAAiD;AAAA,UACzE,QAAA,EAAU;AAAA,SACX,CAAA;AAAA,MACH;AAEA,MAAA,MAAM,SAAA,GAAY,IAAI,eAAA,CAAgB,KAAA,EAAO,QAAQ,EAAE,UAAA,EAAY,OAAA,CAAQ,UAAA,EAAY,CAAA;AACvF,MAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3C,QAAA,IAAI;AACF,UAAA,KAAA,CAAM,OAAA,CAAQ,SAAA,EAAW,CAAC,MAAA,KAAW;AACnC,YAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,CAAO,IAAI,eAAA,CAAgB,4BAA4B,EAAE,QAAA,EAAU,MAAA,EAAQ,CAAC,CAAA;AAAA,iBACpF,OAAA,EAAQ;AAAA,UACf,CAAC,CAAA;AAAA,QACH,SAAS,KAAA,EAAO;AACd,UAAA,MAAA;AAAA,YACE,IAAI,eAAA,CAAgB,CAAA,+BAAA,EAAkC,MAAA,CAAO,KAAK,CAAC,CAAA,CAAA,EAAI;AAAA,cACrE,QAAA,EAAU;AAAA,aACX;AAAA,WACH;AAAA,QACF;AAAA,MACF,CAAC,CAAA;AAED,MAAA,KAAA,CAAM,IAAA,IAAO;AACb,MAAA,GAAA,CAAI,YAAA,CAAa,SAAA,EAAW,CAAA,EAAG,CAAC,CAAA;AAEhC,MAAA,IAAI,OAAA,CAAQ,QAAQ,OAAA,EAAS;AAC3B,QAAA,MAAA,CAAO,KAAA,GAAQ,CAAA;AACf,QAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,QAAA,MAAM,IAAI,cAAA,EAAe;AAAA,MAC3B;AAEA,MAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAO;AAAA,IACxC;AAAA,GACF;AACF;AASA,eAAe,iBAAiB,QAAA,EAA2C;AACzE,EAAA,IAAI,SAAA,GAAY,QAAA;AAEhB,EAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,CAAA,EAAG,KAAA,EAAA,EAAS;AACtC,IAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG,OAAO,SAAA;AACvC,IAAA,IAAI,OAAO,cAAc,UAAA,EAAY;AACnC,MAAA,SAAA,GAAY,MAAO,SAAA,EAA4B;AAC/C,MAAA;AAAA,IACF;AACA,IAAA,IAAI,SAAA,IAAa,OAAO,SAAA,KAAc,QAAA,IAAY,aAAa,SAAA,EAAW;AACxE,MAAA,SAAA,GAAa,SAAA,CAAmC,OAAA;AAChD,MAAA;AAAA,IACF;AACA,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,IAAI,eAAA;AAAA,IACR,uEAAA;AAAA,IACA,EAAE,UAAU,MAAA;AAAO,GACrB;AACF;AAEA,SAAS,gBAAgB,KAAA,EAAwC;AAC/D,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,UAAU,IAAA,IACV,OAAQ,MAAoC,WAAA,KAAgB,UAAA;AAEhE;AAYO,IAAM,cAA8B,iBAAA","file":"wasm.js","sourcesContent":["/**\n * libheif-wasm fallback adapter.\n *\n * A **separate entry point** (`@su-engineering/heic/wasm`) so that bundlers\n * never pull roughly 1.2 MB of codec into the main chunk. Importing this module\n * is still cheap: the wasm itself is only instantiated on the first decode.\n *\n * The core package builds and passes its non-wasm tests with `libheif-js`\n * absent, which is why it is an optional peer dependency rather than a\n * dependency.\n */\nimport { HeicAbortError, HeicDecodeError } from '../src/errors.ts';\nimport type { AdapterRequest, AdapterResult, DecoderAdapter } from '../src/types.ts';\n\n/** The slice of libheif-js's API we use. Declared here so the package can be absent. */\ninterface LibheifImage {\n get_width(): number;\n get_height(): number;\n display(image: ImageData, callback: (result: ImageData | null) => void): void;\n free?(): void;\n}\ninterface LibheifDecoder {\n decode(buffer: Uint8Array | ArrayBuffer): LibheifImage[];\n}\ninterface LibheifModule {\n HeifDecoder: new () => LibheifDecoder;\n}\n\nexport interface WasmAdapterOptions {\n /**\n * Supplies the libheif module. Defaults to `import('libheif-js/wasm-bundle.js')`.\n *\n * Override it to pin a specific build, to serve the wasm from your own origin,\n * or to reuse an instance you already loaded. In a browser without a bundler,\n * this is required, because `libheif-js` is a bare specifier:\n *\n * ```ts\n * createWasmAdapter({\n * loadLibheif: () => import('/vendor/libheif-bundle.mjs'),\n * });\n * ```\n */\n loadLibheif?: () => Promise<unknown>;\n}\n\n/**\n * Builds a libheif-backed adapter.\n *\n * The module is loaded once, on first decode, and reused.\n */\nexport function createWasmAdapter(options: WasmAdapterOptions = {}): DecoderAdapter {\n const load = options.loadLibheif ?? ((): Promise<unknown> => import('libheif-js/wasm-bundle.js'));\n\n let modulePromise: Promise<LibheifModule> | undefined;\n\n return {\n name: 'libheif-wasm',\n\n // libheif applies irot / imir / clap itself: heif_decode_image honours the\n // transformative properties unless ignore_transformations is set, and\n // libheif-js does not expose that option. Declaring it here is what stops the\n // pipeline applying them a second time and rotating the image twice.\n appliesTransforms: true,\n\n async decode(request: AdapterRequest): Promise<AdapterResult> {\n if (request.signal?.aborted) throw new HeicAbortError();\n\n modulePromise ??= Promise.resolve(load()).then(normalizeLibheif);\n const libheif = await modulePromise;\n if (request.signal?.aborted) throw new HeicAbortError();\n\n const decoder = new libheif.HeifDecoder();\n const images = decoder.decode(request.data);\n if (!images || images.length === 0) {\n throw new HeicDecodeError('libheif returned no images', { strategy: 'wasm' });\n }\n\n // Index 0 is the primary image; any others are aux or sequence entries,\n // which v0.1 does not decode.\n const image = images[0]!;\n const width = image.get_width();\n const height = image.get_height();\n if (width <= 0 || height <= 0) {\n throw new HeicDecodeError(`libheif reported ${width}x${height}`, { strategy: 'wasm' });\n }\n\n if (typeof OffscreenCanvas === 'undefined') {\n throw new HeicDecodeError('OffscreenCanvas is required to receive libheif output', {\n strategy: 'wasm',\n });\n }\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d', {\n colorSpace: request.colorSpace,\n alpha: false,\n });\n if (!ctx) {\n throw new HeicDecodeError('Could not get a 2d context for libheif output', {\n strategy: 'wasm',\n });\n }\n\n const imageData = ctx.createImageData(width, height, { colorSpace: request.colorSpace });\n await new Promise<void>((resolve, reject) => {\n try {\n image.display(imageData, (result) => {\n if (!result) reject(new HeicDecodeError('libheif failed to render', { strategy: 'wasm' }));\n else resolve();\n });\n } catch (error) {\n reject(\n new HeicDecodeError(`libheif threw while rendering: ${String(error)}`, {\n strategy: 'wasm',\n }),\n );\n }\n });\n\n image.free?.();\n ctx.putImageData(imageData, 0, 0);\n\n if (request.signal?.aborted) {\n canvas.width = 0;\n canvas.height = 0;\n throw new HeicAbortError();\n }\n\n return { image: canvas, width, height };\n },\n };\n}\n\n/**\n * libheif-js is published in three shapes and bundlers add a fourth wrapper:\n * the CommonJS build exposes `HeifDecoder` directly, the wasm builds default-\n * export an emscripten *factory* that must be called, and interop may nest\n * either under `.default`. Normalising here means callers can hand us whatever\n * their toolchain produced without knowing which one it is.\n */\nasync function normalizeLibheif(imported: unknown): Promise<LibheifModule> {\n let candidate = imported;\n\n for (let depth = 0; depth < 4; depth++) {\n if (isLibheifModule(candidate)) return candidate;\n if (typeof candidate === 'function') {\n candidate = await (candidate as () => unknown)();\n continue;\n }\n if (candidate && typeof candidate === 'object' && 'default' in candidate) {\n candidate = (candidate as { default: unknown }).default;\n continue;\n }\n break;\n }\n\n throw new HeicDecodeError(\n 'The module supplied to the wasm adapter does not expose a HeifDecoder',\n { strategy: 'wasm' },\n );\n}\n\nfunction isLibheifModule(value: unknown): value is LibheifModule {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { HeifDecoder?: unknown }).HeifDecoder === 'function'\n );\n}\n\n/**\n * A ready-made adapter using the default `libheif-js` build.\n *\n * ```ts\n * import { decodeHeic } from '@su-engineering/heic';\n * import { wasmDecoder } from '@su-engineering/heic/wasm';\n *\n * await decodeHeic(file, { wasmLoader: async () => wasmDecoder });\n * ```\n */\nexport const wasmDecoder: DecoderAdapter = createWasmAdapter();\n\nexport type { DecoderAdapter, AdapterRequest, AdapterResult } from '../src/types.ts';\n"]}
|
|
1
|
+
{"version":3,"sources":["../wasm/index.ts"],"names":[],"mappings":"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","file":"wasm.js","sourcesContent":["/**\n * libheif-wasm fallback adapter.\n *\n * A **separate entry point** (`@su-engineering/heic/wasm`) so that bundlers\n * never pull roughly 1.2 MB of codec into the main chunk. Importing this module\n * is still cheap: the wasm itself is only instantiated on the first decode.\n *\n * The core package builds and passes its non-wasm tests with `libheif-js`\n * absent, which is why it is an optional peer dependency rather than a\n * dependency.\n */\nimport { HeicAbortError, HeicDecodeError } from '../src/errors.ts';\nimport type { AdapterRequest, AdapterResult, DecoderAdapter } from '../src/types.ts';\n\n/** The slice of libheif-js's API we use. Declared here so the package can be absent. */\ninterface LibheifImage {\n get_width(): number;\n get_height(): number;\n display(image: ImageData, callback: (result: ImageData | null) => void): void;\n free?(): void;\n}\ninterface LibheifDecoder {\n /** Context allocated by libheif-js's decode method. */\n decoder?: number | null;\n decode(buffer: Uint8Array | ArrayBuffer): LibheifImage[];\n}\ninterface LibheifModule {\n HeifDecoder: new () => LibheifDecoder;\n heif_context_free?: (context: number) => void;\n}\n\nexport interface WasmAdapterOptions {\n /**\n * Supplies the libheif module. Defaults to `import('libheif-js/wasm-bundle.js')`.\n *\n * Override it to pin a specific build, to serve the wasm from your own origin,\n * or to reuse an instance you already loaded. In a browser without a bundler,\n * this is required, because `libheif-js` is a bare specifier:\n *\n * ```ts\n * createWasmAdapter({\n * loadLibheif: () => import('/vendor/libheif-bundle.mjs'),\n * });\n * ```\n */\n loadLibheif?: () => Promise<unknown>;\n}\n\n/**\n * Builds a libheif-backed adapter.\n *\n * The module is loaded once, on first decode, and reused.\n */\nexport function createWasmAdapter(options: WasmAdapterOptions = {}): DecoderAdapter {\n const load = options.loadLibheif ?? ((): Promise<unknown> => import('libheif-js/wasm-bundle.js'));\n\n let modulePromise: Promise<LibheifModule> | undefined;\n\n return {\n name: 'libheif-wasm',\n\n // libheif applies irot / imir / clap itself: heif_decode_image honours the\n // transformative properties unless ignore_transformations is set, and\n // libheif-js does not expose that option. Declaring it here is what stops the\n // pipeline applying them a second time and rotating the image twice.\n appliesTransforms: true,\n\n async decode(request: AdapterRequest): Promise<AdapterResult> {\n if (request.signal?.aborted) throw new HeicAbortError();\n\n modulePromise ??= Promise.resolve(load()).then(normalizeLibheif);\n const libheif = await modulePromise;\n if (request.signal?.aborted) throw new HeicAbortError();\n\n const decoder = new libheif.HeifDecoder();\n let images: LibheifImage[] = [];\n try {\n images = decoder.decode(request.data);\n if (!images || images.length === 0) {\n throw new HeicDecodeError('libheif returned no images', { strategy: 'wasm' });\n }\n\n // Index 0 is the primary image; any others are aux or sequence entries,\n // which v0.1 does not decode.\n const image = images[0]!;\n const width = image.get_width();\n const height = image.get_height();\n if (width <= 0 || height <= 0) {\n throw new HeicDecodeError(`libheif reported ${width}x${height}`, { strategy: 'wasm' });\n }\n\n if (typeof OffscreenCanvas === 'undefined') {\n throw new HeicDecodeError('OffscreenCanvas is required to receive libheif output', {\n strategy: 'wasm',\n });\n }\n const canvas = new OffscreenCanvas(width, height);\n const ctx = canvas.getContext('2d', {\n colorSpace: request.colorSpace,\n alpha: false,\n });\n if (!ctx) {\n throw new HeicDecodeError('Could not get a 2d context for libheif output', {\n strategy: 'wasm',\n });\n }\n\n const imageData = ctx.createImageData(width, height, { colorSpace: request.colorSpace });\n await new Promise<void>((resolve, reject) => {\n try {\n image.display(imageData, (result) => {\n if (!result) reject(new HeicDecodeError('libheif failed to render', { strategy: 'wasm' }));\n else resolve();\n });\n } catch (error) {\n reject(\n new HeicDecodeError(`libheif threw while rendering: ${String(error)}`, {\n strategy: 'wasm',\n }),\n );\n }\n });\n\n ctx.putImageData(imageData, 0, 0);\n\n if (request.signal?.aborted) {\n canvas.width = 0;\n canvas.height = 0;\n throw new HeicAbortError();\n }\n\n return { image: canvas, width, height };\n } finally {\n // Each decode allocates a context. libheif-js only frees it on the next\n // decode on the same instance; this adapter creates a fresh instance.\n // Release every returned handle before its owning context, even on error.\n for (const image of images ?? []) image.free?.();\n if (decoder.decoder && libheif.heif_context_free) {\n libheif.heif_context_free(decoder.decoder);\n decoder.decoder = null;\n }\n }\n },\n };\n}\n\n/**\n * libheif-js is published in three shapes and bundlers add a fourth wrapper:\n * the CommonJS build exposes `HeifDecoder` directly, the wasm builds default-\n * export an emscripten *factory* that must be called, and interop may nest\n * either under `.default`. Normalising here means callers can hand us whatever\n * their toolchain produced without knowing which one it is.\n */\nasync function normalizeLibheif(imported: unknown): Promise<LibheifModule> {\n let candidate = imported;\n\n for (let depth = 0; depth < 4; depth++) {\n if (isLibheifModule(candidate)) return candidate;\n if (typeof candidate === 'function') {\n candidate = await (candidate as () => unknown)();\n continue;\n }\n if (candidate && typeof candidate === 'object' && 'default' in candidate) {\n candidate = (candidate as { default: unknown }).default;\n continue;\n }\n break;\n }\n\n throw new HeicDecodeError(\n 'The module supplied to the wasm adapter does not expose a HeifDecoder',\n { strategy: 'wasm' },\n );\n}\n\nfunction isLibheifModule(value: unknown): value is LibheifModule {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { HeifDecoder?: unknown }).HeifDecoder === 'function'\n );\n}\n\n/**\n * A ready-made adapter using the default `libheif-js` build.\n *\n * ```ts\n * import { decodeHeic } from '@su-engineering/heic';\n * import { wasmDecoder } from '@su-engineering/heic/wasm';\n *\n * await decodeHeic(file, { wasmLoader: async () => wasmDecoder });\n * ```\n */\nexport const wasmDecoder: DecoderAdapter = createWasmAdapter();\n\nexport type { DecoderAdapter, AdapterRequest, AdapterResult } from '../src/types.ts';\n"]}
|
package/docs/api.md
CHANGED
|
@@ -40,6 +40,14 @@ await pending; // Rejects with HeicAbortError at a cancellation checkpoint.
|
|
|
40
40
|
|
|
41
41
|
The decoder returns pixels, not a new HEIC file. EXIF and other source metadata are not serialized into a new output file. Warnings are advisory and do not enumerate every unsupported feature.
|
|
42
42
|
|
|
43
|
+
## `convertHeic(input, options?)`
|
|
44
|
+
|
|
45
|
+
Returns `Promise<ConvertedImage>` using the same inputs and decode options as `decodeHeic`. Adds `type: 'image/jpeg' | 'image/png'` (default JPEG) and `quality` (default `0.92`, a finite number from 0 to 1; ignored for PNG). Invalid type/quality values throw `TypeError`/`RangeError` before decoding.
|
|
46
|
+
|
|
47
|
+
The result contains `blob` and all `DecodedImage` metadata except `image`. Dimensions describe the encoded image. Transforms and `maxDimension` apply before encoding. Its bitmap and conversion canvas are released on success, cancellation, or encoder failure; callers do not need `close()`. Revoke any object URLs you create when finished.
|
|
48
|
+
|
|
49
|
+
Conversion uses `OffscreenCanvas.convertToBlob`, including in workers. Platform encoding errors can propagate. An empty Blob or unexpected MIME type throws `HeicDecodeError`. Cancellation is checked before and after encoding, but cannot interrupt an encoder already running. Quality and encoded bytes can differ across browsers. Source EXIF is not copied; full HDR and cross-browser color equivalence are not promised. The software fallback remains explicit through `wasmLoader` or adapter registration.
|
|
50
|
+
|
|
43
51
|
## `isHeic(input)`
|
|
44
52
|
|
|
45
53
|
Returns `Promise<IsHeicResult>` with `isHeic: boolean` and optional `brand`, `primaryItemType`, and `coding` (`'hevc'`, `'av1'`, or `'unknown'`). Inspects at most the first 64 KiB, including for a `Blob`.
|
|
@@ -107,4 +115,4 @@ These functions do not decode pixels and accept `ArrayBuffer`/`Uint8Array` where
|
|
|
107
115
|
| `parseHvcC`, `hvccToCodecString` | Parse HEVC configuration and produce a WebCodecs codec string. |
|
|
108
116
|
| `hvccToAnnexBPrologue`, `lengthPrefixedToAnnexB` | Prepare HEVC NAL data for Annex B decoding. |
|
|
109
117
|
|
|
110
|
-
Exported types include `BinaryInput`, `IsHeicResult`, `DecodeOptions`, `DecodedImage`, `Strategy`, `OutputColorSpace`, `SourceColor`, `TransformsApplied`, `HeicWarning`, `SupportReport`, `HeicErrorContext`, `DecoderAdapter`, `AdapterRequest`, `AdapterResult`, `HeifFile`, `ItemInfo`, `ItemLocation`, `ItemProperty`, `ItemProperties`, `ItemReferences`, `GridDescriptor`, `HvcC`, `ImagePlan`, `PlannedTile`, `TileGroup`, and `TransformOp`. The WASM entry point additionally exports `WasmAdapterOptions`.
|
|
118
|
+
Exported types include `BinaryInput`, `IsHeicResult`, `DecodeOptions`, `DecodedImage`, `ConvertOptions`, `ConvertedImage`, `Strategy`, `OutputColorSpace`, `SourceColor`, `TransformsApplied`, `HeicWarning`, `SupportReport`, `HeicErrorContext`, `DecoderAdapter`, `AdapterRequest`, `AdapterResult`, `HeifFile`, `ItemInfo`, `ItemLocation`, `ItemProperty`, `ItemProperties`, `ItemReferences`, `GridDescriptor`, `HvcC`, `ImagePlan`, `PlannedTile`, `TileGroup`, and `TransformOp`. The WASM entry point additionally exports `WasmAdapterOptions`.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Benchmarking against heic-to
|
|
2
|
+
|
|
3
|
+
Compare full-resolution JPEG/PNG conversion with **heic-to 1.5.2**, using identical input bytes, MIME type, and JPEG quality. Photos are served only on `127.0.0.1`, never uploaded. The competitor is a pinned development dependency and is not shipped in the package.
|
|
4
|
+
|
|
5
|
+
## Run it
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm install --frozen-lockfile
|
|
9
|
+
pnpm exec playwright install chromium firefox
|
|
10
|
+
pnpm benchmark
|
|
11
|
+
|
|
12
|
+
# Use your own consented photos (quote paths containing spaces):
|
|
13
|
+
pnpm benchmark -- /path/to/one.heic /path/to/two.heic \
|
|
14
|
+
--iterations 10 --cold 5 --output benchmark-results/photos.json
|
|
15
|
+
|
|
16
|
+
pnpm benchmark -- /path/to/one.heic --browser firefox
|
|
17
|
+
pnpm benchmark -- /path/to/one.heic --type image/png
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Options: `--browser chromium|firefox|webkit`, `--iterations` (warm samples, default 5), `--cold` (first-use samples, default 3), `--type` (default `image/jpeg`), `--quality` (default `0.92`), and `--output`. With no files, two small committed synthetic fixtures smoke-test the harness; they are not representative phone-photo performance measurements.
|
|
21
|
+
|
|
22
|
+
For installed Chromium with platform HEVC, set `HEIC_CHROME=/path/to/browser`. This does not install codecs or guarantee hardware decoding. WebKit needs platform dependencies and does not represent released Safari. Close other CPU-intensive tasks and run benchmarks separately from tests.
|
|
23
|
+
|
|
24
|
+
## Methodology
|
|
25
|
+
|
|
26
|
+
| Variant | Decode behavior |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `ours-auto` | Native → WebCodecs → lazy libheif WASM fallback. |
|
|
29
|
+
| `ours-wasm` | Force the libheif-js 1.23.2 WASM fallback. |
|
|
30
|
+
| `heic-to` | Standard `heicTo` API with its bundled libheif build. |
|
|
31
|
+
|
|
32
|
+
- **Cold:** fresh context for every conversion; time module import, local bundle loading/parsing, codec initialization, decoding, and encoding. The browser process is reused. Loopback delivery is unthrottled and is not a mobile-network simulation.
|
|
33
|
+
- **Warm:** separate context per variant, one discarded warm-up, then interleaved sequential conversions with cached modules/codec. Variant order rotates between rounds.
|
|
34
|
+
- **Input fetch:** excluded for all variants. Neither side resizes. Output inspection is outside timing.
|
|
35
|
+
- **Validation:** every Blob must have the requested MIME type, nonzero size, and decodable pixels. Dimensions must match heic-to. A 64×64 thumbnail RGB mean absolute difference against heic-to is diagnostic, not independent proof of correct pixels or color.
|
|
36
|
+
- **Report:** raw samples, median/p95, actual strategy, dimensions, encoded bytes, loaded uncompressed JS bytes, browser/OS/CPU and dependency versions, and raw/gzip standalone core size. Small-sample p95 is usually the maximum, not a reliable population percentile.
|
|
37
|
+
|
|
38
|
+
Generated bundles/reports are ignored by Git. Reports use anonymous fixture IDs and omit filenames, source paths, and photo bytes. Keep private photos outside committed fixtures.
|
|
39
|
+
|
|
40
|
+
## Initial local measurements
|
|
41
|
+
|
|
42
|
+
September 18, 2026 conversion candidate, Linux x64, AMD Ryzen 7 PRO 8840U, Playwright Chromium 153.0.8010.12. Two consented iPhone photos, each 2268×4032 with a 5×8 tile grid, converted at full resolution to JPEG quality 0.92. Five cold samples and ten warm samples per variant; times below are medians. No other test suite ran during these measurements.
|
|
43
|
+
|
|
44
|
+
| Input | ours-auto cold | heic-to cold | ours-auto warm | heic-to warm |
|
|
45
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
46
|
+
| Photo 1 | 475 ms | 1,243 ms | 361 ms | 722 ms |
|
|
47
|
+
| Photo 2 | 587 ms | 1,503 ms | 471 ms | 848 ms |
|
|
48
|
+
|
|
49
|
+
Both `ours-auto` runs used **WASM**, not native/WebCodecs. Forced WASM warm medians were 362/475 ms. All outputs matched full-resolution dimensions; their 64×64 RGB thumbnail MAD against heic-to was zero in this browser. This is a narrow local result using private inputs, not an independently reproducible public corpus or a general performance guarantee. The harness is reproducible with the committed fixtures or your own photos.
|
|
50
|
+
|
|
51
|
+
An isolated Firefox 155 run on the same machine used three cold and five warm samples per variant, with the same photos and JPEG settings:
|
|
52
|
+
|
|
53
|
+
| Input | ours-auto cold | heic-to cold | ours-auto warm | heic-to warm |
|
|
54
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
55
|
+
| Photo 1 | 493 ms | 1,076 ms | 402 ms | 894 ms |
|
|
56
|
+
| Photo 2 | 607 ms | 1,350 ms | 518 ms | 1,117 ms |
|
|
57
|
+
|
|
58
|
+
These auto runs also used WASM; output dimensions matched and thumbnail RGB MAD was zero. Raw/gzip core size is reported separately from codec assets. In this bundled harness, our WASM path loaded approximately 2.02 MB of uncompressed JavaScript versus 3.00 MB for heic-to; production transfer sizes depend on bundling and compression.
|
|
59
|
+
|
|
60
|
+
## Interpretation
|
|
61
|
+
|
|
62
|
+
Our fallback stays unloaded when native or WebCodecs succeeds. `ours-auto` reports the actual successful strategy so a fallback result cannot be presented as a native/hardware speedup. Without platform HEVC, this compares software WASM against heic-to's bundled software implementation.
|
|
63
|
+
|
|
64
|
+
[heic-to 1.5.2](https://github.com/hoppergee/heic-to) documents libheif 1.22.2; this candidate tests libheif-js 1.23.2. Results compare complete package versions, including different codec builds and encoders; they do not isolate wrapper overhead.
|
|
65
|
+
|
|
66
|
+
Do not compare npm unpacked sizes as initial download sizes. `loadedJsBytes` is uncompressed local delivery, not gzip transfer cost. Core size excludes the optional codec. This script does not measure peak memory, UI blocking, constrained-network performance, or establish hardware acceleration. Two photos on one machine do not prove universal performance or compatibility. Test representative tiled, rotated, high-resolution, and color-diverse photos on target devices before making broad claims.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@su-engineering/heic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Browser-first HEIC/HEIF decoder. Decodes Apple HEIC photos without unconditionally downloading a WebAssembly codec.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"validate-corpus": "node --experimental-strip-types tools/validate-corpus.ts",
|
|
42
42
|
"prepublishOnly": "pnpm typecheck && pnpm build && pnpm test:package && pnpm test:unit",
|
|
43
43
|
"release": "node tools/publish.mjs",
|
|
44
|
-
"test:package": "node tools/check-package.mjs"
|
|
44
|
+
"test:package": "node tools/check-package.mjs",
|
|
45
|
+
"benchmark": "pnpm build && node tools/benchmark.mjs"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"heic",
|
|
@@ -50,21 +51,25 @@
|
|
|
50
51
|
"webcodecs",
|
|
51
52
|
"image",
|
|
52
53
|
"browser",
|
|
53
|
-
"iphone"
|
|
54
|
+
"iphone",
|
|
55
|
+
"conversion",
|
|
56
|
+
"jpeg",
|
|
57
|
+
"png"
|
|
54
58
|
],
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@changesets/cli": "^2.27.10",
|
|
57
61
|
"@playwright/test": "^1.62.1",
|
|
58
62
|
"@types/node": "^22.20.1",
|
|
59
63
|
"esbuild": "^0.28.1",
|
|
60
|
-
"
|
|
64
|
+
"heic-to": "1.5.2",
|
|
65
|
+
"libheif-js": "^1.23.2",
|
|
61
66
|
"tsup": "^8.5.1",
|
|
62
67
|
"typescript": "^5.9.3",
|
|
63
68
|
"vite": "^7.3.1",
|
|
64
69
|
"vitest": "^4.1.11"
|
|
65
70
|
},
|
|
66
71
|
"peerDependencies": {
|
|
67
|
-
"libheif-js": "^1.
|
|
72
|
+
"libheif-js": "^1.23.2"
|
|
68
73
|
},
|
|
69
74
|
"peerDependenciesMeta": {
|
|
70
75
|
"libheif-js": {
|