Compare commits

..

4 Commits

Author SHA1 Message Date
Surma
b6f69c57cf moar stuff 2019-02-07 17:35:48 +00:00
Surma
9e19c36b42 Add Xargo and config and stuff 2019-02-07 00:29:11 +00:00
Surma
cf45520be3 Cleanup and add pkg 2019-02-06 10:25:56 +00:00
Surma
8faf8a5b48 First attempt at Oxipng 2019-02-05 19:40:22 +00:00
107 changed files with 8861 additions and 6867 deletions

1
.nvmrc
View File

@@ -1 +0,0 @@
10.16.2

View File

@@ -1,4 +1,7 @@
language: node_js language: node_js
node_js:
- node
- 10
- 8
cache: npm cache: npm
script: npm run build script: npm run build
after_success: npm run sizereport

View File

@@ -1,12 +0,0 @@
FROM rust
RUN rustup target add wasm32-unknown-unknown
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
RUN mkdir /opt/binaryen && \
curl -L https://github.com/WebAssembly/binaryen/releases/download/1.38.32/binaryen-1.38.32-x86-linux.tar.gz | tar -xzf - -C /opt/binaryen --strip 1
RUN mkdir /opt/wabt && \
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
ENV PATH="/opt/binaryen:/opt/wabt:${PATH}"
WORKDIR /src

View File

@@ -1,25 +0,0 @@
#!/bin/bash
set -e
echo "============================================="
echo "Compiling wasm"
echo "============================================="
(
wasm-pack build
wasm-strip pkg/squooshhqx_bg.wasm
echo "Optimising Wasm so it doesn't break Chrome (this takes like 10-15mins. get a cup of tea)"
echo "Once https://crbug.com/974804 is fixed, we can remove this step"
wasm-opt -Os --no-validation -o pkg/squooshhqx_bg.wasm 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 "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

View File

@@ -1,4 +0,0 @@
{
"name": "hqx",
"lockfileVersion": 1
}

View File

@@ -1,7 +0,0 @@
{
"name": "hqx",
"scripts": {
"build:image": "docker build -t squoosh-hqx .",
"build": "docker run --rm -v $(pwd):/src squoosh-hqx ./build.sh"
}
}

View File

@@ -1,15 +0,0 @@
{
"name": "squooshhqx",
"collaborators": [
"Surma <surma@surma.link>"
],
"version": "0.1.0",
"files": [
"squooshhqx_bg.wasm",
"squooshhqx.js",
"squooshhqx.d.ts"
],
"module": "squooshhqx.js",
"types": "squooshhqx.d.ts",
"sideEffects": "false"
}

View File

@@ -1,9 +0,0 @@
/* 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;

View File

@@ -1,46 +0,0 @@
import * as wasm from './squooshhqx_bg.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;
}
let cachegetInt32Memory = null;
function getInt32Memory() {
if (cachegetInt32Memory === null || cachegetInt32Memory.buffer !== wasm.memory.buffer) {
cachegetInt32Memory = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory;
}
function getArrayU32FromWasm(ptr, len) {
return getUint32Memory().subarray(ptr / 4, ptr / 4 + len);
}
/**
* @param {Uint32Array} input_image
* @param {number} input_width
* @param {number} input_height
* @param {number} factor
* @returns {Uint32Array}
*/
export function resize(input_image, input_width, input_height, factor) {
const retptr = 8;
const ret = wasm.resize(retptr, passArray32ToWasm(input_image), WASM_VECTOR_LEN, input_width, input_height, factor);
const memi32 = getInt32Memory();
const v0 = getArrayU32FromWasm(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1]).slice();
wasm.__wbindgen_free(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1] * 4);
return v0;
}

View File

@@ -1,5 +0,0 @@
/* tslint:disable */
export const memory: WebAssembly.Memory;
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): void;

Binary file not shown.

View File

@@ -1,55 +0,0 @@
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;
}

View File

