diff --git a/cli/.npmrc b/cli/.npmrc new file mode 100644 index 00000000..e69de29b diff --git a/cli/src/codecs.js b/cli/src/codecs.js index 6b33c698..1ad33bdc 100644 --- a/cli/src/codecs.js +++ b/cli/src/codecs.js @@ -1,5 +1,5 @@ import { promises as fsp } from "fs"; -import { instantiateEmscriptenWasm } from "./emscripten-utils.js"; +import { instantiateEmscriptenWasm, pathify } from "./emscripten-utils.js"; // MozJPEG import mozEnc from "../../codecs/mozjpeg/enc/mozjpeg_enc.js"; @@ -19,6 +19,14 @@ import avifEncWasm from "asset-url:../../codecs/avif/enc/avif_enc.wasm"; import avifDec from "../../codecs/avif/dec/avif_dec.js"; import avifDecWasm from "asset-url:../../codecs/avif/dec/avif_dec.wasm"; +// PNG +import pngEncDecInit, { + encode as pngEncode, + decode as pngDecode +} from "../../codecs/png/pkg/squoosh_png.js"; +import pngEncDecWasm from "asset-url:../../codecs/png/pkg/squoosh_png_bg.wasm"; +const pngEncDecPromise = pngEncDecInit(fsp.readFile(pathify(pngEncDecWasm))); + // Our decoders currently rely on a `ImageData` global. import ImageData from "./image_data.js"; globalThis.ImageData = ImageData; @@ -114,5 +122,19 @@ export default { min: 0, max: 62 } + }, + png: { + name: "PNG", + extension: "png", + detectors: [/^\x89PNG\x0D\x0A\x1A\x0A/], + dec: async () => { + await pngEncDecPromise; + return { decode: (...args) => pngDecode(...args) }; + }, + enc: async () => { + await pngEncDecPromise; + return { encode: (...args) => new Uint8Array(pngEncode(...args)) }; + }, + defaultEncoderOptions: {} } }; diff --git a/cli/src/emscripten-utils.js b/cli/src/emscripten-utils.js index e02d0f2a..fc363b26 100644 --- a/cli/src/emscripten-utils.js +++ b/cli/src/emscripten-utils.js @@ -1,10 +1,13 @@ -export function instantiateEmscriptenWasm(factory, path) { +export function pathify(path) { if (path.startsWith("file://")) { path = path.slice("file://".length); } + return path; +} +export function instantiateEmscriptenWasm(factory, path) { return factory({ locateFile() { - return path; + return pathify(path); } }); } diff --git a/cli/src/image_data.js b/cli/src/image_data.js index 7d7736be..90517ea3 100644 --- a/cli/src/image_data.js +++ b/cli/src/image_data.js @@ -1,6 +1,11 @@ export default class ImageData { constructor(data, width, height) { - this.data = data; + // Need to manually copy the memory as wasm-bindgen does not by default + // and by the time we get control in JS land, the memory has already + // been corrupted. + // FIXME: This is bad because it’s overhead that we should only need + // to pay for Rust, not for C++. + this.data = new Uint8ClampedArray(data); this.width = width; this.height = height; } diff --git a/codecs/png/.gitignore b/codecs/png/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/codecs/png/.gitignore @@ -0,0 +1 @@ +/target diff --git a/codecs/png/Cargo.lock b/codecs/png/Cargo.lock new file mode 100644 index 00000000..62d5b7e1 --- /dev/null +++ b/codecs/png/Cargo.lock @@ -0,0 +1,204 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "js-sys" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52732a3d3ad72c58ad2dc70624f9c17b46ecd0943b9a4f1ee37c4c18c5d983e2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "png" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "squoosh-png" +version = "0.1.0" +dependencies = [ + "log", + "png", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "syn" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasm-bindgen" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ed2fb8c84bfad20ea66b26a3743f3e7ba8735a69fe7d95118c33ec8fc1244d" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb071268b031a64d92fc6cf691715ca5a40950694d8f683c5bb43db7c730929e" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf592c807080719d1ff2f245a687cbadb3ed28b2077ed7084b47aba8b691f2c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b6c0220ded549d63860c78c38f3bcc558d1ca3f4efa74942c536ddbbb55e87" + +[[package]] +name = "web-sys" +version = "0.3.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be2398f326b7ba09815d0b403095f34dd708579220d099caae89be0b32137b2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/codecs/png/Cargo.toml b/codecs/png/Cargo.toml new file mode 100644 index 00000000..5c784c82 --- /dev/null +++ b/codecs/png/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "squoosh-png" +version = "0.1.0" +authors = ["Surma "] +edition = "2018" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +png = "0.16.7" +# wasm-bindgen 0.2.66+ emits Wasm using exported mutable globals, +# which is a stage 4 feature and not well supported. +# https://github.com/rustwasm/wasm-pack/issues/886 +wasm-bindgen = "=0.2.65" +# Need to pin web-sys to 0.3.42, because newer versions depend +# on newer versions of wasm-bindgen. +web-sys = { version = "=0.3.42", features = ["ImageData"] } +log = { version = "0.4", features = ["release_max_level_off"] } + +[profile.release] +lto = true +opt-level = "s" diff --git a/codecs/png/package-lock.json b/codecs/png/package-lock.json new file mode 100644 index 00000000..a9713147 --- /dev/null +++ b/codecs/png/package-lock.json @@ -0,0 +1,4 @@ +{ + "name": "oxipng", + "lockfileVersion": 1 +} diff --git a/codecs/png/package.json b/codecs/png/package.json new file mode 100644 index 00000000..adce0fe0 --- /dev/null +++ b/codecs/png/package.json @@ -0,0 +1,6 @@ +{ + "name": "oxipng", + "scripts": { + "build": "../build-rust.sh rm -rf pkg && wasm-pack build --debug --target web -- --verbose --locked && rm pkg/.gitignore" + } +} diff --git a/codecs/png/pkg/package.json b/codecs/png/pkg/package.json new file mode 100644 index 00000000..0baa377a --- /dev/null +++ b/codecs/png/pkg/package.json @@ -0,0 +1,15 @@ +{ + "name": "squoosh-png", + "collaborators": [ + "Surma " + ], + "version": "0.1.0", + "files": [ + "squoosh_png_bg.wasm", + "squoosh_png.js", + "squoosh_png.d.ts" + ], + "module": "squoosh_png.js", + "types": "squoosh_png.d.ts", + "sideEffects": false +} \ No newline at end of file diff --git a/codecs/png/pkg/squoosh_png.d.ts b/codecs/png/pkg/squoosh_png.d.ts new file mode 100644 index 00000000..46614316 --- /dev/null +++ b/codecs/png/pkg/squoosh_png.d.ts @@ -0,0 +1,37 @@ +/* tslint:disable */ +/* eslint-disable */ +/** +* @param {Uint8Array} data +* @param {number} width +* @param {number} height +* @returns {Uint8Array} +*/ +export function encode(data: Uint8Array, width: number, height: number): Uint8Array; +/** +* @param {Uint8Array} data +* @returns {ImageData} +*/ +export function decode(data: Uint8Array): ImageData; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly encode: (a: number, b: number, c: number, d: number, e: number) => void; + readonly decode: (a: number, b: number) => number; + readonly __wbindgen_malloc: (a: number) => number; + readonly __wbindgen_realloc: (a: number, b: number, c: number) => number; + readonly __wbindgen_free: (a: number, b: number) => void; + readonly __wbindgen_exn_store: (a: number) => void; +} + +/** +* If `module_or_path` is {RequestInfo} or {URL}, makes a request and +* for everything else, calls `WebAssembly.instantiate` directly. +* +* @param {InitInput | Promise} module_or_path +* +* @returns {Promise} +*/ +export default function init (module_or_path?: InitInput | Promise): Promise; + \ No newline at end of file diff --git a/codecs/png/pkg/squoosh_png.js b/codecs/png/pkg/squoosh_png.js new file mode 100644 index 00000000..a07ed4b7 --- /dev/null +++ b/codecs/png/pkg/squoosh_png.js @@ -0,0 +1,337 @@ + +let wasm; + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +let heap_next = heap.length; + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +let WASM_VECTOR_LEN = 0; + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +let cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (typeof(arg) !== 'string') throw new Error('expected a string argument'); + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + if (ret.read !== arg.length) throw new Error('failed to pass whole string'); + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachegetInt32Memory0 = null; +function getInt32Memory0() { + if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachegetInt32Memory0; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1); + getUint8Memory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function _assertNum(n) { + if (typeof(n) !== 'number') throw new Error('expected a number argument'); +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +* @param {Uint8Array} data +* @param {number} width +* @param {number} height +* @returns {Uint8Array} +*/ +export function encode(data, width, height) { + var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + var len0 = WASM_VECTOR_LEN; + _assertNum(width); + _assertNum(height); + wasm.encode(8, ptr0, len0, width, height); + var r0 = getInt32Memory0()[8 / 4 + 0]; + var r1 = getInt32Memory0()[8 / 4 + 1]; + var v1 = getArrayU8FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + return v1; +} + +/** +* @param {Uint8Array} data +* @returns {ImageData} +*/ +export function decode(data) { + var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc); + var len0 = WASM_VECTOR_LEN; + var ret = wasm.decode(ptr0, len0); + return takeObject(ret); +} + +let cachegetUint8ClampedMemory0 = null; +function getUint8ClampedMemory0() { + if (cachegetUint8ClampedMemory0 === null || cachegetUint8ClampedMemory0.buffer !== wasm.memory.buffer) { + cachegetUint8ClampedMemory0 = new Uint8ClampedArray(wasm.memory.buffer); + } + return cachegetUint8ClampedMemory0; +} + +function getClampedArrayU8FromWasm0(ptr, len) { + return getUint8ClampedMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + if (typeof(heap_next) !== 'number') throw new Error('corrupt heap'); + + heap[idx] = obj; + return idx; +} + +function handleError(f) { + return function () { + try { + return f.apply(this, arguments); + + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } + }; +} + +function logError(f) { + return function () { + try { + return f.apply(this, arguments); + + } catch (e) { + let error = (function () { + try { + return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString(); + } catch(_) { + return ""; + } + }()); + console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error); + throw e; + } + }; +} + +async function load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +async function init(input) { + if (typeof input === 'undefined') { + input = import.meta.url.replace(/\.js$/, '_bg.wasm'); + } + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_newwithu8clampedarrayandsh_d46fa191b076edfe = handleError(function(arg0, arg1, arg2, arg3) { + var ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0, arg3 >>> 0); + return addHeapObject(ret); + }); + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + var ret = debugString(getObject(arg1)); + var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } + + const { instance, module } = await load(await input, imports); + + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + + return wasm; +} + +export default init; + diff --git a/codecs/png/pkg/squoosh_png_bg.d.ts b/codecs/png/pkg/squoosh_png_bg.d.ts new file mode 100644 index 00000000..87b40bef --- /dev/null +++ b/codecs/png/pkg/squoosh_png_bg.d.ts @@ -0,0 +1,9 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export function encode(a: number, b: number, c: number, d: number, e: number): void; +export function decode(a: number, b: number): number; +export function __wbindgen_malloc(a: number): number; +export function __wbindgen_realloc(a: number, b: number, c: number): number; +export function __wbindgen_free(a: number, b: number): void; +export function __wbindgen_exn_store(a: number): void; diff --git a/codecs/png/pkg/squoosh_png_bg.wasm b/codecs/png/pkg/squoosh_png_bg.wasm new file mode 100644 index 00000000..089b985f Binary files /dev/null and b/codecs/png/pkg/squoosh_png_bg.wasm differ diff --git a/codecs/png/src/lib.rs b/codecs/png/src/lib.rs new file mode 100644 index 00000000..11f05476 --- /dev/null +++ b/codecs/png/src/lib.rs @@ -0,0 +1,47 @@ +use std::io::Cursor; + +use wasm_bindgen::prelude::*; +use web_sys::ImageData; + +#[wasm_bindgen(catch)] +pub fn encode(data: &[u8], width: u32, height: u32) -> Vec { + let mut buffer = Cursor::new(Vec::::new()); + + { + let mut encoder = png::Encoder::new(&mut buffer, width, height); + encoder.set_color(png::ColorType::RGBA); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(data).unwrap(); + } + + buffer.into_inner() +} + +#[wasm_bindgen(catch)] +pub fn decode(data: &[u8]) -> ImageData { + let mut decoder = png::Decoder::new(Cursor::new(data)); + decoder.set_transformations(png::Transformations::EXPAND); + let (info, mut reader) = decoder.read_info().unwrap(); + let num_pixels = (info.width * info.height) as usize; + let mut buf = vec![0; num_pixels * 4]; + reader.next_frame(&mut buf).unwrap(); + + // Transformations::EXPAND will make sure color_type is either + // RGBA or RGB. If it’s RGB, we need inject an alpha channel. + if info.color_type == png::ColorType::RGB { + for i in (0..num_pixels).rev() { + buf[i * 4 + 0] = buf[i * 3 + 0]; + buf[i * 4 + 1] = buf[i * 3 + 1]; + buf[i * 4 + 2] = buf[i * 3 + 2]; + buf[i * 4 + 3] = 255; + } + } + + ImageData::new_with_u8_clamped_array_and_sh( + wasm_bindgen::Clamped(&mut buf), + info.width, + info.height, + ) + .unwrap() +}