@keeratita/heic-converter 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Keerati Tansawatcharoen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,240 @@
1
+ # @keeratita/heic-converter
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.
4
+
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
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - 🔒 **CSP Compliant**: Emscripten glue code is compiled with `-s DYNAMIC_EXECUTION=0`. Safe to run without `'unsafe-eval'`.
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.
14
+ - 🌐 **Isomorphic / Universal**: Runs in Node.js (decoding) and browser (decoding & canvas-based encoding).
15
+ - 📦 **No Bloat**: Zero external production dependencies. Small footprint.
16
+ - 🎨 **Format Support**: Convert to `jpeg` (with quality configuration), `png`, and `svg` (embedded lossless vector).
17
+
18
+ ---
19
+
20
+ ## 📦 Installation
21
+
22
+ ```bash
23
+ npm install @keeratita/heic-converter
24
+ ```
25
+
26
+ ---
27
+
28
+ ## 🚀 Usage
29
+
30
+ ### 1. Browser: Simple Conversion
31
+
32
+ In the browser, you can pass a `File` or `Blob` and get a converted `Blob` back:
33
+
34
+ ```typescript
35
+ import { convertHeic } from '@keeratita/heic-converter';
36
+
37
+ // Convert input File/Blob to JPEG
38
+ const heicBlob = /* your file input */;
39
+ const jpegBlob = await convertHeic(heicBlob, {
40
+ to: 'jpeg',
41
+ quality: 0.9
42
+ });
43
+
44
+ // Create preview URL
45
+ const imageUrl = URL.createObjectURL(jpegBlob);
46
+ document.querySelector('img').src = imageUrl;
47
+ ```
48
+
49
+ ### 2. Browser: Serving and Locating WASM (Custom Assets Path)
50
+
51
+ By default, the library tries to fetch `heic-decoder.wasm` relative to the current module script path (`import.meta.url`).
52
+
53
+ If your bundler places files in a custom assets folder or CDN, you can configure the default decoder or inject a custom one:
54
+
55
+ ```typescript
56
+ import { convertHeic, LibheifDecoder } from '@keeratita/heic-converter';
57
+
58
+ // Create decoder with custom asset paths
59
+ const decoder = new LibheifDecoder({
60
+ locateFile: (path, prefix) => `https://cdn.example.com/assets/${path}`
61
+ });
62
+
63
+ // Pass the custom decoder in options
64
+ const pngBlob = await convertHeic(heicBlob, {
65
+ to: 'png',
66
+ decoder: decoder
67
+ });
68
+ ```
69
+
70
+ Alternatively, if you prefer to load the WASM binary manually as an `ArrayBuffer` (e.g. from an API or local bundle):
71
+
72
+ ```typescript
73
+ import { convertHeic, LibheifDecoder } from '@keeratita/heic-converter';
74
+
75
+ const wasmResponse = await fetch('/assets/heic-decoder.wasm');
76
+ const wasmBinary = await wasmResponse.arrayBuffer();
77
+
78
+ const decoder = new LibheifDecoder({ wasmBinary });
79
+
80
+ const jpegBlob = await convertHeic(heicBlob, {
81
+ to: 'jpeg',
82
+ decoder: decoder
83
+ });
84
+ ```
85
+
86
+ ### 3. Node.js: Decoding Raw Pixel Data
87
+
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.
89
+
90
+ 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
+
92
+ ```typescript
93
+ import fs from 'fs';
94
+ import { LibheifDecoder } from '@keeratita/heic-converter';
95
+ import sharp from 'sharp'; // external node image library
96
+
97
+ async function convertNode() {
98
+ const heicData = new Uint8Array(fs.readFileSync('input.heic'));
99
+
100
+ const decoder = new LibheifDecoder();
101
+ await decoder.initialize();
102
+
103
+ // Decodes to { width, height, data: Uint8ClampedArray (RGBA) }
104
+ const { width, height, data } = await decoder.decode(heicData);
105
+
106
+ // Process raw pixels using sharp
107
+ await sharp(Buffer.from(data.buffer), {
108
+ raw: { width, height, channels: 4 }
109
+ })
110
+ .toFormat('jpeg')
111
+ .toFile('output.jpg');
112
+
113
+ // Clean up WASM memory
114
+ decoder.free();
115
+ }
116
+ ```
117
+
118
+ ### 4. Progress Tracking (e.g. for Large Images)
119
+
120
+ For large images, you can pass an `onProgress` callback to track the conversion progress (0% to 100%):
121
+
122
+ ```typescript
123
+ import { convertHeic } from '@keeratita/heic-converter';
124
+
125
+ const heicBlob = /* your file */;
126
+ const jpegBlob = await convertHeic(heicBlob, {
127
+ to: 'jpeg',
128
+ onProgress: (percent) => {
129
+ console.log(`Conversion progress: ${Math.round(percent)}%`);
130
+ // Update progress bar UI
131
+ }
132
+ });
133
+ ```
134
+
135
+ > [!TIP]
136
+ > 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.
137
+
138
+ ---
139
+
140
+ ## 🔒 Content Security Policy (CSP)
141
+
142
+ To comply with strict CSP guidelines, ensure your server headers allow running WebAssembly:
143
+
144
+ ```http
145
+ Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'; img-src 'self' blob: data:;
146
+ ```
147
+
148
+ > [!NOTE]
149
+ > `'wasm-unsafe-eval'` is a CSP Level 3 directive that allows compiling and executing WebAssembly modules without opening the security risks of general JavaScript `'unsafe-eval'`.
150
+
151
+ ---
152
+
153
+ ## 📖 API Reference
154
+
155
+ ### `convertHeic(input, options?)`
156
+
157
+ Converts a HEIC image file to a standard web format.
158
+
159
+ - **`input`**: `Blob | File | ArrayBuffer | Uint8Array`
160
+ - **`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`)
163
+ - `decoder`: `IHeicDecoder` (Inject custom decoder instance)
164
+ - `onProgress`: `(percent: number) => void` (Optional callback, receives progress percentage from `0` to `100` during decoding)
165
+ - **Returns**: `Promise<Blob>`
166
+
167
+ ### `LibheifDecoder(options?)`
168
+
169
+ The default WASM-based implementation of `IHeicDecoder`.
170
+
171
+ - **`options`**: (optional) `LibheifDecoderOptions`
172
+ - `locateFile`: `(path: string, prefix: string) => string`
173
+ - `wasmBinary`: `ArrayBuffer`
174
+ - **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.
178
+
179
+ ### `freeSharedDecoder()`
180
+
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.
182
+
183
+ ```typescript
184
+ import { freeSharedDecoder } from '@keeratita/heic-converter';
185
+
186
+ // After you finish converting all images
187
+ freeSharedDecoder();
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 🛠️ Development & Compiling
193
+
194
+ If you want to build or modify the WASM wrapper, you will need **Docker** installed.
195
+
196
+ ### Build WebAssembly
197
+ To compile the underlying `libheif` and `libde265` libraries from source using Emscripten:
198
+ ```bash
199
+ npm run build:wasm
200
+ ```
201
+
202
+ ### Build JS & TS Typings
203
+ To compile the TypeScript library code to ESM/CJS bundles under the `dist/` directory:
204
+ ```bash
205
+ npm run build
206
+ ```
207
+
208
+ ### Run Unit Tests
209
+ ```bash
210
+ npm run test
211
+ ```
212
+
213
+ ### Run Interactive CSP Sandbox
214
+ To test the converter in a local browser running under a strict Content Security Policy, start the sandbox server:
215
+ ```bash
216
+ npm run sandbox
217
+ ```
218
+ Then navigate to: `http://localhost:3000`
219
+
220
+ ### Release / Versioning
221
+ To bump the package version (following SemVer) and push the release commits/tags to the git remote:
222
+ ```bash
223
+ npm run release
224
+ ```
225
+ Alternatively, you can pass the release type as an argument:
226
+ ```bash
227
+ npm run release patch
228
+ npm run release minor
229
+ npm run release major
230
+ npm run release current
231
+ ```
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
+
234
+
235
+ ---
236
+
237
+ ## 📄 License
238
+
239
+ MIT © Keerati Tansawatcharoen
240
+
Binary file
@@ -0,0 +1,93 @@
1
+ interface DecodedImage {
2
+ width: number;
3
+ height: number;
4
+ data: Uint8ClampedArray;
5
+ }
6
+ interface IHeicDecoder {
7
+ /**
8
+ * Initializes the decoder (e.g., loading WebAssembly module).
9
+ */
10
+ initialize(): Promise<void>;
11
+ /**
12
+ * Decodes HEIC binary data into raw RGBA pixel data.
13
+ * @param data The HEIC file as a Uint8Array.
14
+ * @param onProgress Optional progress callback that receives the progress percentage.
15
+ */
16
+ decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
17
+ /**
18
+ * Cleans up allocated resources.
19
+ */
20
+ free(): void;
21
+ }
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
23
+ interface ConvertOptions {
24
+ /**
25
+ * Target format for the conversion.
26
+ * @default 'jpeg'
27
+ */
28
+ to?: ImageFormat;
29
+ /**
30
+ * Quality of the converted image (between 0.0 and 1.0).
31
+ * Only applicable for 'jpeg' (and 'jpg') format.
32
+ * @default 0.92
33
+ */
34
+ quality?: number;
35
+ /**
36
+ * Optional custom decoder implementation to inject.
37
+ * If not provided, a default LibheifDecoder is used.
38
+ */
39
+ decoder?: IHeicDecoder;
40
+ /**
41
+ * Optional progress callback that receives the progress percentage (0 to 100) during decoding.
42
+ */
43
+ onProgress?: (percent: number) => void;
44
+ }
45
+
46
+ interface LibheifDecoderOptions {
47
+ /**
48
+ * Custom function to locate the WASM file.
49
+ * Useful when serving the WASM file from a custom route or CDN.
50
+ */
51
+ locateFile?: (path: string, prefix: string) => string;
52
+ /**
53
+ * Raw WASM binary buffer. If provided, the library will use this buffer
54
+ * directly instead of attempting to fetch the WASM file.
55
+ */
56
+ wasmBinary?: ArrayBuffer;
57
+ }
58
+ declare class LibheifDecoder implements IHeicDecoder {
59
+ private options?;
60
+ private module;
61
+ private decoderInstance;
62
+ constructor(options?: LibheifDecoderOptions);
63
+ /**
64
+ * Initializes the WebAssembly module and instantiates the HEIC decoder.
65
+ */
66
+ initialize(): Promise<void>;
67
+ /**
68
+ * Decodes HEIC binary data into raw RGBA pixel data.
69
+ * @param data The HEIC file contents as a Uint8Array.
70
+ * @param onProgress Optional progress callback.
71
+ */
72
+ decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
+ /**
74
+ * Cleans up the WebAssembly decoder instance and resources.
75
+ */
76
+ free(): void;
77
+ }
78
+
79
+ /**
80
+ * Releases the resources allocated for the shared default decoder instance.
81
+ * Call this when no further HEIC conversions are needed to free memory.
82
+ */
83
+ declare function freeSharedDecoder(): void;
84
+ /**
85
+ * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
86
+ *
87
+ * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
+ * @param options Conversion configuration options.
89
+ * @returns A Promise resolving to the converted image as a Blob.
90
+ */
91
+ declare function convertHeic(input: Blob | File | ArrayBuffer | Uint8Array, options?: ConvertOptions): Promise<Blob>;
92
+
93
+ export { type ConvertOptions, type DecodedImage, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, convertHeic, freeSharedDecoder };
@@ -0,0 +1,93 @@
1
+ interface DecodedImage {
2
+ width: number;
3
+ height: number;
4
+ data: Uint8ClampedArray;
5
+ }
6
+ interface IHeicDecoder {
7
+ /**
8
+ * Initializes the decoder (e.g., loading WebAssembly module).
9
+ */
10
+ initialize(): Promise<void>;
11
+ /**
12
+ * Decodes HEIC binary data into raw RGBA pixel data.
13
+ * @param data The HEIC file as a Uint8Array.
14
+ * @param onProgress Optional progress callback that receives the progress percentage.
15
+ */
16
+ decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
17
+ /**
18
+ * Cleans up allocated resources.
19
+ */
20
+ free(): void;
21
+ }
22
+ type ImageFormat = 'jpeg' | 'jpg' | 'png' | 'svg';
23
+ interface ConvertOptions {
24
+ /**
25
+ * Target format for the conversion.
26
+ * @default 'jpeg'
27
+ */
28
+ to?: ImageFormat;
29
+ /**
30
+ * Quality of the converted image (between 0.0 and 1.0).
31
+ * Only applicable for 'jpeg' (and 'jpg') format.
32
+ * @default 0.92
33
+ */
34
+ quality?: number;
35
+ /**
36
+ * Optional custom decoder implementation to inject.
37
+ * If not provided, a default LibheifDecoder is used.
38
+ */
39
+ decoder?: IHeicDecoder;
40
+ /**
41
+ * Optional progress callback that receives the progress percentage (0 to 100) during decoding.
42
+ */
43
+ onProgress?: (percent: number) => void;
44
+ }
45
+
46
+ interface LibheifDecoderOptions {
47
+ /**
48
+ * Custom function to locate the WASM file.
49
+ * Useful when serving the WASM file from a custom route or CDN.
50
+ */
51
+ locateFile?: (path: string, prefix: string) => string;
52
+ /**
53
+ * Raw WASM binary buffer. If provided, the library will use this buffer
54
+ * directly instead of attempting to fetch the WASM file.
55
+ */
56
+ wasmBinary?: ArrayBuffer;
57
+ }
58
+ declare class LibheifDecoder implements IHeicDecoder {
59
+ private options?;
60
+ private module;
61
+ private decoderInstance;
62
+ constructor(options?: LibheifDecoderOptions);
63
+ /**
64
+ * Initializes the WebAssembly module and instantiates the HEIC decoder.
65
+ */
66
+ initialize(): Promise<void>;
67
+ /**
68
+ * Decodes HEIC binary data into raw RGBA pixel data.
69
+ * @param data The HEIC file contents as a Uint8Array.
70
+ * @param onProgress Optional progress callback.
71
+ */
72
+ decode(data: Uint8Array, onProgress?: (percent: number) => void): Promise<DecodedImage>;
73
+ /**
74
+ * Cleans up the WebAssembly decoder instance and resources.
75
+ */
76
+ free(): void;
77
+ }
78
+
79
+ /**
80
+ * Releases the resources allocated for the shared default decoder instance.
81
+ * Call this when no further HEIC conversions are needed to free memory.
82
+ */
83
+ declare function freeSharedDecoder(): void;
84
+ /**
85
+ * Converts HEIC image data to a standard web format (JPEG, PNG, or SVG).
86
+ *
87
+ * @param input HEIC image as a Blob, File, ArrayBuffer, or Uint8Array.
88
+ * @param options Conversion configuration options.
89
+ * @returns A Promise resolving to the converted image as a Blob.
90
+ */
91
+ declare function convertHeic(input: Blob | File | ArrayBuffer | Uint8Array, options?: ConvertOptions): Promise<Blob>;
92
+
93
+ export { type ConvertOptions, type DecodedImage, type IHeicDecoder, type ImageFormat, LibheifDecoder, type LibheifDecoderOptions, convertHeic, freeSharedDecoder };