@keeratita/heic-converter 0.1.0 → 0.3.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,10 @@ 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
17
 
18
18
  ---
19
19
 
@@ -25,6 +25,16 @@ npm install @keeratita/heic-converter
25
25
 
26
26
  ---
27
27
 
28
+ ## 🌍 Live Demo (GitHub Pages)
29
+
30
+ Try the browser demo here:
31
+
32
+ https://keeratita.github.io/heic-converter/
33
+
34
+ The demo is auto-deployed from the `main` branch by the GitHub Actions workflow in `.github/workflows/demo-pages.yml`.
35
+
36
+ ---
37
+
28
38
  ## 🚀 Usage
29
39
 
30
40
  ### 1. Browser: Simple Conversion
@@ -46,9 +56,34 @@ const imageUrl = URL.createObjectURL(jpegBlob);
46
56
  document.querySelector('img').src = imageUrl;
47
57
  ```
48
58
 
49
- ### 2. Browser: Serving and Locating WASM (Custom Assets Path)
59
+ ### 2. Browser: WebP Output
50
60
 
51
- By default, the library tries to fetch `heic-decoder.wasm` relative to the current module script path (`import.meta.url`).
61
+ WebP offers excellent compression with quality configuration:
62
+
63
+ ```typescript
64
+ import { convertHeic } from '@keeratita/heic-converter';
65
+
66
+ const webpBlob = await convertHeic(heicBlob, {
67
+ to: 'webp',
68
+ quality: 0.8,
69
+ });
70
+ ```
71
+
72
+ ### 3. Browser: SVG Output
73
+
74
+ SVG wraps the raster image as an embedded lossless PNG inside an SVG container:
75
+
76
+ ```typescript
77
+ import { convertHeic } from '@keeratita/heic-converter';
78
+
79
+ const svgBlob = await convertHeic(heicBlob, {
80
+ to: 'svg',
81
+ });
82
+ ```
83
+
84
+ ### 4. Browser: Serving and Locating WASM (Custom Assets Path)
85
+
86
+ By default, the library tries to fetch `heic-decoder.wasm` relative to the current module script path (`import.meta.url`).
52
87
 
53
88
  If your bundler places files in a custom assets folder or CDN, you can configure the default decoder or inject a custom one:
54
89
 
@@ -57,13 +92,13 @@ import { convertHeic, LibheifDecoder } from '@keeratita/heic-converter';
57
92
 
58
93
  // Create decoder with custom asset paths
59
94
  const decoder = new LibheifDecoder({
60
- locateFile: (path, prefix) => `https://cdn.example.com/assets/${path}`
95
+ locateFile: (path, prefix) => `https://cdn.example.com/assets/${path}`,
61
96
  });
62
97
 
63
98
  // Pass the custom decoder in options
64
99
  const pngBlob = await convertHeic(heicBlob, {
65
100
  to: 'png',
66
- decoder: decoder
101
+ decoder: decoder,
67
102
  });
68
103
  ```
69
104
 
@@ -79,13 +114,13 @@ const decoder = new LibheifDecoder({ wasmBinary });
79
114
 
80
115
  const jpegBlob = await convertHeic(heicBlob, {
81
116
  to: 'jpeg',
82
- decoder: decoder
117
+ decoder: decoder,
83
118
  });
84
119
  ```
85
120
 
86
- ### 3. Node.js: Decoding Raw Pixel Data
121
+ ### 5. Node.js: Decoding Raw Pixel Data
87
122
 
88
- 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.
123
+ 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.
89
124
 
90
125
  However, you can use the `LibheifDecoder` in Node.js to retrieve the raw RGBA pixels and then encode them using libraries like `sharp` or `pngjs`:
91
126
 
@@ -96,26 +131,27 @@ import sharp from 'sharp'; // external node image library
96
131
 
