Compare commits

...

100 Commits

Author SHA1 Message Date
Jake Archibald
1b7d3fa394 1.9.1 2020-02-27 11:37:53 +00:00
Jake Archibald
650db99818 Package-json update 2020-02-27 11:37:32 +00:00
Surma
7638bb795e Merge pull request #727 from GoogleChromeLabs/optipng
optipng build improvements
2020-02-27 12:19:22 +01:00
Ingvar Stepanyan
570e604be0 optipng: switch to bundled zlib and libpng
Benefits:
 - newer versions of the libraries
    - zlib: 1.2.8 -> 1.2.11
	- libpng: 1.6.18beta04 -> 1.6.34
 - much fewer dependencies to install (as libs are already in optipng archive and we don't need napa)
 - much smaller build thanks to customised versions of zlib and libpng containing only APIs necessary for optipng itself: 238950 -> 177359 bytes
 - much faster build thanks to preconfigured libpng and stripped APIs: 2m15s -> 40s
 - much simpler build script: 77 -> 46 lines
2020-02-25 18:45:47 +00:00
Ingvar Stepanyan
a056d1c363 Switch to make to build optipng
Mostly a build config simplification for now, no noticeable changes in time or output size.
2020-02-25 18:20:05 +00:00
Ingvar Stepanyan
fce61c8c89 Switch to emscripten-upstream
Before: 255184 bytes, 2m15s
After: 238270 bytes, 2m6s
2020-02-25 17:14:16 +00:00
Ingvar Stepanyan
d60d0ae47d Update Emscripten (1.39.4) 2020-02-25 16:53:27 +00:00
Jake Archibald
8bf741ed4e 1.9.0 2019-10-30 13:20:33 +00:00
Surma
9f660e5178 add maskable icon (#709)
add maskable icon
2019-10-30 12:25:41 +00:00
Masataka Yakura
ef2318bcc1 support maskable icon
add a new entry to the icons member referring the maskable icon file added in 364a5db
2019-10-30 19:47:45 +09:00
Masataka Yakura
364a5db5d5 add maskable icon file 2019-10-30 19:44:49 +09:00
Surma
4a01d0d548 Update dependency sass-loader to v7.3.1 (#685)
Update dependency sass-loader to v7.3.1
2019-08-22 11:40:24 +01:00
renovate[bot]
4a9c28f89f Update dependency sass-loader to v7.3.1 2019-08-21 16:19:16 +00:00
Surma
3aed873467 Update dependency tslint to v5.19.0 (#686)
Update dependency tslint to v5.19.0
2019-08-21 17:15:47 +01:00
renovate[bot]
2b7f059b8f Update dependency tslint to v5.19.0 2019-08-20 19:54:25 +00:00
Surma
33726e9c68 Update dependency husky to v3.0.4 (#683)
Update dependency husky to v3.0.4
2019-08-17 22:35:54 +01:00
renovate[bot]
3d29f486e7 Update dependency husky to v3.0.4 2019-08-17 13:28:25 +00:00
Surma
79e8a26f06 Update dependency readdirp to v3.1.2 (#681)
Update dependency readdirp to v3.1.2
2019-08-14 15:12:06 +01:00
renovate[bot]
e8efd54766 Update dependency readdirp to v3.1.2 2019-08-14 14:06:24 +00:00
Surma
edcc774c16 Update dependency webpack-dev-server to v3.8.0 (#679)
Update dependency webpack-dev-server to v3.8.0
2019-08-10 00:42:52 +01:00
renovate[bot]
746b33a590 Update dependency webpack-dev-server to v3.8.0 2019-08-09 17:20:25 +00:00
Surma
8a264efc67 Update dependency sass-loader to v7.2.0 (#678)
Update dependency sass-loader to v7.2.0
2019-08-09 14:35:29 +01:00
renovate[bot]
f0af6e97a0 Update dependency sass-loader to v7.2.0 2019-08-09 10:31:33 +00:00
Surma
043107c101 Update dependency file-loader to v4.2.0 (#675)
Update dependency file-loader to v4.2.0
2019-08-09 10:22:19 +01:00
renovate[bot]
e9e3b923e0 Update dependency file-loader to v4.2.0 2019-08-08 12:48:13 +00:00
Surma
aea6e91b1d Update dependency husky to v3.0.3 (#677)
Update dependency husky to v3.0.3
2019-08-08 13:46:49 +01:00
renovate[bot]
9c4717a13d Update dependency husky to v3.0.3 2019-08-08 12:43:05 +00:00
Surma
559cabce9e Update dependency @types/node to v10.14.15 (#676)
Update dependency @types/node to v10.14.15
2019-08-08 09:59:35 +01:00
renovate[bot]
7f6a3de7ca Update dependency @types/node to v10.14.15 2019-08-07 21:33:12 +00:00
Surma
f3adc87f52 Update Node.js to v10.16.2 (#674)
Update Node.js to v10.16.2
2019-08-07 14:34:18 +01:00
renovate[bot]
6499e9f63d Update Node.js to v10.16.2 2019-08-06 22:41:02 +00:00
Surma
ac1f104e49 Update dependency style-loader to v1 (#673)
Update dependency style-loader to v1
2019-08-06 18:11:48 +01:00
renovate[bot]
87f89e6b2f Update dependency style-loader to v1 2019-08-06 10:41:31 +00:00
Ingvar Stepanyan
7f6404d5a8 Delete .gitignore in resize codec 2019-08-05 15:13:40 +01:00
Ingvar Stepanyan
8e857cd393 Use native Wasm+Webpack support for Rust codecs
This delegates loading of Wasm modules to Webpack itself, making wrapper code simpler.

Emscripten-generated modules are still using custom loading glue as they're not compatible with Webpack.
2019-08-05 15:13:40 +01:00
Ingvar Stepanyan
b8f801333d Switch to prebuilt WABT and wasm-pack
Significantly speeds up `npm run build:image` commands (as they don't need to compile anything anymore) and slightly reduces size of Docker images:
 - `squoosh-rotate`: 1.87GB -> 1.84GB
 - `squoosh-resize`: 2.02GB -> 1.85GB
 - `squoosh-hqx`: 2.06GB -> 1.9GB
2019-08-02 17:23:09 +01:00
Ingvar Stepanyan
499956e216 Switch to stable Rust
Nightly is no longer necessary for wee_alloc, so we don't need to rely on unstable versions of Rust anymore.

As a bonus, this reduces size of each Rust-related Docker image by >1 GB:
 - `squoosh-rotate`: 2.94GB -> 1.87GB
 - `squoosh-resize`: 3.09GB -> 2.02GB
 - `squoosh-hqx`: 3.13GB -> 2.06GB
2019-08-02 16:14:44 +01:00
Surma
3932ee2c00 Rename resize package (#668)
Rename `resize` package
2019-08-02 15:21:41 +01:00
Ingvar Stepanyan
68177b7590 Rename resize package
Without this, it was creating files in `pkg` prefixed with `squooshresize`, which were ignored by Git.
2019-08-02 14:53:39 +01:00
Surma
0a2a4122dc Update build.sh 2019-08-02 12:47:12 +01:00
Surma
e6299569d0 Update dependency tslint to v5.18.0 (#633)
Update dependency tslint to v5.18.0
2019-08-01 09:27:35 +01:00
renovate[bot]
578ad8c291 Update dependency tslint to v5.18.0 2019-07-31 20:16:43 +00:00
Surma
eaa294b689 Merge pull request #667 from GoogleChromeLabs/renovate/node-10.x
Update Node.js to v10.16.1
2019-07-31 21:14:35 +01:00
renovate[bot]
6e97cfb8d5 Update Node.js to v10.16.1 2019-07-31 19:49:00 +00:00
Surma
80a68c48b2 Update dependency webpack-dev-server to v3.7.2 (#628)
Update dependency webpack-dev-server to v3.7.2
2019-07-31 15:16:17 +01:00
renovate[bot]
98865f8141 Update dependency webpack-dev-server to v3.7.2 2019-07-31 13:59:11 +00:00
Ingvar Stepanyan
78da9fd144 Fix build on Windows (#666)
* Use `require` for reading JSON

JSON is natively supported by Node.js, so no point in using a custom helper here.

* Fix build on Windows

Fixes #465.
2019-07-31 14:32:26 +01:00
Surma
94c50a989b Update dependency terser-webpack-plugin to v1.4.0 (#665)
Update dependency terser-webpack-plugin to v1.4.0
2019-07-31 14:08:09 +01:00
renovate[bot]
3e525e767c Update dependency terser-webpack-plugin to v1.4.1 2019-07-31 12:18:23 +00:00
Surma
dfcc1e24e4 Update dependency url-loader to v2.1.0 (#656)
Update dependency url-loader to v2.1.0
2019-07-30 19:12:05 +01:00
renovate[bot]
f3aa8edfca Update dependency url-loader to v2.1.0 2019-07-30 11:00:32 +00:00
Surma
ffd7ee6013 Update dependency webpack-bundle-analyzer to v3.4.1 (#664)
Update dependency webpack-bundle-analyzer to v3.4.1
2019-07-30 11:57:30 +01:00
renovate[bot]
64bb09dbc5 Update dependency webpack-bundle-analyzer to v3.4.1 2019-07-30 06:57:22 +00:00
Surma
35fcbc40ed Update dependency husky to v3.0.2 (#663)
Update dependency husky to v3.0.2
2019-07-29 18:35:05 +01:00
renovate[bot]
1b55a48680 Update dependency husky to v3.0.2 2019-07-29 17:32:53 +00:00
Surma
88fbb19d29 Update dependency @types/node to v10.14.13 (#654)
Update dependency @types/node to v10.14.13
2019-07-29 15:35:38 +01:00
renovate[bot]
3dc1501ff7 Update dependency @types/node to v10.14.13 2019-07-27 13:47:47 +00:00
Surma
b7576c559a Update dependency pretty-bytes to v5.3.0 (#662)
Update dependency pretty-bytes to v5.3.0
2019-07-27 14:46:39 +01:00
renovate[bot]
69ed2e1d56 Update dependency pretty-bytes to v5.3.0 2019-07-26 20:28:22 +00:00
Surma
55f7f3d72a Update dependency copy-webpack-plugin to v5.0.4 (#661)
Update dependency copy-webpack-plugin to v5.0.4
2019-07-26 12:05:12 +01:00
renovate[bot]
055d0b4ea1 Update dependency copy-webpack-plugin to v5.0.4 2019-07-26 10:42:39 +00:00
Surma
7735346ed0 Update dependency critters-webpack-plugin to v2.4.0 (#660)
Update dependency critters-webpack-plugin to v2.4.0
2019-07-23 19:47:12 +01:00
renovate[bot]
2a06fdbbe0 Update dependency critters-webpack-plugin to v2.4.0 2019-07-23 14:37:32 +00:00
Surma
19169199e9 Update dependency raw-loader to v3.1.0 (#657)
Update dependency raw-loader to v3.1.0
2019-07-19 12:02:05 +01:00
renovate[bot]
76f3c39b78 Update dependency raw-loader to v3.1.0 2019-07-18 23:26:24 +00:00
Surma
8da52acc4c Update dependency husky to v3.0.1 (#658)
Update dependency husky to v3.0.1
2019-07-19 00:23:39 +01:00
renovate[bot]
9253522a3a Update dependency husky to v3.0.1 2019-07-18 21:54:45 +00:00
Surma
cf39a3e5a5 Update dependency file-loader to v4.1.0 (#655)
Update dependency file-loader to v4.1.0
2019-07-18 22:52:27 +01:00
renovate[bot]
a08877f0ac Update dependency file-loader to v4.1.0 2019-07-18 11:14:26 +00:00
Surma
2f0dc1c067 Update dependency mini-css-extract-plugin to v0.8.0 (#653)
Update dependency mini-css-extract-plugin to v0.8.0
2019-07-17 16:37:17 +01:00
renovate[bot]
535d8c9142 Update dependency mini-css-extract-plugin to v0.8.0 2019-07-16 20:52:34 +00:00
Surma
6dc2ba3bef Update dependency @types/node to v10.14.12 (#646)
Update dependency @types/node to v10.14.12
2019-07-15 15:56:29 +01:00
renovate[bot]
93cfdc8f98 Update dependency @types/node to v10.14.12 2019-07-14 18:48:32 +00:00
Surma
671544349e Merge pull request #652 from GoogleChromeLabs/renovate/script-ext-html-webpack-plugin-2.x
Update dependency script-ext-html-webpack-plugin to v2.1.4
2019-07-14 19:47:18 +01:00
renovate[bot]
d3c1939692 Update dependency script-ext-html-webpack-plugin to v2.1.4 2019-07-12 20:36:17 +00:00
Surma
99642aef96 Update dependency typescript to v3.5.3 (#650)
Update dependency typescript to v3.5.3
2019-07-09 17:56:25 +01:00
renovate[bot]
ac4942b640 Update dependency typescript to v3.5.3 2019-07-08 22:36:40 +00:00
Surma
23cb004a86 Update dependency chokidar to v3.0.2 (#649)
Update dependency chokidar to v3.0.2
2019-07-08 11:49:13 +01:00
renovate[bot]
4b6de60978 Update dependency chokidar to v3.0.2 2019-07-08 10:33:35 +00:00
Surma
de204aa56a Update dependency readdirp to v3.1.1 (#648)
Update dependency readdirp to v3.1.1
2019-07-08 11:31:31 +01:00
renovate[bot]
976f811b36 Update dependency readdirp to v3.1.1 2019-07-06 23:55:06 +00:00
Surma
855fc9e602 Update dependency @types/node to v10.14.10 (#634)
Update dependency @types/node to v10.14.10
2019-07-03 10:34:32 +01:00
renovate[bot]
a8848e717c Update dependency @types/node to v10.14.10 2019-07-02 15:57:11 +00:00
Surma
90b99faf8b Update dependency travis-size-report to v1.1.0 (#625)
Update dependency travis-size-report to v1.1.0
2019-07-02 16:55:37 +01:00
renovate[bot]
d3cfffbbcf Update dependency travis-size-report to v1.1.0 2019-07-02 14:27:10 +00:00
Surma
fade7f9be8 Update dependency url-loader to v2.0.1 (#638)
Update dependency url-loader to v2.0.1
2019-07-02 15:24:31 +01:00
renovate[bot]
b10dd055d3 Update dependency url-loader to v2.0.1 2019-07-02 09:19:05 +00:00
Surma
7532f01222 Update dependency husky to v3 (#645)
Update dependency husky to v3
2019-07-02 12:12:33 +03:00
renovate[bot]
2df09efdee Update dependency husky to v3 2019-07-01 21:30:38 +00:00
Surma
bed4f49a12 Merge pull request #640 from GoogleChromeLabs/renovate/husky-2.x
Update dependency husky to v2.7.0
2019-06-27 18:44:45 +03:00
renovate[bot]
2b7fefff8b Update dependency husky to v2.7.0 2019-06-27 15:40:39 +00:00
Surma
bdcf2519ce Merge pull request #630 from GoogleChromeLabs/the-nittest-of-nits
Fix nit
2019-06-19 14:04:34 +01:00
Mathias Bynens
3f8afb72a6 Fix nit 2019-06-19 14:44:23 +02:00
Jake Archibald
2f834db224 Quick doc fix 2019-06-19 13:02:59 +01:00
Jake Archibald
3541ce7492 1.8.1 2019-06-19 12:25:18 +01:00
Jake Archibald
b6fae0eb4c Hqx fix (#629)
* Optimising wasm so it doesn't break Chrome

* Simpler dockerfile (thanks surma!)
2019-06-19 12:24:24 +01:00
Surma
fbaa282f07 Update dependency ts-loader to v6.0.3 (#627)
Update dependency ts-loader to v6.0.3
2019-06-18 12:56:24 +01:00
renovate[bot]
6136ae7411 Update dependency ts-loader to v6.0.3 2019-06-18 11:30:57 +00:00
Jake Archibald
f024747299 1.8.0 2019-06-18 12:24:41 +01:00
Surma
66feffcc49 Add Hq{2,3,4}x (#624)
* Scaling works on native

* It works in wasm

* Integrate with UI

* Remove benchmark

* Integrate Hqx into Resizer module

* Link against repo for hqx

* Remove unused defaultOpts

* Re-add test file

* Adding size dropdown

* Chrome: go and sit on the naughty step

* Better docs

* Review

* Add link to crbug

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

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

* Terminate worker inbetween resize jobs
2019-06-18 12:23:22 +01:00
49 changed files with 1335 additions and 2096 deletions

2
.nvmrc
View File

@@ -1 +1 @@
10.16.0 10.16.2

5
codecs/hqx/.gitignore vendored Normal file
View File

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

37
codecs/hqx/Cargo.toml Normal file
View File

@@ -0,0 +1,37 @@
[package]
name = "squooshhqx"
version = "0.1.0"
authors = ["Surma <surma@surma.link>"]
[lib]
crate-type = ["cdylib"]
[features]
default = ["console_error_panic_hook", "wee_alloc"]
[dependencies]
cfg-if = "0.1.2"
wasm-bindgen = "0.2.38"
# lazy_static = "1.0.0"
hqx = {git = "https://github.com/CryZe/wasmboy-rs", tag="v0.1.2"}
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.1", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
wee_alloc = { version = "0.4.2", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.2"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
lto = true

12
codecs/hqx/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
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

25
codecs/hqx/build.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/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 "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"

4
codecs/hqx/package-lock.json generated Normal file
View File

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

7
codecs/hqx/package.json Normal file
View File

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

View File

@@ -0,0 +1,15 @@
{
"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"
}

9
codecs/hqx/pkg/squooshhqx.d.ts vendored Normal file
View File

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

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

5
codecs/hqx/pkg/squooshhqx_bg.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/* 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.

55
codecs/hqx/src/lib.rs Normal file
View File

@@ -0,0 +1,55 @@
extern crate cfg_if;
extern crate hqx;
extern crate wasm_bindgen;
mod utils;
use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;
cfg_if! {
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
if #[cfg(feature = "wee_alloc")] {
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}
}
#[wasm_bindgen]
#[no_mangle]
pub fn resize(
input_image: Vec<u32>,
input_width: usize,
input_height: usize,
factor: usize,
) -> Vec<u32> {
let num_output_pixels = input_width * input_height * factor * factor;
let mut output_image = Vec::<u32>::with_capacity(num_output_pixels * 4);
output_image.resize(num_output_pixels, 0);
match factor {
2 => hqx::hq2x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
3 => hqx::hq3x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
4 => hqx::hq4x(
input_image.as_slice(),
output_image.as_mut_slice(),
input_width,
input_height,
),
_ => unreachable!(),
};
return output_image;
}

17
codecs/hqx/src/utils.rs Normal file
View File

@@ -0,0 +1,17 @@
use cfg_if::cfg_if;
cfg_if! {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
pub fn set_panic_hook() {}
}
}

View File

@@ -4,84 +4,40 @@ 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 "============================================="
( (
emcc \ cd node_modules/optipng
${OPTIMIZE} \ CFLAGS="${OPTIMIZE} -Isrc/zlib" emconfigure ./configure --prefix=${PREFIX}
-Wno-implicit-function-declaration \ emmake make
-I ${PREFIX}/include \ emmake make install
-I node_modules/optipng/src/opngreduc \ mkdir -p ${PREFIX}/lib
-I node_modules/optipng/src/pngxtern \ mv ${PREFIX}/bin/optipng ${PREFIX}/lib/liboptipng.so
-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\`" echo "Run \`docker pull trzeci/emscripten-upstream\`"
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 && napa", "install": "tar-dependency install",
"build": "npm run build:wasm", "build": "npm run build:wasm",
"build:wasm": "docker run --rm -v $(pwd):/src -e SKIP_ZLIB=\"${SKIP_ZLIB}\" -e SKIP_LIBPNG=\"${SKIP_LIBPNG}\" trzeci/emscripten ./build.sh" "build:wasm": "docker run --rm -v $(pwd):/src trzeci/emscripten-upstream ./build.sh"
}, },
"tarDependencies": { "tarDependencies": {
"node_modules/optipng": { "node_modules/optipng": {
@@ -11,12 +11,7 @@
"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"
} }
} }

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "squooshresize" name = "resize"
version = "0.1.0" version = "0.1.0"
authors = ["Surma <surma@surma.link>"] authors = ["Surma <surma@surma.link>"]

View File

@@ -1,18 +1,9 @@
FROM ubuntu
RUN apt-get update && \
apt-get install -qqy git build-essential cmake python2.7
RUN git clone --recursive https://github.com/WebAssembly/wabt /usr/src/wabt
RUN mkdir -p /usr/src/wabt/build
WORKDIR /usr/src/wabt/build
RUN cmake .. -DCMAKE_INSTALL_PREFIX=/opt/wabt && \
make && \
make install
FROM rust FROM rust
RUN rustup install nightly && \ RUN rustup target add wasm32-unknown-unknown
rustup target add --toolchain nightly wasm32-unknown-unknown && \ RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
cargo install wasm-pack
COPY --from=0 /opt/wabt /opt/wabt RUN mkdir /opt/wabt && \
ENV PATH="/opt/wabt/bin:${PATH}" 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 WORKDIR /src

View File

@@ -1,53 +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
self = global = this;
load("./pkg/resize.js");
async function init() {
// Adjustable constants.
const inputDimensions = 2000;
const outputDimensions = 1500;
const algorithm = 3; // Lanczos
const iterations = new Array(100);
// Constants. Dont change.
const imageByteSize = inputDimensions * inputDimensions * 4;
const imageBuffer = new Uint8ClampedArray(imageByteSize);
const module = await WebAssembly.compile(readbuffer("./pkg/resize_bg.wasm"));
await wasm_bindgen(module);
[[false, false], [true, false], [false, true], [true, true]].forEach(
opts => {
print(`\npremultiplication: ${opts[0]}`);
print(`color space conversion: ${opts[1]}`);
print(`==============================`);
for (let i = 0; i < 100; i++) {
const start = Date.now();
wasm_bindgen.resize(
imageBuffer,
inputDimensions,
inputDimensions,
outputDimensions,
outputDimensions,
algorithm,
...opts
);
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, e.stack));

View File

@@ -6,9 +6,9 @@ echo "============================================="
echo "Compiling wasm" echo "Compiling wasm"
echo "=============================================" echo "============================================="
( (
rustup run nightly \ wasm-pack build
wasm-pack build --target no-modules
wasm-strip pkg/resize_bg.wasm wasm-strip pkg/resize_bg.wasm
rm pkg/.gitignore
) )
echo "=============================================" echo "============================================="
echo "Compiling wasm done" echo "Compiling wasm done"

View File

@@ -2,7 +2,6 @@
"name": "resize", "name": "resize",
"scripts": { "scripts": {
"build:image": "docker build -t squoosh-resize .", "build:image": "docker build -t squoosh-resize .",
"build": "docker run --rm -v $(pwd):/src squoosh-resize ./build.sh", "build": "docker run --rm -v $(pwd):/src squoosh-resize ./build.sh"
"benchmark": "v8 --no-liftoff --no-wasm-tier-up ./benchmark.js"
} }
} }

View File

@@ -0,0 +1,15 @@
{
"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 +1,13 @@
/* tslint:disable */ /* tslint:disable */
/** /**
* @param {Uint8Array} arg0 * @param {Uint8Array} input_image
* @param {number} arg1 * @param {number} input_width
* @param {number} arg2 * @param {number} input_height
* @param {number} arg3 * @param {number} output_width
* @param {number} arg4 * @param {number} output_height
* @param {number} arg5 * @param {number} typ_idx
* @param {boolean} arg6 * @param {boolean} premultiply
* @param {boolean} arg7 * @param {boolean} color_space_conversion
* @returns {Uint8Array} * @returns {Uint8Array}
*/ */
export function resize(arg0: Uint8Array, arg1: number, arg2: number, arg3: number, arg4: number, arg5: number, arg6: boolean, arg7: boolean): 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,114 +1,50 @@
(function() { import * as wasm from './resize_bg.wasm';
var wasm;
const __exports = {};
let cachegetUint8Memory = null;
let cachegetUint8Memory = null; function getUint8Memory() {
function getUint8Memory() { if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) { cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory;
} }
return cachegetUint8Memory;
}
let WASM_VECTOR_LEN = 0; let WASM_VECTOR_LEN = 0;
function passArray8ToWasm(arg) { function passArray8ToWasm(arg) {
const ptr = wasm.__wbindgen_malloc(arg.length * 1); const ptr = wasm.__wbindgen_malloc(arg.length * 1);
getUint8Memory().set(arg, ptr / 1); getUint8Memory().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length; WASM_VECTOR_LEN = arg.length;
return ptr; 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) { function getArrayU8FromWasm(ptr, len) {
return getUint8Memory().subarray(ptr / 1, ptr / 1 + 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;
}
let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
if (cachedGlobalArgumentPtr === null) {
cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
}
return cachedGlobalArgumentPtr;
}
let cachegetUint32Memory = null;
function getUint32Memory() {
if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
}
return cachegetUint32Memory;
}
/**
* @param {Uint8Array} arg0
* @param {number} arg1
* @param {number} arg2
* @param {number} arg3
* @param {number} arg4
* @param {number} arg5
* @param {boolean} arg6
* @param {boolean} arg7
* @returns {Uint8Array}
*/
__exports.resize = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
const ptr0 = passArray8ToWasm(arg0);
const len0 = WASM_VECTOR_LEN;
const retptr = globalArgumentPtr();
wasm.resize(retptr, ptr0, len0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
const mem = getUint32Memory();
const rustptr = mem[retptr / 4];
const rustlen = mem[retptr / 4 + 1];
const realRet = getArrayU8FromWasm(rustptr, rustlen).slice();
wasm.__wbindgen_free(rustptr, rustlen * 1);
return realRet;
};
const heap = new Array(32);
heap.fill(undefined);
heap.push(undefined, null, true, false);
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
__exports.__wbindgen_object_drop_ref = function(i) { dropObject(i); };
function init(path_or_module) {
let instantiation;
const imports = { './resize': __exports };
if (path_or_module instanceof WebAssembly.Module) {
instantiation = WebAssembly.instantiate(path_or_module, imports)
.then(instance => {
return { instance, module: path_or_module }
});
} else {
const data = fetch(path_or_module);
if (typeof WebAssembly.instantiateStreaming === 'function') {
instantiation = WebAssembly.instantiateStreaming(data, imports)
.catch(e => {
console.warn("`WebAssembly.instantiateStreaming` failed. Assuming this is because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
return data
.then(r => r.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, imports));
});
} else {
instantiation = data
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, imports));
}
}
return instantiation.then(({instance}) => {
wasm = init.wasm = instance.exports;
});
};
self.wasm_bindgen = Object.assign(init, __exports);
})();

View File

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

Binary file not shown.

View File

@@ -1,17 +1,8 @@
FROM ubuntu
RUN apt-get update && \
apt-get install -qqy git build-essential cmake python2.7
RUN git clone --recursive https://github.com/WebAssembly/wabt /usr/src/wabt
RUN mkdir -p /usr/src/wabt/build
WORKDIR /usr/src/wabt/build
RUN cmake .. -DCMAKE_INSTALL_PREFIX=/opt/wabt && \
make && \
make install
FROM rust FROM rust
RUN rustup install nightly && \ RUN rustup target add wasm32-unknown-unknown
rustup target add --toolchain nightly wasm32-unknown-unknown
COPY --from=0 /opt/wabt /opt/wabt RUN mkdir /opt/wabt && \
ENV PATH="/opt/wabt/bin:${PATH}" 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 WORKDIR /src

View File

@@ -6,8 +6,7 @@ echo "============================================="
echo "Compiling wasm" echo "Compiling wasm"
echo "=============================================" echo "============================================="
( (
rustup run nightly \ cargo build \
cargo build \
--target wasm32-unknown-unknown \ --target wasm32-unknown-unknown \
--release --release
cp target/wasm32-unknown-unknown/release/rotate.wasm . cp target/wasm32-unknown-unknown/release/rotate.wasm .

Binary file not shown.

BIN
icon-large-maskable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

1564
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"name": "squoosh", "name": "squoosh",
"version": "1.7.0", "version": "1.9.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",
@@ -16,32 +16,32 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@types/node": "10.14.9", "@types/node": "10.14.15",
"@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.3",
"@webcomponents/custom-elements": "1.2.4", "@webcomponents/custom-elements": "1.2.4",
"@webpack-cli/serve": "0.1.8", "@webpack-cli/serve": "0.1.8",
"assets-webpack-plugin": "3.9.10", "assets-webpack-plugin": "3.9.10",
"chalk": "2.4.2", "chalk": "2.4.2",
"chokidar": "3.0.1", "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.3", "copy-webpack-plugin": "5.0.4",
"critters-webpack-plugin": "2.3.0", "critters-webpack-plugin": "2.4.0",
"css-loader": "1.0.1", "css-loader": "1.0.1",
"ejs": "2.6.2", "ejs": "2.6.2",
"escape-string-regexp": "2.0.0", "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.2.0",
"file-loader": "4.0.0", "file-loader": "4.2.0",
"gzip-size": "5.1.1", "gzip-size": "5.1.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"husky": "2.4.1", "husky": "3.0.4",
"idb-keyval": "3.2.0", "idb-keyval": "3.2.0",
"linkstate": "1.1.1", "linkstate": "1.1.1",
"loader-utils": "1.2.3", "loader-utils": "1.2.3",
"mini-css-extract-plugin": "0.7.0", "mini-css-extract-plugin": "0.8.0",
"minimatch": "3.0.4", "minimatch": "3.0.4",
"node-fetch": "2.6.0", "node-fetch": "2.6.0",
"node-sass": "4.12.0", "node-sass": "4.12.0",
@@ -49,28 +49,28 @@
"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.3.0",
"pretty-bytes": "5.2.0", "pretty-bytes": "5.3.0",
"progress-bar-webpack-plugin": "1.12.1", "progress-bar-webpack-plugin": "1.12.1",
"raw-loader": "3.0.0", "raw-loader": "3.1.0",
"readdirp": "3.0.2", "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": "0.23.1", "style-loader": "1.0.0",
"terser-webpack-plugin": "1.3.0", "terser-webpack-plugin": "1.4.1",
"travis-size-report": "1.0.1", "travis-size-report": "1.1.0",
"ts-loader": "6.0.2", "ts-loader": "6.0.3",
"tslint": "5.17.0", "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": "8.0.1",
"tslint-react": "4.0.0", "tslint-react": "4.0.0",
"typed-css-modules": "0.4.2", "typed-css-modules": "0.4.2",
"typescript": "3.5.2", "typescript": "3.5.3",
"url-loader": "2.0.0", "url-loader": "2.1.0",
"webpack": "4.28.0", "webpack": "4.28.0",
"webpack-bundle-analyzer": "3.3.2", "webpack-bundle-analyzer": "3.4.1",
"webpack-cli": "3.3.4", "webpack-cli": "3.3.4",
"webpack-dev-server": "3.7.1", "webpack-dev-server": "3.8.0",
"worker-plugin": "3.1.0" "worker-plugin": "3.1.0"
} }
} }

View File

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

View File

@@ -0,0 +1,20 @@
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,4 +1,6 @@
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,
@@ -31,6 +33,18 @@ async function rotate(
async function resize( async function resize(
data: ImageData, opts: import('../resize/processor-meta').WorkerResizeOptions, data: ImageData, opts: import('../resize/processor-meta').WorkerResizeOptions,
): Promise<ImageData> { ): 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( const { resize } = await import(
/* webpackChunkName: "process-resize" */ /* webpackChunkName: "process-resize" */
'../resize/processor'); '../resize/processor');
@@ -63,7 +77,15 @@ async function webpDecode(data: ArrayBuffer): Promise<ImageData> {
return decode(data); return decode(data);
} }
const exports = { mozjpegEncode, quantize, rotate, resize, optiPngEncode, webpEncode, webpDecode }; const exports = {
mozjpegEncode,
quantize,
rotate,
resize,
optiPngEncode,
webpEncode,
webpDecode,
};
export type ProcessorWorkerApi = typeof exports; export type ProcessorWorkerApi = typeof exports;
expose(exports, self); expose(exports, self);

View File

@@ -16,6 +16,7 @@ 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;
@@ -94,14 +95,7 @@ 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._workerTimeoutId = self.setTimeout(this.terminateWorker, workerTimeout);
() => {
if (!this._worker) return;
this._worker.terminate();
this._worker = undefined;
},
workerTimeout,
);
} }
/** Abort the current job, if any */ /** Abort the current job, if any */
@@ -111,7 +105,11 @@ 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;

View File

@@ -12,8 +12,9 @@ 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;
} }
@@ -21,12 +22,21 @@ 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,
}; };
form?: HTMLFormElement; private 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!;
@@ -53,18 +63,31 @@ 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.props.aspect); this.form!.height.value = Math.round(Number(this.form!.width.value) / this.getAspect());
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.props.aspect); this.form!.height.value = Math.round(width / this.getAspect());
} }
this.reportOptions(); this.reportOptions();
@@ -74,12 +97,44 @@ 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.props.aspect); this.form!.width.value = Math.round(height * this.getAspect());
} }
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}>
@@ -95,12 +150,22 @@ export default class ResizerOptions extends Component<Props, State> {
<option value="mitchell">Mitchell</option> <option value="mitchell">Mitchell</option>
<option value="catrom">Catmull-Rom</option> <option value="catrom">Catmull-Rom</option>
<option value="triangle">Triangle (bilinear)</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

View File

@@ -1,8 +1,26 @@
type BrowserResizeMethods = 'browser-pixelated' | 'browser-low' | 'browser-medium' | 'browser-high'; type BrowserResizeMethods =
type WorkerResizeMethods = 'triangle' | 'catrom' | 'mitchell' | 'lanczos3'; | 'browser-pixelated'
const workerResizeMethods: WorkerResizeMethods[] = ['triangle', 'catrom', 'mitchell', 'lanczos3']; | 'browser-low'
| 'browser-medium'
| 'browser-high';
type WorkerResizeMethods =
| 'triangle'
| 'catrom'
| 'mitchell'
| 'lanczos3'
| 'hqx';
const workerResizeMethods: WorkerResizeMethods[] = [
'triangle',
'catrom',
'mitchell',
'lanczos3',
'hqx',
];
export type ResizeOptions = BrowserResizeOptions | WorkerResizeOptions | VectorResizeOptions; export type ResizeOptions =
| BrowserResizeOptions
| WorkerResizeOptions
| VectorResizeOptions;
export interface ResizeOptionsCommon { export interface ResizeOptionsCommon {
width: number; width: number;
@@ -29,10 +47,21 @@ export interface VectorResizeOptions extends ResizeOptionsCommon {
* *
* @param opts * @param opts
*/ */
export function isWorkerOptions(opts: ResizeOptions): opts is WorkerResizeOptions { export function isWorkerOptions(
opts: ResizeOptions,
): opts is WorkerResizeOptions {
return (workerResizeMethods as string[]).includes(opts.method); 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.

View File

@@ -1,17 +1,6 @@
import wasmUrl from '../../../codecs/resize/pkg/resize_bg.wasm';
import '../../../codecs/resize/pkg/resize';
import { WorkerResizeOptions } from './processor-meta'; import { WorkerResizeOptions } from './processor-meta';
import { getContainOffsets } from './util'; import { getContainOffsets } from './util';
import { resize as codecResize } from '../../../codecs/resize/pkg';
interface WasmBindgenExports {
resize: typeof import('../../../codecs/resize/pkg/resize').resize;
}
type WasmBindgen = ((url: string) => Promise<void>) & WasmBindgenExports;
declare var wasm_bindgen: WasmBindgen;
const ready = wasm_bindgen(wasmUrl);
function crop(data: ImageData, sx: number, sy: number, sw: number, sh: number): ImageData { function crop(data: ImageData, sx: number, sy: number, sw: number, sh: number): ImageData {
const inputPixels = new Uint32Array(data.data.buffer); const inputPixels = new Uint32Array(data.data.buffer);
@@ -41,9 +30,7 @@ export async function resize(data: ImageData, opts: WorkerResizeOptions): Promis
input = crop(input, Math.round(sx), Math.round(sy), Math.round(sw), Math.round(sh)); input = crop(input, Math.round(sx), Math.round(sy), Math.round(sw), Math.round(sh));
} }
await ready; const result = codecResize(
const result = wasm_bindgen.resize(
new Uint8Array(input.data.buffer), input.width, input.height, opts.width, opts.height, new Uint8Array(input.data.buffer), input.width, input.height, opts.width, opts.height,
resizeMethods.indexOf(opts.method), opts.premultiply, opts.linearRGB, resizeMethods.indexOf(opts.method), opts.premultiply, opts.linearRGB,
); );

View File

@@ -25,3 +25,12 @@ 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

@@ -110,7 +110,10 @@ export default class App extends Component<Props, State> {
@bind @bind
private openEditor() { private openEditor() {
if (this.state.isEditorOpen) return; if (this.state.isEditorOpen) return;
history.pushState(null, '', ROUTE_EDITOR); // Change path, but preserve query string.
const editorURL = new URL(location.href);
editorURL.pathname = ROUTE_EDITOR;
history.pushState(null, '', editorURL.href);
this.setState({ isEditorOpen: true }); this.setState({ isEditorOpen: true });
} }

View File

@@ -146,12 +146,14 @@ 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)}
aspect={source ? source.processed.width / source.processed.height : 1} inputWidth={source ? source.processed.width : 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"

View File

@@ -24,7 +24,7 @@ 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 {
BrowserResizeOptions, isWorkerOptions as isWorkerResizeOptions, BrowserResizeOptions, isWorkerOptions as isWorkerResizeOptions, isHqx, WorkerResizeOptions,
} from '../../codecs/resize/processor-meta'; } from '../../codecs/resize/processor-meta';
import './custom-els/MultiPanel'; import './custom-els/MultiPanel';
import Results from '../results'; import Results from '../results';
@@ -106,6 +106,18 @@ async function preprocessImage(
source.vectorImage, source.vectorImage,
preprocessData.resize, preprocessData.resize,
); );
} 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)) { } else if (isWorkerResizeOptions(preprocessData.resize)) {
result = await processor.workerResize(result, preprocessData.resize); result = await processor.workerResize(result, preprocessData.resize);
} else { } else {

View File

@@ -11,6 +11,12 @@
"src": "/assets/icon-large.png", "src": "/assets/icon-large.png",
"type": "image/png", "type": "image/png",
"sizes": "1024x1024" "sizes": "1024x1024"
},
{
"src": "/assets/icon-large-maskable.png",
"type": "image/png",
"sizes": "1024x1024",
"purpose": "maskable"
} }
], ],
"share_target": { "share_target": {

View File

@@ -12,7 +12,8 @@
"variable-name": [true, "check-format", "allow-leading-underscore"], "variable-name": [true, "check-format", "allow-leading-underscore"],
"no-duplicate-imports": false, "no-duplicate-imports": false,
"prefer-template": [true, "allow-single-concat"], "prefer-template": [true, "allow-single-concat"],
"import-name": false "import-name": false,
"jsx-key": false
}, },
"linterOptions": { "linterOptions": {
"exclude": [ "exclude": [

View File

@@ -1,4 +1,3 @@
const fs = require('fs');
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const CleanPlugin = require('clean-webpack-plugin'); const CleanPlugin = require('clean-webpack-plugin');
@@ -17,11 +16,7 @@ const CrittersPlugin = require('critters-webpack-plugin');
const AssetTemplatePlugin = require('./config/asset-template-plugin'); const AssetTemplatePlugin = require('./config/asset-template-plugin');
const addCssTypes = require('./config/add-css-types'); const addCssTypes = require('./config/add-css-types');
function readJson (filename) { const VERSION = require('./package.json').version;
return JSON.parse(fs.readFileSync(filename));
}
const VERSION = readJson('./package.json').version;
module.exports = async function (_, env) { module.exports = async function (_, env) {
const isProd = env.mode === 'production'; const isProd = env.mode === 'production';
@@ -147,11 +142,14 @@ module.exports = async function (_, env) {
}, },
{ {
// All the codec files define a global with the same name as their file name. `exports-loader` attaches those to `module.exports`. // All the codec files define a global with the same name as their file name. `exports-loader` attaches those to `module.exports`.
test: /\/codecs\/.*\.js$/, test: /\.js$/,
include: path.join(__dirname, 'src/codecs'),
loader: 'exports-loader' loader: 'exports-loader'
}, },
{ {
test: /\/codecs\/.*\.wasm$/, // Emscripten modules don't work with Webpack's Wasm loader.
test: /\.wasm$/,
exclude: /_bg\.wasm$/,
// This is needed to make webpack NOT process wasm files. // This is needed to make webpack NOT process wasm files.
// See https://github.com/webpack/webpack/issues/6725 // See https://github.com/webpack/webpack/issues/6725
type: 'javascript/auto', type: 'javascript/auto',
@@ -160,6 +158,11 @@ module.exports = async function (_, env) {
name: '[name].[hash:5].[ext]', name: '[name].[hash:5].[ext]',
}, },
}, },
{
// Wasm modules generated by Rust + wasm-pack work great with Webpack.
test: /_bg\.wasm$/,
type: 'webassembly/experimental',
},
{ {
test: /\.(png|svg|jpg|gif)$/, test: /\.(png|svg|jpg|gif)$/,
loader: 'file-loader', loader: 'file-loader',
@@ -172,7 +175,7 @@ module.exports = async function (_, env) {
plugins: [ plugins: [
new webpack.IgnorePlugin( new webpack.IgnorePlugin(
/(fs|crypto|path)/, /(fs|crypto|path)/,
new RegExp(`${path.sep}codecs${path.sep}`) /[/\\]codecs[/\\]/
), ),
// Pretty progressbar showing build progress: // Pretty progressbar showing build progress:
@@ -239,7 +242,7 @@ module.exports = async function (_, env) {
removeRedundantAttributes: true, removeRedundantAttributes: true,
removeComments: true removeComments: true
}, },
manifest: readJson('./src/manifest.json'), manifest: require('./src/manifest.json'),
inject: 'body', inject: 'body',
compile: true compile: true
}), }),