Build preprocessor pipeline

This commit is contained in:
Surma
2020-11-02 14:52:35 +00:00
parent 612cee0011
commit af954bd9e0
7 changed files with 269 additions and 62 deletions

View File

@ -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",

View File

@ -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(

View File

@ -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"
}
}

View File

@ -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<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

View File

@ -1,2 +1,135 @@
import * as wasm from "./squoosh_resize_bg.wasm";
export * from "./squoosh_resize_bg.js";
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;

View File

@ -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;
}