97
132
  async function convertNode() {
98
133
  const heicData = new Uint8Array(fs.readFileSync('input.heic'));
99
-
134
+
100
135
  const decoder = new LibheifDecoder();
101
136
  await decoder.initialize();
102
-
103
- // Decodes to { width, height, data: Uint8ClampedArray (RGBA) }
137
+
138
+ // Decodes to { width, height, data: Uint8ClampedArray (RGBA) }.
139
+ // data is an independent copy — safe to use after decoder.free().
104
140
  const { width, height, data } = await decoder.decode(heicData);
105
-
141
+
106
142
  // Process raw pixels using sharp
107
- await sharp(Buffer.from(data.buffer), {
108
- raw: { width, height, channels: 4 }
143
+ await sharp(Buffer.from(data), {
144
+ raw: { width, height, channels: 4 },
109
145
  })
110
- .toFormat('jpeg')
111
- .toFile('output.jpg');
112
-
146
+ .toFormat('jpeg')
147
+ .toFile('output.jpg');
148
+
113
149
  // Clean up WASM memory
114
150
  decoder.free();
115
151
  }
116
152
  ```
117
153
 
118
- ### 4. Progress Tracking (e.g. for Large Images)
154
+ ### 6. Progress Tracking (e.g. for Large Images)
119
155
 
120
156
  For large images, you can pass an `onProgress` callback to track the conversion progress (0% to 100%):
121
157
 
@@ -158,8 +194,8 @@ Converts a HEIC image file to a standard web format.
158
194
 
159
195
  - **`input`**: `Blob | File | ArrayBuffer | Uint8Array`
160
196
  - **`options`**: (optional) `ConvertOptions`
161
- - `to`: `'jpeg' | 'jpg' | 'png' | 'svg'` (Default: `'jpeg'`)
162
- - `quality`: `number` (0.0 to 1.0, only applicable to JPEG. Default: `0.92`)
197
+ - `to`: `'jpeg' | 'jpg' | 'png' | 'webp' | 'svg'` (Default: `'jpeg'`)
198
+ - `quality`: `number` (0.0 to 1.0, applicable to JPEG and WebP. Default: `0.92`)
163
199
  - `decoder`: `IHeicDecoder` (Inject custom decoder instance)
164
200
  - `onProgress`: `(percent: number) => void` (Optional callback, receives progress percentage from `0` to `100` during decoding)
165
201
  - **Returns**: `Promise<Blob>`
@@ -172,13 +208,13 @@ The default WASM-based implementation of `IHeicDecoder`.
172
208
  - `locateFile`: `(path: string, prefix: string) => string`
173
209
  - `wasmBinary`: `ArrayBuffer`
174
210
  - **Methods**:
175
- - `initialize(): Promise<void>`: Loads and initializes the WASM wrapper.
176
- - `decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>`: Decodes the HEIC bytes to raw RGBA, with optional progress callback.
177
- - `free(): void`: Releases allocated WebAssembly heap memory.
211
+ - `initialize(): Promise<void>`: Loads and initializes the WASM wrapper. Safe to call concurrently; the module loads at most once per instance.
212
+ - `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()`.
213
+ - `free(): void`: Releases allocated WebAssembly heap memory. Idempotent.
178
214
 
179
215
  ### `freeSharedDecoder()`
180
216
 
181
- 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.
217
+ 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.
182
218
 
183
219
  ```typescript
184
220
  import { freeSharedDecoder } from '@keeratita/heic-converter';
@@ -194,47 +230,67 @@ freeSharedDecoder();
194
230
  If you want to build or modify the WASM wrapper, you will need **Docker** installed.
195
231
 
196
232
  ### Build WebAssembly
233
+
197
234
  To compile the underlying `libheif` and `libde265` libraries from source using Emscripten:
235
+
198
236
  ```bash
199
237
  npm run build:wasm
200
238
  ```
201
239
 
202
240
  ### Build JS & TS Typings
241
+
203
242
  To compile the TypeScript library code to ESM/CJS bundles under the `dist/` directory:
243
+
204
244
  ```bash
205
245
  npm run build
206
246
  ```
207
247
 
208
248
  ### Run Unit Tests
249
+
209
250
  ```bash
210
251
  npm run test
211
252
  ```
212
253
 
254
+ ### Run Browser E2E Tests
255
+
256
+ Real browser conversions against the CSP sandbox and the GitHub Pages demo (`docs/`):
257
+
258
+ ```bash
259
+ npx playwright install chromium # one-time
260
+ npm run test:e2e
261
+ ```
262
+
213
263
  ### Run Interactive CSP Sandbox
264
+
214
265
  To test the converter in a local browser running under a strict Content Security Policy, start the sandbox server:
266
+
215
267
  ```bash
216
268
  npm run sandbox
217
269
  ```
270
+
218
271
  Then navigate to: `http://localhost:3000`
219
272
 
220
273
  ### Release / Versioning
274
+
221
275
  To bump the package version (following SemVer) and push the release commits/tags to the git remote:
276
+
222
277
  ```bash
223
278
  npm run release
224
279
  ```
280
+
225
281
  Alternatively, you can pass the release type as an argument:
282
+
226
283
  ```bash
227
284
  npm run release patch
228
285
  npm run release minor
229
286
  npm run release major
230
287
  npm run release current
231
288
  ```
232
- This script will automatically run the linter, build the TS library, run the unit tests. For `patch`, `minor`, and `major`, it bumps the version (updating `package.json`/`package-lock.json`), commits the changes with a Conventional Commit message (`chore(release): X.Y.Z`), tags the commit, and pushes both the commit and tag to the remote. For `current`, it simply tags the current commit with the existing version in `package.json` (e.g. `vX.Y.Z`) and pushes that tag to the remote without committing or altering files.
233
289
 
290
+ This script will automatically run the linter, build the TS library, run the unit tests. For `patch`, `minor`, and `major`, it bumps the version (updating `package.json`/`package-lock.json`), commits the changes with a Conventional Commit message (`chore(release): X.Y.Z`), tags the commit, and pushes both the commit and tag to the remote. For `current`, it simply tags the current commit with the existing version in `package.json` (e.g. `vX.Y.Z`) and pushes that tag to the remote without committing or altering files.
234
291
 
235
292
  ---
236
293
 
237
294
  ## 📄 License
238
295
 
239
296
  MIT © Keerati Tansawatcharoen
240
-
Binary file
package/dist/index.d.mts CHANGED
@@ -19,7 +19,7 @@ interface IHeicDecoder {
19
19
  */
20
20
  free(): void;
21
21
  }
22
- type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
23
  interface ConvertOptions {
24
24
  /**
25
25
  * Target format for the conversion.
@@ -28,7 +28,7 @@ interface ConvertOptions {
28
28
  to?: ImageFormat;
29
29
  /**
30
30
  * Quality of the converted image (between 0.0 and 1.0).
31
- * Only applicable for 'jpeg' (and 'jpg') format.
31
+ * Applicable for 'jpeg', 'jpg', and 'webp' formats.
32
32
  * @default 0.92
33
33
  */
34
34
  quality?: number;
@@ -59,9 +59,15 @@ declare class LibheifDecoder implements IHeicDecoder {
59
59
  private options?;
60
60
  private module;
61
61
  private decoderInstance;
62
+ /**
63
+ * Memoized module-loading promise so concurrent initialize()/decode() calls
64
+ * on the same instance never instantiate (or mutate) the module twice.
65
+ */
66
+ private initPromise;
62
67
  constructor(options?: LibheifDecoderOptions);
63
68
  /**
64
69
  * Initializes the WebAssembly module and instantiates the HEIC decoder.
70
+ * Safe to call concurrently: the module is loaded at most once per instance.
65
71
  */
66
72
  initialize(): Promise<void>;
67
73
  /**
@@ -72,17 +78,21 @@ declare class LibheifDecoder implements IHeicDecoder {
72
78
  decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
79
  /**
74
80
  * Cleans up the WebAssembly decoder instance and resources.
81
+ * Idempotent: safe to call multiple times. After free(), the instance must
82
+ * be re-initialized (createHeicDecoderModule runs again on the next call).
75
83
  */
76
84
  free(): void;
77
85
  }
78
86
 
79
87
  /**
80
- * Releases the resources allocated for the shared default decoder instance.
81
- * Call this when no further HEIC conversions are needed to free memory.
88
+ * Releases any cached decoder resources.
89
+ *
90
+ * Decoders are now created and released per conversion, so there is no shared
91
+ * instance to free. This function is kept for API compatibility.
82
92
  */
83
93
  declare function freeSharedDecoder(): void;
84
94
  /**
85
- * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
95
+ * Converts HEIC image data to a standard web format (JPEG, PNG, WebP, or SVG).
86
96
  *
87
97
  * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
98
  * @param options Conversion configuration options.
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ interface IHeicDecoder {
19
19
  */
20
20
  free(): void;
21
21
  }
22
- type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg' | 'webp';
23
23
  interface ConvertOptions {
24
24
  /**
25
25
  * Target format for the conversion.
@@ -28,7 +28,7 @@ interface ConvertOptions {
28
28
  to?: ImageFormat;
29
29
  /**
30
30
  * Quality of the converted image (between 0.0 and 1.0).
31
- * Only applicable for 'jpeg' (and 'jpg') format.
31
+ * Applicable for 'jpeg', 'jpg', and 'webp' formats.
32
32
  * @default 0.92
33
33
  */
34
34
  quality?: number;
@@ -59,9 +59,15 @@ declare class LibheifDecoder implements IHeicDecoder {
59
59
  private options?;
60
60
  private module;
61
61
  private decoderInstance;
62
+ /**
63
+ * Memoized module-loading promise so concurrent initialize()/decode() calls
64
+ * on the same instance never instantiate (or mutate) the module twice.
65
+ */
66
+ private initPromise;
62
67
  constructor(options?: LibheifDecoderOptions);
63
68
  /**
64
69
  * Initializes the WebAssembly module and instantiates the HEIC decoder.
70
+ * Safe to call concurrently: the module is loaded at most once per instance.
65
71
  */
66
72
  initialize(): Promise<void>;
67
73
  /**
@@ -72,17 +78,21 @@ declare class LibheifDecoder implements IHeicDecoder {
72
78
  decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
79
  /**
74
80
  * Cleans up the WebAssembly decoder instance and resources.
81
+ * Idempotent: safe to call multiple times. After free(), the instance must
82
+ * be re-initialized (createHeicDecoderModule runs again on the next call).
75
83
  */
76
84
  free(): void;
77
85
  }
78
86
 
79
87
  /**
80
- * Releases the resources allocated for the shared default decoder instance.
81
- * Call this when no further HEIC conversions are needed to free memory.
88
+ * Releases any cached decoder resources.
89
+ *
90
+ * Decoders are now created and released per conversion, so there is no shared
91
+ * instance to free. This function is kept for API compatibility.
82
92
  */
83
93
  declare function freeSharedDecoder(): void;
84
94
  /**
85
- * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
95
+ * Converts HEIC image data to a standard web format (JPEG, PNG, WebP, or SVG).
86
96
  *
87
97
  * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
98
  * @param options Conversion configuration options.