open-ultrahdr 0.1.0 → 0.1.2

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/dist/index.d.mts CHANGED
@@ -28,6 +28,34 @@ interface GainMapMetadata {
28
28
  /** Maximum HDR capacity (log2 scale) for full HDR output */
29
29
  hdrCapacityMax: number;
30
30
  }
31
+ /**
32
+ * Result of probing an image to check if it's UltraHDR.
33
+ *
34
+ * Provides detailed information about what components were found
35
+ * without fully decoding the image. Useful for batch processing and filtering.
36
+ */
37
+ interface UltraHdrProbeResult {
38
+ /** Whether the image is a valid UltraHDR image (has all required components) */
39
+ isValid: boolean;
40
+ /** Whether a primary JPEG image was found */
41
+ hasPrimaryImage: boolean;
42
+ /** Whether a gain map image was found */
43
+ hasGainMap: boolean;
44
+ /** Whether gain map metadata (XMP) was found */
45
+ hasMetadata: boolean;
46
+ /** Primary image width in pixels (0 if not found) */
47
+ width: number;
48
+ /** Primary image height in pixels (0 if not found) */
49
+ height: number;
50
+ /** Gain map width in pixels (0 if not found) */
51
+ gainMapWidth: number;
52
+ /** Gain map height in pixels (0 if not found) */
53
+ gainMapHeight: number;
54
+ /** HDR capacity (max additional stops of dynamic range), 0 if not found */
55
+ hdrCapacity: number;
56
+ /** Metadata version string (empty if not found) */
57
+ metadataVersion: string;
58
+ }
31
59
  /**
32
60
  * Result of decoding an UltraHDR image.
33
61
  */
@@ -139,6 +167,23 @@ declare const smallSizeEncodeOptions: UltraHdrEncodeOptions;
139
167
  * ```
140
168
  */
141
169
  declare function setLocation(newLocation: string): void;
170
+ /**
171
+ * Sets a direct URL (including data URLs) for the WASM module.
172
+ *
173
+ * Use this when the WASM binary is inlined as a base64 data URL
174
+ * at build time. Unlike `setLocation`, this passes the URL directly
175
+ * to the WASM init function without appending a filename.
176
+ *
177
+ * @param url - Direct URL or data URL for the WASM binary.
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // With a build tool that inlines WASM as base64 data URLs:
182
+ * import wasmDataUrl from 'open-ultrahdr-wasm/pkg/open_ultrahdr_bg.wasm';
183
+ * setWasmUrl(wasmDataUrl);
184
+ * ```
185
+ */
186
+ declare function setWasmUrl(url: string): void;
142
187
  /**
143
188
  * Checks if a buffer contains an UltraHDR image.
144
189
  *
@@ -157,6 +202,37 @@ declare function setLocation(newLocation: string): void;
157
202
  * ```
158
203
  */
159
204
  declare function isUltraHdr(buffer: ArrayBuffer): Promise<boolean>;
205
+ /**
206
+ * Probes an image to check if it's UltraHDR and extracts component information.
207
+ *
208
+ * This function efficiently validates if an image is UltraHDR by checking for
209
+ * required components (primary image, gain map, metadata) without full decoding.
210
+ * Returns structured results useful for batch processing and filtering.
211
+ *
212
+ * Unlike `isUltraHdr`, this function provides detailed information about what
213
+ * was found, making it useful for diagnostics and filtering workflows.
214
+ *
215
+ * @param buffer - Image file contents.
216
+ * @return Probe result with detailed component information.
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const buffer = await file.arrayBuffer();
221
+ * const result = await probeUltraHdr(buffer);
222
+ *
223
+ * if (result.isValid) {
224
+ * console.log('UltraHDR image:', result.width, 'x', result.height);
225
+ * console.log('HDR capacity:', result.hdrCapacity, 'stops');
226
+ * console.log('Gain map:', result.gainMapWidth, 'x', result.gainMapHeight);
227
+ * } else {
228
+ * // Diagnose why it's not a valid UltraHDR
229
+ * if (!result.hasPrimaryImage) console.log('Not a valid JPEG');
230
+ * if (!result.hasGainMap) console.log('Missing gain map');
231
+ * if (!result.hasMetadata) console.log('Missing HDR metadata');
232
+ * }
233
+ * ```
234
+ */
235
+ declare function probeUltraHdr(buffer: ArrayBuffer): Promise<UltraHdrProbeResult>;
160
236
  /**
161
237
  * Decodes an UltraHDR image, extracting all components.
162
238
  *
@@ -263,4 +339,4 @@ declare function estimateHdrHeadroom(metadata: GainMapMetadata): Promise<number>
263
339
  */
