diff --git a/codecs/resize/Cargo.lock b/codecs/resize/Cargo.lock index c8e03297..99cbde89 100644 --- a/codecs/resize/Cargo.lock +++ b/codecs/resize/Cargo.lock @@ -6,6 +6,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "bytemuck" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac" + [[package]] name = "cfg-if" version = "0.1.10" @@ -102,9 +108,21 @@ dependencies = [ [[package]] name = "resize" -version = "0.3.1" +version = "0.5.5" 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]] name = "scoped-tls" diff --git a/codecs/resize/Cargo.toml b/codecs/resize/Cargo.toml index c0bf17a5..76d2b153 100644 --- a/codecs/resize/Cargo.toml +++ b/codecs/resize/Cargo.toml @@ -14,7 +14,7 @@ default = ["console_error_panic_hook", "wee_alloc"] [dependencies] cfg-if = "0.1.2" 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 # logging them with `console.error`. This is great for development, but requires diff --git a/codecs/resize/build.rs b/codecs/resize/build.rs index 8feb48a8..077ab730 100644 --- a/codecs/resize/build.rs +++ b/codecs/resize/build.rs @@ -4,20 +4,14 @@ use std::io::Write; fn main() -> std::io::Result<()> { 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 { srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0))); 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(); - linear_to_srgb_lut.pop().unwrap(); srgb_to_linear_lut.push_str("];"); - linear_to_srgb_lut.push_str("];"); let mut file = std::fs::File::create("src/lut.inc")?; file.write_all(srgb_to_linear_lut.as_bytes())?; - file.write_all(linear_to_srgb_lut.as_bytes())?; Ok(()) } diff --git a/codecs/resize/pkg/squoosh_resize.d.ts b/codecs/resize/pkg/squoosh_resize.d.ts index a07bae4c..c5899071 100644 --- a/codecs/resize/pkg/squoosh_resize.d.ts +++ b/codecs/resize/pkg/squoosh_resize.d.ts @@ -1,60 +1,34 @@ /* tslint:disable */ /* eslint-disable */ /** - * @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: Uint8Array, - input_width: number, - input_height: number, - output_width: number, - output_height: number, - typ_idx: number, - premultiply: boolean, - color_space_conversion: boolean, -): Uint8Array; +* @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: 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 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 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; +* 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 bfb298ba..74f31d1f 100644 --- a/codecs/resize/pkg/squoosh_resize.js +++ b/codecs/resize/pkg/squoosh_resize.js @@ -1,131 +1,107 @@ + let wasm; let cachegetUint8Memory0 = null; function getUint8Memory0() { - if ( - cachegetUint8Memory0 === null || - cachegetUint8Memory0.buffer !== wasm.memory.buffer - ) { - cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachegetUint8Memory0; + 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; + 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; + 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); + 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; +* @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; } 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; + 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); + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; } 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) { - if (typeof input === 'undefined') { - input = import.meta.url.replace(/\.js$/, '_bg.wasm'); - } - const imports = {}; + if (typeof input === 'undefined') { + input = import.meta.url.replace(/\.js$/, '_bg.wasm'); + } + 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; - init.__wbindgen_wasm_module = module; + const { instance, module } = await load(await input, imports); - return wasm; + wasm = instance.exports; + init.__wbindgen_wasm_module = module; + + return wasm; } export default init; + diff --git a/codecs/resize/pkg/squoosh_resize_bg.wasm b/codecs/resize/pkg/squoosh_resize_bg.wasm index f0d8264f..0a10cca3 100644 Binary files a/codecs/resize/pkg/squoosh_resize_bg.wasm and b/codecs/resize/pkg/squoosh_resize_bg.wasm differ diff --git a/codecs/resize/src/lib.rs b/codecs/resize/src/lib.rs index 14832b59..bb7df8ad 100644 --- a/codecs/resize/src/lib.rs +++ b/codecs/resize/src/lib.rs @@ -5,12 +5,12 @@ extern crate wasm_bindgen; mod utils; use cfg_if::cfg_if; -use resize::Pixel::RGBA; +use resize::Pixel; use resize::Type; use wasm_bindgen::prelude::*; mod srgb; -use srgb::Clamp; +use srgb::{linear_to_srgb, Clamp}; cfg_if! { // 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 // convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is // 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 { ( - |v| SRGB_TO_LINEAR_LUT[v as usize] * 255.0, - |v| (LINEAR_TO_SRGB_LUT[v as usize] * 255.0) as u8, + |v| SRGB_TO_LINEAR_LUT[v as usize], + |v| (linear_to_srgb(v) * 255.0).clamp(0.0, 255.0) as u8, ) } 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. fn alpha_multiplier_funcs( with_alpha_premultiplication: bool, -) -> ((fn(f32, u8) -> u8), (fn(u8, u8) -> f32)) { +) -> (fn(f32, f32) -> f32, fn(f32, f32) -> f32) { if with_alpha_premultiplication { - ( - |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), - ) + (|v, a| v * a, |v, a| v / a) } else { - (|v, _a| v as u8, |v, _a| v as f32) + (|v, _a| v, |v, _a| v) } } #[wasm_bindgen] #[no_mangle] pub fn resize( - mut input_image: Vec, + input_image: Vec, input_width: usize, input_height: usize, output_width: usize, @@ -77,44 +77,64 @@ pub fn resize( let num_input_pixels = input_width * input_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); - // If both options are false, there is no preprocessing on the pixel valus - // and we can skip the loop. - if premultiply || color_space_conversion { - for i in 0..num_input_pixels { - for j in 0..3 { - input_image[4 * i + j] = - premultiplier(to_linear(input_image[4 * i + j]), input_image[4 * i + 3]); - } + let mut preprocessed_input_image: Vec = Vec::with_capacity(input_image.len()); + preprocessed_input_image.resize(input_image.len(), 0.0f32); + for i in 0..num_input_pixels { + for j in 0..3 { + preprocessed_input_image[4 * i + j] = premultiplier( + to_linear(input_image[4 * i + j]), + (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( input_width, input_height, output_width, output_height, - RGBA, + Pixel::RGBAF32, typ, ); - let mut output_image = Vec::::with_capacity(num_output_pixels * 4); - output_image.resize(num_output_pixels * 4, 0); - resizer.resize(input_image.as_slice(), output_image.as_mut_slice()); + resizer.resize( + preprocessed_input_image.as_slice(), + unprocessed_output_image.as_mut_slice(), + ); - if premultiply || color_space_conversion { - for i in 0..num_output_pixels { - for j in 0..3 { - // We don’t need to worry about division by zero, as division by zero - // is well-defined on floats to return ±Inf. ±Inf is converted to 0 - // when casting to integers. - output_image[4 * i + j] = to_color_space(demultiplier( - output_image[4 * i + j], - output_image[4 * i + 3], - )); - } + for i in 0..num_output_pixels { + for j in 0..3 { + output_image[4 * i + j] = to_srgb(demultiplier( + unprocessed_output_image[4 * i + j], + unprocessed_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;