Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updating to libheif v1.17.1, adding wasm version #19

Merged
merged 19 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"extends": "eslint:recommended",
"parser": "babel-eslint",
"env": {
"es6": true,
"node": true
Expand All @@ -15,4 +16,4 @@
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}
}
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand All @@ -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/**
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ temp/

# not source code
libheif/
libheif-wasm/
100 changes: 100 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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).

Expand All @@ -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 `<script>` tag, you have the following options:

_Note: in the examples below, make sure to set the latest version when you use it. Always make sure to set a version, to make sure your website does not break unexpectedly when an update is released._

* Use the pure-javascript implementation, exposing a `libheif` global:
```html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/libheif/libheif.js"></script>
```
* Use the wasm bundle, exposing a `libheif` global:
```html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/libheif-wasm/libheif-bundle.js"></script>
```
* Use the ES Module version, which now works in all major browsers and you should try it:
```html
<script type="module">
import libheif from 'https://cdn.jsdelivr.net/npm/[email protected]/libheif-wasm/libheif-bundle.mjs';
</script>
```

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:
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./libheif/libheif.js')();
21 changes: 15 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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 <[email protected]>",
"license": "LGPL-3.0",
Expand All @@ -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"
Expand All @@ -44,6 +52,7 @@
"decoder",
"node",
"browser",
"emscripten"
"emscripten",
"wasm"
]
}
4 changes: 4 additions & 0 deletions scripts/bundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import libheif from '../libheif-wasm/libheif.js';
import wasmBinary from '../libheif-wasm/libheif.wasm';

export default (opts = {}) => libheif({ ...opts, wasmBinary });
79 changes: 69 additions & 10 deletions scripts/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <script>, similar to the js version libheif
js: `
libheif = libheif.default;
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = libheif;
}`
}
});

await esbuild.build({
...buildOptions,
outfile: path.resolve(root, 'libheif-wasm/libheif-bundle.mjs'),
format: 'esm',
banner: {
// hack to avoid the ENVIRONMENT_IS_NODE detection
// the binary is built in, so the environment doesn't matter
js: 'var process;'
}
});
})().then(() => {
console.log(`fetched libheif ${version}`);
}).catch(err => {
Expand Down
Loading