Compare commits

..

60 Commits

Author SHA1 Message Date
Jake Archibald
7346511fa1 Merge v2 codecs (#844)
* wip

* doh, whitespace

* Updating emscripten, restoring export name

* Updating oxipng

* Build wasm

* Fix oxipng; upgrade Rust

* More v2-codec integration

* AVIF now working

* Non-working JXL

* Build hqx with Rust 1.40; refactor build-rust*.sh

* Set web target

* wp2 wip

* wp2 decode options

* Better logo height when loading the logo into squoosh

* Build oxi

* JAKE IS AN IDIOT

* wip oxipng

* Fixing case sensitive imports

* adding log

* another log

* Abort tasks when compress component removed

* Adding progressive option to JXL

* Fix bug going to & from original image

* Exposing epf in jxl

* logs

* Bypass initial CSS plugin

* Revert "logs"

* Adding root

* Fix for finding TSC on Windows

* Use spawn again

* Converting to module paths

* Remove spawnP

* silly

* oops

* logs

* Fixing glob paths in CSS plugin

* Path normalising

* Normalise paths for CSS plugin

* Normalise again

* Use correct func

* Adding lossless mode and near lossless (but hidden in UI)

* Removing useless comments

* Some logging

* Update JXL to v0.1. (#846)

* Rebuild JXL

* Adding slight loss option

Co-authored-by: Ingvar Stepanyan <rreverser@google.com>
Co-authored-by: Luca Versari <veluca93@gmail.com>
2020-11-19 10:55:43 +00:00
Jake Archibald
39ca054112 Change case of dirs 2020-11-12 15:10:05 +00:00
Jake Archibald
1b80dd23ee Patching omt for the service worker 2020-11-12 14:00:10 +00:00
Jake Archibald
d1ee92e884 Inline initial script 2020-11-12 13:59:50 +00:00
Jake Archibald
f1da577ba3 Avoid double builds 2020-11-12 12:28:34 +00:00
Jake Archibald
52b3d0063f Avoid redundant empty file 2020-11-11 16:57:47 +00:00
Jake Archibald
eb9d0d186c Better checking of processor equivalence 2020-11-11 16:49:00 +00:00
Jake Archibald
964390501e Fix initial loading state 2020-11-11 16:16:24 +00:00
Jake Archibald
b412d4c2a5 Moving component instantiation to avoid preact removing it 2020-11-11 15:59:29 +00:00
Jake Archibald
7dfc9310ba This is wrong 2020-11-11 15:50:17 +00:00
Jake Archibald
b38069695a Quantizer bug fix 2020-11-11 15:35:17 +00:00
Jake Archibald
2d21406484 Removing old code, some bugfixes 2020-11-11 15:28:57 +00:00
Jake Archibald
56f9d4b8c8 Bug fixes 2020-11-11 14:49:39 +00:00
Jake Archibald
be4601b93a All options 2020-11-11 12:12:05 +00:00
Jake Archibald
196e6e1aea Adding output 2020-11-10 13:12:31 +00:00
Jake Archibald
6d0d9dc022 Clear loading states 2020-11-10 11:57:28 +00:00
Jake Archibald
324c2b6cab Compress module done (aside from imports) 2020-11-10 11:45:30 +00:00
Jake Archibald
b6fd14b6d3 Progress on single process pass 2020-11-09 18:02:18 +00:00
Jake Archibald
9111aa89ae More work on compress 2020-11-09 12:14:16 +00:00
Jake Archibald
c17b5c36c6 The client needs to be able to see types from the worker 2020-11-09 11:53:49 +00:00
Jake Archibald
4502abb069 Probably should include the client code in the client 2020-11-09 10:58:08 +00:00
Jake Archibald
5a027c7727 Much simpler project structure 2020-11-09 10:54:35 +00:00
Jake Archibald
4da1887826 Processor work 2020-11-09 10:29:25 +00:00
Jake Archibald
b1a639c182 Processors and encoding 2020-11-08 13:25:14 +00:00
Jake Archibald
6dbb182f7b Auto generate client tsconfig 2020-11-08 13:15:25 +00:00
Jake Archibald
a360191759 Standard shape for encoders 2020-11-08 12:01:33 +00:00
Jake Archibald
1cb1c16fa2 Refactor resize 2020-11-06 16:47:37 +00:00
Jake Archibald
ec586bb529 Structure for browser decoders. Some work on decoding. 2020-11-06 13:05:12 +00:00
Jake Archibald
4b8c0178fe Generating processor metadata 2020-11-06 10:25:26 +00:00
Jake Archibald
0d298e3e0a Getting things in to a buildable state 2020-10-07 15:29:15 +01:00
Jake Archibald
13c5b76af6 Adding oxi shared code 2020-10-07 14:29:23 +01:00
Jake Archibald
7836d7e97c Generating feature metadata 2020-09-29 17:56:19 +01:00
Jake Archibald
db2d6f4ca6 Fix worker bridge code 2020-09-29 16:44:58 +01:00
Jake Archibald
b11ae0b8c7 Missing args 2020-09-29 16:36:13 +01:00
Jake Archibald
81d0b38dbd CSS generate comment 2020-09-25 13:57:43 +01:00
Jake Archibald
b47d6b4696 Moving worker and worker bridge 2020-09-25 13:49:58 +01:00
Jake Archibald
f96ae9bdee wip 2020-09-25 13:04:39 +01:00
Jake Archibald
4d8efcea66 SW bits that make it good enough for now 2020-09-25 09:38:19 +01:00
Jake Archibald
e11b4cf22c Service worker building (but not quite right yet) 2020-09-24 17:35:02 +01:00
Jake Archibald
3f2466f44d Re-enable terser 2020-09-24 15:20:13 +01:00
Jake Archibald
778aa41f0d Prerender & client render of intro 2020-09-24 15:19:57 +01:00
Jake Archibald
486957443d Downgrade postcss for modules support 2020-09-24 13:30:53 +01:00
Jake Archibald
02e4eaf4b5 CSS plugin 2020-09-24 11:49:41 +01:00
Jake Archibald
f5d9023ff3 Update postcss 2020-09-23 14:44:13 +01:00
Jake Archibald
455c868e55 First bit of real UI code landed 2020-09-23 14:38:41 +01:00
Jake Archibald
6573103755 Add manifest 2020-09-23 10:58:50 +01:00
Jake Archibald
a30e38856e Avoid infinite rebuilds 2020-09-22 17:48:51 +01:00
Jake Archibald
d2807ebb18 Integrate Oxi 2020-09-22 17:35:47 +01:00
Jake Archibald
fd151fc70d Add hqx 2020-09-22 17:16:55 +01:00
Jake Archibald
812e727de0 Integrate rotate 2020-09-22 14:40:27 +01:00
Jake Archibald
2a6a83f56d Resize working 2020-09-21 15:19:38 +01:00
Jake Archibald
21fc70cbdd wip 2020-09-21 14:25:08 +01:00
Jake Archibald
d9e1177cd8 wip on rust wasm integration 2020-09-21 14:23:04 +01:00
Jake Archibald
300809fdcb Client/shared/worker split for resize 2020-09-21 13:01:07 +01:00
Jake Archibald
7540a15f8d Features folder 2020-09-18 16:28:56 +01:00
Jake Archibald
f92e3c2194 Move encode options definition to the wasm 2020-09-16 12:30:26 +01:00
Jake Archibald
7776134bc2 AVIF in worker 2020-09-16 11:48:13 +01:00
Jake Archibald
2583d689b9 AVIF to module 2020-09-16 11:04:00 +01:00
Jake Archibald
25102095aa Update webp from main branch 2020-09-16 11:03:19 +01:00
Jake Archibald
a6477b82fc wip
# Conflicts:
#	codecs/cpp.Dockerfile
#	codecs/imagequant/example.html
#	codecs/webp/dec/webp_dec.d.ts
#	codecs/webp/dec/webp_dec.js
#	codecs/webp/dec/webp_dec.wasm
#	codecs/webp/enc/webp_enc.d.ts
#	codecs/webp/enc/webp_enc.js
#	codecs/webp/enc/webp_enc.wasm
#	package-lock.json
#	package.json
#	src/codecs/tiny.webp
#	src_old/codecs/encoders.ts
#	src_old/codecs/processor-worker/tiny.avif
#	src_old/codecs/processor-worker/tiny.webp
#	src_old/codecs/tiny.webp
#	src_old/components/compress/index.tsx
#	src_old/lib/util.ts
#	src_old/sw/util.ts
2020-09-16 10:08:50 +01:00
92 changed files with 9298 additions and 2332 deletions

2
.gitattributes vendored
View File

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

View File

@@ -1,22 +0,0 @@
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 @@
14.15.1 12.18.3

View File

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

7
.travis.yml Normal file
View File

@@ -0,0 +1,7 @@
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 Normal file → Executable file
View File

View File

@@ -14,33 +14,12 @@ thread_local const val ImageData = val::global("ImageData");
// R, G, B, A // R, G, B, A
#define COMPONENTS_PER_PIXEL 4 #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) \ #define EXPECT_TRUE(a) \
if (!(a)) { \ if (!(a)) { \
return val::null(); \ return val::null(); \
} }
#define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b)); #define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b));
#endif
val decode(std::string data) { val decode(std::string data) {
std::unique_ptr<JxlDecoder, std::unique_ptr<JxlDecoder,

Binary file not shown.

View File

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

Binary file not shown.

View File

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

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

@@ -3,8 +3,6 @@
set -e set -e
rm -rf pkg,{-parallel} rm -rf pkg,{-parallel}
wasm-pack build -t web wasm-pack build --target web
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' wasm-pack build -t web -d pkg-parallel -- -Z build-std=panic_abort,std --features=parallel 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 rm pkg{,-parallel}/.gitignore

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
use crossbeam_channel::{bounded, Receiver, Sender}; use crossbeam_channel::{Sender, Receiver, bounded};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
@@ -35,8 +35,7 @@ extern "C" {
// shared memory and blocks the current thread until they're all grabbed. // 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, // 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. // reads one `threadPtr` from the shared channel and starts running it.
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> = static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> = OnceCell::new();
OnceCell::new();
#[wasm_bindgen] #[wasm_bindgen]
pub fn worker_initializer(num: usize) -> JsValue { 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 FROM $RUST_IMG AS rust
ARG RUST_IMG ARG RUST_IMG
RUN rustup target add wasm32-unknown-unknown RUN rustup target add wasm32-unknown-unknown
RUN case $RUST_IMG in rustlang/rust@*) rustup component add rust-src; esac RUN if [[ $RUST_IMG = rustlang/rust:* ]] ; then rustup component add rust-src ; fi
COPY --from=wasm-tools /emsdk/upstream/bin/wasm-opt /emsdk/upstream/bin/clang /usr/local/bin/ 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/lib/ /usr/local/lib/
COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/ COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/

View File

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

View File

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

View File

@@ -4,24 +4,6 @@ export interface EncodeOptions {
speed: number; speed: number;
pass: number; pass: number;
sns: 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 { export interface WP2Module extends EmscriptenWasm.Module {

View File

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

Binary file not shown.

View File

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

View File

@@ -18,8 +18,6 @@ import {
resolve as resolvePath, resolve as resolvePath,
dirname, dirname,
normalize as nomalizePath, normalize as nomalizePath,
sep as pathSep,
posix,
} from 'path'; } from 'path';
import postcss from 'postcss'; import postcss from 'postcss';
@@ -41,7 +39,6 @@ const assetRe = new RegExp('/fake/path/to/asset/([^/]+)/', 'g');
const appendCssModule = '\0appendCss'; const appendCssModule = '\0appendCss';
const appendCssSource = ` const appendCssSource = `
export default function appendCss(css) { export default function appendCss(css) {
if (__PRERENDER__) return;
const style = document.createElement('style'); const style = document.createElement('style');
style.textContent = css; style.textContent = css;
document.head.append(style); document.head.append(style);
@@ -175,15 +172,14 @@ export default function (resolveFileUrl) {
return `export default ${cssStr};`; return `export default ${cssStr};`;
} }
if (id.startsWith(addPrefix)) { if (id.startsWith(addPrefix)) {
const path = nomalizePath(id.slice(addPrefix.length)); const path = id.slice(addPrefix.length);
return ( return (
`import css from ${JSON.stringify('css:' + path)};\n` + `import css from 'css:${path}';\n` +
`import appendCss from '${appendCssModule}';\n` + `import appendCss from '${appendCssModule}';\n` +
`appendCss(css);\n` `appendCss(css);\n`
); );
} }
if (id.endsWith(moduleSuffix)) { if (id.endsWith(moduleSuffix)) {
const path = nomalizePath(id.slice(0, -moduleSuffix.length));
if (!pathToResult.has(id)) { if (!pathToResult.has(id)) {
throw Error(`Cannot find ${id} in pathToResult`); throw Error(`Cannot find ${id} in pathToResult`);
} }

View File

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

View File

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

View File

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

8538
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -52,7 +52,7 @@ function jsFileName(chunkInfo) {
const parsedPath = path.parse(chunkInfo.facadeModuleId); const parsedPath = path.parse(chunkInfo.facadeModuleId);
if (parsedPath.name !== 'index') return jsPath; if (parsedPath.name !== 'index') return jsPath;
// Come up with a better name than 'index' // 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); return jsPath.replace('[name]', name);
} }

View File

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

View File

@@ -1,16 +1,16 @@
import type { FileDropEvent } from 'file-drop-element'; import type { FileDropEvent } from 'file-drop-element';
import type SnackBarElement from 'shared/custom-els/snack-bar'; import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import type { SnackOptions } from 'shared/custom-els/snack-bar'; import type { SnackOptions } from 'shared/initial-app/custom-els/snack-bar';
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { linkRef } from 'shared/prerendered-app/util'; import { linkRef } from 'shared/initial-app/util';
import * as style from './style.css'; import * as style from './style.css';
import 'add-css:./style.css'; import 'add-css:./style.css';
import 'file-drop-element'; import 'file-drop-element';
import 'shared/custom-els/snack-bar'; import 'shared/initial-app/custom-els/snack-bar';
import Intro from 'shared/prerendered-app/Intro'; import Intro from 'shared/initial-app/Intro';
import 'shared/custom-els/loading-spinner'; import 'shared/initial-app/custom-els/loading-spinner';
const ROUTE_EDITOR = '/editor'; const ROUTE_EDITOR = '/editor';
@@ -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 // 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 // zoom the image. Once you've done this, it's really difficult to undo. Anyway, this seems to
// prevent it. // prevent it.
document.body.addEventListener('gesturestart', (event: any) => { document.body.addEventListener('gesturestart', (event) => {
event.preventDefault(); event.preventDefault();
}); });
@@ -115,7 +115,11 @@ export default class App extends Component<Props, State> {
return ( return (
<div class={style.app}> <div class={style.app}>
<file-drop onfiledrop={this.onFileDrop} class={style.drop}> <file-drop
accept="image/*"
onfiledrop={this.onFileDrop}
class={style.drop}
>
{showSpinner ? ( {showSpinner ? (
<loading-spinner class={style.appLoader} /> <loading-spinner class={style.appLoader} />
) : isEditorOpen ? ( ) : isEditorOpen ? (

View File

@@ -1,5 +1,5 @@
/// <reference path="../../../shared/custom-els/snack-bar/missing-types.d.ts" /> /// <reference path="../../../shared/initial-app/custom-els/snack-bar/missing-types.d.ts" />
/// <reference path="../../../shared/custom-els/loading-spinner/missing-types.d.ts" /> /// <reference path="../../../shared/initial-app/custom-els/loading-spinner/missing-types.d.ts" />
import type { FileDropElement, FileDropEvent } from 'file-drop-element'; import type { FileDropElement, FileDropEvent } from 'file-drop-element';
interface FileDropAttributes extends preact.JSX.HTMLAttributes { interface FileDropAttributes extends preact.JSX.HTMLAttributes {

View File

@@ -3,7 +3,7 @@ import * as style from './style.css';
import 'add-css:./style.css'; import 'add-css:./style.css';
import RangeInputElement from './custom-els/RangeInput'; import RangeInputElement from './custom-els/RangeInput';
import './custom-els/RangeInput'; import './custom-els/RangeInput';
import { linkRef } from 'shared/prerendered-app/util'; import { linkRef } from 'shared/initial-app/util';
interface Props extends preact.JSX.HTMLAttributes {} interface Props extends preact.JSX.HTMLAttributes {}
interface State {} interface State {}

View File

@@ -73,14 +73,9 @@ export default class TwoUp extends HTMLElement {
connectedCallback() { connectedCallback() {
this._childrenChange(); this._childrenChange();
// prettier-ignore this._handle.innerHTML = `<div class="${
this._handle.innerHTML = styles.scrubber
`<div class="${styles.scrubber}">${ }">${`<svg viewBox="0 0 27 20" fill="currentColor">${'<path d="M17 19.2l9.5-9.6L16.9 0zM9.6 0L0 9.6l9.6 9.6z"/>'}</svg>`}</div>`;
`<svg viewBox="0 0 27 20">${
`<path class="${styles.arrowLeft}" d="M9.6 0L0 9.6l9.6 9.6z"/>` +
`<path class="${styles.arrowRight}" d="M17 19.2l9.5-9.6L16.9 0z"/>`
}</svg>
`}</div>`;
if (!this._everConnected) { if (!this._everConnected) {
this._resetPosition(); this._resetPosition();

View File

@@ -2,11 +2,12 @@ two-up {
display: grid; display: grid;
position: relative; position: relative;
--split-point: 0; --split-point: 0;
--track-color: rgb(0 0 0 / 0.6); --accent-color: #777;
--thumb-background: var(--black); --track-color: var(--accent-color);
--thumb-background: #fff;
--thumb-color: var(--accent-color); --thumb-color: var(--accent-color);
--thumb-size: 62px; --thumb-size: 62px;
--bar-size: 9px; --bar-size: 6px;
--bar-touch-size: 30px; --bar-touch-size: 30px;
} }
@@ -36,6 +37,8 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
height: 100%; height: 100%;
width: var(--bar-size); width: var(--bar-size);
margin: 0 auto; margin: 0 auto;
box-shadow: inset calc(var(--bar-size) / 2) 0 0 rgba(0, 0, 0, 0.1),
0 1px 4px rgba(0, 0, 0, 0.4);
background: var(--track-color); background: var(--track-color);
} }
@@ -44,11 +47,14 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform-origin: 50% 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: var(--thumb-size); width: var(--thumb-size);
height: calc(var(--thumb-size) * 0.9); height: calc(var(--thumb-size) * 0.9);
background: var(--thumb-background); background: var(--thumb-background);
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: var(--thumb-size); border-radius: var(--thumb-size);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
color: var(--thumb-color); color: var(--thumb-color);
box-sizing: border-box; box-sizing: border-box;
padding: 0 calc(var(--thumb-size) * 0.24); padding: 0 calc(var(--thumb-size) * 0.24);
@@ -58,14 +64,6 @@ two-up[legacy-clip-compat] > :not(.two-up-handle) {
flex: 1; flex: 1;
} }
.arrow-left {
fill: var(--pink);
}
.arrow-right {
fill: var(--blue);
}
two-up[orientation='vertical'] .two-up-handle { two-up[orientation='vertical'] .two-up-handle {
width: auto; width: auto;
height: var(--bar-touch-size); height: var(--bar-touch-size);

View File

@@ -10,15 +10,15 @@ import {
ToggleBackgroundIcon, ToggleBackgroundIcon,
AddIcon, AddIcon,
RemoveIcon, RemoveIcon,
BackIcon,
ToggleBackgroundActiveIcon, ToggleBackgroundActiveIcon,
RotateIcon, RotateIcon,
MoreIcon,
} from '../../icons'; } from '../../icons';
import { twoUpHandle } from './custom-els/TwoUp/styles.css'; import { twoUpHandle } from './custom-els/TwoUp/styles.css';
import type { PreprocessorState } from '../../feature-meta'; import type { PreprocessorState } from '../../feature-meta';
import { cleanSet } from '../../util/clean-modify'; import { cleanSet } from '../../util/clean-modify';
import type { SourceImage } from '../../Compress'; import type { SourceImage } from '../../Compress';
import { linkRef } from 'shared/prerendered-app/util'; import { linkRef } from 'shared/initial-app/util';
interface Props { interface Props {
source?: SourceImage; source?: SourceImage;
@@ -28,6 +28,7 @@ interface Props {
rightCompressed?: ImageData; rightCompressed?: ImageData;
leftImgContain: boolean; leftImgContain: boolean;
rightImgContain: boolean; rightImgContain: boolean;
onBack: () => void;
onPreprocessorChange: (newState: PreprocessorState) => void; onPreprocessorChange: (newState: PreprocessorState) => void;
} }
@@ -35,7 +36,6 @@ interface State {
scale: number; scale: number;
editingScale: boolean; editingScale: boolean;
altBackground: boolean; altBackground: boolean;
menuOpen: boolean;
} }
const scaleToOpts: ScaleToOpts = { const scaleToOpts: ScaleToOpts = {
@@ -50,7 +50,6 @@ export default class Output extends Component<Props, State> {
scale: 1, scale: 1,
editingScale: false, editingScale: false,
altBackground: false, altBackground: false,
menuOpen: false,
}; };
canvasLeft?: HTMLCanvasElement; canvasLeft?: HTMLCanvasElement;
canvasRight?: HTMLCanvasElement; canvasRight?: HTMLCanvasElement;
@@ -163,10 +162,6 @@ export default class Output extends Component<Props, State> {
this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts); this.pinchZoomLeft.scaleTo(this.state.scale / 1.25, scaleToOpts);
}; };
private toggleMenu = () => {
this.setState(({ menuOpen }) => ({ menuOpen: !menuOpen }));
};
private onRotateClick = () => { private onRotateClick = () => {
const { preprocessorState: inputProcessorState } = this.props; const { preprocessorState: inputProcessorState } = this.props;
if (!inputProcessorState) return; if (!inputProcessorState) return;
@@ -260,8 +255,8 @@ export default class Output extends Component<Props, State> {
}; };
render( render(
{ mobileView, leftImgContain, rightImgContain, source }: Props, { mobileView, leftImgContain, rightImgContain, source, onBack }: Props,
{ scale, editingScale, altBackground, menuOpen }: State, { scale, editingScale, altBackground }: State,
) { ) {
const leftDraw = this.leftDrawable(); const leftDraw = this.leftDrawable();
const rightDraw = this.rightDrawable(); const rightDraw = this.rightDrawable();
@@ -319,6 +314,12 @@ export default class Output extends Component<Props, State> {
</pinch-zoom> </pinch-zoom>
</two-up> </two-up>
<div class={style.back}>
<button class={style.button} onClick={onBack}>
<BackIcon />
</button>
</div>
<div class={style.controls}> <div class={style.controls}>
<div class={style.zoomControls}> <div class={style.zoomControls}>
<button class={style.button} onClick={this.zoomOut}> <button class={style.button} onClick={this.zoomOut}>
@@ -348,34 +349,26 @@ export default class Output extends Component<Props, State> {
<button class={style.button} onClick={this.zoomIn}> <button class={style.button} onClick={this.zoomIn}>
<AddIcon /> <AddIcon />
</button> </button>
</div>
<div class={style.buttonsNoWrap}>
<button <button
class={`${style.button} ${style.moreButton} ${ class={style.button}
menuOpen ? style.open : '' onClick={this.onRotateClick}
}`} title="Rotate image"
onClick={this.toggleMenu}
> >
<MoreIcon /> <RotateIcon />
</button>
<button
class={`${style.button} ${altBackground ? style.active : ''}`}
onClick={this.toggleBackground}
title="Change canvas color"
>
{altBackground ? (
<ToggleBackgroundActiveIcon />
) : (
<ToggleBackgroundIcon />
)}
</button> </button>
<aside class={`${style.menu} ${menuOpen ? style.open : ''}`}>
<button
class={style.button}
onClick={this.onRotateClick}
title="Rotate image"
>
<RotateIcon />
</button>
<button
class={`${style.button} ${altBackground ? style.active : ''}`}
onClick={this.toggleBackground}
title="Change canvas color"
>
{altBackground ? (
<ToggleBackgroundActiveIcon />
) : (
<ToggleBackgroundIcon />
)}
</button>
</aside>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
.output { .output {
composes: abs-fill from global; composes: abs-fill from '../../../../shared/initial-app/util.css';
&::before { &::before {
content: ''; content: '';
@@ -19,12 +19,12 @@
} }
.two-up { .two-up {
composes: abs-fill from global; composes: abs-fill from '../../../../shared/initial-app/util.css';
--accent-color: var(--button-fg); --accent-color: var(--button-fg);
} }
.pinch-zoom { .pinch-zoom {
composes: abs-fill from global; composes: abs-fill from '../../../../shared/initial-app/util.css';
outline: none; outline: none;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -54,7 +54,6 @@
/* Allow clicks to fall through to the pinch zoom area */ /* Allow clicks to fall through to the pinch zoom area */
pointer-events: none; pointer-events: none;
& > * { & > * {
pointer-events: auto; pointer-events: auto;
} }
@@ -77,7 +76,6 @@
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
margin-left: 0; margin-left: 0;
} }
& :not(:last-child) { & :not(:last-child) {
margin-right: 0; margin-right: 0;
border-right-width: 0; border-right-width: 0;
@@ -92,97 +90,46 @@
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
margin: 4px; margin: 4px;
background-color: rgba(29, 29, 29, 0.92); background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.67); border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px; border-radius: 5px;
line-height: 1; line-height: 1;
white-space: nowrap; white-space: nowrap;
height: 39px; height: 36px;
padding: 0 8px; padding: 0 8px;
cursor: pointer; cursor: pointer;
/*
@media (min-width: 600px) { @media (min-width: 600px) {
height: 39px; height: 48px;
padding: 0 16px; padding: 0 16px;
} }
*/
&:focus { &:focus {
box-shadow: 0 0 0 2px #fff; box-shadow: 0 0 0 2px var(--button-fg);
outline: none; outline: none;
z-index: 1; z-index: 1;
} }
} }
.button { .button {
color: #fff; color: var(--button-fg);
&:hover { &:hover {
/* background-color: #eee; */ background-color: #eee;
background: rgba(40, 40, 40, 0.92);
} }
&.active { &.active {
background: rgba(72, 72, 72, 0.92); background: #34b9eb;
color: #fff; color: #fff;
/*
&:hover { &:hover {
background: rgba(72, 72, 72, 0.92); background: #32a3ce;
} }
*/
}
}
.moreButton {
padding: 0 4px;
&.open {
background: rgba(72, 72, 72, 0.92);
}
}
.menu {
display: inline-block;
position: absolute;
right: 0;
bottom: 100%;
z-index: 100;
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: flex-start;
display: none;
&.open {
display: block;
animation: menuOpen 500ms ease forwards 1;
}
button {
margin-top: 8px;
margin-bottom: 8px;
border-radius: 1.2rem;
}
h5 {
text-transform: uppercase;
font-size: 0.8rem;
color: #fff;
margin: 8px 4px;
padding: 10px 0 0;
}
}
@keyframes menuOpen {
0% {
transform: translateY(10px) scale(0.8);
} }
} }
.zoom { .zoom {
color: #939393; color: #625e80;
cursor: text; cursor: text;
width: 6em; width: 6em;
font: inherit; font: inherit;
@@ -190,7 +137,7 @@
justify-content: center; justify-content: center;
&:focus { &:focus {
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff; box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px var(--button-fg);
} }
} }
@@ -198,10 +145,17 @@
position: relative; position: relative;
top: 1px; top: 1px;
margin: 0 3px 0 0; margin: 0 3px 0 0;
color: #fff; color: #888;
border-bottom: 1px dashed #999; border-bottom: 1px dashed #999;
} }
.back {
position: absolute;
top: 0;
left: 0;
padding: 9px;
}
.buttons-no-wrap { .buttons-no-wrap {
display: flex; display: flex;
pointer-events: none; pointer-events: none;

View File

@@ -8,7 +8,7 @@ import {
CopyAcrossIcon, CopyAcrossIcon,
CopyAcrossIconProps, CopyAcrossIconProps,
} from 'client/lazy-app/icons'; } from 'client/lazy-app/icons';
import 'shared/custom-els/loading-spinner'; import 'shared/initial-app/custom-els/loading-spinner';
import { SourceImage } from '../'; import { SourceImage } from '../';
interface Props { interface Props {

View File

@@ -124,7 +124,7 @@
.copy-to-other { .copy-to-other {
grid-row: 1; grid-row: 1;
grid-column: copy-button; grid-column: copy-button;
composes: unbutton from global; composes: unbutton from '../../../../shared/initial-app/util.css';
composes: download; composes: download;
background: #656565; background: #656565;

View File

@@ -30,7 +30,7 @@ import './custom-els/MultiPanel';
import Results from './Results'; import Results from './Results';
import WorkerBridge from '../worker-bridge'; import WorkerBridge from '../worker-bridge';
import { resize } from 'features/processors/resize/client'; import { resize } from 'features/processors/resize/client';
import type SnackBarElement from 'shared/custom-els/snack-bar'; import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import { CopyAcrossIconProps, ExpandIcon } from '../icons'; import { CopyAcrossIconProps, ExpandIcon } from '../icons';
export type OutputType = EncoderType | 'identity'; export type OutputType = EncoderType | 'identity';
@@ -649,7 +649,6 @@ export default class Compress extends Component<Props, State> {
}); });
} catch (err) { } catch (err) {
if (err.name === 'AbortError') return; if (err.name === 'AbortError') return;
this.setState({ loading: false });
this.props.showSnack(`Preprocessing error: ${err}`); this.props.showSnack(`Preprocessing error: ${err}`);
throw err; throw err;
} }
@@ -773,12 +772,6 @@ export default class Compress extends Component<Props, State> {
this.activeSideJobs[sideIndex] = undefined; this.activeSideJobs[sideIndex] = undefined;
} catch (err) { } catch (err) {
if (err.name === 'AbortError') return; if (err.name === 'AbortError') return;
this.setState((currentState) => {
const sides = cleanMerge(currentState.sides, sideIndex, {
loading: false,
});
return { sides };
});
this.props.showSnack(`Processing error: ${err}`); this.props.showSnack(`Processing error: ${err}`);
throw err; throw err;
} }
@@ -862,22 +855,10 @@ export default class Compress extends Component<Props, State> {
rightCompressed={rightImageData} rightCompressed={rightImageData}
leftImgContain={leftImgContain} leftImgContain={leftImgContain}
rightImgContain={rightImgContain} rightImgContain={rightImgContain}
onBack={onBack}
preprocessorState={preprocessorState} preprocessorState={preprocessorState}
onPreprocessorChange={this.onPreprocessorChange} onPreprocessorChange={this.onPreprocessorChange}
/> />
<button class={style.back} onClick={onBack}>
<svg viewBox="0 0 61 53.3">
<title>Back</title>
<path
class={style.backBlob}
d="M0 25.6c-.5-7.1 4.1-14.5 10-19.1S23.4.1 32.2 0c8.8 0 19 1.6 24.4 8s5.6 17.8 1.7 27a29.7 29.7 0 01-20.5 18c-8.4 1.5-17.3-2.6-24.5-8S.5 32.6.1 25.6z"
/>
<path
class={style.backX}
d="M41.6 17.1l-2-2.1-8.3 8.2-8.2-8.2-2 2 8.2 8.3-8.3 8.2 2.1 2 8.2-8.1 8.3 8.2 2-2-8.2-8.3z"
/>
</svg>
</button>
{mobileView ? ( {mobileView ? (
<div class={style.options}> <div class={style.options}>
<multi-panel class={style.multiPanel} open-one-only> <multi-panel class={style.multiPanel} open-one-only>

View File

@@ -73,24 +73,3 @@
:focus .expand-icon { :focus .expand-icon {
fill: #34b9eb; fill: #34b9eb;
} }
.back {
composes: unbutton from global;
position: absolute;
top: var(--dist);
left: var(--dist);
--dist: 14px;
& > svg {
width: 58px;
}
}
.back-blob {
fill: var(--hot-pink);
opacity: 0.77;
}
.back-x {
fill: var(--white);
}

View File

@@ -37,14 +37,6 @@ export const RotateIcon = (props: preact.JSX.HTMLAttributes) => (
</Icon> </Icon>
); );
export const MoreIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<circle cx="12" cy="6" r="2" fill="#fff" />
<circle cx="12" cy="12" r="2" fill="#fff" />
<circle cx="12" cy="18" r="2" fill="#fff" />
</Icon>
);
export const AddIcon = (props: preact.JSX.HTMLAttributes) => ( export const AddIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}> <Icon {...props}>
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
@@ -75,6 +67,12 @@ export const ExpandIcon = (props: preact.JSX.HTMLAttributes) => (
</Icon> </Icon>
); );
export const BackIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M20 11H7.8l5.6-5.6L12 4l-8 8 8 8 1.4-1.4L7.8 13H20v-2z" />
</Icon>
);
const copyAcrossRotations = { const copyAcrossRotations = {
up: 90, up: 90,
right: 180, right: 180,

View File

@@ -1,4 +1,4 @@
import type SnackBarElement from 'shared/custom-els/snack-bar'; import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import { get, set } from 'idb-keyval'; import { get, set } from 'idb-keyval';

View File

@@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
/// <reference path="../../missing-types.d.ts" /> /// <reference path="../../missing-types.d.ts" />
/// <reference path="../shared/prerendered-app/Intro/missing-types.d.ts" /> /// <reference path="../shared/initial-app/Intro/missing-types.d.ts" />
interface Navigator { interface Navigator {
readonly standalone: boolean; readonly standalone: boolean;

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,9 @@
import { EncodeOptions, UVMode, Csp } from '../shared/meta'; import { EncodeOptions } from '../shared/meta';
import { defaultOptions } from '../shared/meta';
import type WorkerBridge from 'client/lazy-app/worker-bridge'; import type WorkerBridge from 'client/lazy-app/worker-bridge';
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { preventDefault, shallowEqual } from 'client/lazy-app/util'; import { inputFieldValueAsNumber, preventDefault } from 'client/lazy-app/util';
import * as style from 'client/lazy-app/Compress/Options/style.css'; import * as style from 'client/lazy-app/Compress/Options/style.css';
import Range from 'client/lazy-app/Compress/Options/Range'; 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 = ( export const encode = (
signal: AbortSignal, signal: AbortSignal,
@@ -23,286 +18,93 @@ interface Props {
} }
interface State { 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; showAdvanced: boolean;
separateAlpha: boolean;
} }
export class Options extends Component<Props, State> { export class Options extends Component<Props, State> {
static getDerivedStateFromProps(
props: Props,
state: State,
): Partial<State> | null {
if (state.options && shallowEqual(state.options, props.options)) {
return null;
}
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,
};
// 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 = { state: State = {
lossless: false,
slightLoss: 0,
quality: defaultOptions.quality,
showAdvanced: false, 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( private onChange = (event: Event) => {
{}: Props, const form = (event.currentTarget as HTMLInputElement).closest(
{ 'form',
effort, ) as HTMLFormElement;
alphaQuality, const { options } = this.props;
passes, const newOptions: EncodeOptions = {
quality, quality: inputFieldValueAsNumber(form.quality, options.quality),
sns, alpha_quality: inputFieldValueAsNumber(
uvMode, form.alpha_quality,
lossless, options.alpha_quality,
slightLoss, ),
colorSpace, speed: inputFieldValueAsNumber(form.speed, options.speed),
errorDiffusion, pass: inputFieldValueAsNumber(form.pass, options.pass),
useRandomMatrix, sns: inputFieldValueAsNumber(form.sns, options.sns),
separateAlpha, };
showAdvanced, this.props.onChange(newOptions);
}: State, };
) {
render({ options }: Props) {
return ( return (
<form class={style.optionsSection} onSubmit={preventDefault}> <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}> <div class={style.optionOneCell}>
<Range <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" min="0"
max="9" max="9"
step="1" step="1"
value={effort} value={options.speed}
onInput={this._inputChange('effort', 'number')} onInput={this.onChange}
> >
Effort: 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:
</Range> </Range>
</div> </div>
</form> </form>

View File

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

View File

@@ -22,7 +22,7 @@ import {
inputFieldChecked, inputFieldChecked,
} from 'client/lazy-app/util'; } from 'client/lazy-app/util';
import * as style from 'client/lazy-app/Compress/Options/style.css'; import * as style from 'client/lazy-app/Compress/Options/style.css';
import { linkRef } from 'shared/prerendered-app/util'; import { linkRef } from 'shared/initial-app/util';
import Select from 'client/lazy-app/Compress/Options/Select'; import Select from 'client/lazy-app/Compress/Options/Select';
import Expander from 'client/lazy-app/Compress/Options/Expander'; import Expander from 'client/lazy-app/Compress/Options/Expander';
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox'; import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';

View File

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,255 @@
import { h, Component } from 'preact';
import { linkRef } from 'shared/initial-app/util';
import '../custom-els/loading-spinner';
import logo from 'url:./imgs/logo.svg';
import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg';
import artwork from 'url:./imgs/demos/demo-artwork.jpg';
import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg';
import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg';
import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg';
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
import * as style from './style.css';
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
import 'shared/initial-app/custom-els/snack-bar';
const demos = [
{
description: 'Large photo (2.8mb)',
filename: 'photo.jpg',
url: largePhoto,
iconUrl: largePhotoIcon,
},
{
description: 'Artwork (2.9mb)',
filename: 'art.jpg',
url: artwork,
iconUrl: artworkIcon,
},
{
description: 'Device screen (1.6mb)',
filename: 'pixel3.png',
url: deviceScreen,
iconUrl: deviceScreenIcon,
},
{
description: 'SVG icon (13k)',
filename: 'squoosh.svg',
url: logo,
iconUrl: logoIcon,
},
];
const installButtonSource = 'introInstallButton-Purple';
interface Props {
onFile?: (file: File) => void;
showSnack?: SnackBarElement['showSnackbar'];
}
interface State {
fetchingDemoIndex?: number;
beforeInstallEvent?: BeforeInstallPromptEvent;
}
export default class Intro extends Component<Props, State> {
state: State = {};
private fileInput?: HTMLInputElement;
private installingViaButton = false;
constructor() {
super();
if (__PRERENDER__) return;
// Listen for beforeinstallprompt events, indicating Squoosh is installable.
window.addEventListener(
'beforeinstallprompt',
this.onBeforeInstallPromptEvent,
);
// Listen for the appinstalled event, indicating Squoosh has been installed.
window.addEventListener('appinstalled', this.onAppInstalled);
}
private resetFileInput = () => {
this.fileInput!.value = '';
};
private onFileChange = (event: Event): void => {
const fileInput = event.target as HTMLInputElement;
const file = fileInput.files && fileInput.files[0];
if (!file) return;
this.resetFileInput();
this.props.onFile!(file);
};
private onButtonClick = () => {
this.fileInput!.click();
};
private onDemoClick = async (index: number, event: Event) => {
try {
this.setState({ fetchingDemoIndex: index });
const demo = demos[index];
const blob = await fetch(demo.url).then((r) => r.blob());
// Firefox doesn't like content types like 'image/png; charset=UTF-8', which Webpack's dev
// server returns. https://bugzilla.mozilla.org/show_bug.cgi?id=1497925.
const type = /[^;]*/.exec(blob.type)![0];
const file = new File([blob], demo.filename, { type });
this.props.onFile!(file);
} catch (err) {
this.setState({ fetchingDemoIndex: undefined });
this.props.showSnack!("Couldn't fetch demo image");
}
};
private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => {
// Don't show the mini-infobar on mobile
event.preventDefault();
// Save the beforeinstallprompt event so it can be called later.
this.setState({ beforeInstallEvent: event });
// Log the event.
const gaEventInfo = {
eventCategory: 'pwa-install',
eventAction: 'promo-shown',
nonInteraction: true,
};
ga('send', 'event', gaEventInfo);
};
private onInstallClick = async (event: Event) => {
// Get the deferred beforeinstallprompt event
const beforeInstallEvent = this.state.beforeInstallEvent;
// If there's no deferred prompt, bail.
if (!beforeInstallEvent) return;
this.installingViaButton = true;
// Show the browser install prompt
beforeInstallEvent.prompt();
// Wait for the user to accept or dismiss the install prompt
const { outcome } = await beforeInstallEvent.userChoice;
// Send the analytics data
const gaEventInfo = {
eventCategory: 'pwa-install',
eventAction: 'promo-clicked',
eventLabel: installButtonSource,
eventValue: outcome === 'accepted' ? 1 : 0,
};
ga('send', 'event', gaEventInfo);
// If the prompt was dismissed, we aren't going to install via the button.
if (outcome === 'dismissed') {
this.installingViaButton = false;
}
};
private onAppInstalled = () => {
// We don't need the install button, if it's shown
this.setState({ beforeInstallEvent: undefined });
// Don't log analytics if page is not visible
if (document.hidden) {
return;
}
// Try to get the install, if it's not set, use 'browser'
const source = this.installingViaButton ? installButtonSource : 'browser';
ga('send', 'event', 'pwa-install', 'installed', source);
// Clear the install method property
this.installingViaButton = false;
};
render({}: Props, { fetchingDemoIndex, beforeInstallEvent }: State) {
return (
<div class={style.intro}>
<div>
<div class={style.logoSizer}>
<div class={style.logoContainer}>
<img
src={logo}
class={style.logo}
alt="Squoosh"
decoding="async"
/>
</div>
</div>
<p class={style.openImageGuide}>
Drag &amp; drop or{' '}
<button class={style.selectButton} onClick={this.onButtonClick}>
select an image
</button>
<input
class={style.hide}
ref={linkRef(this, 'fileInput')}
type="file"
onChange={this.onFileChange}
/>
</p>
<p>Or try one of these:</p>
<ul class={style.demos}>
{demos.map((demo, i) => (
<li key={demo.url} class={style.demoItem}>
<button
class={style.demoButton}
onClick={this.onDemoClick.bind(this, i)}
>
<div class={style.demo}>
<div class={style.demoImgContainer}>
<div class={style.demoImgAspect}>
<img
class={style.demoIcon}
src={demo.iconUrl}
alt=""
decoding="async"
/>
{fetchingDemoIndex === i && (
<div class={style.demoLoading}>
<loading-spinner class={style.demoLoadingSpinner} />
</div>
)}
</div>
</div>
<div class={style.demoDescription}>{demo.description}</div>
</div>
</button>
</li>
))}
</ul>
</div>
{beforeInstallEvent && (
<button
type="button"
class={style.installButton}
onClick={this.onInstallClick}
>
Install
</button>
)}
<ul class={style.relatedLinks}>
<li>
<a href="https://github.com/GoogleChromeLabs/squoosh/">
View the code
</a>
</li>
<li>
<a href="https://github.com/GoogleChromeLabs/squoosh/issues">
Report a bug
</a>
</li>
<li>
<a href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy">
Privacy
</a>
</li>
</ul>
</div>
);
}
}

View File

@@ -29,12 +29,3 @@ interface BeforeInstallPromptEvent extends Event {
interface WindowEventMap { interface WindowEventMap {
beforeinstallprompt: BeforeInstallPromptEvent; beforeinstallprompt: BeforeInstallPromptEvent;
} }
interface ClipboardItem {
types: string[];
getType(type: string): Promise<Blob>;
}
interface Clipboard {
read(): Promise<ClipboardItem[]>;
}

View File

@@ -0,0 +1,228 @@
@font-face {
font-family: 'intro-text';
font-style: normal;
font-weight: 300;
font-display: block;
/* This only contains the chars for "Drag & drop or" */
src: url('data:font/woff2;base64,d09GMgABAAAAAAXcAA4AAAAACowAAAWJAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg2gcMAZgAGwRCAqIQIcnCxYAATYCJAMoBCAFgwAHIBvqCFEU84FMI2Xh/P3g+Tfn532yQ/IgYz4BrJyhtkkZwFBYAZ49sI63e5v/NnqzIfbADyE0qxOqK8ESLoNNdULHihxbW0W86/qHEk4wT/eHShPRZJYYqUGkQdLSWCeSemZBzwpKyX/LRoAhMEQhqCFBw5RHNCc4hbVn35FsxtTXVHYyo7miu5VN2AW1fwzVauRgXnIGo2IWsYdViUoLu6mms5VFAn+SeQ4eBazfj7QodrMt4oyQHaGADEPRpTbDqJaoTENNK6DpOralUszf6gI/QsAhWZSMKVOirikSJxZRLBVD0S4mB0kTBRwopjZ/mt/2/25+bcSipgiHRmwiFI1g+XhwlshyEAsbJzGiGH+U5whHNgiXooplafI1rMFbmIqjGAPhmcSkVFxeu9hw87aXsGyL+dPE05qUpK2WyaVQcZVW+aDmw3aalLJKNmQORcpZYtBIuTrncN4xXoVZY617TBSsx2T1DHgGU6u4etE04wha1GEwjVkEaDttOrl1FCOwUMxgHnuooJo62ukcWEuc1/aT+dZ8b142t5tbzc3mGnP1EJqVTEGMYTjG14YxtGEEG+0E2axhe6Oa1E8UrDHDFjhTRywYNWrU9JHTlw7RmaslkrrGcTJ+znW4EzzP0zovE4Z5d0hqVhBobftBIKkwL09SOv3hhCuv1Dp9taTeCJ2Mj3KDT8iDng5DkWzPw/UdP8idNDkMnUyOwEauwnYLQeLC7GskNe72QKe97AmuA42E5FjfyYTM+HTdQ+Xqb+q4JvptyKZN1w47qMMwL58fyKZM1U6NXgWlOFdxx7DpXHDTz4UB89WMK3HH3uY7mavFopGF+u36lGlqZsL4ugmbqvZxveycMO+a4uyN3o7GT2qdHpfr6W++kNTn1crdx7Z+FW7PfffTmfnXV/2ivsh5UX93zdlzct6QlSuHSumG3oGNNT9/m9yXnDcnKfsmDx8xUaoKi+uvGs99H2ieUJUg8bTnVwQcDd/SPKwYWDUv+QkpT6MulMrcPTXNWYnIowxvoiwnX+opTMkvzOMGgpNpqnK32CNVwCnassw0BwQwTa0rLS3m1DfIoxx5PIE8SvEmSk3pHSWZiRVKjOOQSylJSHGXkhT/u/tg/Vm9UZQcS59TGb1qjcuuT0925iaaU1vaWpZJM4ukqWWlrdWSIcVNlOImvnrzLn53UpnSLzbGT5lUlpTiKiPJFEmyqywFLtOhcaYJkWkaGe/oGBlnmiIiIiKYpqHxLmdaWg5JpxxHSXpajsuVlkPSvb1JelqOC0pubbAn2A2UsDdYmTmjvbVlgTRhVBSSxpbF1nZD+jvkUR4rcJeSFBp2d19SUsVW5DjkUkoSoITHJ7iJEpZnZaL4OiF7g92DN1mz8b1RiM9RDk9ps9pcanamlnj2ftqbJpHJ0wpkRn2+RJ6qsGflpYrPnxG6A4r9zqGY3qCcqDuhsWGQhoXpQ0663cWFM4qNR0Jxj1R0UBT36pahMneH4NYV27jElOeyAAAACACAABy4uvGyOsj21Y9h3gIA3PuxYAYAuC/7vftf7L+PXunMAQDwBQIAAAIA5vR/HwCvOQ//TzLL7cPIHUC0zMI5v7+tHiVfzWOeSrJKZbFabWGNSnJE+jmsnmTjTZm6kBi9r0aLgm8qNk6t67ATuPlEitG+g+E7in1GMYxCxmIF9YzNJK7lRoSPc6PCD+8fxhp+YjdttDNAJw3UUU83M1jFClaylkpkU08NVZqkh0oaaBLPnaCTNhqpoaok2UkPZqy/JyfpKnVLkhrq6KGZCjpZxTJWqN9uJofD5HGMzSXHLaVbOmuTSnOp6cTQgJlaB6oF7RIITul8N+1sYjnL6aJqqoZ2inaxDIY2s2zwlXUs5zj7OPJmAPao+ZhVHy0A')
format('woff2');
}
@font-face {
font-family: 'intro-text';
font-style: normal;
font-weight: 500;
font-display: block;
/* Only contains the chars for "select an image" */
src: url('data:font/woff2;base64,d09GMgABAAAAAAXMAA4AAAAACwQAAAV5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbg1IcMAZgAGwRCAqJUId/CxoAATYCJAMwBCAFgnAHIBskCcjEh6dNff8Ou/9Tj9VZGnUhJeqFzWGiVVOkxkTthr9f6kWkRdsBbkIj3YuLaloFZWr7aBg22z7IOoWqBWCW5cZU3GBrh0n+dAcBYAUlzYHAzWTYchqKXEyAT0zOLIS1qqm+B8q2ur4OhEMy0PHHUH8KklaSr8T0mp/EU7kRvXlI1E09HXA1qLN8Djxa0AsSDOg3cJARb9mtQJoSK3gvEn372/gcAigg/gOnbsT/MYv491GTReW4rJC5LA+h5FFclF6QQgoZ5Kx7GbsuGeytUgFClkOomY2Gdake3m9HegkHieAx/a0hBTALsy4jvpxBcnFXUnjC+2ZS5zHnDeEaJVwi+ZWqzOm4Uvgy4k6kGv4kFDVkfjk1gkVRRkk2zlo42PBbRJmG30cClJQjak7BnfQqHza4ITKftQZ/ZMUaEiyy1+mYCh4clKhDA5rQglZ0oG9jw+qiNvT+SfxIXCeuFdeIq8VV4gpxyRaGOl0JiChiCocfc7e93DwZIPvWgPiZJLcJugxyjW7UQyl4TJk6dWqYU7Cn0WQiWnNJCdGeprqjW63fpVS3mKy4YGZ6I3ya4nbIVgM1mwkpNEBzixlNxfPmH7owvdE4973OM9quvk11dwvnzDUy/Zn5S5Ywpn/PeqXBQI2m4lna05CRtsI6+GIENjS9K4jWRHUGYA2ozdZm2Smmf0DI3aqpeNbsJfxe7YdFmcZAn5gXLCFa2/Umqiu017APFhMZ0rfQp4sJX0ZrJ+n9UtAljr5VYWb6oj1MrpvX3qe6u8WRJg0bj7aPkDOa7m+E0Oa9Y8eY/gbRbr+efH7hcO49bMd28fbDVHcUmm3XkozQGKjeBHSJ4TQnI879LIFmF2v/BJuEQlffJPfE9oKayS/PsPE44fvM4MsBESxbuEEV39d5pw6oW4vD6S1WQC3UpSbHNbK0Jikl0bphSs+0CGW8Ew4Kzw7zarmcVz873JHTFhKYay18R8vY0ozPiHPAGyROAqlW5fLj5+HbWBn9TpgekKsOy8N+4dlFfL9i/Nk3+gY1bwzZUAvLVNiFpvqHRenetSoVrgn2obGtPsltEVxEeHJAQFhyBIcHT9rDvTJm06e0TLgase2gd2RffGJNg0o1zrdRyi9s1bYE5bi85cK+o/nUwvBR5+jweEBaSMoCub29fEFISmib9Dn5yl5kVFpoGrPQSmZhafQ7WimttNCH7Cktohb6kFpoEfIsdDF7SgvZTbaY3mKFyLXQh+wpzWE3mUNQQkWnR+lgiW9Afkunej49Nz3sYlI8XFTRdkNhUR5d6h4oOpJc8OjcItMXVqoHW2fSW6ycWuiM8NDYoICouLAZ9BYrwwmhycvKlt4Q8hUlCV5nZ7vOm2ut2rizcFpuWpSrT3K1Z3xinbuHnXBTyGAljV/XzHZaNGu6y7vLDziMpIyUdOBBRXXlxznUQiOoheZsZk9njG8er1mNmz2eOCQ3x9BbLP+Zxt+VrbEz9aWxmimRvyl4/sumyoM/nw+LNzV4/uP0/9T/P5f08cvhl38USAS/xTYs2fL/VNFF0vd+SVsRB/khPwW4SCi5SHhx8fDgVPAiAqIJRQL/EuK5bsRzNnAiezD1i7u2VyHHAKRU+E2YUaA5DUsE2ZfbApAmsJcxjBwmQ2Xk4Y2BqZJ+oxSzsNEogz1O3/xkBMKEBHSiC8PoQStaoEIflPCHL/wQBCUKoUITlMhHP+olt5ojykUPOrEQTWgY+f449KMPKnSiB72jGrLQhEZO24925LtbkG5DndTkD2/4um+NQBEyUIJsRBxX24tcDGmbWtGJjq05jhzTaMgCcR+6EA4f+KAXDay5u6RsL7xJsm3w3mzvFvggB8nI/AYBdVnxNvx/2wA=')
format('woff2');
}
@keyframes fade-in {
from {
opacity: 0;
}
}
.intro {
display: grid;
grid-template-rows: 1fr min-content;
align-items: center;
background: rgba(255, 255, 255, 0.25);
text-align: center;
font-size: 2rem;
-webkit-overflow-scrolling: touch;
overflow: auto;
padding: 20px 0 0;
height: 100%;
box-sizing: border-box;
overscroll-behavior: contain;
position: relative;
}
.logo-container {
position: relative;
padding-top: 100%;
}
.logo-sizer {
width: 90%;
max-width: 52vh;
margin: 0 auto;
}
.logo {
composes: abs-fill from '../util.css';
pointer-events: none;
}
.open-image-guide {
font: 300 11vw intro-text, sans-serif;
margin-bottom: 0;
@media (min-width: 460px) {
font-size: 50.6px;
padding: 0 40px;
}
}
.select-button {
composes: unbutton from '../util.css';
font-weight: 500;
color: #5d509e;
&:hover,
&:focus {
text-decoration: underline;
}
}
.hide {
display: none;
}
.demos {
display: block;
padding: 0;
border-top: 1px solid #e8e8e8;
margin: 0 auto;
@media (min-width: 400px) {
display: grid;
grid-template-columns: 1fr 1fr;
}
@media (min-width: 580px) {
border-top: none;
width: 523px;
}
@media (min-width: 900px) {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
width: 773px;
}
}
.demo-item {
background: #fff;
display: flex;
border-bottom: 1px solid #e8e8e8;
@media (min-width: 580px) {
border: 1px solid #e8e8e8;
border-radius: 4px;
margin: 3px;
}
}
.demo-button {
composes: unbutton from '../util.css';
flex: 1;
&:hover,
&:focus {
background: #f5f5f5;
}
}
.demo {
display: flex;
align-items: center;
padding: 7px;
font-size: 1.3rem;
}
.demo-img-container {
overflow: hidden;
display: block;
width: 47px;
background: #ccc;
border-radius: 3px;
flex: 0 0 auto;
}
.demo-img-aspect {
position: relative;
padding-top: 100%;
}
.demo-icon {
composes: abs-fill from '../util.css';
pointer-events: none;
}
.demo-description {
display: flex;
align-items: center;
justify-content: flex-start;
text-align: left;
flex: 1;
padding: 0 10px;
}
.demo-loading {
composes: abs-fill from '../util.css';
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
animation: fade-in 300ms ease-in-out;
}
.demo-loading-spinner {
--color: #fff;
}
.install-button {
composes: unbutton from '../util.css';
&:hover,
&:focus {
background: #504488;
}
background: #5d509e;
border: 1px solid #e8e8e8;
color: #fff;
padding: 14px;
font-size: 1.3rem;
position: absolute;
top: 1rem;
right: 1rem;
animation: fade-in 0.3s ease-in-out;
}
@keyframes fade-in {
from {
opacity: 0;
}
}
.related-links {
display: flex;
padding: 0;
justify-content: center;
font-size: 1.3rem;
& li {
display: block;
border-left: 1px solid #000;
padding: 0 0.6em;
&:first-child {
border-left: none;
}
}
& a:link {
color: #5d509e;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}

View File

@@ -1,5 +1,4 @@
import * as styles from './styles.css'; import * as styles from './styles.css';
import 'add-css:./styles.css';
// So it doesn't cause an error when running in node // So it doesn't cause an error when running in node
const HTMLEl = ((__PRERENDER__ const HTMLEl = ((__PRERENDER__

View File

@@ -1,5 +1,4 @@
import * as style from './styles.css'; import * as style from './styles.css';
import 'add-css:./styles.css';
// So it doesn't cause an error when running in node // So it doesn't cause an error when running in node
const HTMLEl = ((__PRERENDER__ const HTMLEl = ((__PRERENDER__

View File

@@ -0,0 +1,22 @@
.abs-fill {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
contain: strict;
}
.unbutton {
cursor: pointer;
background: none;
border: none;
font: inherit;
padding: 0;
margin: 0;
&:focus {
outline: none;
}
}

View File

@@ -13,23 +13,3 @@
/// <reference path="../../missing-types.d.ts" /> /// <reference path="../../missing-types.d.ts" />
declare const __PRERENDER__: boolean; declare const __PRERENDER__: boolean;
interface ResizeObserverCallback {
(entries: ResizeObserverEntry[], observer: ResizeObserver): void;
}
interface ResizeObserverEntry {
readonly target: Element;
readonly contentRect: DOMRectReadOnly;
}
interface ResizeObserver {
observe(target: Element): void;
unobserve(target: Element): void;
disconnect(): void;
}
declare var ResizeObserver: {
prototype: ResizeObserver;
new (callback: ResizeObserverCallback): ResizeObserver;
};

View File

@@ -1,401 +0,0 @@
import * as style from '../style.css';
import { startBlobs } from './meta';
/**
* Control point x,y - point x,y - control point x,y
*/
export type BlobPoint = [number, number, number, number, number, number];
const maxPointDistance = 0.25;
function randomisePoint(point: BlobPoint): BlobPoint {
const distance = Math.random() * maxPointDistance;
const angle = Math.random() * Math.PI * 2;
const xShift = Math.sin(angle) * distance;
const yShift = Math.cos(angle) * distance;
return [
point[0] + xShift,
point[1] + yShift,
point[2] + xShift,
point[3] + yShift,
point[4] + xShift,
point[5] + yShift,
];
}
function easeInOutQuad(x: number): number {
return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
const rand = (min: number, max: number) => Math.random() * (max - min) + min;
interface CircleBlobPointState {
basePoint: BlobPoint;
pos: number;
duration: number;
startPoint: BlobPoint;
endPoint: BlobPoint;
}
/** Bezier points for a seven point circle, to 3 decimal places */
const sevenPointCircle: BlobPoint[] = [
[-0.304, -1, 0, -1, 0.304, -1],
[0.592, -0.861, 0.782, -0.623, 0.972, -0.386],
[1.043, -0.074, 0.975, 0.223, 0.907, 0.519],
[0.708, 0.769, 0.434, 0.901, 0.16, 1.033],
[-0.16, 1.033, -0.434, 0.901, -0.708, 0.769],
[-0.907, 0.519, -0.975, 0.223, -1.043, -0.074],
[-0.972, -0.386, -0.782, -0.623, -0.592, -0.861],
];
/*
// Should it be needed, here's how the above was created:
function createBezierCirclePoints(points: number): BlobPoint[] {
const anglePerPoint = 360 / points;
const matrix = new DOMMatrix();
const point = new DOMPoint();
const controlDistance = (4 / 3) * Math.tan(Math.PI / (2 * points));
return Array.from({ length: points }, (_, i) => {
point.x = -controlDistance;
point.y = -1;
const cp1 = point.matrixTransform(matrix);
point.x = 0;
point.y = -1;
const p = point.matrixTransform(matrix);
point.x = controlDistance;
point.y = -1;
const cp2 = point.matrixTransform(matrix);
const basePoint: BlobPoint = [cp1.x, cp1.y, p.x, p.y, cp2.x, cp2.y];
matrix.rotateSelf(0, 0, anglePerPoint);
return basePoint;
});
}
*/
interface CircleBlobOptions {
minDuration?: number;
maxDuration?: number;
startPoints?: BlobPoint[];
}
class CircleBlob {
private animStates: CircleBlobPointState[];
private minDuration: number;
private maxDuration: number;
private points: BlobPoint[];
constructor(
basePoints: BlobPoint[],
{
startPoints = basePoints.map((point) => randomisePoint(point)),
minDuration = 4000,
maxDuration = 11000,
}: CircleBlobOptions = {},
) {
this.points = startPoints;
this.minDuration = minDuration;
this.maxDuration = maxDuration;
this.animStates = basePoints.map((basePoint, i) => ({
basePoint,
pos: 0,
duration: rand(minDuration, maxDuration),
startPoint: startPoints[i],
endPoint: randomisePoint(basePoint),
}));
}
advance(timeDelta: number): void {
this.points = this.animStates.map((animState) => {
animState.pos += timeDelta / animState.duration;
if (animState.pos >= 1) {
animState.startPoint = animState.endPoint;
animState.pos = 0;
animState.duration = rand(this.minDuration, this.maxDuration);
animState.endPoint = randomisePoint(animState.basePoint);
}
const eased = easeInOutQuad(animState.pos);
const point = animState.startPoint.map((startPoint, i) => {
const endPoint = animState.endPoint[i];
return (endPoint - startPoint) * eased + startPoint;
}) as BlobPoint;
return point;
});
}
draw(ctx: CanvasRenderingContext2D) {
const points = this.points;
ctx.beginPath();
ctx.moveTo(points[0][2], points[0][3]);
for (let i = 0; i < points.length; i++) {
const nextI = i === points.length - 1 ? 0 : i + 1;
ctx.bezierCurveTo(
points[i][4],
points[i][5],
points[nextI][0],
points[nextI][1],
points[nextI][2],
points[nextI][3],
);
}
ctx.closePath();
ctx.fill();
}
}
const centralBlobsRotationTime = 120000;
class CentralBlobs {
private rotatePos: number = 0;
private blobs = Array.from(
{ length: 4 },
(_, i) => new CircleBlob(sevenPointCircle, { startPoints: startBlobs[i] }),
);
advance(timeDelta: number) {
this.rotatePos =
(this.rotatePos + timeDelta / centralBlobsRotationTime) % 1;
for (const blob of this.blobs) blob.advance(timeDelta);
}
draw(ctx: CanvasRenderingContext2D, x: number, y: number, radius: number) {
ctx.save();
ctx.translate(x, y);
ctx.scale(radius, radius);
ctx.rotate(Math.PI * 2 * this.rotatePos);
for (const blob of this.blobs) blob.draw(ctx);
ctx.restore();
}
}
const bgBlobsMinRadius = 20;
const bgBlobsMaxRadius = 60;
const bgBlobsMinAlpha = 0.1;
const bgBlobsMaxAlpha = 0.8;
const bgBlobsGridSize = 200;
const bgBlobsMinSpinTime = 20000;
const bgBlobsMaxSpinTime = 60000;
const bgBlobsMinVelocity = 0.005;
const bgBlobsMaxVelocity = 0.02;
interface BackgroundBlob {
blob: CircleBlob;
velocity: number;
spinTime: number;
alpha: number;
alphaMultiplier: number;
rotatePos: number;
radius: number;
x: number;
y: number;
}
const bgBlobsAlphaTime = 2000;
class BackgroundBlobs {
private bgBlobs: BackgroundBlob[] = [];
private overallAlphaPos = 0;
constructor(bounds: DOMRect) {
for (let x = 0; x < bounds.width; x += bgBlobsGridSize) {
for (let y = 0; y < bounds.height; y += bgBlobsGridSize) {
this.bgBlobs.push({
blob: new CircleBlob(sevenPointCircle, {
minDuration: 2000,
maxDuration: 5000,
}),
velocity: rand(bgBlobsMinVelocity, bgBlobsMaxVelocity),
alpha:
Math.random() ** 3 * (bgBlobsMaxAlpha - bgBlobsMinAlpha) +
bgBlobsMinAlpha,
alphaMultiplier: 1,
spinTime: rand(bgBlobsMinSpinTime, bgBlobsMaxSpinTime),
rotatePos: 0,
radius:
Math.random() ** 3 * (bgBlobsMaxRadius - bgBlobsMinRadius) +
bgBlobsMinRadius,
x: Math.random() * bgBlobsGridSize + x,
y: Math.random() * bgBlobsGridSize + y,
});
}
}
}
advance(
timeDelta: number,
bounds: DOMRect,
targetX: number,
targetY: number,
targetRadius: number,
) {
if (this.overallAlphaPos !== 1) {
this.overallAlphaPos = Math.min(
1,
this.overallAlphaPos + timeDelta / bgBlobsAlphaTime,
);
}
for (const bgBlob of this.bgBlobs) {
bgBlob.blob.advance(timeDelta);
let dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY);
bgBlob.rotatePos = (bgBlob.rotatePos + timeDelta / bgBlob.spinTime) % 1;
const shiftDist = bgBlob.velocity * timeDelta;
if (dist < 10) {
// Move the circle out to a random edge
switch (Math.floor(Math.random() * 4)) {
case 0: // top
bgBlob.x = Math.random() * bounds.width;
bgBlob.y = -(bgBlob.radius * (1 + maxPointDistance));
break;
case 1: // left
bgBlob.x = -(bgBlob.radius * (1 + maxPointDistance));
bgBlob.y = Math.random() * bounds.height;
break;
case 2: // bottom
bgBlob.x = Math.random() * bounds.width;
bgBlob.y = bounds.height + bgBlob.radius * (1 + maxPointDistance);
break;
case 3: // right
bgBlob.x = bounds.width + bgBlob.radius * (1 + maxPointDistance);
bgBlob.y = Math.random() * bounds.height;
break;
}
}
dist = Math.hypot(bgBlob.x - targetX, bgBlob.y - targetY);
const direction = Math.atan2(targetX - bgBlob.x, targetY - bgBlob.y);
const xShift = Math.sin(direction) * shiftDist;
const yShift = Math.cos(direction) * shiftDist;
bgBlob.x += xShift;
bgBlob.y += yShift;
bgBlob.alphaMultiplier = Math.min(dist / targetRadius, 1);
}
}
draw(ctx: CanvasRenderingContext2D) {
const overallAlpha = easeInOutQuad(this.overallAlphaPos);
for (const bgBlob of this.bgBlobs) {
ctx.save();
ctx.globalAlpha = bgBlob.alpha * bgBlob.alphaMultiplier * overallAlpha;
ctx.translate(bgBlob.x, bgBlob.y);
ctx.scale(bgBlob.radius, bgBlob.radius);
ctx.rotate(Math.PI * 2 * bgBlob.rotatePos);
bgBlob.blob.draw(ctx);
ctx.restore();
}
}
}
const deltaMultiplierStep = 0.01;
export function startBlobAnim(canvas: HTMLCanvasElement) {
let lastTime: number;
const ctx = canvas.getContext('2d')!;
const centralBlobs = new CentralBlobs();
let backgroundBlobs: BackgroundBlobs;
const loadImgEl = document.querySelector('.' + style.loadImg)!;
let hasFocus = document.hasFocus();
let deltaMultiplier = hasFocus ? 1 : 0;
let animating = true;
const visibilityListener = () => {
// 'Pause time' while page is hidden
if (document.visibilityState === 'visible') lastTime = performance.now();
};
const focusListener = () => {
hasFocus = true;
if (!animating) startAnim();
};
const blurListener = () => {
hasFocus = false;
};
new ResizeObserver(() => {
// Redraw for new canvas size
if (!animating) drawFrame(0);
}).observe(canvas);
addEventListener('focus', focusListener);
addEventListener('blur', blurListener);
document.addEventListener('visibilitychange', visibilityListener);
function destruct() {
removeEventListener('focus', focusListener);
removeEventListener('blur', blurListener);
document.removeEventListener('visibilitychange', visibilityListener);
}
function drawFrame(delta: number) {
const canvasBounds = canvas.getBoundingClientRect();
canvas.width = canvasBounds.width * devicePixelRatio;
canvas.height = canvasBounds.height * devicePixelRatio;
const loadImgBounds = loadImgEl.getBoundingClientRect();
const computedStyles = getComputedStyle(canvas);
const blobPink = computedStyles.getPropertyValue('--blob-pink');
const loadImgCenterX =
loadImgBounds.left - canvasBounds.left + loadImgBounds.width / 2;
const loadImgCenterY =
loadImgBounds.top - canvasBounds.top + loadImgBounds.height / 2;
const loadImgRadius = loadImgBounds.height / 2 / (1 + maxPointDistance);
ctx.scale(devicePixelRatio, devicePixelRatio);
if (!backgroundBlobs) backgroundBlobs = new BackgroundBlobs(canvasBounds);
backgroundBlobs.advance(
delta,
canvasBounds,
loadImgCenterX,
loadImgCenterY,
loadImgRadius,
);
centralBlobs.advance(delta);
ctx.globalAlpha = Number(
computedStyles.getPropertyValue('--center-blob-opacity'),
);
ctx.fillStyle = blobPink;
backgroundBlobs.draw(ctx);
centralBlobs.draw(ctx, loadImgCenterX, loadImgCenterY, loadImgRadius);
}
function frame(time: number) {
// Stop the loop if the canvas is gone
if (!canvas.isConnected) {
destruct();
return;
}
// Be kind: If the window isn't focused, bring the animation to a stop.
if (!hasFocus) {
// Bring the anim to a slow stop
deltaMultiplier = Math.max(0, deltaMultiplier - deltaMultiplierStep);
if (deltaMultiplier === 0) {
animating = false;
return;
}
} else if (deltaMultiplier !== 1) {
deltaMultiplier = Math.min(1, deltaMultiplier + deltaMultiplierStep);
}
const delta = (time - lastTime) * deltaMultiplier;
lastTime = time;
drawFrame(delta);
requestAnimationFrame(frame);
}
function startAnim() {
animating = true;
requestAnimationFrame((time: number) => {
lastTime = time;
frame(time);
});
}
startAnim();
}

View File

@@ -1,41 +0,0 @@
import type { BlobPoint } from '.';
/** Start points, for the shape we use in prerender */
export const startBlobs: BlobPoint[][] = [
[
[-0.232, -1.029, 0.073, -1.029, 0.377, -1.029],
[0.565, -1.098, 0.755, -0.86, 0.945, -0.622],
[0.917, -0.01, 0.849, 0.286, 0.782, 0.583],
[0.85, 0.687, 0.576, 0.819, 0.302, 0.951],
[-0.198, 1.009, -0.472, 0.877, -0.746, 0.745],
[-0.98, 0.513, -1.048, 0.216, -1.116, -0.08],
[-0.964, -0.395, -0.774, -0.633, -0.584, -0.871],
],
[
[-0.505, -1.109, -0.201, -1.109, 0.104, -1.109],
[0.641, -0.684, 0.831, -0.446, 1.02, -0.208],
[1.041, 0.034, 0.973, 0.331, 0.905, 0.628],
[0.734, 0.794, 0.46, 0.926, 0.186, 1.058],
[-0.135, 0.809, -0.409, 0.677, -0.684, 0.545],
[-0.935, 0.404, -1.002, 0.108, -1.07, -0.189],
[-0.883, -0.402, -0.693, -0.64, -0.503, -0.878],
],
[
[-0.376, -1.168, -0.071, -1.168, 0.233, -1.168],
[0.732, -0.956, 0.922, -0.718, 1.112, -0.48],
[1.173, 0.027, 1.105, 0.324, 1.038, 0.621],
[0.707, 0.81, 0.433, 0.943, 0.159, 1.075],
[-0.096, 1.135, -0.37, 1.003, -0.644, 0.871],
[-0.86, 0.457, -0.927, 0.161, -0.995, -0.136],
[-0.87, -0.516, -0.68, -0.754, -0.49, -0.992],
],
[
[-0.309, -0.998, -0.004, -0.998, 0.3, -0.998],
[0.535, -0.852, 0.725, -0.614, 0.915, -0.376],
[1.05, -0.09, 0.982, 0.207, 0.915, 0.504],
[0.659, 0.807, 0.385, 0.939, 0.111, 1.071],
[-0.178, 1.048, -0.452, 0.916, -0.727, 0.784],
[-0.942, 0.582, -1.009, 0.285, -1.077, -0.011],
[-1.141, -0.335, -0.951, -0.573, -0.761, -0.811],
],
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29.34 28.61"><path fill="#838383" d="M14.67 0a14.67 14.67 0 00-4.64 28.59c.74.13 1-.32 1-.7l-.02-2.74c-4.08.89-4.94-1.73-4.94-1.73a3.88 3.88 0 00-1.63-2.14c-1.33-.91.1-.9.1-.9A3.08 3.08 0 016.8 21.9a3.12 3.12 0 004.27 1.22 3.12 3.12 0 01.93-1.96c-3.26-.37-6.68-1.63-6.68-7.25a5.68 5.68 0 011.5-3.94 5.27 5.27 0 01.15-3.9S8.2 5.7 11 7.58a13.9 13.9 0 017.34 0c2.8-1.9 4.03-1.5 4.03-1.5a5.27 5.27 0 01.15 3.9 5.67 5.67 0 011.5 3.93c0 5.63-3.42 6.87-6.7 7.24a3.5 3.5 0 011 2.71l-.01 4.03c0 .39.26.85 1 .7A14.67 14.67 0 0014.67 0z"/></svg>

Before

Width:  |  Height:  |  Size: 588 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,382 +0,0 @@
import { h, Component } from 'preact';
import { linkRef } from 'shared/prerendered-app/util';
import '../../custom-els/loading-spinner';
import logo from 'url:./imgs/logo.svg';
import githubLogo from 'url:./imgs/github-logo.svg';
import largePhoto from 'url:./imgs/demos/demo-large-photo.jpg';
import artwork from 'url:./imgs/demos/demo-artwork.jpg';
import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg';
import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg';
import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg';
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
import logoWithText from 'url:./imgs/logo-with-text.svg';
import * as style from './style.css';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import 'shared/custom-els/snack-bar';
import { startBlobs } from './blob-anim/meta';
const demos = [
{
description: 'Large photo',
size: '2.8mb',
filename: 'photo.jpg',
url: largePhoto,
iconUrl: largePhotoIcon,
},
{
description: 'Artwork',
size: '2.9mb',
filename: 'art.jpg',
url: artwork,
iconUrl: artworkIcon,
},
{
description: 'Device screen',
size: '1.6mb',
filename: 'pixel3.png',
url: deviceScreen,
iconUrl: deviceScreenIcon,
},
{
description: 'SVG icon',
size: '13k',
filename: 'squoosh.svg',
url: logo,
iconUrl: logoIcon,
},
] as const;
const blobAnimImport =
!__PRERENDER__ && matchMedia('(prefers-reduced-motion: reduce)').matches
? undefined
: import('./blob-anim');
const installButtonSource = 'introInstallButton-Purple';
const supportsClipboardAPI =
!__PRERENDER__ && navigator.clipboard && navigator.clipboard.read;
async function getImageClipboardItem(
items: ClipboardItem[],
): Promise<undefined | Blob> {
for (const item of items) {
const type = item.types.find((type) => type.startsWith('image/'));
if (type) return item.getType(type);
}
}
interface Props {
onFile?: (file: File) => void;
showSnack?: SnackBarElement['showSnackbar'];
}
interface State {
fetchingDemoIndex?: number;
beforeInstallEvent?: BeforeInstallPromptEvent;
showBlobSVG: boolean;
}
export default class Intro extends Component<Props, State> {
state: State = {
showBlobSVG: true,
};
private fileInput?: HTMLInputElement;
private blobCanvas?: HTMLCanvasElement;
private installingViaButton = false;
componentDidMount() {
// Listen for beforeinstallprompt events, indicating Squoosh is installable.
window.addEventListener(
'beforeinstallprompt',
this.onBeforeInstallPromptEvent,
);
// Listen for the appinstalled event, indicating Squoosh has been installed.
window.addEventListener('appinstalled', this.onAppInstalled);
if (blobAnimImport) {
blobAnimImport.then((module) => {
this.setState(
{
showBlobSVG: false,
},
() => module.startBlobAnim(this.blobCanvas!),
);
});
}
// TODO: remove this
const demo = demos[3];
fetch(demo.url)
.then((r) => r.blob())
.then((blob) =>
this.props.onFile!(
new File([blob], demo.filename, { type: blob.type }),
),
);
}
componentWillUnmount() {
window.removeEventListener(
'beforeinstallprompt',
this.onBeforeInstallPromptEvent,
);
window.removeEventListener('appinstalled', this.onAppInstalled);
}
private onFileChange = (event: Event): void => {
const fileInput = event.target as HTMLInputElement;
const file = fileInput.files && fileInput.files[0];
if (!file) return;
this.fileInput!.value = '';
this.props.onFile!(file);
};
private onOpenClick = () => {
this.fileInput!.click();
};
private onDemoClick = async (index: number, event: Event) => {
try {
this.setState({ fetchingDemoIndex: index });
const demo = demos[index];
const blob = await fetch(demo.url).then((r) => r.blob());
const file = new File([blob], demo.filename, { type: blob.type });
this.props.onFile!(file);
} catch (err) {
this.setState({ fetchingDemoIndex: undefined });
this.props.showSnack!("Couldn't fetch demo image");
}
};
private onBeforeInstallPromptEvent = (event: BeforeInstallPromptEvent) => {
// Don't show the mini-infobar on mobile
event.preventDefault();
// Save the beforeinstallprompt event so it can be called later.
this.setState({ beforeInstallEvent: event });
// Log the event.
const gaEventInfo = {
eventCategory: 'pwa-install',
eventAction: 'promo-shown',
nonInteraction: true,
};
ga('send', 'event', gaEventInfo);
};
private onInstallClick = async (event: Event) => {
// Get the deferred beforeinstallprompt event
const beforeInstallEvent = this.state.beforeInstallEvent;
// If there's no deferred prompt, bail.
if (!beforeInstallEvent) return;
this.installingViaButton = true;
// Show the browser install prompt
beforeInstallEvent.prompt();
// Wait for the user to accept or dismiss the install prompt
const { outcome } = await beforeInstallEvent.userChoice;
// Send the analytics data
const gaEventInfo = {
eventCategory: 'pwa-install',
eventAction: 'promo-clicked',
eventLabel: installButtonSource,
eventValue: outcome === 'accepted' ? 1 : 0,
};
ga('send', 'event', gaEventInfo);
// If the prompt was dismissed, we aren't going to install via the button.
if (outcome === 'dismissed') {
this.installingViaButton = false;
}
};
private onAppInstalled = () => {
// We don't need the install button, if it's shown
this.setState({ beforeInstallEvent: undefined });
// Don't log analytics if page is not visible
if (document.hidden) return;
// Try to get the install, if it's not set, use 'browser'
const source = this.installingViaButton ? installButtonSource : 'browser';
ga('send', 'event', 'pwa-install', 'installed', source);
// Clear the install method property
this.installingViaButton = false;
};
private onPasteClick = async () => {
let clipboardItems: ClipboardItem[];
try {
clipboardItems = await navigator.clipboard.read();
} catch (err) {
this.props.showSnack!(`No permission to access clipboard`);
return;
}
const blob = await getImageClipboardItem(clipboardItems);
if (!blob) {
this.props.showSnack!(`No image found in the clipboard`);
return;
}
this.props.onFile!(new File([blob], 'image.unknown'));
};
render(
{}: Props,
{ fetchingDemoIndex, beforeInstallEvent, showBlobSVG }: State,
) {
return (
<div class={style.intro}>
<input
class={style.hide}
ref={linkRef(this, 'fileInput')}
type="file"
onChange={this.onFileChange}
/>
<div class={style.main}>
{!__PRERENDER__ && (
<canvas
ref={linkRef(this, 'blobCanvas')}
class={style.blobCanvas}
/>
)}
<h1 class={style.logoContainer}>
<img
class={style.logo}
src={logoWithText}
alt="Squoosh"
width="539"
height="162"
/>
</h1>
<div class={style.loadImg}>
{showBlobSVG && (
<svg
class={style.blobSvg}
viewBox="-1.25 -1.25 2.5 2.5"
preserveAspectRatio="xMidYMid slice"
>
{startBlobs.map((points) => (
<path
d={points
.map((point, i) => {
const nextI = i === points.length - 1 ? 0 : i + 1;
let d = '';
if (i === 0) {
d += `M${point[2]} ${point[3]}`;
}
return (
d +
`C${point[4]} ${point[5]} ${points[nextI][0]} ${points[nextI][1]} ${points[nextI][2]} ${points[nextI][3]}`
);
})
.join('')}
/>
))}
</svg>
)}
<div
class={style.loadImgContent}
style={{ visibility: __PRERENDER__ ? 'hidden' : '' }}
>
<button class={style.loadBtn} onClick={this.onOpenClick}>
<svg viewBox="0 0 24 24" class={style.loadIcon}>
<path d="M19 7v3h-2V7h-3V5h3V2h2v3h3v2h-3zm-3 4V8h-3V5H5a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2v-8h-3zM5 19l3-4 2 3 3-4 4 5H5z" />
</svg>
</button>
<div>
<span class={style.dropText}>Drop </span>OR{' '}
{supportsClipboardAPI ? (
<button class={style.pasteBtn} onClick={this.onPasteClick}>
Paste
</button>
) : (
'Paste'
)}
</div>
</div>
</div>
</div>
<div class={style.demosContainer}>
<svg viewBox="0 0 1920 140" class={style.topWave}>
<path
d="M1920 0l-107 28c-106 29-320 85-533 93-213 7-427-36-640-50s-427 0-533 7L0 85v171h1920z"
class={style.subWave}
/>
<path
d="M0 129l64-26c64-27 192-81 320-75 128 5 256 69 384 64 128-6 256-80 384-91s256 43 384 70c128 26 256 26 320 26h64v96H0z"
class={style.mainWave}
/>
</svg>
<div class={style.contentPadding}>
<p class={style.demoTitle}>
Or <strong>try one</strong> of these:
</p>
<ul class={style.demos}>
{demos.map((demo, i) => (
<li>
<button
class="unbutton"
onClick={(event) => this.onDemoClick(i, event)}
>
<div>
<div class={style.demoIconContainer}>
<img
class={style.demoIcon}
src={demo.iconUrl}
alt={demo.description}
/>
{fetchingDemoIndex === i && (
<div class={style.demoLoader}>
<loading-spinner />
</div>
)}
</div>
<div class={style.demoSize}>{demo.size}</div>
</div>
</button>
</li>
))}
</ul>
</div>
</div>
<div class={style.footer}>
<svg viewBox="0 0 1920 79" class={style.topWave}>
<path
d="M0 59l64-11c64-11 192-34 320-43s256-5 384 4 256 23 384 34 256 21 384 14 256-30 320-41l64-11v94H0z"
class={style.footerWave}
/>
</svg>
<div class={style.contentPadding}>
<footer class={style.footerItems}>
<a
class={style.footerLink}
href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy"
>
Privacy
</a>
<a
class={style.footerLinkWithLogo}
href="https://github.com/GoogleChromeLabs/squoosh"
>
<img src={githubLogo} alt="" width="10" height="10" />
Source on Github
</a>
</footer>
</div>
</div>
{beforeInstallEvent && (
<button class={style.installBtn} onClick={this.onInstallClick}>
Install
</button>
)}
</div>
);
}
}

View File

@@ -1,243 +0,0 @@
.intro {
composes: abs-fill from global;
-webkit-overflow-scrolling: touch;
overflow: auto;
overscroll-behavior: contain;
display: grid;
grid-template-rows: 1fr max-content max-content;
font-size: 1.2rem;
color: var(--dark-text);
}
.blob-canvas {
composes: abs-fill from global;
width: 100%;
height: 100%;
}
.hide {
display: none;
}
.main {
min-height: 541px;
display: grid;
grid-template-rows: max-content max-content;
justify-items: center;
position: relative;
--blob-pink: var(--hot-pink);
--center-blob-opacity: 0.3;
@media (min-width: 600px) {
min-height: 688px;
}
}
.logo-container {
margin: 5rem 0 1rem;
}
.logo {
transform: translate(-1%, 0);
width: 189px;
height: auto;
}
.load-img {
position: relative;
color: var(--white);
font-style: italic;
font-size: 1.2rem;
}
.blob-svg {
composes: abs-fill from global;
width: 100%;
height: 100%;
fill: var(--blob-pink);
& path {
opacity: var(--center-blob-opacity);
}
}
.load-img-content {
position: relative;
--size: 29rem;
max-width: var(--size);
width: 100vw;
height: var(--size);
display: grid;
grid-template-rows: max-content max-content;
justify-items: center;
align-content: center;
gap: 0.7rem;
@media (min-width: 600px) {
--size: 36rem;
}
}
.load-btn {
composes: unbutton from global;
}
.load-icon {
--size: 5rem;
width: var(--size);
height: var(--size);
fill: var(--white);
transform: translate(4.3%, -1%);
}
.paste-btn {
composes: unbutton from global;
text-decoration: underline;
font: inherit;
color: inherit;
}
.demos-container {
position: relative;
background: var(--deep-blue);
padding-bottom: 5.2vw;
}
.top-wave {
position: absolute;
left: 0;
right: 0;
bottom: 100%;
}
.main-wave {
fill: var(--deep-blue);
}
.sub-wave {
fill: var(--light-blue);
}
.footer {
position: relative;
background: var(--light-gray);
}
.footer-wave {
fill: var(--light-gray);
}
.content-padding {
padding: 2rem;
}
.footer-items {
display: grid;
justify-content: end;
grid-auto-columns: max-content;
grid-auto-flow: column;
align-items: center;
gap: 4rem;
}
.footer-link {
text-decoration: none;
color: inherit;
}
.footer-link-with-logo {
composes: footer-link;
display: grid;
grid-template-columns: 1.8em max-content;
align-items: center;
gap: 0.6em;
img {
width: 100%;
height: auto;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
}
.install-btn {
composes: unbutton from global;
position: absolute;
top: 1rem;
right: 1rem;
background: var(--deep-blue);
border-radius: 0.4em;
color: var(--white);
padding: 0.5em 1em;
font-size: 1.6rem;
animation: fade-in 600ms ease-in-out;
}
.demo-title {
color: var(--white);
margin: 0;
font-size: 2rem;
text-align: center;
}
.demos {
display: grid;
gap: 3rem;
justify-items: center;
justify-content: center;
padding: 0;
margin: 3rem auto;
--demo-size: 80px;
grid-template-columns: repeat(auto-fit, var(--demo-size));
@media (min-width: 740px) {
--demo-size: 100px;
gap: 6rem;
}
& > li {
display: block;
}
}
.demo-size {
background: var(--dim-blue);
border-radius: 1000px;
color: var(--white);
width: max-content;
padding: 0.5rem 1.2rem;
margin: 0.7rem auto 0;
}
.demo-icon-container {
border-radius: var(--demo-size);
position: relative;
overflow: hidden;
}
.demo-icon {
width: var(--demo-size);
height: var(--demo-size);
display: block;
}
.demo-loader {
composes: abs-fill from global;
background: rgba(0, 0, 0, 0.5);
display: grid;
justify-content: center;
align-content: center;
animation: fade-in 600ms ease-in-out;
& > loading-spinner {
--color: var(--white);
}
}
.drop-text {
@media (max-width: 599px) {
display: none;
}
}

View File

@@ -1,21 +0,0 @@
html {
--pink: #ff3385;
--hot-pink: #ff0066;
--white: #fff;
--black: #000;
--blue: #5fb4e4;
--dim-blue: #0a7bcc;
--deep-blue: #09f;
--light-blue: #76c8ff;
--light-gray: #eaeaea;
--dark-text: #343a3e;
/* Old stuff: */
--gray-dark: rgba(0, 0, 0, 0.8);
--button-fg-color: 95, 180, 228;
--button-fg: rgb(95, 180, 228);
--negative: rgb(207, 113, 127);
--positive: rgb(149, 212, 159);
}

View File

@@ -1,24 +0,0 @@
:global {
.abs-fill {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
contain: strict;
}
.unbutton {
cursor: pointer;
background: none;
border: none;
font: inherit;
padding: 0;
margin: 0;
&:focus {
outline: none;
}
}
}

View File

@@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
/// <reference path="../../missing-types.d.ts" /> /// <reference path="../../missing-types.d.ts" />
/// <reference path="../shared/prerendered-app/Intro/missing-types.d.ts" /> /// <reference path="../shared/initial-app/Intro/missing-types.d.ts" />
declare module 'client-bundle:*' { declare module 'client-bundle:*' {
const url: string; const url: string;

View File

@@ -25,8 +25,8 @@ body {
height: 100%; height: 100%;
padding: 0; padding: 0;
margin: 0; margin: 0;
font: 12px/1.3 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', font: 12px/1.3 system-ui, -apple-system, BlinkMacSystemFont, Roboto, Helvetica,
Helvetica, Arial, 'Lucida Grande', sans-serif; sans-serif;
overflow: hidden; overflow: hidden;
overscroll-behavior: none; overscroll-behavior: none;
contain: strict; contain: strict;
@@ -34,6 +34,16 @@ body {
background-size: 20px 20px; background-size: 20px 20px;
} }
:root {
--gray-dark: rgba(0, 0, 0, 0.8);
--button-fg-color: 95, 180, 228;
--button-fg: rgb(95, 180, 228);
--negative: rgb(207, 113, 127);
--positive: rgb(149, 212, 159);
}
:global(#app) { :global(#app) {
position: absolute; position: absolute;
left: 0; left: 0;

View File

@@ -17,7 +17,7 @@ import initialCss from 'initial-css:';
import { allSrc } from 'client-bundle:client/initial-app'; import { allSrc } from 'client-bundle:client/initial-app';
import favicon from 'url:static-build/assets/favicon.ico'; import favicon from 'url:static-build/assets/favicon.ico';
import { escapeStyleScriptContent } from 'static-build/utils'; import { escapeStyleScriptContent } from 'static-build/utils';
import Intro from 'shared/prerendered-app/Intro'; import Intro from 'shared/initial-app/Intro';
interface Props {} interface Props {}