open-ultrahdr 0.1.2 → 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/README.md +3 -1
- package/dist/index.d.mts +30 -147
- package/dist/index.d.ts +30 -147
- package/dist/index.js +60 -59
- package/dist/index.mjs +60 -59
- package/package.json +4 -4
- package/src/index.ts +90 -299
- package/src/types.ts +16 -2
package/src/index.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Open UltraHDR Library
|
|
3
3
|
*
|
|
4
|
-
* TypeScript bindings for the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* TypeScript bindings for the libultrahdr WASM library. Provides detection,
|
|
5
|
+
* encoding, and decoding of UltraHDR JPEG images implementing the
|
|
6
|
+
* ISO 21496-1 (gain map) specification.
|
|
7
|
+
*
|
|
8
|
+
* Backed by upstream `libultrahdr` compiled to WebAssembly via Emscripten/embind.
|
|
7
9
|
*
|
|
8
10
|
* @example
|
|
9
11
|
* ```typescript
|
|
10
12
|
* import { isUltraHdr, decodeUltraHdr, setLocation } from 'open-ultrahdr';
|
|
11
13
|
*
|
|
12
|
-
* // Set the location for WASM files
|
|
13
14
|
* setLocation('/path/to/wasm/');
|
|
14
15
|
*
|
|
15
|
-
* // Check if an image is UltraHDR
|
|
16
16
|
* const buffer = await file.arrayBuffer();
|
|
17
17
|
* if (await isUltraHdr(buffer)) {
|
|
18
18
|
* const result = await decodeUltraHdr('item-1', buffer);
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
// Re-export types
|
|
25
24
|
export type {
|
|
26
25
|
ItemId,
|
|
27
26
|
GainMapMetadata,
|
|
@@ -48,189 +47,100 @@ import type {
|
|
|
48
47
|
|
|
49
48
|
import { defaultEncodeOptions } from './types';
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
interface WasmUltraHdrEncodeOptions {
|
|
53
|
-
baseQuality: number;
|
|
54
|
-
gainMapQuality: number;
|
|
55
|
-
targetHdrCapacity: number;
|
|
56
|
-
includeIsoMetadata: boolean;
|
|
57
|
-
includeUltrahdrV1: boolean;
|
|
58
|
-
gainMapScale: number;
|
|
59
|
-
}
|
|
50
|
+
import type { OpenUltraHdrModule } from 'open-ultrahdr-wasm';
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
version: string;
|
|
63
|
-
baseRenditionIsHdr: boolean;
|
|
64
|
-
gainMapMin: number[];
|
|
65
|
-
gainMapMax: number[];
|
|
66
|
-
gamma: number[];
|
|
67
|
-
offsetSdr: number[];
|
|
68
|
-
offsetHdr: number[];
|
|
69
|
-
hdrCapacityMin: number;
|
|
70
|
-
hdrCapacityMax: number;
|
|
71
|
-
}
|
|
52
|
+
const WASM_FILENAME = 'open_ultrahdr.wasm';
|
|
72
53
|
|
|
73
|
-
// WASM module type (these come from the generated WASM bindings)
|
|
74
|
-
interface UltraHdrWasmModule {
|
|
75
|
-
default: (moduleOrPath?: string | URL | Response | BufferSource) => Promise<unknown>;
|
|
76
|
-
isUltraHdr: (buffer: Uint8Array) => boolean;
|
|
77
|
-
probeUltraHdr: (buffer: Uint8Array) => UltraHdrProbeResult;
|
|
78
|
-
decodeUltraHdr: (buffer: Uint8Array) => UltraHdrDecodeResult;
|
|
79
|
-
encodeUltraHdr: (
|
|
80
|
-
sdrBuffer: Uint8Array,
|
|
81
|
-
hdrBuffer: Float32Array,
|
|
82
|
-
options: WasmUltraHdrEncodeOptions
|
|
83
|
-
) => Uint8Array;
|
|
84
|
-
extractSdrBase: (buffer: Uint8Array) => Uint8Array;
|
|
85
|
-
getMetadata: (buffer: Uint8Array) => WasmGainMapMetadata;
|
|
86
|
-
createDefaultOptions: () => WasmUltraHdrEncodeOptions;
|
|
87
|
-
createHighQualityOptions: () => WasmUltraHdrEncodeOptions;
|
|
88
|
-
createSmallSizeOptions: () => WasmUltraHdrEncodeOptions;
|
|
89
|
-
createDefaultMetadata: () => WasmGainMapMetadata;
|
|
90
|
-
validateMetadata: (metadata: WasmGainMapMetadata) => boolean;
|
|
91
|
-
estimateHdrHeadroom: (metadata: WasmGainMapMetadata) => number;
|
|
92
|
-
isMeaningfulHdr: (metadata: WasmGainMapMetadata) => boolean;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Location prefix for WASM files.
|
|
97
|
-
* Set this before calling any other functions.
|
|
98
|
-
*/
|
|
99
54
|
let location = '';
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
let
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Cached WASM module instance.
|
|
110
|
-
*/
|
|
111
|
-
let wasmInstance: UltraHdrWasmModule | null = null;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Promise for ongoing WASM initialization.
|
|
115
|
-
*/
|
|
116
|
-
let initPromise: Promise<UltraHdrWasmModule> | null = null;
|
|
55
|
+
let explicitWasmUrl: string | null = null;
|
|
56
|
+
let wasmInstance: OpenUltraHdrModule | null = null;
|
|
57
|
+
let initPromise: Promise<OpenUltraHdrModule> | null = null;
|
|
58
|
+
// Bumped by resetCache() so that any in-flight getWasm() call started under a
|
|
59
|
+
// previous configuration cannot publish a stale instance after setLocation/
|
|
60
|
+
// setWasmUrl is called.
|
|
61
|
+
let initGeneration = 0;
|
|
117
62
|
|
|
118
63
|
/**
|
|
119
64
|
* Sets the location/public path for loading WASM files.
|
|
120
65
|
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
66
|
+
* Must be called before using any other functions when the WASM file is not
|
|
67
|
+
* served from the same directory as the JavaScript bundle.
|
|
123
68
|
*
|
|
124
69
|
* @param newLocation - Base URL or path where WASM files are located.
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```typescript
|
|
128
|
-
* // Set location before any other calls
|
|
129
|
-
* setLocation('/assets/wasm/');
|
|
130
|
-
* ```
|
|
131
70
|
*/
|
|
132
71
|
export function setLocation(newLocation: string): void {
|
|
133
72
|
location = newLocation;
|
|
73
|
+
explicitWasmUrl = null;
|
|
74
|
+
resetCache();
|
|
134
75
|
}
|
|
135
76
|
|
|
136
77
|
/**
|
|
137
|
-
* Sets
|
|
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.
|
|
78
|
+
* Sets an explicit URL (or `data:` URL) for the WASM file.
|
|
142
79
|
*
|
|
143
|
-
*
|
|
80
|
+
* Useful when bundling the WASM as a base64 data URL.
|
|
144
81
|
*
|
|
145
|
-
* @
|
|
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
|
-
* ```
|
|
82
|
+
* @param url - Full URL or data URL pointing to `open_ultrahdr.wasm`.
|
|
151
83
|
*/
|
|
152
84
|
export function setWasmUrl(url: string): void {
|
|
153
|
-
|
|
85
|
+
explicitWasmUrl = url;
|
|
86
|
+
resetCache();
|
|
154
87
|
}
|
|
155
88
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return typeof metadata === 'object' && metadata !== null && '__wbg_ptr' in metadata;
|
|
89
|
+
function resetCache(): void {
|
|
90
|
+
initGeneration += 1;
|
|
91
|
+
wasmInstance = null;
|
|
92
|
+
initPromise = null;
|
|
161
93
|
}
|
|
162
94
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
async function toWasmMetadata(metadata: GainMapMetadata): Promise<WasmGainMapMetadata> {
|
|
167
|
-
// If it's already a WASM instance, return as-is
|
|
168
|
-
if (isWasmMetadataInstance(metadata)) {
|
|
169
|
-
return metadata as unknown as WasmGainMapMetadata;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Create a new WASM instance and copy properties
|
|
173
|
-
const wasm = await getWasm();
|
|
174
|
-
const wasmMetadata = wasm.createDefaultMetadata();
|
|
175
|
-
|
|
176
|
-
wasmMetadata.version = metadata.version;
|
|
177
|
-
wasmMetadata.baseRenditionIsHdr = metadata.baseRenditionIsHdr;
|
|
178
|
-
wasmMetadata.gainMapMin = metadata.gainMapMin;
|
|
179
|
-
wasmMetadata.gainMapMax = metadata.gainMapMax;
|
|
180
|
-
wasmMetadata.gamma = metadata.gamma;
|
|
181
|
-
wasmMetadata.offsetSdr = metadata.offsetSdr;
|
|
182
|
-
wasmMetadata.offsetHdr = metadata.offsetHdr;
|
|
183
|
-
wasmMetadata.hdrCapacityMin = metadata.hdrCapacityMin;
|
|
184
|
-
wasmMetadata.hdrCapacityMax = metadata.hdrCapacityMax;
|
|
185
|
-
|
|
186
|
-
return wasmMetadata;
|
|
95
|
+
function joinPath(base: string, name: string): string {
|
|
96
|
+
if (!base) return name;
|
|
97
|
+
return base.endsWith('/') ? `${base}${name}` : `${base}/${name}`;
|
|
187
98
|
}
|
|
188
99
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
async function getWasm(): Promise<UltraHdrWasmModule> {
|
|
193
|
-
if (wasmInstance) {
|
|
194
|
-
return wasmInstance;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (initPromise) {
|
|
198
|
-
return initPromise;
|
|
199
|
-
}
|
|
100
|
+
async function getWasm(): Promise<OpenUltraHdrModule> {
|
|
101
|
+
if (wasmInstance) return wasmInstance;
|
|
102
|
+
if (initPromise) return initPromise;
|
|
200
103
|
|
|
104
|
+
const generation = initGeneration;
|
|
201
105
|
initPromise = (async () => {
|
|
202
106
|
try {
|
|
203
|
-
|
|
204
|
-
const
|
|
107
|
+
const wasmModule = await import('open-ultrahdr-wasm');
|
|
108
|
+
const factory = (wasmModule as unknown as { default: typeof wasmModule.default }).default;
|
|
205
109
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
await UltraHdrWasm.default(wasmUrl);
|
|
211
|
-
} else if (location) {
|
|
212
|
-
const base = location.endsWith('/') ? location : `${location}/`;
|
|
213
|
-
const wasmPath = base + 'open_ultrahdr_bg.wasm';
|
|
110
|
+
const isNode =
|
|
111
|
+
typeof process !== 'undefined' && !!process.versions && !!process.versions.node;
|
|
112
|
+
|
|
113
|
+
const moduleOptions: { locateFile?: (path: string) => string; wasmBinary?: Uint8Array } = {};
|
|
214
114
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
115
|
+
if (explicitWasmUrl) {
|
|
116
|
+
moduleOptions.locateFile = (path: string) =>
|
|
117
|
+
path.endsWith('.wasm') ? explicitWasmUrl! : path;
|
|
118
|
+
} else if (location) {
|
|
119
|
+
if (isNode) {
|
|
120
|
+
// In Node, sidestep fetch entirely and provide the bytes directly.
|
|
121
|
+
const fs = await import('node:fs');
|
|
122
|
+
const wasmBytes = await fs.promises.readFile(joinPath(location, WASM_FILENAME));
|
|
123
|
+
moduleOptions.wasmBinary = new Uint8Array(wasmBytes);
|
|
221
124
|
} else {
|
|
222
|
-
|
|
223
|
-
|
|
125
|
+
moduleOptions.locateFile = (path: string) =>
|
|
126
|
+
path.endsWith('.wasm') ? joinPath(location, WASM_FILENAME) : path;
|
|
224
127
|
}
|
|
225
|
-
} else {
|
|
226
|
-
// Let the WASM module use its default URL resolution (import.meta.url)
|
|
227
|
-
await UltraHdrWasm.default();
|
|
228
128
|
}
|
|
229
129
|
|
|
230
|
-
|
|
231
|
-
|
|
130
|
+
const instance = (await factory(moduleOptions)) as unknown as OpenUltraHdrModule;
|
|
131
|
+
// If the configuration changed while we were initializing, drop this
|
|
132
|
+
// instance and fall back to a fresh init under the new config.
|
|
133
|
+
if (generation !== initGeneration) {
|
|
134
|
+
return getWasm();
|
|
135
|
+
}
|
|
136
|
+
wasmInstance = instance;
|
|
137
|
+
return instance;
|
|
232
138
|
} catch (err) {
|
|
233
|
-
|
|
139
|
+
// Only clear the in-flight promise if it still belongs to this generation;
|
|
140
|
+
// otherwise resetCache() has already moved on.
|
|
141
|
+
if (generation === initGeneration) {
|
|
142
|
+
initPromise = null;
|
|
143
|
+
}
|
|
234
144
|
throw err;
|
|
235
145
|
}
|
|
236
146
|
})();
|
|
@@ -240,20 +150,6 @@ async function getWasm(): Promise<UltraHdrWasmModule> {
|
|
|
240
150
|
|
|
241
151
|
/**
|
|
242
152
|
* Checks if a buffer contains an UltraHDR image.
|
|
243
|
-
*
|
|
244
|
-
* This is a fast check that looks for gain map metadata without
|
|
245
|
-
* fully decoding the image.
|
|
246
|
-
*
|
|
247
|
-
* @param buffer - JPEG file contents.
|
|
248
|
-
* @return True if the image contains UltraHDR/gain map data.
|
|
249
|
-
*
|
|
250
|
-
* @example
|
|
251
|
-
* ```typescript
|
|
252
|
-
* const buffer = await file.arrayBuffer();
|
|
253
|
-
* if (await isUltraHdr(buffer)) {
|
|
254
|
-
* console.log('This is an UltraHDR image!');
|
|
255
|
-
* }
|
|
256
|
-
* ```
|
|
257
153
|
*/
|
|
258
154
|
export async function isUltraHdr(buffer: ArrayBuffer): Promise<boolean> {
|
|
259
155
|
const wasm = await getWasm();
|
|
@@ -261,62 +157,35 @@ export async function isUltraHdr(buffer: ArrayBuffer): Promise<boolean> {
|
|
|
261
157
|
}
|
|
262
158
|
|
|
263
159
|
/**
|
|
264
|
-
* Probes an image to check if it's UltraHDR and extracts component
|
|
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.
|
|
160
|
+
* Probes an image to check if it's UltraHDR and extracts component info.
|
|
272
161
|
*
|
|
273
|
-
*
|
|
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
|
-
* ```
|
|
162
|
+
* Never throws — invalid inputs return a result with all flags set to false.
|
|
292
163
|
*/
|
|
293
164
|
export async function probeUltraHdr(buffer: ArrayBuffer): Promise<UltraHdrProbeResult> {
|
|
294
|
-
|
|
295
|
-
|
|
165
|
+
try {
|
|
166
|
+
const wasm = await getWasm();
|
|
167
|
+
return wasm.probeUltraHdr(new Uint8Array(buffer));
|
|
168
|
+
} catch {
|
|
169
|
+
return {
|
|
170
|
+
isValid: false,
|
|
171
|
+
hasPrimaryImage: false,
|
|
172
|
+
hasGainMap: false,
|
|
173
|
+
hasMetadata: false,
|
|
174
|
+
width: 0,
|
|
175
|
+
height: 0,
|
|
176
|
+
gainMapWidth: 0,
|
|
177
|
+
gainMapHeight: 0,
|
|
178
|
+
hdrCapacity: 0,
|
|
179
|
+
metadataVersion: '',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
296
182
|
}
|
|
297
183
|
|
|
298
184
|
/**
|
|
299
185
|
* Decodes an UltraHDR image, extracting all components.
|
|
300
|
-
*
|
|
301
|
-
* @param id - Unique identifier for this operation (for cancellation).
|
|
302
|
-
* @param buffer - UltraHDR JPEG file contents.
|
|
303
|
-
* @return Decoded result with SDR image, gain map, and metadata.
|
|
304
|
-
*
|
|
305
|
-
* @throws Error if the buffer is not a valid UltraHDR JPEG.
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* ```typescript
|
|
309
|
-
* const buffer = await file.arrayBuffer();
|
|
310
|
-
* const result = await decodeUltraHdr('upload-1', buffer);
|
|
311
|
-
*
|
|
312
|
-
* // Access components
|
|
313
|
-
* const sdrBlob = new Blob([result.sdrImage], { type: 'image/jpeg' });
|
|
314
|
-
* console.log('Image size:', result.width, 'x', result.height);
|
|
315
|
-
* console.log('HDR capacity:', result.metadata.hdrCapacityMax);
|
|
316
|
-
* ```
|
|
317
186
|
*/
|
|
318
187
|
export async function decodeUltraHdr(
|
|
319
|
-
|
|
188
|
+
_id: ItemId,
|
|
320
189
|
buffer: ArrayBuffer
|
|
321
190
|
): Promise<UltraHdrDecodeResult> {
|
|
322
191
|
const wasm = await getWasm();
|
|
@@ -325,56 +194,20 @@ export async function decodeUltraHdr(
|
|
|
325
194
|
|
|
326
195
|
/**
|
|
327
196
|
* Encodes an UltraHDR JPEG from SDR and HDR inputs.
|
|
328
|
-
*
|
|
329
|
-
* @param id - Unique identifier for this operation.
|
|
330
|
-
* @param sdrBuffer - SDR JPEG image bytes.
|
|
331
|
-
* @param hdrBuffer - HDR linear RGB data (Float32Array, 3 values per pixel).
|
|
332
|
-
* @param options - Encoding options.
|
|
333
|
-
* @return Encoded UltraHDR JPEG as ArrayBuffer.
|
|
334
|
-
*
|
|
335
|
-
* @throws Error if inputs are invalid or dimensions don't match.
|
|
336
|
-
*
|
|
337
|
-
* @example
|
|
338
|
-
* ```typescript
|
|
339
|
-
* const sdrBuffer = await sdrFile.arrayBuffer();
|
|
340
|
-
* const hdrData = await getHdrLinearData(); // Float32Array
|
|
341
|
-
*
|
|
342
|
-
* const ultraHdr = await encodeUltraHdr('encode-1', sdrBuffer, hdrData, {
|
|
343
|
-
* ...defaultEncodeOptions,
|
|
344
|
-
* targetHdrCapacity: 4.0,
|
|
345
|
-
* });
|
|
346
|
-
*
|
|
347
|
-
* // Create downloadable file
|
|
348
|
-
* const blob = new Blob([ultraHdr], { type: 'image/jpeg' });
|
|
349
|
-
* ```
|
|
350
197
|
*/
|
|
351
198
|
export async function encodeUltraHdr(
|
|
352
|
-
|
|
199
|
+
_id: ItemId,
|
|
353
200
|
sdrBuffer: ArrayBuffer,
|
|
354
201
|
hdrBuffer: ArrayBuffer,
|
|
355
202
|
options?: Partial<UltraHdrEncodeOptions>
|
|
356
203
|
): Promise<ArrayBuffer> {
|
|
357
204
|
const wasm = await getWasm();
|
|
358
|
-
|
|
359
|
-
// Create a proper WASM options instance
|
|
360
|
-
const wasmOpts = wasm.createDefaultOptions();
|
|
361
|
-
|
|
362
|
-
// Apply user-provided options
|
|
363
|
-
const mergedOpts = { ...defaultEncodeOptions, ...options };
|
|
364
|
-
wasmOpts.baseQuality = mergedOpts.baseQuality;
|
|
365
|
-
wasmOpts.gainMapQuality = mergedOpts.gainMapQuality;
|
|
366
|
-
wasmOpts.targetHdrCapacity = mergedOpts.targetHdrCapacity;
|
|
367
|
-
wasmOpts.includeIsoMetadata = mergedOpts.includeIsoMetadata;
|
|
368
|
-
wasmOpts.includeUltrahdrV1 = mergedOpts.includeUltrahdrV1;
|
|
369
|
-
wasmOpts.gainMapScale = mergedOpts.gainMapScale;
|
|
370
|
-
|
|
205
|
+
const merged: UltraHdrEncodeOptions = { ...defaultEncodeOptions, ...options };
|
|
371
206
|
const result = wasm.encodeUltraHdr(
|
|
372
207
|
new Uint8Array(sdrBuffer),
|
|
373
208
|
new Float32Array(hdrBuffer),
|
|
374
|
-
|
|
209
|
+
merged
|
|
375
210
|
);
|
|
376
|
-
|
|
377
|
-
// Ensure we return a proper ArrayBuffer (not SharedArrayBuffer)
|
|
378
211
|
return result.buffer.slice(
|
|
379
212
|
result.byteOffset,
|
|
380
213
|
result.byteOffset + result.byteLength
|
|
@@ -383,26 +216,10 @@ export async function encodeUltraHdr(
|
|
|
383
216
|
|
|
384
217
|
/**
|
|
385
218
|
* Extracts the SDR base image from an UltraHDR JPEG.
|
|
386
|
-
*
|
|
387
|
-
* This produces a standard JPEG that can be displayed on any device,
|
|
388
|
-
* without the gain map metadata. Useful for backwards compatibility.
|
|
389
|
-
*
|
|
390
|
-
* @param buffer - UltraHDR JPEG file contents.
|
|
391
|
-
* @return Standard JPEG without gain map.
|
|
392
|
-
*
|
|
393
|
-
* @example
|
|
394
|
-
* ```typescript
|
|
395
|
-
* const ultraHdrBuffer = await file.arrayBuffer();
|
|
396
|
-
* const sdrBuffer = await extractSdrBase(ultraHdrBuffer);
|
|
397
|
-
*
|
|
398
|
-
* // Use the SDR image for non-HDR displays
|
|
399
|
-
* const blob = new Blob([sdrBuffer], { type: 'image/jpeg' });
|
|
400
|
-
* ```
|
|
401
219
|
*/
|
|
402
220
|
export async function extractSdrBase(buffer: ArrayBuffer): Promise<ArrayBuffer> {
|
|
403
221
|
const wasm = await getWasm();
|
|
404
222
|
const result = wasm.extractSdrBase(new Uint8Array(buffer));
|
|
405
|
-
// Ensure we return a proper ArrayBuffer (not SharedArrayBuffer)
|
|
406
223
|
return result.buffer.slice(
|
|
407
224
|
result.byteOffset,
|
|
408
225
|
result.byteOffset + result.byteLength
|
|
@@ -411,20 +228,6 @@ export async function extractSdrBase(buffer: ArrayBuffer): Promise<ArrayBuffer>
|
|
|
411
228
|
|
|
412
229
|
/**
|
|
413
230
|
* Gets gain map metadata from an UltraHDR JPEG.
|
|
414
|
-
*
|
|
415
|
-
* This is faster than `decodeUltraHdr` when you only need the metadata.
|
|
416
|
-
*
|
|
417
|
-
* @param buffer - UltraHDR JPEG file contents.
|
|
418
|
-
* @return Gain map metadata.
|
|
419
|
-
*
|
|
420
|
-
* @throws Error if the buffer doesn't contain gain map metadata.
|
|
421
|
-
*
|
|
422
|
-
* @example
|
|
423
|
-
* ```typescript
|
|
424
|
-
* const metadata = await getMetadata(buffer);
|
|
425
|
-
* console.log('Version:', metadata.version);
|
|
426
|
-
* console.log('HDR headroom:', metadata.hdrCapacityMax, 'stops');
|
|
427
|
-
* ```
|
|
428
231
|
*/
|
|
429
232
|
export async function getMetadata(buffer: ArrayBuffer): Promise<GainMapMetadata> {
|
|
430
233
|
const wasm = await getWasm();
|
|
@@ -433,36 +236,24 @@ export async function getMetadata(buffer: ArrayBuffer): Promise<GainMapMetadata>
|
|
|
433
236
|
|
|
434
237
|
/**
|
|
435
238
|
* Validates gain map metadata.
|
|
436
|
-
*
|
|
437
|
-
* @param metadata - The metadata to validate.
|
|
438
|
-
* @return True if the metadata is valid.
|
|
439
239
|
*/
|
|
440
240
|
export async function validateMetadata(metadata: GainMapMetadata): Promise<boolean> {
|
|
441
241
|
const wasm = await getWasm();
|
|
442
|
-
|
|
443
|
-
return wasm.validateMetadata(wasmMetadata);
|
|
242
|
+
return wasm.validateMetadata(metadata);
|
|
444
243
|
}
|
|
445
244
|
|
|
446
245
|
/**
|
|
447
246
|
* Estimates the HDR headroom from metadata.
|
|
448
|
-
*
|
|
449
|
-
* @param metadata - The gain map metadata.
|
|
450
|
-
* @return Maximum additional stops of dynamic range above SDR.
|
|
451
247
|
*/
|
|
452
248
|
export async function estimateHdrHeadroom(metadata: GainMapMetadata): Promise<number> {
|
|
453
249
|
const wasm = await getWasm();
|
|
454
|
-
|
|
455
|
-
return wasm.estimateHdrHeadroom(wasmMetadata);
|
|
250
|
+
return wasm.estimateHdrHeadroom(metadata);
|
|
456
251
|
}
|
|
457
252
|
|
|
458
253
|
/**
|
|
459
254
|
* Checks if metadata indicates a meaningful HDR image.
|
|
460
|
-
*
|
|
461
|
-
* @param metadata - The gain map metadata.
|
|
462
|
-
* @return True if the gain map provides significant dynamic range extension.
|
|
463
255
|
*/
|
|
464
256
|
export async function isMeaningfulHdr(metadata: GainMapMetadata): Promise<boolean> {
|
|
465
257
|
const wasm = await getWasm();
|
|
466
|
-
|
|
467
|
-
return wasm.isMeaningfulHdr(wasmMetadata);
|
|
258
|
+
return wasm.isMeaningfulHdr(metadata);
|
|
468
259
|
}
|
package/src/types.ts
CHANGED
|
@@ -115,10 +115,24 @@ export interface UltraHdrEncodeOptions {
|
|
|
115
115
|
/** Target HDR capacity (typically 2.0-4.0) */
|
|
116
116
|
targetHdrCapacity: number;
|
|
117
117
|
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Whether to include ISO 21496-1 metadata.
|
|
120
|
+
*
|
|
121
|
+
* Currently a no-op: libultrahdr's encoder unconditionally emits ISO
|
|
122
|
+
* 21496-1 metadata and exposes no public toggle. Kept for API stability
|
|
123
|
+
* with prior versions; setting this to `false` will not suppress the
|
|
124
|
+
* metadata block.
|
|
125
|
+
*/
|
|
119
126
|
includeIsoMetadata: boolean;
|
|
120
127
|
|
|
121
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Whether to include UltraHDR v1 metadata for Android compatibility.
|
|
130
|
+
*
|
|
131
|
+
* Currently a no-op: libultrahdr's encoder unconditionally emits the v1
|
|
132
|
+
* metadata block and exposes no public toggle. Kept for API stability
|
|
133
|
+
* with prior versions; setting this to `false` will not suppress the
|
|
134
|
+
* metadata block.
|
|
135
|
+
*/
|
|
122
136
|
includeUltrahdrV1: boolean;
|
|
123
137
|
|
|
124
138
|
/** Downscale factor for the gain map (1 = same size, 2 = half, 4 = quarter) */
|