├── libwebp
├── libwebp.wasm
├── binding.cpp
└── libwebp.js
├── package.json
├── io.js
├── examples.js
├── libwebp.js
├── COPYING.LESSER
├── bin
└── webpmux
├── parser.js
├── README.md
└── webp.js
/libwebp/libwebp.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ApeironTsuka/node-webpmux/HEAD/libwebp/libwebp.wasm
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-webpmux",
3 | "version": "3.2.1",
4 | "description": "A pure Javascript/WebAssembly re-implementation of webpmux",
5 | "main": "webp.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/ApeironTsuka/node-webpmux.git"
12 | },
13 | "author": "ApeironTsuka",
14 | "license": "LGPL-3.0-or-later",
15 | "bugs": {
16 | "url": "https://github.com/ApeironTsuka/node-webpmux/issues"
17 | },
18 | "homepage": "https://github.com/ApeironTsuka/node-webpmux#readme"
19 | }
20 |
--------------------------------------------------------------------------------
/io.js:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 |
17 | let fs = {};
18 | if (typeof window === 'undefined') {
19 | const _fs = require('fs');
20 | const { promisify } = require('util');
21 | const { basename } = require('path');
22 | fs = {
23 | read: promisify(_fs.read),
24 | write: promisify(_fs.write),
25 | open: promisify(_fs.open),
26 | close: promisify(_fs.close),
27 | basename,
28 | avail: true
29 | };
30 | } else {
31 | let f = async () => { throw new Error('Running inside a browser; filesystem support is not available'); };
32 | fs = {
33 | read: f,
34 | write: f,
35 | open: f,
36 | close: f,
37 | basename: f,
38 | err: f,
39 | avail: false
40 | };
41 | }
42 | module.exports = fs;
43 |
--------------------------------------------------------------------------------
/examples.js:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 |
17 | /*
18 | This file contains examples for how to do some common/basic things.
19 | It will *not* execute. This is on purpose.
20 | Most lesser-used features, such as frame offsets, animation background color, loop count, etc., aren't described here.
21 | You can find the full descriptions of function arguments in the README.
22 | */
23 | process.exit(); // To make certain it cannot be executed
24 | const WebP = require('node-webpmux');
25 |
26 | // Creating an empty (1x1, black) image
27 | img = await WebP.Image.getEmptyImage();
28 |
29 | // Loading from disk
30 | await img.load('image.webp');
31 |
32 | // Or a Buffer
33 | await img.load(buffer);
34 |
35 | // Save to a new image on disk
36 | await img.save('path/to/wherever.webp');
37 |
38 | // Or a Buffer
39 | buffer = await img.save(null);
40 |
41 | // Or overwrite the original on disk
42 | await img.save();
43 |
44 | // Get a Buffer of size img.width * img.height * 4 containing the image's pixel data in RGBA order
45 | pixels = await img.getImageData();
46 |
47 | // Set the image's pixel data, lossless preset 9, while perfectly preserving alpha pixels
48 | await img.setImageData(pixels, { lossless: 9, exact: true });
49 | // These two are useful for modifying images, or converting to/from other formats
50 | // An example of this, using PNGjs's sync API for brevity
51 | png = PNG.sync.read(fs.readFileSync('example.png'));
52 | await img.setImageData(png.data, { width: png.width, height: png.height });
53 | // ^ from PNG, or to PNG v
54 | pixels = await img.getImageData();
55 | fs.writeFileSync('example.png', PNG.sync.write({ data: pixels, width: img.width, height: img.height }, { deflateLevel: 9 }));
56 |
57 | // For animations..
58 | pixels = await img.getFrameData(5);
59 | frame = img.frames[5]; // in case you need frame.width and frame.height, as you would for converting to/from other formats
60 | await img.setFrameData(5, pixels, { lossless: 9, exact: true });
61 |
62 | // Replacing a frame from disk
63 | await img.replaceFrame(4, 'different frame.webp');
64 |
65 | // Or from a Buffer
66 | await img.replaceFrame(4, buffer);
67 |
68 | // Or, you can generate a new frame completely from scratch
69 | width = 20; height = 50;
70 | pixels = Buffer.alloc(width * height * 4);
71 | /* ... populate `pixels` ... omitting it here ... */
72 | img = await WebP.Image.getEmptyImage();
73 | await img.setImageData(pixels, { width, height });
74 |
75 | // To add the new frame
76 | frame = await WebP.Image.generateFrame({ img });
77 | anim.frames.push(frame);
78 | // You can also pass `path` or `buffer` instead of `img` to generate a frame using one of those sources
79 |
80 | // Or to use it to replace an existing one while preserving the original frame's settings
81 | await anim.replaceFrame(4, await img.save(null));
82 |
83 | // Or if you want to replace the whole frame, settings and all
84 | anim.frames.splice(4, 1, frame);
85 |
86 | // To create an entire animation from scratch and save it to disk in one go
87 | frames = [];
88 | /* ... populate `frames` using generateFrame ... omitting it here ... */
89 | await WebP.Image.save('anim.webp', { frames });
90 |
91 | // Or to a Buffer
92 | buffer = await WebP.Image.save(null, { frames });
93 |
94 | // If you instead want to create an animation to do more things to
95 | anim = await WebP.Image.getEmptyImage();
96 | anim.convertToAnim();
97 | anim.frames = frames;
98 |
99 | // To export a frame to disk
100 | await anim.demux('directory/to/place/it', { frame: 4 });
101 |
102 | // For a range of frames to disk
103 | await anim.demux('directory/to/place/them', { start: 2, end: 5 });
104 |
105 | // Or for all the frames to disk
106 | await anim.demux('directory/to/place/them');
107 |
108 | // To export to a Buffer instead. Supports the three variants described for .demux() above
109 | await anim.demux({ buffers: true, start: 1, end: 3 });
110 |
111 | // To add metadata (here EXIF is shown, but XMP and ICCP are also supported)
112 | // Note that *no* validation is done on metadata. Make sure your source data is valid before adding it.
113 | img.exif = fs.readFileSync('metadata.exif');
114 |
115 | // For a quick-and-dirty way to set frame data via a Worker for a moderate speed-up from threaded saving. This example is using NodeJS Workers, but should also work via Web Workers in a browser.
116 |
117 | // saver.js
118 | const { Worker } = require('worker_threads');
119 | const WebP = require('node-webpmux');
120 | function spawnWorker(data) {
121 | return new Promise((res, rej) => {
122 | let worker = new Worker('saver.worker.js', { workerData: data });
123 | worker.on('message', res);
124 | worker.on('error', rej);
125 | worker.on('exit', (code) => { if (code != 0) { rej(new Error(`Worker stopped with exit code ${code}`)); } });
126 | });
127 | }
128 | async function go() {
129 | let img = await WebP.Image.load('anim.webp'), newFrames = [], promises = [];
130 | /* populate newFrames via getFrameData and make changes as desired */
131 | for (let i = 0, { frames } = img.data, l = frames.length; i < l; i++) {
132 | promises.push(spawnWorker({ webp: img, frame: i, data: newFrames[i] }).then((newdata) => { img.data.frames[i] = newdata; }));
133 | }
134 | await Promise.all(promises);
135 | await img.save('newanim.webp');
136 | }
137 | go().then(/* ... */)'
138 |
139 | // saver.worker.js
140 | const { parentPort, workerData } = require('worker_threads');
141 | const WebP = require('node-webpmux');
142 |
143 | async function saveFrame(d) {
144 | let { data, frame, webp } = d;
145 | let img = WebP.Image.from(webp);
146 | await WebP.Image.initLib();
147 | await img.setFrameData(frame, data, { lossless: 9 });
148 | return img.data.frames[frame];
149 | }
150 |
151 | parentPort.postMessage(await saveFrame(workerData));
152 |
--------------------------------------------------------------------------------
/libwebp.js:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 |
17 | const libwebpF = require('./libwebp/libwebp.js');
18 | const ranges = {
19 | preset: { n: 0, m: 5 },
20 | lossless: { n: 0, m: 9 },
21 | quality: { n: 0, m: 100 },
22 | method: { n: 0, m: 6 },
23 | exact: { n: 0, m: 1 }
24 | }, advranges = {
25 | imageHint: { n: 0, m: 3 },
26 | targetSize: undefined,
27 | targetPSNR: undefined,
28 | segments: { n: 1, m: 4 },
29 | snsStrength: { n: 0, m: 100 },
30 | filterStrength: { n: 0, m: 100 },
31 | filterSharpness: { n: 0, m: 7 },
32 | filterType: { n: 0, m: 1 },
33 | autoFilter: { n: 0, m: 1 },
34 | alphaCompression: { n: 0, m: 1 },
35 | alphaFiltering: { n: 0, m: 2 },
36 | alphaQuality: { n: 0, m: 100 },
37 | pass: { n: 1, m: 10 },
38 | showCompressed: { n: 0, m: 1 },
39 | preprocessing: { n: 0, m: 2 },
40 | partitions: { n: 0, m: 3 },
41 | partitionLimit: { n: 0, m: 100 },
42 | emulateJpegSize: { n: 0, m: 1 },
43 | threadLevel: { n: 0, m: 5 },
44 | lowMemory: { n: 0, m: 1 },
45 | nearLossless: { n: 0, m: 100 },
46 | useDeltaPalette: { n: 0, m: 1 },
47 | useSharpYUV: { n: 0, m: 1 },
48 | qMin: { n: 0, m: 100 },
49 | qMax: { n: 0, m: 100 }
50 | };
51 | function checkOpts(o) {
52 | for (let i = 0, keys = Object.keys(o), l = keys.length; i < l; i++) {
53 | let key = keys[i], r = ranges[key];
54 | if (!r) { continue; }
55 | if ((o[key] < r.n) || (o[key] > r.m)) { throw new Error(`${key} out of range ${r.n}..${r.m}`); }
56 | }
57 | }
58 | function checkAdv(adv) {
59 | for (let i = 0, keys = Object.keys(adv), l = keys.length; i < l; i++) {
60 | let key = keys[i], r = ranges[key];
61 | if (!r) { continue; }
62 | if ((adv[key] < r.n) || (adv[key] > r.m)) { throw new Error(`advanced.${key} out of range ${r.n}..${r.m}`); }
63 | }
64 | }
65 | module.exports = class libWebP {
66 | enc = 0;
67 | async init() {
68 | let Module = this.Module = await libwebpF();
69 | this.api = Module.WebPEnc;
70 | this.api.getResult = (e) => { return new Uint8Array(new Uint8Array(Module.HEAP8.buffer, e.getResult(), e.getResultSize())); };
71 | this.api.decodeRGBA = Module.cwrap('decodeRGBA', 'number', [ 'number', 'number' ]);
72 | this.api.decodeFree = Module.cwrap('decodeFree', '', [ 'number' ]);
73 | this.api.allocBuffer = Module.cwrap('allocBuffer', 'number', [ 'number' ]);
74 | this.api.destroyBuffer = Module.cwrap('destroyBuffer', '', [ 'number' ]);
75 | }
76 | initEnc() { if (!this.enc) { this.enc = new this.Module.WebPEnc(); } }
77 | destroyEnc() { if (this.enc) { this.enc.delete(); delete this.enc; } }
78 | encodeImage(data, width, height, { preset, lossless, quality, method, exact, advanced } = {}) {
79 | let { api, Module } = this, p, ret = {}, enc;
80 | this.initEnc();
81 | enc = this.enc;
82 | enc.init();
83 | checkOpts({ preset, lossless, quality, method, exact });
84 | if (preset != undefined) { enc.setPreset(preset); }
85 | if (lossless != undefined) { enc.setLosslessPreset(lossless); }
86 | if (quality != undefined) { enc.setQuality(quality); }
87 | if (method != undefined) { enc.setMethod(method); }
88 | if (exact != undefined) { enc.setExact(!!exact); }
89 | if (advanced != undefined) {
90 | checkAdv(advanced);
91 | if (advanced.imageHint != undefined) { enc.advImageHint(advanced.imageHint); }
92 | if (advanced.targetSize != undefined) { enc.advTargetSize(advanced.targetSize); }
93 | if (advanced.targetPSNR != undefined) { enc.advTargetPSNR(advanced.targetPSNR); }
94 | if (advanced.segments != undefined) { enc.advSegments(advanced.segments); }
95 | if (advanced.snsStrength != undefined) { enc.advSnsStrength(advanced.snsStrength); }
96 | if (advanced.filterStrength != undefined) { enc.advFilterStrength(advanced.filterStrength); }
97 | if (advanced.filterSharpness != undefined) { enc.advFilterSharpness(advanced.filterSharpness); }
98 | if (advanced.filterType != undefined) { enc.advFilterType(advanced.filterType); }
99 | if (advanced.autoFilter != undefined) { enc.advAutoFilter(advanced.autoFilter); }
100 | if (advanced.alphaCompression != undefined) { enc.advAlphaCompression(advanced.alphaCompression); }
101 | if (advanced.alphaFiltering != undefined) { enc.advAlphaFiltering(advanced.alphaFiltering); }
102 | if (advanced.alphaQuality != undefined) { enc.advAlphaQuality(advanced.alphaQuality); }
103 | if (advanced.pass != undefined) { enc.advPass(advanced.pass); }
104 | if (advanced.showCompressed != undefined) { enc.advShowCompressed(advanced.showCompressed); }
105 | if (advanced.preprocessing != undefined) { enc.advPreprocessing(advanced.preprocessing); }
106 | if (advanced.partitions != undefined) { enc.advPartitions(advanced.partitions); }
107 | if (advanced.partitionLimit != undefined) { enc.advPartitionLimit(advanced.partitionLimit); }
108 | if (advanced.emulateJpegSize != undefined) { enc.advEmulateJpegSize(advanced.emulateJpegSize); }
109 | if (advanced.threadLevel != undefined) { enc.advThreadLevel(advanced.threadLevel); }
110 | if (advanced.lowMemory != undefined) { enc.advLowMemory(advanced.lowMemory); }
111 | if (advanced.nearLossless != undefined) { enc.advNearLossless(advanced.nearLossless); }
112 | if (advanced.useDeltaPalette != undefined) { enc.advUseDeltaPalette(advanced.useDeltaPalette); }
113 | if (advanced.useSharpYUV != undefined) { enc.advUseSharpYUV(advanced.useSharpYUV); }
114 | if (advanced.qMin != undefined) { enc.advQMin(advanced.qMin); }
115 | if (advanced.qMax != undefined) { enc.advQMax(advanced.qMax); }
116 | }
117 | p = api.allocBuffer(data.length);
118 | Module.HEAP8.set(data, p);
119 | enc.loadRGBA(p, width, height);
120 | api.destroyBuffer(p);
121 | ret.res = enc.encode();
122 | if (ret.res == 0) { ret.buf = api.getResult(enc); }
123 | this.destroyEnc();
124 | return ret;
125 | }
126 | decodeImage(data, width, height) {
127 | let { api, Module } = this, p, ret;
128 | let np = api.allocBuffer(data.length);
129 | Module.HEAP8.set(data, np);
130 | let bp = api.decodeRGBA(np, data.length);
131 | ret = new Uint8Array(new Uint8Array(Module.HEAP8.buffer, bp, width * height * 4));
132 | api.decodeFree(bp);
133 | api.destroyBuffer(np);
134 | return ret;
135 | }
136 | };
137 |
--------------------------------------------------------------------------------
/COPYING.LESSER:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/libwebp/binding.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include "libwebp/src/webp/encode.h"
21 | #include "libwebp/src/webp/decode.h"
22 |
23 | using namespace emscripten;
24 |
25 | class WebPEnc {
26 | public:
27 | WebPEnc() { this->ready = false; this->picAlloc = false; }
28 | ~WebPEnc() { this->reset(); }
29 | bool init() {
30 | if (this->ready) { return false; }
31 | WebPPictureInit(&(this->pic));
32 | WebPMemoryWriterInit(&(this->writer));
33 | this->pic.writer = WebPMemoryWrite;
34 | this->pic.custom_ptr = &(this->writer);
35 | WebPConfigInit(&(this->config));
36 | this->ready = true;
37 | return true;
38 | }
39 | void reset() {
40 | if (!this->ready) { return; }
41 | if (this->picAlloc) { WebPPictureFree(&(this->pic)); }
42 | WebPMemoryWriterClear(&(this->writer));
43 | this->ready = false;
44 | this->picAlloc = false;
45 | }
46 | // Clunky workaround for Embind not supporting pointers to primitives (first argument should be a const uint8_t *)
47 | bool loadRGBA(const int input, int width, int height) {
48 | if (!this->ready) { return false; }
49 | this->pic.width = width;
50 | this->pic.height = height;
51 | WebPPictureImportRGBA(&(this->pic), reinterpret_cast(input), width * 4);
52 | this->picAlloc = true;
53 | return true;
54 | }
55 | bool setPreset(int en) {
56 | if (!this->ready) { return false; }
57 | if (en > 0) { WebPConfigPreset(&(this->config), (WebPPreset)en, 100.0f); }
58 | else { WebPConfigInit(&(this->config)); }
59 | return true;
60 | }
61 | bool setLosslessPreset(int en) {
62 | if (!this->ready) { return false; }
63 | if (en > 0) { WebPConfigLosslessPreset(&(this->config), en); this->pic.use_argb = 1; }
64 | else { WebPConfigInit(&(this->config)); this->pic.use_argb = 0; }
65 | return true;
66 | }
67 | bool setQuality(float q) { if (!this->ready) { return false; } this->config.quality = q; return true; }
68 | bool setMethod(int m) { if (!this->ready) { return false; } this->config.method = m; return true; }
69 | bool setExact(bool ex) { if (!this->ready) { return false; } this->config.exact = ex ? 1 : 0; return true; }
70 | int encode() {
71 | if (!this->ready) { return -1; }
72 | if (!WebPValidateConfig(&(this->config))) { return -2; }
73 | if (!WebPEncode(&(this->config), &(this->pic))) { return this->pic.error_code; }
74 | return 0;
75 | }
76 | // Clunky workaround for Embind not supporting pointers to primitives (this should return uint8_t*)
77 | int getResult() { return (int)this->writer.mem; }
78 | size_t getResultSize() { return this->writer.size; }
79 | bool advImageHint(int en) { if (!this->ready) { return false; } this->config.image_hint = (WebPImageHint)en; return true; }
80 | bool advTargetSize(int s) { if (!this->ready) { return false; } this->config.target_size = s; return true; }
81 | bool advTargetPSNR(float psnr) { if (!this->ready) { return false; } this->config.target_PSNR = psnr; return true; }
82 | bool advSegments(int seg) { if (!this->ready) { return false; } this->config.segments = seg; return true; }
83 | bool advSnsStrength(int str) { if (!this->ready) { return false; } this->config.sns_strength = str; return true; }
84 | bool advFilterStrength(int str) { if (!this->ready) { return false; } this->config.filter_strength = str; return true; }
85 | bool advFilterSharpness(int shr) { if (!this->ready) { return false; } this->config.filter_sharpness = shr ? 1 : 0; return true; }
86 | bool advFilterType(int type) { if (!this->ready) { return false; } this->config.filter_type = type ? 1 : 0; return true; }
87 | bool advAutoFilter(int filter) { if (!this->ready) { return false; } this->config.autofilter = filter ? 1 : 0; return true; }
88 | bool advAlphaCompression(int comp) { if (!this->ready) { return false; } this->config.alpha_compression = comp; return true; }
89 | bool advAlphaFiltering(int filter) { if (!this->ready) { return false; } this->config.alpha_filtering = filter; return true; }
90 | bool advAlphaQuality(int qual) { if (!this->ready) { return false; } this->config.alpha_quality = qual; return true; }
91 | bool advPass(int pass) { if (!this->ready) { return false; } this->config.pass = pass; return true; }
92 | bool advShowCompressed(int comp) { if (!this->ready) { return false; } this->config.show_compressed = comp ? 1 : 0; return true; }
93 | bool advPreprocessing(int prepro) { if (!this->ready) { return false; } this->config.preprocessing = prepro; return true; }
94 | bool advPartitions(int parts) { if (!this->ready) { return false; } this->config.partitions = parts; return true; }
95 | bool advPartitionLimit(int limit) { if (!this->ready) { return false; } this->config.partition_limit = limit; return true; }
96 | bool advEmulateJpegSize(int emulate) { if (!this->ready) { return false; } this->config.emulate_jpeg_size = emulate ? 1 : 0; return true; }
97 | bool advThreadLevel(int threads) { if (!this->ready) { return false; } this->config.thread_level = threads; return true; }
98 | bool advLowMemory(int low) { if (!this->ready) { return false; } this->config.low_memory = low ? 1 : 0; return true; }
99 | bool advNearLossless(int near) { if (!this->ready) { return false; } this->config.near_lossless = near; return true; }
100 | bool advUseDeltaPalette(int delta) { if (!this->ready) { return false; } this->config.use_delta_palette = 0 /*delta*/; return true; }
101 | bool advUseSharpYUV(int sharp) { if (!this->ready) { return false; } this->config.use_sharp_yuv = sharp ? 1 : 0; return true; }
102 | bool advQMin(int min) { if (!this->ready) { return false; } this->config.qmin = min; return true; }
103 | bool advQMax(int max) { if (!this->ready) { return false; } this->config.qmax = max; return true; }
104 | private:
105 | bool ready;
106 | bool picAlloc;
107 | WebPConfig config;
108 | WebPPicture pic;
109 | WebPMemoryWriter writer;
110 | };
111 | // Encoder hooks
112 | EMSCRIPTEN_BINDINGS(WebPBinding) {
113 | class_("WebPEnc")
114 | .constructor<>()
115 | .function("init", &WebPEnc::init)
116 | .function("reset", &WebPEnc::reset)
117 | .function("loadRGBA", &WebPEnc::loadRGBA)
118 | .function("setPreset", &WebPEnc::setPreset)
119 | .function("setLosslessPreset", &WebPEnc::setLosslessPreset)
120 | .function("setQuality", &WebPEnc::setQuality)
121 | .function("setMethod", &WebPEnc::setMethod)
122 | .function("setExact", &WebPEnc::setExact)
123 | .function("encode", &WebPEnc::encode)
124 | .function("getResult", &WebPEnc::getResult)
125 | .function("getResultSize", &WebPEnc::getResultSize)
126 | .function("advImageHint", &WebPEnc::advImageHint)
127 | .function("advTargetSize", &WebPEnc::advTargetSize)
128 | .function("advTargetPSNR", &WebPEnc::advTargetPSNR)
129 | .function("advSegments", &WebPEnc::advSegments)
130 | .function("advSnsStrength", &WebPEnc::advSnsStrength)
131 | .function("advFilterStrength", &WebPEnc::advFilterStrength)
132 | .function("advFilterSharpness", &WebPEnc::advFilterSharpness)
133 | .function("advFilterType", &WebPEnc::advFilterType)
134 | .function("advAutoFilter", &WebPEnc::advAutoFilter)
135 | .function("advAlphaCompression", &WebPEnc::advAlphaCompression)
136 | .function("advAlphaFiltering", &WebPEnc::advAlphaFiltering)
137 | .function("advAlphaQuality", &WebPEnc::advAlphaQuality)
138 | .function("advPass", &WebPEnc::advPass)
139 | .function("advShowCompressed", &WebPEnc::advShowCompressed)
140 | .function("advPreprocessing", &WebPEnc::advPreprocessing)
141 | .function("advPartitions", &WebPEnc::advPartitions)
142 | .function("advPartitionLimit", &WebPEnc::advPartitionLimit)
143 | .function("advEmulateJpegSize", &WebPEnc::advEmulateJpegSize)
144 | .function("advThreadLevel", &WebPEnc::advThreadLevel)
145 | .function("advLowMemory", &WebPEnc::advLowMemory)
146 | .function("advNearLossless", &WebPEnc::advNearLossless)
147 | .function("advUseDeltaPalette", &WebPEnc::advUseDeltaPalette)
148 | .function("advUseSharpYUV", &WebPEnc::advUseSharpYUV)
149 | .function("advQMin", &WebPEnc::advQMin)
150 | .function("advQMax", &WebPEnc::advQMax);
151 | }
152 | extern "C" {
153 | // Decoder
154 | EMSCRIPTEN_KEEPALIVE uint8_t *decodeRGBA(const uint8_t *data, size_t dataSize) { return WebPDecodeRGBA(data, dataSize, 0, 0); }
155 | EMSCRIPTEN_KEEPALIVE void decodeFree(uint8_t *data) { WebPFree(data); }
156 | // Utility
157 | EMSCRIPTEN_KEEPALIVE uint8_t *allocBuffer(size_t size) { return (uint8_t*)malloc(size * sizeof(uint8_t)); }
158 | EMSCRIPTEN_KEEPALIVE void destroyBuffer(uint8_t *p) { free(p); }
159 | }
160 |
--------------------------------------------------------------------------------
/bin/webpmux:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | node-webpmux - NodeJS module for interacting with WebP images
5 | Copyright (C) 2023 ApeironTsuka
6 |
7 | This program is free software: you can redistribute it and/or modify
8 | it under the terms of the GNU Lesser General Public License as published by
9 | the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 |
12 | This program is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU Lesser General Public License for more details.
16 | */
17 |
18 | const fs = require('fs');
19 | const WebP = require('../webp.js');
20 | const intTest = /^[0-9]+$/;
21 |
22 | function parseDuration(d) {
23 | let a = d.split(',');
24 | if (a.length == 1) { return { dur: a[0], start: 0, end: 0 }; }
25 | if (a.length == 2) { return { dur: a[0], start: a[1], end: a[1] }; }
26 | if (a.length == 3) { return { dur: a[0], start: a[1], end: a[2] }; }
27 | throw new Error('Failed to parse duration');
28 | }
29 | function parseFrame(f) {
30 | let out = {}, a = f.split('+');
31 | if (a.length < 2) { throw new Error('Failed to parse frame setting shorthand'); }
32 | out.duration = a[1];
33 | out.x = a[2];
34 | out.y = a[3];
35 | if (a[4] == 1) { out.dispose = true; }
36 | else if (a[4] == 0) { out.dispose = false; }
37 | else if (a[4] !== undefined) {
38 | let x = a[4].split('-');
39 | if (x[0] == 1) { out.dispose = true; }
40 | else if (x[0] == 0) { out.dispose = false; }
41 | if (x[1] == 'b') { out.blend = false; }
42 | }
43 | if (a[5] == 'b') { out.blend = true; }
44 | return out;
45 | }
46 | function parseCmdLine(args) {
47 | let state = {}, tester = /^-/;
48 | let test = (_i) => {
49 | let i = _i+1;
50 | if (i >= args.length) { return false; }
51 | else if (tester.test(args[i])) { return false; }
52 | return true;
53 | };
54 | for (let i = 0, l = args.length; i < l; i++) {
55 | switch (args[i]) {
56 | case '-get':
57 | if (!test(i)) { throw new Error('GET_OPTS missing argument'); }
58 | state.get = { what: args[++i] };
59 | switch (state.get.what) {
60 | case 'icc': case 'iccp': state.get.what = 'iccp'; break;
61 | case 'exif': case 'xmp': break;
62 | case 'frame':
63 | if (!test(i)) { throw new Error('GET_OPTS frame missing argument'); }
64 | state.get.frame = args[++i];
65 | break;
66 | default: throw new Error(`Unknown GET_OPTS ${state.set.what}`);
67 | }
68 | break;
69 | case '-set':
70 | if (!test(i)) { throw new Error('SET_OPTS missing argument'); }
71 | state.set = { what: args[++i] };
72 | switch (state.set.what) {
73 | case 'loop': if (!test(i)) { throw new Error('SET_OPTS loop missing argument'); } state.set.loop = args[++i]; break;
74 | case 'iccp': case 'icc': if (!test(i)) { throw new Error(`SET_OPTS ${state.set.what} missing argument`); } state.set.what = 'iccp'; state.set.iccp = args[++i]; break;
75 | case 'exif': if (!test(i)) { throw new Error('SET_OPTS exif missing argument'); } state.set.exif = args[++i]; break;
76 | case 'xmp': if (!test(i)) { throw new Error('SET_OPTS xmp missing argument'); } state.set.xmp = args[++i]; break;
77 | default: throw new Error(`Unknown SET_OPTS ${state.set.what}`);
78 | }
79 | break;
80 | case '-strip': if (!test(i)) { throw new Error('STRIP_OPTS missing argument'); } state.strip = args[++i]; break;
81 | case '-duration': if (!test(i)) { throw new Error('DUR_OPTS missing argument'); } if (!state.duration) { state.duration = []; } state.duration.push(parseDuration(args[++i])); break;
82 | case '-frame':
83 | {
84 | let f = {};
85 | if (!test(i)) { throw new Error('FRAME_OPTS missing argument'); }
86 | if (!state.frames) { state.frames = []; }
87 | f.path = args[++i];
88 | if (!/\.webp$/i.test(f.path)) { throw new Error('First argument to -frame must be a webp image'); }
89 | if (!test(i)) { throw new Error('Missing arguments in -frame'); }
90 | if (args[i+1][0] == '+') { f.bin = parseFrame(args[++i]); }
91 | else {
92 | let ni;
93 | for (let x = i+1, xl = l; x < xl; x++) {
94 | switch (args[x]) {
95 | case 'duration': if (!test(x)) { throw new Error('FRAME_OPTS duration missing argument'); } f.duration = args[++x]; break;
96 | case 'x': if (!test(x)) { throw new Error('FRAME_OPTS x missing argument'); } f.x = args[++x]; break;
97 | case 'y': if (!test(x)) { throw new Error('FRAME_OPTS y missing argument'); } f.y = args[++x]; break;
98 | case 'dispose': if (!test(x)) { throw new Error('FRAME_OPTS dispose missing argument'); } f.dispose = args[++x]; break;
99 | case 'blend': if (!test(x)) { throw new Error('FRAME_OPTS blend missing argument'); } f.blend = args[++x]; break;
100 | default: ni = x-1; xl = x; break;
101 | }
102 | }
103 | i = ni;
104 | }
105 | state.frames.push(f);
106 | }
107 | break;
108 | case '-info': state.info = true; break;
109 | case '-h': case '-help': state.help = true; break;
110 | case '-version': state.version = true; break;
111 | case '-o': if (!test(i)) { throw new Error('OUT missing argument'); } state.out = args[++i]; break;
112 | case '-loop': if (!test(i)) { throw new Error('COUNT missing argument'); } state.loop = args[++i]; break;
113 | case '-bg': if (!test(i)) { throw new Error('COLOR missing argument'); } state.bg = args[++i].split(','); break;
114 | default: if (!state.in) { state.in = args[i]; } else { throw new Error(`Unknown flag ${args[i]}`); }
115 | }
116 | }
117 | return state;
118 | }
119 | function printHelp() {
120 | console.log(`Usage: webpmux -get GET_OPTS IN -o OUT
121 | webpmux -set SET_OPTS IN -o OUT
122 | webpmux -strip STRIP_OPTS IN -o OUT
123 | webpmux -duration DUR_OPTS [-duration ...] IN -o OUT
124 | webpmux -frame FRAME_OPTS [-frame ...] [-loop COUNT] [-bg COLOR] -o OUT
125 | webpmux -info IN
126 | webpmux [-h|-help]
127 | webpmux -version
128 |
129 | GET_OPTS:
130 | Extract the relevant data:
131 | iccp get ICC profile
132 | icc get ICC profile (backwards support)
133 | exif get EXIF metadata
134 | xmp get XMP metadata
135 | frame n get nth frame (first frame is frame 1)
136 |
137 | SET_OPTS:
138 | Set color profile/metadata:
139 | loop COUNT set the loop count
140 | iccp file.iccp set the ICC profile
141 | icc file.icc set the ICC profile (backwards support)
142 | exif file.exif set the EXIF metadata
143 | xmp file.xmp set the XMP metadata
144 | where: 'file.icc'/'file.iccp' contains the ICC profile to be set.
145 | 'file.exif' contains the EXIF metadata to be set.
146 | 'file.xmp' contains the XMP metadata to be set.
147 |
148 | DUR_OPTS:
149 | Set duration of selected frames
150 | duration set duration for each frame
151 | duration,frame set duration of a particular frame
152 | duration,start,end set duration of frames in the
153 | interval [start, end]
154 | where: 'duration' is the duration in milliseconds.
155 | 'start' is the start frame index.
156 | 'end' is the inclusive end frame index.
157 | The special 'end' value '0' means: last frame.
158 |
159 | STRIP_OPTS:
160 | Strip color profile/metadata:
161 | iccp strip ICC profile
162 | icc strip ICC profile (for backwards support)
163 | exif strip EXIF metadata
164 | xmp strip XMP metadata
165 |
166 | FRAME_OPTS:
167 | Create an animation frame:
168 | frame.webp the animation frame
169 | WEBPMUX_FRAMES legacy frame settings
170 | OR
171 | frame.webp the animation frame
172 | duration N the pause duration before next frame
173 | x X the x offset for this frame
174 | y Y the y offset for this frame
175 | dispose on/off dispose method for this frame (on: background, off: none)
176 | blend on/off blending method for this frame
177 |
178 | COUNT:
179 | Number of times to repeat the animation.
180 | Valid range is 0 to 65535 [Default: 0 (infinite)]
181 |
182 | COLOR:
183 | Background color of the animation canvas.
184 | R,G,B,A ('normal' mode)
185 | A,R,G,B ('legacy' mode)
186 | where: 'A', 'R', 'G', and 'B' are integers in the range 0 to 255 specifying
187 | the Alpha, Red, Green, and Blue component values respectively
188 | [Default: 255, 255, 255, 255]
189 |
190 | WEBPMUX_FRAMES (for drop-in support for the upstream webpmux binary, puts it into 'legacy' mode):
191 | +d[+x+y[+m[+b]]]
192 | where: 'd' is the pause duration before next frame
193 | 'x', 'y' specify the image offset for this frame
194 | 'm' is the dispose method for this frame (0 or 1)
195 | 'b' is the blending method for this frame (+b or -b)
196 |
197 | IN & OUT are in WebP format.
198 |
199 | Note: The nature of EXIF, XMP, and ICC data is not checked and is assumed to be valid.`);
200 | }
201 | function printInfo(img) {
202 | let f = [];
203 | let pad = (s, n) => { let o = `${s}`; while (o.length < n) { o = ` ${o}`; } return o; };
204 | let fra = (fr) => { return fr.vp8 ? fr.vp8.alpha : fr.vp8l ? fr.vp8l.alpha : false; };
205 | let bgcol = (c) => { return `0x${c[0].toString(16)}${c[0].toString(16)}${c[1].toString(16)}${c[2].toString(16)}${c[3].toString(16)}`.toUpperCase(); }
206 | console.log(`Canvas size: ${img.width} x ${img.height}`);
207 | if (img.hasAnim) { f.push('animation'); }
208 | if (img.hasAlpha) { f.push(!img.hasAnim ? 'transparency' : 'alpha'); }
209 | if (f.length == 0) { console.log('No features present.'); }
210 | else { console.log(`Features present: ${f.join(' ')}`); }
211 | if (img.hasAnim) {
212 | console.log(`Background color : ${bgcol(img.anim.bgColor)} Loop Count : ${img.anim.loops}`);
213 | console.log(`Number of frames: ${img.frames.length}`);
214 | console.log('No.: width height alpha x_offset y_offset duration dispose blend image_size compression');
215 | for (let i = 0, fr = img.frames, l = fr.length; i < l; i++) {
216 | let out = '';
217 | out += `${pad(i+1, 3)}: ${pad(fr[i].width, 5)} ${pad(fr[i].height, 5)} ${fra(fr[i]) ? 'yes' : ' no'} `;
218 | out += `${pad(fr[i].x, 8)} ${pad(fr[i].y, 8)} ${pad(fr[i].delay, 8)} ${pad(fr[i].dispose ? 'background' : 'none', 10)} `;
219 | out += `${pad(fr[i].blend ? 'yes' : 'no', 5)} ${pad(fr[i].alph ? fr[i].raw.length+14 : fr[i].raw.length-4, 10)} `;
220 | out += `${pad(fr[i].vp8 ? 'lossy' : 'lossless', 11)}`;
221 | console.log(out);
222 | }
223 | } else {
224 | let size = (fs.statSync(img.path)).size;
225 | if (img.hasAlpha) { console.log(`Size of the image (with alpha): ${size}`); }
226 | }
227 | }
228 | async function main() {
229 | let state = parseCmdLine(process.argv.slice(2)), img = new WebP.Image(), d;
230 | if (state.help) { printHelp(); }
231 | else if (state.version) { console.log(`node-webpmux ${JSON.parse(fs.readFileSync(`${__dirname}/../package.json`)).version}`); }
232 | else if (state.get) {
233 | if (!state.in) { console.log('Missing input file'); return; }
234 | if (!state.out) { console.log('Missing output file'); return; }
235 | try { await img.load(state.in); }
236 | catch (e) { console.log(`Error opening ${state.in}`); return; }
237 | switch (state.get.what) {
238 | case 'iccp': d = img.iccp; break;
239 | case 'exif': d = img.exif; break;
240 | case 'xmp': d = img.xmp; break;
241 | case 'frame': d = (await img.demuxToBuffers({ frame: d.frame-1 }))[0]; break;
242 | }
243 | fs.writeFileSync(state.out, d);
244 | }
245 | else if (state.set) {
246 | if (!state.in) { console.log('Missing input file'); return; }
247 | if (!state.out) { console.log('Missing output file'); return; }
248 | try { await img.load(state.in); }
249 | catch (e) { console.log(`Error opening ${state.in}`); return; }
250 | switch (state.set.what) {
251 | case 'loop':
252 | if (!img.hasAnim) { console.log("Image isn't an animation; cannot set loop count"); return; }
253 | if (!intTest(state.set.loop)) { console.log('Loop count must be a number 0 <= n <= 65535'); return; }
254 | if ((state.set.loop < 0) || (state.set.loop >= 65536)) { console.log('Loop count must be a number 0 <= n <= 65535'); return; }
255 | img.anim.loops = state.set.loop;
256 | try { await img.save(state.out); }
257 | catch (e) { console.log(e); }
258 | break;
259 | case 'iccp':
260 | case 'exif':
261 | case 'xmp':
262 | try { d = fs.readFileSync(state.set[state.set.what]); }
263 | catch (e) { console.log(`Could not open/read ${state.set[state.set.what]}`); return; }
264 | img[state.set.what] = d;
265 | try { await img.save(state.out); }
266 | catch (e) { console.log(e); }
267 | break;
268 | }
269 | }
270 | else if (state.strip) {
271 | if (!state.in) { console.log('Missing input file'); return; }
272 | if (!state.out) { console.log('Missing output file'); return; }
273 | try { await img.load(state.in); }
274 | catch (e) { console.log(`Error opening ${state.in}`); return; }
275 | img[state.strip.what] = undefined;
276 | try { await img.save(state.out); }
277 | catch (e) { console.log(e); }
278 | }
279 | else if (state.duration) {
280 | if (!state.in) { console.log('Missing input file'); return; }
281 | if (!state.out) { console.log('Missing output file'); return; }
282 | try { await img.load(state.in); }
283 | catch (e) { console.log(`Error opening ${state.in}`); return; }
284 | if (!img.hasAnim) { console.log("Image isn't an animation; cannot set frame durations"); return; }
285 | for (let i = 0, dur = state.duration, l = dur.length; i < l; i++) {
286 | if (!intTest(dur.dur)) { console.log('Duration must be a number'); return; }
287 | if (!intTest(dur.start)) { console.log('Start frame must be a number'); return; }
288 | if (!intTest(dur.end)) { console.log('End grame must be a number'); return; }
289 | if (dur.end == 0) { dur.end = img.frames.length-1; }
290 | if (dur.end >= img.frames.length) { console.log('Warning: End frame beyond frame count; clipping'); dur.end = img.frames.length-1; }
291 | if (dur.start >= img.frames.length) { console.log('Warning: Start frame beyond frame count; clipping'); dur.start = img.frames.length-1; }
292 | for (let x = dur.start, xl = dur.end; x <= xl; x++) { img.frames[x].delay = dur.dur; }
293 | }
294 | try { await img.save(state.out); }
295 | catch (e) { console.log(e); }
296 | }
297 | else if (state.frames) {
298 | let bin = false;
299 | if (!state.out) { console.log('Missing output file'); return; }
300 | img = await WebP.Image.getEmptyImage();
301 | img.convertToAnim();
302 | for (let i = 0, f = state.frames, l = f.length; i < l; i++) {
303 | if (f[i].bin) { bin = true; d = f[i].bin; }
304 | else { d = f[i]; }
305 | d = WebP.Image.generateFrame({
306 | path: f[i].path,
307 | x: d.x,
308 | y: d.y,
309 | delay: d.duration,
310 | dispose: d.dispose,
311 | blend: d.blend
312 | });
313 | img.frames.push(d);
314 | }
315 | if (state.loop !== undefined) { img.anim.loops = state.loop; }
316 | if (state.bg !== undefined) {
317 | if (bin) { img.anim.bgColor = [ state.bg[3], state.bg[0], state.bg[1], state.bg[2] ]; }
318 | else { img.anim.bgColor = [ state.bg[0], state.bg[1], state.bg[2], state.bg[3] ]; }
319 | }
320 | try { await img.save(state.out); }
321 | catch (e) { console.log(e); }
322 | }
323 | else if (state.info) {
324 | if (!state.in) { console.log('Missing input file'); return; }
325 | try { await img.load(state.in); }
326 | catch (e) { console.log(`Error opening ${state.in}`); return; }
327 | printInfo(img);
328 | } else { printHelp(); }
329 | }
330 | main().then(()=>{});
331 |
--------------------------------------------------------------------------------
/parser.js:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 |
17 | const IO = require('./io.js');
18 | const nullByte = Buffer.alloc(1);
19 | nullByte[0] = 0;
20 | const intfTypes = {
21 | NONE: 0,
22 | FILE: 1,
23 | BUFFER: 2
24 | };
25 | const constants = {
26 | TYPE_LOSSY: 0,
27 | TYPE_LOSSLESS: 1,
28 | TYPE_EXTENDED: 2
29 | };
30 | function VP8Width(data) { return ((data[7] << 8) | data[6]) & 0b0011111111111111; }
31 | function VP8Height(data) { return ((data[9] << 8) | data[8]) & 0b0011111111111111; }
32 | function VP8LWidth(data) { return (((data[2] << 8) | data[1]) & 0b0011111111111111) + 1; }
33 | function VP8LHeight(data) { return ((((data[4] << 16) | (data[3] << 8) | data[2]) >> 6) & 0b0011111111111111) + 1; }
34 | function doesVP8LHaveAlpha(data) { return !!(data[4] & 0b00010000); }
35 | function createBasicChunk(name, data) {
36 | let header = Buffer.alloc(8), size = data.length;
37 | header.write(name, 0);
38 | header.writeUInt32LE(size, 4);
39 | if (size&1) { return { size: size + 9, chunks: [ header, data, nullByte ] }; }
40 | else { return { size: size + 8, chunks: [ header, data ] }; }
41 | }
42 | class WebPReader {
43 | constructor() { this.type = intfTypes.NONE; }
44 | readFile(path) { this.type = intfTypes.FILE; this.path = path; }
45 | readBuffer(buf) { this.type = intfTypes.BUFFER; this.buf = buf; this.cursor = 0; }
46 | async readBytes(n, mod) {
47 | let { type } = this;
48 | if (type == intfTypes.FILE) {
49 | let b = Buffer.alloc(n), br;
50 | br = (await IO.read(this.fp, b, 0, n, undefined)).bytesRead;
51 | return mod ? b : br == n ? b : undefined;
52 | } else if (type == intfTypes.BUFFER) { let b = this.buf.slice(this.cursor, this.cursor + n); this.cursor += n; return b; }
53 | else { throw new Error('Reader not initialized'); }
54 | }
55 | async readFileHeader() {
56 | let buf = await this.readBytes(12);
57 | if (buf === undefined) { throw new Error('Reached end while reading header'); }
58 | if (buf.toString('utf8', 0, 4) != 'RIFF') { throw new Error('Bad header (not RIFF)'); }
59 | if (buf.toString('utf8', 8, 12) != 'WEBP') { throw new Error('Bad header (not WEBP)'); }
60 | return { fileSize: buf.readUInt32LE(4) };
61 | }
62 | async readChunkHeader() {
63 | let buf = await this.readBytes(8, true);
64 | if (buf.length == 0) { return { fourCC: '\x00\x00\x00\x00', size: 0 }; }
65 | else if (buf.length < 8) { throw new Error('Reached end while reading chunk header'); }
66 | return { fourCC: buf.toString('utf8', 0, 4), size: buf.readUInt32LE(4) };
67 | }
68 | async readChunkContents(size) {
69 | let buf = await this.readBytes(size);
70 | if (size & 1) { await this.readBytes(1); }
71 | return buf;
72 | }
73 | async readChunk_raw(n, size) {
74 | let buf = await this.readChunkContents(size);
75 | if (buf === undefined) { throw new Error(`Reached end while reading ${n} chunk`); }
76 | return { raw: buf };
77 | }
78 | async readChunk_VP8(size) {
79 | let buf = await this.readChunkContents(size);
80 | if (buf === undefined) { throw new Error('Reached end while reading VP8 chunk'); }
81 | return { raw: buf, width: VP8Width(buf), height: VP8Height(buf) };
82 | }
83 | async readChunk_VP8L(size) {
84 | let buf = await this.readChunkContents(size);
85 | if (buf === undefined) { throw new Error('Reached end while reading VP8L chunk'); }
86 | return { raw: buf, alpha: doesVP8LHaveAlpha(buf), width: VP8LWidth(buf), height: VP8LHeight(buf) };
87 | }
88 | async readChunk_VP8X(size) {
89 | let buf = await this.readChunkContents(size);
90 | if (buf === undefined) { throw new Error('Reached end while reading VP8X chunk'); }
91 | return {
92 | raw: buf,
93 | hasICCP: !!(buf[0] & 0b00100000),
94 | hasAlpha: !!(buf[0] & 0b00010000),
95 | hasEXIF: !!(buf[0] & 0b00001000),
96 | hasXMP: !!(buf[0] & 0b00000100),
97 | hasAnim: !!(buf[0] & 0b00000010),
98 | width: buf.readUIntLE(4, 3) + 1,
99 | height: buf.readUIntLE(7, 3) + 1
100 | };
101 | }
102 | async readChunk_ANIM(size) {
103 | let buf = await this.readChunkContents(size);
104 | if (buf === undefined) { throw new Error('Reached end while reading ANIM chunk'); }
105 | return { raw: buf, bgColor: buf.slice(0, 4), loops: buf.readUInt16LE(4) };
106 | }
107 | async readChunk_ANMF(size) {
108 | let buf = await this.readChunkContents(size);
109 | if (buf === undefined) { throw new Error('Reached end while reading ANMF chunk'); }
110 | let out = {
111 | raw: buf,
112 | x: buf.readUIntLE(0, 3),
113 | y: buf.readUIntLE(3, 3),
114 | width: buf.readUIntLE(6, 3) + 1,
115 | height: buf.readUIntLE(9, 3) + 1,
116 | delay: buf.readUIntLE(12, 3),
117 | blend: !(buf[15] & 0b00000010),
118 | dispose: !!(buf[15] & 0b00000001)
119 | }, keepLooping = true, anmfReader = new WebPReader();
120 | anmfReader.readBuffer(buf);
121 | anmfReader.cursor = 16;
122 | while (keepLooping) {
123 | let header = await anmfReader.readChunkHeader();
124 | switch (header.fourCC) {
125 | case 'VP8 ':
126 | if (!out.vp8) {
127 | out.type = constants.TYPE_LOSSY;
128 | out.vp8 = await anmfReader.readChunk_VP8(header.size);
129 | if (out.alph) { out.vp8.alpha = true; }
130 | }
131 | break;
132 | case 'VP8L':
133 | if (!out.vp8l) {
134 | out.type = constants.TYPE_LOSSLESS;
135 | out.vp8l = await anmfReader.readChunk_VP8L(header.size);
136 | }
137 | break;
138 | case 'ALPH':
139 | if (!out.alph) {
140 | out.alph = await anmfReader.readChunk_ALPH(header.size);
141 | if (out.vp8) { out.vp8.alpha = true; }
142 | }
143 | break;
144 | case '\x00\x00\x00\x00':
145 | default:
146 | keepLooping = false;
147 | break;
148 | }
149 | if (anmfReader.cursor >= buf.length) { break; }
150 | }
151 | return out;
152 | }
153 | async readChunk_ALPH(size) { return this.readChunk_raw('ALPH', size); }
154 | async readChunk_ICCP(size) { return this.readChunk_raw('ICCP', size); }
155 | async readChunk_EXIF(size) { return this.readChunk_raw('EXIF', size); }
156 | async readChunk_XMP(size) { return this.readChunk_raw('XMP ', size); }
157 | async readChunk_skip(size) {
158 | let buf = await this.readChunkContents(size);
159 | if (buf === undefined) { throw new Error('Reached end while skipping chunk'); }
160 | }
161 | async read() {
162 | if (this.type == intfTypes.FILE) { this.fp = await IO.open(this.path, 'r'); }
163 | let keepLooping = true, first = true, { fileSize } = await this.readFileHeader(), out = {};
164 | while (keepLooping) {
165 | let { fourCC, size } = await this.readChunkHeader();
166 | switch (fourCC) {
167 | case 'VP8 ':
168 | if (!out.vp8) {
169 | out.vp8 = await this.readChunk_VP8(size);
170 | if (out.alph) { out.vp8.alpha = true; }
171 | if (first) { out.type = constants.TYPE_LOSSY; keepLooping = false; }
172 | } else { await this.readChunk_skip(size); }
173 | break;
174 | case 'VP8L':
175 | if (!out.vp8l) {
176 | out.vp8l = await this.readChunk_VP8L(size);
177 | if (first) { out.type = constants.TYPE_LOSSLESS; keepLooping = false; }
178 | } else { await this.readChunk_skip(size); }
179 | break;
180 | case 'VP8X':
181 | if (!out.extended) {
182 | out.type = constants.TYPE_EXTENDED;
183 | out.extended = await this.readChunk_VP8X(size);
184 | } else { await this.readChunk_skip(size); }
185 | break;
186 | case 'ANIM':
187 | if (!out.anim) {
188 | let { raw, bgColor, loops } = await this.readChunk_ANIM(size);
189 | out.anim = {
190 | bgColor: [ bgColor[2], bgColor[1], bgColor[0], bgColor[3] ],
191 | loops,
192 | frames: [],
193 | raw
194 | };
195 | } else { await this.readChunk_skip(size); }
196 | break;
197 | case 'ANMF': out.anim.frames.push(await this.readChunk_ANMF(size)); break;
198 | case 'ALPH':
199 | if (!out.alph) {
200 | out.alph = await this.readChunk_ALPH(size);
201 | if (out.vp8) { out.vp8.alpha = true; }
202 | } else { await this.readChunk_skip(size); }
203 | break;
204 | case 'ICCP':
205 | if (!out.iccp) { out.iccp = await this.readChunk_ICCP(size); }
206 | else { await this.readChunk_skip(size); }
207 | break;
208 | case 'EXIF':
209 | if (!out.exif) { out.exif = await this.readChunk_EXIF(size); }
210 | else { await this.readChunk_skip(size); }
211 | break;
212 | case 'XMP ':
213 | if (!out.xmp) { out.xmp = await this.readChunk_XMP(size); }
214 | else { await this.readChunk_skip(size); }
215 | break;
216 | case '\x00\x00\x00\x00': keepLooping = false; break;
217 | default: await this.readChunk_skip(size); break;
218 | }
219 | first = false;
220 | }
221 | if (this.type == intfTypes.FILE) { await IO.close(this.fp); }
222 | return out;
223 | }
224 | }
225 | class WebPWriter {
226 | constructor() { this.type = intfTypes.NONE; this.chunks = []; this.width = this.height = 0; }
227 | reset() { this.chunks.length = 0; width = 0; height = 0; }
228 | writeFile(path) { this.type = intfTypes.FILE; this.path = path; }
229 | writeBuffer() { this.type = intfTypes.BUFFER; }
230 | async commit() {
231 | let { chunks } = this, size = 4, fp;
232 | if (this.type == intfTypes.NONE) { throw new Error('Writer not initialized'); }
233 | if (chunks.length == 0) { throw new Error('Nothing to write'); }
234 | for (let i = 1, l = chunks.length; i < l; i++) { size += chunks[i].length; }
235 | chunks[0].writeUInt32LE(size, 4);
236 | if (this.type == intfTypes.FILE) {
237 | fp = await IO.open(this.path, 'w');
238 | for (let i = 0, l = chunks.length; i < l; i++) { await IO.write(fp, chunks[i], 0, undefined, undefined); }
239 | await IO.close(fp);
240 | } else { return Buffer.concat(chunks); }
241 | }
242 | writeBytes(...chunks) {
243 | if (this.type == intfTypes.NONE) { throw new Error('Writer not initialized'); }
244 | this.chunks.push(...chunks);
245 | }
246 | writeFileHeader() {
247 | let buf = Buffer.alloc(12);
248 | buf.write('RIFF', 0);
249 | buf.write('WEBP', 8);
250 | this.writeBytes(buf);
251 | }
252 | writeChunk_VP8(vp8) { this.writeBytes(...((createBasicChunk('VP8 ', vp8.raw)).chunks)); }
253 | writeChunk_VP8L(vp8l) { this.writeBytes(...((createBasicChunk('VP8L', vp8l.raw)).chunks)); }
254 | writeChunk_VP8X(vp8x) {
255 | let buf = Buffer.alloc(18);
256 | buf.write('VP8X', 0);
257 | buf.writeUInt32LE(10, 4);
258 | buf.writeUIntLE(vp8x.width - 1, 12, 3);
259 | buf.writeUIntLE(vp8x.height - 1, 15, 3);
260 | if (vp8x.hasICCP) { buf[8] |= 0b00100000; }
261 | if (vp8x.hasAlpha) { buf[8] |= 0b00010000; }
262 | if (vp8x.hasEXIF) { buf[8] |= 0b00001000; }
263 | if (vp8x.hasXMP) { buf[8] |= 0b00000100; }
264 | if (vp8x.hasAnim) { buf[8] |= 0b00000010; }
265 | this.vp8x = buf;
266 | this.writeBytes(buf);
267 | }
268 | updateChunk_VP8X_size(width, height) {
269 | this.vp8x.writeUIntLE(width, 12, 3);
270 | this.vp8x.writeUIntLE(height, 15, 3);
271 | }
272 | writeChunk_ANIM(anim) {
273 | let buf = Buffer.alloc(14);
274 | buf.write('ANIM', 0);
275 | buf.writeUInt32LE(6, 4);
276 | buf.writeUInt8(anim.bgColor[2], 8);
277 | buf.writeUInt8(anim.bgColor[1], 9);
278 | buf.writeUInt8(anim.bgColor[0], 10);
279 | buf.writeUInt8(anim.bgColor[3], 11);
280 | buf.writeUInt16LE(anim.loops, 12);
281 | this.writeBytes(buf);
282 | }
283 | writeChunk_ANMF(anmf) {
284 | let buf = Buffer.alloc(24), { img } = anmf, size = 16, alpha = false;
285 | buf.write('ANMF', 0);
286 | buf.writeUIntLE(anmf.x, 8, 3);
287 | buf.writeUIntLE(anmf.y, 11, 3);
288 | buf.writeUIntLE(anmf.delay, 20, 3);
289 | if (!anmf.blend) { buf[23] |= 0b00000010; }
290 | if (anmf.dispose) { buf[23] |= 0b00000001; }
291 | switch (img.type) {
292 | case constants.TYPE_LOSSY:
293 | {
294 | let b;
295 | this.width = Math.max(this.width, img.vp8.width);
296 | this.height = Math.max(this.height, img.vp8.height);
297 | buf.writeUIntLE(img.vp8.width - 1, 14, 3);
298 | buf.writeUIntLE(img.vp8.height - 1, 17, 3);
299 | this.writeBytes(buf);
300 | if (img.vp8.alpha) {
301 | b = createBasicChunk('ALPH', img.alph.raw);
302 | this.writeBytes(...b.chunks);
303 | size += b.size;
304 | }
305 | b = createBasicChunk('VP8 ', img.vp8.raw);
306 | this.writeBytes(...b.chunks);
307 | size += b.size;
308 | }
309 | break;
310 | case constants.TYPE_LOSSLESS:
311 | {
312 | let b = createBasicChunk('VP8L', img.vp8l.raw);
313 | this.width = Math.max(this.width, img.vp8l.width);
314 | this.height = Math.max(this.height, img.vp8l.height);
315 | buf.writeUIntLE(img.vp8l.width - 1, 14, 3);
316 | buf.writeUIntLE(img.vp8l.height - 1, 17, 3);
317 | if (img.vp8l.alpha) { alpha = true; }
318 | this.writeBytes(buf, ...b.chunks);
319 | size += b.size;
320 | }
321 | break;
322 | case constants.TYPE_EXTENDED:
323 | if (img.extended.hasAnim) {
324 | let fr = img.anim.frames;
325 | if (img.extended.hasAlpha) { alpha = true; }
326 | for (let i = 0, l = fr.length; i < l; i++) {
327 | let b = Buffer.alloc(8), c = fr[i].raw;
328 | this.width = Math.max(this.width, fr[i].width + anmf.x);
329 | this.height = Math.max(this.height, fr[i].height + anmf.y);
330 | b.write('ANMF', 0);
331 | b.writeUInt32LE(c.length, 4);
332 | c.writeUIntLE(anmf.x, 0, 3);
333 | c.writeUIntLE(anmf.y, 3, 3);
334 | c.writeUIntLE(anmf.delay, 12, 3);
335 | if (!anmf.blend) { c[15] |= 0b00000010; } else { c[15] &= 0b11111101; }
336 | if (anmf.dispose) { c[15] |= 0b00000001; } else { c[15] &= 0b11111110; }
337 | this.writeBytes(b, c);
338 | if (c.length & 1) { this.writeBytes(nullByte); }
339 | }
340 | } else {
341 | let b;
342 | this.width = Math.max(this.width, img.extended.width);
343 | this.height = Math.max(this.height, img.extended.height);
344 | if (img.vp8) {
345 | buf.writeUIntLE(img.vp8.width - 1, 14, 3);
346 | buf.writeUIntLE(img.vp8.height - 1, 17, 3);
347 | this.writeBytes(buf);
348 | if (img.alph) {
349 | b = createBasicChunk('ALPH', img.alph.raw);
350 | alpha = true;
351 | this.writeBytes(...b.chunks);
352 | size += b.size;
353 | }
354 | b = createBasicChunk('VP8 ', img.vp8.raw);
355 | this.writeBytes(...b.chunks);
356 | size += b.size;
357 | } else if (img.vp8l) {
358 | buf.writeUIntLE(img.vp8l.width - 1, 14, 3);
359 | buf.writeUIntLE(img.vp8l.height - 1, 17, 3);
360 | if (img.vp8l.alpha) { alpha = true; }
361 | b = createBasicChunk('VP8L', img.vp8l.raw);
362 | this.writeBytes(buf, ...b.chunks);
363 | size += b.size;
364 | }
365 | }
366 | break;
367 | default: throw new Error('Unknown image type');
368 | }
369 | buf.writeUInt32LE(size, 4);
370 | if (alpha) { this.vp8x[8] |= 0b00010000; }
371 | }
372 | writeChunk_ALPH(alph) { this.writeBytes(...((createBasicChunk('ALPH', alph.raw)).chunks)); }
373 | writeChunk_ICCP(iccp) { this.writeBytes(...((createBasicChunk('ICCP', iccp.raw)).chunks)); }
374 | writeChunk_EXIF(exif) { this.writeBytes(...((createBasicChunk('EXIF', exif.raw)).chunks)); }
375 | writeChunk_XMP(xmp) { this.writeBytes(...((createBasicChunk('XMP ', xmp.raw)).chunks)); }
376 | }
377 | module.exports = { WebPReader, WebPWriter };
378 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-webpmux
2 |
3 | A mostly-complete pure Javascript re-implementation of webpmux.
4 | Can load "simple" lossy/lossless images as well as animations.
5 |
6 | ### Install
7 | ```npm install node-webpmux```
8 |
9 | ### Basic usage
10 | ```javascript
11 | const WebP = require('node-webpmux');
12 | let img = new WebP.Image();
13 | // Load an animation
14 | await img.load('img.webp');
15 | // Extract the (unprocessed) fourth frame
16 | await img.demux('.', { frame: 3 });
17 | // Replace the fourth frame with a new image from disk
18 | await img.replaceFrame(3, 'different.webp'); // This preserves the existing frame settings
19 | // Alternatively you can do
20 | // let frame = Image.generateFrame({ path: 'different.webp' });
21 | // img.frames[3] = frame;
22 | // Which will completely replace the frame
23 | // Save a new copy
24 | await img.save({ path: 'newimg.webp' });
25 | // Or alternatively, img.save() to save over the existing one
26 | ```
27 | ### Exports
28 | `TYPE_LOSSY`
29 | `TYPE_LOSSLESS`
30 | `TYPE_EXTENDED`
31 | Constants for what type of image is loaded.
32 |
33 | `encodeResults`: enum of values that set[Image/Frame]Data returns.
34 |
35 | `Image`: The main class.
36 |
37 | ### Class definition:
38 |
39 | #### Class properties
40 |
41 | ##### `.width` (read-only)
42 | The width of the loaded image.
43 |
44 | ##### `.height` (read-only)
45 | The height of the loaded image.
46 |
47 | ##### `.type` (read-only)
48 | The type of image from the TYPE_* constants table.
49 |
50 | ##### `.hasAnim` (read-only)
51 | A boolean flag for easily checking if the image is an animation.
52 |
53 | ##### `.hasAlpha` (read-only)
54 | A boolean flag for easily checking if the image has transparency in any way.
55 |
56 | ##### `.frames` (read-only)
57 | Returns the array of frames, if any, or undefined.
58 | Note that while the frames themselves are read/write, you shouldn't modify them.
59 |
60 | ##### `.frameCount` (read-only)
61 | The number of frames in the image's animation, or 0 if it's not an animation.
62 |
63 | ##### `.anim` (read-only)
64 | Direct access to the raw animation data (see below in the _Layout for internal Image data_ section).
65 |
66 | ##### `.iccp` (read/write)
67 | A Buffer containing the raw ICCP data stored in the image, or undefined if there isn't any.
68 |
69 | ##### `.exif` (read/write)
70 | A Buffer containing the raw EXIF data stored in the image, or undefined if there isn't any.
71 |
72 | ##### `.xmp` (read/write)
73 | A Buffer containing the raw XMP data stored in the image, or undefined if there isn't any.
74 |
75 | #### Image member functions
76 |
77 | ##### `async .initLib()`
78 | Calls Image.initLib(). This member function is no longer particularly useful and is kept for convenience.
79 |
80 | ##### `async .load(source)`
81 | If `source` is a string, it tries to load that as a path to a WebP image.
82 | If `source` is a buffer, it tries to load the contents of the buffer as a WebP image.
83 |
84 | ##### `.convertToAnim()`
85 | Sets the image up for being an animation.
86 |
87 | ##### `async .demux({ path = undefined, buffers = false, frame = -1, prefix = '#FNAME#', start = 0, end = 0 })`
88 | Dump the individual, unprocessed WebP frames to a directory.
89 | * `path`: The directory to dump the frames to, if desired.
90 | * `buffers`: Return the frames as an array of Buffers instead of dumping to a path.
91 | * `prefix`: What to prefix the frame names with. Default is the file name of the original image (without .webp).
92 | Format is \_\.webp.
93 | * `frame`: What frame to dump. Defaults to -1, which has it dump all available frames. Overrides `start`/`end`.
94 | * `start`: The first frame to dump. Defaults to the first frame.
95 | * `end`: The last frame to dump. Defaults to the last frame.
96 |
97 | ##### `async .replaceFrame(frameIndex, source)`
98 | Replaces a frame in the animation with another image from `source`. All other frame settings are preserved.
99 | * `frameIndex`: Which frame to replace. Frame indexes are 0-based.
100 | * `source`: If this is a string, the frame is loaded from disk. If this is a Buffer, the frame is loaded from there.
101 |
102 | ##### `async .save(path = this.path, options)`
103 | Save the image to `path`. Options are described below in the _Options for saving_ section.
104 | If `path` is `null`, this will save the image to a Buffer and return it.
105 |
106 | ##### `async .getImageData()`
107 | Get the raw RGBA pixel data for the image.
108 | Returns a Buffer in the format `[ r, g, b, a, r, g, b, a, ... ]`. Values are range 0 - 255.
109 | Use this for non-animations.
110 | On error, this returns a Buffer full of 0s.
111 |
112 | ##### `async .setImageData(buffer, { width = 0, height = 0, preset = 0, quality = 75, exact = false, lossless = 0, method = 4, advanced = undefined })`
113 | Encode `buf` as a new WebP using the provided settings and replace the image pixel data with it.
114 | This preserves EXIF/ICCP/XMP if present.
115 | Use this for non-animations.
116 | * `buffer`: A Buffer object with the raw pixels in RGBA order.
117 | Options:
118 | * `width`/`height`
119 | If either are > 0, override the existing width and/or height with this value.
120 | Use this if the pixel data in `buf` has different dimensions than the original image.
121 | * `preset`: What image preset to use, if any.
122 | Range is 0 - 5
123 | Default is 0 (DEFAULT).
124 | An enum of constants can be found under WebP.presets
125 | * `quality`: What quality to set.
126 | Range is 0 - 100.
127 | Default is 75.
128 | * `exact`: Preserve data in transparent pixels.
129 | Defaults to `false`, which means transparent pixels may be modified to help with compression.
130 | * `lossless`: Save the data as a lossy/lossless image.
131 | Range is 0 - 9.
132 | Default is 0 (lossy).
133 | Higher values will result in smaller files, but requires more processing time.
134 | * `method`: Compression method to use.
135 | Range is 0 - 6.
136 | Default is 4.
137 | Higher values will result in smaller files, but requires more processing time.
138 | * `advanced`: Access to more advanced encoding settings offered by libwebp
139 | * * `imageHint`: Hint for what type of image it is (only used for lossless encoding for now, according to libwebp spec).
140 | Range is 0 - 3.
141 | Default is 0 (DEFAULT).
142 | An enum of constants can be found under WebP.hints
143 | * * `targetSize`: Specifies the desired target size in bytes.
144 | Default is 0 (no target).
145 | Takes precedence over the `method` parameter.
146 | * * `targetPSNR`: Specifies the minimum distortion to try to achieve.
147 | Default is 0 (no target).
148 | Takes precedence over the `targetSize` parameter.
149 | * * `segments`: Maximum number of segments to use.
150 | Range is 1 - 4.
151 | Default is 4.
152 | * * `snsStrength`: Spacial Noise Shaping.
153 | Range is 0 - 100.
154 | Default is 50.
155 | * * `filterStrength`
156 | Range is 0 - 100.
157 | Default is 0 (off).
158 | * * `filterSharpness`
159 | Range is 0 - 7, with 7 being the least sharp.
160 | Default is 0 (off).
161 | * * `filterType`
162 | Range is 0 - 1.
163 | Default is 1.
164 | 0 is simple; 1 is strong.
165 | Only used if `filterStrength` > 0 or `autoFilter` > 0.
166 | * * `autoFilter`: Auto-adjust the filter's strength.
167 | Range is 0 - 1.
168 | Default is 0 (off).
169 | * * `alphaCompression`: Algorithm for encoding the alpha plane.
170 | Range is 0 - 1.
171 | Default is 1 (Lossless).
172 | 0 is off; 1 is lossless.
173 | * * `alphaFiltering`: Predictive filtering method for alpha place.
174 | Range is 0 - 2.
175 | Default is 1 (Fast).
176 | 0 is none; 1 is fast; 2 is best
177 | * * `alphaQuality`
178 | Range is 0 - 100.
179 | Default is 100.
180 | * * `pass`: Number of entropy-analysis passes.
181 | Range is 1 - 10.
182 | Default is 1.
183 | * * `showCompressed`: Export the compressed picture back.
184 | Range is 0 - 1.
185 | Default is 0 (don't).
186 | In-loop filtering is not applied.
187 | * * `preprocessing`: Preprocessing filter.
188 | Range is 0 - 2.
189 | Default is 0 (None).
190 | 0 is none; 1 is segment-smooth; 2 is pseudo-random dithering.
191 | * * `partitions`: log2(number of token partitions).
192 | Range is 0 - 3.
193 | Default is 0.
194 | Higher values result in harder progressive decoding.
195 | * * `partitionLimit`: Quality degredation allowed to fit the 512k limit on prediction modes coding.
196 | Range is 0 - 100.
197 | Default is 0.
198 | * * `emulateJpegSize`: Compression parameters are remapped to better mat the expected output size from JPEG compression.
199 | Range is 0 - 1.
200 | Default is 0 (Off).
201 | Generally, the output size will be smaller but the degredation will be lower.
202 | * * `threadLevel`: Try to use multi-threaded encoding.
203 | Default is 0 (Off).
204 | NOTE: Currently the WebAssembly is NOT compiled with support for threads, so this option does nothing.
205 | NodeJS doesn't support threads in WebAssembly without an experimental flag, and my testing with it didn't appear to use threads regardless.
206 | * * `lowMemory`: Reduce memory usage but increase CPU use.
207 | Range is 0 - 1.
208 | Default is 0 (Off).
209 | * * `nearLossless`: Near lossless encoding.
210 | Range is 0 - 100.
211 | Default is 100 (off).
212 | 0 is max loss, 100 is off.
213 | * * `useDeltaPalette`: Reserved for future lossless feature.
214 | Range is 0 - 0.
215 | Default is 0 (Off).
216 | Setting this will do nothing, as it's forced back to 0.
217 | * * `useSharpYUV`: Use sharp (and slow) RGB->YUV conversion.
218 | Range is 0 - 1.
219 | Default is 0 (Off).
220 | * * `qMin`: Minimum permissible quality factor.
221 | Range is 0 - 100.
222 | Default is 0.
223 | * * `qMax`: Maximum permissible quality factor.
224 | Range is 0 - 100.
225 | Default is 100.
226 |
227 | If `lossless` is set above 0, then setting `quality` or `method` is discouraged as they will override settings in the lossless preset.
228 | Return value can be checked against the values in `WebP.encodeResults`.
229 |
230 | ##### `async .getFrameData(frameIndex)`
231 | Get the raw RGBA pixel data for a specific frame.
232 | Use this for animations.
233 | * `frameIndex`: Which frame to get. Frame indexes are 0-based.
234 | Otherwise identical to `.getImageData()`
235 |
236 | ##### `async .setFrameData(frameIndex, buffer, { width = 0, height = 0, preset = 0, quality = 75, exact = false, lossless = 0, method = 4, advanced = undefined })`
237 | Encode `buffer` as a new WebP using the provided settings and replace an existing frame's pixel data with it.
238 | Use this for animations.
239 | * `frameIndex`: Which frame to get. Frame indexes are 0-based.
240 | Otherwise identical to `.setImageData()`.
241 |
242 | #### Static functions
243 |
244 | ##### `async Image.initLib()`
245 | Initialize the internal library used for [get/set]ImageData and [get/set]FrameData described above.
246 | There is no need to call this unless you plan to use one of those 4 functions.
247 |
248 | ##### `Image.from(webp)`
249 | Use the contents of `webp` and return a new Image using them.
250 | Mainly useful for passing Image into a Web Worker or NodeJS Worker and converting the passed object back into an Image instance.
251 | Such an approach can be used to greatly speed up saving of animations or multiple images as libwebp is *not* multi-threaded beyond saving the alpha layer of lossy images.
252 |
253 | ##### `async Image.save(path, options)`
254 | Save the `options` using `Image.getEmptyImage()`.
255 | Works the same as `.save()` otherwise.
256 | Can be used to create an animation from scratch by passing `frames` in `options`.
257 | Example: `Image.save('animation.webp', { frames: ... })` for saving to file
258 | OR
259 | Example: `Image.save(null, { frames: ... })` for saving to Buffer
260 |
261 | ##### `async Image.getEmptyImage(ext)`
262 | Returns a basic, lossy 1x1 black image with no alpha or metadata.
263 | Useful if you need to create a WebP from scratch, such as when converting from PNG.
264 | `.setImageData()` would be used to change the canvas size/contents.
265 | Set `ext` to `true` to force the image to be an extended type, if desired. This is mainly for use internally.
266 |
267 | ##### `async Image.generateFrame({ path = undefined, buffer = undefined, img = undefined, x = undefined, y = undefined, delay = undefined, blend = undefined, dispose = undefined })`
268 | Generates enough of an `anmf` structure to be placed in `.frames`.
269 | Note that, at the moment, only *static* images are supported in this function.
270 | * `path`/`buffer`/`img`
271 | Only one of these can be present.
272 | `path` will load image data from file.
273 | `buffer` will load from the buffer.
274 | `img` will use an existing Image instance.
275 | * `x`/`y`/`delay`/`blend`/`dispose`
276 | Explicitly set these properties. See the _Options for saving_ section for what these do.
277 |
278 | ### Options for saving
279 | #### These options affect both static images and animations
280 | * `exif`/`iccp`/`xmp`
281 | Save or override EXIF/ICCP/XMP chunks.
282 | Pass `true` to save the existing ones, or pass a Buffer to replace them.
283 | Note that there is no verification whatsoever that the data passed is valid.
284 |
285 | #### The options below are only used when saving an animation:
286 | * `width`/`height`: Width/height of the image.
287 | Range 0 - 16777216.
288 | The product of width*height must NOT exceed (2 ** 32) - 1.
289 | Passing 0 to either flags it for being set automatically.
290 | * `bgColor`: The background color of the animation.
291 | Format is [ r, g, b, a ].
292 | Defaults to [ 255, 255, 255, 255 ].
293 | * `loops`: Number of times the animation loops.
294 | Range is 0 - 65535, with 0 being an infinite loop.
295 | Default is 0.
296 | * `x`/`y`/`delay`/`blend`/`dispose`: Changes the default frame x/y position where a frame omits it (see below).
297 | * * `x`/`y` defaults to 0.
298 | * * `delay` defaults to 100.
299 | * * `blend` defaults to `true`.
300 | * * `dispose` defaults to `false`.
301 | * * `frames`: An array of objects defining each frame of the animation with the following properties.
302 | * * * `x`/`y`: x, y offset to place the frame within the animation.
303 | Range 0 - 16777215.
304 | Default is 0,0 (defined above).
305 | * * * `delay`: Length of this frame in miliseconds.
306 | Range 0 - 16777215.
307 | Default is 100 (defined above).
308 | According to the documentation, delays <= 10ms are WebP implementation defined, and many tools/browsers/etc assign their own minimum-allowed delay.
309 | * * * `blend`: Boolean flag for whether or not to use alpha blending when drawing the frame.
310 | Default is `true` (defined above).
311 | * * * `dispose`: Boolean flag to control frame disposal method.
312 | `true` causes the background color to be drawn under the frame.
313 | `false` draws the new frame directly.
314 | Default is `false` (defined above).
315 |
316 | ### Information about the internal library
317 |
318 | [get/set]ImageData and [get/set]FrameData are powered by Google's official libwebp library obtained from the [GitHub mirror](https://github.com/webmproject/libwebp).
319 | Commit 613be8f was the latest at the time of compilation.
320 | This library was compiled with Emscripten with the command `emcc -O3 -s WASM=1 -s MODULARIZE -s EXPORTED_RUNTIME_METHODS='[cwrap]' -s ALLOW_MEMORY_GROWTH=1 -s EXPORT_NAME=LibWebP -DHAVE_CONFIG_H -I libwebp binding.cpp libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c libwebp/sharpyuv/*.c --bind -o libwebp.js`.
321 | binding.cpp is a shim I wrote to bridge the needed parts together and can be found in the libwebp/ directory.
322 | libwebp.js, found in the root, is the Javascript interface to it.
323 |
324 | At present, the only options for encoding are setting the lossless preset, quality, method, and exact flag.
325 | If access to other options is desired (see upstream libwebp/src/webp/encode.h, struct WebPConfig for settings), leave a feature request and I'll add it.
326 | The upstream command line tool `cwebp` can be used to play with the features and see what you find useful.
327 |
328 | ### Layout for internal Image data
329 | ```javascript
330 | {
331 | path, // The path loaded.
332 | loaded, // Boolean flag for if this object has an image loaded.
333 | data: { // The loaded data.
334 | type, // The type of image from the constants table.
335 | vp8: { // The lossy format image. Only if .type is TYPE_LOSSY or TYPE_EXTENDED.
336 | raw, // The raw, compressed image data from the VP8 chunk.
337 | width, height // The width/height, extracted from the VP8 image data.
338 | },
339 | vp8l: { // The lossless format image. Only if .type is TYPE_LOSSLESS or TYPE_EXTENDED.
340 | raw, // The raw, compressed image data from the VP8L chunk.
341 | alpha, // A flag for if this image has alpha data, extracted from the VP8L image data.
342 | width, height // The width/height, extracted from the VP8L image data.
343 | },
344 | extended: { // Only if .type is TYPE_EXTENDED.
345 | raw, // The raw data for the VP8X chunk.
346 | hasICCP, // Flag for if there's an ICC profile chunk defined.
347 | hasAlpha, // Flag for if any image/frame defined has alpha data.
348 | hasEXIF, // Flag for if there's an EXIF chunk defined.
349 | hasXMP, // Flag for if there's an XMP chunk defined.
350 | hasAnim, // Flag for if this image has an animation defined.
351 | width, height // Width/height of the image.
352 | },
353 | anim: {
354 | raw, // A Buffer containing the raw data for the ANIM chunk. Mainly for internal use.
355 | bgColor, // The background color in [ r, g, b, a ] format.
356 | loops, // The loop count.
357 | frames: [ // Array of frames
358 | { // The frame object definition
359 | raw, // The raw data for the ANMF chunk. Mainly for internal use.
360 | type, // The type of image this frame is, from the constants table.
361 | x, y, // The frame's x, y position.
362 | width, height, // The frame's width and height.
363 | delay, // The duration of the frame.
364 | blend, dispose, // The frame's blend/dispose flags.
365 | // Additionally, one or more of the following.
366 | vp8, // The raw, compressed WebP data for a lossy image. If present, there will be no `vp8l`.
367 | vp8l, // The raw, compressed WebP data for a lossless image. If present, there will be no `vp8` or `alph`.
368 | alph // The raw, compressed WebP data for an alpha map. Might be present if the image is lossy.
369 | },
370 | ...
371 | ]
372 | },
373 | alph: {
374 | raw // The raw alpha map chunk. Only likely to be here if .vp8 is also defined and .type is TYPE_EXTENDED.
375 | },
376 | iccp: {
377 | raw // The raw ICCP chunk, if defined.
378 | },
379 | exif: {
380 | raw // The raw EXIF chunk, if defined.
381 | },
382 | xmp: {
383 | raw // The raw XMP chunk, if defined.
384 | }
385 | }
386 | }
387 | ```
388 | ### Breaking changes from 1.x
389 | Image.muxAnim and .muxAnim were merged into Image.save and .save respectively.
390 | * Replace `Image.muxAnim({ path, frames, width, height, bgColor, loops, delay, x, y, blend, dispose, exif, iccp, xmp })`
391 | * With `Image.save(path, undefined, { frames, width, height, bgColor, loops, delay, x, y, blend, dispose, exif, iccp, xmp })`
392 |
393 | * Replace `.muxAnim({ path, width, height, bgColor, loops, delay, x, y, blend, dispose, exif, iccp, xmp })`
394 | * With `.save(path, { width, height, bgColor, loops, delay, x, y, blend, dispose, exif, iccp, xmp })`
395 |
396 | `.anim.backgroundColor` renamed to `.anim.bgColor` for brevity and consisteny.
397 | `.anim.loopCount` renamed to `.anim.loop` for consistency.
398 | `.anim.frameCount` and `.frameCount` were removed. Should use `.anim.frames.length` and `.frames.length` respectively instead.
399 | `.demuxAnim()` was renamed to `.demux()`
400 |
401 | ## Breaking changes from 2.0.0 to 2.0.1
402 | Image.generateFrame()'s `duration` input renamed to `delay`
403 |
404 | ## Breaking changes from 2.x to 3.0.0
405 | File and buffer codepaths have been merged.
406 | * Replace `.loadBuffer(buffer)`
407 | * With `.load(buffer)`
408 | * Replace `Image.loadBuffer(buffer)`
409 | * With `Image.load(buffer)`
410 |
411 | * Replace `.saveBuffer(settings)`
412 | * With `.save(null, settings)`
413 | * Replace `Image.saveBuffer(settings)`
414 | * With `Image.save(null, settings)`
415 | * Note that it's specifically `null` here. This is because the default behavior of .save() is still saving to the path it was loaded from.
416 |
417 | * Replace `.demuxToBuffers({ setting, setting, ... })`
418 | * With `.demux({ buffers: true, setting, setting, ... })`
419 | * Replace `.demux(path, settings)`
420 | * With `.demux({ path, setting, setting, ... })`
421 |
422 | * Replace `.replaceFrameBuffer(frame, buffer)`
423 | * With `.replaceFrame(frame, buffer)`
424 |
--------------------------------------------------------------------------------
/webp.js:
--------------------------------------------------------------------------------
1 | /*
2 | node-webpmux - NodeJS module for interacting with WebP images
3 | Copyright (C) 2023 ApeironTsuka
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU Lesser General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU Lesser General Public License for more details.
14 | */
15 |
16 |
17 | // For more information on the WebP format, see https://developers.google.com/speed/webp/docs/riff_container
18 | const { WebPReader, WebPWriter } = require('./parser.js');
19 | const IO = require('./io.js');
20 | const emptyImageBuffer = Buffer.from([
21 | 0x52, 0x49, 0x46, 0x46, 0x24, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50, 0x38, 0x20,
22 | 0x18, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00,
23 | 0x34, 0x25, 0xa4, 0x00, 0x03, 0x70, 0x00, 0xfe, 0xfb, 0xfd, 0x50, 0x00
24 | ]);
25 | const constants = {
26 | TYPE_LOSSY: 0,
27 | TYPE_LOSSLESS: 1,
28 | TYPE_EXTENDED: 2
29 | };
30 | const encodeResults = {
31 | // These are errors from binding.cpp
32 | LIB_NOT_READY: -1, // .initEnc() was not called. This happens internally during .encodeImage() and thus should never happen.
33 | LIB_INVALID_CONFIG: -2, // invalid options passed in via set[Image/Frame]Data. This should never happen.
34 | SUCCESS: 0,
35 | // These errors are from native code and can be found in upstream libwebp/src/encode.h, WebPEncodingError enum
36 | VP8_ENC_ERROR_OUT_OF_MEMORY: 1, // memory error allocating objects
37 | VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY: 2, // memory error while flushing bits
38 | VP8_ENC_ERROR_NULL_PARAMETER: 3, // a pointer parameter is NULL
39 | VP8_ENC_ERROR_INVALID_CONFIGURATION: 4, // configuration is invalid
40 | VP8_ENC_ERROR_BAD_DIMENSION: 5, // picture has invalid width/height
41 | VP8_ENC_ERROR_PARTITION0_OVERFLOW: 6, // partition is bigger than 512k
42 | VP8_ENC_ERROR_PARTITION_OVERFLOW: 7, // partition is bigger than 16M
43 | VP8_ENC_ERROR_BAD_WRITE: 8, // error while flushing bytes
44 | VP8_ENC_ERROR_FILE_TOO_BIG: 9, // file is bigger than 4G
45 | VP8_ENC_ERROR_USER_ABORT: 10, // abort request by user
46 | VP8_ENC_ERROR_LAST: 11 // list terminator. always last.
47 | };
48 | const imageHints = {
49 | DEFAULT: 0,
50 | PICTURE: 1, // digital picture, such as a portrait. Indoors shot
51 | PHOTO: 2, // outdoor photograph with natural lighting
52 | GRAPH: 3 // discrete tone image (graph, map-tile, etc)
53 | };
54 | const imagePresets = {
55 | DEFAULT: 0,
56 | PICTURE: 1, // digital picture, such as a portrait. Indoors shot
57 | PHOTO: 2, // outdoor photograph with natural lighting
58 | DRAWING: 3, // hand or line drawing, with high-contrast details
59 | ICON: 4, // small-sized, colorful images
60 | TEXT: 5 // text-like
61 | };
62 |
63 | class Image {
64 | constructor() { this.data = null; this.loaded = false; this.path = ''; }
65 | async initLib() { return Image.initLib(); }
66 | clear() { this.data = null; this.path = ''; this.loaded = false; }
67 | // Convenience getters/setters
68 | get width() { let d = this.data; return !this.loaded ? undefined : d.extended ? d.extended.width : d.vp8l ? d.vp8l.width : d.vp8 ? d.vp8.width : undefined; }
69 | get height() { let d = this.data; return !this.loaded ? undefined : d.extended ? d.extended.height : d.vp8l ? d.vp8l.height : d.vp8 ? d.vp8.height : undefined; }
70 | get type() { return this.loaded ? this.data.type : undefined; }
71 | get hasAnim() { return this.loaded ? this.data.extended ? this.data.extended.hasAnim : false : false; }
72 | get hasAlpha() { return this.loaded ? this.data.extended ? this.data.extended.hasAlpha : this.data.vp8 ? this.data.vp8.alpha : this.data.vp8l ? this.data.vp8l.alpha : false : false; }
73 | get anim() { return this.hasAnim ? this.data.anim : undefined; }
74 | get frames() { return this.anim ? this.anim.frames : undefined; }
75 | get iccp() { return this.data.extended ? this.data.extended.hasICCP ? this.data.iccp.raw : undefined : undefined; }
76 | set iccp(raw) {
77 | if (!this.data.extended) { this._convertToExtended(); }
78 | if (raw === undefined) { this.data.extended.hasICCP = false; delete this.data.iccp; }
79 | else { this.data.iccp = { raw }; this.data.extended.hasICCP = true; }
80 | }
81 | get exif() { return this.data.extended ? this.data.extended.hasEXIF ? this.data.exif.raw : undefined : undefined; }
82 | set exif(raw) {
83 | if (!this.data.extended) { this._convertToExtended(); }
84 | if (raw === undefined) { this.data.extended.hasEXIF = false; delete this.data.exif; }
85 | else { this.data.exif = { raw }; this.data.extended.hasEXIF = true; }
86 | }
87 | get xmp() { return this.data.extended ? this.data.extended.hasXMP ? this.data.xmp.raw : undefined : undefined; }
88 | set xmp(raw) {
89 | if (!this.data.extended) { this._convertToExtended(); }
90 | if (raw === undefined) { this.data.extended.hasXMP = false; delete this.data.xmp; }
91 | else { this.data.xmp = { raw }; this.data.extended.hasXMP = true; }
92 | }
93 | // Private member functions
94 | _convertToExtended() {
95 | if (!this.loaded) { throw new Error('No image loaded'); }
96 | this.data.type = constants.TYPE_EXTENDED;
97 | this.data.extended = {
98 | hasICCP: false,
99 | hasAlpha: false,
100 | hasEXIF: false,
101 | hasXMP: false,
102 | width: this.data.vp8 ? this.data.vp8.width : this.data.vp8l ? this.data.vp8l.width : 1,
103 | height: this.data.vp8 ? this.data.vp8.height : this.data.vp8l ? this.data.vp8l.height : 1
104 | };
105 | }
106 | async _demuxFrame(d, frame) {
107 | let { hasICCP, hasEXIF, hasXMP } = this.data.extended ? this.data.extended : { hasICCP: false, hasEXIF: false, hasXMP: false }, hasAlpha = ((frame.vp8) && (frame.vp8.alpha)), writer = new WebPWriter();
108 | if (typeof d === 'string') { writer.writeFile(d); }
109 | else { writer.writeBuffer(); }
110 | writer.writeFileHeader();
111 | if ((hasICCP) || (hasEXIF) || (hasXMP) || (hasAlpha)) {
112 | writer.writeChunk_VP8X({
113 | hasICCP,
114 | hasEXIF,
115 | hasXMP,
116 | hasAlpha: ((frame.vp8l) && (frame.vp8l.alpha)) || hasAlpha,
117 | width: frame.width,
118 | height: frame.height
119 | });
120 | }
121 | if (frame.vp8l) { writer.writeChunk_VP8L(frame.vp8l); }
122 | else if (frame.vp8) {
123 | if (frame.vp8.alpha) { writer.writeChunk_ALPH(frame.alph); }
124 | writer.writeChunk_VP8(frame.vp8);
125 | } else { throw new Error('Frame has no VP8/VP8L?'); }
126 | if ((hasICCP) || (hasEXIF) || (hasXMP) || (hasAlpha)) {
127 | if (this.data.extended.hasICCP) { writer.writeChunk_ICCP(this.data.iccp); }
128 | if (this.data.extended.hasEXIF) { writer.writeChunk_EXIF(this.data.exif); }
129 | if (this.data.extended.hasXMP) { writer.writeChunk_XMP(this.data.xmp); }
130 | }
131 | return writer.commit();
132 | }
133 | async _save(writer, { width = undefined, height = undefined, frames = undefined, bgColor = [ 255, 255, 255, 255 ], loops = 0, delay = 100, x = 0, y = 0, blend = true, dispose = false, exif = false, iccp = false, xmp = false } = {}) {
134 | let _width = width !== undefined ? width : this.width - 1, _height = height !== undefined ? height : this.height - 1, isAnim = this.hasAnim || frames !== undefined;
135 | if ((_width < 0) || (_width > (1 << 24))) { throw new Error('Width out of range'); }
136 | else if ((_height < 0) || (_height > (1 << 24))) { throw new Error('Height out of range'); }
137 | else if ((_height * _width) > (Math.pow(2, 32) - 1)) { throw new Error(`Width * height too large (${_width}, ${_height})`); }
138 | if (isAnim) {
139 | if ((loops < 0) || (loops >= (1 << 24))) { throw new Error('Loops out of range'); }
140 | else if ((delay < 0) || (delay >= (1 << 24))) { throw new Error('Delay out of range'); }
141 | else if ((x < 0) || (x >= (1 << 24))) { throw new Error('X out of range'); }
142 | else if ((y < 0) || (y >= (1 << 24))) { throw new Error('Y out of range'); }
143 | } else { if ((_width == 0) || (_height == 0)) { throw new Error('Width/height cannot be 0'); } }
144 | writer.writeFileHeader();
145 | switch (this.type) {
146 | case constants.TYPE_LOSSY: writer.writeChunk_VP8(this.data.vp8); break;
147 | case constants.TYPE_LOSSLESS: writer.writeChunk_VP8L(this.data.vp8l); break;
148 | case constants.TYPE_EXTENDED:
149 | {
150 | let hasICCP = iccp === true ? !!this.iccp : iccp,
151 | hasEXIF = exif === true ? !!this.exif : exif,
152 | hasXMP = xmp === true ? !!this.xmp : xmp;
153 | writer.writeChunk_VP8X({
154 | hasICCP, hasEXIF, hasXMP,
155 | hasAlpha: ((this.data.alph) || ((this.data.vp8l) && (this.data.vp8l.alpha))),
156 | hasAnim: isAnim,
157 | width: _width,
158 | height: _height
159 | });
160 | if (hasICCP) { writer.writeChunk_ICCP(iccp !== true ? iccp : this.data.iccp); }
161 | if (isAnim) {
162 | let _frames = frames || this.frames;
163 | writer.writeChunk_ANIM({ bgColor, loops });
164 | for (let i = 0, l = _frames.length; i < l; i++) {
165 | let fr = _frames[i],
166 | _delay = fr.delay == undefined ? delay : fr.delay,
167 | _x = fr.x == undefined ? x :fr.x,
168 | _y = fr.y == undefined ? y : fr.y,
169 | _blend = fr.blend == undefined ? blend : fr.blend,
170 | _dispose = fr.dispose == undefined ? dispose : fr.dispose, img;
171 | if ((_delay < 0) || (_delay >= (1 << 24))) { throw new Error(`Delay out of range on frame ${i}`); }
172 | else if ((_x < 0) || (_x >= (1 << 24))) { throw new Error(`X out of range on frame ${i}`); }
173 | else if ((_y < 0) || (_y >= (1 << 24))) { throw new Error(`Y out of range on frame ${i}`); }
174 | if (fr.path) { img = new Image(); await img.load(fr.path); img = img.data; }
175 | else if (fr.buffer) { img = new Image(); await img.load(fr.buffer); img = img.data; }
176 | else if (fr.img) { img = fr.img.data; }
177 | else { img = fr; }
178 | writer.writeChunk_ANMF({
179 | x: _x,
180 | y: _y,
181 | delay: _delay,
182 | blend: _blend,
183 | dispose: _dispose,
184 | img
185 | });
186 | }
187 | if ((_width == 0) || (_height == 0)) { writer.updateChunk_VP8X_size(_width == 0 ? writer.width : _width, _height == 0 ? writer.height : _height); }
188 | } else {
189 | if (this.data.vp8) {
190 | if (this.data.alph) { writer.writeChunk_ALPH(this.data.alph); }
191 | writer.writeChunk_VP8(this.data.vp8);
192 | } else if (this.data.vp8l) { writer.writeChunk_VP8L(this.data.vp8l); }
193 | }
194 | if (hasEXIF) { writer.writeChunk_EXIF(exif !== true ? exif : this.data.exif); }
195 | if (hasXMP) { writer.writeChunk_XMP(xmp !== true ? xmp : this.data.xmp); }
196 | }
197 | break;
198 | default: throw new Error('Unknown image type');
199 | }
200 | return writer.commit();
201 | }
202 | // Public member functions
203 | async load(d) {
204 | let reader = new WebPReader();
205 | if (typeof d === 'string') { if (!IO.avail) { await IO.err(); } reader.readFile(d); this.path = d; }
206 | else { reader.readBuffer(d); }
207 | this.data = await reader.read();
208 | this.loaded = true;
209 | }
210 | convertToAnim() {
211 | if (!this.data.extended) { this._convertToExtended(); }
212 | if (this.hasAnim) { return; }
213 | if (this.data.vp8) { delete this.data.vp8; }
214 | if (this.data.vp8l) { delete this.data.vp8l; }
215 | if (this.data.alph) { delete this.data.alph; }
216 | this.data.extended.hasAnim = true;
217 | this.data.anim = {
218 | bgColor: [ 255, 255, 255, 255],
219 | loops: 0,
220 | frames: []
221 | };
222 | }
223 | async demux({ path = undefined, buffers = false, frame = -1, prefix = '#FNAME#', start = 0, end = 0 } = {}) {
224 | if (!this.hasAnim) { throw new Error("This image isn't an animation"); }
225 | let _end = end == 0 ? this.frames.length : end, bufs = [];
226 | if (start < 0) { start = 0; }
227 | if (_end >= this.frames.length) { _end = this.frames.length - 1; }
228 | if (start > _end) { let n = start; start = _end; _end = n; }
229 | if (frame != -1) { start = _end = frame; }
230 | for (let i = start; i <= _end; i++) {
231 | let t = await this._demuxFrame(path ? (`${path}/${prefix}_${i}.webp`).replace(/#FNAME#/g, IO.basename(this.path, '.webp')) : undefined, this.anim.frames[i]);
232 | if (buffers) { bufs.push(t); }
233 | }
234 | if (buffers) { return bufs; }
235 | }
236 | async replaceFrame(frameIndex, d) {
237 | if (!this.hasAnim) { throw new Error("WebP isn't animated"); }
238 | if (typeof frameIndex !== 'number') { throw new Error('Frame index expects a number'); }
239 | if ((frameIndex < 0) || (frameIndex >= this.frames.length)) { throw new Error(`Frame index out of bounds (0 <= index < ${this.frames.length})`); }
240 | let r = new Image(), fr = this.frames[frameIndex];
241 | await r.load(d);
242 | switch (r.type) {
243 | case constants.TYPE_LOSSY:
244 | case constants.TYPE_LOSSLESS:
245 | break;
246 | case constants.TYPE_EXTENDED:
247 | if (r.hasAnim) { throw new Error('Merging animations not currently supported'); }
248 | break;
249 | default: throw new Error('Unknown WebP type');
250 | }
251 | switch (fr.type) {
252 | case constants.TYPE_LOSSY:
253 | if (fr.vp8.alpha) { delete fr.alph; }
254 | delete fr.vp8;
255 | break;
256 | case constants.TYPE_LOSSLESS:
257 | delete fr.vp8l;
258 | break;
259 | default: throw new Error('Unknown frame type');
260 | }
261 | switch (r.type) {
262 | case constants.TYPE_LOSSY:
263 | fr.vp8 = r.data.vp8;
264 | fr.type = constants.TYPE_LOSSY;
265 | break;
266 | case constants.TYPE_LOSSLESS:
267 | fr.vp8l = r.data.vp8l;
268 | fr.type = constants.TYPE_LOSSLESS;
269 | break;
270 | case constants.TYPE_EXTENDED:
271 | if (r.data.vp8) {
272 | fr.vp8 = r.data.vp8;
273 | if (r.data.vp8.alpha) { fr.alph = r.data.alph; }
274 | fr.type = constants.TYPE_LOSSY;
275 | } else if (r.data.vp8l) { fr.vp8l = r.data.vp8l; fr.type = constants.TYPE_LOSSLESS; }
276 | break;
277 | }
278 | fr.width = r.width;
279 | fr.height = r.height;
280 | }
281 | async save(path = this.path, { width = this.width, height = this.height, frames = this.frames, bgColor = this.hasAnim ? this.anim.bgColor : [ 255, 255, 255, 255 ], loops = this.hasAnim ? this.anim.loops : 0, delay = 100, x = 0, y = 0, blend = true, dispose = false, exif = !!this.exif, iccp = !!this.iccp, xmp = !!this.xmp } = {}) {
282 | let writer = new WebPWriter();
283 | if (path !== null) { if (!IO.avail) { await IO.err(); } writer.writeFile(path); }
284 | else { writer.writeBuffer(); }
285 | return this._save(writer, { width, height, frames, bgColor, loops, delay, x, y, blend, dispose, exif, iccp, xmp });
286 | }
287 | async getImageData() {
288 | if (!Image.libwebp) { throw new Error('Must call Image.initLib() before using getImageData'); }
289 | if (this.hasAnim) { throw new Error('Calling getImageData on animations is not supported'); }
290 | let buf = await this.save(null);
291 | return Image.libwebp.decodeImage(buf, this.width, this.height);
292 | }
293 | async setImageData(buf, { width = 0, height = 0, preset = undefined, quality = undefined, exact = undefined, lossless = undefined, method = undefined, advanced = undefined } = {}) {
294 | if (!Image.libwebp) { throw new Error('Must call Image.initLib() before using setImageData'); }
295 | if (this.hasAnim) { throw new Error('Calling setImageData on animations is not supported'); }
296 | if ((quality !== undefined) && ((quality < 0) || (quality > 100))) { throw new Error('Quality out of range'); }
297 | if ((lossless !== undefined) && ((lossless < 0) || (lossless > 9))) { throw new Error('Lossless preset out of range'); }
298 | if ((method !== undefined) && ((method < 0) || (method > 6))) { throw new Error('Method out of range'); }
299 | let ret = Image.libwebp.encodeImage(buf, width > 0 ? width : this.width, height > 0 ? height : this.height, { preset, quality, exact, lossless, method, advanced }), img = new Image(), keepEx = false, ex;
300 | if (ret.res !== encodeResults.SUCCESS) { return ret.res; }
301 | await img.load(Buffer.from(ret.buf));
302 | switch (this.type) {
303 | case constants.TYPE_LOSSY: delete this.data.vp8; break;
304 | case constants.TYPE_LOSSLESS: delete this.data.vp8l; break;
305 | case constants.TYPE_EXTENDED:
306 | ex = this.data.extended;
307 | delete this.data.extended;
308 | if ((ex.hasICCP) || (ex.hasEXIF) || (ex.hasXMP)) { keepEx = true; }
309 | if (this.data.vp8) { delete this.data.vp8; }
310 | if (this.data.vp8l) { delete this.data.vp8l; }
311 | if (this.data.alph) { delete this.data.alph; }
312 | break;
313 | }
314 | switch (img.type) {
315 | case constants.TYPE_LOSSY:
316 | if (keepEx) { this.data.type = constants.TYPE_EXTENDED; ex.hasAlpha = false; ex.width = img.width; ex.height = img.height; this.data.extended = ex; }
317 | else { this.data.type = constants.TYPE_LOSSY; }
318 | this.data.vp8 = img.data.vp8;
319 | break;
320 | case constants.TYPE_LOSSLESS:
321 | if (keepEx) { this.data.type = constants.TYPE_EXTENDED; ex.hasAlpha = img.data.vp8l.alpha; ex.width = img.width; ex.height = img.height; this.data.extended = ex; }
322 | else { this.data.type = constants.TYPE_LOSSLESS; }
323 | this.data.vp8l = img.data.vp8l;
324 | break;
325 | case constants.TYPE_EXTENDED:
326 | this.data.type = constants.TYPE_EXTENDED;
327 | if (keepEx) { ex.hasAlpha = img.data.alph || ((img.data.vp8l) && (img.data.vp8l.alpha)); ex.width = img.width; ex.height = img.height; this.data.extended = ex; }
328 | else { this.data.extended = img.data.extended; }
329 | if (img.data.vp8) { this.data.vp8 = img.data.vp8; }
330 | if (img.data.vp8l) { this.data.vp8l = img.data.vp8l; }
331 | if (img.data.alph) { this.data.alph = img.data.alph; }
332 | break;
333 | }
334 | return encodeResults.SUCCESS;
335 | }
336 | async getFrameData(frameIndex) {
337 | if (!Image.libwebp) { throw new Error('Must call Image.initLib() before using getFrameData'); }
338 | if (!this.hasAnim) { throw new Error('Calling getFrameData on non-animations is not supported'); }
339 | if (typeof frameIndex !== 'number') { throw new Error('Frame index expects a number'); }
340 | if ((frameIndex < 0) || (frameIndex >= this.frames.length)) { throw new Error('Frame index out of range'); }
341 | let fr = this.frames[frameIndex], buf = await this._demuxFrame(null, fr);
342 | return Image.libwebp.decodeImage(buf, fr.width, fr.height);
343 | }
344 | async setFrameData(frameIndex, buf, { width = 0, height = 0, preset = undefined, quality = undefined, exact = undefined, lossless = undefined, method = undefined, advanced = undefined } = {}) {
345 | if (!Image.libwebp) { throw new Error('Must call Image.initLib() before using setFrameData'); }
346 | if (!this.hasAnim) { throw new Error('Calling setFrameData on non-animations is not supported'); }
347 | if (typeof frameIndex !== 'number') { throw new Error('Frame index expects a number'); }
348 | if ((frameIndex < 0) || (frameIndex >= this.frames.length)) { throw new Error('Frame index out of range'); }
349 | if ((quality !== undefined) && ((quality < 0) || (quality > 100))) { throw new Error('Quality out of range'); }
350 | if ((lossless !== undefined) && ((lossless < 0) || (lossless > 9))) { throw new Error('Lossless preset out of range'); }
351 | if ((method !== undefined) && ((method < 0) || (method > 6))) { throw new Error('Method out of range'); }
352 | let fr = this.frames[frameIndex], ret = Image.libwebp.encodeImage(buf, width > 0 ? width : fr.width, height > 0 ? height : fr.height, { preset, quality, exact, lossless, method, advanced }), img = new Image();
353 | if (ret.res !== encodeResults.SUCCESS) { return ret.res; }
354 | await img.load(Buffer.from(ret.buf));
355 | switch (fr.type) {
356 | case constants.TYPE_LOSSY: delete fr.vp8; if (fr.alph) { delete fr.alph; } break;
357 | case constants.TYPE_LOSSLESS: delete fr.vp8l; break;
358 | }
359 | fr.width = img.width;
360 | fr.height = img.height;
361 | switch (img.type) {
362 | case constants.TYPE_LOSSY: fr.type = img.type; fr.vp8 = img.data.vp8; break;
363 | case constants.TYPE_LOSSLESS: fr.type = img.type; fr.vp8l = img.data.vp8l; break;
364 | case constants.TYPE_EXTENDED:
365 | if (img.data.vp8) {
366 | fr.type = constants.TYPE_LOSSY;
367 | fr.vp8 = img.data.vp8;
368 | if (img.data.vp8.alpha) { fr.alph = img.data.alph; }
369 | } else if (img.data.vp8l) {
370 | fr.type = constants.TYPE_LOSSLESS;
371 | fr.vp8l = img.data.vp8l;
372 | }
373 | break;
374 | }
375 | return encodeResults.SUCCESS;
376 | }
377 | // Public static functions
378 | static async initLib() {
379 | if (!Image.libwebp) {
380 | const libWebP = require('./libwebp.js');
381 | Image.libwebp = new libWebP();
382 | await Image.libwebp.init();
383 | }
384 | }
385 | static async save(d, opts) {
386 | if ((opts.frames) && ((opts.width === undefined) || (opts.height === undefined))) { throw new Error('Must provide both width and height when passing frames'); }
387 | return (await Image.getEmptyImage(!!opts.frames)).save(d, opts);
388 | }
389 | static async getEmptyImage(ext) {
390 | let img = new Image();
391 | await img.load(emptyImageBuffer);
392 | if (ext) { img.exif = undefined; }
393 | return img;
394 | }
395 | static async generateFrame({ path = undefined, buffer = undefined, img = undefined, x = undefined, y = undefined, delay = undefined, blend = undefined, dispose = undefined } = {}) {
396 | let _img = img;
397 | if (((!path) && (!buffer) && (!img)) ||
398 | ((path) && (buffer) && (img))) { throw new Error('Must provide either `path`, `buffer`, or `img`'); }
399 | if (!img) {
400 | _img = new Image();
401 | if (path) { await _img.load(path); }
402 | else { await _img.load(buffer); }
403 | }
404 | if (_img.hasAnim) { throw new Error('Merging animations is not currently supported'); }
405 | return {
406 | img: _img,
407 | x,
408 | y,
409 | delay,
410 | blend,
411 | dispose
412 | };
413 | }
414 | static from(webp) {
415 | let img = new Image();
416 | img.data = webp.data;
417 | img.loaded = webp.loaded;
418 | img.path = webp.path;
419 | return img;
420 | }
421 | }
422 | module.exports = {
423 | TYPE_LOSSY: constants.TYPE_LOSSY,
424 | TYPE_LOSSLESS: constants.TYPE_LOSSLESS,
425 | TYPE_EXTENDED: constants.TYPE_EXTENDED,
426 | encodeResults,
427 | hints: imageHints,
428 | presets: imagePresets,
429 | Image
430 | };
431 |
432 |
--------------------------------------------------------------------------------
/libwebp/libwebp.js:
--------------------------------------------------------------------------------
1 |
2 | var LibWebP = (() => {
3 | var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
4 | if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
5 | return (
6 | function(LibWebP) {
7 | LibWebP = LibWebP || {};
8 |
9 | var Module=typeof LibWebP!="undefined"?LibWebP:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}var fs;var nodePath;var requireNodeFS;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}requireNodeFS=(()=>{if(!nodePath){fs=require("fs");nodePath=require("path")}});read_=function shell_read(filename,binary){requireNodeFS();filename=nodePath["normalize"](filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=(filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret});readAsync=((filename,onload,onerror)=>{requireNodeFS();filename=nodePath["normalize"](filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})});if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=((status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)});Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=(url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText});if(ENVIRONMENT_IS_WORKER){readBinary=(url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)})}readAsync=((url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=(()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()});xhr.onerror=onerror;xhr.send(null)})}setWindowTitle=(title=>document.title=title)}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=value=>{tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}function getCFunc(ident){var func=Module["_"+ident];return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":function(str){var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":function(arr){var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string")return UTF8ToString(ret);if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){{if(Module["onAbort"]){Module["onAbort"](what)}}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="libwebp.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["r"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["z"];addOnInit(Module["asm"]["s"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func=="number"){if(callback.arg===undefined){getWasmTableEntry(func)()}else{getWasmTableEntry(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var wasmTableMirror=[];function getWasmTableEntry(funcPtr){var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func}function ___assert_fail(condition,filename,line,func){abort("Assertion failed: "+UTF8ToString(condition)+", at: "+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=(handle=>handle);return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=(handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle});detachFinalizer=(handle=>finalizationRegistry.unregister(handle));return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}return getWasmTableEntry(ptr).apply(null,args)}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function heap32VectorToArray(count,firstElement){var array=[];for(var i=0;i>2)+i])}return array}function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=(()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)});whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=(value=>value<>>bitshift)}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;ilengthBytesUTF8(value))}else{getLength=(()=>value.length)}var length=getLength();var ptr=_malloc(4+length+1);HEAPU32[ptr>>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;iHEAPU16);shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=(()=>HEAPU32);shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function _abort(){abort("")}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_max(){return 2147483648}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=_emscripten_get_heap_max();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}function _setTempRet0(val){setTempRet0(val)}embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");InternalError=Module["InternalError"]=extendError(Error,"InternalError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var asmLibraryArg={"a":___assert_fail,"k":__embind_register_bigint,"i":__embind_register_bool,"q":__embind_register_class,"p":__embind_register_class_constructor,"b":__embind_register_class_function,"o":__embind_register_emval,"h":__embind_register_float,"d":__embind_register_integer,"c":__embind_register_memory_view,"g":__embind_register_std_string,"e":__embind_register_std_wstring,"j":__embind_register_void,"l":_abort,"n":_emscripten_memcpy_big,"m":_emscripten_resize_heap,"f":_setTempRet0};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["s"]).apply(null,arguments)};var _decodeRGBA=Module["_decodeRGBA"]=function(){return(_decodeRGBA=Module["_decodeRGBA"]=Module["asm"]["t"]).apply(null,arguments)};var _decodeFree=Module["_decodeFree"]=function(){return(_decodeFree=Module["_decodeFree"]=Module["asm"]["u"]).apply(null,arguments)};var _allocBuffer=Module["_allocBuffer"]=function(){return(_allocBuffer=Module["_allocBuffer"]=Module["asm"]["v"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["w"]).apply(null,arguments)};var _destroyBuffer=Module["_destroyBuffer"]=function(){return(_destroyBuffer=Module["_destroyBuffer"]=Module["asm"]["x"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["y"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["A"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["B"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["C"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["D"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["E"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["F"]).apply(null,arguments)};var dynCall_jii=Module["dynCall_jii"]=function(){return(dynCall_jii=Module["dynCall_jii"]=Module["asm"]["G"]).apply(null,arguments)};var dynCall_jiiiii=Module["dynCall_jiiiii"]=function(){return(dynCall_jiiiii=Module["dynCall_jiiiii"]=Module["asm"]["H"]).apply(null,arguments)};Module["cwrap"]=cwrap;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();
10 |
11 |
12 | return LibWebP.ready
13 | }
14 | );
15 | })();
16 | if (typeof exports === 'object' && typeof module === 'object')
17 | module.exports = LibWebP;
18 | else if (typeof define === 'function' && define['amd'])
19 | define([], function() { return LibWebP; });
20 | else if (typeof exports === 'object')
21 | exports["LibWebP"] = LibWebP;
22 |
--------------------------------------------------------------------------------