264
340
  declare function isMeaningfulHdr(metadata: GainMapMetadata): Promise<boolean>;
265
341
 
266
- export { ColorGamut, type GainMapMetadata, type ItemId, TransferFunction, type UltraHdrDecodeResult, type UltraHdrEncodeOptions, decodeUltraHdr, defaultEncodeOptions, encodeUltraHdr, estimateHdrHeadroom, extractSdrBase, getMetadata, highQualityEncodeOptions, isMeaningfulHdr, isUltraHdr, setLocation, smallSizeEncodeOptions, validateMetadata };
342
+ export { ColorGamut, type GainMapMetadata, type ItemId, TransferFunction, type UltraHdrDecodeResult, type UltraHdrEncodeOptions, type UltraHdrProbeResult, decodeUltraHdr, defaultEncodeOptions, encodeUltraHdr, estimateHdrHeadroom, extractSdrBase, getMetadata, highQualityEncodeOptions, isMeaningfulHdr, isUltraHdr, probeUltraHdr, setLocation, setWasmUrl, smallSizeEncodeOptions, validateMetadata };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,34 @@ interface GainMapMetadata {
28
28
  /** Maximum HDR capacity (log2 scale) for full HDR output */
29
29
  hdrCapacityMax: number;
30
30
  }
31
+ /**
32
+ * Result of probing an image to check if it's UltraHDR.
33
+ *
34
+ * Provides detailed information about what components were found
35
+ * without fully decoding the image. Useful for batch processing and filtering.
36
+ */
37
+ interface UltraHdrProbeResult {
38
+ /** Whether the image is a valid UltraHDR image (has all required components) */
39
+ isValid: boolean;
40
+ /** Whether a primary JPEG image was found */
41
+ hasPrimaryImage: boolean;
42
+ /** Whether a gain map image was found */
43
+ hasGainMap: boolean;
44
+ /** Whether gain map metadata (XMP) was found */
45
+ hasMetadata: boolean;
46
+ /** Primary image width in pixels (0 if not found) */
47
+ width: number;
48
+ /** Primary image height in pixels (0 if not found) */
49
+ height: number;
50
+ /** Gain map width in pixels (0 if not found) */
51
+ gainMapWidth: number;
52
+ /** Gain map height in pixels (0 if not found) */
53
+ gainMapHeight: number;
54
+ /** HDR capacity (max additional stops of dynamic range), 0 if not found */
55
+ hdrCapacity: number;
56
+ /** Metadata version string (empty if not found) */
57
+ metadataVersion: string;
58
+ }
31
59
  /**
32
60
  * Result of decoding an UltraHDR image.
33
61
  */
@@ -139,6 +167,23 @@ declare const smallSizeEncodeOptions: UltraHdrEncodeOptions;
139
167
  * ```
140
168
  */
141
169
  declare function setLocation(newLocation: string): void;
170
+ /**
171
+ * Sets a direct URL (including data URLs) for the WASM module.
172
+ *
173
+ * Use this when the WASM binary is inlined as a base64 data URL
174
+ * at build time. Unlike `setLocation`, this passes the URL directly
175
+ * to the WASM init function without appending a filename.
176
+ *
177
+ * @param url - Direct URL or data URL for the WASM binary.
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // With a build tool that inlines WASM as base64 data URLs:
182
+ * import wasmDataUrl from 'open-ultrahdr-wasm/pkg/open_ultrahdr_bg.wasm';
183
+ * setWasmUrl(wasmDataUrl);
184
+ * ```
185
+ */
186
+ declare function setWasmUrl(url: string): void;
142
187
  /**
143
188
  * Checks if a buffer contains an UltraHDR image.
144
189
  *
@@ -157,6 +202,37 @@ declare function setLocation(newLocation: string): void;
157
202
  * ```
158
203
  */