@@ -4,40 +4,84 @@ set -e
export OPTIMIZE="-Os" export OPTIMIZE="-Os"
export PREFIX="/src/build" export PREFIX="/src/build"
export CFLAGS="${OPTIMIZE} -I${PREFIX}/include/"
export CPPFLAGS="${OPTIMIZE} -I${PREFIX}/include/"
export LDFLAGS="${OPTIMIZE} -L${PREFIX}/lib/"
apt-get update
apt-get install -qqy autoconf libtool
echo "============================================="
echo "Compiling zlib"
echo "============================================="
test -n "$SKIP_ZLIB" || (
cd node_modules/zlib
emconfigure ./configure --prefix=${PREFIX}/
emmake make
emmake make install
)
echo "============================================="
echo "Compiling zlib done"
echo "============================================="
echo "============================================="
echo "Compiling libpng"
echo "============================================="
test -n "$SKIP_LIBPNG" || (
cd node_modules/libpng
autoreconf -i
emconfigure ./configure --with-zlib-prefix=${PREFIX}/ --prefix=${PREFIX}/
emmake make
emmake make install
)
echo "============================================="
echo "Compiling libpng done"
echo "============================================="
echo "=============================================" echo "============================================="
echo "Compiling optipng" echo "Compiling optipng"
echo "=============================================" echo "============================================="
( (
cd node_modules/optipng emcc \
CFLAGS="${OPTIMIZE} -Isrc/zlib" emconfigure ./configure --prefix=${PREFIX} ${OPTIMIZE} \
emmake make -Wno-implicit-function-declaration \
emmake make install -I ${PREFIX}/include \
mkdir -p ${PREFIX}/lib -I node_modules/optipng/src/opngreduc \
mv ${PREFIX}/bin/optipng ${PREFIX}/lib/liboptipng.so -I node_modules/optipng/src/pngxtern \
-I node_modules/optipng/src/cexcept \
-I node_modules/optipng/src/gifread \
-I node_modules/optipng/src/pnmio \
-I node_modules/optipng/src/minitiff \
--std=c99 -c \
node_modules/optipng/src/opngreduc/*.c \
node_modules/optipng/src/pngxtern/*.c \
node_modules/optipng/src/gifread/*.c \
node_modules/optipng/src/minitiff/*.c \
node_modules/optipng/src/pnmio/*.c \
node_modules/optipng/src/optipng/*.c
emcc \
--bind \
${OPTIMIZE} \
-s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME="optipng"' \
-I ${PREFIX}/include \
-I node_modules/optipng/src/opngreduc \
-I node_modules/optipng/src/pngxtern \
-I node_modules/optipng/src/cexcept \
-I node_modules/optipng/src/gifread \
-I node_modules/optipng/src/pnmio \
-I node_modules/optipng/src/minitiff \
-o "optipng.js" \
--std=c++11 \
optipng.cpp \
*.o \
${PREFIX}/lib/libz.so ${PREFIX}/lib/libpng.a
) )
echo "=============================================" echo "============================================="
echo "Compiling optipng done" echo "Compiling optipng done"
echo "=============================================" echo "============================================="
echo "============================================="
echo "Compiling optipng wrapper"
echo "============================================="
(
emcc \
--bind \
${OPTIMIZE} \
-s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME="optipng"' \
-o "optipng.js" \
--std=c++11 \
optipng.cpp \
${PREFIX}/lib/liboptipng.so
)
echo "============================================="
echo "Compiling optipng wrapper done"
echo "============================================="
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
echo "Did you update your docker image?" echo "Did you update your docker image?"
echo "Run \`docker pull trzeci/emscripten-upstream\`" echo "Run \`docker pull trzeci/emscripten\`"
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
{ {
"name": "optipng", "name": "optipng",
"scripts": { "scripts": {
"install": "tar-dependency install", "install": "tar-dependency install && napa",
"build": "npm run build:wasm", "build": "npm run build:wasm",
"build:wasm": "docker run --rm -v $(pwd):/src trzeci/emscripten-upstream ./build.sh" "build:wasm": "docker run --rm -v $(pwd):/src -e SKIP_ZLIB=\"${SKIP_ZLIB}\" -e SKIP_LIBPNG=\"${SKIP_LIBPNG}\" trzeci/emscripten ./build.sh"
}, },
"tarDependencies": { "tarDependencies": {
"node_modules/optipng": { "node_modules/optipng": {
@@ -11,7 +11,12 @@
"strip": 1 "strip": 1
} }
}, },
"napa": {
"libpng": "emscripten-ports/libpng",
"zlib": "emscripten-ports/zlib"
},
"dependencies": { "dependencies": {
"napa": "3.0.0",
"tar-dependency": "0.0.3" "tar-dependency": "0.0.3"
} }
} }

0
codecs/oxipng/.cargo-ok Normal file
View File

View File

@@ -1,5 +1,5 @@
/target
**/*.rs.bk **/*.rs.bk
target
Cargo.lock Cargo.lock
bin/ bin/
pkg/README.md wasm-pack.log

View File

@@ -1,19 +1,19 @@
[package] [package]
name = "resize" name = "test"
version = "0.1.0" version = "0.1.0"
authors = ["Surma <surma@surma.link>"] authors = ["Surma <surma@surma.link>"]
[lib] [lib]
#crate-type = ["cdylib", "rlib"] path = "lib.rs"
crate-type = ["cdylib"] crate-type = ["cdylib", "rlib"]
[features] [features]
default = ["console_error_panic_hook", "wee_alloc"] default = ["console_error_panic_hook"]
[dependencies] [dependencies]
cfg-if = "0.1.2" cfg-if = "0.1.2"
wasm-bindgen = "0.2.38" wasm-bindgen = "0.2"
resize = "0.3.0" oxipng = "2.2.0"
# 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
@@ -34,4 +34,3 @@ wasm-bindgen-test = "0.2"
[profile.release] [profile.release]
# Tell `rustc` to optimize for small code size. # Tell `rustc` to optimize for small code size.
opt-level = "s" opt-level = "s"
lto = true

52
codecs/oxipng/README.md Normal file
View File

@@ -0,0 +1,52 @@
# 🦀🕸️ `wasm-pack-template`
A template for kick starting a Rust and WebAssembly project using
[`wasm-pack`](https://github.com/rustwasm/wasm-pack).
This template is designed for compiling Rust libraries into WebAssembly and
publishing the resulting package to NPM.
* Want to use the published NPM package in a Website? [Check out
`create-wasm-app`.](https://github.com/rustwasm/create-wasm-app)
* Want to make a monorepo-style Website without publishing to NPM? Check out
[`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
and/or
[`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
## 🔋 Batteries Included
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
between WebAssembly and JavaScript.
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
for logging panic messages to the developer console.
* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
for small code size.
## 🚴 Usage
### 🐑 Use `cargo generate` to Clone this Template
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```
wasm-pack test --headless --firefox
```
### 🎁 Publish to NPM with `wasm-pack publish`
```
wasm-pack publish
```

5
codecs/oxipng/Xargo.toml Normal file
View File

@@ -0,0 +1,5 @@
[target.wasm32-unknown-unknown.dependencies]
time = {}
[target.wasm32-unknown-unknown.dependencies.std]
features = ["wasm_syscall"]

View File

@@ -0,0 +1,52 @@
# 🦀🕸️ `wasm-pack-template`
A template for kick starting a Rust and WebAssembly project using
[`wasm-pack`](https://github.com/rustwasm/wasm-pack).
This template is designed for compiling Rust libraries into WebAssembly and
publishing the resulting package to NPM.
* Want to use the published NPM package in a Website? [Check out
`create-wasm-app`.](https://github.com/rustwasm/create-wasm-app)
* Want to make a monorepo-style Website without publishing to NPM? Check out
[`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
and/or
[`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
## 🔋 Batteries Included
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
between WebAssembly and JavaScript.
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
for logging panic messages to the developer console.
* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
for small code size.
## 🚴 Usage
### 🐑 Use `cargo generate` to Clone this Template
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```
wasm-pack test --headless --firefox
```
### 🎁 Publish to NPM with `wasm-pack publish`
```
wasm-pack publish
```

BIN
codecs/oxipng/pkg/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

14
codecs/oxipng/pkg/lol.js Normal file
View File

@@ -0,0 +1,14 @@
const oxipng = require("./oxipng_wasm");
const repl = require("repl");
const fs = require("fs");
async function init() {
// const img = fs.readFileSync("img.png")
// const output = oxipng.compress(img, 0);
// fs.writeFileSync("output.png", output);
console.log(">>>", oxipng.doit());
const r = repl.start("node> ");
r.context.i = oxipng;
}
init();

3
codecs/oxipng/pkg/oxipng.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/* tslint:disable */
export function compress(arg0: Uint8Array, arg1: number): Uint8Array;

View File

@@ -0,0 +1,73 @@
/* tslint:disable */
var wasm;
const TextDecoder = require('util').TextDecoder;
let cachedTextDecoder = new TextDecoder('utf-8');
let cachegetUint8Memory = null;
function getUint8Memory() {
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory;
}
function getStringFromWasm(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}
module.exports.__wbg_log_64e6f53d8e6d5db5 = function(arg0, arg1) {
let varg0 = getStringFromWasm(arg0, arg1);
console.log(varg0);
};
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
* @returns {Uint8Array}
*/
module.exports.compress = function(arg0, arg1) {
const ptr0 = passArray8ToWasm(arg0);
const len0 = WASM_VECTOR_LEN;
const retptr = globalArgumentPtr();
wasm.compress(retptr, ptr0, len0, arg1);
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;
};
wasm = require('./oxipng_bg');

View File

@@ -1,5 +1,6 @@
/* tslint:disable */ /* tslint:disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export function __wbindgen_global_argument_ptr(): number;
export function compress(a: number, b: number, c: number, d: number): void;
export function __wbindgen_malloc(a: number): number; export function __wbindgen_malloc(a: number): number;
export function __wbindgen_free(a: number, b: number): void; 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, i: number, j: number): void;

View File

@@ -0,0 +1,9 @@
const path = require('path').join(__dirname, 'oxipng_bg.wasm');
const bytes = require('fs').readFileSync(path);
let imports = {};
imports['./oxipng'] = require('./oxipng');
const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
module.exports = wasmInstance.exports;

Binary file not shown.

3
codecs/oxipng/pkg/oxipng_manual.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/* tslint:disable */
export function compress(arg0: Uint8Array, arg1: number): Promise<Uint8Array>;

View File

@@ -0,0 +1,77 @@
/* tslint:disable */
import wasmUrl from './oxipng_bg.wasm';
let wasm;
const instancePromise = WebAssembly.instantiateStreaming(fetch(wasmUrl), {
"./oxipng": {__wbg_log_64e6f53d8e6d5db5}
});
let cachedTextDecoder = new TextDecoder('utf-8');
let cachegetUint8Memory = null;
function getUint8Memory() {
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory;
}
function getStringFromWasm(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
}
export function __wbg_log_64e6f53d8e6d5db5(arg0, arg1) {
let varg0 = getStringFromWasm(arg0, arg1);
console.log(varg0);
}
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
* @returns {Uint8Array}
*/
export async function compress(arg0, arg1) {
wasm = (await instancePromise).instance.exports;
debugger;
const ptr0 = passArray8ToWasm(arg0);
const len0 = WASM_VECTOR_LEN;
const retptr = globalArgumentPtr();
wasm.compress(retptr, ptr0, len0, arg1);
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;
}

3
codecs/oxipng/pkg/oxipng_wasm.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/* tslint:disable */
export function doit(): number;

View File

@@ -0,0 +1,11 @@
/* tslint:disable */
var wasm;
/**
* @returns {number}
*/
module.exports.doit = function() {
return wasm.doit();
};
wasm = require('./oxipng_wasm_bg');

3
codecs/oxipng/pkg/oxipng_wasm_bg.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/* tslint:disable */
export const memory: WebAssembly.Memory;
export function doit(): number;

View File

@@ -0,0 +1,8 @@
const path = require('path').join(__dirname, 'oxipng_wasm_bg.wasm');
const bytes = require('fs').readFileSync(path);
let imports = {};
const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
module.exports = wasmInstance.exports;

Binary file not shown.

View File

@@ -0,0 +1,15 @@
{
"name": "oxipng-wasm",
"collaborators": [
"Surma <surma@surma.link>"
],
"version": "0.1.0",
"files": [
"oxipng_wasm_bg.wasm",
"oxipng_wasm.js",
"oxipng_wasm_bg.js",
"oxipng_wasm.d.ts"
],
"main": "oxipng_wasm.js",
"types": "oxipng_wasm.d.ts"
}

View File

42
codecs/oxipng/src/lib.rs Normal file
View File

@@ -0,0 +1,42 @@
extern crate cfg_if;
extern crate wasm_bindgen;
// extern crate oxipng;
mod utils;
use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;
use std::time::{Instant};
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]
// extern {
// #[wasm_bindgen(js_namespace = console)]
// fn log(s: &str);
// }
// #[wasm_bindgen]
// pub fn compress(img: Vec<u8>, level: u8) -> Vec<u8> {
// log(&format!("len: {}, level: {}", img.len(), level));
// let mut options = oxipng::Options::from_preset(level);
// options.threads = 0;
// let result = oxipng::optimize_from_memory(img.as_slice(), &options);
// match result {
// Ok(v) => v,
// Err(e) => e.to_string().as_bytes().to_vec()
// }
// }
#[wasm_bindgen]
pub fn doit() -> u32 {
let start = Instant::now();
start.elapsed().as_secs() as u32
}

1
codecs/oxipng/tmp/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target

View File

@@ -1,19 +1,17 @@
[package] [package]
name = "squooshhqx" name = "loltest"
version = "0.1.0" version = "0.1.0"
authors = ["Surma <surma@surma.link>"] authors = ["Surma <surma@surma.link>"]
[lib] [lib]
crate-type = ["cdylib"] path = "lib.rs"
crate-type = ["cdylib", "rlib"]
[features] [features]
default = ["console_error_panic_hook", "wee_alloc"] default = ["console_error_panic_hook"]
[dependencies] [dependencies]
cfg-if = "0.1.2" wasm-bindgen = "0.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 # 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
@@ -21,17 +19,9 @@ hqx = {git = "https://github.com/CryZe/wasmboy-rs", tag="v0.1.2"}
# code size when deploying. # code size when deploying.
console_error_panic_hook = { version = "0.1.1", optional = true } 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] [dev-dependencies]
wasm-bindgen-test = "0.2" wasm-bindgen-test = "0.2"
[profile.release] [profile.release]
# Tell `rustc` to optimize for small code size. # Tell `rustc` to optimize for small code size.
opt-level = "s" opt-level = "s"
lto = true

View File

@@ -0,0 +1,2 @@
[dependencies.std]
features = ["wasm_syscall"]

View File

@@ -0,0 +1,20 @@
Im trying to activate [the `wasm_syscall` feature][1] in Rusts stdlib for WebAssembly.
Here is my `Cargo.toml` and my `Xargo.toml`. But even with this setup the generated wasm file is still hard-coded to panic.
**HELP?**
My current command to compile is:
```
xargo build --target wasm32-unknown-unknown --release
```
If you have [`wasm2wat`][2] installed, you can verify the generate code via
```
wasm2wat target/wasm32-unknown-unknown/release/loltest.wasm | grep -A5 perform::
```
[1]: https://github.com/rust-lang/rust/blob/b139669f374eb5024a50eb13f116ff763b1c5935/src/libstd/sys/wasm/mod.rs#L309
[2]: https://github.com/WebAssembly/wabt

14
codecs/oxipng/tmp/lib.rs Normal file
View File

@@ -0,0 +1,14 @@
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
use std::thread::spawn;
#[wasm_bindgen]
pub fn doit() {
// let child = spawn(move || -> u32 {
// 5
// });
// let result = child.join().unwrap();
let result = spawn();
println!("Result: {}", result);
}

47
codecs/oxipng/tmp/lol.js Normal file
View File

