@keeratita/heic-converter 0.2.0 โ†’ 0.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @keeratita/heic-converter
2
2
 
3
- A modern, lightweight TypeScript library to convert `.heic` and `.heif` images to standard web formats (JPEG, PNG, SVG) client-side in the browser or on the backend in Node.js.
3
+ A modern, lightweight TypeScript library to convert `.heic` and `.heif` images to standard web formats (JPEG, PNG, WebP, SVG) client-side in the browser or on the backend in Node.js.
4
4
 
5
5
  Designed specifically for environments with strict **Content Security Policy (CSP)** rules, it is built with WebAssembly compiled **without** dynamic code execution (`eval()` or `new Function()`).
6
6
 
@@ -10,10 +10,13 @@ Designed specifically for environments with strict **Content Security Policy (CS
10
10
 
11
11
  - ๐Ÿ”’ **CSP Compliant**: Emscripten glue code is compiled with `-s DYNAMIC_EXECUTION=0`. Safe to run without `'unsafe-eval'`.
12
12
  - ๐Ÿงฉ **Dependency Injection Architecture**: Swap the decoder module easily by implementing a simple `IHeicDecoder` interface.
13
- - โšก **Optimized Performance**: Reuses a single shared WASM instance across calls by default. Decodes an image in milliseconds.
13
+ - โšก **Optimized Performance**: A fresh decoder instance is created and released per conversion โ€” memory is reclaimed promptly and concurrent conversions never share mutable WASM state.
14
14
  - ๐ŸŒ **Isomorphic / Universal**: Runs in Node.js (decoding) and browser (decoding & canvas-based encoding).
15
15
  - ๐Ÿ“ฆ **No Bloat**: Zero external production dependencies. Small footprint.
16
- - ๐ŸŽจ **Format Support**: Convert to `jpeg` (with quality configuration), `png`, and `svg` (embedded lossless vector).
16
+ - ๐ŸŽจ **Format Support**: Convert to `jpeg` (with quality configuration), `png`, `webp`, and `svg` (embedded lossless vector).
17
+ - ๐Ÿ“ **Resize Support**: Downscale with `maxWidth`/`maxHeight` or apply a uniform `scale` factor during conversion.
18
+ - ๐Ÿ“š **Batch Conversion**: Convert many images at once with bounded concurrency via `convertMany`.
19
+ - ๐Ÿงต **Web Worker Helper**: Offload conversions to a Web Worker with `convertHeicInWorker` to keep the UI thread responsive.
17
20
 
18
21
  ---
19
22
 
@@ -56,7 +59,32 @@ const imageUrl = URL.createObjectURL(jpegBlob);
56
59
  document.querySelector('img').src = imageUrl;
57
60
  ```
58
61
 
59
- ### 2. Browser: Serving and Locating WASM (Custom Assets Path)
62
+ ### 2. Browser: WebP Output
63
+
64
+ WebP offers excellent compression with quality configuration:
65
+
66
+ ```typescript
67
+ import { convertHeic } from '@keeratita/heic-converter';
68
+
69
+ const webpBlob = await convertHeic(heicBlob, {
70
+ to: 'webp',
71
+ quality: 0.8,
72
+ });
73
+ ```
74
+
75
+ ### 3. Browser: SVG Output
76
+
77
+ SVG wraps the raster image as an embedded lossless PNG inside an SVG container:
78
+
79
+ ```typescript
80
+ import { convertHeic } from '@keeratita/heic-converter';
81
+
82
+ const svgBlob = await convertHeic(heicBlob, {
83
+ to: 'svg',
84
+ });
85
+ ```
86
+
87
+ ### 4. Browser: Serving and Locating WASM (Custom Assets Path)
60
88
 
61
89
  By default, the library tries to fetch `heic-decoder.wasm` relative to the current module script path (`import.meta.url`).
62
90
 
@@ -93,7 +121,7 @@ const jpegBlob = await convertHeic(heicBlob, {
93
121
  });
94
122
  ```
95
123
 
96
- ### 3. Node.js: Decoding Raw Pixel Data
124
+ ### 5. Node.js: Decoding Raw Pixel Data
97
125
 
98
126
  Since Node.js lacks the native browser Canvas API, `convertHeic` (which relies on Canvas to encode raster formats) will throw an error on the backend.
99
127
 
@@ -110,11 +138,12 @@ async function convertNode() {
110
138
  const decoder = new LibheifDecoder();
111
139
  await decoder.initialize();
112
140
 
113
- // Decodes to { width, height, data: Uint8ClampedArray (RGBA) }
141
+ // Decodes to { width, height, data: Uint8ClampedArray (RGBA) }.
142
+ // data is an independent copy โ€” safe to use after decoder.free().
114
143
  const { width, height, data } = await decoder.decode(heicData);
115
144
 
116
145
  // Process raw pixels using sharp
117
- await sharp(Buffer.from(data.buffer), {
146
+ await sharp(Buffer.from(data), {
118
147
  raw: { width, height, channels: 4 },
119
148
  })
120
149
  .toFormat('jpeg')
@@ -125,7 +154,7 @@ async function convertNode() {
125
154
  }
126
155
  ```
127
156
 
128
- ### 4. Progress Tracking (e.g. for Large Images)
157
+ ### 6. Progress Tracking (e.g. for Large Images)
129
158
 
130
159
  For large images, you can pass an `onProgress` callback to track the conversion progress (0% to 100%):
131
160
 
@@ -145,6 +174,89 @@ const jpegBlob = await convertHeic(heicBlob, {
145
174
  > [!TIP]
146
175
  > Since the WebAssembly module runs on the main browser thread, the UI thread will be occupied during conversion. For maximum responsiveness when converting large images, it is highly recommended to run this library inside a standard JS **Web Worker** and communicate progress back to the main thread.
147
176
 
177
+ ### 7. Resizing Images
178
+
179
+ Downscale to fit within maximum dimensions (aspect ratio is preserved, images smaller than the bounds are never upscaled):
180
+
181
+ ```typescript
182
+ import { convertHeic } from '@keeratita/heic-converter';
183
+
184
+ const thumbnailBlob = await convertHeic(heicBlob, {
185
+ to: 'jpeg',
186
+ maxWidth: 800,
187
+ maxHeight: 600,
188
+ });
189
+ ```
190
+
191
+ Or apply a uniform scale factor (can also upscale):
192
+
193
+ ```typescript
194
+ const halfSizeBlob = await convertHeic(heicBlob, {
195
+ to: 'webp',
196
+ scale: 0.5,
197
+ });
198
+ ```
199
+
200
+ ### 8. Batch Conversion
201
+
202
+ Convert many images at once with bounded concurrency (default `4`). Results are returned in input order; if any conversion fails, the promise rejects as soon as the failure is known with an error that identifies the failing item:
203
+
204
+ ```typescript
205
+ import { convertMany } from '@keeratita/heic-converter';
206
+
207
+ const blobs = await convertMany(heicFiles, {
208
+ to: 'png',
209
+ concurrency: 3,
210
+ onProgress: (index, percent) => {
211
+ console.log(`Image ${index}: ${Math.round(percent)}%`);
212
+ },
213
+ });
214
+ ```
215
+
216
+ ### 9. Web Worker Conversion
217
+
218
+ Run the conversion inside a Web Worker so the main thread stays responsive. Create a worker script that uses this library:
219
+
220
+ ```js
221
+ // converter.worker.js
222
+ import { convertHeic } from '@keeratita/heic-converter';
223
+
224
+ self.onmessage = async (event) => {
225
+ const { input, options } = event.data;
226
+ try {
227
+ const blob = await convertHeic(input, {
228
+ ...options,
229
+ onProgress: (percent) => self.postMessage({ type: 'progress', percent }),
230
+ });
231
+ self.postMessage({ type: 'result', ok: true, blob });
232
+ } catch (error) {
233
+ self.postMessage({ type: 'result', ok: false, error: error?.stack ?? error?.message ?? String(error) });
234
+ }
235
+ };
236
+ ```
237
+
238
+ Then convert from the main thread:
239
+
240
+ ```typescript
241
+ import { convertHeicInWorker } from '@keeratita/heic-converter';
242
+
243
+ const jpegBlob = await convertHeicInWorker(heicBlob, {
244
+ workerUrl: new URL('./converter.worker.js', import.meta.url),
245
+ workerType: 'module',
246
+ to: 'jpeg',
247
+ quality: 0.9,
248
+ onProgress: (percent) => console.log(`${Math.round(percent)}%`),
249
+ });
250
+ ```
251
+
252
+ > [!NOTE]
253
+ > - Use `workerType: 'module'` when the worker script uses ES module imports (as in the example above). The default is `'classic'`, which requires the script to be pre-bundled (e.g. by Vite or webpack) โ€” static ES imports are not supported in classic workers.
254
+ > - `workerUrl` should be a compile-time constant; the script runs with the page's privileges.
255
+ > - Only `progress` and `result` messages are understood; any other message type is ignored.
256
+ > - `decoder`, `onProgress`, and `workerUrl` are not structured-cloneable, so they are not sent to the worker. Progress is forwarded through `{ type: 'progress', percent }` messages instead.
257
+ > - `timeoutMs` (default `60000`) bounds how long the promise waits for a result; set `0` to disable.
258
+ > - This helper is **browser-only**: it rejects in Node.js, where there is no global `Worker`.
259
+
148
260
  ---
149
261
 
150
262
  ## ๐Ÿ”’ Content Security Policy (CSP)
@@ -168,10 +280,35 @@ Converts a HEIC image file to a standard web format.
168
280
 
169
281
  - **`input`**: `Blob | File | ArrayBuffer | Uint8Array`
170
282
  - **`options`**: (optional) `ConvertOptions`
171
- - `to`: `'jpeg' | 'jpg' | 'png' | 'svg'` (Default: `'jpeg'`)
172
- - `quality`: `number` (0.0 to 1.0, only applicable to JPEG. Default: `0.92`)
283
+ - `to`: `'jpeg' | 'jpg' | 'png' | 'webp' | 'svg'` (Default: `'jpeg'`)
284
+ - `quality`: `number` (0.0 to 1.0, applicable to JPEG and WebP. Default: `0.92`)
173
285
  - `decoder`: `IHeicDecoder` (Inject custom decoder instance)
174
286
  - `onProgress`: `(percent: number) => void` (Optional callback, receives progress percentage from `0` to `100` during decoding)
287
+ - `maxWidth`: `number` (Downscale to fit within this width, preserving aspect ratio. Never upscales)
288
+ - `maxHeight`: `number` (Downscale to fit within this height, preserving aspect ratio. Never upscales)
289
+ - `scale`: `number` (Uniform scale factor, e.g. `0.5` halves the image. Takes precedence over `maxWidth`/`maxHeight`)
290
+ - **Returns**: `Promise<Blob>`
291
+
292
+ ### `convertMany(inputs, options?)`
293
+
294
+ Converts multiple HEIC images with bounded concurrency. Results are returned in input order; rejects as soon as a conversion fails, with an error identifying the failing item index.
295
+
296
+ - **`inputs`**: `Array<Blob | File | ArrayBuffer | Uint8Array>`
297
+ - **`options`**: (optional) `ConvertManyOptions` โ€” same as `ConvertOptions`, except `onProgress` uses the batch signature below; plus:
298
+ - `concurrency`: `number` (Maximum concurrent conversions. Default: `4`)
299
+ - `onProgress`: `(index: number, percent: number) => void` (Per-item progress callback)
300
+ - `decoder`: `IHeicDecoder` (Optional. When provided, the same instance is shared by all concurrent conversions and must be safe for concurrent `decode()` calls)
301
+ - **Returns**: `Promise<Blob[]>`
302
+
303
+ ### `convertHeicInWorker(input, options)`
304
+
305
+ Converts a HEIC image inside a Web Worker. The worker script must implement the message protocol shown in [Usage section 9](#9-web-worker-conversion). Browser-only; rejects in Node.js.
306
+
307
+ - **`input`**: `Blob | File | ArrayBuffer | Uint8Array`
308
+ - **`options`**: `WorkerConvertOptions` โ€” same as `ConvertOptions`, plus:
309
+ - `workerUrl`: `string | URL` (URL of the worker script; should be a compile-time constant)
310
+ - `workerType`: `'classic' | 'module'` (Worker script type. Default: `'classic'`; use `'module'` for scripts with ES imports)
311
+ - `timeoutMs`: `number` (Maximum wait for the result in milliseconds. Default: `60000`; `0` disables)
175
312
  - **Returns**: `Promise<Blob>`
176
313
 
177
314
  ### `LibheifDecoder(options?)`
@@ -182,13 +319,13 @@ The default WASM-based implementation of `IHeicDecoder`.
182
319
  - `locateFile`: `(path: string, prefix: string) => string`
183
320
  - `wasmBinary`: `ArrayBuffer`
184
321
  - **Methods**:
185
- - `initialize(): Promise<void>`: Loads and initializes the WASM wrapper.
186
- - `decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>`: Decodes the HEIC bytes to raw RGBA, with optional progress callback.
187
- - `free(): void`: Releases allocated WebAssembly heap memory.
322
+ - `initialize(): Promise<void>`: Loads and initializes the WASM wrapper. Safe to call concurrently; the module loads at most once per instance.
323
+ - `decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>`: Decodes the HEIC bytes to raw RGBA, with optional progress callback. Returns an independent copy of the pixel data โ€” safe to use after `free()`.
324
+ - `free(): void`: Releases allocated WebAssembly heap memory. Idempotent.
188
325
 
189
326
  ### `freeSharedDecoder()`
190
327
 
191
- The library keeps a shared instance of `LibheifDecoder` to speed up subsequent calls. Call `freeSharedDecoder()` when your application is done converting images to release memory.
328
+ Kept for **API compatibility**. Decoders are now created and released per conversion, so there is no shared instance to release โ€” calling this function is a no-op.
192
329
 
193
330
  ```typescript
194
331
  import { freeSharedDecoder } from '@keeratita/heic-converter';
@@ -225,6 +362,15 @@ npm run build
225
362
  npm run test
226
363
  ```
227
364
 
365
+ ### Run Browser E2E Tests
366
+
367
+ Real browser conversions against the CSP sandbox and the GitHub Pages demo (`docs/`):
368
+
369
+ ```bash
370
+ npx playwright install chromium # one-time
371
+ npm run test:e2e
372
+ ```
373
+
228
374
  ### Run Interactive CSP Sandbox
229
375
 
230
376
  To test the converter in a local browser running under a strict Content Security Policy, start the sandbox server:
Binary file
package/dist/index.d.mts CHANGED
@@ -19,8 +19,28 @@ interface IHeicDecoder {
19
19
  */
20
20
  free(): void;
21
21
  }
22
- type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
23
- interface ConvertOptions {
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
+ type HeicInput = Blob | File | ArrayBuffer | Uint8Array;
24
+ interface ResizeOptions {
25
+ /**
26
+ * Maximum width in pixels. The image is downscaled to fit within this
27
+ * bound while preserving the aspect ratio. Images smaller than the bound
28
+ * are never upscaled.
29
+ */
30
+ maxWidth?: number;
31
+ /**
32
+ * Maximum height in pixels. The image is downscaled to fit within this
33
+ * bound while preserving the aspect ratio. Images smaller than the bound
34
+ * are never upscaled.
35
+ */
36
+ maxHeight?: number;
37
+ /**
38
+ * Uniform scale factor applied to both dimensions (e.g. 0.5 halves the
39
+ * image). Takes precedence over `maxWidth` and `maxHeight` when set.
40
+ */
41
+ scale?: number;
42
+ }
43
+ interface ConvertOptions extends ResizeOptions {
24
44
  /**
25
45
  * Target format for the conversion.
26
46
  * @default 'jpeg'
@@ -28,7 +48,7 @@ interface ConvertOptions {
28
48
  to?: ImageFormat;
29
49
  /**
30
50
  * Quality of the converted image (between 0.0 and 1.0).
31
- * Only applicable for 'jpeg' (and 'jpg') format.
51
+ * Applicable for 'jpeg', 'jpg', and 'webp' formats.
32
52
  * @default 0.92
33
53
  */
34
54
  quality?: number;
@@ -42,6 +62,25 @@ interface ConvertOptions {
42
62
  */
43
63
  onProgress?: (percent: number) => void;
44
64
  }
65
+ interface ConvertManyOptions extends Omit<ConvertOptions, 'onProgress'> {
66
+ /**
67
+ * Maximum number of conversions running concurrently.
68
+ * @default 4
69
+ */
70
+ concurrency?: number;
71
+ /**
72
+ * Optional progress callback that receives the item index and its
73
+ * progress percentage (0 to 100) during decoding.
74
+ */
75
+ onProgress?: (index: number, percent: number) => void;
76
+ /**
77
+ * Optional custom decoder implementation to inject. When provided, the
78
+ * same instance is shared by all concurrent conversions, so it must be
79
+ * safe for concurrent `decode()` calls. If not provided, a fresh
80
+ * default LibheifDecoder is created per item.
81
+ */
82
+ decoder?: IHeicDecoder;
83
+ }
45
84
 
46
85
  interface LibheifDecoderOptions {
47
86
  /**
@@ -59,9 +98,15 @@ declare class LibheifDecoder implements IHeicDecoder {
59
98
  private options?;
60
99
  private module;
61
100
  private decoderInstance;
101
+ /**
102
+ * Memoized module-loading promise so concurrent initialize()/decode() calls
103
+ * on the same instance never instantiate (or mutate) the module twice.
104
+ */
105
+ private initPromise;
62
106
  constructor(options?: LibheifDecoderOptions);
63
107
  /**
64
108
  * Initializes the WebAssembly module and instantiates the HEIC decoder.
109
+ * Safe to call concurrently: the module is loaded at most once per instance.
65
110
  */
66
111
  initialize(): Promise<void>;
67
112
  /**
@@ -72,22 +117,105 @@ declare class LibheifDecoder implements IHeicDecoder {
72
117
  decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
118
  /**
74
119
  * Cleans up the WebAssembly decoder instance and resources.
120
+ * Idempotent: safe to call multiple times. After free(), the instance must
121
+ * be re-initialized (createHeicDecoderModule runs again on the next call).
75
122
  */
76
123
  free(): void;
77
124
  }
78
125
 
126
+ interface WorkerConvertOptions extends ConvertOptions {
127
+ /**
128
+ * URL of the worker script that performs the conversion. Should be a
129
+ * compile-time constant (e.g. `new URL('./worker.js', import.meta.url)`
130
+ * under a bundler); the script runs with the page's privileges.
131
+ */
132
+ workerUrl: string | URL;
133
+ /**
134
+ * Maximum time in milliseconds to wait for the worker result before
135
+ * rejecting with a timeout. Defaults to 60000 (60s). Set to 0 to
136
+ * disable the timeout.
137
+ */
138
+ timeoutMs?: number;
139
+ /**
140
+ * Worker script type. Use `'module'` when the script uses ES module
141
+ * imports (e.g. `import { convertHeic } from ...`); `'classic'`
142
+ * scripts must be pre-bundled.
143
+ * @default 'classic'
144
+ */
145
+ workerType?: 'classic' | 'module';
146
+ }
147
+ /**
148
+ * Converts a HEIC image inside a Web Worker so the main thread stays
149
+ * responsive during the (potentially slow) WASM decode.
150
+ *
151
+ * The worker script is user-provided and must handle the following message
152
+ * protocol:
153
+ *
154
+ * ```js
155
+ * // converter.worker.js
156
+ * import { convertHeic } from '@keeratita/heic-converter';
157
+ *
158
+ * self.onmessage = async (event) => {
159
+ * const { input, options } = event.data;
160
+ * try {
161
+ * const blob = await convertHeic(input, {
162
+ * ...options,
163
+ * onProgress: (percent) => self.postMessage({ type: 'progress', percent }),
164
+ * });
165
+ * self.postMessage({ type: 'result', ok: true, blob });
166
+ * } catch (error) {
167
+ * self.postMessage({ type: 'result', ok: false, error: error?.stack ?? error?.message ?? String(error) });
168
+ * }
169
+ * };
170
+ * ```
171
+ *
172
+ * ```ts
173
+ * const jpegBlob = await convertHeicInWorker(heicBlob, {
174
+ * workerUrl: new URL('./converter.worker.js', import.meta.url),
175
+ * workerType: 'module',
176
+ * to: 'jpeg',
177
+ * });
178
+ * ```
179
+ *
180
+ * Only `progress` and `result` messages are understood; any other message
181
+ * type is ignored. Use `workerType: 'module'` when the script uses ES
182
+ * module imports (as in the example above); `'classic'` scripts must be
183
+ * pre-bundled, since static ES imports are not supported there.
184
+ *
185
+ * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
186
+ * @param options Conversion options plus the worker script URL.
187
+ * @returns A Promise resolving to the converted image as a Blob.
188
+ */
189
+ declare function convertHeicInWorker(input: HeicInput, options: WorkerConvertOptions): Promise<Blob>;
190
+
79
191
  /**
80
- * Releases the resources allocated for the shared default decoder instance.
81
- * Call this when no further HEIC conversions are needed to free memory.
192
+ * Releases any cached decoder resources.
193
+ *
194
+ * Decoders are now created and released per conversion, so there is no shared
195
+ * instance to free. This function is kept for API compatibility.
82
196
  */
83
197
  declare function freeSharedDecoder(): void;
84
198
  /**
85
- * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
199
+ * Converts HEIC image data to a standard web format (JPEG, PNG, WebP, or SVG).
86
200
  *
87
201
  * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
202
  * @param options Conversion configuration options.
89
203
  * @returns A Promise resolving to the converted image as a Blob.
90
204
  */
91
- declare function convertHeic(input: Blob | File | ArrayBuffer | Uint8Array, options?: ConvertOptions): Promise<Blob>;
205
+ declare function convertHeic(input: HeicInput, options?: ConvertOptions): Promise<Blob>;
206
+ /**
207
+ * Converts multiple HEIC images to a standard web format.
208
+ *
209
+ * Conversions run with a bounded concurrency (default 4) and results are
210
+ * returned in the same order as the inputs. If any conversion fails, the
211
+ * returned promise rejects as soon as the failure is known (in-flight
212
+ * conversions are allowed to finish in the background) with an error that
213
+ * identifies the failing item index.
214
+ *
215
+ * @param inputs HEIC images as Blobs, Files, ArrayBuffers, or Uint8Arrays.
216
+ * @param options Batch conversion options.
217
+ * @returns A Promise resolving to the converted images as Blobs, in input order.
218
+ */
219
+ declare function convertMany(inputs: HeicInput[], options?: ConvertManyOptions): Promise<Blob[]>;
92
220
 
93
- export { type ConvertOptions, type DecodedImage, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, convertHeic, freeSharedDecoder };
221
+ export { type ConvertManyOptions, type ConvertOptions, type DecodedImage, type HeicInput, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, type ResizeOptions, type WorkerConvertOptions, convertHeic, convertHeicInWorker, convertMany, freeSharedDecoder };
package/dist/index.d.ts CHANGED
@@ -19,8 +19,28 @@ interface IHeicDecoder {
19
19
  */
20
20
  free(): void;
21
21
  }
22
- type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
23
- interface ConvertOptions {
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
+ type HeicInput = Blob | File | ArrayBuffer | Uint8Array;
24
+ interface ResizeOptions {
25
+ /**
26
+ * Maximum width in pixels. The image is downscaled to fit within this
27
+ * bound while preserving the aspect ratio. Images smaller than the bound
28
+ * are never upscaled.
29
+ */
30
+ maxWidth?: number;
31
+ /**
32
+ * Maximum height in pixels. The image is downscaled to fit within this
33
+ * bound while preserving the aspect ratio. Images smaller than the bound
34
+ * are never upscaled.
35
+ */
36
+ maxHeight?: number;
37
+ /**
38
+ * Uniform scale factor applied to both dimensions (e.g. 0.5 halves the
39
+ * image). Takes precedence over `maxWidth` and `maxHeight` when set.
40
+ */
41
+ scale?: number;
42
+ }
43
+ interface ConvertOptions extends ResizeOptions {
24
44
  /**
25
45
  * Target format for the conversion.
26
46
  * @default 'jpeg'
@@ -28,7 +48,7 @@ interface ConvertOptions {
28
48
  to?: ImageFormat;
29
49
  /**
30
50
  * Quality of the converted image (between 0.0 and 1.0).
31
- * Only applicable for 'jpeg' (and 'jpg') format.
51
+ * Applicable for 'jpeg', 'jpg', and 'webp' formats.
32
52
  * @default 0.92
33
53
  */
34
54
  quality?: number;
@@ -42,6 +62,25 @@ interface ConvertOptions {
42
62
  */
43
63
  onProgress?: (percent: number) => void;
44
64
  }
65
+ interface ConvertManyOptions extends Omit<ConvertOptions, 'onProgress'> {
66
+ /**
67
+ * Maximum number of conversions running concurrently.
68
+ * @default 4
69
+ */
70
+ concurrency?: number;
71
+ /**
72
+ * Optional progress callback that receives the item index and its
73
+ * progress percentage (0 to 100) during decoding.
74
+ */
75
+ onProgress?: (index: number, percent: number) => void;
76
+ /**
77
+ * Optional custom decoder implementation to inject. When provided, the
78
+ * same instance is shared by all concurrent conversions, so it must be
79
+ * safe for concurrent `decode()` calls. If not provided, a fresh
80
+ * default LibheifDecoder is created per item.
81
+ */
82
+ decoder?: IHeicDecoder;
83
+ }
45
84
 
46
85
  interface LibheifDecoderOptions {
47
86
  /**
@@ -59,9 +98,15 @@ declare class LibheifDecoder implements IHeicDecoder {
59
98
  private options?;
60
99
  private module;
61
100
  private decoderInstance;
101
+ /**
102
+ * Memoized module-loading promise so concurrent initialize()/decode() calls
103
+ * on the same instance never instantiate (or mutate) the module twice.
104
+ */
105
+ private initPromise;
62
106
  constructor(options?: LibheifDecoderOptions);
63
107
  /**
64
108
  * Initializes the WebAssembly module and instantiates the HEIC decoder.
109
+ * Safe to call concurrently: the module is loaded at most once per instance.
65
110
  */
66
111
  initialize(): Promise<void>;
67
112
  /**
@@ -72,22 +117,105 @@ declare class LibheifDecoder implements IHeicDecoder {
72
117
  decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
118
  /**
74
119
  * Cleans up the WebAssembly decoder instance and resources.
120
+ * Idempotent: safe to call multiple times. After free(), the instance must
121
+ * be re-initialized (createHeicDecoderModule runs again on the next call).
75
122
  */
76
123
  free(): void;
77
124
  }
78
125
 
126
+ interface WorkerConvertOptions extends ConvertOptions {
127
+ /**
128
+ * URL of the worker script that performs the conversion. Should be a
129
+ * compile-time constant (e.g. `new URL('./worker.js', import.meta.url)`
130
+ * under a bundler); the script runs with the page's privileges.
131
+ */
132
+ workerUrl: string | URL;
133
+ /**
134
+ * Maximum time in milliseconds to wait for the worker result before
135
+ * rejecting with a timeout. Defaults to 60000 (60s). Set to 0 to
136
+ * disable the timeout.
137
+ */
138
+ timeoutMs?: number;
139
+ /**
140
+ * Worker script type. Use `'module'` when the script uses ES module
141
+ * imports (e.g. `import { convertHeic } from ...`); `'classic'`
142
+ * scripts must be pre-bundled.
143
+ * @default 'classic'
144
+ */
145
+ workerType?: 'classic' | 'module';
146
+ }
147
+ /**
148
+ * Converts a HEIC image inside a Web Worker so the main thread stays
149
+ * responsive during the (potentially slow) WASM decode.
150
+ *
151
+ * The worker script is user-provided and must handle the following message
152
+ * protocol:
153
+ *
154
+ * ```js
155
+ * // converter.worker.js
156
+ * import { convertHeic } from '@keeratita/heic-converter';
157
+ *
158
+ * self.onmessage = async (event) => {
159
+ * const { input, options } = event.data;
160
+ * try {
161
+ * const blob = await convertHeic(input, {
162
+ * ...options,
163
+ * onProgress: (percent) => self.postMessage({ type: 'progress', percent }),
164
+ * });
165
+ * self.postMessage({ type: 'result', ok: true, blob });
166
+ * } catch (error) {
167
+ * self.postMessage({ type: 'result', ok: false, error: error?.stack ?? error?.message ?? String(error) });
168
+ * }
169
+ * };
170
+ * ```
171
+ *
172
+ * ```ts
173
+ * const jpegBlob = await convertHeicInWorker(heicBlob, {
174
+ * workerUrl: new URL('./converter.worker.js', import.meta.url),
175
+ * workerType: 'module',
176
+ * to: 'jpeg',
177
+ * });
178
+ * ```
179
+ *
180
+ * Only `progress` and `result` messages are understood; any other message
181
+ * type is ignored. Use `workerType: 'module'` when the script uses ES
182
+ * module imports (as in the example above); `'classic'` scripts must be
183
+ * pre-bundled, since static ES imports are not supported there.
184
+ *
185
+ * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
186
+ * @param options Conversion options plus the worker script URL.
187
+ * @returns A Promise resolving to the converted image as a Blob.
188
+ */
189
+ declare function convertHeicInWorker(input: HeicInput, options: WorkerConvertOptions): Promise<Blob>;
190
+
79
191
  /**
80
- * Releases the resources allocated for the shared default decoder instance.
81
- * Call this when no further HEIC conversions are needed to free memory.
192
+ * Releases any cached decoder resources.
193
+ *
194
+ * Decoders are now created and released per conversion, so there is no shared
195
+ * instance to free. This function is kept for API compatibility.
82
196
  */
83
197
  declare function freeSharedDecoder(): void;
84
198
  /**
85
- * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
199
+ * Converts HEIC image data to a standard web format (JPEG, PNG, WebP, or SVG).
86
200
  *
87
201
  * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
202
  * @param options Conversion configuration options.
89
203
  * @returns A Promise resolving to the converted image as a Blob.
90
204
  */
91
- declare function convertHeic(input: Blob | File | ArrayBuffer | Uint8Array, options?: ConvertOptions): Promise<Blob>;
205
+ declare function convertHeic(input: HeicInput, options?: ConvertOptions): Promise<Blob>;
206
+ /**
207
+ * Converts multiple HEIC images to a standard web format.
208
+ *
209
+ * Conversions run with a bounded concurrency (default 4) and results are
210
+ * returned in the same order as the inputs. If any conversion fails, the
211
+ * returned promise rejects as soon as the failure is known (in-flight
212
+ * conversions are allowed to finish in the background) with an error that
213
+ * identifies the failing item index.
214
+ *
215
+ * @param inputs HEIC images as Blobs, Files, ArrayBuffers, or Uint8Arrays.
216
+ * @param options Batch conversion options.
217
+ * @returns A Promise resolving to the converted images as Blobs, in input order.
218
+ */
219
+ declare function convertMany(inputs: HeicInput[], options?: ConvertManyOptions): Promise<Blob[]>;
92
220
 
93
- export { type ConvertOptions, type DecodedImage, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, convertHeic, freeSharedDecoder };
221
+ export { type ConvertManyOptions, type ConvertOptions, type DecodedImage, type HeicInput, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, type ResizeOptions, type WorkerConvertOptions, convertHeic, convertHeicInWorker, convertMany, freeSharedDecoder };