159
204
  declare function isUltraHdr(buffer: ArrayBuffer): Promise<boolean>;
205
+ /**
206
+ * Probes an image to check if it's UltraHDR and extracts component information.
207
+ *
208
+ * This function efficiently validates if an image is UltraHDR by checking for
209
+ * required components (primary image, gain map, metadata) without full decoding.
210
+ * Returns structured results useful for batch processing and filtering.
211
+ *
212
+ * Unlike `isUltraHdr`, this function provides detailed information about what
213
+ * was found, making it useful for diagnostics and filtering workflows.
214
+ *
215
+ * @param buffer - Image file contents.
216
+ * @return Probe result with detailed component information.
217
+ *
218
+ * @example
219
+ * ```typescript
220
+ * const buffer = await file.arrayBuffer();
221
+ * const result = await probeUltraHdr(buffer);
222
+ *
223
+ * if (result.isValid) {
224
+ * console.log('UltraHDR image:', result.width, 'x', result.height);
225
+ * console.log('HDR capacity:', result.hdrCapacity, 'stops');
226
+ * console.log('Gain map:', result.gainMapWidth, 'x', result.gainMapHeight);
227
+ * } else {
228
+ * // Diagnose why it's not a valid UltraHDR
229
+ * if (!result.hasPrimaryImage) console.log('Not a valid JPEG');
230
+ * if (!result.hasGainMap) console.log('Missing gain map');
231
+ * if (!result.hasMetadata) console.log('Missing HDR metadata');
232
+ * }
233
+ * ```
234
+ */
235
+ declare function probeUltraHdr(buffer: ArrayBuffer): Promise<UltraHdrProbeResult>;
160
236
  /**
161
237
  * Decodes an UltraHDR image, extracting all components.
162
238
  *
@@ -263,4 +339,4 @@ declare function estimateHdrHeadroom(metadata: GainMapMetadata): Promise<number>
263
339
  */
264
340
  declare function isMeaningfulHdr(metadata: GainMapMetadata): Promise<boolean>;
265
341
 
266
- export { ColorGamut, type GainMapMetadata, type ItemId, TransferFunction, type UltraHdrDecodeResult, type UltraHdrEncodeOptions, decodeUltraHdr, defaultEncodeOptions, encodeUltraHdr, estimateHdrHeadroom, extractSdrBase, getMetadata, highQualityEncodeOptions, isMeaningfulHdr, isUltraHdr, setLocation, smallSizeEncodeOptions, validateMetadata };
342
+ export { ColorGamut, type GainMapMetadata, type ItemId, TransferFunction, type UltraHdrDecodeResult, type UltraHdrEncodeOptions, type UltraHdrProbeResult, decodeUltraHdr, defaultEncodeOptions, encodeUltraHdr, estimateHdrHeadroom, extractSdrBase, getMetadata, highQualityEncodeOptions, isMeaningfulHdr, isUltraHdr, probeUltraHdr, setLocation, setWasmUrl, smallSizeEncodeOptions, validateMetadata };
package/dist/index.js CHANGED
@@ -41,7 +41,9 @@ __export(index_exports, {
41
41
  highQualityEncodeOptions: () => highQualityEncodeOptions,
42
42
  isMeaningfulHdr: () => isMeaningfulHdr,
43
43
  isUltraHdr: () => isUltraHdr,
44
+ probeUltraHdr: () => probeUltraHdr,
44
45
  setLocation: () => setLocation,
46
+ setWasmUrl: () => setWasmUrl,
45
47
  smallSizeEncodeOptions: () => smallSizeEncodeOptions,
46
48
  validateMetadata: () => validateMetadata
47
49
  });