@@ -0,0 +1,47 @@
// const oxipng = require("./oxipng_wasm");
const repl = require("repl");
const fs = require("fs");
const dec = new TextDecoder();
let buffer = '';
async function init() {
const { instance } = await WebAssembly.instantiate(
fs.readFileSync("./target/wasm32-unknown-unknown/release/loltest.wasm"),
{
__wbindgen_placeholder__: {
__wbindgen_describe(v) {
console.log(`__wbindgen_desribe(${v})`);
}
},
env: {
// See https://github.com/rust-lang/rust/blob/master/src/libstd/sys/wasm/mod.rs
rust_wasm_syscall(syscall, ptr) {
switch(syscall) {
case 1: // Write
const [fd, dataPtr, len] = new Uint32Array(instance.exports.memory.buffer, ptr, 3 * 4);
const fragment = new Uint8Array(instance.exports.memory.buffer, dataPtr, len);
buffer += dec.decode(fragment);
const idx = buffer.indexOf('\n');
if(idx !== -1) {
console.log(buffer.slice(0, idx));
buffer = buffer.slice(idx);
}
return 1;
case 6: // Time
return 1;
default:
return 0; // False, unimplemented
}
}
}
}
);
try {
instance.exports.doit();
} catch{}
const r = repl.start("node> ");
r.context.i = instance;
}
init();

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
**/*.rs.bk
target
Cargo.lock
bin/
pkg/README.md
lut.inc

View File

@@ -1,9 +0,0 @@
FROM rust
RUN rustup target add wasm32-unknown-unknown
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
RUN mkdir /opt/wabt && \
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
ENV PATH="/opt/wabt:${PATH}"
WORKDIR /src

View File

@@ -1,23 +0,0 @@
include!("./src/srgb.rs");
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(())
}

View File

@@ -1,22 +0,0 @@
#!/bin/bash
set -e
echo "============================================="
echo "Compiling wasm"
echo "============================================="
(
wasm-pack build
wasm-strip pkg/resize_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-resize .\`"
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

View File

@@ -1,4 +0,0 @@
{
"name": "resize",
"lockfileVersion": 1
}

View File

@@ -1,7 +0,0 @@
{
"name": "resize",
"scripts": {
"build:image": "docker build -t squoosh-resize .",
"build": "docker run --rm -v $(pwd):/src squoosh-resize ./build.sh"
}
}

View File

@@ -1,15 +0,0 @@
{
"name": "resize",
"collaborators": [
"Surma <surma@surma.link>"
],
"version": "0.1.0",
"files": [
"resize_bg.wasm",
"resize.js",
"resize.d.ts"
],
"module": "resize.js",
"types": "resize.d.ts",
"sideEffects": "false"
}

View File

@@ -1,13 +0,0 @@
/* tslint: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;

View File

@@ -1,50 +0,0 @@
import * as wasm from './resize_bg.wasm';
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;
}
let cachegetInt32Memory = null;
function getInt32Memory() {
if (cachegetInt32Memory === null || cachegetInt32Memory.buffer !== wasm.memory.buffer) {
cachegetInt32Memory = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory;
}
function getArrayU8FromWasm(ptr, len) {
return getUint8Memory().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) {
const retptr = 8;
const ret = wasm.resize(retptr, passArray8ToWasm(input_image), WASM_VECTOR_LEN, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
const memi32 = getInt32Memory();
const v0 = getArrayU8FromWasm(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1]).slice();
wasm.__wbindgen_free(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1] * 1);
return v0;
}

Binary file not shown.

View File

@@ -1,121 +0,0 @@
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::*;
mod srgb;
use srgb::Clamp;
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;
}
}
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)) {
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,
)
} else {
(|v| v as f32, |v| v as u8)
}
}
// If `with_alpha_premultiplication` is true, this function returns a function
// that premultiply the alpha channel with the given channel value and another
// function that reverses that process. If `with_alpha_premultiplication` is
// false, the functions just return the channel value.
fn alpha_multiplier_funcs(
with_alpha_premultiplication: bool,
) -> ((fn(f32, u8) -> u8), (fn(u8, u8) -> 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),
)
} else {
(|v, _a| v as u8, |v, _a| v as f32)
}
}
#[wasm_bindgen]
#[no_mangle]
pub fn resize(
mut input_image: Vec<u8>,
input_width: usize,
input_height: usize,
output_width: usize,
output_height: usize,
typ_idx: usize,
premultiply: bool,
color_space_conversion: bool,
) -> Vec<u8> {
let typ = match typ_idx {
0 => Type::Triangle,
1 => Type::Catrom,
2 => Type::Mitchell,
3 => Type::Lanczos3,
_ => panic!("Nope"),
};
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 (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 resizer = resize::new(
input_width,
input_height,
output_width,
output_height,
RGBA,
typ,
);
let mut output_image = Vec::<u8>::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());
if premultiply || color_space_conversion {
for i in 0..num_output_pixels {
for j in 0..3 {
// We dont 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],
));
}
}
}
return output_image;
}

View File

@@ -1,29 +0,0 @@
pub trait Clamp: std::cmp::PartialOrd + Sized {
fn clamp(self, min: Self, max: Self) -> Self {
if self.lt(&min) {
min
} else if self.gt(&max) {
max
} else {
self
}
}
}
impl Clamp for f32 {}
pub fn srgb_to_linear(v: f32) -> f32 {
if v < 0.04045 {
v / 12.92
} else {
((v + 0.055) / 1.055).powf(2.4).clamp(0.0, 1.0)
}
}
pub fn linear_to_srgb(v: f32) -> f32 {
if v < 0.0031308 {
v * 12.92
} else {
(1.055 * v.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0)
}
}

View File

@@ -1,17 +0,0 @@
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() {}
}
}

View File

@@ -1,2 +0,0 @@
target
Cargo.lock

View File

@@ -1,14 +0,0 @@
[package]
name = "rotate"
version = "0.1.0"
authors = ["Surma <surma@google.com>"]
edition = "2018"
[lib]
name = "rotate"
path = "rotate.rs"
crate-type = ["cdylib", "rlib"]
[profile.release]
lto = true
opt-level = "s"

View File

@@ -1,8 +0,0 @@
FROM rust
RUN rustup target add wasm32-unknown-unknown
RUN mkdir /opt/wabt && \
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
ENV PATH="/opt/wabt:${PATH}"
WORKDIR /src

View File

@@ -1,45 +0,0 @@
// THIS IS NOT A NODE SCRIPT
// This is a d8 script. Please install jsvu[1] and install v8.
// Then run `npm run --silent benchmark`.
// [1]: https://github.com/GoogleChromeLabs/jsvu
async function init() {
// Adjustable constants.
const imageDimensions = 4096;
const iterations = new Array(100);
// Constants. Dont change.
const imageByteSize = imageDimensions * imageDimensions * 4;
const wasmPageSize = 64 * 1024;
const buffer = readbuffer("rotate.wasm");
const { instance } = await WebAssembly.instantiate(buffer);
const pagesAvailable = Math.floor(
instance.exports.memory.buffer.byteLength / wasmPageSize
);
const pagesNeeded = Math.floor((imageByteSize * 2 + 4) / wasmPageSize) + 1;
const additionalPagesNeeded = pagesNeeded - pagesAvailable;
if (additionalPagesNeeded > 0) {
instance.exports.memory.grow(additionalPagesNeeded);
}
[0, 90, 180, 270].forEach(rotation => {
print(`\n${rotation} degrees`);
print(`==============================`);
for (let i = 0; i < 100; i++) {
const start = Date.now();
instance.exports.rotate(imageDimensions, imageDimensions, rotation);
iterations[i] = Date.now() - start;
}
const average = iterations.reduce((sum, c) => sum + c) / iterations.length;
const stddev = Math.sqrt(
iterations
.map(i => Math.pow(i - average, 2))
.reduce((sum, c) => sum + c) / iterations.length
);
print(`n = ${iterations.length}`);
print(`Average: ${average}`);
print(`StdDev: ${stddev}`);
});
}
init().catch(e => console.error(e.stack));

View File

@@ -1,24 +0,0 @@
#!/bin/bash
set -e
echo "============================================="
echo "Compiling wasm"
echo "============================================="
(
cargo build \
--target wasm32-unknown-unknown \
--release
cp target/wasm32-unknown-unknown/release/rotate.wasm .
wasm-strip rotate.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-rotate .\`"
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

View File

@@ -1,11 +0,0 @@
{
"name": "rotate",
"scripts": {
"build:image": "docker build -t squoosh-rotate .",
"build": "docker run --rm -v $(pwd):/src squoosh-rotate ./build.sh",
"benchmark": "echo File size after gzip && npm run benchmark:filesize && echo Optimizing && npm run -s benchmark:optimizing",
"benchmark:baseline": "v8 --liftoff --no-wasm-tier-up --no-opt ./benchmark.js",
"benchmark:optimizing": "v8 --no-liftoff --no-wasm-tier-up ./benchmark.js",
"benchmark:filesize": "cat rotate.wasm | gzip -c9n | wc -c"
}
}

View File

@@ -1,113 +0,0 @@
use std::slice::{from_raw_parts, from_raw_parts_mut};
// This function is taken from Zachary Dremann
// https://github.com/GoogleChromeLabs/squoosh/pull/462
trait HardUnwrap<T> {
fn unwrap_hard(self) -> T;
}
impl<T> HardUnwrap<T> for Option<T> {
#[cfg(not(debug_assertions))]
#[inline]
fn unwrap_hard(self) -> T {
match self {
Some(t) => t,
None => std::process::abort(),
}
}
#[cfg(debug_assertions)]
fn unwrap_hard(self) -> T {
self.unwrap()
}
}
const TILE_SIZE: usize = 16;
fn get_buffers<'a>(width: usize, height: usize) -> (&'a [u32], &'a mut [u32]) {
let num_pixels = width * height;
let in_b: &[u32];
let out_b: &mut [u32];
unsafe {
in_b = from_raw_parts::<u32>(8 as *const u32, num_pixels);
out_b = from_raw_parts_mut::<u32>((num_pixels * 4 + 8) as *mut u32, num_pixels);
}
return (in_b, out_b);
}
#[inline(never)]
fn rotate_0(width: usize, height: usize) {
let (in_b, out_b) = get_buffers(width, height);
for (in_p, out_p) in in_b.iter().zip(out_b.iter_mut()) {
*out_p = *in_p;
}
}
#[inline(never)]
fn rotate_90(width: usize, height: usize) {
let (in_b, out_b) = get_buffers(width, height);
let new_width = height;
let _new_height = width;
for y_start in (0..height).step_by(TILE_SIZE) {
for x_start in (0..width).step_by(TILE_SIZE) {
for y in y_start..(y_start + TILE_SIZE).min(height) {
let in_offset = y * width;
let in_bounds = if x_start + TILE_SIZE < width {
(in_offset + x_start)..(in_offset + x_start + TILE_SIZE)
} else {
(in_offset + x_start)..(in_offset + width)
};
let in_chunk = in_b.get(in_bounds).unwrap_hard();
for (x, in_p) in in_chunk.iter().enumerate() {
let new_x = (new_width - 1) - y;
let new_y = x + x_start;
*out_b.get_mut(new_y * new_width + new_x).unwrap_hard() = *in_p;
}
}
}
}
}
#[inline(never)]
fn rotate_180(width: usize, height: usize) {
let (in_b, out_b) = get_buffers(width, height);
for (in_p, out_p) in in_b.iter().zip(out_b.iter_mut().rev()) {
*out_p = *in_p;
}
}
#[inline(never)]
fn rotate_270(width: usize, height: usize) {
let (in_b, out_b) = get_buffers(width, height);
let new_width = height;
let new_height = width;
for y_start in (0..height).step_by(TILE_SIZE) {
for x_start in (0..width).step_by(TILE_SIZE) {
for y in y_start..(y_start + TILE_SIZE).min(height) {
let in_offset = y * width;
let in_bounds = if x_start + TILE_SIZE < width {
(in_offset + x_start)..(in_offset + x_start + TILE_SIZE)
} else {
(in_offset + x_start)..(in_offset + width)
};
let in_chunk = in_b.get(in_bounds).unwrap_hard();
for (x, in_p) in in_chunk.iter().enumerate() {
let new_x = y;
let new_y = new_height - 1 - (x_start + x);
*out_b.get_mut(new_y * new_width + new_x).unwrap_hard() = *in_p;
}
}
}
}
}
#[no_mangle]
fn rotate(width: usize, height: usize, rotate: usize) {
match rotate {
0 => rotate_0(width, height),
90 => rotate_90(width, height),
180 => rotate_180(width, height),
270 => rotate_270(width, height),
_ => std::process::abort(),
}
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

10756
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
{ {
"private": true, "private": true,
"name": "squoosh", "name": "squoosh",
"version": "1.9.1", "version": "1.3.1",
"license": "apache-2.0", "license": "apache-2.0",
"scripts": { "scripts": {
"start": "webpack-dev-server --host 0.0.0.0 --hot", "start": "webpack-dev-server --host 0.0.0.0 --hot",
"build": "webpack -p", "build": "webpack -p",
"lint": "tslint -c tslint.json -p tsconfig.json -t verbose", "lint": "tslint -c tslint.json -p tsconfig.json -t verbose 'src/**/*.{ts,tsx,js,jsx}'",
"lintfix": "tslint -c tslint.json -p tsconfig.json -t verbose --fix 'src/**/*.{ts,tsx,js,jsx}'", "lintfix": "tslint -c tslint.json -p tsconfig.json -t verbose --fix 'src/**/*.{ts,tsx,js,jsx}'"
"sizereport": "sizereport --config"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
@@ -16,61 +15,55 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@types/node": "10.14.15", "@types/node": "10.12.21",
"@types/pretty-bytes": "5.1.0", "@types/pretty-bytes": "5.1.0",
"@types/webassembly-js-api": "0.0.3", "@types/webassembly-js-api": "0.0.2",
"@webcomponents/custom-elements": "1.2.4", "@webcomponents/custom-elements": "1.2.1",
"@webpack-cli/serve": "0.1.8", "@webpack-cli/serve": "0.1.3",
"assets-webpack-plugin": "3.9.10", "assets-webpack-plugin": "3.9.7",
"chalk": "2.4.2", "chokidar": "2.0.4",
"chokidar": "3.0.2",
"classnames": "2.2.6", "classnames": "2.2.6",
"clean-webpack-plugin": "1.0.1", "clean-webpack-plugin": "1.0.1",
"comlink": "3.1.1", "comlink": "3.1.1",
"copy-webpack-plugin": "5.0.4", "copy-webpack-plugin": "4.6.0",
"critters-webpack-plugin": "2.4.0", "critters-webpack-plugin": "2.2.0",
"css-loader": "1.0.1", "css-loader": "1.0.1",
"ejs": "2.6.2", "ejs": "2.6.1",
"escape-string-regexp": "2.0.0",
"exports-loader": "0.7.0", "exports-loader": "0.7.0",
"file-drop-element": "0.2.0", "file-drop-element": "0.0.9",
"file-loader": "4.2.0", "file-loader": "3.0.1",
"gzip-size": "5.1.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"husky": "3.0.4", "husky": "1.3.1",
"idb-keyval": "3.2.0", "idb-keyval": "3.1.0",
"linkstate": "1.1.1", "linkstate": "1.1.1",
"loader-utils": "1.2.3", "loader-utils": "1.2.0",
"mini-css-extract-plugin": "0.8.0", "mini-css-extract-plugin": "0.5.0",
"minimatch": "3.0.4", "minimatch": "3.0.4",
"node-fetch": "2.6.0", "node-sass": "4.11.0",
"node-sass": "4.12.0",
"optimize-css-assets-webpack-plugin": "5.0.1", "optimize-css-assets-webpack-plugin": "5.0.1",
"pointer-tracker": "2.0.3", "pointer-tracker": "2.0.3",
"preact": "8.4.2", "preact": "8.4.2",
"prerender-loader": "1.3.0", "prerender-loader": "1.2.0",
"pretty-bytes": "5.3.0", "pretty-bytes": "5.1.0",
"progress-bar-webpack-plugin": "1.12.1", "progress-bar-webpack-plugin": "1.12.1",
"raw-loader": "3.1.0", "raw-loader": "1.0.0",
"readdirp": "3.1.2", "sass-loader": "7.1.0",
"sass-loader": "7.3.1", "script-ext-html-webpack-plugin": "2.1.3",
"script-ext-html-webpack-plugin": "2.1.4",
"source-map-loader": "0.2.4", "source-map-loader": "0.2.4",
"style-loader": "1.0.0", "style-loader": "0.23.1",
"terser-webpack-plugin": "1.4.1", "terser-webpack-plugin": "1.2.2",
"travis-size-report": "1.1.0", "ts-loader": "5.3.3",
"ts-loader": "6.0.3", "tslint": "5.12.1",
"tslint": "5.19.0",
"tslint-config-airbnb": "5.11.1", "tslint-config-airbnb": "5.11.1",
"tslint-config-semistandard": "8.0.1", "tslint-config-semistandard": "7.0.0",
"tslint-react": "4.0.0", "tslint-react": "3.6.0",
"typed-css-modules": "0.4.2", "typed-css-modules": "0.3.7",
"typescript": "3.5.3", "typescript": "3.2.4",
"url-loader": "2.1.0", "url-loader": "1.1.2",
"webpack": "4.28.0", "webpack": "4.28.0",
"webpack-bundle-analyzer": "3.4.1", "webpack-bundle-analyzer": "3.0.3",
"webpack-cli": "3.3.4", "webpack-cli": "3.2.3",
"webpack-dev-server": "3.8.0", "webpack-dev-server": "3.1.14",
"worker-plugin": "3.1.0" "worker-plugin": "3.0.0"
} }
} }

