Merge branch 'dev' into update_jxl_v02

This commit is contained in:
Surma
2021-01-05 16:37:42 +00:00
committed by GitHub
10 changed files with 195 additions and 218 deletions

View File

@@ -183,31 +183,36 @@ function progressTracker(results) {
return tracker; return tracker;
} }
async function checkInputFilesValid(files) { async function getInputFiles(paths) {
const validFiles = []; const validFiles = [];
for (const file of files) { for (const path of paths) {
try { const files = (await fsp.lstat(path)).isDirectory()
await fsp.stat(file); ? (await fsp.readdir(path)).map(file => join(path, file))
} catch (err) { : [path];
if (err.code === 'ENOENT') { for (const file of files) {
console.warn( try {
`Warning: Input file does not exist: ${resolvePath(file)}`, await fsp.stat(file);
); } catch (err) {
continue; if (err.code === 'ENOENT') {
} else { console.warn(
throw err; `Warning: Input file does not exist: ${resolvePath(file)}`,
);
continue;
} else {
throw err;
}
} }
}
validFiles.push(file); validFiles.push(file);
}
} }
return validFiles; return validFiles;
} }
async function processFiles(files) { async function processFiles(files) {
files = await checkInputFilesValid(files); files = await getInputFiles(files);
const parallelism = cpus().length; const parallelism = cpus().length;

View File

@@ -24,7 +24,7 @@ function jobPromise(worker, msg) {
export default class WorkerPool { export default class WorkerPool {
constructor(numWorkers, workerFile) { constructor(numWorkers, workerFile) {
this.closing = false; this.numWorkers = numWorkers;
this.jobQueue = new TransformStream(); this.jobQueue = new TransformStream();
this.workerQueue = new TransformStream(); this.workerQueue = new TransformStream();
@@ -42,7 +42,6 @@ export default class WorkerPool {
while (true) { while (true) {
const { value, done } = await reader.read(); const { value, done } = await reader.read();
if (done) { if (done) {
this.workerQueue.writable.close();
await this._terminateAll(); await this._terminateAll();
return; return;
} }
@@ -50,12 +49,6 @@ export default class WorkerPool {
const worker = await this._nextWorker(); const worker = await this._nextWorker();
jobPromise(worker, msg).then((result) => { jobPromise(worker, msg).then((result) => {
resolve(result); resolve(result);
// If we are in the process of closing, `workerQueue` is
// already closed and we cant requeue the worker.
if (this.closing) {
worker.terminate();
return;
}
const writer = this.workerQueue.writable.getWriter(); const writer = this.workerQueue.writable.getWriter();
writer.write(worker); writer.write(worker);
writer.releaseLock(); writer.releaseLock();
@@ -71,18 +64,15 @@ export default class WorkerPool {
} }
async _terminateAll() { async _terminateAll() {
while (true) { for (let n = 0; n < this.numWorkers; n++) {
const worker = await this._nextWorker(); const worker = await this._nextWorker();
if (!worker) {
return;
}
worker.terminate(); worker.terminate();
} }
this.workerQueue.writable.close();
} }
async join() { async join() {
this.closing = true; this.jobQueue.writable.getWriter().close();
this.jobQueue.writable.close();
await this.done; await this.done;
} }

View File

@@ -6,6 +6,12 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "bytemuck"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@@ -102,9 +108,21 @@ dependencies = [
[[package]] [[package]]
name = "resize" name = "resize"
version = "0.3.1" version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e653e390eafbfebb2b3c5fcfbc90d801bc410d0de1f44f266ffbf2151d28aa" checksum = "f2a08c42ea86684dc00256494c4eb8b54707890ddac50c05060a717f29669029"
dependencies = [
"rgb",
]
[[package]]
name = "rgb"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287f3c3f8236abb92d8b7e36797f19159df4b58f0a658cc3fb6dd3004b1f3bd3"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"

View File

@@ -14,7 +14,7 @@ default = ["console_error_panic_hook", "wee_alloc"]
[dependencies] [dependencies]
cfg-if = "0.1.2" cfg-if = "0.1.2"
wasm-bindgen = "0.2.38" wasm-bindgen = "0.2.38"
resize = "0.3.0" resize = "0.5.5"
# The `console_error_panic_hook` crate provides better debugging of panics by # The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires # logging them with `console.error`. This is great for development, but requires

View File

@@ -4,20 +4,14 @@ use std::io::Write;
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = ["); let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = [");
let mut linear_to_srgb_lut = String::from("static LINEAR_TO_SRGB_LUT: [f32; 256] = [");
for i in 0..256 { for i in 0..256 {
srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0))); srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0)));
srgb_to_linear_lut.push_str(","); srgb_to_linear_lut.push_str(",");
linear_to_srgb_lut.push_str(&format!("{0:.7}", linear_to_srgb((i as f32) / 255.0)));
linear_to_srgb_lut.push_str(",");
} }
srgb_to_linear_lut.pop().unwrap(); srgb_to_linear_lut.pop().unwrap();
linear_to_srgb_lut.pop().unwrap();
srgb_to_linear_lut.push_str("];"); srgb_to_linear_lut.push_str("];");
linear_to_srgb_lut.push_str("];");
let mut file = std::fs::File::create("src/lut.inc")?; let mut file = std::fs::File::create("src/lut.inc")?;
file.write_all(srgb_to_linear_lut.as_bytes())?; file.write_all(srgb_to_linear_lut.as_bytes())?;
file.write_all(linear_to_srgb_lut.as_bytes())?;
Ok(()) Ok(())
} }

View File

@@ -1,60 +1,34 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
/** /**
* @param {Uint8Array} input_image * @param {Uint8Array} input_image
* @param {number} input_width * @param {number} input_width
* @param {number} input_height * @param {number} input_height
* @param {number} output_width * @param {number} output_width
* @param {number} output_height * @param {number} output_height
* @param {number} typ_idx * @param {number} typ_idx
* @param {boolean} premultiply * @param {boolean} premultiply
* @param {boolean} color_space_conversion * @param {boolean} color_space_conversion
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
export function resize( 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;
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 = export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
| RequestInfo
| URL
| Response
| BufferSource
| WebAssembly.Module;
export interface InitOutput { export interface InitOutput {
readonly memory: WebAssembly.Memory; readonly memory: WebAssembly.Memory;
readonly resize: ( readonly resize: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => void;
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_malloc: (a: number) => number;
readonly __wbindgen_free: (a: number, b: number) => void; readonly __wbindgen_free: (a: number, b: number) => void;
} }
/** /**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly. * for everything else, calls `WebAssembly.instantiate` directly.
* *
* @param {InitInput | Promise<InitInput>} module_or_path * @param {InitInput | Promise<InitInput>} module_or_path
* *
* @returns {Promise<InitOutput>} * @returns {Promise<InitOutput>}
*/ */
export default function init( export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
module_or_path?: InitInput | Promise<InitInput>,
): Promise<InitOutput>;

View File

@@ -1,131 +1,107 @@
let wasm; let wasm;
let cachegetUint8Memory0 = null; let cachegetUint8Memory0 = null;
function getUint8Memory0() { function getUint8Memory0() {
if ( if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 === null || cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
cachegetUint8Memory0.buffer !== wasm.memory.buffer }
) { return cachegetUint8Memory0;
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
} }
let WASM_VECTOR_LEN = 0; let WASM_VECTOR_LEN = 0;
function passArray8ToWasm0(arg, malloc) { function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1); const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1); getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length; WASM_VECTOR_LEN = arg.length;
return ptr; return ptr;
} }
let cachegetInt32Memory0 = null; let cachegetInt32Memory0 = null;
function getInt32Memory0() { function getInt32Memory0() {
if ( if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 === null || cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
cachegetInt32Memory0.buffer !== wasm.memory.buffer }
) { return cachegetInt32Memory0;
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
} }
function getArrayU8FromWasm0(ptr, len) { function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
} }
/** /**
* @param {Uint8Array} input_image * @param {Uint8Array} input_image
* @param {number} input_width * @param {number} input_width
* @param {number} input_height * @param {number} input_height
* @param {number} output_width * @param {number} output_width
* @param {number} output_height * @param {number} output_height
* @param {number} typ_idx * @param {number} typ_idx
* @param {boolean} premultiply * @param {boolean} premultiply
* @param {boolean} color_space_conversion * @param {boolean} color_space_conversion
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
export function resize( export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
input_image, var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
input_width, var len0 = WASM_VECTOR_LEN;
input_height, wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
output_width, var r0 = getInt32Memory0()[8 / 4 + 0];
output_height, var r1 = getInt32Memory0()[8 / 4 + 1];
typ_idx, var v1 = getArrayU8FromWasm0(r0, r1).slice();
premultiply, wasm.__wbindgen_free(r0, r1 * 1);
color_space_conversion, return v1;
) {
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;
} }
async function load(module, imports) { async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) { if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try { if (typeof WebAssembly.instantiateStreaming === 'function') {
return await WebAssembly.instantiateStreaming(module, imports); try {
} catch (e) { return await WebAssembly.instantiateStreaming(module, imports);
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn( } catch (e) {
'`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', if (module.headers.get('Content-Type') != 'application/wasm') {
e, 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 { } else {
throw e; throw e;
}
}
} }
}
}
const bytes = await module.arrayBuffer(); const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports); return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else { } else {
return instance;
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
} }
}
} }
async function init(input) { async function init(input) {
if (typeof input === 'undefined') { if (typeof input === 'undefined') {
input = import.meta.url.replace(/\.js$/, '_bg.wasm'); input = import.meta.url.replace(/\.js$/, '_bg.wasm');
} }
const imports = {}; const imports = {};
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); if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
wasm = instance.exports; const { instance, module } = await load(await input, imports);
init.__wbindgen_wasm_module = module;
return wasm; wasm = instance.exports;
init.__wbindgen_wasm_module = module;
return wasm;
} }
export default init; export default init;

View File

@@ -5,12 +5,12 @@ extern crate wasm_bindgen;
mod utils; mod utils;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use resize::Pixel::RGBA; use resize::Pixel;
use resize::Type; use resize::Type;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
mod srgb; mod srgb;
use srgb::Clamp; use srgb::{linear_to_srgb, Clamp};
cfg_if! { cfg_if! {
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
@@ -27,14 +27,17 @@ include!("./lut.inc");
// If `with_space_conversion` is true, this function returns 2 functions that // If `with_space_conversion` is true, this function returns 2 functions that
// convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is // convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is
// false, the 2 functions returned do nothing. // false, the 2 functions returned do nothing.
fn converter_funcs(with_space_conversion: bool) -> ((fn(u8) -> f32), (fn(f32) -> u8)) { fn srgb_converter_funcs(with_space_conversion: bool) -> (fn(u8) -> f32, fn(f32) -> u8) {
if with_space_conversion { if with_space_conversion {
( (
|v| SRGB_TO_LINEAR_LUT[v as usize] * 255.0, |v| SRGB_TO_LINEAR_LUT[v as usize],
|v| (LINEAR_TO_SRGB_LUT[v as usize] * 255.0) as u8, |v| (linear_to_srgb(v) * 255.0).clamp(0.0, 255.0) as u8,
) )
} else { } else {
(|v| v as f32, |v| v as u8) (
|v| (v as f32) / 255.0,
|v| (v * 255.0).clamp(0.0, 255.0) as u8,
)
} }
} }
@@ -44,21 +47,18 @@ fn converter_funcs(with_space_conversion: bool) -> ((fn(u8) -> f32), (fn(f32) ->
// false, the functions just return the channel value. // false, the functions just return the channel value.
fn alpha_multiplier_funcs( fn alpha_multiplier_funcs(
with_alpha_premultiplication: bool, with_alpha_premultiplication: bool,
) -> ((fn(f32, u8) -> u8), (fn(u8, u8) -> f32)) { ) -> (fn(f32, f32) -> f32, fn(f32, f32) -> f32) {
if with_alpha_premultiplication { if with_alpha_premultiplication {
( (|v, a| v * a, |v, a| v / a)
|v, a| (v * (a as f32) / 255.0) as u8,
|v, a| ((v as f32) * 255.0 / (a as f32)).clamp(0.0, 255.0),
)
} else { } else {
(|v, _a| v as u8, |v, _a| v as f32) (|v, _a| v, |v, _a| v)
} }
} }
#[wasm_bindgen] #[wasm_bindgen]
#[no_mangle] #[no_mangle]
pub fn resize( pub fn resize(
mut input_image: Vec<u8>, input_image: Vec<u8>,
input_width: usize, input_width: usize,
input_height: usize, input_height: usize,
output_width: usize, output_width: usize,
@@ -77,44 +77,64 @@ pub fn resize(
let num_input_pixels = input_width * input_height; let num_input_pixels = input_width * input_height;
let num_output_pixels = output_width * output_height; let num_output_pixels = output_width * output_height;
let (to_linear, to_color_space) = converter_funcs(color_space_conversion); let mut output_image = vec![0u8; num_output_pixels * 4];
// If both options are false, there is no preprocessing on the pixel values
// and we can skip the loop.
if !premultiply && !color_space_conversion {
let mut resizer = resize::new(
input_width,
input_height,
output_width,
output_height,
Pixel::RGBA,
typ,
);
resizer.resize(input_image.as_slice(), output_image.as_mut_slice());
return output_image;
}
// Otherwise, we convert to f32 images to keep the
// conversions as lossless and high-fidelity as possible.
let (to_linear, to_srgb) = srgb_converter_funcs(color_space_conversion);
let (premultiplier, demultiplier) = alpha_multiplier_funcs(premultiply); let (premultiplier, demultiplier) = alpha_multiplier_funcs(premultiply);
// If both options are false, there is no preprocessing on the pixel valus let mut preprocessed_input_image: Vec<f32> = Vec::with_capacity(input_image.len());
// and we can skip the loop. preprocessed_input_image.resize(input_image.len(), 0.0f32);
if premultiply || color_space_conversion { for i in 0..num_input_pixels {
for i in 0..num_input_pixels { for j in 0..3 {
for j in 0..3 { preprocessed_input_image[4 * i + j] = premultiplier(
input_image[4 * i + j] = to_linear(input_image[4 * i + j]),
premultiplier(to_linear(input_image[4 * i + j]), input_image[4 * i + 3]); (input_image[4 * i + 3] as f32) / 255.0,
} );
} }
preprocessed_input_image[4 * i + 3] = (input_image[4 * i + 3] as f32) / 255.0;
} }
let mut unprocessed_output_image = vec![0.0f32; num_output_pixels * 4];
let mut resizer = resize::new( let mut resizer = resize::new(
input_width, input_width,
input_height, input_height,
output_width, output_width,
output_height, output_height,
RGBA, Pixel::RGBAF32,
typ, typ,
); );
let mut output_image = Vec::<u8>::with_capacity(num_output_pixels * 4); resizer.resize(
output_image.resize(num_output_pixels * 4, 0); preprocessed_input_image.as_slice(),
resizer.resize(input_image.as_slice(), output_image.as_mut_slice()); unprocessed_output_image.as_mut_slice(),
);
if premultiply || color_space_conversion { for i in 0..num_output_pixels {
for i in 0..num_output_pixels { for j in 0..3 {
for j in 0..3 { output_image[4 * i + j] = to_srgb(demultiplier(
// We dont need to worry about division by zero, as division by zero unprocessed_output_image[4 * i + j],
// is well-defined on floats to return ±Inf. ±Inf is converted to 0 unprocessed_output_image[4 * i + 3],
// when casting to integers. ));
output_image[4 * i + j] = to_color_space(demultiplier(
output_image[4 * i + j],
output_image[4 * i + 3],
));
}
} }
output_image[4 * i + 3] =
(unprocessed_output_image[4 * i + 3] * 255.0).clamp(0.0, 255.0) as u8;
} }
return output_image; return output_image;

Binary file not shown.