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(