diff --git a/cli/src/codecs.js b/cli/src/codecs.js index 5c323e17..f2317ddb 100644 --- a/cli/src/codecs.js +++ b/cli/src/codecs.js @@ -30,11 +30,71 @@ import * as oxipng from "../../codecs/oxipng/pkg/squoosh_oxipng.js"; import oxipngWasm from "asset-url:../../codecs/oxipng/pkg/squoosh_oxipng_bg.wasm"; const oxipngPromise = oxipng.default(fsp.readFile(pathify(oxipngWasm))); +import * as resize from "../../codecs/resize/pkg/squoosh_resize.js"; +import resizeWasm from "asset-url:../../codecs/resize/pkg/squoosh_resize_bg.wasm"; +const resizePromise = resize.default(fsp.readFile(pathify(resizeWasm))); + // Our decoders currently rely on a `ImageData` global. import ImageData from "./image_data.js"; globalThis.ImageData = ImageData; -export default { +function resizeNameToIndex(name) { + switch (name) { + case "triangle": + return 0; + case "catrom": + return 1; + case "mitchell": + return 2; + case "lanczos3": + return 3; + default: + throw Error(`Unknown resize algorithm "${name}"`); + } +} + +export const preprocessors = { + resize: { + name: "Resize", + description: "Resize the image before compressing", + instantiate: async () => { + await resizePromise; + return ( + buffer, + input_width, + input_height, + { width, height, method, premultiply, linearRGB } + ) => + new ImageData( + resize.resize( + buffer, + input_width, + input_height, + width, + height, + resizeNameToIndex(method), + premultiply, + linearRGB + ), + width, + height + ); + }, + defaultOptions: { + // Width and height will always default to the image size. + // This is set elsewhere. + width: 1, + height: 1, + // This will be set to 'vector' if the input is SVG. + method: "lanczos3", + fitMethod: "stretch", + premultiply: true, + linearRGB: true + } + } +}; + +export const codecs = { mozjpeg: { name: "MozJPEG", extension: "jpg", diff --git a/cli/src/index.js b/cli/src/index.js index 45941a48..0cb55eed 100644 --- a/cli/src/index.js +++ b/cli/src/index.js @@ -8,7 +8,7 @@ import { version } from "json:../package.json"; import ora from 'ora'; import kleur from 'kleur'; -import supportedFormats from "./codecs.js"; +import {codecs as supportedFormats, preprocessors} from "./codecs.js"; import WorkerPool from "./worker_pool.js"; import { autoOptimize } from "./auto-optimizer.js"; @@ -47,6 +47,16 @@ async function decodeFile(file) { }; } +async function preprocessImage({ + preprocessorName, + options, + file +}) { + const preprocessor = await preprocessors[preprocessorName].instantiate(); + file.bitmap= await preprocessor(file.bitmap.data, file.bitmap.width, file.bitmap.height, options); + return file; +} + async function encodeFile({ file, size, @@ -110,12 +120,16 @@ async function encodeFile({ // both decoding and encoding go through the worker pool function handleJob(params) { const { operation } = params; - if (operation === 'encode') { + switch(operation) { + case "encode": return encodeFile(params); - } - if (operation === 'decode') { + case "decode": return decodeFile(params.file); - } + case "preprocess": + return preprocessImage(params); + default: + throw Error(`Invalid job "${operation}"`); + } } function progressTracker(results) { @@ -175,7 +189,7 @@ async function processFiles(files) { await fsp.mkdir(program.outputDir, { recursive: true }); let decoded = 0; - const decodedFiles = await Promise.all(files.map(async file => { + let decodedFiles = await Promise.all(files.map(async file => { const result = await workerPool.dispatchJob({ operation: 'decode', file }); results.set(file, { file: result.file, @@ -186,6 +200,31 @@ async function processFiles(files) { return result; })); + for (const [preprocessorName, value] of Object.entries(preprocessors)) { + if(!program[preprocessorName]) { + continue; + } + const preprocessorParam = program[preprocessorName]; + const preprocessorOptions = Object.assign( + {}, + value.defaultOptions, + JSON5.parse(preprocessorParam) + ); + + decodedFiles = await Promise.all(decodedFiles.map(async file => { + return workerPool.dispatchJob({ + file, + operation: "preprocess", + preprocessorName, + options: preprocessorOptions + }); + })); + + for (const { file, bitmap, size } of decodedFiles) { + + } + } + progress.progressOffset = decoded; progress.setStatus('Encoding ' + kleur.dim(`(${parallelism} threads)`)); progress.setProgress(0, files.length); @@ -261,6 +300,13 @@ if (isMainThread) { ) .action(processFiles); + // Create a CLI option for each supported preprocessor + for (const [key, value] of Object.entries(preprocessors)) { + program.option( + `--${key} [config]`, + value.description + ); + } // Create a CLI option for each supported encoder for (const [key, value] of Object.entries(supportedFormats)) { program.option( diff --git a/codecs/resize/package.json b/codecs/resize/package.json index facaf7d4..f439272c 100644 --- a/codecs/resize/package.json +++ b/codecs/resize/package.json @@ -1,6 +1,6 @@ { "name": "resize", "scripts": { - "build": "../build-rust.sh" + "build": "../build-rust.sh rm -rf pkg && wasm-pack build --debug --target web -- --verbose --locked && rm pkg/.gitignore" } } diff --git a/codecs/resize/pkg/squoosh_resize.d.ts b/codecs/resize/pkg/squoosh_resize.d.ts index e984b053..c5899071 100644 --- a/codecs/resize/pkg/squoosh_resize.d.ts +++ b/codecs/resize/pkg/squoosh_resize.d.ts @@ -12,3 +12,23 @@ * @returns {Uint8Array} */ export function resize(input_image: Uint8Array, input_width: number, input_height: number, output_width: number, output_height: number, typ_idx: number, premultiply: boolean, color_space_conversion: boolean): Uint8Array; + +export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; + +export interface InitOutput { + readonly memory: WebAssembly.Memory; + readonly resize: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => void; + readonly __wbindgen_malloc: (a: number) => number; + readonly __wbindgen_free: (a: number, b: 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/resize/pkg/squoosh_resize.js b/codecs/resize/pkg/squoosh_resize.js index ae32b04d..f4d3e739 100644 --- a/codecs/resize/pkg/squoosh_resize.js +++ b/codecs/resize/pkg/squoosh_resize.js @@ -1,2 +1,135 @@ -import * as wasm from "./squoosh_resize_bg.wasm"; -export * from "./squoosh_resize_bg.js"; \ No newline at end of file + +let wasm; + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +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 _assertBoolean(n) { + if (typeof(n) !== 'boolean') { + throw new Error('expected a boolean argument'); + } +} + +let cachegetInt32Memory0 = null; +function getInt32Memory0() { + if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachegetInt32Memory0; +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +* @param {Uint8Array} input_image +* @param {number} input_width +* @param {number} input_height +* @param {number} output_width +* @param {number} output_height +* @param {number} typ_idx +* @param {boolean} premultiply +* @param {boolean} color_space_conversion +* @returns {Uint8Array} +*/ +export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) { + var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc); + var len0 = WASM_VECTOR_LEN; + _assertNum(input_width); + _assertNum(input_height); + _assertNum(output_width); + _assertNum(output_height); + _assertNum(typ_idx); + _assertBoolean(premultiply); + _assertBoolean(color_space_conversion); + wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion); + 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; +} + +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.__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/resize/pkg/squoosh_resize_bg.js b/codecs/resize/pkg/squoosh_resize_bg.js deleted file mode 100644 index bfbf2f9f..00000000 --- a/codecs/resize/pkg/squoosh_resize_bg.js +++ /dev/null @@ -1,52 +0,0 @@ -import * as wasm from './squoosh_resize_bg.wasm'; - -let cachegetUint8Memory0 = null; -function getUint8Memory0() { - if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { - cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachegetUint8Memory0; -} - -let WASM_VECTOR_LEN = 0; - -function passArray8ToWasm0(arg, malloc) { - const ptr = malloc(arg.length * 1); - getUint8Memory0().set(arg, ptr / 1); - WASM_VECTOR_LEN = arg.length; - return ptr; -} - -let cachegetInt32Memory0 = null; -function getInt32Memory0() { - if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { - cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachegetInt32Memory0; -} - -function getArrayU8FromWasm0(ptr, len) { - return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); -} -/** -* @param {Uint8Array} input_image -* @param {number} input_width -* @param {number} input_height -* @param {number} output_width -* @param {number} output_height -* @param {number} typ_idx -* @param {boolean} premultiply -* @param {boolean} color_space_conversion -* @returns {Uint8Array} -*/ -export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) { - var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc); - var len0 = WASM_VECTOR_LEN; - wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion); - 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; -} - diff --git a/codecs/resize/pkg/squoosh_resize_bg.wasm b/codecs/resize/pkg/squoosh_resize_bg.wasm index 35425a77..d0f59c0f 100644 Binary files a/codecs/resize/pkg/squoosh_resize_bg.wasm and b/codecs/resize/pkg/squoosh_resize_bg.wasm differ