View File

@@ -1,14 +0,0 @@
const escapeRE = require("escape-string-regexp");
module.exports = {
repo: "GoogleChromeLabs/squoosh",
path: "build/**/!(*.map)",
branch: "master",
findRenamed(path, newPaths) {
const nameParts = /^(.+\.)[a-f0-9]+(\..+)$/.exec(path);
if (!nameParts) return;
const matchRe = new RegExp(`^${escapeRE(nameParts[1])}[a-f0-9]+${escapeRE(nameParts[2])}$`);
return newPaths.find(newPath => matchRe.test(newPath));
}
};

View File

@@ -1,5 +1,6 @@
import * as identity from './identity/encoder-meta'; import * as identity from './identity/encoder-meta';
import * as optiPNG from './optipng/encoder-meta'; import * as optiPNG from './optipng/encoder-meta';
import * as oxiPNG from './oxipng/encoder-meta';
import * as mozJPEG from './mozjpeg/encoder-meta'; import * as mozJPEG from './mozjpeg/encoder-meta';
import * as webP from './webp/encoder-meta'; import * as webP from './webp/encoder-meta';
import * as browserPNG from './browser-png/encoder-meta'; import * as browserPNG from './browser-png/encoder-meta';
@@ -18,6 +19,7 @@ export interface EncoderSupportMap {
export type EncoderState = export type EncoderState =
identity.EncoderState | identity.EncoderState |
optiPNG.EncoderState | optiPNG.EncoderState |
oxiPNG.EncoderState |
mozJPEG.EncoderState | mozJPEG.EncoderState |
webP.EncoderState | webP.EncoderState |
browserPNG.EncoderState | browserPNG.EncoderState |
@@ -32,6 +34,7 @@ export type EncoderState =
export type EncoderOptions = export type EncoderOptions =
identity.EncodeOptions | identity.EncodeOptions |
optiPNG.EncodeOptions | optiPNG.EncodeOptions |
oxiPNG.EncodeOptions |
mozJPEG.EncodeOptions | mozJPEG.EncodeOptions |
webP.EncodeOptions | webP.EncodeOptions |
browserPNG.EncodeOptions | browserPNG.EncodeOptions |
@@ -48,6 +51,7 @@ export type EncoderType = keyof typeof encoderMap;
export const encoderMap = { export const encoderMap = {
[identity.type]: identity, [identity.type]: identity,
[optiPNG.type]: optiPNG, [optiPNG.type]: optiPNG,
[oxiPNG.type]: oxiPNG,
[mozJPEG.type]: mozJPEG, [mozJPEG.type]: mozJPEG,
[webP.type]: webP, [webP.type]: webP,
[browserPNG.type]: browserPNG, [browserPNG.type]: browserPNG,

View File

@@ -1,3 +0,0 @@
export interface HqxOptions {
factor: 2 | 3 | 4;
}

View File

@@ -1,20 +0,0 @@
import { resize } from '../../../codecs/hqx/pkg';
import { HqxOptions } from './processor-meta';
export async function hqx(
data: ImageData,
opts: HqxOptions,
): Promise<ImageData> {
const input = data;
const result = 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,
);
}

View File

@@ -1,12 +1,12 @@
import imagequant, { QuantizerModule } from '../../../codecs/imagequant/imagequant'; import imagequant, { QuantizerModule } from '../../../codecs/imagequant/imagequant';
import wasmUrl from '../../../codecs/imagequant/imagequant.wasm'; import wasmUrl from '../../../codecs/imagequant/imagequant.wasm';
import { QuantizeOptions } from './processor-meta'; import { QuantizeOptions } from './processor-meta';
import { initEmscriptenModule } from '../util'; import { initWasmModule } from '../util';
let emscriptenModule: Promise<QuantizerModule>; let emscriptenModule: Promise<QuantizerModule>;
export async function process(data: ImageData, opts: QuantizeOptions): Promise<ImageData> { export async function process(data: ImageData, opts: QuantizeOptions): Promise<ImageData> {
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(imagequant, wasmUrl); if (!emscriptenModule) emscriptenModule = initWasmModule(imagequant, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;

View File

@@ -1,12 +1,12 @@
import mozjpeg_enc, { MozJPEGModule } from '../../../codecs/mozjpeg_enc/mozjpeg_enc'; import mozjpeg_enc, { MozJPEGModule } from '../../../codecs/mozjpeg_enc/mozjpeg_enc';
import wasmUrl from '../../../codecs/mozjpeg_enc/mozjpeg_enc.wasm'; import wasmUrl from '../../../codecs/mozjpeg_enc/mozjpeg_enc.wasm';
import { EncodeOptions } from './encoder-meta'; import { EncodeOptions } from './encoder-meta';
import { initEmscriptenModule } from '../util'; import { initWasmModule } from '../util';
let emscriptenModule: Promise<MozJPEGModule>; let emscriptenModule: Promise<MozJPEGModule>;
export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> { export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> {
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(mozjpeg_enc, wasmUrl); if (!emscriptenModule) emscriptenModule = initWasmModule(mozjpeg_enc, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height, options); const resultView = module.encode(data.data, data.width, data.height, options);

View File

@@ -3,7 +3,7 @@ export interface EncodeOptions {
} }
export interface EncoderState { type: typeof type; options: EncodeOptions; } export interface EncoderState { type: typeof type; options: EncodeOptions; }
export const type = 'png'; export const type = 'optipng';
export const label = 'OptiPNG'; export const label = 'OptiPNG';
export const mimeType = 'image/png'; export const mimeType = 'image/png';
export const extension = 'png'; export const extension = 'png';

View File

@@ -1,12 +1,12 @@
import optipng, { OptiPngModule } from '../../../codecs/optipng/optipng'; import optipng, { OptiPngModule } from '../../../codecs/optipng/optipng';
import wasmUrl from '../../../codecs/optipng/optipng.wasm'; import wasmUrl from '../../../codecs/optipng/optipng.wasm';
import { EncodeOptions } from './encoder-meta'; import { EncodeOptions } from './encoder-meta';
import { initEmscriptenModule } from '../util'; import { initWasmModule } from '../util';
let emscriptenModule: Promise<OptiPngModule>; let emscriptenModule: Promise<OptiPngModule>;
export async function compress(data: BufferSource, options: EncodeOptions): Promise<ArrayBuffer> { export async function compress(data: BufferSource, options: EncodeOptions): Promise<ArrayBuffer> {
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(optipng, wasmUrl); if (!emscriptenModule) emscriptenModule = initWasmModule(optipng, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;
const resultView = module.compress(data, options); const resultView = module.compress(data, options);

View File

@@ -0,0 +1,13 @@
export interface EncodeOptions {
level: number;
}
export interface EncoderState { type: typeof type; options: EncodeOptions; }
export const type = 'oxipng';
export const label = 'OxiPNG';
export const mimeType = 'image/png';
export const extension = 'png';
export const defaultOptions: EncodeOptions = {
level: 6,
};

View File

@@ -0,0 +1,18 @@
import * as oxipng from '../../../codecs/oxipng/pkg/oxipng_manual';
import { EncodeOptions } from './encoder-meta';
export async function compress(data: BufferSource, { level }: EncodeOptions): Promise<ArrayBuffer> {
let buffer: ArrayBuffer;
if (ArrayBuffer.isView(data)) {
buffer = data.buffer;
} else {
buffer = data;
}
debugger;
const resultView = await oxipng.compress(new Uint8Array(buffer), level);
const result = new Uint8Array(resultView);
// wasm cant run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return result.buffer as ArrayBuffer;
}

View File

@@ -0,0 +1,42 @@
import { h, Component } from 'preact';
import { bind } from '../../lib/initial-util';
import { inputFieldValueAsNumber, preventDefault } from '../../lib/util';
import { EncodeOptions } from './encoder-meta';
import Range from '../../components/range';
import * as style from '../../components/Options/style.scss';
type Props = {
options: EncodeOptions;
onChange(newOptions: EncodeOptions): void;
};
export default class OxiPNGEncoderOptions extends Component<Props, {}> {
@bind
onChange(event: Event) {
const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement;
const options: EncodeOptions = {
level: inputFieldValueAsNumber(form.level),
};
this.props.onChange(options);
}
render({ options }: Props) {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<div class={style.optionOneCell}>
<Range
name="level"
min="0"
max="9"
step="1"
value={options.level}
onInput={this.onChange}
>
Effort:
</Range>
</div>
</form>
);
}
}

View File

@@ -1,13 +1,12 @@
import { expose } from 'comlink'; import { expose } from 'comlink';
import { isHqx } from '../resize/processor-meta';
import { clamp } from '../util';
async function mozjpegEncode( async function mozjpegEncode(
data: ImageData, options: import('../mozjpeg/encoder-meta').EncodeOptions, data: ImageData, options: import('../mozjpeg/encoder-meta').EncodeOptions,
): Promise<ArrayBuffer> { ): Promise<ArrayBuffer> {
const { encode } = await import( const { encode } = await import(
/* webpackChunkName: "process-mozjpeg-enc" */ /* webpackChunkName: "process-mozjpeg-enc" */
'../mozjpeg/encoder'); '../mozjpeg/encoder',
);
return encode(data, options); return encode(data, options);
} }
@@ -16,7 +15,8 @@ async function quantize(
): Promise<ImageData> { ): Promise<ImageData> {
const { process } = await import( const { process } = await import(
/* webpackChunkName: "process-imagequant" */ /* webpackChunkName: "process-imagequant" */
'../imagequant/processor'); '../imagequant/processor',
);
return process(data, opts); return process(data, opts);
} }
@@ -25,39 +25,29 @@ async function rotate(
): Promise<ImageData> { ): Promise<ImageData> {
const { rotate } = await import( const { rotate } = await import(
/* webpackChunkName: "process-rotate" */ /* webpackChunkName: "process-rotate" */
'../rotate/processor'); '../rotate/processor',
);
return rotate(data, opts); return rotate(data, opts);
} }
async function resize(
data: ImageData, opts: import('../resize/processor-meta').WorkerResizeOptions,
): Promise<ImageData> {
if (isHqx(opts)) {
const { hqx } = await import(
/* webpackChunkName: "process-hqx" */
'../hqx/processor');
const widthRatio = opts.width / data.width;
const heightRatio = opts.height / data.height;
const ratio = Math.max(widthRatio, heightRatio);
if (ratio <= 1) return data;
const factor = clamp(Math.ceil(ratio), { min: 2, max: 4 }) as 2|3|4;
return hqx(data, { factor });
}
const { resize } = await import(
/* webpackChunkName: "process-resize" */
'../resize/processor');
return resize(data, opts);
}
async function optiPngEncode( async function optiPngEncode(
data: BufferSource, options: import('../optipng/encoder-meta').EncodeOptions, data: BufferSource, options: import('../optipng/encoder-meta').EncodeOptions,
): Promise<ArrayBuffer> { ): Promise<ArrayBuffer> {
const { compress } = await import( const { compress } = await import(
/* webpackChunkName: "process-optipng" */ /* webpackChunkName: "process-optipng" */
'../optipng/encoder'); '../optipng/encoder',
);
return compress(data, options);
}
async function oxiPngEncode(
data: BufferSource, options: import('../oxipng/encoder-meta').EncodeOptions,
): Promise<ArrayBuffer> {
const { compress } = await import(
/* webpackChunkName: "process-optipng" */
'../oxipng/encoder',
);
return compress(data, options); return compress(data, options);
} }
@@ -66,14 +56,16 @@ async function webpEncode(
): Promise<ArrayBuffer> { ): Promise<ArrayBuffer> {
const { encode } = await import( const { encode } = await import(
/* webpackChunkName: "process-webp-enc" */ /* webpackChunkName: "process-webp-enc" */
'../webp/encoder'); '../webp/encoder',
);
return encode(data, options); return encode(data, options);
} }
async function webpDecode(data: ArrayBuffer): Promise<ImageData> { async function webpDecode(data: ArrayBuffer): Promise<ImageData> {
const { decode } = await import( const { decode } = await import(
/* webpackChunkName: "process-webp-dec" */ /* webpackChunkName: "process-webp-dec" */
'../webp/decoder'); '../webp/decoder',
);
return decode(data); return decode(data);
} }
@@ -81,8 +73,8 @@ const exports = {
mozjpegEncode, mozjpegEncode,
quantize, quantize,
rotate, rotate,
resize,
optiPngEncode, optiPngEncode,
oxiPngEncode,
webpEncode, webpEncode,
webpDecode, webpDecode,
}; };

View File

@@ -3,11 +3,12 @@ import { QuantizeOptions } from './imagequant/processor-meta';
import { canvasEncode, blobToArrayBuffer } from '../lib/util'; import { canvasEncode, blobToArrayBuffer } from '../lib/util';
import { EncodeOptions as MozJPEGEncoderOptions } from './mozjpeg/encoder-meta'; import { EncodeOptions as MozJPEGEncoderOptions } from './mozjpeg/encoder-meta';
import { EncodeOptions as OptiPNGEncoderOptions } from './optipng/encoder-meta'; import { EncodeOptions as OptiPNGEncoderOptions } from './optipng/encoder-meta';
import { EncodeOptions as OxiPNGEncoderOptions } from './oxipng/encoder-meta';
import { EncodeOptions as WebPEncoderOptions } from './webp/encoder-meta'; import { EncodeOptions as WebPEncoderOptions } from './webp/encoder-meta';
import { EncodeOptions as BrowserJPEGOptions } from './browser-jpeg/encoder-meta'; import { EncodeOptions as BrowserJPEGOptions } from './browser-jpeg/encoder-meta';
import { EncodeOptions as BrowserWebpEncodeOptions } from './browser-webp/encoder-meta'; import { EncodeOptions as BrowserWebpEncodeOptions } from './browser-webp/encoder-meta';
import { BrowserResizeOptions, VectorResizeOptions } from './resize/processor-meta'; import { BitmapResizeOptions, VectorResizeOptions } from './resize/processor-meta';
import { browserResize, vectorResize } from './resize/processor-sync'; import { resize, vectorResize } from './resize/processor';
import * as browserBMP from './browser-bmp/encoder'; import * as browserBMP from './browser-bmp/encoder';
import * as browserPNG from './browser-png/encoder'; import * as browserPNG from './browser-png/encoder';
import * as browserJPEG from './browser-jpeg/encoder'; import * as browserJPEG from './browser-jpeg/encoder';
@@ -16,7 +17,6 @@ import * as browserGIF from './browser-gif/encoder';
import * as browserTIFF from './browser-tiff/encoder'; import * as browserTIFF from './browser-tiff/encoder';
import * as browserJP2 from './browser-jp2/encoder'; import * as browserJP2 from './browser-jp2/encoder';
import * as browserPDF from './browser-pdf/encoder'; import * as browserPDF from './browser-pdf/encoder';
import { bind } from '../lib/initial-util';
type ProcessorWorkerApi = import('./processor-worker').ProcessorWorkerApi; type ProcessorWorkerApi = import('./processor-worker').ProcessorWorkerApi;
@@ -95,7 +95,14 @@ export default class Processor {
if (!this._worker) return; if (!this._worker) return;
// If the worker is unused for 10 seconds, remove it to save memory. // If the worker is unused for 10 seconds, remove it to save memory.
this._workerTimeoutId = self.setTimeout(this.terminateWorker, workerTimeout); this._workerTimeoutId = self.setTimeout(
() => {
if (!this._worker) return;
this._worker.terminate();
this._worker = undefined;
},
workerTimeout,
);
} }
/** Abort the current job, if any */ /** Abort the current job, if any */
@@ -105,11 +112,7 @@ export default class Processor {
this._abortRejector(new DOMException('Aborted', 'AbortError')); this._abortRejector(new DOMException('Aborted', 'AbortError'));
this._abortRejector = undefined; this._abortRejector = undefined;
this._busy = false; this._busy = false;
this.terminateWorker();
}
@bind
terminateWorker() {
if (!this._worker) return; if (!this._worker) return;
this._worker.terminate(); this._worker.terminate();
this._worker = undefined; this._worker = undefined;
@@ -128,13 +131,6 @@ export default class Processor {
return this._workerApi!.rotate(data, opts); return this._workerApi!.rotate(data, opts);
} }
@Processor._processingJob({ needsWorker: true })
workerResize(
data: ImageData, opts: import('./resize/processor-meta').WorkerResizeOptions,
): Promise<ImageData> {
return this._workerApi!.resize(data, opts);
}
@Processor._processingJob({ needsWorker: true }) @Processor._processingJob({ needsWorker: true })
mozjpegEncode( mozjpegEncode(
data: ImageData, opts: MozJPEGEncoderOptions, data: ImageData, opts: MozJPEGEncoderOptions,
@@ -152,6 +148,16 @@ export default class Processor {
return this._workerApi!.optiPngEncode(pngBuffer, opts); return this._workerApi!.optiPngEncode(pngBuffer, opts);
} }
@Processor._processingJob({ needsWorker: true })
async oxiPngEncode(
data: ImageData, opts: OxiPNGEncoderOptions,
): Promise<ArrayBuffer> {
// OptiPNG expects PNG input.
const pngBlob = await canvasEncode(data, 'image/png');
const pngBuffer = await blobToArrayBuffer(pngBlob);
return this._workerApi!.oxiPngEncode(pngBuffer, opts);
}
@Processor._processingJob({ needsWorker: true }) @Processor._processingJob({ needsWorker: true })
webpEncode(data: ImageData, opts: WebPEncoderOptions): Promise<ArrayBuffer> { webpEncode(data: ImageData, opts: WebPEncoderOptions): Promise<ArrayBuffer> {
return this._workerApi!.webpEncode(data, opts); return this._workerApi!.webpEncode(data, opts);
@@ -207,9 +213,9 @@ export default class Processor {
// Synchronous jobs // Synchronous jobs
resize(data: ImageData, opts: BrowserResizeOptions) { resize(data: ImageData, opts: BitmapResizeOptions) {
this.abortCurrent(); this.abortCurrent();
return browserResize(data, opts); return resize(data, opts);
} }
vectorResize(data: HTMLImageElement, opts: VectorResizeOptions) { vectorResize(data: HTMLImageElement, opts: VectorResizeOptions) {

View File

@@ -1,10 +1,8 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import linkState from 'linkstate'; import linkState from 'linkstate';
import { bind, linkRef } from '../../lib/initial-util'; import { bind, linkRef } from '../../lib/initial-util';
import { import { inputFieldValueAsNumber, inputFieldValue, preventDefault } from '../../lib/util';
inputFieldValueAsNumber, inputFieldValue, preventDefault, inputFieldChecked, import { ResizeOptions } from './processor-meta';
} from '../../lib/util';
import { ResizeOptions, isWorkerOptions } from './processor-meta';
import * as style from '../../components/Options/style.scss'; import * as style from '../../components/Options/style.scss';
import Checkbox from '../../components/checkbox'; import Checkbox from '../../components/checkbox';
import Expander from '../../components/expander'; import Expander from '../../components/expander';
@@ -12,9 +10,8 @@ import Select from '../../components/select';
interface Props { interface Props {
isVector: Boolean; isVector: Boolean;
inputWidth: number;
inputHeight: number;
options: ResizeOptions; options: ResizeOptions;
aspect: number;
onChange(newOptions: ResizeOptions): void; onChange(newOptions: ResizeOptions): void;
} }
@@ -22,21 +19,12 @@ interface State {
maintainAspect: boolean; maintainAspect: boolean;
} }
const sizePresets = [0.25, 0.3333, 0.5, 1, 2, 3, 4];
export default class ResizerOptions extends Component<Props, State> { export default class ResizerOptions extends Component<Props, State> {
state: State = { state: State = {
maintainAspect: true, maintainAspect: true,
}; };
private form?: HTMLFormElement; form?: HTMLFormElement;
private presetWidths: { [idx: number]: number } = {};
private presetHeights: { [idx: number]: number } = {};
constructor(props: Props) {
super(props);
this.generatePresetValues(props.inputWidth, props.inputHeight);
}
private reportOptions() { private reportOptions() {
const form = this.form!; const form = this.form!;
@@ -50,8 +38,6 @@ export default class ResizerOptions extends Component<Props, State> {
width: inputFieldValueAsNumber(width), width: inputFieldValueAsNumber(width),
height: inputFieldValueAsNumber(height), height: inputFieldValueAsNumber(height),
method: form.resizeMethod.value, method: form.resizeMethod.value,
premultiply: inputFieldChecked(form.premultiply, true),
linearRGB: inputFieldChecked(form.linearRGB, true),
// Casting, as the formfield only returns the correct values. // Casting, as the formfield only returns the correct values.
fitMethod: inputFieldValue(form.fitMethod, options.fitMethod) as ResizeOptions['fitMethod'], fitMethod: inputFieldValue(form.fitMethod, options.fitMethod) as ResizeOptions['fitMethod'],
}; };
@@ -63,31 +49,18 @@ export default class ResizerOptions extends Component<Props, State> {
this.reportOptions(); this.reportOptions();
} }
private getAspect() {
return this.props.inputWidth / this.props.inputHeight;
}
componentDidUpdate(prevProps: Props, prevState: State) { componentDidUpdate(prevProps: Props, prevState: State) {
if (!prevState.maintainAspect && this.state.maintainAspect) { if (!prevState.maintainAspect && this.state.maintainAspect) {
this.form!.height.value = Math.round(Number(this.form!.width.value) / this.getAspect()); this.form!.height.value = Math.round(Number(this.form!.width.value) / this.props.aspect);
this.reportOptions(); this.reportOptions();
} }
} }
componentWillReceiveProps(nextProps: Props) {
if (
this.props.inputWidth !== nextProps.inputWidth ||
this.props.inputHeight !== nextProps.inputHeight
) {
this.generatePresetValues(nextProps.inputWidth, nextProps.inputHeight);
}
}
@bind @bind
private onWidthInput() { private onWidthInput() {
if (this.state.maintainAspect) { if (this.state.maintainAspect) {
const width = inputFieldValueAsNumber(this.form!.width); const width = inputFieldValueAsNumber(this.form!.width);
this.form!.height.value = Math.round(width / this.getAspect()); this.form!.height.value = Math.round(width / this.props.aspect);
} }
this.reportOptions(); this.reportOptions();
@@ -97,44 +70,12 @@ export default class ResizerOptions extends Component<Props, State> {
private onHeightInput() { private onHeightInput() {
if (this.state.maintainAspect) { if (this.state.maintainAspect) {
const height = inputFieldValueAsNumber(this.form!.height); const height = inputFieldValueAsNumber(this.form!.height);
this.form!.width.value = Math.round(height * this.getAspect()); this.form!.width.value = Math.round(height * this.props.aspect);
} }
this.reportOptions(); this.reportOptions();
} }
private generatePresetValues(width: number, height: number) {
for (const preset of sizePresets) {
this.presetWidths[preset] = Math.round(width * preset);
this.presetHeights[preset] = Math.round(height * preset);
}
}
private getPreset(): number | string {
const { width, height } = this.props.options;
for (const preset of sizePresets) {
if (
width === this.presetWidths[preset] &&
height === this.presetHeights[preset]
) return preset;
}
return 'custom';
}
@bind
private onPresetChange(event: Event) {
const select = event.target as HTMLSelectElement;
if (select.value === 'custom') return;
const multiplier = Number(select.value);
(this.form!.width as HTMLInputElement).value =
Math.round(this.props.inputWidth * multiplier) + '';
(this.form!.height as HTMLInputElement).value =
Math.round(this.props.inputHeight * multiplier) + '';
this.reportOptions();
}
render({ options, isVector }: Props, { maintainAspect }: State) { render({ options, isVector }: Props, { maintainAspect }: State) {
return ( return (
<form ref={linkRef(this, 'form')} class={style.optionsSection} onSubmit={preventDefault}> <form ref={linkRef(this, 'form')} class={style.optionsSection} onSubmit={preventDefault}>
@@ -146,26 +87,12 @@ export default class ResizerOptions extends Component<Props, State> {
onChange={this.onChange} onChange={this.onChange}
> >
{isVector && <option value="vector">Vector</option>} {isVector && <option value="vector">Vector</option>}
<option value="lanczos3">Lanczos3</option>
<option value="mitchell">Mitchell</option>
<option value="catrom">Catmull-Rom</option>
<option value="triangle">Triangle (bilinear)</option>
<option value="hqx">hqx (pixel art)</option>
<option value="browser-pixelated">Browser pixelated</option> <option value="browser-pixelated">Browser pixelated</option>
<option value="browser-low">Browser low quality</option> <option value="browser-low">Browser low quality</option>
<option value="browser-medium">Browser medium quality</option> <option value="browser-medium">Browser medium quality</option>
<option value="browser-high">Browser high quality</option> <option value="browser-high">Browser high quality</option>
</Select> </Select>
</label> </label>
<label class={style.optionTextFirst}>
Preset:
<Select value={this.getPreset()} onChange={this.onPresetChange}>
{sizePresets.map(preset =>
<option value={preset}>{preset * 100}%</option>,
)}
<option value="custom">Custom</option>
</Select>
</label>
<label class={style.optionTextFirst}> <label class={style.optionTextFirst}>
Width: Width:
<input <input
@@ -190,30 +117,6 @@ export default class ResizerOptions extends Component<Props, State> {
onInput={this.onHeightInput} onInput={this.onHeightInput}
/> />
</label> </label>
<Expander>
{isWorkerOptions(options) ?
<label class={style.optionInputFirst}>
<Checkbox
name="premultiply"
checked={options.premultiply}
onChange={this.onChange}
/>
Premultiply alpha channel
</label>
: null
}
{isWorkerOptions(options) ?
<label class={style.optionInputFirst}>
<Checkbox
name="linearRGB"
checked={options.linearRGB}
onChange={this.onChange}
/>
Linear RGB
</label>
: null
}
</Expander>
<label class={style.optionInputFirst}> <label class={style.optionInputFirst}>
<Checkbox <Checkbox
name="maintainAspect" name="maintainAspect"

View File

@@ -1,75 +1,26 @@
type BrowserResizeMethods = type BitmapResizeMethods = 'browser-pixelated' | 'browser-low' | 'browser-medium' | 'browser-high';
| 'browser-pixelated'
| 'browser-low'
| 'browser-medium'
| 'browser-high';
type WorkerResizeMethods =
| 'triangle'
| 'catrom'
| 'mitchell'
| 'lanczos3'
| 'hqx';
const workerResizeMethods: WorkerResizeMethods[] = [
'triangle',
'catrom',
'mitchell',
'lanczos3',
'hqx',
];
export type ResizeOptions = export interface ResizeOptions {
| BrowserResizeOptions
| WorkerResizeOptions
| VectorResizeOptions;
export interface ResizeOptionsCommon {
width: number; width: number;
height: number; height: number;
method: 'vector' | BitmapResizeMethods;
fitMethod: 'stretch' | 'contain'; fitMethod: 'stretch' | 'contain';
} }
export interface BrowserResizeOptions extends ResizeOptionsCommon { export interface BitmapResizeOptions extends ResizeOptions {
method: BrowserResizeMethods; method: BitmapResizeMethods;
} }
export interface WorkerResizeOptions extends ResizeOptionsCommon { export interface VectorResizeOptions extends ResizeOptions {
method: WorkerResizeMethods;
premultiply: boolean;
linearRGB: boolean;
}
export interface VectorResizeOptions extends ResizeOptionsCommon {
method: 'vector'; method: 'vector';
} }
/**
* Return whether a set of options are worker resize options.
*
* @param opts
*/
export function isWorkerOptions(
opts: ResizeOptions,
): opts is WorkerResizeOptions {
return (workerResizeMethods as string[]).includes(opts.method);
}
/**
* Return whether a set of options are from the HQ<n>X set
*
* @param opts
*/
export function isHqx(opts: ResizeOptions): opts is WorkerResizeOptions {
return opts.method === 'hqx';
}
export const defaultOptions: ResizeOptions = { export const defaultOptions: ResizeOptions = {
// Width and height will always default to the image size. // Width and height will always default to the image size.
// This is set elsewhere. // This is set elsewhere.
width: 1, width: 1,
height: 1, height: 1,
// This will be set to 'vector' if the input is SVG. // This will be set to 'vector' if the input is SVG.
method: 'lanczos3', method: 'browser-high',
fitMethod: 'stretch', fitMethod: 'stretch',
premultiply: true,
linearRGB: true,
}; };

View File

@@ -1,35 +0,0 @@
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,
});
}

View File

@@ -1,39 +1,49 @@
import { WorkerResizeOptions } from './processor-meta'; import { nativeResize, NativeResizeMethod, drawableToImageData } from '../../lib/util';
import { getContainOffsets } from './util'; import { BitmapResizeOptions, VectorResizeOptions } from './processor-meta';
import { resize as codecResize } from '../../../codecs/resize/pkg';
function crop(data: ImageData, sx: number, sy: number, sw: number, sh: number): ImageData { function getContainOffsets(sw: number, sh: number, dw: number, dh: number) {
const inputPixels = new Uint32Array(data.data.buffer); const currentAspect = sw / sh;
const endAspect = dw / dh;
// Copy within the same buffer for speed and memory efficiency. if (endAspect > currentAspect) {
for (let y = 0; y < sh; y += 1) { const newSh = sw / endAspect;
const start = ((y + sy) * data.width) + sx; const newSy = (sh - newSh) / 2;
inputPixels.copyWithin(y * sw, start, start + sw); return { sw, sh: newSh, sx: 0, sy: newSy };
} }
return new ImageData( const newSw = sh * endAspect;
new Uint8ClampedArray(inputPixels.buffer.slice(0, sw * sh * 4)), const newSx = (sw - newSw) / 2;
sw, sh, return { sh, sw: newSw, sx: newSx, sy: 0 };
);
} }
/** Resize methods by index */ export function resize(data: ImageData, opts: BitmapResizeOptions): ImageData {
const resizeMethods: WorkerResizeOptions['method'][] = [ let sx = 0;
'triangle', 'catrom', 'mitchell', 'lanczos3', let sy = 0;
]; let sw = data.width;
let sh = data.height;
export async function resize(data: ImageData, opts: WorkerResizeOptions): Promise<ImageData> {
let input = data;
if (opts.fitMethod === 'contain') { if (opts.fitMethod === 'contain') {
const { sx, sy, sw, sh } = getContainOffsets(data.width, data.height, opts.width, opts.height); ({ sx, sy, sw, sh } = getContainOffsets(sw, sh, opts.width, opts.height));
input = crop(input, Math.round(sx), Math.round(sy), Math.round(sw), Math.round(sh));
} }
const result = codecResize( return nativeResize(
new Uint8Array(input.data.buffer), input.width, input.height, opts.width, opts.height, data, sx, sy, sw, sh, opts.width, opts.height,
resizeMethods.indexOf(opts.method), opts.premultiply, opts.linearRGB, opts.method.slice('browser-'.length) as NativeResizeMethod,
); );
}
return new ImageData(new Uint8ClampedArray(result.buffer), opts.width, opts.height);
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,
});
} }

View File

@@ -1,14 +0,0 @@
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 };
}

View File

@@ -3,10 +3,3 @@ export interface RotateOptions {
} }
export const defaultOptions: RotateOptions = { rotate: 0 }; export const defaultOptions: RotateOptions = { rotate: 0 };
export interface RotateModuleInstance {
exports: {
memory: WebAssembly.Memory;
rotate(width: number, height: number, rotate: 0 | 90 | 180 | 270): void;
};
}

View File

@@ -1,43 +1,73 @@
import wasmUrl from '../../../codecs/rotate/rotate.wasm'; import { RotateOptions } from './processor-meta';
import { RotateOptions, RotateModuleInstance } from './processor-meta';
// We are loading a 500B module here. Loading the code to feature-detect export function rotate(data: ImageData, opts: RotateOptions): ImageData {
// `instantiateStreaming` probably takes longer to load than the time we save by const { rotate } = opts;
// using `instantiateStreaming` in the first place. So lets just use const flipDimensions = rotate % 180 !== 0;
// `ArrayBuffer`s here. const { width: inputWidth, height: inputHeight } = data;
const instancePromise = fetch(wasmUrl) const outputWidth = flipDimensions ? inputHeight : inputWidth;
.then(r => r.arrayBuffer()) const outputHeight = flipDimensions ? inputWidth : inputHeight;
.then(buf => WebAssembly.instantiate(buf)); const out = new ImageData(outputWidth, outputHeight);
let i = 0;
export async function rotate( // In the straight-copy case, d1 is x, d2 is y.
data: ImageData, // x starts at 0 and increases.
opts: RotateOptions, // y starts at 0 and increases.
): Promise<ImageData> { let d1Start = 0;
const { instance } = (await instancePromise) as { let d1Limit = inputWidth;
instance: RotateModuleInstance; let d1Advance = 1;
}; let d1Multiplier = 1;
let d2Start = 0;
let d2Limit = inputHeight;
let d2Advance = 1;
let d2Multiplier = inputWidth;
// Number of wasm memory pages (á 64KiB) needed to store the image twice. if (rotate === 90) {
const bytesPerImage = data.width * data.height * 4; // d1 is y, d2 is x.
const numPagesNeeded = Math.ceil((bytesPerImage * 2 + 8) / (64 * 1024)); // y starts at its max value and decreases.
// Only count full pages, just to be safe. // x starts at 0 and increases.
const numPagesAvailable = Math.floor( d1Start = inputHeight - 1;
instance.exports.memory.buffer.byteLength / (64 * 1024), d1Limit = inputHeight;
); d1Advance = -1;
const additionalPagesToAllocate = numPagesNeeded - numPagesAvailable; d1Multiplier = inputWidth;
d2Start = 0;
if (additionalPagesToAllocate > 0) { d2Limit = inputWidth;
instance.exports.memory.grow(additionalPagesToAllocate); 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 view = new Uint8ClampedArray(instance.exports.memory.buffer);
view.set(data.data, 8);
instance.exports.rotate(data.width, data.height, opts.rotate); 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;
}
}
const flipDimensions = opts.rotate % 180 !== 0; return out;
return new ImageData(
view.slice(bytesPerImage + 8, bytesPerImage * 2 + 8),
flipDimensions ? data.height : data.width,
flipDimensions ? data.width : data.height,
);
} }

View File

@@ -2,7 +2,7 @@ type ModuleFactory<M extends EmscriptenWasm.Module> = (
opts: EmscriptenWasm.ModuleOpts, opts: EmscriptenWasm.ModuleOpts,
) => M; ) => M;
export function initEmscriptenModule<T extends EmscriptenWasm.Module>( export function initWasmModule<T extends EmscriptenWasm.Module>(
moduleFactory: ModuleFactory<T>, moduleFactory: ModuleFactory<T>,
wasmUrl: string, wasmUrl: string,
): Promise<T> { ): Promise<T> {
@@ -25,12 +25,3 @@ export function initEmscriptenModule<T extends EmscriptenWasm.Module>(
}); });
}); });
} }
interface ClampOpts {
min?: number;
max?: number;
}
export function clamp(x: number, opts: ClampOpts): number {
return Math.min(Math.max(x, opts.min || Number.MIN_VALUE), opts.max || Number.MAX_VALUE);
}