@@ -88,11 +90,15 @@ var smallSizeEncodeOptions = {
88
90
 
89
91
  // src/index.ts
90
92
  var location = "";
93
+ var wasmUrl = "";
91
94
  var wasmInstance = null;
92
95
  var initPromise = null;
93
96
  function setLocation(newLocation) {
94
97
  location = newLocation;
95
98
  }
99
+ function setWasmUrl(url) {
100
+ wasmUrl = url;
101
+ }
96
102
  function isWasmMetadataInstance(metadata) {
97
103
  return typeof metadata === "object" && metadata !== null && "__wbg_ptr" in metadata;
98
104
  }
@@ -121,15 +127,29 @@ async function getWasm() {
121
127
  return initPromise;
122
128
  }
123
129
  initPromise = (async () => {
124
- const UltraHdrWasm = await import("open-ultrahdr-wasm");
125
- if (location) {
126
- const wasmUrl = location + "open_ultrahdr_bg.wasm";
127
- await UltraHdrWasm.default(wasmUrl);
128
- } else {
129
- await UltraHdrWasm.default();
130
+ try {
131
+ const UltraHdrWasm = await import("open-ultrahdr-wasm");
132
+ if (wasmUrl) {
133
+ await UltraHdrWasm.default(wasmUrl);
134
+ } else if (location) {
135
+ const base = location.endsWith("/") ? location : `${location}/`;
136
+ const wasmPath = base + "open_ultrahdr_bg.wasm";
137
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
138
+ const fs = await import("fs");
139
+ const wasmBytes = await fs.promises.readFile(wasmPath);
140
+ await UltraHdrWasm.default(wasmBytes);
141
+ } else {
142
+ await UltraHdrWasm.default(wasmPath);
143
+ }
144
+ } else {
145
+ await UltraHdrWasm.default();
146
+ }
147
+ wasmInstance = UltraHdrWasm;
148
+ return wasmInstance;
149
+ } catch (err) {
150
+ initPromise = null;
151
+ throw err;
130
152
  }
131
- wasmInstance = UltraHdrWasm;
132
- return wasmInstance;
133
153
  })();
134
154
  return initPromise;
135
155
  }
@@ -137,6 +157,10 @@ async function isUltraHdr(buffer) {
137
157
  const wasm = await getWasm();
138
158
  return wasm.isUltraHdr(new Uint8Array(buffer));
139
159
  }
160
+ async function probeUltraHdr(buffer) {
161
+ const wasm = await getWasm();
162
+ return wasm.probeUltraHdr(new Uint8Array(buffer));
163
+ }
140
164
  async function decodeUltraHdr(id, buffer) {
141
165
  const wasm = await getWasm();
142
166
  return wasm.decodeUltraHdr(new Uint8Array(buffer));
@@ -156,12 +180,18 @@ async function encodeUltraHdr(id, sdrBuffer, hdrBuffer, options) {
156
180
  new Float32Array(hdrBuffer),
157
181
  wasmOpts
158
182
  );
159
- return result.buffer.slice(0);
183
+ return result.buffer.slice(
184
+ result.byteOffset,
185
+ result.byteOffset + result.byteLength
186
+ );
160
187
  }
161
188
  async function extractSdrBase(buffer) {
162
189
  const wasm = await getWasm();
163
190
  const result = wasm.extractSdrBase(new Uint8Array(buffer));
164
- return result.buffer.slice(0);
191
+ return result.buffer.slice(
192
+ result.byteOffset,
193
+ result.byteOffset + result.byteLength
194
+ );
165
195
  }
166
196
  async function getMetadata(buffer) {
167
197
  const wasm = await getWasm();
@@ -195,7 +225,9 @@ async function isMeaningfulHdr(metadata) {
195
225
  highQualityEncodeOptions,
196
226
  isMeaningfulHdr,
197
227
  isUltraHdr,
228
+ probeUltraHdr,
198
229
  setLocation,
230
+ setWasmUrl,
199
231
  smallSizeEncodeOptions,
200
232
  validateMetadata
201
233
  });
