@keeratita/heic-converter 0.3.0 โ†’ 0.4.1

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
@@ -14,6 +14,9 @@ Designed specifically for environments with strict **Content Security Policy (CS
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
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
 
@@ -171,6 +174,89 @@ const jpegBlob = await convertHeic(heicBlob, {
171
174
  > [!TIP]
172
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.
173
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
+
174
260
  ---
175
261
 
176
262
  ## ๐Ÿ”’ Content Security Policy (CSP)
@@ -198,6 +284,31 @@ Converts a HEIC image file to a standard web format.
198
284
  - `quality`: `number` (0.0 to 1.0, applicable to JPEG and WebP. Default: `0.92`)
199
285
  - `decoder`: `IHeicDecoder` (Inject custom decoder instance)
200
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)
201
312
  - **Returns**: `Promise<Blob>`
202
313
 
203
314
  ### `LibheifDecoder(options?)`
@@ -231,12 +342,14 @@ If you want to build or modify the WASM wrapper, you will need **Docker** instal
231
342
 
232
343
  ### Build WebAssembly
233
344
 
234
- To compile the underlying `libheif` and `libde265` libraries from source using Emscripten:
345
+ To compile the underlying [`libheif`](https://github.com/strukturag/libheif) and [`libde265`](https://github.com/strukturag/libde265) libraries from source using Emscripten:
235
346
 
236
347
  ```bash
237
348
  npm run build:wasm
238
349
  ```
239
350
 
351
+ See [WASM_DEPENDENCIES.md](WASM_DEPENDENCIES.md) for the pinned upstream library versions.
352
+
240
353
  ### Build JS & TS Typings
241
354
 
242
355
  To compile the TypeScript library code to ESM/CJS bundles under the `dist/` directory:
Binary file
package/dist/index.d.mts CHANGED
@@ -20,7 +20,27 @@ interface IHeicDecoder {
20
20
  free(): void;
21
21
  }
22
22
  type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
- interface ConvertOptions {
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'
@@ -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
  /**
@@ -84,6 +123,71 @@ declare class LibheifDecoder implements IHeicDecoder {
84
123
  free(): void;
85
124
  }
86
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
+
87
191
  /**
88
192
  * Releases any cached decoder resources.
89
193
  *
@@ -98,6 +202,20 @@ declare function freeSharedDecoder(): void;
98
202
  * @param options Conversion configuration options.
99
203
  * @returns A Promise resolving to the converted image as a Blob.
100
204
  */
101
- 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[]>;
102
220
 
103
- 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
@@ -20,7 +20,27 @@ interface IHeicDecoder {
20
20
  free(): void;
21
21
  }
22
22
  type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
- interface ConvertOptions {
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'
@@ -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
  /**
@@ -84,6 +123,71 @@ declare class LibheifDecoder implements IHeicDecoder {
84
123
  free(): void;
85
124
  }
86
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
+
87
191
  /**
88
192
  * Releases any cached decoder resources.
89
193
  *
@@ -98,6 +202,20 @@ declare function freeSharedDecoder(): void;
98
202
  * @param options Conversion configuration options.
99
203
  * @returns A Promise resolving to the converted image as a Blob.
100
204
  */
101
- 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[]>;
102
220
 
103
- 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 };