Skip to content

Commit

Permalink
Add Hq{2,3,4}x (#624)
Browse files Browse the repository at this point in the history
* Scaling works on native

* It works in wasm

* Integrate with UI

* Remove benchmark

* Integrate Hqx into Resizer module

* Link against repo for hqx

* Remove unused defaultOpts

* Re-add test file

* Adding size dropdown

* Chrome: go and sit on the naughty step

* Better docs

* Review

* Add link to crbug

* Update src/codecs/processor-worker/index.ts

Co-Authored-By: Jake Archibald <jaffathecake@gmail.com>

* Terminate worker inbetween resize jobs
  • Loading branch information
surma authored and jakearchibald committed Jun 18, 2019
1 parent 24254df commit 66feffc
Show file tree
Hide file tree
Showing 25 changed files with 539 additions and 23 deletions.
5 changes: 5 additions & 0 deletions codecs/hqx/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/*.rs.bk
target
Cargo.lock
bin/
pkg/README.md
37 changes: 37 additions & 0 deletions codecs/hqx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "squooshhqx"
version = "0.1.0"
authors = ["Surma <surma@surma.link>"]

[lib]
crate-type = ["cdylib"]

[features]
default = ["console_error_panic_hook", "wee_alloc"]

[dependencies]
cfg-if = "0.1.2"
wasm-bindgen = "0.2.38"
# lazy_static = "1.0.0"
hqx = {git = "https://github.com/CryZe/wasmboy-rs", tag="v0.1.2"}

# 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
18 changes: 18 additions & 0 deletions codecs/hqx/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions codecs/hqx/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/bash

set -e

echo "============================================="
echo "Compiling wasm"
echo "============================================="
(
rustup run nightly \
wasm-pack build --target no-modules
wasm-strip pkg/squooshhqx_bg.wasm
rm pkg/.gitignore
)
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-hqx .\`"
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
24 changes: 24 additions & 0 deletions codecs/hqx/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<script src ="./pkg/squooshhqx.js"></script>
<script type="module">
async function run() {
await wasm_bindgen("./pkg/squooshhqx_bg.wasm");
const bitmap = await createImageBitmap(await fetch("https://i.imgur.com/MNDnBSc.png").then(r => r.blob()));
const canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, 0, 0);
const imgdata = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
const factor = 4;
const r = wasm_bindgen.resize(new Uint32Array(imgdata.data.buffer), bitmap.width, bitmap.height, factor);

canvas.width = bitmap.width * factor;
canvas.height = bitmap.height * factor;
const output = new ImageData(new Uint8ClampedArray(r.buffer), canvas.width, canvas.height);
ctx.putImageData(output, 0, 0);
canvas.style = `width: ${canvas.width}px; height: ${canvas.height}px; image-rendering: pixelated;`;
document.body.append(canvas);
}
run();
</script>
4 changes: 4 additions & 0 deletions codecs/hqx/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions codecs/hqx/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "hqx",
"scripts": {
"build:image": "docker build -t squoosh-hqx .",
"build": "docker run --rm -v $(pwd):/src squoosh-hqx ./build.sh"
}
}
14 changes: 14 additions & 0 deletions codecs/hqx/pkg/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "squooshhqx",
"collaborators": [
"Surma <surma@surma.link>"
],
"version": "0.1.0",
"files": [
"squooshhqx_bg.wasm",
"squooshhqx.js",
"squooshhqx.d.ts"
],
"browser": "squooshhqx.js",
"types": "squooshhqx.d.ts"
}
20 changes: 20 additions & 0 deletions codecs/hqx/pkg/squooshhqx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* tslint:disable */
/**
* @param {Uint32Array} input_image
* @param {number} input_width
* @param {number} input_height
* @param {number} factor
* @returns {Uint32Array}
*/
export function resize(input_image: Uint32Array, input_width: number, input_height: number, factor: number): Uint32Array;

/**
* If `module_or_path` is {RequestInfo}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {RequestInfo | BufferSource | WebAssembly.Module} module_or_path
*
* @returns {Promise<any>}
*/
export default function init (module_or_path: RequestInfo | BufferSource | WebAssembly.Module): Promise<any>;

97 changes: 97 additions & 0 deletions codecs/hqx/pkg/squooshhqx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
(function() {
const __exports = {};
let wasm;

let cachegetUint32Memory = null;
function getUint32Memory() {
if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
}
return cachegetUint32Memory;
}

let WASM_VECTOR_LEN = 0;

function passArray32ToWasm(arg) {
const ptr = wasm.__wbindgen_malloc(arg.length * 4);
getUint32Memory().set(arg, ptr / 4);
WASM_VECTOR_LEN = arg.length;
return ptr;
}

function getArrayU32FromWasm(ptr, len) {
return getUint32Memory().subarray(ptr / 4, ptr / 4 + len);
}

let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
if (cachedGlobalArgumentPtr === null) {
cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
}
return cachedGlobalArgumentPtr;
}
/**
* @param {Uint32Array} input_image
* @param {number} input_width
* @param {number} input_height
* @param {number} factor
* @returns {Uint32Array}
*/
__exports.resize = function(input_image, input_width, input_height, factor) {
const ptr0 = passArray32ToWasm(input_image);
const len0 = WASM_VECTOR_LEN;
const retptr = globalArgumentPtr();
wasm.resize(retptr, ptr0, len0, input_width, input_height, factor);
const mem = getUint32Memory();
const rustptr = mem[retptr / 4];
const rustlen = mem[retptr / 4 + 1];

const realRet = getArrayU32FromWasm(rustptr, rustlen).slice();
wasm.__wbindgen_free(rustptr, rustlen * 4);
return realRet;

};

function init(module) {

let result;
const imports = {};

if (module instanceof URL || typeof module === 'string' || module instanceof Request) {

const response = fetch(module);
if (typeof WebAssembly.instantiateStreaming === 'function') {
result = WebAssembly.instantiateStreaming(response, 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 response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
});
} else {
result = response
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
}
} else {

result = WebAssembly.instantiate(module, imports)
.then(result => {
if (result instanceof WebAssembly.Instance) {
return { instance: result, module };
} else {
return result;
}
});
}
return result.then(({instance, module}) => {
wasm = instance.exports;
init.__wbindgen_wasm_module = module;

return wasm;
});
}

