@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 +84 -28
- package/dist/heic-decoder.wasm +0 -0
- package/dist/index.d.mts +15 -5
- package/dist/index.d.ts +15 -5
- package/dist/index.js +7 -4250
- package/dist/index.mjs +7 -4210
- package/package.json +5 -2
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**:
|
|
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:
|
|
59
|
+
### 2. Browser: WebP Output
|
|
50
60
|
|
|
51
|
-
|
|
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
|
-
###
|
|
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
|
|
108
|
-
raw: { width, height, channels: 4 }
|
|
143
|
+
await sharp(Buffer.from(data), {
|
|
144
|
+
raw: { width, height, channels: 4 },
|
|
109
145
|
})
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
###
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
package/dist/heic-decoder.wasm
CHANGED
|
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
|
-
*
|
|
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
|
|
81
|
-
*
|
|
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
|
-
*
|
|
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
|
|
81
|
-
*
|
|
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.
|