From b6a8f7eebad8c72c98f747dc3ca5eb79a82a9b33 Mon Sep 17 00:00:00 2001 From: Surma Date: Wed, 23 Jan 2019 10:24:44 -0500 Subject: [PATCH 1/3] Rotate implementation in Rust --- codecs/rotate/Dockerfile | 17 ++++++ codecs/rotate/build.sh | 24 ++++++++ codecs/rotate/package.json | 7 +++ codecs/rotate/rotate.rs | 81 +++++++++++++++++++++++++ codecs/rotate/rotate.wasm | Bin 0 -> 3139 bytes src/codecs/rotate/processor-meta.ts | 7 +++ src/codecs/rotate/processor.ts | 91 +++++++--------------------- 7 files changed, 158 insertions(+), 69 deletions(-) create mode 100644 codecs/rotate/Dockerfile create mode 100755 codecs/rotate/build.sh create mode 100644 codecs/rotate/package.json create mode 100644 codecs/rotate/rotate.rs create mode 100755 codecs/rotate/rotate.wasm diff --git a/codecs/rotate/Dockerfile b/codecs/rotate/Dockerfile new file mode 100644 index 00000000..bf4a416d --- /dev/null +++ b/codecs/rotate/Dockerfile @@ -0,0 +1,17 @@ +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 + +COPY --from=0 /opt/wabt /opt/wabt +ENV PATH="/opt/wabt/bin:${PATH}" +WORKDIR /src diff --git a/codecs/rotate/build.sh b/codecs/rotate/build.sh new file mode 100755 index 00000000..021473ff --- /dev/null +++ b/codecs/rotate/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +echo "=============================================" +echo "Compiling wasm" +echo "=============================================" +( + rustup run nightly \ + rustc \ + --target=wasm32-unknown-unknown \ + -C opt-level=3 \ + -o rotate.wasm \ + rotate.rs + wasm-strip rotate.wasm +) +echo "=============================================" +echo "Compiling wasm done" +echo "=============================================" + +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" +echo "Did you update your docker image?" +echo "Run \`docker build -t squoosh-rotate .\`" +echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" diff --git a/codecs/rotate/package.json b/codecs/rotate/package.json new file mode 100644 index 00000000..d7388aaa --- /dev/null +++ b/codecs/rotate/package.json @@ -0,0 +1,7 @@ +{ + "name": "rotate", + "scripts": { + "build:image": "docker build -t squoosh-rotate .", + "build": "docker run --rm -v $(pwd):/src squoosh-rotate ./build.sh" + } +} diff --git a/codecs/rotate/rotate.rs b/codecs/rotate/rotate.rs new file mode 100644 index 00000000..335899c1 --- /dev/null +++ b/codecs/rotate/rotate.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use core::slice::from_raw_parts_mut; + +#[no_mangle] +fn rotate(input_width: isize, input_height: isize, rotate: isize) { + let mut i = 0isize; + + // In the straight-copy case, d1 is x, d2 is y. + // x starts at 0 and increases. + // y starts at 0 and increases. + let mut d1_start: isize = 0; + let mut d1_limit: isize = input_width; + let mut d1_advance: isize = 1; + let mut d1_multiplier: isize = 1; + let mut d2_start: isize = 0; + let mut d2_limit: isize = input_height; + let mut d2_advance: isize = 1; + let mut d2_multiplier: isize = input_width; + + if rotate == 90 { + // d1 is y, d2 is x. + // y starts at its max value and decreases. + // x starts at 0 and increases. + d1_start = input_height - 1; + d1_limit = input_height; + d1_advance = -1; + d1_multiplier = input_width; + d2_start = 0; + d2_limit = input_width; + d2_advance = 1; + d2_multiplier = 1; + } else if rotate == 180 { + // d1 is x, d2 is y. + // x starts at its max and decreases. + // y starts at its max and decreases. + d1_start = input_width - 1; + d1_limit = input_width; + d1_advance = -1; + d1_multiplier = 1; + d2_start = input_height - 1; + d2_limit = input_height; + d2_advance = -1; + d2_multiplier = input_width; + } else if rotate == 270 { + // d1 is y, d2 is x. + // y starts at 0 and increases. + // x starts at its max and decreases. + d1_start = 0; + d1_limit = input_height; + d1_advance = 1; + d1_multiplier = input_width; + d2_start = input_width - 1; + d2_limit = input_width; + d2_advance = -1; + d2_multiplier = 1; + } + + let num_pixels = (input_width * input_height) as usize; + let in_b: &mut [u32]; + let out_b: &mut [u32]; + unsafe { + in_b = from_raw_parts_mut::(4 as *mut u32, num_pixels); + out_b = from_raw_parts_mut::((input_width * input_height * 4 + 4) as *mut u32, num_pixels); + } + + for d2 in 0..d2_limit { + for d1 in 0..d1_limit { + let in_idx = (d1_start + d1 * d1_advance) * d1_multiplier + (d2_start + d2 * d2_advance) * d2_multiplier; + out_b[i as usize] = in_b[in_idx as usize]; + i += 1; + } + } +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} diff --git a/codecs/rotate/rotate.wasm b/codecs/rotate/rotate.wasm new file mode 100755 index 0000000000000000000000000000000000000000..0bcab5e9ea22f17c0072ddb088b83422cb0071a7 GIT binary patch literal 3139 zcmb7GJ8vCD6h1R^Uwe1gzKK!j`DXU6eeEP9$iAAHGvA!^o!89L;?_+@ zMC?Lmfe!|3upkCzATl#rU<1-Sdcy83w6JRcqHA%{Xox1Y+Knc=#Tt#KV05hYf*3G0 z96ls_KObA9uDPWJr#_2tD|^Vb%)mIdq1&o3=*FU~KoEeYP*T;E>Y zUM6N)j1;p)OSHQWPFOhfqhJ#QMV{a@`m60?(#bV^CRkF-Xz-{HEvM;zpe&qo~CD;zOJF`XzS z=A9fCxfI$ALZVG@^-X0>m)@sCV;Gp;Zl(AUXRR^cMtarDRSio*$bC|u|4eV7ej!C} zxUxzqO245#07C>GyKBfchSe3nrGCc>)w!)6XlvyKqSpw)R(DF-l5K3St~|CYpbLJG z+WYR83s;cUriUXgp)z%8Hf`NcvWdgG1SP{)8^$hG>dpEH0|zAxt(7KH z)|&^423nEdg!!PHCep}p6K3a#HXBXLs(2~_gO{aP>D|*Y1#u2NqXd!d(#89K{`1FI zKYjhl-(-K`uy!5q9->vjjCbr3Li{T3rtc9{9d`tA!)K?w4k{j{ZW#}B-{4BXKCXas zUI+dlsErN3tB;eX*8)>1Q2E?rVg^md&Jn4HY(ov%tZrIx*B#{GTJRm+6VgIPrM=== zw(Kgt<8{X`uqVwm1zb;;VTqJeYQE6&7H+n->TI|iR;?A!QB2Y~3Va5eV)m30@aPgc zn4|5Xc5Pg<+66XE0fqo~$#@)|9s{?IK?QlT^R7LX0F)yN z;7AVezUUkX49bII|TB+C$>I}0T&#@&QrHnh^hh#K-ruOIx65o;_4~S=1 zNrZfIBv-HNV%wZ1SEE#GN4>a#3WNZ!guz3KHobyc(102~QNwFNb>7)glN;($bzb0n z%^rY@UB*eLjhN;WAg+4;o_N%_GWOOt9JyfduXP(p~;-EnO zvq9jIHtjpQhw2vbkzxGTw#^p`J0NVHVsoHodkC(n3PW|2T~j3cLFgV0Qx)s6>~@sS zE^2T**43(xb?2y(O6ypxt>=j~p4X?u+Ppc>3=F=h%dVs$MyIB$#dg$~oZ*#OLV8y!yaAzqv(3H`bPx-;?XNx8?e~^4j|CwWY0TxqW?E zt}d_18(UId!N61i>%0tqUiTQu@Cco ssP5xfA0pCUX=Kk3^~gdb=I}d(-}t(X6*n>>hEd?>0536~#dsX!zpSPoUjP6A literal 0 HcmV?d00001 diff --git a/src/codecs/rotate/processor-meta.ts b/src/codecs/rotate/processor-meta.ts index 854bcafa..1b70ccdd 100644 --- a/src/codecs/rotate/processor-meta.ts +++ b/src/codecs/rotate/processor-meta.ts @@ -3,3 +3,10 @@ export interface RotateOptions { } export const defaultOptions: RotateOptions = { rotate: 0 }; + +export interface RotateModuleInstance { + exports: { + memory: WebAssembly.Memory; + rotate(width: number, height: number, rotate: 0 | 90 | 180 | 270): void; + }; +} diff --git a/src/codecs/rotate/processor.ts b/src/codecs/rotate/processor.ts index da25e2ba..6aa80081 100644 --- a/src/codecs/rotate/processor.ts +++ b/src/codecs/rotate/processor.ts @@ -1,73 +1,26 @@ -import { RotateOptions } from './processor-meta'; +import wasmUrl from '../../../codecs/rotate/rotate.wasm'; +import { RotateOptions, RotateModuleInstance } from './processor-meta'; -export function rotate(data: ImageData, opts: RotateOptions): ImageData { - const { rotate } = opts; - const flipDimensions = rotate % 180 !== 0; - const { width: inputWidth, height: inputHeight } = data; - const outputWidth = flipDimensions ? inputHeight : inputWidth; - const outputHeight = flipDimensions ? inputWidth : inputHeight; - const out = new ImageData(outputWidth, outputHeight); - let i = 0; +export async function rotate( + data: ImageData, + opts: RotateOptions, +): Promise { + const flipDimensions = opts.rotate % 180 !== 0; + // Number of wasm memory pages (á 64KiB) needed to store the image twice. + const bytesPerImage = data.width * data.height * 4; + const numPagesNeeded = Math.ceil(bytesPerImage * 2 / (64 * 1024)); + const { instance } = (await (WebAssembly as any).instantiateStreaming( + fetch(wasmUrl), + )) as { instance: RotateModuleInstance }; - // In the straight-copy case, d1 is x, d2 is y. - // x starts at 0 and increases. - // y starts at 0 and increases. - let d1Start = 0; - let d1Limit = inputWidth; - let d1Advance = 1; - let d1Multiplier = 1; - let d2Start = 0; - let d2Limit = inputHeight; - let d2Advance = 1; - let d2Multiplier = inputWidth; + instance.exports.memory.grow(numPagesNeeded); + const view = new Uint8ClampedArray(instance.exports.memory.buffer); + view.set(data.data); - if (rotate === 90) { - // d1 is y, d2 is x. - // y starts at its max value and decreases. - // x starts at 0 and increases. - d1Start = inputHeight - 1; - d1Limit = inputHeight; - d1Advance = -1; - d1Multiplier = inputWidth; - d2Start = 0; - d2Limit = inputWidth; - d2Advance = 1; - d2Multiplier = 1; - } else if (rotate === 180) { - // d1 is x, d2 is y. - // x starts at its max and decreases. - // y starts at its max and decreases. - d1Start = inputWidth - 1; - d1Limit = inputWidth; - d1Advance = -1; - d1Multiplier = 1; - d2Start = inputHeight - 1; - d2Limit = inputHeight; - d2Advance = -1; - d2Multiplier = inputWidth; - } else if (rotate === 270) { - // d1 is y, d2 is x. - // y starts at 0 and increases. - // x starts at its max and decreases. - d1Start = 0; - d1Limit = inputHeight; - d1Advance = 1; - d1Multiplier = inputWidth; - d2Start = inputWidth - 1; - d2Limit = inputWidth; - d2Advance = -1; - d2Multiplier = 1; - } - - const inB = new Uint32Array(data.data.buffer); - const outB = new Uint32Array(out.data.buffer); - for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) { - for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) { - const start = ((d1 * d1Multiplier) + (d2 * d2Multiplier)); - outB[i] = inB[start]; - i += 1; - } - } - - return out; + instance.exports.rotate(data.width, data.height, opts.rotate); + return new ImageData( + view.slice(bytesPerImage, bytesPerImage * 2), + flipDimensions ? data.height : data.width, + flipDimensions ? data.width : data.height, + ); } From ef3faa58bc7a4c60e051ed8e77a055d0a49b2b22 Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 11 Feb 2019 16:14:37 +0000 Subject: [PATCH 2/3] Reuse rotate instance and calculate pages correctly --- src/codecs/rotate/processor.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/codecs/rotate/processor.ts b/src/codecs/rotate/processor.ts index 6aa80081..0f795e51 100644 --- a/src/codecs/rotate/processor.ts +++ b/src/codecs/rotate/processor.ts @@ -1,23 +1,30 @@ import wasmUrl from '../../../codecs/rotate/rotate.wasm'; import { RotateOptions, RotateModuleInstance } from './processor-meta'; +const instancePromise = (WebAssembly as any).instantiateStreaming(fetch(wasmUrl)); + export async function rotate( data: ImageData, opts: RotateOptions, ): Promise { - const flipDimensions = opts.rotate % 180 !== 0; + const { instance } = (await instancePromise) as {instance: RotateModuleInstance}; + // Number of wasm memory pages (á 64KiB) needed to store the image twice. const bytesPerImage = data.width * data.height * 4; - const numPagesNeeded = Math.ceil(bytesPerImage * 2 / (64 * 1024)); - const { instance } = (await (WebAssembly as any).instantiateStreaming( - fetch(wasmUrl), - )) as { instance: RotateModuleInstance }; + const numPagesNeeded = Math.ceil(bytesPerImage * 2 / (64 * 1024) + 4); + // Only count full pages, just to be safe. + const numPagesAvailable = Math.floor(instance.exports.memory.buffer.byteLength / (64 * 1024)); + const additionalPagesToAllocate = numPagesNeeded - numPagesAvailable; - instance.exports.memory.grow(numPagesNeeded); + if (additionalPagesToAllocate > 0) { + instance.exports.memory.grow(additionalPagesToAllocate); + } const view = new Uint8ClampedArray(instance.exports.memory.buffer); view.set(data.data); instance.exports.rotate(data.width, data.height, opts.rotate); + + const flipDimensions = opts.rotate % 180 !== 0; return new ImageData( view.slice(bytesPerImage, bytesPerImage * 2), flipDimensions ? data.height : data.width, From 9a352245350babc5178f62ccff0764b5158d0dee Mon Sep 17 00:00:00 2001 From: Surma Date: Mon, 11 Feb 2019 16:21:42 +0000 Subject: [PATCH 3/3] Update wasm build --- codecs/rotate/build.sh | 2 ++ codecs/rotate/rotate.wasm | Bin 3139 -> 3128 bytes 2 files changed, 2 insertions(+) diff --git a/codecs/rotate/build.sh b/codecs/rotate/build.sh index 021473ff..61c11a30 100755 --- a/codecs/rotate/build.sh +++ b/codecs/rotate/build.sh @@ -20,5 +20,7 @@ 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-rotate .\`" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" diff --git a/codecs/rotate/rotate.wasm b/codecs/rotate/rotate.wasm index 0bcab5e9ea22f17c0072ddb088b83422cb0071a7..1cba4adcf3876080f85f4b157430c4c0d21ac8c0 100755 GIT binary patch delta 118 zcmX>su|s0Q3dYSFSMFe9e6%@EEOQTfkA+0Hp#VL;wH) delta 199 zcmdlXaadx)3dVgKSMFe9e6Tr*nUm30iB*BY(ICr;fq{pSTY<@uGfRQlu`WxAISVYq zSmL;^;XngJmOcZ6H3I{;0;40NV-HBgkwJlV@+uZ-eqIKr9q<0VoO60%`xgf8shcme z_%f