diff --git a/.eslintrc.json b/.eslintrc.json
index 36b7885..75587e8 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,5 +1,6 @@
{
"extends": "eslint:recommended",
+ "parser": "babel-eslint",
"env": {
"es6": true,
"node": true
@@ -15,4 +16,4 @@
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
-}
\ No newline at end of file
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c65ff82..ac0966e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,27 +14,27 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node-version: [8, 10, 12, 14, 16, 18]
+ node-version: [12, 14, 16, 18, 20]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run fetch
- run: npm test
- run: npm run lint
- - name: inspect tarball
- run: npm pack
+ - name: Inspect tarball
+ run: npm pack --dry-run
publish:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
needs: test
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
- name: Use Node.js 14
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: 14
registry-url: https://registry.npmjs.org/
@@ -46,7 +46,7 @@ jobs:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
- name: Output logs
if: ${{ always() }}
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: npm-logs
path: /home/runner/.npm/_logs/**
diff --git a/.gitignore b/.gitignore
index 107d7e8..dc0e3a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,3 +40,4 @@ temp/
# not source code
libheif/
+libheif-wasm/
diff --git a/README.md b/README.md
index 004ac4a..97a3ef8 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
> An Emscripten build of [`libheif`](https://github.com/strukturag/libheif) distributed as an npm module for Node.JS and the browser.
[![github actions test][github-actions-test.svg]][github-actions-test.link]
+[![jsdelivr][jsdelivr.svg]][jsdelivr.link]
[![npm-downloads][npm-downloads.svg]][npm.link]
[![npm-version][npm-version.svg]][npm.link]
@@ -11,6 +12,8 @@
[npm-downloads.svg]: https://img.shields.io/npm/dm/libheif-js.svg
[npm.link]: https://www.npmjs.com/package/libheif-js
[npm-version.svg]: https://img.shields.io/npm/v/libheif-js.svg
+[jsdelivr.svg]: https://img.shields.io/jsdelivr/npm/hm/libheif-js?color=bd33a4
+[jsdelivr.link]: https://www.jsdelivr.com/package/npm/libheif-js
This module will respect the major and minor versions of the included `libheif`, with the patch version representing changes in this module itself. For the exact version of `libheif`, please see the [install script](scripts/install.js).
@@ -20,6 +23,103 @@ This module will respect the major and minor versions of the included `libheif`,
npm install libheif-js
```
+## Usage
+
+Starting with version 1.17, there are multiple variants of `libheif` that you can use:
+
+* The default is still the classic pure-javascript implementation (for backwards compatibility, of course). You can still bundle this into your project with your bundler of choice.
+ ```js
+ const libheif = require('libheif-js');
+ ```
+* There is a `wasm` version available for use in NodeJS. This version will dymanically load the `.wasm` binary at runtime. While you may try to run this through a bundler, you are on your own for making it work.
+ ```js
+ const libheif = require('libheif-js/wasm');
+ ```
+* There is also a `wasm` version that is pre-bundled for you, which includes the `.wasm` binary inside the `.js` bundle. You will have a much easier time using this in your browser bundle project.
+ ```js
+ const libheif = require('libheif-js/wasm-bundle');
+ ```
+
+If you'd like to include this module directly into an `html` page using a `
+ ```
+* Use the wasm bundle, exposing a `libheif` global:
+ ```html
+
+ ```
+* Use the ES Module version, which now works in all major browsers and you should try it:
+ ```html
+
+ ```
+
+In all cases, you can use this sample code to decode an image:
+
+```js
+const file = fs.readFileSync('./temp/0002.heic');
+
+const decoder = new libheif.HeifDecoder();
+const data = decoder.decode(file);
+// data in an array holding all images inside the heic file
+
+const image = data[0];
+const width = image.get_width();
+const height = image.get_height();
+```
+
+In NodeJS, you might use this decoded data with other libraries, such as `pngjs`:
+
+```js
+const { PNG } = require('pngjs');
+
+const arrayBuffer = await new Promise((resolve, reject) => {
+ image.display({ data: new Uint8ClampedArray(width*height*4), width, height }, (displayData) => {
+ if (!displayData) {
+ return reject(new Error('HEIF processing error'));
+ }
+
+ resolve(displayData.data.buffer);
+ });
+});
+
+const imageData = { width, height, data: arrayBuffer };
+
+const png = new PNG({ width: imageData.width, height: imageData.height });
+png.data = Buffer.from(imageData.data);
+
+const pngBuffer = PNG.sync.write(png);
+```
+
+In the browser, you might use this decoded data with `canvas` to display or convert the image:
+
+```js
+const canvas = document.createElement('canvas');
+
+canvas.width = width;
+canvas.height = height;
+
+const context = canvas.getContext('2d');
+const imageData = context.createImageData(width, height);
+
+await new Promise((resolve, reject) => {
+ image.display(imageData, (displayData) => {
+ if (!displayData) {
+ return reject(new Error('HEIF processing error'));
+ }
+
+ resolve();
+ });
+});
+
+context.putImageData(imageData, 0, 0);
+```
+
## Related
This module contains the low-level `libheif` implementation. For more user-friendly functionality, check out these projects:
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..3381a4c
--- /dev/null
+++ b/index.js
@@ -0,0 +1 @@
+module.exports = require('./libheif/libheif.js')();
diff --git a/package.json b/package.json
index cbb5bf6..b790790 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
"name": "libheif-js",
- "version": "1.15.1",
+ "version": "1.17.1",
"description": "Emscripten distribution of libheif for Node.JS and the browser",
- "main": "libheif/libheif.js",
+ "main": "index.js",
"scripts": {
"pretest": "npm run -s images",
- "test": "mocha test/**/*.test.js",
+ "test": "mocha test/**/*.test.js --timeout 4000",
"fetch": "node scripts/install.js",
"images": "node scripts/images.js",
"inspect": "node scripts/convert.js < temp/0002.heic > out.png",
@@ -16,7 +16,11 @@
"url": "git+https://github.com/catdad-experiments/libheif-js.git"
},
"files": [
- "libheif"
+ "index.js",
+ "wasm.js",
+ "wasm-bundle.js",
+ "libheif",
+ "libheif-wasm"
],
"author": "Kiril Vatev ",
"license": "LGPL-3.0",
@@ -25,14 +29,18 @@
},
"homepage": "https://github.com/catdad-experiments/libheif-js#readme",
"devDependencies": {
+ "babel-eslint": "^10.1.0",
"chai": "^4.2.0",
+ "esbuild": "^0.19.5",
"eslint": "^5.16.0",
"fs-extra": "^8.1.0",
+ "gunzip-maybe": "^1.4.2",
"mocha": "^7.0.0",
"node-fetch": "^2.6.0",
"pixelmatch": "^5.2.1",
"pngjs": "^3.4.0",
- "rootrequire": "^1.0.0"
+ "rootrequire": "^1.0.0",
+ "tar-stream": "^3.1.6"
},
"engines": {
"node": ">=8.0.0"
@@ -44,6 +52,7 @@
"decoder",
"node",
"browser",
- "emscripten"
+ "emscripten",
+ "wasm"
]
}
diff --git a/scripts/bundle.js b/scripts/bundle.js
new file mode 100644
index 0000000..cc56c21
--- /dev/null
+++ b/scripts/bundle.js
@@ -0,0 +1,4 @@
+import libheif from '../libheif-wasm/libheif.js';
+import wasmBinary from '../libheif-wasm/libheif.wasm';
+
+export default (opts = {}) => libheif({ ...opts, wasmBinary });
diff --git a/scripts/install.js b/scripts/install.js
index f7773a7..408e9cb 100644
--- a/scripts/install.js
+++ b/scripts/install.js
@@ -3,30 +3,89 @@ const path = require('path');
const fs = require('fs-extra');
const fetch = require('node-fetch');
const root = require('rootrequire');
+const tar = require('tar-stream');
+const gunzip = require('gunzip-maybe');
-const libheifDir = path.resolve(root, 'libheif');
-const libheif = path.resolve(libheifDir, 'libheif.js');
-const libheifLicense = path.resolve(libheifDir, 'LICENSE');
+const esbuild = require('esbuild');
-const version = 'v1.15.1';
+const version = 'v1.17.1';
const base = `https://github.com/catdad-experiments/libheif-emscripten/releases/download/${version}`;
-const lib = `${base}/libheif.js`;
-const license = `${base}/LICENSE`;
+const tarball = `${base}/libheif.tar.gz`;
-const response = async url => {
+const getStream = async url => {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`failed response: ${res.status} ${res.statusText}`);
}
- return await res.buffer();
+ return res.body;
+};
+
+const autoReadStream = async stream => {
+ let result = Buffer.from('');
+
+ for await (const data of stream) {
+ result = Buffer.concat([result, data]);
+ }
+
+ return result;
};
(async () => {
- await fs.outputFile(libheif, await response(lib));
- await fs.outputFile(libheifLicense, await response(license));
+ await fs.remove(path.resolve(root, 'libheif'));
+ await fs.remove(path.resolve(root, 'libheif-wasm'));
+
+ for await (const entry of (await getStream(tarball)).pipe(gunzip()).pipe(tar.extract())) {
+ const basedir = entry.header.name.split('/')[0];
+
+ if (entry.header.type === 'file' && ['libheif', 'libheif-wasm'].includes(basedir)) {
+ const outfile = path.resolve(root, entry.header.name);
+ console.log(` writing "${outfile}"`);
+ await fs.outputFile(outfile, await autoReadStream(entry));
+ } else {
+ await autoReadStream(entry);
+ }
+ }
+
+ const buildOptions = {
+ entryPoints: [path.resolve(root, 'scripts/bundle.js')],
+ bundle: true,
+ minify: true,
+ external: ['fs', 'path', 'require'],
+ loader: {
+ '.wasm': 'binary'
+ },
+ platform: 'neutral'
+ };
+
+ await esbuild.build({
+ ...buildOptions,
+ outfile: path.resolve(root, 'libheif-wasm/libheif-bundle.js'),
+ format: 'iife',
+ globalName: 'libheif',
+ footer: {
+ // hack to support a single bundle as a node cjs module
+ // and a browser