diff --git a/codecs/resize/.gitignore b/codecs/resize/.gitignore new file mode 100644 index 00000000..53f30e50 --- /dev/null +++ b/codecs/resize/.gitignore @@ -0,0 +1,5 @@ +**/*.rs.bk +target +Cargo.lock +bin/ +pkg/README.md diff --git a/codecs/resize/Cargo.toml b/codecs/resize/Cargo.toml new file mode 100644 index 00000000..b1f14fe6 --- /dev/null +++ b/codecs/resize/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "resize" +version = "0.1.0" +authors = ["Surma "] + +[lib] +#crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] + +[features] +default = ["console_error_panic_hook", "wee_alloc"] + +[dependencies] +cfg-if = "0.1.2" +wasm-bindgen = "0.2.38" +resize = "0.3.0" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.1", optional = true } + +# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size +# compared to the default allocator's ~10K. It is slower than the default +# allocator, however. +# +# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now. +wee_alloc = { version = "0.4.2", optional = true } + +[dev-dependencies] +wasm-bindgen-test = "0.2" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true diff --git a/codecs/resize/Dockerfile b/codecs/resize/Dockerfile new file mode 100644 index 00000000..1142b581 --- /dev/null +++ b/codecs/resize/Dockerfile @@ -0,0 +1,18 @@ +FROM ubuntu +RUN apt-get update && \ + apt-get install -qqy git build-essential cmake python2.7 +RUN git clone --recursive https://github.com/WebAssembly/wabt /usr/src/wabt +RUN mkdir -p /usr/src/wabt/build +WORKDIR /usr/src/wabt/build +RUN cmake .. -DCMAKE_INSTALL_PREFIX=/opt/wabt && \ + make && \ + make install + +FROM rust +RUN rustup install nightly && \ + rustup target add --toolchain nightly wasm32-unknown-unknown && \ + cargo install wasm-pack + +COPY --from=0 /opt/wabt /opt/wabt +ENV PATH="/opt/wabt/bin:${PATH}" +WORKDIR /src diff --git a/codecs/resize/build.sh b/codecs/resize/build.sh new file mode 100755 index 00000000..240b16ba --- /dev/null +++ b/codecs/resize/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +echo "=============================================" +echo "Compiling wasm" +echo "=============================================" +( + rustup run nightly \ + wasm-pack build --target no-modules + wasm-strip pkg/resize_bg.wasm +) +echo "=============================================" +echo "Compiling wasm done" +echo "=============================================" + +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +echo "Did you update your docker image?" +echo "Run \`docker pull ubuntu\`" +echo "Run \`docker pull rust\`" +echo "Run \`docker build -t squoosh-resize .\`" +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" diff --git a/codecs/resize/package-lock.json b/codecs/resize/package-lock.json new file mode 100644 index 00000000..ca1a498d --- /dev/null +++ b/codecs/resize/package-lock.json @@ -0,0 +1,4 @@ +{ + "name": "resize", + "lockfileVersion": 1 +} diff --git a/codecs/resize/package.json b/codecs/resize/package.json new file mode 100644 index 00000000..439f4262 --- /dev/null +++ b/codecs/resize/package.json @@ -0,0 +1,7 @@ +{ + "name": "resize", + "scripts": { + "build:image": "docker build -t squoosh-resize .", + "build": "docker run --rm -v $(pwd):/src squoosh-resize ./build.sh" + } +} diff --git a/codecs/resize/pkg/resize.d.ts b/codecs/resize/pkg/resize.d.ts new file mode 100644 index 00000000..3f68ed7b --- /dev/null +++ b/codecs/resize/pkg/resize.d.ts @@ -0,0 +1,11 @@ +/* tslint:disable */ +/** +* @param {Uint8Array} arg0 +* @param {number} arg1 +* @param {number} arg2 +* @param {number} arg3 +* @param {number} arg4 +* @param {number} arg5 +* @returns {Uint8Array} +*/ +export function resize(arg0: Uint8Array, arg1: number, arg2: number, arg3: number, arg4: number, arg5: number): Uint8Array; diff --git a/codecs/resize/pkg/resize.js b/codecs/resize/pkg/resize.js new file mode 100644 index 00000000..ec76ff7d --- /dev/null +++ b/codecs/resize/pkg/resize.js @@ -0,0 +1,112 @@ +(function() { + var wasm; + const __exports = {}; + + + let cachegetUint8Memory = null; + function getUint8Memory() { + if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) { + cachegetUint8Memory = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory; + } + + let WASM_VECTOR_LEN = 0; + + function passArray8ToWasm(arg) { + const ptr = wasm.__wbindgen_malloc(arg.length * 1); + getUint8Memory().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; + } + + function getArrayU8FromWasm(ptr, len) { + return getUint8Memory().subarray(ptr / 1, ptr / 1 + len); + } + + let cachedGlobalArgumentPtr = null; + function globalArgumentPtr() { + if (cachedGlobalArgumentPtr === null) { + cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr(); + } + return cachedGlobalArgumentPtr; + } + + let cachegetUint32Memory = null; + function getUint32Memory() { + if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) { + cachegetUint32Memory = new Uint32Array(wasm.memory.buffer); + } + return cachegetUint32Memory; + } + /** + * @param {Uint8Array} arg0 + * @param {number} arg1 + * @param {number} arg2 + * @param {number} arg3 + * @param {number} arg4 + * @param {number} arg5 + * @returns {Uint8Array} + */ + __exports.resize = function(arg0, arg1, arg2, arg3, arg4, arg5) { + const ptr0 = passArray8ToWasm(arg0); + const len0 = WASM_VECTOR_LEN; + const retptr = globalArgumentPtr(); + wasm.resize(retptr, ptr0, len0, arg1, arg2, arg3, arg4, arg5); + const mem = getUint32Memory(); + const rustptr = mem[retptr / 4]; + const rustlen = mem[retptr / 4 + 1]; + + const realRet = getArrayU8FromWasm(rustptr, rustlen).slice(); + wasm.__wbindgen_free(rustptr, rustlen * 1); + return realRet; + + }; + + const heap = new Array(32); + + heap.fill(undefined); + + heap.push(undefined, null, true, false); + + let heap_next = heap.length; + + function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; + } + + __exports.__wbindgen_object_drop_ref = function(i) { dropObject(i); }; + + function init(path_or_module) { + let instantiation; + const imports = { './resize': __exports }; + if (path_or_module instanceof WebAssembly.Module) { + instantiation = WebAssembly.instantiate(path_or_module, imports) + .then(instance => { + return { instance, module: path_or_module } + }); + } else { + const data = fetch(path_or_module); + if (typeof WebAssembly.instantiateStreaming === 'function') { + instantiation = WebAssembly.instantiateStreaming(data, imports) + .catch(e => { + console.warn("`WebAssembly.instantiateStreaming` failed. Assuming this is because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + return data + .then(r => r.arrayBuffer()) + .then(bytes => WebAssembly.instantiate(bytes, imports)); + }); + } else { + instantiation = data + .then(response => response.arrayBuffer()) + .then(buffer => WebAssembly.instantiate(buffer, imports)); + } + } + return instantiation.then(({instance}) => { + wasm = init.wasm = instance.exports; + + }); +}; +self.wasm_bindgen = Object.assign(init, __exports); +})(); diff --git a/codecs/resize/pkg/resize_bg.d.ts b/codecs/resize/pkg/resize_bg.d.ts new file mode 100644 index 00000000..58426e0e --- /dev/null +++ b/codecs/resize/pkg/resize_bg.d.ts @@ -0,0 +1,6 @@ +/* tslint:disable */ +export const memory: WebAssembly.Memory; +export function __wbindgen_global_argument_ptr(): number; +export function __wbindgen_malloc(a: number): number; +export function __wbindgen_free(a: number, b: number): void; +export function resize(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number): void; diff --git a/codecs/resize/pkg/resize_bg.wasm b/codecs/resize/pkg/resize_bg.wasm new file mode 100644 index 00000000..f7d98afd Binary files /dev/null and b/codecs/resize/pkg/resize_bg.wasm differ diff --git a/codecs/resize/src/lib.rs b/codecs/resize/src/lib.rs new file mode 100644 index 00000000..bf04a497 --- /dev/null +++ b/codecs/resize/src/lib.rs @@ -0,0 +1,52 @@ +extern crate cfg_if; +extern crate resize; +extern crate wasm_bindgen; + +mod utils; + +use cfg_if::cfg_if; +use resize::Pixel::RGBA; +use resize::Type; +use wasm_bindgen::prelude::*; + +cfg_if! { + // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global + // allocator. + if #[cfg(feature = "wee_alloc")] { + extern crate wee_alloc; + #[global_allocator] + static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + } +} + +#[wasm_bindgen] +#[no_mangle] +pub fn resize( + input_image: Vec, + input_width: usize, + input_height: usize, + output_width: usize, + output_height: usize, + typ_idx: usize, +) -> Vec { + let typ = match typ_idx { + 0 => Type::Triangle, + 1 => Type::Catrom, + 2 => Type::Mitchell, + 3 => Type::Lanczos3, + _ => panic!("Nope"), + }; + let num_output_pixels = output_width * output_height; + let mut resizer = resize::new( + input_width, + input_height, + output_width, + output_height, + RGBA, + 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()); + return output_image; +} diff --git a/codecs/resize/src/utils.rs b/codecs/resize/src/utils.rs new file mode 100644 index 00000000..2ffc954d --- /dev/null +++ b/codecs/resize/src/utils.rs @@ -0,0 +1,17 @@ +use cfg_if::cfg_if; + +cfg_if! { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + if #[cfg(feature = "console_error_panic_hook")] { + extern crate console_error_panic_hook; + pub use self::console_error_panic_hook::set_once as set_panic_hook; + } else { + #[inline] + pub fn set_panic_hook() {} + } +} diff --git a/package.json b/package.json index 535b09be..9498f94e 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "webpack-dev-server --host 0.0.0.0 --hot", "build": "webpack -p", - "lint": "tslint -c tslint.json -p tsconfig.json -t verbose 'src/**/*.{ts,tsx,js,jsx}'", + "lint": "tslint -c tslint.json -p tsconfig.json -t verbose", "lintfix": "tslint -c tslint.json -p tsconfig.json -t verbose --fix 'src/**/*.{ts,tsx,js,jsx}'", "sizereport": "node config/size-report.js" }, diff --git a/src/codecs/imagequant/processor.ts b/src/codecs/imagequant/processor.ts index c9362211..be6f4da2 100644 --- a/src/codecs/imagequant/processor.ts +++ b/src/codecs/imagequant/processor.ts @@ -1,12 +1,12 @@ import imagequant, { QuantizerModule } from '../../../codecs/imagequant/imagequant'; import wasmUrl from '../../../codecs/imagequant/imagequant.wasm'; import { QuantizeOptions } from './processor-meta'; -import { initWasmModule } from '../util'; +import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function process(data: ImageData, opts: QuantizeOptions): Promise { - if (!emscriptenModule) emscriptenModule = initWasmModule(imagequant, wasmUrl); + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(imagequant, wasmUrl); const module = await emscriptenModule; diff --git a/src/codecs/mozjpeg/encoder.ts b/src/codecs/mozjpeg/encoder.ts index 6514a169..b1a65173 100644 --- a/src/codecs/mozjpeg/encoder.ts +++ b/src/codecs/mozjpeg/encoder.ts @@ -1,12 +1,12 @@ import mozjpeg_enc, { MozJPEGModule } from '../../../codecs/mozjpeg_enc/mozjpeg_enc'; import wasmUrl from '../../../codecs/mozjpeg_enc/mozjpeg_enc.wasm'; import { EncodeOptions } from './encoder-meta'; -import { initWasmModule } from '../util'; +import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function encode(data: ImageData, options: EncodeOptions): Promise { - if (!emscriptenModule) emscriptenModule = initWasmModule(mozjpeg_enc, wasmUrl); + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(mozjpeg_enc, wasmUrl); const module = await emscriptenModule; const resultView = module.encode(data.data, data.width, data.height, options); diff --git a/src/codecs/optipng/encoder.ts b/src/codecs/optipng/encoder.ts index 0e5b00a1..164c4063 100644 --- a/src/codecs/optipng/encoder.ts +++ b/src/codecs/optipng/encoder.ts @@ -1,12 +1,12 @@ import optipng, { OptiPngModule } from '../../../codecs/optipng/optipng'; import wasmUrl from '../../../codecs/optipng/optipng.wasm'; import { EncodeOptions } from './encoder-meta'; -import { initWasmModule } from '../util'; +import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function compress(data: BufferSource, options: EncodeOptions): Promise { - if (!emscriptenModule) emscriptenModule = initWasmModule(optipng, wasmUrl); + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(optipng, wasmUrl); const module = await emscriptenModule; const resultView = module.compress(data, options); diff --git a/src/codecs/processor-worker/index.ts b/src/codecs/processor-worker/index.ts index 1c7217a4..c4eac690 100644 --- a/src/codecs/processor-worker/index.ts +++ b/src/codecs/processor-worker/index.ts @@ -28,6 +28,16 @@ async function rotate( return rotate(data, opts); } +async function resize( + data: ImageData, opts: import('../resize/processor-meta').WorkerResizeOptions, +): Promise { + const { resize } = await import( + /* webpackChunkName: "process-resize" */ + '../resize/processor'); + + return resize(data, opts); +} + async function optiPngEncode( data: BufferSource, options: import('../optipng/encoder-meta').EncodeOptions, ): Promise { @@ -53,7 +63,7 @@ async function webpDecode(data: ArrayBuffer): Promise { return decode(data); } -const exports = { mozjpegEncode, quantize, rotate, optiPngEncode, webpEncode, webpDecode }; +const exports = { mozjpegEncode, quantize, rotate, resize, optiPngEncode, webpEncode, webpDecode }; export type ProcessorWorkerApi = typeof exports; expose(exports, self); diff --git a/src/codecs/processor.ts b/src/codecs/processor.ts index acaa69df..68063248 100644 --- a/src/codecs/processor.ts +++ b/src/codecs/processor.ts @@ -6,8 +6,8 @@ import { EncodeOptions as OptiPNGEncoderOptions } from './optipng/encoder-meta'; import { EncodeOptions as WebPEncoderOptions } from './webp/encoder-meta'; import { EncodeOptions as BrowserJPEGOptions } from './browser-jpeg/encoder-meta'; import { EncodeOptions as BrowserWebpEncodeOptions } from './browser-webp/encoder-meta'; -import { BitmapResizeOptions, VectorResizeOptions } from './resize/processor-meta'; -import { resize, vectorResize } from './resize/processor'; +import { BrowserResizeOptions, VectorResizeOptions } from './resize/processor-meta'; +import { browserResize, vectorResize } from './resize/processor-sync'; import * as browserBMP from './browser-bmp/encoder'; import * as browserPNG from './browser-png/encoder'; import * as browserJPEG from './browser-jpeg/encoder'; @@ -130,6 +130,13 @@ export default class Processor { return this._workerApi!.rotate(data, opts); } + @Processor._processingJob({ needsWorker: true }) + workerResize( + data: ImageData, opts: import('./resize/processor-meta').WorkerResizeOptions, + ): Promise { + return this._workerApi!.resize(data, opts); + } + @Processor._processingJob({ needsWorker: true }) mozjpegEncode( data: ImageData, opts: MozJPEGEncoderOptions, @@ -202,9 +209,9 @@ export default class Processor { // Synchronous jobs - resize(data: ImageData, opts: BitmapResizeOptions) { + resize(data: ImageData, opts: BrowserResizeOptions) { this.abortCurrent(); - return resize(data, opts); + return browserResize(data, opts); } vectorResize(data: HTMLImageElement, opts: VectorResizeOptions) { diff --git a/src/codecs/resize/options.tsx b/src/codecs/resize/options.tsx index a6624434..cc9dc15a 100644 --- a/src/codecs/resize/options.tsx +++ b/src/codecs/resize/options.tsx @@ -87,6 +87,10 @@ export default class ResizerOptions extends Component { onChange={this.onChange} > {isVector && } + + + + diff --git a/src/codecs/resize/processor-meta.ts b/src/codecs/resize/processor-meta.ts index fad86f72..a08b8475 100644 --- a/src/codecs/resize/processor-meta.ts +++ b/src/codecs/resize/processor-meta.ts @@ -1,14 +1,19 @@ -type BitmapResizeMethods = 'browser-pixelated' | 'browser-low' | 'browser-medium' | 'browser-high'; +type BrowserResizeMethods = 'browser-pixelated' | 'browser-low' | 'browser-medium' | 'browser-high'; +type WorkerResizeMethods = 'point' | 'triangle' | 'catrom' | 'mitchell' | 'lanczos3'; export interface ResizeOptions { width: number; height: number; - method: 'vector' | BitmapResizeMethods; + method: 'vector' | BrowserResizeMethods | WorkerResizeMethods; fitMethod: 'stretch' | 'contain'; } -export interface BitmapResizeOptions extends ResizeOptions { - method: BitmapResizeMethods; +export interface BrowserResizeOptions extends ResizeOptions { + method: BrowserResizeMethods; +} + +export interface WorkerResizeOptions extends ResizeOptions { + method: WorkerResizeMethods; } export interface VectorResizeOptions extends ResizeOptions { @@ -21,6 +26,6 @@ export const defaultOptions: ResizeOptions = { width: 1, height: 1, // This will be set to 'vector' if the input is SVG. - method: 'browser-high', + method: 'lanczos3', fitMethod: 'stretch', }; diff --git a/src/codecs/resize/processor-sync.ts b/src/codecs/resize/processor-sync.ts new file mode 100644 index 00000000..f2192185 --- /dev/null +++ b/src/codecs/resize/processor-sync.ts @@ -0,0 +1,35 @@ +import { nativeResize, NativeResizeMethod, drawableToImageData } from '../../lib/util'; +import { BrowserResizeOptions, VectorResizeOptions } from './processor-meta'; +import { getContainOffsets } from './util'; + +export function browserResize(data: ImageData, opts: BrowserResizeOptions): ImageData { + let sx = 0; + let sy = 0; + let sw = data.width; + let sh = data.height; + + if (opts.fitMethod === 'contain') { + ({ sx, sy, sw, sh } = getContainOffsets(sw, sh, opts.width, opts.height)); + } + + return nativeResize( + data, sx, sy, sw, sh, opts.width, opts.height, + opts.method.slice('browser-'.length) as NativeResizeMethod, + ); +} + +export function vectorResize(data: HTMLImageElement, opts: VectorResizeOptions): ImageData { + let sx = 0; + let sy = 0; + let sw = data.width; + let sh = data.height; + + if (opts.fitMethod === 'contain') { + ({ sx, sy, sw, sh } = getContainOffsets(sw, sh, opts.width, opts.height)); + } + + return drawableToImageData(data, { + sx, sy, sw, sh, + width: opts.width, height: opts.height, + }); +} diff --git a/src/codecs/resize/processor.ts b/src/codecs/resize/processor.ts index 5db7aae8..ac2c0feb 100644 --- a/src/codecs/resize/processor.ts +++ b/src/codecs/resize/processor.ts @@ -1,49 +1,52 @@ -import { nativeResize, NativeResizeMethod, drawableToImageData } from '../../lib/util'; -import { BitmapResizeOptions, VectorResizeOptions } from './processor-meta'; +import wasmUrl from '../../../codecs/resize/pkg/resize_bg.wasm'; +import '../../../codecs/resize/pkg/resize'; +import { WorkerResizeOptions } from './processor-meta'; +import { getContainOffsets } from './util'; -function getContainOffsets(sw: number, sh: number, dw: number, dh: number) { - const currentAspect = sw / sh; - const endAspect = dw / dh; - - if (endAspect > currentAspect) { - const newSh = sw / endAspect; - const newSy = (sh - newSh) / 2; - return { sw, sh: newSh, sx: 0, sy: newSy }; - } - - const newSw = sh * endAspect; - const newSx = (sw - newSw) / 2; - return { sh, sw: newSw, sx: newSx, sy: 0 }; +interface WasmBindgenExports { + resize: typeof import('../../../codecs/resize/pkg/resize').resize; } -export function resize(data: ImageData, opts: BitmapResizeOptions): ImageData { - let sx = 0; - let sy = 0; - let sw = data.width; - let sh = data.height; +type WasmBindgen = ((url: string) => Promise) & WasmBindgenExports; - if (opts.fitMethod === 'contain') { - ({ sx, sy, sw, sh } = getContainOffsets(sw, sh, opts.width, opts.height)); +declare var wasm_bindgen: WasmBindgen; + +const ready = wasm_bindgen(wasmUrl); + +function crop(data: ImageData, sx: number, sy: number, sw: number, sh: number): ImageData { + const inputPixels = new Uint32Array(data.data.buffer); + + // Copy within the same buffer for speed and memory efficiency. + for (let y = 0; y < sh; y += 1) { + const start = ((y + sy) * data.width) + sx; + inputPixels.copyWithin(y * sw, start, start + sw); } - return nativeResize( - data, sx, sy, sw, sh, opts.width, opts.height, - opts.method.slice('browser-'.length) as NativeResizeMethod, + return new ImageData( + new Uint8ClampedArray(inputPixels.buffer.slice(0, sw * sh * 4)), + sw, sh, ); } -export function vectorResize(data: HTMLImageElement, opts: VectorResizeOptions): ImageData { - let sx = 0; - let sy = 0; - let sw = data.width; - let sh = data.height; +/** Resize methods by index */ +const resizeMethods: WorkerResizeOptions['method'][] = [ + 'triangle', 'catrom', 'mitchell', 'lanczos3', +]; + +export async function resize(data: ImageData, opts: WorkerResizeOptions): Promise { + let input = data; if (opts.fitMethod === 'contain') { - ({ sx, sy, sw, sh } = getContainOffsets(sw, sh, opts.width, opts.height)); + const { sx, sy, sw, sh } = getContainOffsets(data.width, data.height, opts.width, opts.height); + input = crop(input, Math.round(sx), Math.round(sy), Math.round(sw), Math.round(sh)); } - return drawableToImageData(data, { - sx, sy, sw, sh, - width: opts.width, height: opts.height, - }); + await ready; + + const result = wasm_bindgen.resize( + new Uint8Array(input.data.buffer), input.width, input.height, opts.width, opts.height, + resizeMethods.indexOf(opts.method), + ); + + return new ImageData(new Uint8ClampedArray(result.buffer), opts.width, opts.height); } diff --git a/src/codecs/resize/util.ts b/src/codecs/resize/util.ts new file mode 100644 index 00000000..4e67c47d --- /dev/null +++ b/src/codecs/resize/util.ts @@ -0,0 +1,14 @@ +export function getContainOffsets(sw: number, sh: number, dw: number, dh: number) { + const currentAspect = sw / sh; + const endAspect = dw / dh; + + if (endAspect > currentAspect) { + const newSh = sw / endAspect; + const newSy = (sh - newSh) / 2; + return { sw, sh: newSh, sx: 0, sy: newSy }; + } + + const newSw = sh * endAspect; + const newSx = (sw - newSw) / 2; + return { sh, sw: newSw, sx: newSx, sy: 0 }; +} diff --git a/src/codecs/util.ts b/src/codecs/util.ts index 7498e816..03f9a284 100644 --- a/src/codecs/util.ts +++ b/src/codecs/util.ts @@ -2,7 +2,7 @@ type ModuleFactory = ( opts: EmscriptenWasm.ModuleOpts, ) => M; -export function initWasmModule( +export function initEmscriptenModule( moduleFactory: ModuleFactory, wasmUrl: string, ): Promise { diff --git a/src/codecs/webp/decoder.ts b/src/codecs/webp/decoder.ts index 6fa6e9e9..6b25cbd2 100644 --- a/src/codecs/webp/decoder.ts +++ b/src/codecs/webp/decoder.ts @@ -1,11 +1,11 @@ import webp_dec, { WebPModule } from '../../../codecs/webp_dec/webp_dec'; import wasmUrl from '../../../codecs/webp_dec/webp_dec.wasm'; -import { initWasmModule } from '../util'; +import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function decode(data: ArrayBuffer): Promise { - if (!emscriptenModule) emscriptenModule = initWasmModule(webp_dec, wasmUrl); + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(webp_dec, wasmUrl); const module = await emscriptenModule; const rawImage = module.decode(data); diff --git a/src/codecs/webp/encoder.ts b/src/codecs/webp/encoder.ts index b308fab4..5fafb577 100644 --- a/src/codecs/webp/encoder.ts +++ b/src/codecs/webp/encoder.ts @@ -1,12 +1,12 @@ import webp_enc, { WebPModule } from '../../../codecs/webp_enc/webp_enc'; import wasmUrl from '../../../codecs/webp_enc/webp_enc.wasm'; import { EncodeOptions } from './encoder-meta'; -import { initWasmModule } from '../util'; +import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function encode(data: ImageData, options: EncodeOptions): Promise { - if (!emscriptenModule) emscriptenModule = initWasmModule(webp_enc, wasmUrl); + if (!emscriptenModule) emscriptenModule = initEmscriptenModule(webp_enc, wasmUrl); const module = await emscriptenModule; const resultView = module.encode(data.data, data.width, data.height, options); diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index 2bb90f03..71713ea0 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -31,7 +31,11 @@ import { import { decodeImage } from '../../codecs/decoders'; import { cleanMerge, cleanSet } from '../../lib/clean-modify'; import Processor from '../../codecs/processor'; -import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta'; +import { + VectorResizeOptions, + BrowserResizeOptions, + WorkerResizeOptions, +} from '../../codecs/resize/processor-meta'; import './custom-els/MultiPanel'; import Results from '../results'; import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons'; @@ -112,8 +116,10 @@ async function preprocessImage( source.vectorImage, preprocessData.resize as VectorResizeOptions, ); + } else if (preprocessData.resize.method.startsWith('browser-')) { + result = processor.resize(result, preprocessData.resize as BrowserResizeOptions); } else { - result = processor.resize(result, preprocessData.resize as BitmapResizeOptions); + result = await processor.workerResize(result, preprocessData.resize as WorkerResizeOptions); } } if (preprocessData.quantizer.enabled) { @@ -441,7 +447,7 @@ export default class Compress extends Component { newState = cleanMerge(newState, `sides.${i}.latestSettings.preprocessorState.resize`, { width: processed.width, height: processed.height, - method: vectorImage ? 'vector' : 'browser-high', + method: vectorImage ? 'vector' : 'lanczos3', }); }