self.wasm_bindgen = Object.assign(init, __exports);

})();
6 changes: 6 additions & 0 deletions codecs/hqx/pkg/squooshhqx_bg.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* tslint:disable */
export const memory: WebAssembly.Memory;
export function resize(a: number, b: number, c: number, d: number, e: number, f: number): void;
export function __wbindgen_global_argument_ptr(): number;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_free(a: number, b: number): void;
Binary file added codecs/hqx/pkg/squooshhqx_bg.wasm
Binary file not shown.
55 changes: 55 additions & 0 deletions codecs/hqx/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
extern crate cfg_if;
extern crate hqx;
extern crate wasm_bindgen;

mod utils;

use cfg_if::cfg_if;
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<u32>,
input_width: usize,
input_height: usize,
factor: usize,
) -> Vec<u32> {
let num_output_pixels = input_width * input_height * factor * factor;
let mut output_image = Vec::<u32>::with_capacity(num_output_pixels * 4);
output_image.resize(num_output_pixels, 0);

match factor {
2 => hqx::hq2x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
3 => hqx::hq3x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
4 => hqx::hq4x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
_ => unreachable!(),
};

return output_image;
}
17 changes: 17 additions & 0 deletions codecs/hqx/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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() {}
}
}
3 changes: 3 additions & 0 deletions src/codecs/hqx/processor-meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface HqxOptions {
factor: 2 | 3 | 4;
}
32 changes: 32 additions & 0 deletions src/codecs/hqx/processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import wasmUrl from '../../../codecs/hqx/pkg/squooshhqx_bg.wasm';
import '../../../codecs/hqx/pkg/squooshhqx';
import { HqxOptions } from './processor-meta';

interface WasmBindgenExports {
resize: typeof import('../../../codecs/hqx/pkg/squooshhqx').resize;
}

type WasmBindgen = ((url: string) => Promise<void>) & WasmBindgenExports;

declare var wasm_bindgen: WasmBindgen;

const ready = wasm_bindgen(wasmUrl);

export async function hqx(
data: ImageData,
opts: HqxOptions,
): Promise<ImageData> {
const input = data;
await ready;
const result = wasm_bindgen.resize(
new Uint32Array(input.data.buffer),
input.width,
input.height,
opts.factor,
);
return new ImageData(
new Uint8ClampedArray(result.buffer),
data.width * opts.factor,
data.height * opts.factor,
);
}
Loading

0 comments on commit 66feffc

Please sign in to comment.