├── 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 | --------------------------------------------------------------------------------