View File

@@ -1,11 +1,11 @@
import webp_dec, { WebPModule } from '../../../codecs/webp_dec/webp_dec'; import webp_dec, { WebPModule } from '../../../codecs/webp_dec/webp_dec';
import wasmUrl from '../../../codecs/webp_dec/webp_dec.wasm'; import wasmUrl from '../../../codecs/webp_dec/webp_dec.wasm';
import { initEmscriptenModule } from '../util'; import { initWasmModule } from '../util';
let emscriptenModule: Promise<WebPModule>; let emscriptenModule: Promise<WebPModule>;
export async function decode(data: ArrayBuffer): Promise<ImageData> { export async function decode(data: ArrayBuffer): Promise<ImageData> {
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(webp_dec, wasmUrl); if (!emscriptenModule) emscriptenModule = initWasmModule(webp_dec, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;
const rawImage = module.decode(data); const rawImage = module.decode(data);

View File

@@ -1,12 +1,12 @@
import webp_enc, { WebPModule } from '../../../codecs/webp_enc/webp_enc'; import webp_enc, { WebPModule } from '../../../codecs/webp_enc/webp_enc';
import wasmUrl from '../../../codecs/webp_enc/webp_enc.wasm'; import wasmUrl from '../../../codecs/webp_enc/webp_enc.wasm';
import { EncodeOptions } from './encoder-meta'; import { EncodeOptions } from './encoder-meta';
import { initEmscriptenModule } from '../util'; import { initWasmModule } from '../util';
let emscriptenModule: Promise<WebPModule>; let emscriptenModule: Promise<WebPModule>;
export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> { export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> {
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(webp_enc, wasmUrl); if (!emscriptenModule) emscriptenModule = initWasmModule(webp_enc, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height, options); const resultView = module.encode(data.data, data.width, data.height, options);

View File

@@ -13,11 +13,12 @@ const ROUTE_EDITOR = '/editor';
const compressPromise = import( const compressPromise = import(
/* webpackChunkName: "main-app" */ /* webpackChunkName: "main-app" */
'../compress'); '../compress',
);
const swBridgePromise = import( const offlinerPromise = import(
/* webpackChunkName: "sw-bridge" */ /* webpackChunkName: "offliner" */
'../../lib/sw-bridge'); '../../lib/offliner',
);
function back() { function back() {
window.history.back(); window.history.back();
@@ -26,7 +27,6 @@ function back() {
interface Props {} interface Props {}
interface State { interface State {
awaitingShareTarget: boolean;
file?: File | Fileish; file?: File | Fileish;
isEditorOpen: Boolean; isEditorOpen: Boolean;
Compress?: typeof import('../compress').default; Compress?: typeof import('../compress').default;
@@ -34,7 +34,6 @@ interface State {
export default class App extends Component<Props, State> { export default class App extends Component<Props, State> {
state: State = { state: State = {
awaitingShareTarget: new URL(location.href).searchParams.has('share-target'),
isEditorOpen: false, isEditorOpen: false,
file: undefined, file: undefined,
Compress: undefined, Compress: undefined,
@@ -51,15 +50,7 @@ export default class App extends Component<Props, State> {
this.showSnack('Failed to load app'); this.showSnack('Failed to load app');
}); });
swBridgePromise.then(async ({ offliner, getSharedImage }) => { offlinerPromise.then(({ offliner }) => offliner(this.showSnack));
offliner(this.showSnack);
if (!this.state.awaitingShareTarget) return;
const file = await getSharedImage();
// Remove the ?share-target from the URL
history.replaceState('', '', '/');
this.openEditor();
this.setState({ file, awaitingShareTarget: false });
});
// In development, persist application state across hot reloads: // In development, persist application state across hot reloads:
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
@@ -83,9 +74,8 @@ export default class App extends Component<Props, State> {
} }
@bind @bind
private onFileDrop({ files }: FileDropEvent) { private onFileDrop({ file }: FileDropEvent) {
if (!files || files.length === 0) return; if (!file) return;
const file = files[0];
this.openEditor(); this.openEditor();
this.setState({ file }); this.setState({ file });
} }
@@ -110,25 +100,19 @@ export default class App extends Component<Props, State> {
@bind @bind
private openEditor() { private openEditor() {
if (this.state.isEditorOpen) return; if (this.state.isEditorOpen) return;
// Change path, but preserve query string. history.pushState(null, '', ROUTE_EDITOR);
const editorURL = new URL(location.href);
editorURL.pathname = ROUTE_EDITOR;
history.pushState(null, '', editorURL.href);
this.setState({ isEditorOpen: true }); this.setState({ isEditorOpen: true });
} }
render({}: Props, { file, isEditorOpen, Compress, awaitingShareTarget }: State) { render({}: Props, { file, isEditorOpen, Compress }: State) {
const showSpinner = awaitingShareTarget || (isEditorOpen && !Compress);
return ( return (
<div id="app" class={style.app}> <div id="app" class={style.app}>
<file-drop accept="image/*" onfiledrop={this.onFileDrop} class={style.drop}> <file-drop accept="image/*" onfiledrop={this.onFileDrop} class={style.drop}>
{ {!isEditorOpen
showSpinner ? <Intro onFile={this.onIntroPickFile} showSnack={this.showSnack} />
? <loading-spinner class={style.appLoader}/> : (Compress)
: isEditorOpen ? <Compress file={file!} showSnack={this.showSnack} onBack={back} />
? Compress && <Compress file={file!} showSnack={this.showSnack} onBack={back} /> : <loading-spinner class={style.appLoader}/>
: <Intro onFile={this.onIntroPickFile} showSnack={this.showSnack} />
} }
<snack-bar ref={linkRef(this, 'snackbar')} /> <snack-bar ref={linkRef(this, 'snackbar')} />
</file-drop> </file-drop>

View File

@@ -146,14 +146,12 @@ export default class Options extends Component<Props, State> {
{preprocessorState.resize.enabled ? {preprocessorState.resize.enabled ?
<ResizeOptionsComponent <ResizeOptionsComponent
isVector={Boolean(source && source.vectorImage)} isVector={Boolean(source && source.vectorImage)}
inputWidth={source ? source.processed.width : 1} aspect={source ? source.processed.width / source.processed.height : 1}
inputHeight={source ? source.processed.height : 1}
options={preprocessorState.resize} options={preprocessorState.resize}
onChange={this.onResizeOptionsChange} onChange={this.onResizeOptionsChange}
/> />
: null} : null}
</Expander> </Expander>
<label class={style.sectionEnabler}> <label class={style.sectionEnabler}>
<Checkbox <Checkbox
name="quantizer.enable" name="quantizer.enable"
@@ -180,7 +178,6 @@ export default class Options extends Component<Props, State> {
{encoderSupportMap ? {encoderSupportMap ?
<Select value={encoderState.type} onChange={this.onEncoderTypeChange} large> <Select value={encoderState.type} onChange={this.onEncoderTypeChange} large>
{encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => ( {encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => (
// tslint:disable-next-line:jsx-key
<option value={encoder.type}>{encoder.label}</option> <option value={encoder.type}>{encoder.label}</option>
))} ))}
</Select> </Select>

View File

@@ -8,6 +8,7 @@ import Options from '../Options';
import ResultCache from './result-cache'; import ResultCache from './result-cache';
import * as identity from '../../codecs/identity/encoder-meta'; import * as identity from '../../codecs/identity/encoder-meta';
import * as optiPNG from '../../codecs/optipng/encoder-meta'; import * as optiPNG from '../../codecs/optipng/encoder-meta';
import * as oxiPNG from '../../codecs/oxipng/encoder-meta';
import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta'; import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta';
import * as webP from '../../codecs/webp/encoder-meta'; import * as webP from '../../codecs/webp/encoder-meta';
import * as browserPNG from '../../codecs/browser-png/encoder-meta'; import * as browserPNG from '../../codecs/browser-png/encoder-meta';
@@ -18,14 +19,20 @@ import * as browserTIFF from '../../codecs/browser-tiff/encoder-meta';
import * as browserJP2 from '../../codecs/browser-jp2/encoder-meta'; import * as browserJP2 from '../../codecs/browser-jp2/encoder-meta';
import * as browserBMP from '../../codecs/browser-bmp/encoder-meta'; import * as browserBMP from '../../codecs/browser-bmp/encoder-meta';
import * as browserPDF from '../../codecs/browser-pdf/encoder-meta'; import * as browserPDF from '../../codecs/browser-pdf/encoder-meta';
import { EncoderState, EncoderType, EncoderOptions, encoderMap } from '../../codecs/encoders'; import {
import { PreprocessorState, defaultPreprocessorState } from '../../codecs/preprocessors'; EncoderState,
EncoderType,
EncoderOptions,
encoderMap,
} from '../../codecs/encoders';
import {
PreprocessorState,
defaultPreprocessorState,
} from '../../codecs/preprocessors';
import { decodeImage } from '../../codecs/decoders'; import { decodeImage } from '../../codecs/decoders';
import { cleanMerge, cleanSet } from '../../lib/clean-modify'; import { cleanMerge, cleanSet } from '../../lib/clean-modify';
import Processor from '../../codecs/processor'; import Processor from '../../codecs/processor';
import { import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta';
BrowserResizeOptions, isWorkerOptions as isWorkerResizeOptions, isHqx, WorkerResizeOptions,
} from '../../codecs/resize/processor-meta';
import './custom-els/MultiPanel'; import './custom-els/MultiPanel';
import Results from '../results'; import Results from '../results';
import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons'; import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons';
@@ -104,24 +111,10 @@ async function preprocessImage(
if (preprocessData.resize.method === 'vector' && source.vectorImage) { if (preprocessData.resize.method === 'vector' && source.vectorImage) {
result = processor.vectorResize( result = processor.vectorResize(
source.vectorImage, source.vectorImage,
preprocessData.resize, preprocessData.resize as VectorResizeOptions,
); );
} else if (isHqx(preprocessData.resize)) {
// Hqx can only do x2, x3 or x4.
result = await processor.workerResize(result, preprocessData.resize);
// Seems like the globals from Rust from hqx and resize are conflicting.
// For now we can fix that by terminating the worker.
// TODO: Use wasm-bindgens new --web target to create a proper ES6 module
// and remove this.
processor.terminateWorker();
// If the target size is not a clean x2, x3 or x4, use Catmull-Rom
// for the remaining scaling.
const pixelOpts = { ...preprocessData.resize, method: 'catrom' };
result = await processor.workerResize(result, pixelOpts as WorkerResizeOptions);
} else if (isWorkerResizeOptions(preprocessData.resize)) {
result = await processor.workerResize(result, preprocessData.resize);
} else { } else {
result = processor.resize(result, preprocessData.resize as BrowserResizeOptions); result = processor.resize(result, preprocessData.resize as BitmapResizeOptions);
} }
} }
if (preprocessData.quantizer.enabled) { if (preprocessData.quantizer.enabled) {
@@ -139,6 +132,7 @@ async function compressImage(
const compressedData = await (() => { const compressedData = await (() => {
switch (encodeData.type) { switch (encodeData.type) {
case optiPNG.type: return processor.optiPngEncode(image, encodeData.options); case optiPNG.type: return processor.optiPngEncode(image, encodeData.options);
case oxiPNG.type: return processor.oxiPngEncode(image, encodeData.options);
case mozJPEG.type: return processor.mozjpegEncode(image, encodeData.options); case mozJPEG.type: return processor.mozjpegEncode(image, encodeData.options);
case webP.type: return processor.webpEncode(image, encodeData.options); case webP.type: return processor.webpEncode(image, encodeData.options);
case browserPNG.type: return processor.browserPngEncode(image); case browserPNG.type: return processor.browserPngEncode(image);
@@ -256,7 +250,7 @@ export default class Compress extends Component<Props, State> {
this.widthQuery.addListener(this.onMobileWidthChange); this.widthQuery.addListener(this.onMobileWidthChange);
this.updateFile(props.file); this.updateFile(props.file);
import('../../lib/sw-bridge').then(({ mainAppLoaded }) => mainAppLoaded()); import('../../lib/offliner').then(({ mainAppLoaded }) => mainAppLoaded());
} }
@bind @bind
@@ -449,7 +443,7 @@ export default class Compress extends Component<Props, State> {
newState = cleanMerge(newState, `sides.${i}.latestSettings.preprocessorState.resize`, { newState = cleanMerge(newState, `sides.${i}.latestSettings.preprocessorState.resize`, {
width: processed.width, width: processed.width,
height: processed.height, height: processed.height,
method: vectorImage ? 'vector' : 'lanczos3', method: vectorImage ? 'vector' : 'browser-high',
}); });
} }
@@ -579,7 +573,6 @@ export default class Compress extends Component<Props, State> {
const [leftImageData, rightImageData] = sides.map(i => i.data); const [leftImageData, rightImageData] = sides.map(i => i.data);
const options = sides.map((side, index) => ( const options = sides.map((side, index) => (
// tslint:disable-next-line:jsx-key
<Options <Options
source={source} source={source}
mobileView={mobileView} mobileView={mobileView}
@@ -595,7 +588,6 @@ export default class Compress extends Component<Props, State> {
(mobileView ? ['down', 'up'] : ['right', 'left']) as CopyAcrossIconProps['copyDirection'][]; (mobileView ? ['down', 'up'] : ['right', 'left']) as CopyAcrossIconProps['copyDirection'][];
const results = sides.map((side, index) => ( const results = sides.map((side, index) => (
// tslint:disable-next-line:jsx-key
<Results <Results
downloadUrl={side.downloadUrl} downloadUrl={side.downloadUrl}
imageFile={side.file} imageFile={side.file}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -53,17 +53,11 @@ export default class Intro extends Component<Props, State> {
state: State = {}; state: State = {};
private fileInput?: HTMLInputElement; private fileInput?: HTMLInputElement;
@bind
private resetFileInput() {
this.fileInput!.value = '';
}
@bind @bind
private onFileChange(event: Event): void { private onFileChange(event: Event): void {
const fileInput = event.target as HTMLInputElement; const fileInput = event.target as HTMLInputElement;
const file = fileInput.files && fileInput.files[0]; const file = fileInput.files && fileInput.files[0];
if (!file) return; if (!file) return;
this.resetFileInput();
this.props.onFile(file); this.props.onFile(file);
} }

Some files were not shown because too many files have changed in this diff Show More