Compare commits

..

52 Commits

Author SHA1 Message Date
Surma
8105633ca6 Update src/client/lazy-app/util/cli-invocation-generator.ts 2020-12-07 11:17:38 +00:00
Surma
46764f3375 Show snack on error 2020-12-01 13:14:35 +00:00
Surma
0371cfd292 Add CLI button 2020-12-01 13:12:28 +00:00
Jake Archibald
3d1ecc1215 Don't restrict drag & drop to images (so it works with wp2 & JXL) 2020-11-23 14:23:21 +00:00
Jake Archibald
25fb1a9c80 Fix dev server config & redirect /editor 2020-11-23 14:20:41 +00:00
Ingvar Stepanyan
3ae1cf86f5 Autoformat staged C++ and Rust 2020-11-21 03:56:30 +00:00
Luca Versari
a699a5c4dc Fix Lossless+Progressive JXL.
Also limit the workaround for memory usage to large images.
2020-11-20 22:27:49 +00:00
Ingvar Stepanyan
613401c541 Add .gitattributes to fold generated files in PRs 2020-11-20 22:21:21 +00:00
Ingvar Stepanyan
f450373e3f Fix nightly Rust pinning; rebuild Oxi 2020-11-20 22:21:21 +00:00
Ingvar Stepanyan
750872aca6 Delete old oxipng files 2020-11-20 22:21:21 +00:00
Jason Miller
beaabe47dc Merge pull request #858 from GoogleChromeLabs/gh-actions
Switch from Travis to Github Actions
2020-11-20 17:08:51 -05:00
Ingvar Stepanyan
8f7369068c Remove sizereport step
Apparently it doesn't exist anymore, even though it was referenced in Travis.
2020-11-20 21:41:57 +00:00
Ingvar Stepanyan
10bfd60e20 Try to fix multiple OS 2020-11-20 21:39:18 +00:00
Ingvar Stepanyan
7f08348509 Delete .travis.yml 2020-11-20 21:37:38 +00:00
Ingvar Stepanyan
f77ddac652 Add Github Action 2020-11-20 21:37:00 +00:00
Jake Archibald
13631f1cfc Extra Wp2 Options (#853)
* wip

* wip

* Add extra options

* Even more options!

* Update src/features/encoders/wp2/client/index.tsx

Co-authored-by: Surma <surma@surma.dev>

Co-authored-by: Surma <surma@surma.dev>
2020-11-20 16:12:38 +00:00
Jake Archibald
f11e692d58 Unset loading on error. Fixes #855 2020-11-20 16:11:57 +00:00
Jake Archibald
f0221b626d Prettier ignore file 2020-11-20 11:51:43 +00:00
Jake Archibald
10c5ed0495 Don't prettify codec code 2020-11-20 10:48:41 +00:00
Ingvar Stepanyan
d945c79796 Use run-p for cross-platform parallel runs (#850) 2020-11-20 08:53:44 +00:00
Jake Archibald
30b628c1b9 Fixing windows build (#849)
Co-authored-by: Ingvar Stepanyan <rreverser@google.com>
2020-11-19 15:03:51 +00:00
Jake Archibald
6ebf94d1b6 Auto edge filter 2020-11-19 11:35:12 +00:00
Jake Archibald
a229662bed Change JXL defaults 2020-11-19 11:27:03 +00:00
Jake Archibald
e995b445ef Updating node version and lockfile 2020-11-19 11:20:04 +00:00
Jake Archibald
6da590c7d0 Merge branch 'rollup-build' into dev
# Conflicts:
#	_headers.ejs
#	codecs/oxipng/pkg/squoosh_oxipng_bg.js
#	src/codecs/avif/encoder.ts
#	src/codecs/oxipng/encoder.ts
#	src/codecs/processor.ts
#	src/codecs/util.ts
#	src/components/intro/imgs/logo.svg
#	src/missing-types.d.ts
#	webpack.config.js
2020-11-19 11:12:29 +00:00
Jake Archibald
56e10b3aa2 Rollup build 2020-11-19 11:00:23 +00:00
Ingvar Stepanyan
fd87ae7d2a Force-rebuild codecs with -O3
Follow-up to https://github.com/GoogleChromeLabs/squoosh/pull/838.

I have no idea what the JS+Wasm diffs in the original PR even represented if they weren't proper rebuilds, but this time I just removed node_modules in all codecs to enforce a proper, clean rebuild for each C++ codec.

(Still think the speed-ups are worth it.)
2020-11-02 17:37:35 +00:00
Ingvar Stepanyan
5df7dd7590 Update helper.Makefile 2020-11-02 13:54:41 +00:00
Ingvar Stepanyan
013946b137 Pass CODEC_DIR and LIBAOM_DIR via export
Slightly simpler than passing them in HELPER_MAKEFLAGS.
2020-11-02 13:54:41 +00:00
Ingvar Stepanyan
81c183b0d6 Restructure the AVIF directories
Change the way AVIF finds AOM from default ([avif source]/ext/aom) to custom paths. This allows us to avoid unpacking same archives into duplicate folders, and instead make multiple builds from the same source.
2020-11-02 13:54:41 +00:00
Ingvar Stepanyan
f523db6403 Try out new flags for building only AVIF encoder/decoder
See the discussion in https://github.com/AOMediaCodec/libavif/issues/254 where this was implemented.

This allows us to avoid using ERROR_ON_UNDEFINED_SYMBOLS and build a truly separate encoder/decoder libs.
2020-11-02 13:54:41 +00:00
Ingvar Stepanyan
cc6ea9e11c Switch to -O3 for C++ codecs 2020-11-02 12:46:12 +00:00
Cătălin Mariș
bd4b67037b Further optimize logo.svg (#761)
Co-authored-by: Jake Archibald <jaffathecake@gmail.com>
2020-10-15 15:48:56 +01:00
Ingvar Stepanyan
8c5c97e106 Remove obsolete @ts-ignore 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
a9d3bd71b5 Bump oxipng
Integrating some upstream fixes from my branch.
2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
0d0a9b4cdf Add COOP+COEP headers 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
f583770696 Explicitly disable HDR only for encoder 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
bae243ccdb Add feature detection to OxiPNG 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
02c113a68f Point oxipng to a patched version
Some upstream changes required for parallel build to work.
2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
600eead007 Disable parallel feature for non-parallel OxiPNG 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
05416768d5 Update oxipng build system 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
35d31f2324 Add some comments to explain Rust thread glue 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
82fadac70e Fixup import.meta in OxiPNG 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
47f9d22dd8 Switch to crossbeam-channel
Still not perfect due to usage of a static global, but this is much cleaner and more efficient thanks to proper blocking of Workers that wait for new messages instead of a manual spin-loop.
2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
9420dba3bc Parallel OxiPNG improvements
- Refactor to work around Chromium's issue with postMessage queuing. https://bugs.chromium.org/p/chromium/issues/detail?id=1075645
 - Convert codec code to TypeScript.
 - Make separate parallel and non-parallel builds.
 - Switch to nightly Rust for OxiPNG to allow parallel builds (but also reuse it for regular builds to avoid installing two toolchains).
2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
e462875807 Type fix for gesturestart event 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
0747d2c419 Rework fallback for postMessage issue
Now initialise all workers with module+memory separately, and then instead of using postMessage to send thread pointers, push them into a crossbeam-deque on the Rust side.

Rayon already depends on crossbeam-dequeue, so we're not even adding another dependency, and this model allows us to push "tasks" (thread pointers) on the main thread and pop them on worker threads in arbitrary order without sacrificing correctness.
2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
4c658b79ef OxiPNG + threads PoC 2020-10-07 20:42:48 +01:00
Ingvar Stepanyan
685558847f Multithread AVIF PoC 2020-10-07 20:42:48 +01:00
Trevor Manz
63ac34a662 Promisify emscripten modules & fix webp examples (#817) 2020-09-30 00:05:59 +01:00
Surma
42f9e4aed2 Merge pull request #828 from GoogleChromeLabs/create-dir
Ensure node_modules is created
2020-09-16 10:36:11 +01:00
Jake Archibald
e14790f0b9 Ensure node_modules is created 2020-09-16 10:24:20 +01:00
46 changed files with 1073 additions and 8691 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
/codecs/**/*.js linguist-generated=true
/codecs/*/pkg*/*.d.ts linguist-generated=true

22
.github/workflows/node.js.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- id: nvmrc
uses: browniebroke/read-nvmrc-action@v1
- uses: actions/setup-node@v1
with:
node-version: '${{ steps.nvmrc.outputs.node_version }}'
- run: npm ci
- run: npm run build

2
.nvmrc
View File

@@ -1 +1 @@
12.18.3
14.15.1

12
.prettierignore Normal file
View File

@@ -0,0 +1,12 @@
codecs
.tmp
node_modules
*.scss.d.ts
*.css.d.ts
build
*.o
# Auto-generated by lib/feature-plugin.js
src/features-worker/index.ts
src/client/lazy-app/worker-bridge/meta.ts
src/client/lazy-app/feature-meta/index.ts

View File

@@ -1,7 +0,0 @@
language: node_js
cache: npm
script: npm run build
after_success: npm run sizereport
os:
- linux
- windows

0
codecs/avif/enc/avif_enc_mt.wasm Executable file → Normal file
View File

View File

@@ -14,12 +14,33 @@ thread_local const val ImageData = val::global("ImageData");
// R, G, B, A
#define COMPONENTS_PER_PIXEL 4
#ifndef JXL_DEBUG_ON_ALL_ERROR
#define JXL_DEBUG_ON_ALL_ERROR 0
#endif
#if JXL_DEBUG_ON_ALL_ERROR
#define EXPECT_TRUE(a) \
if (!(a)) { \
fprintf(stderr, "Assertion failure (%d): %s\n", __LINE__, #a); \
return val::null(); \
}
#define EXPECT_EQ(a, b) \
{ \
int a_ = a; \
int b_ = b; \
if (a_ != b_) { \
fprintf(stderr, "Assertion failure (%d): %s (%d) != %s (%d)\n", __LINE__, #a, a_, #b, b_); \
return val::null(); \
} \
}
#else
#define EXPECT_TRUE(a) \
if (!(a)) { \
return val::null(); \
}
#define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b));
#endif
val decode(std::string data) {
std::unique_ptr<JxlDecoder,

Binary file not shown.

View File

@@ -38,7 +38,14 @@ val encode(std::string image, int width, int height, JXLOptions options) {
// Reduce memory usage of tree learning for lossless data.
// TODO(veluca93): this is a mitigation for excessive memory usage in the JXL encoder.
cparams.options.nb_repeats = 0.1;
float megapixels = width * height * 0.000001;
if (megapixels > 8) {
cparams.options.nb_repeats = 0.1;
} else if (megapixels > 4) {
cparams.options.nb_repeats = 0.3;
} else {
// default is OK.
}
float quality = options.quality;
@@ -59,8 +66,10 @@ val encode(std::string image, int width, int height, JXLOptions options) {
if (options.progressive) {
cparams.qprogressive_mode = true;
cparams.progressive_dc = 1;
cparams.responsive = 1;
if (!cparams.modular_mode) {
cparams.progressive_dc = 1;
}
}
if (cparams.modular_mode) {

Binary file not shown.

View File

@@ -247,18 +247,18 @@ checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
[[package]]
name = "libdeflate-sys"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e39efa87b84db3e13ff4e2dfac1e57220abcbd7fe8ec44d238f7f4f787cc1f"
checksum = "2f5b1582a0ebf8c55a46166c04d7c66f6bb17add3a6cbf69a082ac2219f31671"
dependencies = [
"cc",
]
[[package]]
name = "libdeflater"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4810980d791f26d470e2d7d91a3d4d22aa3a4b709fb7e9c5e43ee54f83a01f2"
checksum = "93edd93a53970951da84ef733a8b6e30189a8f8a9e19610f69e4cc5bb1f4d654"
dependencies = [
"libdeflate-sys",
]
@@ -359,9 +359,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]]
name = "oxipng"
version = "4.0.0"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea40b366cecfce76ee3b082e7e6567b82cdef75644a22442ca8584bc666ff4eb"
checksum = "9fefb26bde273c3db896a313151301a69e698a7495ee577fe2168ed7065c29c4"
dependencies = [
"bit-vec",
"byteorder",

4
codecs/oxipng/build.sh Executable file → Normal file
View File

@@ -3,6 +3,8 @@
set -e
rm -rf pkg,{-parallel}
wasm-pack build --target web
wasm-pack build -t web
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' wasm-pack build -t web -d pkg-parallel -- -Z build-std=panic_abort,std --features=parallel
# Workaround https://github.com/rustwasm/wasm-bindgen/issues/2133:
sed -i "s|maybe_memory:|maybe_memory?:|" pkg-parallel/squoosh_oxipng.d.ts
rm pkg{,-parallel}/.gitignore

View File

@@ -1,6 +1,6 @@
{
"name": "oxipng",
"scripts": {
"build": "RUST_IMG=rustlang/rust:8bb115b1090d ../build-rust.sh ./build.sh"
"build": "RUST_IMG=rustlang/rust@sha256:744aeea5a38f95aa7a96ec37269a65f0c6197a1cdd87d6534e12bb869141d807 ../build-rust.sh ./build.sh"
}
}

View File

@@ -12,4 +12,4 @@
"module": "squoosh_oxipng.js",
"types": "squoosh_oxipng.d.ts",
"sideEffects": false
}
}

View File

@@ -1,29 +1,24 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
export function optimise(data: Uint8Array, level: number): Uint8Array;
/**
* @param {number} num
* @returns {any}
*/
* @param {number} num
* @returns {any}
*/
export function worker_initializer(num: number): any;
/**
*/
*/
export function start_main_thread(): void;
/**
*/
*/
export function start_worker_thread(): void;
export type InitInput =
| RequestInfo
| URL
| Response
| BufferSource
| WebAssembly.Module;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly optimise: (a: number, b: number, c: number, d: number) => void;
@@ -39,15 +34,13 @@ export interface InitOutput {
}
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
* @param {WebAssembly.Memory} maybe_memory
*
* @returns {Promise<InitOutput>}
*/
export default function init(
module_or_path?: InitInput | Promise<InitInput>,
maybe_memory?: WebAssembly.Memory,
): Promise<InitOutput>;
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
* @param {WebAssembly.Memory} maybe_memory
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>, maybe_memory?: WebAssembly.Memory): Promise<InitOutput>;

View File

@@ -1,3 +1,4 @@
let wasm;
let memory;
@@ -8,189 +9,172 @@ heap.push(undefined, null, true, false);
let heap_next = heap.length;
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
heap[idx] = obj;
return idx;
}
let cachedTextDecoder = new TextDecoder('utf-8', {
ignoreBOM: true,
fatal: true,
});
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (
cachegetUint8Memory0 === null ||
cachegetUint8Memory0.buffer !== wasm.__wbindgen_export_0.buffer
) {
cachegetUint8Memory0 = new Uint8Array(wasm.__wbindgen_export_0.buffer);
}
return cachegetUint8Memory0;
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.__wbindgen_export_0.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.__wbindgen_export_0.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().slice(ptr, ptr + len));
return cachedTextDecoder.decode(getUint8Memory0().slice(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (
cachegetInt32Memory0 === null ||
cachegetInt32Memory0.buffer !== wasm.__wbindgen_export_0.buffer
) {
cachegetInt32Memory0 = new Int32Array(wasm.__wbindgen_export_0.buffer);
}
return cachegetInt32Memory0;
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.__wbindgen_export_0.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.__wbindgen_export_0.buffer);
}
return cachegetInt32Memory0;
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
export function optimise(data, level) {
try {
const retptr = wasm.__wbindgen_export_1.value - 16;
wasm.__wbindgen_export_1.value = retptr;
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
var len0 = WASM_VECTOR_LEN;
wasm.optimise(retptr, ptr0, len0, level);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
} finally {
wasm.__wbindgen_export_1.value += 16;
}
try {
const retptr = wasm.__wbindgen_export_1.value - 16;
wasm.__wbindgen_export_1.value = retptr;
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
var len0 = WASM_VECTOR_LEN;
wasm.optimise(retptr, ptr0, len0, level);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
} finally {
wasm.__wbindgen_export_1.value += 16;
}
}
function getObject(idx) {
return heap[idx];
}
function getObject(idx) { return heap[idx]; }
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
const ret = getObject(idx);
dropObject(idx);
return ret;
}
/**
* @param {number} num
* @returns {any}
*/
* @param {number} num
* @returns {any}
*/
export function worker_initializer(num) {
var ret = wasm.worker_initializer(num);
return takeObject(ret);
var ret = wasm.worker_initializer(num);
return takeObject(ret);
}
/**
*/
*/
export function start_main_thread() {
wasm.start_main_thread();
wasm.start_main_thread();
}
/**
*/
*/
export function start_worker_thread() {
wasm.start_worker_thread();
wasm.start_worker_thread();
}
async function load(module, imports, maybe_memory) {
if (typeof Response === 'function' && module instanceof Response) {
memory = imports.wbg.memory = new WebAssembly.Memory({
initial: 17,
maximum: 16384,
shared: true,
});
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn(
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
e,
);
} else {
throw e;
if (typeof Response === 'function' && module instanceof Response) {
memory = imports.wbg.memory = new WebAssembly.Memory({initial:17,maximum:16384,shared:true});
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
memory = imports.wbg.memory = maybe_memory;
const instance = await WebAssembly.instantiate(module, imports);
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
memory = imports.wbg.memory = maybe_memory;
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
}
async function init(input, maybe_memory) {
if (typeof input === 'undefined') {
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_module = function () {
var ret = init.__wbindgen_wasm_module;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_memory = function () {
var ret = wasm.__wbindgen_export_0;
return addHeapObject(ret);
};
imports.wbg.__wbg_of_6510501edc06d65e = function (arg0, arg1) {
var ret = Array.of(takeObject(arg0), takeObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
if (typeof input === 'undefined') {
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_module = function() {
var ret = init.__wbindgen_wasm_module;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_memory = function() {
var ret = wasm.__wbindgen_export_0;
return addHeapObject(ret);
};
imports.wbg.__wbg_of_6510501edc06d65e = function(arg0, arg1) {
var ret = Array.of(takeObject(arg0), takeObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
if (
typeof input === 'string' ||
(typeof Request === 'function' && input instanceof Request) ||
(typeof URL === 'function' && input instanceof URL)
) {
input = fetch(input);
}
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
const { instance, module } = await load(await input, imports, maybe_memory);
const { instance, module } = await load(await input, imports, maybe_memory);
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
wasm.__wbindgen_start();
return wasm;
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
wasm.__wbindgen_start();
return wasm;
}
export default init;

View File

@@ -1,18 +1,13 @@
/* tslint:disable */
/* eslint-disable */
/**
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
export function optimise(data: Uint8Array, level: number): Uint8Array;
export type InitInput =
| RequestInfo
| URL
| Response
| BufferSource
| WebAssembly.Module;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
@@ -24,13 +19,12 @@ export interface InitOutput {
}
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init(
module_or_path?: InitInput | Promise<InitInput>,
): Promise<InitOutput>;
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;

View File

@@ -1,126 +1,118 @@
let wasm;
let cachedTextDecoder = new TextDecoder('utf-8', {
ignoreBOM: true,
fatal: true,
});
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (
cachegetUint8Memory0 === null ||
cachegetUint8Memory0.buffer !== wasm.memory.buffer
) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
const ptr = malloc(arg.length * 1);
getUint8Memory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (
cachegetInt32Memory0 === null ||
cachegetInt32Memory0.buffer !== wasm.memory.buffer
) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
* @param {Uint8Array} data
* @param {number} level
* @returns {Uint8Array}
*/
export function optimise(data, level) {
try {
const retptr = wasm.__wbindgen_export_0.value - 16;
wasm.__wbindgen_export_0.value = retptr;
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
var len0 = WASM_VECTOR_LEN;
wasm.optimise(retptr, ptr0, len0, level);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
} finally {
wasm.__wbindgen_export_0.value += 16;
}
try {
const retptr = wasm.__wbindgen_export_0.value - 16;
wasm.__wbindgen_export_0.value = retptr;
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
var len0 = WASM_VECTOR_LEN;
wasm.optimise(retptr, ptr0, len0, level);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
} finally {
wasm.__wbindgen_export_0.value += 16;
}
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn(
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
e,
);
} else {
throw e;
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
}
async function init(input) {
if (typeof input === 'undefined') {
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
if (typeof input === 'undefined') {
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
if (
typeof input === 'string' ||
(typeof Request === 'function' && input instanceof Request) ||
(typeof URL === 'function' && input instanceof URL)
) {
input = fetch(input);
}
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
const { instance, module } = await load(await input, imports);
const { instance, module } = await load(await input, imports);
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
return wasm;
return wasm;
}
export default init;

View File

@@ -1,5 +1,5 @@
use wasm_bindgen::prelude::*;
use oxipng::AlphaOptim;
use wasm_bindgen::prelude::*;
mod malloc_shim;
@@ -8,14 +8,14 @@ pub mod parallel;
#[wasm_bindgen]
pub fn optimise(data: &[u8], level: u8) -> Vec<u8> {
let mut options = oxipng::Options::from_preset(level);
options.alphas.insert(AlphaOptim::Black);
options.alphas.insert(AlphaOptim::White);
options.alphas.insert(AlphaOptim::Up);
options.alphas.insert(AlphaOptim::Down);
options.alphas.insert(AlphaOptim::Left);
options.alphas.insert(AlphaOptim::Right);
let mut options = oxipng::Options::from_preset(level);
options.alphas.insert(AlphaOptim::Black);
options.alphas.insert(AlphaOptim::White);
options.alphas.insert(AlphaOptim::Up);
options.alphas.insert(AlphaOptim::Down);
options.alphas.insert(AlphaOptim::Left);
options.alphas.insert(AlphaOptim::Right);
options.deflate = oxipng::Deflaters::Libdeflater;
oxipng::optimize_from_memory(data, &options).unwrap_throw()
options.deflate = oxipng::Deflaters::Libdeflater;
oxipng::optimize_from_memory(data, &options).unwrap_throw()
}

View File

@@ -1,4 +1,4 @@
use crossbeam_channel::{Sender, Receiver, bounded};
use crossbeam_channel::{bounded, Receiver, Sender};
use once_cell::sync::OnceCell;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
@@ -35,7 +35,8 @@ extern "C" {
// shared memory and blocks the current thread until they're all grabbed.
// 4) Provide a `worker_initializer` that is expected to be invoked from various workers,
// reads one `threadPtr` from the shared channel and starts running it.
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> = OnceCell::new();
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> =
OnceCell::new();
#[wasm_bindgen]
pub fn worker_initializer(num: usize) -> JsValue {

View File

@@ -7,7 +7,7 @@ RUN wget -qO- https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1/was
FROM $RUST_IMG AS rust
ARG RUST_IMG
RUN rustup target add wasm32-unknown-unknown
RUN if [[ $RUST_IMG = rustlang/rust:* ]] ; then rustup component add rust-src ; fi
RUN case $RUST_IMG in rustlang/rust@*) rustup component add rust-src; esac
COPY --from=wasm-tools /emsdk/upstream/bin/wasm-opt /emsdk/upstream/bin/clang /usr/local/bin/
COPY --from=wasm-tools /emsdk/upstream/lib/ /usr/local/lib/
COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/

View File

@@ -17,7 +17,10 @@ val decode(std::string buffer) {
int width, height;
std::unique_ptr<uint8_t[]> rgba(
WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height));
return rgba ? ImageData.new_(Uint8ClampedArray.new_(typed_memory_view(width * height * 4, rgba.get())), width, height) : val::null();
return rgba ? ImageData.new_(
Uint8ClampedArray.new_(typed_memory_view(width * height * 4, rgba.get())),
width, height)
: val::null();
}
EMSCRIPTEN_BINDINGS(my_module) {

View File

@@ -7,7 +7,31 @@ using namespace emscripten;
thread_local const val Uint8Array = val::global("Uint8Array");
val encode(std::string image_in, int image_width, int image_height, WP2::EncoderConfig config) {
struct WP2Options {
float quality;
float alpha_quality;
int speed;
int pass;
int uv_mode;
float sns;
int csp_type;
int error_diffusion;
bool use_random_matrix;
};
val encode(std::string image_in, int image_width, int image_height, WP2Options options) {
WP2::EncoderConfig config = {};
config.quality = options.quality;
config.alpha_quality = options.alpha_quality;
config.speed = options.speed;
config.pass = options.pass;
config.uv_mode = static_cast<WP2::EncoderConfig::UVMode>(options.uv_mode);
config.csp_type = static_cast<WP2::Csp>(options.csp_type);
config.sns = options.sns;
config.error_diffusion = options.error_diffusion;
config.use_random_matrix = options.use_random_matrix;
uint8_t* image_buffer = (uint8_t*)image_in.c_str();
WP2::ArgbBuffer src = WP2::ArgbBuffer();
WP2Status status =
@@ -27,12 +51,16 @@ val encode(std::string image_in, int image_width, int image_height, WP2::Encoder
}
EMSCRIPTEN_BINDINGS(my_module) {
value_object<WP2::EncoderConfig>("WP2EncoderConfig")
.field("quality", &WP2::EncoderConfig::quality)
.field("alpha_quality", &WP2::EncoderConfig::alpha_quality)
.field("speed", &WP2::EncoderConfig::speed)
.field("pass", &WP2::EncoderConfig::pass)
.field("sns", &WP2::EncoderConfig::sns);
value_object<WP2Options>("WP2Options")
.field("quality", &WP2Options::quality)
.field("alpha_quality", &WP2Options::alpha_quality)
.field("speed", &WP2Options::speed)
.field("pass", &WP2Options::pass)
.field("uv_mode", &WP2Options::uv_mode)
.field("csp_type", &WP2Options::csp_type)
.field("error_diffusion", &WP2Options::error_diffusion)
.field("use_random_matrix", &WP2Options::use_random_matrix)
.field("sns", &WP2Options::sns);
function("encode", &encode);
}

View File

@@ -4,6 +4,24 @@ export interface EncodeOptions {
speed: number;
pass: number;
sns: number;
uv_mode: UVMode;
csp_type: Csp;
error_diffusion: number;
use_random_matrix: boolean;
}
export const enum UVMode {
UVModeAdapt = 0, // Mix of 420 and 444 (per block)
UVMode420, // All blocks 420
UVMode444, // All blocks 444
UVModeAuto, // Choose any of the above automatically
}
export const enum Csp {
kYCoCg,
kYCbCr,
kCustom,
kYIQ,
}
export interface WP2Module extends EmscriptenWasm.Module {

View File

@@ -576,7 +576,7 @@ var wp2_enc = (function () {
},
});
var ob = {
p: function (a, b, c, d) {
t: function (a, b, c, d) {
A(
'Assertion failed: ' +
C(a) +
@@ -807,7 +807,7 @@ var wp2_enc = (function () {
return [];
});
},
c: function (a, b, c, d, e) {
d: function (a, b, c, d, e) {
function g(l) {
return l;
}
@@ -1000,10 +1000,10 @@ var wp2_enc = (function () {
},
});
},
q: function (a, b, c, d, e, g) {
p: function (a, b, c, d, e, g) {
Fa[a] = { name: U(b), da: Y(c, d), ea: Y(e, g), U: [] };
},
e: function (a, b, c, d, e, g, m, h, k, l) {
c: function (a, b, c, d, e, g, m, h, k, l) {
Fa[a].U.push({
X: U(b),
aa: c,
@@ -1034,7 +1034,7 @@ var wp2_enc = (function () {
m: function (a) {
4 < a && (X[a].T += 1);
},
t: function (a, b, c, d) {
q: function (a, b, c, d) {
a || W('Cannot use deleted val. handle = ' + a);
a = X[a].value;
var e = ib[b];
@@ -1079,7 +1079,7 @@ var wp2_enc = (function () {
u: function (a, b, c) {
D.copyWithin(a, b, b + c);
},
d: function (a) {
e: function (a) {
a >>>= 0;
var b = D.length;
if (2147483648 < a) return !1;

Binary file not shown.

View File

@@ -11,6 +11,7 @@
* limitations under the License.
*/
import rollup from 'rollup';
import * as path from 'path';
const prefix = 'client-bundle:';
const entryPathPlaceholder = 'CLIENT_BUNDLE_PLUGIN_ENTRY_PATH';
@@ -119,9 +120,10 @@ export default function (inputOptions, outputOptions, resolveFileUrl) {
return;
}
const id = entryPointPlaceholderMap.get(num);
const id = path.normalize(entryPointPlaceholderMap.get(num));
const clientEntry = clientOutput.find(
(item) => item.facadeModuleId === id,
(item) =>
item.facadeModuleId && path.normalize(item.facadeModuleId) === id,
);
if (property.startsWith(entryPathPlaceholder)) {

View File

@@ -18,6 +18,8 @@ import {
resolve as resolvePath,
dirname,
normalize as nomalizePath,
sep as pathSep,
posix,
} from 'path';
import postcss from 'postcss';
@@ -172,14 +174,15 @@ export default function (resolveFileUrl) {
return `export default ${cssStr};`;
}
if (id.startsWith(addPrefix)) {
const path = id.slice(addPrefix.length);
const path = nomalizePath(id.slice(addPrefix.length));
return (
`import css from 'css:${path}';\n` +
`import css from ${JSON.stringify('css:' + path)};\n` +
`import appendCss from '${appendCssModule}';\n` +
`appendCss(css);\n`
);
}
if (id.endsWith(moduleSuffix)) {
const path = nomalizePath(id.slice(0, -moduleSuffix.length));
if (!pathToResult.has(id)) {
throw Error(`Cannot find ${id} in pathToResult`);
}

View File

@@ -19,15 +19,16 @@ import glob from 'glob';
const globP = promisify(glob);
const moduleId = 'initial-css:';
const initialCssModule = '\0initialCss';
export default function initialCssPlugin() {
return {
name: 'initial-css-plugin',
resolveId(id) {
if (id === moduleId) return moduleId;
if (id === moduleId) return initialCssModule;
},
async load(id) {
if (id !== moduleId) return;
if (id !== initialCssModule) return;
const matches = await globP('shared/initial-app/**/*.css', {
nodir: true,

View File

@@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { posix as pathUtils } from 'path';
import { posix as pathUtils, isAbsolute } from 'path';
export default function resolveDirs(paths) {
const pathBaseDir = paths.map((path) => [
@@ -30,6 +30,7 @@ export default function resolveDirs(paths) {
if (!resolveResult) {
throw new Error(`Couldn't find ${'./' + id}`);
}
if (isAbsolute(resolveResult.id)) return resolveResult.id;
return pathUtils.resolve(resolveResult.id);
},
};

View File

@@ -127,15 +127,6 @@ export default function simpleTS(mainPath, { noBuild, watch } = {}) {
relative(process.cwd(), id),
).replace(extRe, '.js');
console.log(
`simple-ts mapping`,
id,
'to',
newId,
'with outDir',
config.options.outDir,
);
return fsp.readFile(newId, { encoding: 'utf8' });
},
};

8538
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,9 @@
"scripts": {
"build": "rollup -c && node lib/move-output.js",
"debug": "node --inspect-brk node_modules/.bin/rollup -c",
"dev": "rollup -cw & npm run serve",
"serve": "serve --config server.json .tmp/build/static"
"dev": "run-p watch serve",
"watch": "rollup -cw",
"serve": "serve --config ../../../serve.json .tmp/build/static"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^15.1.0",
@@ -25,6 +26,7 @@
"lint-staged": "^10.5.1",
"lodash.camelcase": "^4.3.0",
"mime-types": "^2.1.27",
"npm-run-all": "^4.1.5",
"pointer-tracker": "^2.4.0",
"postcss": "^7.0.35",
"postcss-modules": "^3.2.2",
@@ -47,9 +49,9 @@
}
},
"lint-staged": {
"*.{js,css,json,md,ts,tsx}": [
"prettier --write"
]
"*.{js,css,json,md,ts,tsx}": "prettier --write",
"*.{c,h,cpp,hpp}": "clang-format -i",
"*.rs": "rustfmt"
},
"dependencies": {
"wasm-feature-detect": "^1.2.9"

View File

@@ -52,7 +52,7 @@ function jsFileName(chunkInfo) {
const parsedPath = path.parse(chunkInfo.facadeModuleId);
if (parsedPath.name !== 'index') return jsPath;
// Come up with a better name than 'index'
const name = parsedPath.dir.split('/').slice(-1);
const name = parsedPath.dir.split(/\\|\//).slice(-1);
return jsPath.replace('[name]', name);
}

View File

@@ -9,5 +9,6 @@
}
]
}
]
],
"redirects": [{ "source": "/editor", "destination": "/" }]
}

View File

@@ -67,7 +67,7 @@ export default class App extends Component<Props, State> {
// really breaks things on Squoosh, as you can easily end up zooming the UI when you mean to
// zoom the image. Once you've done this, it's really difficult to undo. Anyway, this seems to
// prevent it.
document.body.addEventListener('gesturestart', (event) => {
document.body.addEventListener('gesturestart', (event: any) => {
event.preventDefault();
});
@@ -115,11 +115,7 @@ export default class App extends Component<Props, State> {
return (
<div class={style.app}>
<file-drop
accept="image/*"
onfiledrop={this.onFileDrop}
class={style.drop}
>
<file-drop onfiledrop={this.onFileDrop} class={style.drop}>
{showSpinner ? (
<loading-spinner class={style.appLoader} />
) : isEditorOpen ? (

View File

@@ -5,6 +5,7 @@ import 'add-css:./style.css';
import { cleanSet, cleanMerge } from '../../util/clean-modify';
import type { SourceImage, OutputType } from '..';
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import {
EncoderOptions,
EncoderState,
@@ -18,7 +19,10 @@ import Select from './Select';
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
import { generateCliInvocation } from '../../util/cli-invocation-generator';
interface Props {
showSnack: SnackBarElement['showSnackbar'];
mobileView: boolean;
source?: SourceImage;
encoderState?: EncoderState;
@@ -97,6 +101,22 @@ export default class Options extends Component<Props, State> {
);
};
private onCreateCLIInvocation = () => {
if (!this.props.encoderState) {
return;
}
try {
const cliInvocation = generateCliInvocation(
this.props.encoderState,
this.props.processorState,
);
navigator.clipboard.writeText(cliInvocation);
} catch (e) {
this.props.showSnack(e);
}
};
render(
{ source, encoderState, processorState, onEncoderOptionsChange }: Props,
{ supportedEncoderMap }: State,
@@ -110,7 +130,9 @@ export default class Options extends Component<Props, State> {
<Expander>
{!encoderState ? null : (
<div>
<h3 class={style.optionsTitle}>Edit</h3>
<h3 class={style.optionsTitle}>
<button onClick={this.onCreateCLIInvocation}>CLI</button>Edit
</h3>
<label class={style.sectionEnabler}>
<Checkbox
name="resize.enable"

View File

@@ -649,6 +649,7 @@ export default class Compress extends Component<Props, State> {
});
} catch (err) {
if (err.name === 'AbortError') return;
this.setState({ loading: false });
this.props.showSnack(`Preprocessing error: ${err}`);
throw err;
}
@@ -772,6 +773,12 @@ export default class Compress extends Component<Props, State> {
this.activeSideJobs[sideIndex] = undefined;
} catch (err) {
if (err.name === 'AbortError') return;
this.setState((currentState) => {
const sides = cleanMerge(currentState.sides, sideIndex, {
loading: false,
});
return { sides };
});
this.props.showSnack(`Processing error: ${err}`);
throw err;
}
@@ -779,7 +786,7 @@ export default class Compress extends Component<Props, State> {
}
render(
{ onBack }: Props,
{ onBack, showSnack }: Props,
{ loading, sides, source, mobileView, preprocessorState }: State,
) {
const [leftSide, rightSide] = sides;
@@ -787,6 +794,7 @@ export default class Compress extends Component<Props, State> {
const options = sides.map((side, index) => (
<Options
showSnack={showSnack}
source={source}
mobileView={mobileView}
processorState={side.latestSettings.processorState}

View File

@@ -0,0 +1,51 @@
/**
* Copyright 2020 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { EncoderState, ProcessorState } from '../feature-meta';
// Maps our encoder.type values to CLI parameter names
const typeMap = new Map<string, string>([
['avif', '--avif'],
["jxl", "--jxl"],
['mozJPEG', '--mozjpeg'],
['oxiPNG', '--oxipng'],
['webP', '--webp'],
["wp2", "--wp2"],
]);
// Same as JSON.stringify, but with single quotes around the entire value
// so that shells dont do weird stuff.
function cliJson<T>(v: T): string {
return "'" + JSON.stringify(v) + "'";
}
export function generateCliInvocation(
encoder: EncoderState,
processor: ProcessorState,
): string {
if (!typeMap.has(encoder.type)) {
throw Error(`Encoder ${encoder.type} is unsupported in the CLI`);
}
return [
'npx',
'@squoosh/cli',
...(processor.resize.enabled
? ['--resize', cliJson(processor.resize)]
: []),
...(processor.quantize.enabled
? ['--quant', cliJson(processor.quantize)]
: []),
typeMap.get(encoder.type)!,
cliJson(encoder.options),
].join(' ');
}

View File

@@ -315,9 +315,9 @@ export class Options extends Component<Props, State> {
value={subsample}
onChange={this._inputChange('subsample', 'number')}
>
<option value="1">4:2:0</option>
<option value="1">Half</option>
{/*<option value="2">4:2:2</option>*/}
<option value="3">4:4:4</option>
<option value="3">Off</option>
</Select>
</label>
)}

View File

@@ -27,6 +27,7 @@ interface State {
edgePreservingFilter: number;
lossless: boolean;
slightLoss: boolean;
autoEdgePreservingFilter: boolean;
}
const maxSpeed = 7;
@@ -48,9 +49,10 @@ export class Options extends Component<Props, State> {
effort: maxSpeed - options.speed,
quality: options.quality,
progressive: options.progressive,
edgePreservingFilter: options.epf,
edgePreservingFilter: options.epf === -1 ? 2 : options.epf,
lossless: options.quality === 100,
slightLoss: options.lossyPalette,
autoEdgePreservingFilter: options.epf === -1,
};
}
@@ -86,7 +88,9 @@ export class Options extends Component<Props, State> {
speed: maxSpeed - optionState.effort,
quality: optionState.lossless ? 100 : optionState.quality,
progressive: optionState.progressive,
epf: optionState.edgePreservingFilter,
epf: optionState.autoEdgePreservingFilter
? -1
: optionState.edgePreservingFilter,
nearLossless: 0,
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
};
@@ -112,6 +116,7 @@ export class Options extends Component<Props, State> {
edgePreservingFilter,
lossless,
slightLoss,
autoEdgePreservingFilter,
}: State,
) {
// I'm rendering both lossy and lossless forms, as it becomes much easier when
@@ -152,16 +157,34 @@ export class Options extends Component<Props, State> {
Quality:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
min="0"
max="3"
value={edgePreservingFilter}
onInput={this._inputChange('edgePreservingFilter', 'number')}
>
Edge preserving filter:
</Range>
</div>
<label class={style.optionInputFirst}>
<Checkbox
name="autoEdgeFilter"
checked={autoEdgePreservingFilter}
onChange={this._inputChange(
'autoEdgePreservingFilter',
'boolean',
)}
/>
Auto edge filter
</label>
<Expander>
{!autoEdgePreservingFilter && (
<div class={style.optionOneCell}>
<Range
min="0"
max="3"
value={edgePreservingFilter}
onInput={this._inputChange(
'edgePreservingFilter',
'number',
)}
>
Edge preserving filter:
</Range>
</div>
)}
</Expander>
</div>
)}
</Expander>

View File

@@ -18,10 +18,10 @@ export const label = 'JPEG XL (beta)';
export const mimeType = 'image/jpegxl';
export const extension = 'jxl';
export const defaultOptions: EncodeOptions = {
speed: 5,
quality: 50,
speed: 4,
quality: 75,
progressive: false,
epf: 2,
epf: -1,
nearLossless: 0,
lossyPalette: false,
};

View File

@@ -1,9 +1,14 @@
import { EncodeOptions } from '../shared/meta';
import { EncodeOptions, UVMode, Csp } from '../shared/meta';
import { defaultOptions } from '../shared/meta';
import type WorkerBridge from 'client/lazy-app/worker-bridge';
import { h, Component } from 'preact';
import { inputFieldValueAsNumber, preventDefault } from 'client/lazy-app/util';
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
import * as style from 'client/lazy-app/Compress/Options/style.css';
import Range from 'client/lazy-app/Compress/Options/Range';
import Select from 'client/lazy-app/Compress/Options/Select';
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
import Expander from 'client/lazy-app/Compress/Options/Expander';
import linkState from 'linkstate';
export const encode = (
signal: AbortSignal,
@@ -18,93 +23,286 @@ interface Props {
}
interface State {
options: EncodeOptions;
effort: number;
quality: number;
alphaQuality: number;
passes: number;
sns: number;
uvMode: number;
lossless: boolean;
slightLoss: number;
colorSpace: number;
errorDiffusion: number;
useRandomMatrix: boolean;
showAdvanced: boolean;
separateAlpha: boolean;
}
export class Options extends Component<Props, State> {
state: State = {
showAdvanced: false,
};
static getDerivedStateFromProps(
props: Props,
state: State,
): Partial<State> | null {
if (state.options && shallowEqual(state.options, props.options)) {
return null;
}
private onChange = (event: Event) => {
const form = (event.currentTarget as HTMLInputElement).closest(
'form',
) as HTMLFormElement;
const { options } = this.props;
const newOptions: EncodeOptions = {
quality: inputFieldValueAsNumber(form.quality, options.quality),
alpha_quality: inputFieldValueAsNumber(
form.alpha_quality,
options.alpha_quality,
),
speed: inputFieldValueAsNumber(form.speed, options.speed),
pass: inputFieldValueAsNumber(form.pass, options.pass),
sns: inputFieldValueAsNumber(form.sns, options.sns),
const { options } = props;
const modifyState: Partial<State> = {
options,
effort: options.speed,
alphaQuality: options.alpha_quality,
passes: options.pass,
sns: options.sns,
uvMode: options.uv_mode,
colorSpace: options.csp_type,
errorDiffusion: options.error_diffusion,
useRandomMatrix: options.use_random_matrix,
separateAlpha: options.quality !== options.alpha_quality,
};
this.props.onChange(newOptions);
// If quality is > 95, it's lossless with slight loss
if (options.quality > 95) {
modifyState.lossless = true;
modifyState.slightLoss = 100 - options.quality;
} else {
modifyState.quality = options.quality;
modifyState.lossless = false;
}
return modifyState;
}
// Other state is set in getDerivedStateFromProps
state: State = {
lossless: false,
slightLoss: 0,
quality: defaultOptions.quality,
showAdvanced: false,
} as State;
private _inputChangeCallbacks = new Map<string, (event: Event) => void>();
private _inputChange = (prop: keyof State, type: 'number' | 'boolean') => {
// Cache the callback for performance
if (!this._inputChangeCallbacks.has(prop)) {
this._inputChangeCallbacks.set(prop, (event: Event) => {
const formEl = event.target as HTMLInputElement | HTMLSelectElement;
const newVal =
type === 'boolean'
? 'checked' in formEl
? formEl.checked
: !!formEl.value
: Number(formEl.value);
const newState: Partial<State> = {
[prop]: newVal,
};
const optionState = {
...this.state,
...newState,
};
const newOptions: EncodeOptions = {
speed: optionState.effort,
quality: optionState.lossless
? 100 - optionState.slightLoss
: optionState.quality,
alpha_quality: optionState.separateAlpha
? optionState.alphaQuality
: optionState.quality,
pass: optionState.passes,
sns: optionState.sns,
uv_mode: optionState.uvMode,
csp_type: optionState.colorSpace,
error_diffusion: optionState.errorDiffusion,
use_random_matrix: optionState.useRandomMatrix,
};
// Updating options, so we don't recalculate in getDerivedStateFromProps.
newState.options = newOptions;
this.setState(newState);
this.props.onChange(newOptions);
});
}
return this._inputChangeCallbacks.get(prop)!;
};
render({ options }: Props) {
render(
{}: Props,
{
effort,
alphaQuality,
passes,
quality,
sns,
uvMode,
lossless,
slightLoss,
colorSpace,
errorDiffusion,
useRandomMatrix,
separateAlpha,
showAdvanced,
}: State,
) {
return (
<form class={style.optionsSection} onSubmit={preventDefault}>
<label class={style.optionInputFirst}>
<Checkbox
checked={lossless}
onChange={this._inputChange('lossless', 'boolean')}
/>
Lossless
</label>
<Expander>
{lossless && (
<div class={style.optionOneCell}>
<Range
min="0"
max="5"
step="0.1"
value={slightLoss}
onInput={this._inputChange('slightLoss', 'number')}
>
Slight loss:
</Range>
</div>
)}
</Expander>
<Expander>
{!lossless && (
<div>
<div class={style.optionOneCell}>
<Range
min="0"
max="95"
step="0.1"
value={quality}
onInput={this._inputChange('quality', 'number')}
>
Quality:
</Range>
</div>
<label class={style.optionInputFirst}>
<Checkbox
checked={separateAlpha}
onChange={this._inputChange('separateAlpha', 'boolean')}
/>
Separate alpha quality
</label>
<Expander>
{separateAlpha && (
<div class={style.optionOneCell}>
<Range
min="0"
max="100"
step="1"
value={alphaQuality}
onInput={this._inputChange('alphaQuality', 'number')}
>
Alpha Quality:
</Range>
</div>
)}
</Expander>
<label class={style.optionInputFirst}>
<Checkbox
checked={showAdvanced}
onChange={linkState(this, 'showAdvanced')}
/>
Show advanced settings
</label>
<Expander>
{showAdvanced && (
<div>
<div class={style.optionOneCell}>
<Range
min="1"
max="10"
step="1"
value={passes}
onInput={this._inputChange('passes', 'number')}
>
Passes:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
min="0"
max="100"
step="1"
value={sns}
onInput={this._inputChange('sns', 'number')}
>
Spatial noise shaping:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
min="0"
max="100"
step="1"
value={errorDiffusion}
onInput={this._inputChange('errorDiffusion', 'number')}
>
Error diffusion:
</Range>
</div>
<label class={style.optionTextFirst}>
Subsample chroma:
<Select
value={uvMode}
onInput={this._inputChange('uvMode', 'number')}
>
<option value={UVMode.UVModeAuto}>Auto</option>
<option value={UVMode.UVModeAdapt}>Vary</option>
<option value={UVMode.UVMode420}>Half</option>
<option value={UVMode.UVMode444}>Off</option>
</Select>
</label>
<label class={style.optionTextFirst}>
Color space:
<Select
value={colorSpace}
onInput={this._inputChange('colorSpace', 'number')}
>
<option value={Csp.kYCoCg}>YCoCg</option>
<option value={Csp.kYCbCr}>YCbCr</option>
<option value={Csp.kYIQ}>YIQ</option>
</Select>
</label>
<label class={style.optionInputFirst}>
<Checkbox
checked={useRandomMatrix}
onChange={this._inputChange(
'useRandomMatrix',
'boolean',
)}
/>
Random matrix
</label>
</div>
)}
</Expander>
</div>
)}
</Expander>
<div class={style.optionOneCell}>
<Range
name="quality"
min="0"
max="100"
step="1"
value={options.quality}
onInput={this.onChange}
>
Quality:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
name="alpha_quality"
min="0"
max="100"
step="1"
value={options.alpha_quality}
onInput={this.onChange}
>
Alpha Quality:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
name="speed"
min="0"
max="9"
step="1"
value={options.speed}
onInput={this.onChange}
value={effort}
onInput={this._inputChange('effort', 'number')}
>
Speed:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
name="pass"
min="1"
max="10"
step="1"
value={options.pass}
onInput={this.onChange}
>
Pass:
</Range>
</div>
<div class={style.optionOneCell}>
<Range
name="sns"
min="0"
max="100"
step="1"
value={options.sns}
onInput={this.onChange}
>
Spatial noise shaping:
Effort:
</Range>
</div>
</form>

View File

@@ -11,16 +11,21 @@
* limitations under the License.
*/
import type { EncodeOptions } from 'codecs/wp2/enc/wp2_enc';
import { UVMode, Csp } from 'codecs/wp2/enc/wp2_enc';
export { EncodeOptions };
export { EncodeOptions, UVMode, Csp };
export const label = 'WebP v2 (unstable)';
export const mimeType = 'image/webp2';
export const extension = 'wp2';
export const defaultOptions: EncodeOptions = {
quality: 75,
alpha_quality: 100,
alpha_quality: 75,
speed: 5,
pass: 1,
sns: 50,
uv_mode: UVMode.UVModeAuto,
csp_type: Csp.kYCoCg,
error_diffusion: 0,
use_random_matrix: false,
};