package/dist/index.mjs CHANGED
@@ -39,11 +39,15 @@ var smallSizeEncodeOptions = {
39
39
 
40
40
  // src/index.ts
41
41
  var location = "";
42
+ var wasmUrl = "";
42
43
  var wasmInstance = null;
43
44
  var initPromise = null;
44
45
  function setLocation(newLocation) {
45
46
  location = newLocation;
46
47
  }
48
+ function setWasmUrl(url) {
49
+ wasmUrl = url;
50
+ }
47
51
  function isWasmMetadataInstance(metadata) {
48
52
  return typeof metadata === "object" && metadata !== null && "__wbg_ptr" in metadata;
49
53
  }
@@ -72,15 +76,29 @@ async function getWasm() {
72
76
  return initPromise;
73
77
  }
74
78
  initPromise = (async () => {
75
- const UltraHdrWasm = await import("open-ultrahdr-wasm");
76
- if (location) {
77
- const wasmUrl = location + "open_ultrahdr_bg.wasm";
78
- await UltraHdrWasm.default(wasmUrl);
79
- } else {
80
- await UltraHdrWasm.default();
79
+ try {
80
+ const UltraHdrWasm = await import("open-ultrahdr-wasm");
81
+ if (wasmUrl) {
82
+ await UltraHdrWasm.default(wasmUrl);
83
+ } else if (location) {
84
+ const base = location.endsWith("/") ? location : `${location}/`;
85
+ const wasmPath = base + "open_ultrahdr_bg.wasm";
86
+ if (typeof process !== "undefined" && process.versions && process.versions.node) {
87
+ const fs = await import("fs");
88
+ const wasmBytes = await fs.promises.readFile(wasmPath);
89
+ await UltraHdrWasm.default(wasmBytes);
90
+ } else {
91
+ await UltraHdrWasm.default(wasmPath);
92
+ }
93
+ } else {
94
+ await UltraHdrWasm.default();
95
+ }
96
+ wasmInstance = UltraHdrWasm;
97
+ return wasmInstance;
98
+ } catch (err) {
99
+ initPromise = null;
100
+ throw err;
81
101
  }
82
- wasmInstance = UltraHdrWasm;
83
- return wasmInstance;
84
102
  })();
85
103
  return initPromise;
86
104
  }
@@ -88,6 +106,10 @@ async function isUltraHdr(buffer) {
88
106
  const wasm = await getWasm();
89
107
  return wasm.isUltraHdr(new Uint8Array(buffer));
90
108
  }
109
+ async function probeUltraHdr(buffer) {
110
+ const wasm = await getWasm();
111
+ return wasm.probeUltraHdr(new Uint8Array(buffer));
112
+ }
91
113
  async function decodeUltraHdr(id, buffer) {
92
114
  const wasm = await getWasm();
93
115
  return wasm.decodeUltraHdr(new Uint8Array(buffer));
@@ -107,12 +129,18 @@ async function encodeUltraHdr(id, sdrBuffer, hdrBuffer, options) {
107
129
  new Float32Array(hdrBuffer),
108
130
  wasmOpts
109
131
  );
110
- return result.buffer.slice(0);
132
+ return result.buffer.slice(
133
+ result.byteOffset,
134
+ result.byteOffset + result.byteLength
135
+ );
111
136
  }
112
137
  async function extractSdrBase(buffer) {
113
138
  const wasm = await getWasm();
114
139
  const result = wasm.extractSdrBase(new Uint8Array(buffer));
115
- return result.buffer.slice(0);
140
+ return result.buffer.slice(
141
+ result.byteOffset,
142
+ result.byteOffset + result.byteLength
143
+ );
116
144
  }
117
145
  async function getMetadata(buffer) {
118
146
  const wasm = await getWasm();
@@ -145,7 +173,9 @@ export {
145
173
  highQualityEncodeOptions,
146
174
  isMeaningfulHdr,
147
175
  isUltraHdr,
176
+ probeUltraHdr,
148
177
  setLocation,
178
+ setWasmUrl,
149
179
  smallSizeEncodeOptions,
150
180
  validateMetadata
151
181
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-ultrahdr",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "UltraHDR (ISO 21496-1) gain map support for JavaScript/TypeScript",
5
5
  "author": "Adam Silverstein",
6
6
  "license": "GPL-2.0-or-later",
@@ -15,14 +15,14 @@
15
15
  "homepage": "https://github.com/adamsilverstein/lib-open-ultrahdr",
16
16
  "repository": {
17
17
  "type": "git",
18
- "url": "https://github.com/adamsilverstein/lib-open-ultrahdr.git",
18
+ "url": "git+https://github.com/adamsilverstein/lib-open-ultrahdr.git",
19
19
  "directory": "js"
20
20
  },
21
21
  "bugs": {
22
22
  "url": "https://github.com/adamsilverstein/lib-open-ultrahdr/issues"
23
23
  },
24
24
  "engines": {
25
- "node": ">=18.0.0"
25
+ "node": ">=20.0.0"
26
26
  },
27
27
  "files": [
28
28
  "src",
@@ -52,11 +52,12 @@
52
52
  "lint:fix": "eslint src __tests__ --fix"
53
53
  },
54
54
  "dependencies": {
55
- "open-ultrahdr-wasm": "^0.1.1"
55
+ "open-ultrahdr-wasm": "^0.1.2"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@eslint/js": "^9.0.0",
59
- "@vitest/coverage-v8": "^2.0.0",
59
+ "@types/node": "^25.1.0",
60
+ "@vitest/coverage-v8": "^4.0.0",
60
61
  "eslint": "^9.0.0",
61
62
  "eslint-config-prettier": "^9.0.0",
62
63
  "tsup": "^8.0.0",
@@ -64,7 +65,7 @@
64
65
  "typescript-eslint": "^8.0.0",
65
66
  "vite-plugin-top-level-await": "^1.6.0",
66
67
  "vite-plugin-wasm": "^3.5.0",
67
- "vitest": "^2.0.0"
68
+ "vitest": "^4.0.0"
68
69
  },
69
70
  "publishConfig": {
70
71
  "access": "public"
package/src/index.ts CHANGED
@@ -22,7 +22,13 @@
22
22
  */
23
23
 
24
24
  // Re-export types
25
- export type { ItemId, GainMapMetadata, UltraHdrDecodeResult, UltraHdrEncodeOptions } from './types';
25
+ export type {
26
+ ItemId,
27
+ GainMapMetadata,
28
+ UltraHdrDecodeResult,
29
+ UltraHdrEncodeOptions,
30
+ UltraHdrProbeResult,
31
+ } from './types';
26
32
 
27
33
  export {
28
34
  ColorGamut,
@@ -32,7 +38,13 @@ export {
32
38
  smallSizeEncodeOptions,
33
39
  } from './types';
34
40
 
35
- import type { ItemId, GainMapMetadata, UltraHdrDecodeResult, UltraHdrEncodeOptions } from './types';
41
+ import type {
42
+ ItemId,
43
+ GainMapMetadata,
44
+ UltraHdrDecodeResult,
45
+ UltraHdrEncodeOptions,
46
+ UltraHdrProbeResult,
47
+ } from './types';
36
48
 
37
49
  import { defaultEncodeOptions } from './types';
38
50
 
@@ -62,6 +74,7 @@ interface WasmGainMapMetadata {
62
74
  interface UltraHdrWasmModule {
63
75
  default: (moduleOrPath?: string | URL | Response | BufferSource) => Promise<unknown>;
64
76
  isUltraHdr: (buffer: Uint8Array) => boolean;
77
+ probeUltraHdr: (buffer: Uint8Array) => UltraHdrProbeResult;
65
78
  decodeUltraHdr: (buffer: Uint8Array) => UltraHdrDecodeResult;
66
79
  encodeUltraHdr: (
67
80
  sdrBuffer: Uint8Array,
@@ -85,6 +98,13 @@ interface UltraHdrWasmModule {
85
98
  */
86
99
  let location = '';
87
100
 
101
+ /**
102
+ * Direct URL or data URL for the WASM module.
103
+ * When set, this is passed directly to the WASM init function,
104
+ * bypassing the location prefix + filename construction.
105
+ */
106
+ let wasmUrl = '';
107
+
88
108
  /**
89
109
  * Cached WASM module instance.
90
110
  */
@@ -113,6 +133,26 @@ export function setLocation(newLocation: string): void {
113
133
  location = newLocation;
114
134
  }
115
135
 
136
+ /**
137
+ * Sets a direct URL (including data URLs) for the WASM module.
138
+ *
139
+ * Use this when the WASM binary is inlined as a base64 data URL
140
+ * at build time. Unlike `setLocation`, this passes the URL directly
141
+ * to the WASM init function without appending a filename.
142
+ *
143
+ * @param url - Direct URL or data URL for the WASM binary.
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * // With a build tool that inlines WASM as base64 data URLs:
148
+ * import wasmDataUrl from 'open-ultrahdr-wasm/pkg/open_ultrahdr_bg.wasm';
149
+ * setWasmUrl(wasmDataUrl);
150
+ * ```
151
+ */
152
+ export function setWasmUrl(url: string): void {
153
+ wasmUrl = url;
154
+ }
155
+
116
156
  /**
117
157
  * Checks if metadata is a WASM class instance (has __wbg_ptr property).
118
158
  */
@@ -159,21 +199,40 @@ async function getWasm(): Promise<UltraHdrWasmModule> {
159
199
  }
160
200
 
161
201
  initPromise = (async () => {
162
- // Dynamic import of the WASM package
163
- const UltraHdrWasm = (await import('open-ultrahdr-wasm')) as unknown as UltraHdrWasmModule;
164
-
165
- // Initialize WASM with the location prefix for the .wasm file
166
- // If location is set, construct the full URL to the WASM file
167
- if (location) {
168
- const wasmUrl = location + 'open_ultrahdr_bg.wasm';
169
- await UltraHdrWasm.default(wasmUrl);
170
- } else {
171
- // Let the WASM module use its default URL resolution (import.meta.url)
172
- await UltraHdrWasm.default();
202
+ try {
203
+ // Dynamic import of the WASM package
204
+ const UltraHdrWasm = (await import('open-ultrahdr-wasm')) as unknown as UltraHdrWasmModule;
205
+
206
+ // Initialize WASM module.
207
+ // Priority: wasmUrl (direct URL/data URL) > location (path prefix) > default
208
+ if (wasmUrl) {
209
+ // Direct URL (e.g. inlined base64 data URL from build tool)
210
+ await UltraHdrWasm.default(wasmUrl);
211
+ } else if (location) {
212
+ const base = location.endsWith('/') ? location : `${location}/`;
213
+ const wasmPath = base + 'open_ultrahdr_bg.wasm';
214
+
215
+ // In Node.js, load the WASM file from the filesystem
216
+ // instead of using fetch which doesn't work with file:// URLs
217
+ if (typeof process !== 'undefined' && process.versions && process.versions.node) {
218
+ const fs = await import('fs');
219
+ const wasmBytes = await fs.promises.readFile(wasmPath);
220
+ await UltraHdrWasm.default(wasmBytes);
221
+ } else {
222
+ // In browser, use the URL directly
223
+ await UltraHdrWasm.default(wasmPath);
224
+ }
225
+ } else {
226
+ // Let the WASM module use its default URL resolution (import.meta.url)
227
+ await UltraHdrWasm.default();
228
+ }
229
+
230
+ wasmInstance = UltraHdrWasm as unknown as UltraHdrWasmModule;
231
+ return wasmInstance;
232
+ } catch (err) {
233
+ initPromise = null;
234
+ throw err;
173
235
  }
174
-
175
- wasmInstance = UltraHdrWasm as unknown as UltraHdrWasmModule;
176
- return wasmInstance;
177
236
  })();
178
237
 
179
238
  return initPromise;
@@ -201,6 +260,41 @@ export async function isUltraHdr(buffer: ArrayBuffer): Promise<boolean> {
201
260
  return wasm.isUltraHdr(new Uint8Array(buffer));
202
261
  }
203
262
 
263
+ /**
264
+ * Probes an image to check if it's UltraHDR and extracts component information.
265
+ *
266
+ * This function efficiently validates if an image is UltraHDR by checking for
267
+ * required components (primary image, gain map, metadata) without full decoding.
268
+ * Returns structured results useful for batch processing and filtering.
269
+ *
270
+ * Unlike `isUltraHdr`, this function provides detailed information about what
271
+ * was found, making it useful for diagnostics and filtering workflows.
272
+ *
273
+ * @param buffer - Image file contents.
274
+ * @return Probe result with detailed component information.
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const buffer = await file.arrayBuffer();
279
+ * const result = await probeUltraHdr(buffer);
280
+ *
281
+ * if (result.isValid) {
282
+ * console.log('UltraHDR image:', result.width, 'x', result.height);
283
+ * console.log('HDR capacity:', result.hdrCapacity, 'stops');
284
+ * console.log('Gain map:', result.gainMapWidth, 'x', result.gainMapHeight);
285
+ * } else {
286
+ * // Diagnose why it's not a valid UltraHDR
287
+ * if (!result.hasPrimaryImage) console.log('Not a valid JPEG');
288
+ * if (!result.hasGainMap) console.log('Missing gain map');
289
+ * if (!result.hasMetadata) console.log('Missing HDR metadata');
290
+ * }
291
+ * ```
292
+ */
293
+ export async function probeUltraHdr(buffer: ArrayBuffer): Promise<UltraHdrProbeResult> {
294
+ const wasm = await getWasm();
295
+ return wasm.probeUltraHdr(new Uint8Array(buffer));
296
+ }
297
+
204
298
  /**
205
299
  * Decodes an UltraHDR image, extracting all components.
206
300
  *
@@ -281,7 +375,10 @@ export async function encodeUltraHdr(
281
375
  );
282
376
 
283
377
  // Ensure we return a proper ArrayBuffer (not SharedArrayBuffer)
284
- return result.buffer.slice(0) as ArrayBuffer;
378
+ return result.buffer.slice(
379
+ result.byteOffset,
380
+ result.byteOffset + result.byteLength
381
+ ) as ArrayBuffer;
285
382
  }
286
383
 
287
384
  /**
@@ -306,7 +403,10 @@ export async function extractSdrBase(buffer: ArrayBuffer): Promise<ArrayBuffer>
306
403
  const wasm = await getWasm();
307
404
  const result = wasm.extractSdrBase(new Uint8Array(buffer));
308
405
  // Ensure we return a proper ArrayBuffer (not SharedArrayBuffer)
309
- return result.buffer.slice(0) as ArrayBuffer;
406
+ return result.buffer.slice(
407
+ result.byteOffset,
408
+ result.byteOffset + result.byteLength
409
+ ) as ArrayBuffer;
310
410
  }
311
411
 
312
412
  /**
package/src/types.ts CHANGED
@@ -38,6 +38,44 @@ export interface GainMapMetadata {
38
38
  hdrCapacityMax: number;
39
39
  }
40
40
 
41
+ /**
42
+ * Result of probing an image to check if it's UltraHDR.
43
+ *
44
+ * Provides detailed information about what components were found
45
+ * without fully decoding the image. Useful for batch processing and filtering.
46
+ */
47
+ export interface UltraHdrProbeResult {
48
+ /** Whether the image is a valid UltraHDR image (has all required components) */
49
+ isValid: boolean;
50
+
51
+ /** Whether a primary JPEG image was found */
52
+ hasPrimaryImage: boolean;
53
+
54
+ /** Whether a gain map image was found */
55
+ hasGainMap: boolean;
56
+
57
+ /** Whether gain map metadata (XMP) was found */
58
+ hasMetadata: boolean;
59
+
60
+ /** Primary image width in pixels (0 if not found) */
61
+ width: number;
62
+
63
+ /** Primary image height in pixels (0 if not found) */
64
+ height: number;
65
+
66
+ /** Gain map width in pixels (0 if not found) */
67
+ gainMapWidth: number;
68
+
69
+ /** Gain map height in pixels (0 if not found) */
70
+ gainMapHeight: number;
71
+
72
+ /** HDR capacity (max additional stops of dynamic range), 0 if not found */
73
+ hdrCapacity: number;
74
+
75
+ /** Metadata version string (empty if not found) */
76
+ metadataVersion: string;
77
+ }
78
+
41
79
  /**
42
80
  * Result of decoding an UltraHDR image.
43
81
  */