mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 17:27:09 +00:00
Compare commits
31 Commits
detect-ima
...
cli-invoc-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce3d94297d | ||
|
|
76ef6294f3 | ||
|
|
50bc8e4106 | ||
|
|
1b8f051438 | ||
|
|
cf894b7d19 | ||
|
|
d1d181fccd | ||
|
|
a65bbdf811 | ||
|
|
7de8fa9da3 | ||
|
|
646747b039 | ||
|
|
a2fb7a38cd | ||
|
|
81890e972b | ||
|
|
c2aa35aa02 | ||
|
|
c6d936cd49 | ||
|
|
60b79da936 | ||
|
|
dbd80f15eb | ||
|
|
ed3c79894d | ||
|
|
213028cfdd | ||
|
|
952aea049d | ||
|
|
e3b053db12 | ||
|
|
b8574b228a | ||
|
|
ee8ea539e7 | ||
|
|
a7a991ae45 | ||
|
|
32232c7f0b | ||
|
|
bb78632cf5 | ||
|
|
68cd15bd14 | ||
|
|
bde3a93b6e | ||
|
|
7aeef5ff37 | ||
|
|
0ee234f03b | ||
|
|
8105633ca6 | ||
|
|
46764f3375 | ||
|
|
0371cfd292 |
@@ -30,7 +30,7 @@ npm run build
|
|||||||
You can run the development server with:
|
You can run the development server with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run dev
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
[squoosh]: https://squoosh.app
|
[squoosh]: https://squoosh.app
|
||||||
|
|||||||
@@ -183,36 +183,31 @@ function progressTracker(results) {
|
|||||||
return tracker;
|
return tracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getInputFiles(paths) {
|
async function checkInputFilesValid(files) {
|
||||||
const validFiles = [];
|
const validFiles = [];
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const file of files) {
|
||||||
const files = (await fsp.lstat(path)).isDirectory()
|
try {
|
||||||
? (await fsp.readdir(path)).map(file => join(path, file))
|
await fsp.stat(file);
|
||||||
: [path];
|
} catch (err) {
|
||||||
for (const file of files) {
|
if (err.code === 'ENOENT') {
|
||||||
try {
|
console.warn(
|
||||||
await fsp.stat(file);
|
`Warning: Input file does not exist: ${resolvePath(file)}`,
|
||||||
} catch (err) {
|
);
|
||||||
if (err.code === 'ENOENT') {
|
continue;
|
||||||
console.warn(
|
} else {
|
||||||
`Warning: Input file does not exist: ${resolvePath(file)}`,
|
throw err;
|
||||||
);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
validFiles.push(file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validFiles.push(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
return validFiles;
|
return validFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processFiles(files) {
|
async function processFiles(files) {
|
||||||
files = await getInputFiles(files);
|
files = await checkInputFilesValid(files);
|
||||||
|
|
||||||
const parallelism = cpus().length;
|
const parallelism = cpus().length;
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function jobPromise(worker, msg) {
|
|||||||
|
|
||||||
export default class WorkerPool {
|
export default class WorkerPool {
|
||||||
constructor(numWorkers, workerFile) {
|
constructor(numWorkers, workerFile) {
|
||||||
this.numWorkers = numWorkers;
|
this.closing = false;
|
||||||
this.jobQueue = new TransformStream();
|
this.jobQueue = new TransformStream();
|
||||||
this.workerQueue = new TransformStream();
|
this.workerQueue = new TransformStream();
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ export default class WorkerPool {
|
|||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
|
this.workerQueue.writable.close();
|
||||||
await this._terminateAll();
|
await this._terminateAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,12 @@ export default class WorkerPool {
|
|||||||
const worker = await this._nextWorker();
|
const worker = await this._nextWorker();
|
||||||
jobPromise(worker, msg).then((result) => {
|
jobPromise(worker, msg).then((result) => {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
|
// If we are in the process of closing, `workerQueue` is
|
||||||
|
// already closed and we can’t requeue the worker.
|
||||||
|
if (this.closing) {
|
||||||
|
worker.terminate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const writer = this.workerQueue.writable.getWriter();
|
const writer = this.workerQueue.writable.getWriter();
|
||||||
writer.write(worker);
|
writer.write(worker);
|
||||||
writer.releaseLock();
|
writer.releaseLock();
|
||||||
@@ -64,15 +71,18 @@ export default class WorkerPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _terminateAll() {
|
async _terminateAll() {
|
||||||
for (let n = 0; n < this.numWorkers; n++) {
|
while (true) {
|
||||||
const worker = await this._nextWorker();
|
const worker = await this._nextWorker();
|
||||||
|
if (!worker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
worker.terminate();
|
worker.terminate();
|
||||||
}
|
}
|
||||||
this.workerQueue.writable.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async join() {
|
async join() {
|
||||||
this.jobQueue.writable.getWriter().close();
|
this.closing = true;
|
||||||
|
this.jobQueue.writable.close();
|
||||||
await this.done;
|
await this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CODEC_URL = https://gitlab.com/wg1/jpeg-xl.git
|
CODEC_URL = https://gitlab.com/wg1/jpeg-xl.git
|
||||||
CODEC_VERSION = v0.2
|
CODEC_VERSION = 739e6cd1305fdec5acfa47ad414189b3d3ecb6a4
|
||||||
CODEC_DIR = node_modules/jxl
|
CODEC_DIR = node_modules/jxl
|
||||||
CODEC_BUILD_ROOT := $(CODEC_DIR)/build
|
CODEC_BUILD_ROOT := $(CODEC_DIR)/build
|
||||||
CODEC_MT_BUILD_DIR := $(CODEC_BUILD_ROOT)/mt
|
CODEC_MT_BUILD_DIR := $(CODEC_BUILD_ROOT)/mt
|
||||||
@@ -41,6 +41,8 @@ $(OUT_JS):
|
|||||||
-I $(CODEC_BUILD_DIR)/lib/include \
|
-I $(CODEC_BUILD_DIR)/lib/include \
|
||||||
-I $(CODEC_DIR)/third_party/highway \
|
-I $(CODEC_DIR)/third_party/highway \
|
||||||
-I $(CODEC_DIR)/third_party/skcms \
|
-I $(CODEC_DIR)/third_party/skcms \
|
||||||
|
-I $(CODEC_DIR)/third_party/brunsli \
|
||||||
|
-I $(CODEC_DIR)/third_party/brunsli/c/include \
|
||||||
--bind \
|
--bind \
|
||||||
--closure 1 \
|
--closure 1 \
|
||||||
-s ALLOW_MEMORY_GROWTH=1 \
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
@@ -51,6 +53,9 @@ $(OUT_JS):
|
|||||||
-s EXPORT_NAME="$(basename $(@F))" \
|
-s EXPORT_NAME="$(basename $(@F))" \
|
||||||
-o $@ \
|
-o $@ \
|
||||||
$+ \
|
$+ \
|
||||||
|
$(CODEC_BUILD_DIR)/artifacts/libbrunslienc-static.bc \
|
||||||
|
$(CODEC_BUILD_DIR)/artifacts/libbrunslicommon-static.bc \
|
||||||
|
$(CODEC_BUILD_DIR)/artifacts/libbrunslidec-static.bc \
|
||||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlidec-static.a \
|
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlidec-static.a \
|
||||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlienc-static.a \
|
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlienc-static.a \
|
||||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlicommon-static.a \
|
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlicommon-static.a \
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ val decode(std::string data) {
|
|||||||
icc_profile.data(), icc_profile.size()));
|
icc_profile.data(), icc_profile.size()));
|
||||||
|
|
||||||
auto float_pixels = std::make_unique<float[]>(component_count);
|
auto float_pixels = std::make_unique<float[]>(component_count);
|
||||||
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
|
||||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec.get(), &format, float_pixels.get(),
|
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec.get(), &format, float_pixels.get(),
|
||||||
component_count * sizeof(float)));
|
component_count * sizeof(float)));
|
||||||
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -102,8 +102,8 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
|||||||
auto result = jxl::ConvertImage(
|
auto result = jxl::ConvertImage(
|
||||||
jxl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(image.data()), image.size()), width,
|
jxl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(image.data()), image.size()), width,
|
||||||
height, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/true,
|
height, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/true,
|
||||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/8, JXL_LITTLE_ENDIAN,
|
/*alpha_is_premultiplied=*/false, /*bits_per_alpha=*/8, /*bits_per_sample=*/8,
|
||||||
/*flipped_y=*/false, pool_ptr, main);
|
/*big_endian=*/false, /*flipped_y=*/false, pool_ptr, main);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return val::null();
|
return val::null();
|
||||||
|
|||||||
Binary file not shown.
4
codecs/jxl/enc/jxl_enc_mt.js
generated
4
codecs/jxl/enc/jxl_enc_mt.js
generated
@@ -17,7 +17,7 @@ function Da(a){return 2*a.length}function Ea(a,b){for(var c=0,d="";!(c>=b/4);){v
|
|||||||
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
|
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
|
||||||
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
|
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
|
||||||
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt.wasm";
|
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt.wasm";
|
||||||
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60853:function(a,b){setTimeout(function(){Ta(a,b)},0)},60931:function(){throw"Canceled!";}};
|
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60341:function(a,b){setTimeout(function(){Ta(a,b)},0)},60419:function(){throw"Canceled!";}};
|
||||||
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
|
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
|
||||||
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
|
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
|
||||||
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
|
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
|
||||||
@@ -98,7 +98,7 @@ D._emscripten_sync_run_in_main_thread_1=function(){return(D._emscripten_sync_run
|
|||||||
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
|
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
|
||||||
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
|
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
|
||||||
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
|
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
|
||||||
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3060568;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
|
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3060056;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
|
||||||
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
|
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
|
||||||
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
|
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
|
||||||
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
|
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
|
||||||
|
|||||||
Binary file not shown.
4
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
4
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
@@ -17,7 +17,7 @@ function Da(a){return 2*a.length}function Ea(a,b){for(var c=0,d="";!(c>=b/4);){v
|
|||||||
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
|
function Ga(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}function Ha(a,b){e().set(a,b)}var n,aa,ba,ca,fa,ha,ia,ka,ma;function u(a){n=a;D.HEAP8=aa=new Int8Array(a);D.HEAP16=ca=new Int16Array(a);D.HEAP32=ha=new Int32Array(a);D.HEAPU8=ba=new Uint8Array(a);D.HEAPU16=fa=new Uint16Array(a);D.HEAPU32=ia=new Uint32Array(a);D.HEAPF32=ka=new Float32Array(a);D.HEAPF64=ma=new Float64Array(a)}var Ia=D.INITIAL_MEMORY||16777216;
|
||||||
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
|
if(G)m=D.wasmMemory,n=D.buffer;else if(D.wasmMemory)m=D.wasmMemory;else if(m=new WebAssembly.Memory({initial:Ia/65536,maximum:32768,shared:!0}),!(m.buffer instanceof SharedArrayBuffer))throw J("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),Error("bad memory");m&&(n=m.buffer);Ia=n.byteLength;u(n);var M,Ja=[],Ka=[],La=[],Ma=[];
|
||||||
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt_simd.wasm";
|
function Na(){var a=D.preRun.shift();Ja.unshift(a)}var N=0,Oa=null,Pa=null;D.preloadedImages={};D.preloadedAudios={};function K(a){if(D.onAbort)D.onAbort(a);G&&console.error("Pthread aborting at "+Error().stack);J(a);va=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");oa(a);throw a;}function Qa(){var a=O;return String.prototype.startsWith?a.startsWith("data:application/octet-stream;base64,"):0===a.indexOf("data:application/octet-stream;base64,")}var O="jxl_enc_mt_simd.wasm";
|
||||||
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60933:function(a,b){setTimeout(function(){Ta(a,b)},0)},61011:function(){throw"Canceled!";}};
|
Qa()||(O=qa(O));function Ra(){try{if(ta)return new Uint8Array(ta);if(ra)return ra(O);throw"both async and sync fetching of the wasm failed";}catch(a){K(a)}}function Sa(){return ta||"function"!==typeof fetch?Promise.resolve().then(Ra):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return Ra()})}var Ua={60389:function(a,b){setTimeout(function(){Ta(a,b)},0)},60467:function(){throw"Canceled!";}};
|
||||||
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
|
function Va(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(D);else{var c=b.Bb;"number"===typeof c?void 0===b.Va?M.get(c)():M.get(c)(b.Va):c(void 0===b.Va?null:b.Va)}}}function Wa(a,b,c){var d;-1!=a.indexOf("j")?d=c&&c.length?D["dynCall_"+a].apply(null,[b].concat(c)):D["dynCall_"+a].call(null,b):d=M.get(b).apply(null,c);return d}D.dynCall=Wa;var P=0,Xa=0,Ya=0;function Za(a,b,c){P=a|0;Ya=b|0;Xa=c|0}D.registerPthreadPtr=Za;
|
||||||
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
|
function $a(a,b){if(0>=a||a>e().length||a&1||0>b)return-28;if(0==b)return 0;2147483647<=b&&(b=Infinity);var c=Atomics.load(A(),Q.rb>>2),d=0;if(c==a&&Atomics.compareExchange(A(),Q.rb>>2,c,0)==c&&(--b,d=1,0>=b))return 1;a=Atomics.notify(A(),a>>2,b);if(0<=a)return a+d;throw"Atomics.notify returned an unexpected value "+a;}D._emscripten_futex_wake=$a;
|
||||||
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
|
function ab(a){if(G)throw"Internal Error! cleanupThread() can only ever be called from main application thread!";if(!a)throw"Internal Error! Null pthread_ptr in cleanupThread!";A()[a+12>>2]=0;(a=Q.Oa[a])&&Q.bb(a.worker)}
|
||||||
@@ -99,7 +99,7 @@ D._emscripten_sync_run_in_main_thread_1=function(){return(D._emscripten_sync_run
|
|||||||
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
|
D._emscripten_sync_run_in_main_thread_3=function(){return(D._emscripten_sync_run_in_main_thread_3=D.asm.va).apply(null,arguments)};var Ec=D._emscripten_sync_run_in_main_thread_4=function(){return(Ec=D._emscripten_sync_run_in_main_thread_4=D.asm.wa).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_5=function(){return(D._emscripten_sync_run_in_main_thread_5=D.asm.xa).apply(null,arguments)};
|
||||||
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
|
D._emscripten_sync_run_in_main_thread_6=function(){return(D._emscripten_sync_run_in_main_thread_6=D.asm.ya).apply(null,arguments)};D._emscripten_sync_run_in_main_thread_7=function(){return(D._emscripten_sync_run_in_main_thread_7=D.asm.za).apply(null,arguments)};
|
||||||
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
|
var Ub=D._emscripten_run_in_main_runtime_thread_js=function(){return(Ub=D._emscripten_run_in_main_runtime_thread_js=D.asm.Aa).apply(null,arguments)},$b=D.__emscripten_call_on_thread=function(){return($b=D.__emscripten_call_on_thread=D.asm.Ba).apply(null,arguments)};D._emscripten_tls_init=function(){return(D._emscripten_tls_init=D.asm.Ca).apply(null,arguments)};D.dynCall_viijii=function(){return(D.dynCall_viijii=D.asm.Da).apply(null,arguments)};
|
||||||
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3060648;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
|
D.dynCall_iiji=function(){return(D.dynCall_iiji=D.asm.Ea).apply(null,arguments)};D.dynCall_jiji=function(){return(D.dynCall_jiji=D.asm.Fa).apply(null,arguments)};D.dynCall_iiiiiijj=function(){return(D.dynCall_iiiiiijj=D.asm.Ga).apply(null,arguments)};D.dynCall_iiiiij=function(){return(D.dynCall_iiiiij=D.asm.Ha).apply(null,arguments)};D.dynCall_iiiiijj=function(){return(D.dynCall_iiiiijj=D.asm.Ia).apply(null,arguments)};var cb=D._main_thread_futex=3060104;D.PThread=Q;D.PThread=Q;D._pthread_self=pc;
|
||||||
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
|
D.wasmMemory=m;D.ExitStatus=Hc;var Ic;function Hc(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}Pa=function Jc(){Ic||Kc();Ic||(Pa=Jc)};
|
||||||
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
|
function Kc(){function a(){if(!Ic&&(Ic=!0,D.calledRun=!0,!va)){Va(Ka);G||Va(La);na(D);if(D.onRuntimeInitialized)D.onRuntimeInitialized();if(!G){if(D.postRun)for("function"==typeof D.postRun&&(D.postRun=[D.postRun]);D.postRun.length;){var b=D.postRun.shift();Ma.unshift(b)}Va(Ma)}}}if(!(0<N)){if(!G){if(D.preRun)for("function"==typeof D.preRun&&(D.preRun=[D.preRun]);D.preRun.length;)Na();Va(Ja)}0<N||(D.setStatus?(D.setStatus("Running..."),setTimeout(function(){setTimeout(function(){D.setStatus("")},
|
||||||
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
|
1);a()},1)):a())}}D.run=Kc;if(D.preInit)for("function"==typeof D.preInit&&(D.preInit=[D.preInit]);0<D.preInit.length;)D.preInit.pop()();G||(noExitRuntime=!0);G?Q.Lb():Kc();
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
92
codecs/oxipng/Cargo.lock
generated
92
codecs/oxipng/Cargo.lock
generated
@@ -20,9 +20,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-vec"
|
name = "bit-vec"
|
||||||
version = "0.6.3"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
@@ -56,9 +56,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.66"
|
version = "1.0.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
@@ -98,9 +98,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_fn"
|
name = "const_fn"
|
||||||
version = "0.4.5"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
@@ -143,9 +143,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-epoch"
|
name = "crossbeam-epoch"
|
||||||
version = "0.9.1"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
|
checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"const_fn",
|
"const_fn",
|
||||||
@@ -157,12 +157,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.1"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
"const_fn",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -199,9 +200,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.23.12"
|
version = "0.23.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5"
|
checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -214,9 +215,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.6.1"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
|
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -225,9 +226,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319"
|
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
@@ -240,24 +241,24 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.81"
|
version = "0.2.80"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflate-sys"
|
name = "libdeflate-sys"
|
||||||
version = "0.7.1"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a95fa4be7085dd06a8296dcc3f399f12ab8b0309c157dcaa90669130b175b97"
|
checksum = "2f5b1582a0ebf8c55a46166c04d7c66f6bb17add3a6cbf69a082ac2219f31671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdeflater"
|
name = "libdeflater"
|
||||||
version = "0.7.1"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccc147465654929bf7b56518cc46d11701ba290f4ff94398ae3f89f1663cf60f"
|
checksum = "93edd93a53970951da84ef733a8b6e30189a8f8a9e19610f69e4cc5bb1f4d654"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libdeflate-sys",
|
"libdeflate-sys",
|
||||||
]
|
]
|
||||||
@@ -273,9 +274,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.6.1"
|
version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
|
checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
@@ -358,9 +359,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxipng"
|
name = "oxipng"
|
||||||
version = "4.0.3"
|
version = "4.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50d0b53912a666fe2970f8ab254e283531c816aed16551ab66c52485eadb44e6"
|
checksum = "9fefb26bde273c3db896a313151301a69e698a7495ee577fe2168ed7065c29c4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@@ -389,9 +390,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.16.8"
|
version = "0.16.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
@@ -410,9 +411,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.8"
|
version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
@@ -477,9 +478,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver-parser"
|
name = "semver-parser"
|
||||||
version = "0.10.2"
|
version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
@@ -489,7 +490,6 @@ name = "squoosh-oxipng"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"libdeflater",
|
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"oxipng",
|
"oxipng",
|
||||||
@@ -499,9 +499,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.58"
|
version = "1.0.48"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
|
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -522,19 +522,19 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.69"
|
version = "0.2.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
|
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 0.1.10",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.69"
|
version = "0.2.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
|
checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -547,9 +547,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.69"
|
version = "0.2.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
|
checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -557,9 +557,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.69"
|
version = "0.2.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
|
checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -570,6 +570,6 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.69"
|
version = "0.2.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ wasm-opt = ["-O", "--no-validation"]
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
oxipng = { version = "4.0.1", default-features = false, features = ["libdeflater"] }
|
oxipng = { version = "4.0.0", default-features = false, features = ["libdeflater"] }
|
||||||
libdeflater = { version = "0.7.1", features = ["freestanding"] }
|
|
||||||
wasm-bindgen = "0.2.68"
|
wasm-bindgen = "0.2.68"
|
||||||
log = { version = "0.4.11", features = ["release_max_level_off"] }
|
log = { version = "0.4.11", features = ["release_max_level_off"] }
|
||||||
rayon = { version = "1.5.0", optional = true }
|
rayon = { version = "1.5.0", optional = true }
|
||||||
|
|||||||
0
codecs/oxipng/build.sh
Executable file → Normal file
0
codecs/oxipng/build.sh
Executable file → Normal file
2
codecs/oxipng/pkg-parallel/squoosh_oxipng.d.ts
generated
vendored
2
codecs/oxipng/pkg-parallel/squoosh_oxipng.d.ts
generated
vendored
@@ -22,6 +22,8 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|||||||
|
|
||||||
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;
|
||||||
|
readonly malloc: (a: number) => number;
|
||||||
|
readonly free: (a: number) => void;
|
||||||
readonly worker_initializer: (a: number) => number;
|
readonly worker_initializer: (a: number) => number;
|
||||||
readonly start_main_thread: () => void;
|
readonly start_main_thread: () => void;
|
||||||
readonly start_worker_thread: () => void;
|
readonly start_worker_thread: () => void;
|
||||||
|
|||||||
Binary file not shown.
2
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm.d.ts
generated
vendored
2
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm.d.ts
generated
vendored
@@ -1,6 +1,8 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export function optimise(a: number, b: number, c: number, d: number): void;
|
export function optimise(a: number, b: number, c: number, d: number): void;
|
||||||
|
export function malloc(a: number): number;
|
||||||
|
export function free(a: number): void;
|
||||||
export function worker_initializer(a: number): number;
|
export function worker_initializer(a: number): number;
|
||||||
export function start_main_thread(): void;
|
export function start_main_thread(): void;
|
||||||
export function start_worker_thread(): void;
|
export function start_worker_thread(): void;
|
||||||
|
|||||||
2
codecs/oxipng/pkg/squoosh_oxipng.d.ts
generated
vendored
2
codecs/oxipng/pkg/squoosh_oxipng.d.ts
generated
vendored
@@ -12,6 +12,8 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|||||||
export interface InitOutput {
|
export interface InitOutput {
|
||||||
readonly memory: WebAssembly.Memory;
|
readonly memory: WebAssembly.Memory;
|
||||||
readonly optimise: (a: number, b: number, c: number, d: number) => void;
|
readonly optimise: (a: number, b: number, c: number, d: number) => void;
|
||||||
|
readonly malloc: (a: number) => number;
|
||||||
|
readonly free: (a: number) => void;
|
||||||
readonly __wbindgen_malloc: (a: number) => number;
|
readonly __wbindgen_malloc: (a: number) => number;
|
||||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
2
codecs/oxipng/pkg/squoosh_oxipng_bg.wasm.d.ts
generated
vendored
2
codecs/oxipng/pkg/squoosh_oxipng_bg.wasm.d.ts
generated
vendored
@@ -2,5 +2,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export function optimise(a: number, b: number, c: number, d: number): void;
|
export function optimise(a: number, b: number, c: number, d: number): void;
|
||||||
|
export function malloc(a: number): number;
|
||||||
|
export function free(a: number): void;
|
||||||
export function __wbindgen_malloc(a: number): number;
|
export function __wbindgen_malloc(a: number): number;
|
||||||
export function __wbindgen_free(a: number, b: number): void;
|
export function __wbindgen_free(a: number, b: number): void;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use oxipng::AlphaOptim;
|
use oxipng::AlphaOptim;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
mod malloc_shim;
|
||||||
|
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
pub mod parallel;
|
pub mod parallel;
|
||||||
|
|
||||||
|
|||||||
47
codecs/oxipng/src/malloc_shim.rs
Normal file
47
codecs/oxipng/src/malloc_shim.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//! This is a module that provides `malloc` and `free` for `libdeflate`.
|
||||||
|
//! These implementations are compatible with the standard signatures
|
||||||
|
//! but use Rust allocator instead of including libc one as well.
|
||||||
|
//!
|
||||||
|
//! Rust allocator APIs requires passing size and alignment to the
|
||||||
|
//! `dealloc` function. This is different from C API, which only
|
||||||
|
//! expects a pointer in `free` and expects allocators to take care of
|
||||||
|
//! storing any necessary information elsewhere.
|
||||||
|
//!
|
||||||
|
//! In order to simulate C API, we allocate a `size_and_data_ptr`
|
||||||
|
//! of size `sizeof(usize) + size` where `size` is the requested number
|
||||||
|
//! of bytes. Then, we store `size` at the beginning of the allocated
|
||||||
|
//! chunk (within those `sizeof(usize)` bytes) and return
|
||||||
|
//! `data_ptr = size_and_data_ptr + sizeof(usize)` to the calleer:
|
||||||
|
//!
|
||||||
|
//! [`size`][...actual data]
|
||||||
|
//! -^------------------ `size_and_data_ptr`
|
||||||
|
//! ---------^---------- `data_ptr`
|
||||||
|
//!
|
||||||
|
//! Then, in `free`, the caller gives us `data_ptr`. We can subtract
|
||||||
|
//! `sizeof(usize)` back and get the original `size_and_data_ptr`.
|
||||||
|
//! At this point we can read `size` back and call the Rust `dealloc`
|
||||||
|
//! for the whole allocated chunk.
|
||||||
|
//!
|
||||||
|
//! I've raised an upstream issue to hopefully make this easier in
|
||||||
|
//! future: https://github.com/ebiggers/libdeflate/issues/62
|
||||||
|
|
||||||
|
use std::alloc::*;
|
||||||
|
use std::mem::{align_of, size_of};
|
||||||
|
|
||||||
|
unsafe fn layout_for(size: usize) -> Layout {
|
||||||
|
Layout::from_size_align_unchecked(size_of::<usize>() + size, align_of::<usize>())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn malloc(size: usize) -> *mut u8 {
|
||||||
|
let size_and_data_ptr = alloc(layout_for(size));
|
||||||
|
*(size_and_data_ptr as *mut usize) = size;
|
||||||
|
size_and_data_ptr.add(size_of::<usize>())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn free(data_ptr: *mut u8) {
|
||||||
|
let size_and_data_ptr = data_ptr.sub(size_of::<usize>());
|
||||||
|
let size = *(size_and_data_ptr as *const usize);
|
||||||
|
dealloc(size_and_data_ptr, layout_for(size))
|
||||||
|
}
|
||||||
22
codecs/resize/Cargo.lock
generated
22
codecs/resize/Cargo.lock
generated
@@ -6,12 +6,6 @@ version = "3.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytemuck"
|
|
||||||
version = "1.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
@@ -108,21 +102,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "resize"
|
name = "resize"
|
||||||
version = "0.5.5"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2a08c42ea86684dc00256494c4eb8b54707890ddac50c05060a717f29669029"
|
checksum = "b9e653e390eafbfebb2b3c5fcfbc90d801bc410d0de1f44f266ffbf2151d28aa"
|
||||||
dependencies = [
|
|
||||||
"rgb",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rgb"
|
|
||||||
version = "0.8.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "287f3c3f8236abb92d8b7e36797f19159df4b58f0a658cc3fb6dd3004b1f3bd3"
|
|
||||||
dependencies = [
|
|
||||||
"bytemuck",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ default = ["console_error_panic_hook", "wee_alloc"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
cfg-if = "0.1.2"
|
cfg-if = "0.1.2"
|
||||||
wasm-bindgen = "0.2.38"
|
wasm-bindgen = "0.2.38"
|
||||||
resize = "0.5.5"
|
resize = "0.3.0"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
# logging them with `console.error`. This is great for development, but requires
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ use std::io::Write;
|
|||||||
|
|
||||||
fn main() -> std::io::Result<()> {
|
fn main() -> std::io::Result<()> {
|
||||||
let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = [");
|
let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = [");
|
||||||
|
let mut linear_to_srgb_lut = String::from("static LINEAR_TO_SRGB_LUT: [f32; 256] = [");
|
||||||
for i in 0..256 {
|
for i in 0..256 {
|
||||||
srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0)));
|
srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0)));
|
||||||
srgb_to_linear_lut.push_str(",");
|
srgb_to_linear_lut.push_str(",");
|
||||||
|
linear_to_srgb_lut.push_str(&format!("{0:.7}", linear_to_srgb((i as f32) / 255.0)));
|
||||||
|
linear_to_srgb_lut.push_str(",");
|
||||||
}
|
}
|
||||||
srgb_to_linear_lut.pop().unwrap();
|
srgb_to_linear_lut.pop().unwrap();
|
||||||
|
linear_to_srgb_lut.pop().unwrap();
|
||||||
srgb_to_linear_lut.push_str("];");
|
srgb_to_linear_lut.push_str("];");
|
||||||
|
linear_to_srgb_lut.push_str("];");
|
||||||
|
|
||||||
let mut file = std::fs::File::create("src/lut.inc")?;
|
let mut file = std::fs::File::create("src/lut.inc")?;
|
||||||
file.write_all(srgb_to_linear_lut.as_bytes())?;
|
file.write_all(srgb_to_linear_lut.as_bytes())?;
|
||||||
|
file.write_all(linear_to_srgb_lut.as_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
70
codecs/resize/pkg/squoosh_resize.d.ts
generated
vendored
70
codecs/resize/pkg/squoosh_resize.d.ts
generated
vendored
@@ -1,34 +1,60 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/**
|
/**
|
||||||
* @param {Uint8Array} input_image
|
* @param {Uint8Array} input_image
|
||||||
* @param {number} input_width
|
* @param {number} input_width
|
||||||
* @param {number} input_height
|
* @param {number} input_height
|
||||||
* @param {number} output_width
|
* @param {number} output_width
|
||||||
* @param {number} output_height
|
* @param {number} output_height
|
||||||
* @param {number} typ_idx
|
* @param {number} typ_idx
|
||||||
* @param {boolean} premultiply
|
* @param {boolean} premultiply
|
||||||
* @param {boolean} color_space_conversion
|
* @param {boolean} color_space_conversion
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function resize(input_image: Uint8Array, input_width: number, input_height: number, output_width: number, output_height: number, typ_idx: number, premultiply: boolean, color_space_conversion: boolean): Uint8Array;
|
export function resize(
|
||||||
|
input_image: Uint8Array,
|
||||||
|
input_width: number,
|
||||||
|
input_height: number,
|
||||||
|
output_width: number,
|
||||||
|
output_height: number,
|
||||||
|
typ_idx: number,
|
||||||
|
premultiply: boolean,
|
||||||
|
color_space_conversion: boolean,
|
||||||
|
): Uint8Array;
|
||||||
|
|
||||||
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;
|
||||||
readonly resize: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => void;
|
readonly resize: (
|
||||||
|
a: number,
|
||||||
|
b: number,
|
||||||
|
c: number,
|
||||||
|
d: number,
|
||||||
|
e: number,
|
||||||
|
f: number,
|
||||||
|
g: number,
|
||||||
|
h: number,
|
||||||
|
i: number,
|
||||||
|
j: number,
|
||||||
|
) => void;
|
||||||
readonly __wbindgen_malloc: (a: number) => number;
|
readonly __wbindgen_malloc: (a: number) => number;
|
||||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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>;
|
||||||
|
|||||||
170
codecs/resize/pkg/squoosh_resize.js
generated
170
codecs/resize/pkg/squoosh_resize.js
generated
@@ -1,107 +1,131 @@
|
|||||||
|
|
||||||
let wasm;
|
let wasm;
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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} input_image
|
* @param {Uint8Array} input_image
|
||||||
* @param {number} input_width
|
* @param {number} input_width
|
||||||
* @param {number} input_height
|
* @param {number} input_height
|
||||||
* @param {number} output_width
|
* @param {number} output_width
|
||||||
* @param {number} output_height
|
* @param {number} output_height
|
||||||
* @param {number} typ_idx
|
* @param {number} typ_idx
|
||||||
* @param {boolean} premultiply
|
* @param {boolean} premultiply
|
||||||
* @param {boolean} color_space_conversion
|
* @param {boolean} color_space_conversion
|
||||||
* @returns {Uint8Array}
|
* @returns {Uint8Array}
|
||||||
*/
|
*/
|
||||||
export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
|
export function resize(
|
||||||
var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
|
input_image,
|
||||||
var len0 = WASM_VECTOR_LEN;
|
input_width,
|
||||||
wasm.resize(8, ptr0, len0, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
|
input_height,
|
||||||
var r0 = getInt32Memory0()[8 / 4 + 0];
|
output_width,
|
||||||
var r1 = getInt32Memory0()[8 / 4 + 1];
|
output_height,
|
||||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
typ_idx,
|
||||||
wasm.__wbindgen_free(r0, r1 * 1);
|
premultiply,
|
||||||
return v1;
|
color_space_conversion,
|
||||||
|
) {
|
||||||
|
var ptr0 = passArray8ToWasm0(input_image, wasm.__wbindgen_malloc);
|
||||||
|
var len0 = WASM_VECTOR_LEN;
|
||||||
|
wasm.resize(
|
||||||
|
8,
|
||||||
|
ptr0,
|
||||||
|
len0,
|
||||||
|
input_width,
|
||||||
|
input_height,
|
||||||
|
output_width,
|
||||||
|
output_height,
|
||||||
|
typ_idx,
|
||||||
|
premultiply,
|
||||||
|
color_space_conversion,
|
||||||
|
);
|
||||||
|
var r0 = getInt32Memory0()[8 / 4 + 0];
|
||||||
|
var r1 = getInt32Memory0()[8 / 4 + 1];
|
||||||
|
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||||
|
wasm.__wbindgen_free(r0, r1 * 1);
|
||||||
|
return v1;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {};
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof input === 'string' ||
|
||||||
|
(typeof Request === 'function' && input instanceof Request) ||
|
||||||
|
(typeof URL === 'function' && input instanceof URL)
|
||||||
|
) {
|
||||||
|
input = fetch(input);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
const { instance, module } = await load(await input, imports);
|
||||||
input = fetch(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { instance, module } = await load(await input, imports);
|
wasm = instance.exports;
|
||||||
|
init.__wbindgen_wasm_module = module;
|
||||||
|
|
||||||
wasm = instance.exports;
|
return wasm;
|
||||||
init.__wbindgen_wasm_module = module;
|
|
||||||
|
|
||||||
return wasm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default init;
|
export default init;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -5,12 +5,12 @@ extern crate wasm_bindgen;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use resize::Pixel;
|
use resize::Pixel::RGBA;
|
||||||
use resize::Type;
|
use resize::Type;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
mod srgb;
|
mod srgb;
|
||||||
use srgb::{linear_to_srgb, Clamp};
|
use srgb::Clamp;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
@@ -27,17 +27,14 @@ include!("./lut.inc");
|
|||||||
// If `with_space_conversion` is true, this function returns 2 functions that
|
// If `with_space_conversion` is true, this function returns 2 functions that
|
||||||
// convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is
|
// convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is
|
||||||
// false, the 2 functions returned do nothing.
|
// false, the 2 functions returned do nothing.
|
||||||
fn srgb_converter_funcs(with_space_conversion: bool) -> (fn(u8) -> f32, fn(f32) -> u8) {
|
fn converter_funcs(with_space_conversion: bool) -> ((fn(u8) -> f32), (fn(f32) -> u8)) {
|
||||||
if with_space_conversion {
|
if with_space_conversion {
|
||||||
(
|
(
|
||||||
|v| SRGB_TO_LINEAR_LUT[v as usize],
|
|v| SRGB_TO_LINEAR_LUT[v as usize] * 255.0,
|
||||||
|v| (linear_to_srgb(v) * 255.0).clamp(0.0, 255.0) as u8,
|
|v| (LINEAR_TO_SRGB_LUT[v as usize] * 255.0) as u8,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(|v| v as f32, |v| v as u8)
|
||||||
|v| (v as f32) / 255.0,
|
|
||||||
|v| (v * 255.0).clamp(0.0, 255.0) as u8,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,18 +44,21 @@ fn srgb_converter_funcs(with_space_conversion: bool) -> (fn(u8) -> f32, fn(f32)
|
|||||||
// false, the functions just return the channel value.
|
// false, the functions just return the channel value.
|
||||||
fn alpha_multiplier_funcs(
|
fn alpha_multiplier_funcs(
|
||||||
with_alpha_premultiplication: bool,
|
with_alpha_premultiplication: bool,
|
||||||
) -> (fn(f32, f32) -> f32, fn(f32, f32) -> f32) {
|
) -> ((fn(f32, u8) -> u8), (fn(u8, u8) -> f32)) {
|
||||||
if with_alpha_premultiplication {
|
if with_alpha_premultiplication {
|
||||||
(|v, a| v * a, |v, a| v / a)
|
(
|
||||||
|
|v, a| (v * (a as f32) / 255.0) as u8,
|
||||||
|
|v, a| ((v as f32) * 255.0 / (a as f32)).clamp(0.0, 255.0),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(|v, _a| v, |v, _a| v)
|
(|v, _a| v as u8, |v, _a| v as f32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn resize(
|
pub fn resize(
|
||||||
input_image: Vec<u8>,
|
mut input_image: Vec<u8>,
|
||||||
input_width: usize,
|
input_width: usize,
|
||||||
input_height: usize,
|
input_height: usize,
|
||||||
output_width: usize,
|
output_width: usize,
|
||||||
@@ -77,64 +77,44 @@ pub fn resize(
|
|||||||
let num_input_pixels = input_width * input_height;
|
let num_input_pixels = input_width * input_height;
|
||||||
let num_output_pixels = output_width * output_height;
|
let num_output_pixels = output_width * output_height;
|
||||||
|
|
||||||
let mut output_image = vec![0u8; num_output_pixels * 4];
|
let (to_linear, to_color_space) = converter_funcs(color_space_conversion);
|
||||||
|
|
||||||
// If both options are false, there is no preprocessing on the pixel values
|
|
||||||
// and we can skip the loop.
|
|
||||||
if !premultiply && !color_space_conversion {
|
|
||||||
let mut resizer = resize::new(
|
|
||||||
input_width,
|
|
||||||
input_height,
|
|
||||||
output_width,
|
|
||||||
output_height,
|
|
||||||
Pixel::RGBA,
|
|
||||||
typ,
|
|
||||||
);
|
|
||||||
resizer.resize(input_image.as_slice(), output_image.as_mut_slice());
|
|
||||||
return output_image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we convert to f32 images to keep the
|
|
||||||
// conversions as lossless and high-fidelity as possible.
|
|
||||||
let (to_linear, to_srgb) = srgb_converter_funcs(color_space_conversion);
|
|
||||||
let (premultiplier, demultiplier) = alpha_multiplier_funcs(premultiply);
|
let (premultiplier, demultiplier) = alpha_multiplier_funcs(premultiply);
|
||||||
|
|
||||||
let mut preprocessed_input_image: Vec<f32> = Vec::with_capacity(input_image.len());
|
// If both options are false, there is no preprocessing on the pixel valus
|
||||||
preprocessed_input_image.resize(input_image.len(), 0.0f32);
|
// and we can skip the loop.
|
||||||
for i in 0..num_input_pixels {
|
if premultiply || color_space_conversion {
|
||||||
for j in 0..3 {
|
for i in 0..num_input_pixels {
|
||||||
preprocessed_input_image[4 * i + j] = premultiplier(
|
for j in 0..3 {
|
||||||
to_linear(input_image[4 * i + j]),
|
input_image[4 * i + j] =
|
||||||
(input_image[4 * i + 3] as f32) / 255.0,
|
premultiplier(to_linear(input_image[4 * i + j]), input_image[4 * i + 3]);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
preprocessed_input_image[4 * i + 3] = (input_image[4 * i + 3] as f32) / 255.0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut unprocessed_output_image = vec![0.0f32; num_output_pixels * 4];
|
|
||||||
|
|
||||||
let mut resizer = resize::new(
|
let mut resizer = resize::new(
|
||||||
input_width,
|
input_width,
|
||||||
input_height,
|
input_height,
|
||||||
output_width,
|
output_width,
|
||||||
output_height,
|
output_height,
|
||||||
Pixel::RGBAF32,
|
RGBA,
|
||||||
typ,
|
typ,
|
||||||
);
|
);
|
||||||
resizer.resize(
|
let mut output_image = Vec::<u8>::with_capacity(num_output_pixels * 4);
|
||||||
preprocessed_input_image.as_slice(),
|
output_image.resize(num_output_pixels * 4, 0);
|
||||||
unprocessed_output_image.as_mut_slice(),
|
resizer.resize(input_image.as_slice(), output_image.as_mut_slice());
|
||||||
);
|
|
||||||
|
|
||||||
for i in 0..num_output_pixels {
|
if premultiply || color_space_conversion {
|
||||||
for j in 0..3 {
|
for i in 0..num_output_pixels {
|
||||||
output_image[4 * i + j] = to_srgb(demultiplier(
|
for j in 0..3 {
|
||||||
unprocessed_output_image[4 * i + j],
|
// We don’t need to worry about division by zero, as division by zero
|
||||||
unprocessed_output_image[4 * i + 3],
|
// is well-defined on floats to return ±Inf. ±Inf is converted to 0
|
||||||
));
|
// when casting to integers.
|
||||||
|
output_image[4 * i + j] = to_color_space(demultiplier(
|
||||||
|
output_image[4 * i + j],
|
||||||
|
output_image[4 * i + 3],
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
output_image[4 * i + 3] =
|
|
||||||
(unprocessed_output_image[4 * i + 3] * 255.0).clamp(0.0, 255.0) as u8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output_image;
|
return output_image;
|
||||||
|
|||||||
Binary file not shown.
41
lib/data-url-plugin.js
Normal file
41
lib/data-url-plugin.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
|
import { lookup as lookupMime } from 'mime-types';
|
||||||
|
|
||||||
|
const prefix = 'data-url:';
|
||||||
|
|
||||||
|
export default function dataURLPlugin() {
|
||||||
|
return {
|
||||||
|
name: 'data-url-plugin',
|
||||||
|
async resolveId(id, importer) {
|
||||||
|
if (!id.startsWith(prefix)) return;
|
||||||
|
return (
|
||||||
|
prefix + (await this.resolve(id.slice(prefix.length), importer)).id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (!id.startsWith(prefix)) return;
|
||||||
|
const realId = id.slice(prefix.length);
|
||||||
|
this.addWatchFile(realId);
|
||||||
|
|
||||||
|
const source = await fs.readFile(realId);
|
||||||
|
const type = lookupMime(realId) || 'text/plain';
|
||||||
|
|
||||||
|
return `export default 'data:${type};base64,${source.toString(
|
||||||
|
'base64',
|
||||||
|
)}';`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
5
missing-types.d.ts
vendored
5
missing-types.d.ts
vendored
@@ -32,6 +32,11 @@ declare module 'css:*' {
|
|||||||
export default source;
|
export default source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'data-url:*' {
|
||||||
|
const url: string;
|
||||||
|
export default url;
|
||||||
|
}
|
||||||
|
|
||||||
declare var ga: {
|
declare var ga: {
|
||||||
(...args: any[]): void;
|
(...args: any[]): void;
|
||||||
q: any[];
|
q: any[];
|
||||||
|
|||||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -6036,9 +6036,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"preact": {
|
"preact": {
|
||||||
"version": "10.5.5",
|
"version": "10.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.5.7.tgz",
|
||||||
"integrity": "sha512-5ONLNH1SXMzzbQoExZX4TELemNt+TEDb622xXFNfZngjjM9qtrzseJt+EfiUu4TZ6EJ95X5sE1ES4yqHFSIdhg==",
|
"integrity": "sha512-4oEpz75t/0UNcwmcsjk+BIcDdk68oao+7kxcpc1hQPNs2Oo3ZL9xFz8UBf350mxk/VEdD41L5b4l2dE3Ug3RYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"preact-render-to-string": {
|
"preact-render-to-string": {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"postcss-nested": "^4.2.3",
|
"postcss-nested": "^4.2.3",
|
||||||
"postcss-simple-vars": "^5.0.2",
|
"postcss-simple-vars": "^5.0.2",
|
||||||
"postcss-url": "^8.0.0",
|
"postcss-url": "^8.0.0",
|
||||||
"preact": "^10.5.5",
|
"preact": "^10.5.7",
|
||||||
"preact-render-to-string": "^5.1.11",
|
"preact-render-to-string": "^5.1.11",
|
||||||
"prettier": "^2.1.2",
|
"prettier": "^2.1.2",
|
||||||
"rollup": "^2.33.1",
|
"rollup": "^2.33.1",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import emitFiles from './lib/emit-files-plugin';
|
|||||||
import featurePlugin from './lib/feature-plugin';
|
import featurePlugin from './lib/feature-plugin';
|
||||||
import initialCssPlugin from './lib/initial-css-plugin';
|
import initialCssPlugin from './lib/initial-css-plugin';
|
||||||
import serviceWorkerPlugin from './lib/sw-plugin';
|
import serviceWorkerPlugin from './lib/sw-plugin';
|
||||||
|
import dataURLPlugin from './lib/data-url-plugin';
|
||||||
import entryDataPlugin from './lib/entry-data-plugin';
|
import entryDataPlugin from './lib/entry-data-plugin';
|
||||||
|
|
||||||
function resolveFileUrl({ fileName }) {
|
function resolveFileUrl({ fileName }) {
|
||||||
@@ -81,6 +82,7 @@ export default async function ({ watch }) {
|
|||||||
'codecs',
|
'codecs',
|
||||||
]),
|
]),
|
||||||
urlPlugin(),
|
urlPlugin(),
|
||||||
|
dataURLPlugin(),
|
||||||
cssPlugin(resolveFileUrl),
|
cssPlugin(resolveFileUrl),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
37
src/client/lazy-app/Compress/CanvasImage.tsx
Normal file
37
src/client/lazy-app/Compress/CanvasImage.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { h, Component, createRef } from 'preact';
|
||||||
|
import { drawDataToCanvas } from '../util';
|
||||||
|
|
||||||
|
export interface CanvasImageProps
|
||||||
|
extends h.JSX.HTMLAttributes<HTMLCanvasElement> {
|
||||||
|
image?: ImageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CanvasImage extends Component<CanvasImageProps> {
|
||||||
|
canvas = createRef<HTMLCanvasElement>();
|
||||||
|
componentDidUpdate(prevProps: CanvasImageProps) {
|
||||||
|
if (this.props.image !== prevProps.image) {
|
||||||
|
this.draw(this.props.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.image) {
|
||||||
|
this.draw(this.props.image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
draw(image?: ImageData) {
|
||||||
|
const canvas = this.canvas.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
if (!image) canvas.getContext('2d');
|
||||||
|
else drawDataToCanvas(canvas, image);
|
||||||
|
}
|
||||||
|
render({ image, ...props }: CanvasImageProps) {
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={this.canvas}
|
||||||
|
width={image?.width}
|
||||||
|
height={image?.height}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/client/lazy-app/Compress/ClickOutsideDetector.tsx
Normal file
54
src/client/lazy-app/Compress/ClickOutsideDetector.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
cloneElement,
|
||||||
|
createRef,
|
||||||
|
toChildArray,
|
||||||
|
ComponentChildren,
|
||||||
|
RefObject,
|
||||||
|
} from 'preact';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ComponentChildren;
|
||||||
|
onClick?(e: MouseEvent | KeyboardEvent): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClickOutsideDetector extends Component<Props> {
|
||||||
|
private _roots: RefObject<Element>[] = [];
|
||||||
|
|
||||||
|
private handleClick = (e: MouseEvent) => {
|
||||||
|
let target = e.target as Node;
|
||||||
|
// check if the click came from within any of our child elements:
|
||||||
|
for (const { current: root } of this._roots) {
|
||||||
|
if (root && (root === target || root.contains(target))) return;
|
||||||
|
}
|
||||||
|
const { onClick } = this.props;
|
||||||
|
if (onClick) onClick(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleKey = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
if (onClick) onClick(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
addEventListener('click', this.handleClick, { passive: true });
|
||||||
|
addEventListener('keydown', this.handleKey, { passive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
removeEventListener('click', this.handleClick);
|
||||||
|
removeEventListener('keydown', this.handleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ children }: Props) {
|
||||||
|
this._roots = [];
|
||||||
|
return toChildArray(children).map((child) => {
|
||||||
|
if (typeof child !== 'object') return child;
|
||||||
|
const ref = createRef();
|
||||||
|
this._roots.push(ref);
|
||||||
|
return cloneElement(child, { ref });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
210
src/client/lazy-app/Compress/Flyout/index.tsx
Normal file
210
src/client/lazy-app/Compress/Flyout/index.tsx
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import {
|
||||||
|
h,
|
||||||
|
cloneElement,
|
||||||
|
Component,
|
||||||
|
VNode,
|
||||||
|
createRef,
|
||||||
|
ComponentChildren,
|
||||||
|
ComponentProps,
|
||||||
|
Fragment,
|
||||||
|
render,
|
||||||
|
} from 'preact';
|
||||||
|
import * as style from './style.css';
|
||||||
|
import 'add-css:./style.css';
|
||||||
|
|
||||||
|
type Anchor = 'left' | 'right' | 'top' | 'bottom';
|
||||||
|
type Direction = 'left' | 'right' | 'up' | 'down';
|
||||||
|
|
||||||
|
const has = (haystack: string | string[] | undefined, needle: string) =>
|
||||||
|
Array.isArray(haystack) ? haystack.includes(needle) : haystack === needle;
|
||||||
|
|
||||||
|
interface Props extends Omit<ComponentProps<'aside'>, 'ref'> {
|
||||||
|
showing?: boolean;
|
||||||
|
direction?: Direction | Direction[];
|
||||||
|
anchor?: Anchor;
|
||||||
|
toggle?: VNode;
|
||||||
|
children?: ComponentChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
showing: boolean;
|
||||||
|
hasShown: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Flyout extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
showing: this.props.showing === true,
|
||||||
|
hasShown: this.props.showing === true,
|
||||||
|
};
|
||||||
|
|
||||||
|
private wrap = createRef<HTMLElement>();
|
||||||
|
|
||||||
|
private menu = createRef<HTMLElement>();
|
||||||
|
|
||||||
|
private resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
|
private shown?: number;
|
||||||
|
|
||||||
|
private dismiss = (event: Event) => {
|
||||||
|
if (this.menu.current && this.menu.current.contains(event.target as Node))
|
||||||
|
return;
|
||||||
|
// prevent toggle buttons from immediately dismissing:
|
||||||
|
if (this.shown && Date.now() - this.shown < 10) return;
|
||||||
|
this.setShowing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
hide = () => {
|
||||||
|
this.setShowing(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
show = () => {
|
||||||
|
this.setShowing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.setShowing(!this.state.showing);
|
||||||
|
};
|
||||||
|
|
||||||
|
private setShowing = (showing?: boolean) => {
|
||||||
|
this.shown = Date.now();
|
||||||
|
if (showing) this.setState({ showing: true, hasShown: true });
|
||||||
|
else this.setState({ showing: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
private reposition = () => {
|
||||||
|
const menu = this.menu.current;
|
||||||
|
const wrap = this.wrap.current;
|
||||||
|
if (!menu || !wrap || !this.state.showing) return;
|
||||||
|
const bbox = wrap.getBoundingClientRect();
|
||||||
|
|
||||||
|
const { direction = 'down', anchor = 'right' } = this.props;
|
||||||
|
const { innerWidth, innerHeight } = window;
|
||||||
|
|
||||||
|
const anchorX = has(anchor, 'left') ? bbox.left : bbox.right;
|
||||||
|
|
||||||
|
menu.style.left = menu.style.right = menu.style.top = menu.style.bottom =
|
||||||
|
'';
|
||||||
|
|
||||||
|
if (has(direction, 'left')) {
|
||||||
|
menu.style.right = innerWidth - anchorX + 'px';
|
||||||
|
} else {
|
||||||
|
menu.style.left = anchorX + 'px';
|
||||||
|
}
|
||||||
|
if (has(direction, 'up')) {
|
||||||
|
const anchorY = has(anchor, 'bottom') ? bbox.bottom : bbox.top;
|
||||||
|
menu.style.bottom = innerHeight - anchorY + 'px';
|
||||||
|
} else {
|
||||||
|
const anchorY = has(anchor, 'top') ? bbox.top : bbox.bottom;
|
||||||
|
menu.style.top = anchorY + 'px';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps({ showing }: Props) {
|
||||||
|
if (showing !== this.props.showing) {
|
||||||
|
this.setShowing(showing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
addEventListener('click', this.dismiss, { passive: true });
|
||||||
|
addEventListener('resize', this.reposition, { passive: true });
|
||||||
|
if (typeof ResizeObserver === 'function' && this.wrap.current) {
|
||||||
|
this.resizeObserver = new ResizeObserver(this.reposition);
|
||||||
|
this.resizeObserver.observe(this.wrap.current);
|
||||||
|
}
|
||||||
|
if (this.props.showing) this.setShowing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
removeEventListener('click', this.dismiss);
|
||||||
|
removeEventListener('resize', this.reposition);
|
||||||
|
if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
|
if (this.state.showing && !prevState.showing) {
|
||||||
|
const menu = this.menu.current;
|
||||||
|
if (menu) {
|
||||||
|
this.reposition();
|
||||||
|
|
||||||
|
let toFocus = menu.firstElementChild;
|
||||||
|
for (let child of menu.children) {
|
||||||
|
if (child.hasAttribute('autofocus')) {
|
||||||
|
toFocus = child;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// @ts-ignore-next
|
||||||
|
if (toFocus) toFocus.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
{ direction, anchor, toggle, children, ...props }: Props,
|
||||||
|
{ showing }: State,
|
||||||
|
) {
|
||||||
|
const toggleProps = {
|
||||||
|
flyoutOpen: showing,
|
||||||
|
onClick: this.toggle,
|
||||||
|
};
|
||||||
|
|
||||||
|
const directionText = Array.isArray(direction)
|
||||||
|
? direction.join(' ')
|
||||||
|
: direction;
|
||||||
|
const anchorText = Array.isArray(anchor) ? anchor.join(' ') : anchor;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class={style.wrap}
|
||||||
|
ref={this.wrap}
|
||||||
|
data-flyout-open={showing ? '' : undefined}
|
||||||
|
>
|
||||||
|
{toggle && cloneElement(toggle, toggleProps)}
|
||||||
|
|
||||||
|
{showing &&
|
||||||
|
createPortal(
|
||||||
|
<aside
|
||||||
|
{...props}
|
||||||
|
class={`${style.flyout} ${props.class || props.className || ''}`}
|
||||||
|
ref={this.menu}
|
||||||
|
data-anchor={anchorText}
|
||||||
|
data-direction={directionText}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</aside>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not worth pulling in compat
|
||||||
|
function createPortal(children: ComponentChildren, parent: Element) {
|
||||||
|
return <Portal parent={parent}>{children}</Portal>;
|
||||||
|
}
|
||||||
|
// this is probably overly careful, since it works directly rendering into parent
|
||||||
|
function createPersistentFragment(parent: Element) {
|
||||||
|
const frag = {
|
||||||
|
nodeType: 11,
|
||||||
|
childNodes: [],
|
||||||
|
appendChild: parent.appendChild.bind(parent),
|
||||||
|
insertBefore: parent.insertBefore.bind(parent),
|
||||||
|
removeChild: parent.removeChild.bind(parent),
|
||||||
|
};
|
||||||
|
return (frag as unknown) as Element;
|
||||||
|
}
|
||||||
|
class Portal extends Component<{
|
||||||
|
children: ComponentChildren;
|
||||||
|
parent: Element;
|
||||||
|
}> {
|
||||||
|
root = createPersistentFragment(this.props.parent);
|
||||||
|
componentWillUnmount() {
|
||||||
|
render(null, this.root);
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
render(<Fragment>{this.props.children}</Fragment>, this.root);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/client/lazy-app/Compress/Flyout/style.css
Normal file
41
src/client/lazy-app/Compress/Flyout/style.css
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
.wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flyout {
|
||||||
|
display: inline-block;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
overflow: visible;
|
||||||
|
outline: none;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
animation: menuOpen 350ms ease forwards 1;
|
||||||
|
--flyout-offset-y: -20px;
|
||||||
|
|
||||||
|
&[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-direction*='left'] {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-direction*='up'] {
|
||||||
|
--flyout-offset-y: 20px;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes menuOpen {
|
||||||
|
0% {
|
||||||
|
transform: translateY(var(--flyout-offset-y, 0));
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component, createRef } from 'preact';
|
||||||
|
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
@@ -15,9 +15,10 @@ import {
|
|||||||
import Expander from './Expander';
|
import Expander from './Expander';
|
||||||
import Toggle from './Toggle';
|
import Toggle from './Toggle';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
import Flyout from '../Flyout';
|
||||||
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
import { Options as QuantOptionsComponent } from 'features/processors/quantize/client';
|
||||||
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
import { Options as ResizeOptionsComponent } from 'features/processors/resize/client';
|
||||||
import { CLIIcon, SwapIcon } from 'client/lazy-app/icons';
|
import { CLIIcon, MoreIcon, SwapIcon } from 'client/lazy-app/icons';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: 0 | 1;
|
index: 0 | 1;
|
||||||
@@ -64,6 +65,8 @@ export default class Options extends Component<Props, State> {
|
|||||||
supportedEncoderMap: undefined,
|
supportedEncoderMap: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
menu = createRef<Flyout>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
supportedEncoderMapP.then((supportedEncoderMap) =>
|
supportedEncoderMapP.then((supportedEncoderMap) =>
|
||||||
@@ -110,10 +113,12 @@ export default class Options extends Component<Props, State> {
|
|||||||
|
|
||||||
private onCopyCliClick = () => {
|
private onCopyCliClick = () => {
|
||||||
this.props.onCopyCliClick(this.props.index);
|
this.props.onCopyCliClick(this.props.index);
|
||||||
|
if (this.menu.current) this.menu.current.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCopyToOtherSideClick = () => {
|
private onCopyToOtherSideClick = () => {
|
||||||
this.props.onCopyToOtherSideClick(this.props.index);
|
this.props.onCopyToOtherSideClick(this.props.index);
|
||||||
|
if (this.menu.current) this.menu.current.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@@ -136,23 +141,33 @@ export default class Options extends Component<Props, State> {
|
|||||||
{!encoderState ? null : (
|
{!encoderState ? null : (
|
||||||
<div>
|
<div>
|
||||||
<h3 class={style.optionsTitle}>
|
<h3 class={style.optionsTitle}>
|
||||||
<div class={style.titleAndButtons}>
|
Edit
|
||||||
Edit
|
<Flyout
|
||||||
|
ref={this.menu}
|
||||||
|
class={style.menu}
|
||||||
|
direction={['up', 'left']}
|
||||||
|
anchor="right"
|
||||||
|
toggle={
|
||||||
|
<button class={style.titleButton}>
|
||||||
|
<MoreIcon />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class={style.cliButton}
|
class={style.menuButton}
|
||||||
title="Copy npx command"
|
|
||||||
onClick={this.onCopyCliClick}
|
onClick={this.onCopyCliClick}
|
||||||
>
|
>
|
||||||
<CLIIcon />
|
<CLIIcon />
|
||||||
|
Copy npx command
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class={style.copyOverButton}
|
class={style.menuButton}
|
||||||
title="Copy settings to other side"
|
|
||||||
onClick={this.onCopyToOtherSideClick}
|
onClick={this.onCopyToOtherSideClick}
|
||||||
>
|
>
|
||||||
<SwapIcon />
|
<SwapIcon />
|
||||||
|
Copy settings to other side
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</Flyout>
|
||||||
</h3>
|
</h3>
|
||||||
<label class={style.sectionEnabler}>
|
<label class={style.sectionEnabler}>
|
||||||
Resize
|
Resize
|
||||||
|
|||||||
@@ -14,13 +14,21 @@
|
|||||||
background-color: var(--main-theme-color);
|
background-color: var(--main-theme-color);
|
||||||
color: var(--header-text-color);
|
color: var(--header-text-color);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px var(--horizontal-padding);
|
height: 38px;
|
||||||
|
padding: 0 var(--horizontal-padding);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
border-bottom: 1px solid var(--off-black);
|
border-bottom: 1px solid var(--off-black);
|
||||||
transition: all 300ms ease-in-out;
|
transition: all 300ms ease-in-out;
|
||||||
transition-property: background-color, color;
|
transition-property: background-color, color;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-auto-columns: max-content;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
gap: 0.8rem 0;
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -82,36 +90,63 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-and-buttons {
|
.menu {
|
||||||
grid-template-columns: 1fr;
|
transform: translateY(-10px);
|
||||||
grid-auto-columns: max-content;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
display: grid;
|
|
||||||
gap: 0.8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-button {
|
.title-button {
|
||||||
|
position: relative;
|
||||||
|
left: 10px;
|
||||||
composes: unbutton from global;
|
composes: unbutton from global;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
--size: 20px;
|
--size: 24px;
|
||||||
|
fill: var(--header-text-color);
|
||||||
display: block;
|
display: block;
|
||||||
width: var(--size);
|
width: var(--size);
|
||||||
height: var(--size);
|
height: var(--size);
|
||||||
|
padding: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cli-button {
|
.menu-button {
|
||||||
composes: title-button;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 8px 0;
|
||||||
|
background-color: rgba(29, 29, 29, 0.92);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||||
|
border-radius: 2rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 39px;
|
||||||
|
padding: 0 16px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
svg {
|
&:hover {
|
||||||
stroke: var(--header-text-color);
|
background: rgba(50, 50, 50, 0.92);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
&:focus {
|
||||||
.copy-over-button {
|
box-shadow: 0 0 0 2px #fff;
|
||||||
composes: title-button;
|
outline: none;
|
||||||
|
z-index: 1;
|
||||||
svg {
|
}
|
||||||
fill: var(--header-text-color);
|
|
||||||
|
& > svg {
|
||||||
|
position: relative;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 12px;
|
||||||
|
color: var(--main-theme-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ export default class TwoUp extends HTMLElement {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('keydown', event => this._onKeyDown(event));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -96,29 +94,6 @@ export default class TwoUp extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyDown event handler
|
|
||||||
private _onKeyDown(event: KeyboardEvent) {
|
|
||||||
if (event.code === 'Digit1' || event.code === 'Numpad1') {
|
|
||||||
this._position = 0;
|
|
||||||
this._relativePosition = 0;
|
|
||||||
this._setPosition();
|
|
||||||
} else if (event.code === 'Digit2' || event.code === 'Numpad2') {
|
|
||||||
const dimensionAxis = this.orientation === 'vertical' ? 'height' : 'width';
|
|
||||||
const bounds = this.getBoundingClientRect();
|
|
||||||
|
|
||||||
this._position = bounds[dimensionAxis] / 2;
|
|
||||||
this._relativePosition = (this._position / bounds[dimensionAxis]) / 2;
|
|
||||||
this._setPosition();
|
|
||||||
} else if (event.code === 'Digit3' || event.code === 'Numpad3') {
|
|
||||||
const dimensionAxis = this.orientation === 'vertical' ? 'height' : 'width';
|
|
||||||
const bounds = this.getBoundingClientRect();
|
|
||||||
|
|
||||||
this._position = bounds[dimensionAxis];
|
|
||||||
this._relativePosition = this._position / bounds[dimensionAxis];
|
|
||||||
this._setPosition();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _resetPosition() {
|
private _resetPosition() {
|
||||||
// Set the initial position of the handle.
|
// Set the initial position of the handle.
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { h, Component, Fragment } from 'preact';
|
import { h, createRef, Component, Fragment } from 'preact';
|
||||||
import type PinchZoom from './custom-els/PinchZoom';
|
import type PinchZoom from './custom-els/PinchZoom';
|
||||||
import type { ScaleToOpts } from './custom-els/PinchZoom';
|
import type { ScaleToOpts } from './custom-els/PinchZoom';
|
||||||
import './custom-els/PinchZoom';
|
import './custom-els/PinchZoom';
|
||||||
@@ -10,30 +10,37 @@ import {
|
|||||||
ToggleBackgroundIcon,
|
ToggleBackgroundIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
RemoveIcon,
|
RemoveIcon,
|
||||||
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/prerendered-app/util';
|
||||||
|
import Flyout from '../Flyout';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
preprocessorState?: PreprocessorState;
|
preprocessorState?: PreprocessorState;
|
||||||
|
hidden?: boolean;
|
||||||
mobileView: boolean;
|
mobileView: boolean;
|
||||||
leftCompressed?: ImageData;
|
leftCompressed?: ImageData;
|
||||||
rightCompressed?: ImageData;
|
rightCompressed?: ImageData;
|
||||||
leftImgContain: boolean;
|
leftImgContain: boolean;
|
||||||
rightImgContain: boolean;
|
rightImgContain: boolean;
|
||||||
onPreprocessorChange: (newState: PreprocessorState) => void;
|
onPreprocessorChange?: (newState: PreprocessorState) => void;
|
||||||
|
onShowPreprocessorTransforms?: () => void;
|
||||||
|
onToggleBackground?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
scale: number;
|
scale: number;
|
||||||
editingScale: boolean;
|
editingScale: boolean;
|
||||||
altBackground: boolean;
|
altBackground: boolean;
|
||||||
|
transform: boolean;
|
||||||
|
menuOpen: boolean;
|
||||||
|
smallControls: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleToOpts: ScaleToOpts = {
|
const scaleToOpts: ScaleToOpts = {
|
||||||
@@ -48,12 +55,18 @@ export default class Output extends Component<Props, State> {
|
|||||||
scale: 1,
|
scale: 1,
|
||||||
editingScale: false,
|
editingScale: false,
|
||||||
altBackground: false,
|
altBackground: false,
|
||||||
|
transform: false,
|
||||||
|
menuOpen: false,
|
||||||
|
smallControls:
|
||||||
|
typeof matchMedia === 'function' &&
|
||||||
|
matchMedia('(max-width: 859px)').matches,
|
||||||
};
|
};
|
||||||
canvasLeft?: HTMLCanvasElement;
|
canvasLeft?: HTMLCanvasElement;
|
||||||
canvasRight?: HTMLCanvasElement;
|
canvasRight?: HTMLCanvasElement;
|
||||||
pinchZoomLeft?: PinchZoom;
|
pinchZoomLeft?: PinchZoom;
|
||||||
pinchZoomRight?: PinchZoom;
|
pinchZoomRight?: PinchZoom;
|
||||||
scaleInput?: HTMLInputElement;
|
scaleInput?: HTMLInputElement;
|
||||||
|
flyout = createRef<Flyout>();
|
||||||
retargetedEvents = new WeakSet<Event>();
|
retargetedEvents = new WeakSet<Event>();
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -75,6 +88,12 @@ export default class Output extends Component<Props, State> {
|
|||||||
if (this.canvasRight && rightDraw) {
|
if (this.canvasRight && rightDraw) {
|
||||||
drawDataToCanvas(this.canvasRight, rightDraw);
|
drawDataToCanvas(this.canvasRight, rightDraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof matchMedia === 'function') {
|
||||||
|
matchMedia('(max-width: 859px)').addEventListener('change', (e) =>
|
||||||
|
this.setState({ smallControls: e.matches }),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
@@ -144,12 +163,6 @@ export default class Output extends Component<Props, State> {
|
|||||||
return props.rightCompressed || (props.source && props.source.preprocessed);
|
return props.rightCompressed || (props.source && props.source.preprocessed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleBackground = () => {
|
|
||||||
this.setState({
|
|
||||||
altBackground: !this.state.altBackground,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private zoomIn = () => {
|
private zoomIn = () => {
|
||||||
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
|
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
|
||||||
this.pinchZoomLeft.scaleTo(this.state.scale * 1.25, scaleToOpts);
|
this.pinchZoomLeft.scaleTo(this.state.scale * 1.25, scaleToOpts);
|
||||||
@@ -160,17 +173,30 @@ 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 onRotateClick = () => {
|
private fitToViewport = () => {
|
||||||
const { preprocessorState: inputProcessorState } = this.props;
|
if (!this.pinchZoomLeft) throw Error('Missing pinch-zoom element');
|
||||||
if (!inputProcessorState) return;
|
const img = this.props.source?.preprocessed;
|
||||||
|
if (!img) return;
|
||||||
const newState = cleanSet(
|
const scale = Number(
|
||||||
inputProcessorState,
|
Math.min(
|
||||||
'rotate.rotate',
|
(window.innerWidth - 20) / img.width,
|
||||||
(inputProcessorState.rotate.rotate + 90) % 360,
|
(window.innerHeight - 20) / img.height,
|
||||||
|
).toFixed(2),
|
||||||
);
|
);
|
||||||
|
this.pinchZoomLeft.scaleTo(Number(scale.toFixed(2)), scaleToOpts);
|
||||||
|
this.recenter();
|
||||||
|
// this.hideMenu();
|
||||||
|
};
|
||||||
|
|
||||||
this.props.onPreprocessorChange(newState);
|
private recenter = () => {
|
||||||
|
const img = this.props.source?.preprocessed;
|
||||||
|
if (!img || !this.pinchZoomLeft) return;
|
||||||
|
let scale = this.pinchZoomLeft.scale;
|
||||||
|
this.pinchZoomLeft.setTransform({
|
||||||
|
x: (img.width - img.width * scale) / 2,
|
||||||
|
y: (img.height - img.height * scale) / 2,
|
||||||
|
allowChangeEvent: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onScaleValueFocus = () => {
|
private onScaleValueFocus = () => {
|
||||||
@@ -253,8 +279,16 @@ export default class Output extends Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{ mobileView, leftImgContain, rightImgContain, source }: Props,
|
{
|
||||||
{ scale, editingScale, altBackground }: State,
|
source,
|
||||||
|
mobileView,
|
||||||
|
hidden,
|
||||||
|
leftImgContain,
|
||||||
|
rightImgContain,
|
||||||
|
onShowPreprocessorTransforms,
|
||||||
|
onToggleBackground,
|
||||||
|
}: Props,
|
||||||
|
{ scale, editingScale, smallControls }: State,
|
||||||
) {
|
) {
|
||||||
const leftDraw = this.leftDrawable();
|
const leftDraw = this.leftDrawable();
|
||||||
const rightDraw = this.rightDrawable();
|
const rightDraw = this.rightDrawable();
|
||||||
@@ -263,9 +297,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div
|
<div class={style.output} hidden={hidden}>
|
||||||
class={`${style.output} ${altBackground ? style.altBackground : ''}`}
|
|
||||||
>
|
|
||||||
<two-up
|
<two-up
|
||||||
legacy-clip-compat
|
legacy-clip-compat
|
||||||
class={style.twoUp}
|
class={style.twoUp}
|
||||||
@@ -291,7 +323,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
style={{
|
style={{
|
||||||
width: originalImage ? originalImage.width : '',
|
width: originalImage ? originalImage.width : '',
|
||||||
height: originalImage ? originalImage.height : '',
|
height: originalImage ? originalImage.height : '',
|
||||||
objectFit: leftImgContain ? 'contain' : '',
|
objectFit: leftImgContain ? 'contain' : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</pinch-zoom>
|
</pinch-zoom>
|
||||||
@@ -307,15 +339,16 @@ export default class Output extends Component<Props, State> {
|
|||||||
style={{
|
style={{
|
||||||
width: originalImage ? originalImage.width : '',
|
width: originalImage ? originalImage.width : '',
|
||||||
height: originalImage ? originalImage.height : '',
|
height: originalImage ? originalImage.height : '',
|
||||||
objectFit: rightImgContain ? 'contain' : '',
|
objectFit: rightImgContain ? 'contain' : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</pinch-zoom>
|
</pinch-zoom>
|
||||||
</two-up>
|
</two-up>
|
||||||
</div>
|
</div>
|
||||||
<div class={style.controls}>
|
|
||||||
|
<div class={style.controls} hidden={hidden}>
|
||||||
<div class={style.buttonGroup}>
|
<div class={style.buttonGroup}>
|
||||||
<button class={style.firstButton} onClick={this.zoomOut}>
|
<button class={style.button} onClick={this.zoomOut}>
|
||||||
<RemoveIcon />
|
<RemoveIcon />
|
||||||
</button>
|
</button>
|
||||||
{editingScale ? (
|
{editingScale ? (
|
||||||
@@ -342,18 +375,34 @@ export default class Output extends Component<Props, State> {
|
|||||||
<button class={style.lastButton} onClick={this.zoomIn}>
|
<button class={style.lastButton} onClick={this.zoomIn}>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<div class={style.buttonGroup}>
|
<Flyout
|
||||||
<button class={style.firstButton} onClick={this.onRotateClick}>
|
class={style.menu}
|
||||||
<RotateIcon />
|
showing={hidden ? false : undefined}
|
||||||
</button>
|
anchor="right"
|
||||||
<button class={style.lastButton} onClick={this.toggleBackground}>
|
direction={smallControls ? ['down', 'left'] : 'up'}
|
||||||
{altBackground ? (
|
toggle={
|
||||||
<ToggleBackgroundActiveIcon />
|
<button class={`${style.button} ${style.moreButton}`}>
|
||||||
) : (
|
<MoreIcon />
|
||||||
<ToggleBackgroundIcon />
|
</button>
|
||||||
)}
|
}
|
||||||
</button>
|
>
|
||||||
|
<button
|
||||||
|
class={style.button}
|
||||||
|
onClick={onShowPreprocessorTransforms}
|
||||||
|
>
|
||||||
|
<RotateIcon /> Rotate & Transform
|
||||||
|
</button>
|
||||||
|
<button class={style.button} onClick={this.fitToViewport}>
|
||||||
|
Fit to viewport
|
||||||
|
</button>
|
||||||
|
<button class={style.button} onClick={this.recenter}>
|
||||||
|
Re-center
|
||||||
|
</button>
|
||||||
|
<button class={style.button} onClick={onToggleBackground}>
|
||||||
|
<ToggleBackgroundIcon /> Change canvas color
|
||||||
|
</button>
|
||||||
|
</Flyout>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
.output {
|
.output {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
|
||||||
&::before {
|
&[hidden] {
|
||||||
content: '';
|
display: none;
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: #000;
|
|
||||||
opacity: 0.8;
|
|
||||||
transition: opacity 500ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.alt-background::before {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +30,21 @@
|
|||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
contain: content;
|
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
padding: 9px 66px;
|
padding: 9px 66px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* Had to disable containment because of the overflow menu. */
|
||||||
|
/*
|
||||||
|
contain: content;
|
||||||
|
overflow: hidden;
|
||||||
|
*/
|
||||||
|
transition: transform 500ms ease;
|
||||||
|
|
||||||
/* 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;
|
||||||
}
|
}
|
||||||
@@ -62,13 +55,34 @@
|
|||||||
grid-area: viewportOpts;
|
grid-area: viewportOpts;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[hidden] {
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(-200%);
|
||||||
|
|
||||||
|
@media (min-width: 860px) {
|
||||||
|
transform: translateY(200%);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
margin: 0 3px;
|
|
||||||
|
& > :not(:first-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :not(:nth-last-child(2)) {
|
||||||
|
margin-right: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.button,
|
.button,
|
||||||
@@ -76,9 +90,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
margin: 4px;
|
||||||
background-color: rgba(29, 29, 29, 0.92);
|
background-color: rgba(29, 29, 29, 0.92);
|
||||||
border: 1px solid rgba(0, 0, 0, 0.67);
|
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||||
border-width: 1px 0 1px 1px;
|
border-radius: 6px;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
height: 39px;
|
height: 39px;
|
||||||
@@ -161,3 +176,64 @@ input.zoom {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Three-dot menu */
|
||||||
|
.moreButton {
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
transform-origin: center;
|
||||||
|
transition: transform 200ms ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls [data-flyout-open] {
|
||||||
|
.moreButton {
|
||||||
|
background: rgba(82, 82, 82, 0.92);
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(50, 50, 50, 0.4);
|
||||||
|
backdrop-filter: blur(2px) contrast(70%);
|
||||||
|
animation: menuShimFadeIn 350ms ease forwards 1;
|
||||||
|
will-change: opacity;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes menuShimFadeIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
button {
|
||||||
|
margin: 8px 0;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
position: relative;
|
||||||
|
left: -6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #fff;
|
||||||
|
margin: 8px 4px;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
330
src/client/lazy-app/Compress/Transform/Cropper/index.tsx
Normal file
330
src/client/lazy-app/Compress/Transform/Cropper/index.tsx
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import { h, Component, ComponentChildren } from 'preact';
|
||||||
|
import * as style from './style.css';
|
||||||
|
import 'add-css:./style.css';
|
||||||
|
import { shallowEqual } from 'client/lazy-app/util';
|
||||||
|
|
||||||
|
export interface CropBox {
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimum CropBox size
|
||||||
|
const MIN_SIZE = 2;
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
size: { width: number; height: number };
|
||||||
|
scale?: number;
|
||||||
|
lockAspect?: boolean;
|
||||||
|
crop: CropBox;
|
||||||
|
onChange?(crop: CropBox): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Edge = keyof CropBox;
|
||||||
|
|
||||||
|
interface PointerTrack {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
edges: { edge: Edge; value: number }[];
|
||||||
|
aspect: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
crop: CropBox;
|
||||||
|
pan: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Cropper extends Component<Props, State> {
|
||||||
|
private pointers = new Map<number, PointerTrack>();
|
||||||
|
|
||||||
|
state = {
|
||||||
|
crop: this.normalizeCrop({ ...this.props.crop }),
|
||||||
|
pan: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps: Props, nextState: State) {
|
||||||
|
if (!shallowEqual(nextState, this.state)) return true;
|
||||||
|
const { size, scale, lockAspect, crop } = this.props;
|
||||||
|
return (
|
||||||
|
size.width !== nextProps.size.width ||
|
||||||
|
size.height !== nextProps.size.height ||
|
||||||
|
scale !== nextProps.scale ||
|
||||||
|
lockAspect !== nextProps.lockAspect ||
|
||||||
|
!shallowEqual(crop, nextProps.crop)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps({ crop }: Props, nextState: State) {
|
||||||
|
const current = nextState.crop || this.state.crop;
|
||||||
|
if (crop !== this.props.crop && !shallowEqual(crop, current)) {
|
||||||
|
// this.setState({ crop: nextProps.crop });
|
||||||
|
this.setCrop(crop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeCrop(crop: CropBox) {
|
||||||
|
crop.left = Math.round(Math.max(0, crop.left));
|
||||||
|
crop.top = Math.round(Math.max(0, crop.top));
|
||||||
|
crop.right = Math.round(Math.max(0, crop.right));
|
||||||
|
crop.bottom = Math.round(Math.max(0, crop.bottom));
|
||||||
|
return crop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCrop(cropUpdate: Partial<CropBox>) {
|
||||||
|
const crop = this.normalizeCrop({ ...this.state.crop, ...cropUpdate });
|
||||||
|
// ignore crop updates that normalize to the same values
|
||||||
|
const old = this.state.crop;
|
||||||
|
if (
|
||||||
|
crop.left === old.left &&
|
||||||
|
crop.right === old.right &&
|
||||||
|
crop.top === old.top &&
|
||||||
|
crop.bottom === old.bottom
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({ crop });
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(crop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPointerDown = (event: PointerEvent) => {
|
||||||
|
if (event.button !== 0 || this.state.pan) return;
|
||||||
|
|
||||||
|
const target = event.target as SVGElement;
|
||||||
|
const edgeAttr = target.getAttribute('data-edge');
|
||||||
|
if (edgeAttr) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
let aspect;
|
||||||
|
const edges = edgeAttr.split(/ *, */) as Edge[];
|
||||||
|
if (this.props.lockAspect) {
|
||||||
|
if (edges.length === 1) return;
|
||||||
|
const { size } = this.props;
|
||||||
|
const oldCrop = this.state.crop;
|
||||||
|
aspect =
|
||||||
|
(size.width - oldCrop.left - oldCrop.right) /
|
||||||
|
(size.height - oldCrop.top - oldCrop.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pointers.set(event.pointerId, {
|
||||||
|
x: event.x,
|
||||||
|
y: event.y,
|
||||||
|
edges: edges.map((edge) => ({ edge, value: this.state.crop[edge] })),
|
||||||
|
aspect,
|
||||||
|
});
|
||||||
|
target.setPointerCapture(event.pointerId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onPointerMove = (event: PointerEvent) => {
|
||||||
|
const target = event.target as SVGElement;
|
||||||
|
const down = this.pointers.get(event.pointerId);
|
||||||
|
if (down && target.hasPointerCapture(event.pointerId)) {
|
||||||
|
const { size } = this.props;
|
||||||
|
const oldCrop = this.state.crop;
|
||||||
|
const scale = this.props.scale || 1;
|
||||||
|
let dx = (event.x - down.x) / scale;
|
||||||
|
let dy = (event.y - down.y) / scale;
|
||||||
|
|
||||||
|
if (down.aspect && down.edges.length === 2) {
|
||||||
|
const dir = (dx + dy) / 2;
|
||||||
|
dx = dir * down.aspect;
|
||||||
|
dy = dir / down.aspect;
|
||||||
|
}
|
||||||
|
const crop: Partial<CropBox> = {};
|
||||||
|
for (const { edge, value } of down.edges) {
|
||||||
|
let edgeValue = value;
|
||||||
|
switch (edge) {
|
||||||
|
case 'left':
|
||||||
|
edgeValue += dx;
|
||||||
|
break;
|
||||||
|
case 'right':
|
||||||
|
edgeValue -= dx;
|
||||||
|
break;
|
||||||
|
case 'top':
|
||||||
|
edgeValue += dy;
|
||||||
|
break;
|
||||||
|
case 'bottom':
|
||||||
|
edgeValue -= dy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
crop[edge] = edgeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent MOVE from resizing the cropbox:
|
||||||
|
if (crop.left && crop.right) {
|
||||||
|
if (crop.left < 0) crop.right += crop.left;
|
||||||
|
if (crop.right < 0) crop.left += crop.right;
|
||||||
|
} else {
|
||||||
|
// enforce minimum 1px cropbox width
|
||||||
|
if (crop.left) {
|
||||||
|
if (down.aspect) crop.left = Math.max(0, crop.left);
|
||||||
|
else
|
||||||
|
crop.left = Math.min(
|
||||||
|
crop.left,
|
||||||
|
size.width - oldCrop.right - MIN_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (crop.right) {
|
||||||
|
if (down.aspect) crop.right = Math.max(0, crop.right);
|
||||||
|
crop.right = Math.min(
|
||||||
|
crop.right,
|
||||||
|
size.width - oldCrop.left - MIN_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
down.aspect &&
|
||||||
|
(crop.left ?? oldCrop.left) + (crop.right ?? oldCrop.right) >
|
||||||
|
size.width
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (crop.top && crop.bottom) {
|
||||||
|
if (crop.top < 0) crop.bottom += crop.top;
|
||||||
|
if (crop.bottom < 0) crop.top += crop.bottom;
|
||||||
|
} else {
|
||||||
|
// enforce minimum 1px cropbox height
|
||||||
|
if (crop.top) {
|
||||||
|
if (down.aspect) crop.top = Math.max(0, crop.top);
|
||||||
|
crop.top = Math.min(
|
||||||
|
crop.top,
|
||||||
|
size.height - oldCrop.bottom - MIN_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (crop.bottom) {
|
||||||
|
if (down.aspect) crop.bottom = Math.max(0, crop.bottom);
|
||||||
|
crop.bottom = Math.min(
|
||||||
|
crop.bottom,
|
||||||
|
size.height - oldCrop.top - MIN_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
down.aspect &&
|
||||||
|
(crop.top ?? oldCrop.top) + (crop.bottom ?? oldCrop.bottom) >
|
||||||
|
size.height
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setCrop(crop);
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onPointerUp = (event: PointerEvent) => {
|
||||||
|
const target = event.target as SVGElement;
|
||||||
|
const down = this.pointers.get(event.pointerId);
|
||||||
|
if (down && target.hasPointerCapture(event.pointerId)) {
|
||||||
|
this.onPointerMove(event);
|
||||||
|
target.releasePointerCapture(event.pointerId);
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
this.pointers.delete(event.pointerId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === ' ') {
|
||||||
|
if (!this.state.pan) {
|
||||||
|
this.setState({ pan: true });
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onKeyUp = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === ' ') this.setState({ pan: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
addEventListener('keydown', this.onKeyDown);
|
||||||
|
addEventListener('keyup', this.onKeyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
addEventListener('keydown', this.onKeyDown);
|
||||||
|
addEventListener('keyup', this.onKeyUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ size, scale }: Props, { crop, pan }: State) {
|
||||||
|
const x = crop.left;
|
||||||
|
const y = crop.top;
|
||||||
|
const width = size.width - crop.left - crop.right;
|
||||||
|
const height = size.height - crop.top - crop.bottom;
|
||||||
|
|
||||||
|
const s = (x: number) => x / (scale || 1);
|
||||||
|
|
||||||
|
const clip = `polygon(0 0, 0 100%, 100% 100%, 100% 0, 0 0, ${s(x)}px ${s(
|
||||||
|
y,
|
||||||
|
)}px, ${s(x + width)}px ${s(y)}px, ${s(x + width)}px ${s(
|
||||||
|
y + height,
|
||||||
|
)}px, ${s(x)}px ${s(y + height)}px, ${s(x)}px ${s(y)}px)`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
class={`${style.cropper} ${pan ? style.pan : ''}`}
|
||||||
|
width={size.width + 20}
|
||||||
|
height={size.height + 20}
|
||||||
|
viewBox={`-10 -10 ${size.width + 20} ${size.height + 20}`}
|
||||||
|
style={{
|
||||||
|
// this is hack to force style invalidation in Chrome
|
||||||
|
zoom: (scale || 1).toFixed(3),
|
||||||
|
}}
|
||||||
|
onPointerDown={this.onPointerDown}
|
||||||
|
onPointerMove={this.onPointerMove}
|
||||||
|
onPointerUp={this.onPointerUp}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
class={style.background}
|
||||||
|
width={size.width}
|
||||||
|
height={size.height}
|
||||||
|
clip-path={clip}
|
||||||
|
/>
|
||||||
|
<svg x={x} y={y} width={width} height={height}>
|
||||||
|
<Freezer>
|
||||||
|
<rect
|
||||||
|
id="box"
|
||||||
|
class={style.cropbox}
|
||||||
|
data-edge="left,right,top,bottom"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<rect class={style.edge} data-edge="top" width="100%" />
|
||||||
|
<rect class={style.edge} data-edge="bottom" width="100%" y="100%" />
|
||||||
|
<rect class={style.edge} data-edge="left" height="100%" />
|
||||||
|
<rect class={style.edge} data-edge="right" height="100%" x="100%" />
|
||||||
|
|
||||||
|
<circle class={style.corner} data-edge="left,top" />
|
||||||
|
<circle class={style.corner} data-edge="right,top" cx="100%" />
|
||||||
|
<circle
|
||||||
|
class={style.corner}
|
||||||
|
data-edge="right,bottom"
|
||||||
|
cx="100%"
|
||||||
|
cy="100%"
|
||||||
|
/>
|
||||||
|
<circle class={style.corner} data-edge="left,bottom" cy="100%" />
|
||||||
|
</Freezer>
|
||||||
|
</svg>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FreezerProps {
|
||||||
|
children: ComponentChildren;
|
||||||
|
}
|
||||||
|
class Freezer extends Component<FreezerProps> {
|
||||||
|
shouldComponentUpdate() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
render({ children }: FreezerProps) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/client/lazy-app/Compress/Transform/Cropper/style.css
Normal file
119
src/client/lazy-app/Compress/Transform/Cropper/style.css
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
.cropper {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(-10px / var(--scale, 1));
|
||||||
|
top: calc(-10px / var(--scale, 1));
|
||||||
|
right: calc(-10px / var(--scale, 1));
|
||||||
|
bottom: calc(-10px / var(--scale, 1));
|
||||||
|
shape-rendering: crispedges;
|
||||||
|
overflow: hidden;
|
||||||
|
contain: strict;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
transform: scale(calc(1 / var(--scale))) !important;
|
||||||
|
zoom: var(--scale, 1);
|
||||||
|
|
||||||
|
&.pan {
|
||||||
|
cursor: grabbing;
|
||||||
|
& * {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
margin: -10px;
|
||||||
|
padding: 10px;
|
||||||
|
overflow: visible;
|
||||||
|
contain: strict;
|
||||||
|
/* overflow: visible; */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
pointer-events: none;
|
||||||
|
fill: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropbox {
|
||||||
|
fill: none;
|
||||||
|
stroke: white;
|
||||||
|
stroke-width: calc(1.5px / var(--scale, 1));
|
||||||
|
stroke-dasharray: calc(5px / var(--scale, 1)), calc(5px / var(--scale, 1));
|
||||||
|
stroke-dashoffset: 50%;
|
||||||
|
/* Accept pointer input even though this is unpainted transparent */
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
/* animation: ants 1s linear forwards infinite; */
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
@keyframes ants {
|
||||||
|
0% { stroke-dashoffset: 0; }
|
||||||
|
100% { stroke-dashoffset: -12; }
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
.edge {
|
||||||
|
fill: #aaa;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 250ms ease;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: all;
|
||||||
|
--edge-width: calc(10px / var(--scale, 1));
|
||||||
|
|
||||||
|
@media (max-width: 779px) {
|
||||||
|
--edge-width: calc(20px / var(--scale, 1));
|
||||||
|
fill: rgba(0, 0, 0, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-edge='left'],
|
||||||
|
&[data-edge='right'] {
|
||||||
|
cursor: ew-resize;
|
||||||
|
transform: translate(calc(var(--edge-width, 10px) / -2), 0);
|
||||||
|
width: var(--edge-width, 10px);
|
||||||
|
}
|
||||||
|
&[data-edge='top'],
|
||||||
|
&[data-edge='bottom'] {
|
||||||
|
cursor: ns-resize;
|
||||||
|
transform: translate(0, calc(var(--edge-width, 10px) / -2));
|
||||||
|
height: var(--edge-width, 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
opacity: 0.1;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.corner {
|
||||||
|
r: calc(4px / var(--scale, 1));
|
||||||
|
stroke-width: calc(4px / var(--scale, 1));
|
||||||
|
stroke: rgba(225, 225, 225, 0.01);
|
||||||
|
fill: white;
|
||||||
|
shape-rendering: geometricprecision;
|
||||||
|
pointer-events: all;
|
||||||
|
transition: fill 250ms ease, stroke 250ms ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
stroke: rgba(225, 225, 225, 0.5);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 779px) {
|
||||||
|
r: calc(10 / var(--scale, 1));
|
||||||
|
stroke-width: calc(2 / var(--scale, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-edge='left,top'] {
|
||||||
|
cursor: nw-resize;
|
||||||
|
}
|
||||||
|
&[data-edge='right,top'] {
|
||||||
|
cursor: ne-resize;
|
||||||
|
}
|
||||||
|
&[data-edge='right,bottom'] {
|
||||||
|
cursor: se-resize;
|
||||||
|
}
|
||||||
|
&[data-edge='left,bottom'] {
|
||||||
|
cursor: sw-resize;
|
||||||
|
}
|
||||||
|
}
|
||||||
587
src/client/lazy-app/Compress/Transform/index.tsx
Normal file
587
src/client/lazy-app/Compress/Transform/index.tsx
Normal file
@@ -0,0 +1,587 @@
|
|||||||
|
import {
|
||||||
|
h,
|
||||||
|
Component,
|
||||||
|
Fragment,
|
||||||
|
createRef,
|
||||||
|
FunctionComponent,
|
||||||
|
ComponentChildren,
|
||||||
|
} from 'preact';
|
||||||
|
import type {
|
||||||
|
default as PinchZoom,
|
||||||
|
ScaleToOpts,
|
||||||
|
} from '../Output/custom-els/PinchZoom';
|
||||||
|
import '../Output/custom-els/PinchZoom';
|
||||||
|
import * as style from './style.css';
|
||||||
|
import 'add-css:./style.css';
|
||||||
|
import {
|
||||||
|
AddIcon,
|
||||||
|
CheckmarkIcon,
|
||||||
|
CompareIcon,
|
||||||
|
FlipHorizontallyIcon,
|
||||||
|
FlipVerticallyIcon,
|
||||||
|
RemoveIcon,
|
||||||
|
RotateClockwiseIcon,
|
||||||
|
RotateCounterClockwiseIcon,
|
||||||
|
SwapIcon,
|
||||||
|
} from '../../icons';
|
||||||
|
import { cleanSet } from '../../util/clean-modify';
|
||||||
|
import type { SourceImage } from '../../Compress';
|
||||||
|
import { PreprocessorState } from 'client/lazy-app/feature-meta';
|
||||||
|
import Cropper, { CropBox } from './Cropper';
|
||||||
|
import CanvasImage from '../CanvasImage';
|
||||||
|
import Expander from '../Options/Expander';
|
||||||
|
import Select from '../Options/Select';
|
||||||
|
import Checkbox from '../Options/Checkbox';
|
||||||
|
|
||||||
|
const ROTATE_ORIENTATIONS = [0, 90, 180, 270] as const;
|
||||||
|
|
||||||
|
const cropPresets = {
|
||||||
|
square: {
|
||||||
|
name: 'Square',
|
||||||
|
ratio: 1,
|
||||||
|
},
|
||||||
|
'4:3': {
|
||||||
|
name: '4:3',
|
||||||
|
ratio: 4 / 3,
|
||||||
|
},
|
||||||
|
'16:9': {
|
||||||
|
name: '16:9',
|
||||||
|
ratio: 16 / 9,
|
||||||
|
},
|
||||||
|
'16:10': {
|
||||||
|
name: '16:10',
|
||||||
|
ratio: 16 / 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type CropPresetId = keyof typeof cropPresets;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
source: SourceImage;
|
||||||
|
preprocessorState: PreprocessorState;
|
||||||
|
mobileView: boolean;
|
||||||
|
onCancel?(): void;
|
||||||
|
onSave?(e: { preprocessorState: PreprocessorState }): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
scale: number;
|
||||||
|
editingScale: boolean;
|
||||||
|
rotate: typeof ROTATE_ORIENTATIONS[number];
|
||||||
|
// crop: false | CropBox;
|
||||||
|
crop: CropBox;
|
||||||
|
cropPreset: keyof typeof cropPresets | undefined;
|
||||||
|
lockAspect: boolean;
|
||||||
|
flip: PreprocessorState['flip'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const scaleToOpts: ScaleToOpts = {
|
||||||
|
originX: '50%',
|
||||||
|
originY: '50%',
|
||||||
|
relativeTo: 'container',
|
||||||
|
allowChangeEvent: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Transform extends Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
scale: 1,
|
||||||
|
editingScale: false,
|
||||||
|
cropPreset: undefined,
|
||||||
|
lockAspect: false,
|
||||||
|
...this.fromPreprocessorState(this.props.preprocessorState),
|
||||||
|
};
|
||||||
|
pinchZoom = createRef<PinchZoom>();
|
||||||
|
scaleInput = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
// static getDerivedStateFromProps({ source, preprocessorState }: Props) {
|
||||||
|
// return {
|
||||||
|
// rotate: preprocessorState.rotate.rotate || 0,
|
||||||
|
// crop: preprocessorState.crop || false,
|
||||||
|
// flip: preprocessorState.flip || { horizontal: false, vertical: false },
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
componentWillReceiveProps(
|
||||||
|
{ source, preprocessorState }: Props,
|
||||||
|
{ crop, cropPreset }: State,
|
||||||
|
) {
|
||||||
|
if (preprocessorState !== this.props.preprocessorState) {
|
||||||
|
this.setState(this.fromPreprocessorState(preprocessorState));
|
||||||
|
}
|
||||||
|
const { width, height } = source.decoded;
|
||||||
|
const cropWidth = width - crop.left - crop.right;
|
||||||
|
const cropHeight = height - crop.top - crop.bottom;
|
||||||
|
for (const [id, preset] of Object.entries(cropPresets)) {
|
||||||
|
if (cropHeight * preset.ratio === cropWidth) {
|
||||||
|
if (cropPreset !== id) {
|
||||||
|
this.setState({ cropPreset: id as CropPresetId });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fromPreprocessorState(preprocessorState?: PreprocessorState) {
|
||||||
|
const state: Pick<State, 'rotate' | 'crop' | 'flip'> = {
|
||||||
|
rotate: preprocessorState ? preprocessorState.rotate.rotate : 0,
|
||||||
|
crop: Object.assign(
|
||||||
|
{
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
(preprocessorState && preprocessorState.crop) || {},
|
||||||
|
),
|
||||||
|
flip: Object.assign(
|
||||||
|
{
|
||||||
|
horizontal: false,
|
||||||
|
vertical: false,
|
||||||
|
},
|
||||||
|
(preprocessorState && preprocessorState.flip) || {},
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private save = () => {
|
||||||
|
const { preprocessorState, onSave } = this.props;
|
||||||
|
const { rotate, crop, flip } = this.state;
|
||||||
|
|
||||||
|
let newState = cleanSet(preprocessorState, 'rotate.rotate', rotate);
|
||||||
|
newState = cleanSet(newState, 'crop', crop);
|
||||||
|
newState = cleanSet(newState, 'flip', flip);
|
||||||
|
|
||||||
|
if (onSave) onSave({ preprocessorState: newState });
|
||||||
|
};
|
||||||
|
|
||||||
|
private cancel = () => {
|
||||||
|
const { onCancel, onSave } = this.props;
|
||||||
|
if (onCancel) onCancel();
|
||||||
|
else if (onSave)
|
||||||
|
onSave({ preprocessorState: this.props.preprocessorState });
|
||||||
|
};
|
||||||
|
|
||||||
|
private zoomIn = () => {
|
||||||
|
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
|
||||||
|
this.pinchZoom.current.scaleTo(this.state.scale * 1.25, scaleToOpts);
|
||||||
|
};
|
||||||
|
|
||||||
|
private zoomOut = () => {
|
||||||
|
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
|
||||||
|
this.pinchZoom.current.scaleTo(this.state.scale / 1.25, scaleToOpts);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onScaleValueFocus = () => {
|
||||||
|
this.setState({ editingScale: true }, () => {
|
||||||
|
if (this.scaleInput.current) {
|
||||||
|
// Firefox unfocuses the input straight away unless I force a style
|
||||||
|
// calculation here. I have no idea why, but it's late and I'm quite
|
||||||
|
// tired.
|
||||||
|
getComputedStyle(this.scaleInput.current).transform;
|
||||||
|
this.scaleInput.current.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onScaleInputBlur = () => {
|
||||||
|
this.setState({ editingScale: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onScaleInputChanged = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const percent = parseFloat(target.value);
|
||||||
|
if (isNaN(percent)) return;
|
||||||
|
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
|
||||||
|
|
||||||
|
this.pinchZoom.current.scaleTo(percent / 100, scaleToOpts);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onPinchZoomChange = () => {
|
||||||
|
if (!this.pinchZoom.current) throw Error('Missing pinch-zoom element');
|
||||||
|
this.setState({
|
||||||
|
scale: this.pinchZoom.current.scale,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCropChange = (crop: CropBox) => {
|
||||||
|
this.setState({ crop });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCropPresetChange = (event: Event) => {
|
||||||
|
const { value } = event.target as HTMLSelectElement;
|
||||||
|
const cropPreset = value ? (value as keyof typeof cropPresets) : undefined;
|
||||||
|
const crop = { ...this.state.crop };
|
||||||
|
if (cropPreset) {
|
||||||
|
const preset = cropPresets[cropPreset];
|
||||||
|
const { width, height } = this.props.source.decoded;
|
||||||
|
const w = width - crop.left - crop.right;
|
||||||
|
const h = w / preset.ratio;
|
||||||
|
crop.bottom = height - crop.top - h;
|
||||||
|
if (crop.bottom < 0) {
|
||||||
|
crop.top += crop.bottom;
|
||||||
|
crop.bottom = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
crop,
|
||||||
|
cropPreset,
|
||||||
|
lockAspect: !!cropPreset,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private swapCropDimensions = () => {
|
||||||
|
const { width, height } = this.props.source.decoded;
|
||||||
|
let { left, right, top, bottom } = this.state.crop;
|
||||||
|
const cropWidth = width - left - right;
|
||||||
|
const cropHeight = height - top - bottom;
|
||||||
|
const centerX = left - right;
|
||||||
|
const centerY = top - bottom;
|
||||||
|
const crop = {
|
||||||
|
top: (width - cropWidth) / 2 + centerY / 2,
|
||||||
|
bottom: (width - cropWidth) / 2 - centerY / 2,
|
||||||
|
left: (height - cropHeight) / 2 + centerX / 2,
|
||||||
|
right: (height - cropHeight) / 2 - centerX / 2,
|
||||||
|
};
|
||||||
|
this.setCrop(crop);
|
||||||
|
};
|
||||||
|
|
||||||
|
private setCrop(crop: CropBox) {
|
||||||
|
if (crop.top < 0) {
|
||||||
|
crop.bottom += crop.top;
|
||||||
|
crop.top = 0;
|
||||||
|
}
|
||||||
|
if (crop.bottom < 0) {
|
||||||
|
crop.top += crop.bottom;
|
||||||
|
crop.bottom = 0;
|
||||||
|
}
|
||||||
|
if (crop.left < 0) {
|
||||||
|
crop.right += crop.left;
|
||||||
|
crop.left = 0;
|
||||||
|
}
|
||||||
|
if (crop.right < 0) {
|
||||||
|
crop.left += crop.right;
|
||||||
|
crop.right = 0;
|
||||||
|
}
|
||||||
|
this.setState({ crop });
|
||||||
|
}
|
||||||
|
|
||||||
|
// yeah these could just += 90
|
||||||
|
private rotateClockwise = () => {
|
||||||
|
let { rotate, crop } = this.state;
|
||||||
|
this.setState({
|
||||||
|
rotate: ((rotate + 90) % 360) as typeof ROTATE_ORIENTATIONS[number],
|
||||||
|
crop: {
|
||||||
|
top: crop.left,
|
||||||
|
left: crop.bottom,
|
||||||
|
bottom: crop.right,
|
||||||
|
right: crop.top,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private rotateCounterClockwise = () => {
|
||||||
|
let { rotate, crop } = this.state;
|
||||||
|
this.setState({
|
||||||
|
rotate: (rotate
|
||||||
|
? rotate - 90
|
||||||
|
: 270) as typeof ROTATE_ORIENTATIONS[number],
|
||||||
|
crop: {
|
||||||
|
top: crop.right,
|
||||||
|
right: crop.bottom,
|
||||||
|
bottom: crop.left,
|
||||||
|
left: crop.top,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private flipHorizontally = () => {
|
||||||
|
const { horizontal, vertical } = this.state.flip;
|
||||||
|
this.setState({ flip: { horizontal: !horizontal, vertical } });
|
||||||
|
};
|
||||||
|
|
||||||
|
private flipVertically = () => {
|
||||||
|
const { horizontal, vertical } = this.state.flip;
|
||||||
|
this.setState({ flip: { horizontal, vertical: !vertical } });
|
||||||
|
};
|
||||||
|
|
||||||
|
private toggleLockAspect = () => {
|
||||||
|
this.setState({ lockAspect: !this.state.lockAspect });
|
||||||
|
};
|
||||||
|
|
||||||
|
private setCropWidth = (
|
||||||
|
event: preact.JSX.TargetedEvent<HTMLInputElement, Event>,
|
||||||
|
) => {
|
||||||
|
const { width, height } = this.props.source.decoded;
|
||||||
|
const newWidth = Math.min(width, parseInt(event.currentTarget.value, 10));
|
||||||
|
let { top, right, bottom, left } = this.state.crop;
|
||||||
|
const aspect = (width - left - right) / (height - top - bottom);
|
||||||
|
right = width - newWidth - left;
|
||||||
|
if (this.state.lockAspect) {
|
||||||
|
const newHeight = newWidth / aspect;
|
||||||
|
if (newHeight > height) return;
|
||||||
|
bottom = height - newHeight - top;
|
||||||
|
}
|
||||||
|
this.setCrop({ top, right, bottom, left });
|
||||||
|
};
|
||||||
|
|
||||||
|
private setCropHeight = (
|
||||||
|
event: preact.JSX.TargetedEvent<HTMLInputElement, Event>,
|
||||||
|
) => {
|
||||||
|
const { width, height } = this.props.source.decoded;
|
||||||
|
const newHeight = Math.min(height, parseInt(event.currentTarget.value, 10));
|
||||||
|
let { top, right, bottom, left } = this.state.crop;
|
||||||
|
const aspect = (width - left - right) / (height - top - bottom);
|
||||||
|
bottom = height - newHeight - top;
|
||||||
|
if (this.state.lockAspect) {
|
||||||
|
const newWidth = newHeight * aspect;
|
||||||
|
if (newWidth > width) return;
|
||||||
|
right = width - newWidth - left;
|
||||||
|
}
|
||||||
|
this.setCrop({ top, right, bottom, left });
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
{ mobileView, source }: Props,
|
||||||
|
{ scale, editingScale, rotate, flip, crop, cropPreset, lockAspect }: State,
|
||||||
|
) {
|
||||||
|
const image = source.decoded;
|
||||||
|
|
||||||
|
const width = source.decoded.width - crop.left - crop.right;
|
||||||
|
const height = source.decoded.height - crop.top - crop.bottom;
|
||||||
|
|
||||||
|
let transform =
|
||||||
|
`rotate(${rotate}deg) ` +
|
||||||
|
`scale(${flip.horizontal ? -1 : 1}, ${flip.vertical ? -1 : 1})`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<CancelButton onClick={this.cancel} />
|
||||||
|
<SaveButton onClick={this.save} />
|
||||||
|
|
||||||
|
<div class={style.transform}>
|
||||||
|
<pinch-zoom
|
||||||
|
class={style.pinchZoom}
|
||||||
|
onChange={this.onPinchZoomChange}
|
||||||
|
ref={this.pinchZoom}
|
||||||
|
>
|
||||||
|
{/* <Backdrop width={image.width} height={image.height} /> */}
|
||||||
|
<div
|
||||||
|
class={style.wrap}
|
||||||
|
style={{
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CanvasImage
|
||||||
|
class={style.pinchTarget}
|
||||||
|
image={image}
|
||||||
|
style={{ transform }}
|
||||||
|
/>
|
||||||
|
{crop && (
|
||||||
|
<Cropper
|
||||||
|
size={{ width: image.width, height: image.height }}
|
||||||
|
scale={scale}
|
||||||
|
lockAspect={lockAspect}
|
||||||
|
crop={crop}
|
||||||
|
onChange={this.onCropChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</pinch-zoom>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={style.controls}>
|
||||||
|
<div class={style.zoomControls}>
|
||||||
|
<button class={style.button} onClick={this.zoomOut}>
|
||||||
|
<RemoveIcon />
|
||||||
|
</button>
|
||||||
|
{editingScale ? (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="1"
|
||||||
|
max="1000000"
|
||||||
|
ref={this.scaleInput}
|
||||||
|
class={style.zoom}
|
||||||
|
value={Math.round(scale * 100)}
|
||||||
|
onInput={this.onScaleInputChanged}
|
||||||
|
onBlur={this.onScaleInputBlur}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
class={style.zoom}
|
||||||
|
tabIndex={0}
|
||||||
|
onFocus={this.onScaleValueFocus}
|
||||||
|
>
|
||||||
|
<span class={style.zoomValue}>{Math.round(scale * 100)}</span>%
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<button class={style.button} onClick={this.zoomIn}>
|
||||||
|
<AddIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={style.options}>
|
||||||
|
<h3 class={style.optionsTitle}>Modify Source</h3>
|
||||||
|
|
||||||
|
<div class={style.optionsSection}>
|
||||||
|
<h4 class={style.optionsSectionTitle}>Crop</h4>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Select
|
||||||
|
large
|
||||||
|
value={cropPreset}
|
||||||
|
onChange={this.onCropPresetChange}
|
||||||
|
>
|
||||||
|
<option value="">Custom</option>
|
||||||
|
{Object.entries(cropPresets).map(([type, preset]) => (
|
||||||
|
<option value={type}>{preset.name}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<label class={style.optionCheckbox}>
|
||||||
|
<Checkbox checked={lockAspect} onClick={this.toggleLockAspect} />
|
||||||
|
Lock aspect-ratio
|
||||||
|
</label>
|
||||||
|
<div class={style.optionsDimensions}>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="width"
|
||||||
|
value={width}
|
||||||
|
title="Crop width"
|
||||||
|
onInput={this.setCropWidth}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class={style.optionsButton}
|
||||||
|
title="swap"
|
||||||
|
onClick={this.swapCropDimensions}
|
||||||
|
>
|
||||||
|
<SwapIcon />
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="height"
|
||||||
|
value={height}
|
||||||
|
title="Crop height"
|
||||||
|
onInput={this.setCropHeight}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={style.optionButtonRow}>
|
||||||
|
Flip
|
||||||
|
<button
|
||||||
|
class={style.optionsButton}
|
||||||
|
data-active={flip.vertical}
|
||||||
|
title="Flip vertically"
|
||||||
|
onClick={this.flipVertically}
|
||||||
|
>
|
||||||
|
<FlipVerticallyIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={style.optionsButton}
|
||||||
|
data-active={flip.horizontal}
|
||||||
|
title="Flip horizontally"
|
||||||
|
onClick={this.flipHorizontally}
|
||||||
|
>
|
||||||
|
<FlipHorizontallyIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={style.optionButtonRow}>
|
||||||
|
Rotate
|
||||||
|
<button
|
||||||
|
class={style.optionsButton}
|
||||||
|
title="Rotate clockwise"
|
||||||
|
onClick={this.rotateClockwise}
|
||||||
|
>
|
||||||
|
<RotateClockwiseIcon />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class={style.optionsButton}
|
||||||
|
title="Rotate counter-clockwise"
|
||||||
|
onClick={this.rotateCounterClockwise}
|
||||||
|
>
|
||||||
|
<RotateCounterClockwiseIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CancelButton = ({ onClick }: { onClick: () => void }) => (
|
||||||
|
<button class={style.cancel} onClick={onClick}>
|
||||||
|
<svg viewBox="0 0 80 80" width="80" height="80">
|
||||||
|
<path d="M8.06 40.98c-.53-7.1 4.05-14.52 9.98-19.1s13.32-6.35 22.13-6.43c8.84-.12 19.12 1.51 24.4 7.97s5.6 17.74 1.68 26.97c-3.89 9.26-11.97 16.45-20.46 18-8.43 1.55-17.28-2.62-24.5-8.08S8.54 48.08 8.07 40.98z" />
|
||||||
|
</svg>
|
||||||
|
<CompareIcon class={style.icon} />
|
||||||
|
<span>Cancel</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const SaveButton = ({ onClick }: { onClick: () => void }) => (
|
||||||
|
<button class={style.save} onClick={onClick}>
|
||||||
|
<svg viewBox="0 0 89 87" width="89" height="87">
|
||||||
|
<path
|
||||||
|
fill="#0c99ff"
|
||||||
|
opacity=".7"
|
||||||
|
d="M27.3 71.9c-8-4-15.6-12.3-16.9-21-1.2-8.7 4-17.8 10.5-26s14.4-15.6 24-16 21.2 6 28.6 16.5c7.4 10.5 10.8 25 6.6 34S64.1 71.7 54 73.5c-10.2 2-18.7 2.3-26.7-1.6z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#0c99ff"
|
||||||
|
opacity=".7"
|
||||||
|
d="M14.6 24.8c4.3-7.8 13-15 21.8-15.7 8.7-.8 17.5 4.8 25.4 11.8 7.8 6.9 14.8 15.2 14.8 24.9s-7.2 20.7-18 27.6c-10.9 6.8-25.6 9.5-34.3 4.8S13 61.6 11.6 51.4c-1.3-10.3-1.3-18.8 3-26.6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<CheckmarkIcon class={style.icon} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
interface BackdropProps {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @TODO this could at least use clip-path. It's too expensive this way. */
|
||||||
|
class Backdrop extends Component<BackdropProps> {
|
||||||
|
shouldComponentUpdate({ width, height }: BackdropProps) {
|
||||||
|
return width !== this.props.width || height !== this.props.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ width, height }: BackdropProps) {
|
||||||
|
const transform =
|
||||||
|
`transform-origin: 50% 50%; transform: translate(var(--x), var(--y)) ` +
|
||||||
|
`translate(-${width / 2}px, -${height / 2}px) ` +
|
||||||
|
`scale(calc(var(--scale, 1) * 0.99999));`;
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
class={style.backdrop}
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
shape-rendering="optimizeSpeed"
|
||||||
|
>
|
||||||
|
<mask id="bgmask">
|
||||||
|
<rect width="100%" height="100%" fill="white" />
|
||||||
|
<rect
|
||||||
|
style={transform}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
x="50%"
|
||||||
|
y="50%"
|
||||||
|
fill="black"
|
||||||
|
/>
|
||||||
|
</mask>
|
||||||
|
<rect
|
||||||
|
class={style.backdropArea}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
mask="url(#bgmask)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
356
src/client/lazy-app/Compress/Transform/style.css
Normal file
356
src/client/lazy-app/Compress/Transform/style.css
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
.transform {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
/*
|
||||||
|
& > canvas {
|
||||||
|
transition: transform 150ms ease;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transform: none !important;
|
||||||
|
will-change: initial !important;
|
||||||
|
contain: strict;
|
||||||
|
|
||||||
|
& * {
|
||||||
|
contain: strict;
|
||||||
|
}
|
||||||
|
/* background: rgba(255, 0, 0, 0.5); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.backdropArea {
|
||||||
|
fill: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pinch-zoom {
|
||||||
|
composes: abs-fill from global;
|
||||||
|
outline: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pinch-target {
|
||||||
|
/* This fixes a severe painting bug in Chrome.
|
||||||
|
* We should try to remove this once the issue is fixed.
|
||||||
|
* https://bugs.chromium.org/p/chromium/issues/detail?id=870222#c10 */
|
||||||
|
will-change: auto;
|
||||||
|
/* Prevent the image becoming misshapen due to default flexbox layout. */
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel,
|
||||||
|
.save {
|
||||||
|
composes: unbutton from global;
|
||||||
|
position: absolute;
|
||||||
|
padding: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
grid-area: 1/1/1/1;
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @TODO use grid */
|
||||||
|
.cancel {
|
||||||
|
fill: rgba(0, 0, 0, 0.7);
|
||||||
|
|
||||||
|
& > svg:not(.icon) {
|
||||||
|
display: block;
|
||||||
|
margin: -8px 0;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 28px;
|
||||||
|
top: 22px;
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
font-size: 80%;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
fill: rgba(0, 0, 0, 0.9);
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 78px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
max-width: 250px;
|
||||||
|
margin: 0;
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
max-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
align-self: end;
|
||||||
|
border-radius: var(--options-radius) 0 0 var(--options-radius);
|
||||||
|
animation: slideInFromRight 500ms ease-out forwards 1;
|
||||||
|
--horizontal-padding: 15px;
|
||||||
|
--main-theme-color: var(--blue);
|
||||||
|
|
||||||
|
/* Hide on mobile (for now) */
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slideInFromRight {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-title {
|
||||||
|
background-color: var(--main-theme-color);
|
||||||
|
color: var(--dark-text);
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px var(--horizontal-padding);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-bottom: 1px solid var(--off-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section {
|
||||||
|
padding: 5px 0;
|
||||||
|
background: var(--off-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-section-title {
|
||||||
|
font: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px var(--horizontal-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-base {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.7em;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px var(--horizontal-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-button {
|
||||||
|
composes: unbutton from global;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--dark-gray);
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--off-black);
|
||||||
|
border-color: var(--med-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active] {
|
||||||
|
background-color: var(--dark-gray);
|
||||||
|
border-color: var(--med-gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-dimensions {
|
||||||
|
composes: option-base;
|
||||||
|
grid-template-columns: 1fr 0fr 1fr;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
font: inherit;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-one-cell {
|
||||||
|
composes: option-base;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-button-row {
|
||||||
|
composes: option-base;
|
||||||
|
grid-template-columns: 1fr auto auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-checkbox {
|
||||||
|
composes: option-base;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Zoom controls */
|
||||||
|
.controls {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 9px 84px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
/* Allow clicks to fall through to the pinch zoom area */
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 860px) {
|
||||||
|
padding: 9px;
|
||||||
|
top: auto;
|
||||||
|
left: 320px;
|
||||||
|
right: 320px;
|
||||||
|
bottom: 0;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-controls {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
& > :not(:first-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :not(:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
border-right-width: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.zoom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 4px;
|
||||||
|
background-color: rgba(29, 29, 29, 0.92);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.67);
|
||||||
|
border-radius: 6px;
|
||||||
|
line-height: 1.1;
|
||||||
|
white-space: nowrap;
|
||||||
|
height: 39px;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
@media (min-width: 600px) {
|
||||||
|
height: 39px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
/* box-shadow: 0 0 0 2px var(--hot-pink); */
|
||||||
|
box-shadow: 0 0 0 2px #fff;
|
||||||
|
outline: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(50, 50, 50, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: rgba(72, 72, 72, 0.92);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom {
|
||||||
|
cursor: text;
|
||||||
|
width: 7rem;
|
||||||
|
font: inherit;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.2), 0 0 0 2px #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
span.zoom {
|
||||||
|
color: #939393;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
input.zoom {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-indent: 3px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-value {
|
||||||
|
margin: 0 3px 0 0;
|
||||||
|
padding: 0 2px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px dashed #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-no-wrap {
|
||||||
|
display: flex;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
canDecodeImageType,
|
canDecodeImageType,
|
||||||
abortable,
|
abortable,
|
||||||
assertSignal,
|
assertSignal,
|
||||||
|
shallowEqual,
|
||||||
} from '../util';
|
} from '../util';
|
||||||
import {
|
import {
|
||||||
PreprocessorState,
|
PreprocessorState,
|
||||||
@@ -31,7 +32,7 @@ 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/custom-els/snack-bar';
|
||||||
import { Arrow, ExpandIcon } from '../icons';
|
import Transform from './Transform';
|
||||||
import { generateCliInvocation } from '../util/cli';
|
import { generateCliInvocation } from '../util/cli';
|
||||||
|
|
||||||
export type OutputType = EncoderType | 'identity';
|
export type OutputType = EncoderType | 'identity';
|
||||||
@@ -69,8 +70,11 @@ interface State {
|
|||||||
sides: [Side, Side];
|
sides: [Side, Side];
|
||||||
/** Source image load */
|
/** Source image load */
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
/** Showing preprocessor transformations modal */
|
||||||
|
transform: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
mobileView: boolean;
|
mobileView: boolean;
|
||||||
|
altBackground: boolean;
|
||||||
preprocessorState: PreprocessorState;
|
preprocessorState: PreprocessorState;
|
||||||
encodedPreprocessorState?: PreprocessorState;
|
encodedPreprocessorState?: PreprocessorState;
|
||||||
}
|
}
|
||||||
@@ -126,13 +130,18 @@ async function preprocessImage(
|
|||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
assertSignal(signal);
|
assertSignal(signal);
|
||||||
let processedData = data;
|
let processedData = data;
|
||||||
|
const { rotate, flip, crop } = preprocessorState;
|
||||||
|
|
||||||
if (preprocessorState.rotate.rotate !== 0) {
|
if (flip && (flip.horizontal || flip.vertical)) {
|
||||||
processedData = await workerBridge.rotate(
|
processedData = await workerBridge.flip(signal, processedData, flip);
|
||||||
signal,
|
}
|
||||||
processedData,
|
|
||||||
preprocessorState.rotate,
|
if (rotate.rotate !== 0) {
|
||||||
);
|
processedData = await workerBridge.rotate(signal, processedData, rotate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (crop && (crop.left || crop.top || crop.right || crop.bottom)) {
|
||||||
|
processedData = await workerBridge.crop(signal, processedData, crop);
|
||||||
}
|
}
|
||||||
|
|
||||||
return processedData;
|
return processedData;
|
||||||
@@ -274,6 +283,9 @@ export default class Compress extends Component<Props, State> {
|
|||||||
state: State = {
|
state: State = {
|
||||||
source: undefined,
|
source: undefined,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
/** @TODO remove this */
|
||||||
|
// transform: true,
|
||||||
|
transform: false,
|
||||||
preprocessorState: defaultPreprocessorState,
|
preprocessorState: defaultPreprocessorState,
|
||||||
sides: [
|
sides: [
|
||||||
{
|
{
|
||||||
@@ -295,6 +307,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
mobileView: this.widthQuery.matches,
|
mobileView: this.widthQuery.matches,
|
||||||
|
altBackground: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly encodeCache = new ResultCache();
|
private readonly encodeCache = new ResultCache();
|
||||||
@@ -320,6 +333,12 @@ export default class Compress extends Component<Props, State> {
|
|||||||
this.setState({ mobileView: this.widthQuery.matches });
|
this.setState({ mobileView: this.widthQuery.matches });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private toggleBackground = () => {
|
||||||
|
this.setState({
|
||||||
|
altBackground: !this.state.altBackground,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private onEncoderTypeChange = (index: 0 | 1, newType: OutputType): void => {
|
private onEncoderTypeChange = (index: 0 | 1, newType: OutputType): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
sides: cleanSet(
|
sides: cleanSet(
|
||||||
@@ -361,6 +380,19 @@ export default class Compress extends Component<Props, State> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private showPreprocessorTransforms = () => {
|
||||||
|
this.setState({ transform: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onTransformUpdated = ({
|
||||||
|
preprocessorState,
|
||||||
|
}: { preprocessorState?: PreprocessorState } = {}) => {
|
||||||
|
if (preprocessorState) {
|
||||||
|
this.onPreprocessorChange(preprocessorState);
|
||||||
|
}
|
||||||
|
this.setState({ transform: false });
|
||||||
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: Props): void {
|
componentWillReceiveProps(nextProps: Props): void {
|
||||||
if (nextProps.file !== this.props.file) {
|
if (nextProps.file !== this.props.file) {
|
||||||
this.sourceFile = nextProps.file;
|
this.sourceFile = nextProps.file;
|
||||||
@@ -417,25 +449,38 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const newRotate = preprocessorState.rotate.rotate;
|
const newRotate = preprocessorState.rotate.rotate;
|
||||||
const orientationChanged = oldRotate % 180 !== newRotate % 180;
|
const orientationChanged = oldRotate % 180 !== newRotate % 180;
|
||||||
|
|
||||||
|
const { crop } = preprocessorState;
|
||||||
|
const cropChanged = !shallowEqual(crop, this.state.preprocessorState.crop);
|
||||||
|
|
||||||
this.setState((state) => ({
|
this.setState((state) => ({
|
||||||
loading: true,
|
loading: true,
|
||||||
preprocessorState,
|
preprocessorState,
|
||||||
// Flip resize values if orientation has changed
|
// Flip resize values if orientation has changed
|
||||||
sides: !orientationChanged
|
sides:
|
||||||
? state.sides
|
!orientationChanged && !cropChanged
|
||||||
: (state.sides.map((side) => {
|
? state.sides
|
||||||
const currentResizeSettings =
|
: (state.sides.map((side) => {
|
||||||
side.latestSettings.processorState.resize;
|
const currentResizeSettings =
|
||||||
const resizeSettings: Partial<ProcessorState['resize']> = {
|
side.latestSettings.processorState.resize;
|
||||||
width: currentResizeSettings.height,
|
let resizeSettings: Partial<ProcessorState['resize']>;
|
||||||
height: currentResizeSettings.width,
|
if (cropChanged) {
|
||||||
};
|
const img = state.source?.decoded;
|
||||||
return cleanMerge(
|
resizeSettings = {
|
||||||
side,
|
width: img ? img.width - crop.left - crop.right : undefined,
|
||||||
'latestSettings.processorState.resize',
|
height: img ? img.height - crop.top - crop.bottom : undefined,
|
||||||
resizeSettings,
|
};
|
||||||
);
|
} else {
|
||||||
}) as [Side, Side]),
|
resizeSettings = {
|
||||||
|
width: currentResizeSettings.height,
|
||||||
|
height: currentResizeSettings.width,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cleanMerge(
|
||||||
|
side,
|
||||||
|
'latestSettings.processorState.resize',
|
||||||
|
resizeSettings,
|
||||||
|
);
|
||||||
|
}) as [Side, Side]),
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -604,7 +649,6 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const resizeState: Partial<ProcessorState['resize']> = {
|
const resizeState: Partial<ProcessorState['resize']> = {
|
||||||
width: decoded.width,
|
width: decoded.width,
|
||||||
height: decoded.height,
|
height: decoded.height,
|
||||||
method: vectorImage ? 'vector' : 'lanczos3',
|
|
||||||
// Disable resizing, to make it clearer to the user that something changed here
|
// Disable resizing, to make it clearer to the user that something changed here
|
||||||
enabled: false,
|
enabled: false,
|
||||||
};
|
};
|
||||||
@@ -814,12 +858,22 @@ export default class Compress extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
{ onBack }: Props,
|
{ onBack, showSnack }: Props,
|
||||||
{ loading, sides, source, mobileView, preprocessorState }: State,
|
{
|
||||||
|
loading,
|
||||||
|
sides,
|
||||||
|
source,
|
||||||
|
mobileView,
|
||||||
|
altBackground,
|
||||||
|
transform,
|
||||||
|
preprocessorState,
|
||||||
|
}: State,
|
||||||
) {
|
) {
|
||||||
const [leftSide, rightSide] = sides;
|
const [leftSide, rightSide] = sides;
|
||||||
const [leftImageData, rightImageData] = sides.map((i) => i.data);
|
const [leftImageData, rightImageData] = sides.map((i) => i.data);
|
||||||
|
|
||||||
|
transform = (source && source.decoded && transform) || false;
|
||||||
|
|
||||||
const options = sides.map((side, index) => (
|
const options = sides.map((side, index) => (
|
||||||
<Options
|
<Options
|
||||||
index={index as 0 | 1}
|
index={index as 0 | 1}
|
||||||
@@ -864,8 +918,13 @@ export default class Compress extends Component<Props, State> {
|
|||||||
rightDisplaySettings.processorState.resize.fitMethod === 'contain';
|
rightDisplaySettings.processorState.resize.fitMethod === 'contain';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.compress}>
|
<div
|
||||||
|
class={`${style.compress} ${transform ? style.transforming : ''} ${
|
||||||
|
altBackground ? style.altBackground : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<Output
|
<Output
|
||||||
|
hidden={transform}
|
||||||
source={source}
|
source={source}
|
||||||
mobileView={mobileView}
|
mobileView={mobileView}
|
||||||
leftCompressed={leftImageData}
|
leftCompressed={leftImageData}
|
||||||
@@ -874,6 +933,8 @@ export default class Compress extends Component<Props, State> {
|
|||||||
rightImgContain={rightImgContain}
|
rightImgContain={rightImgContain}
|
||||||
preprocessorState={preprocessorState}
|
preprocessorState={preprocessorState}
|
||||||
onPreprocessorChange={this.onPreprocessorChange}
|
onPreprocessorChange={this.onPreprocessorChange}
|
||||||
|
onShowPreprocessorTransforms={this.showPreprocessorTransforms}
|
||||||
|
onToggleBackground={this.toggleBackground}
|
||||||
/>
|
/>
|
||||||
<button class={style.back} onClick={onBack}>
|
<button class={style.back} onClick={onBack}>
|
||||||
<svg viewBox="0 0 61 53.3">
|
<svg viewBox="0 0 61 53.3">
|
||||||
@@ -909,6 +970,16 @@ export default class Compress extends Component<Props, State> {
|
|||||||
</div>,
|
</div>,
|
||||||
]
|
]
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{transform && (
|
||||||
|
<Transform
|
||||||
|
mobileView={mobileView}
|
||||||
|
source={source!}
|
||||||
|
preprocessorState={preprocessorState!}
|
||||||
|
onSave={this.onTransformUpdated}
|
||||||
|
onCancel={this.onTransformUpdated}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,47 @@
|
|||||||
'header header header'
|
'header header header'
|
||||||
'optsLeft viewportOpts optsRight';
|
'optsLeft viewportOpts optsRight';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* darker squares background */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #000;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 500ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alt-background::before {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* transformation is modal and we sweep away the comparison UI */
|
||||||
|
&.transforming {
|
||||||
|
& > .options {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
& > .options + .options {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
& > .options {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .back {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > :first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
@@ -33,6 +74,7 @@
|
|||||||
grid-template-rows: 1fr max-content;
|
grid-template-rows: 1fr max-content;
|
||||||
align-content: end;
|
align-content: end;
|
||||||
align-self: end;
|
align-self: end;
|
||||||
|
transition: transform 500ms ease;
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
|
|
||||||
const Icon = (props: preact.JSX.HTMLAttributes) => (
|
const Icon = (props: preact.JSX.HTMLAttributes | preact.JSX.SVGAttributes) => (
|
||||||
// @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019
|
// @ts-ignore - TS bug https://github.com/microsoft/TypeScript/issues/16019
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
@@ -11,6 +11,56 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const CLIIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M1 2.7H23v18.5H1zm5.5 13l3.7-3.7-3.7-3.7m5.5 7.4h5.6" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SwapIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M8.5 8.6v6.8L5.1 12l3.4-3.4M10 5l-7 7 7 7V5zm4 0v14l7-7-7-7z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FlipVerticallyIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M21 9V7h-2v2zM9 5V3H7v2zM5 21h14a2 2 0 002-2v-4h-2v4H5v-4H3v4a2 2 0 002 2zM3 5h2V3a2 2 0 00-2 2zm20 8v-2H1v2zm-6-8V3h-2v2zM5 9V7H3v2zm8-4V3h-2v2zm8 0a2 2 0 00-2-2v2z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const FlipHorizontallyIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M15 21h2v-2h-2zm4-12h2V7h-2zM3 5v14a2 2 0 002 2h4v-2H5V5h4V3H5a2 2 0 00-2 2zm16-2v2h2a2 2 0 00-2-2zm-8 20h2V1h-2zm8-6h2v-2h-2zM15 5h2V3h-2zm4 8h2v-2h-2zm0 8a2 2 0 002-2h-2z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RotateClockwiseIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M16.05 5.34l-5.2-5.2v3.5a9.12 9.12 0 000 18.1v-2.3a6.84 6.84 0 010-13.5v4.47zm5 6.22a9.03 9.03 0 00-1.85-4.44l-1.62 1.62a6.63 6.63 0 011.16 2.82zm-7.91 7.87v2.31a9.05 9.05 0 004.45-1.84l-1.64-1.64a6.6 6.6 0 01-2.81 1.18zm4.44-2.76l1.62 1.61a9.03 9.03 0 001.85-4.44h-2.3a6.73 6.73 0 01-1.17 2.83z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const RotateCounterClockwiseIcon = (
|
||||||
|
props: preact.JSX.HTMLAttributes,
|
||||||
|
) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M7.95 5.34l5.19-5.2v3.5a9.12 9.12 0 010 18.1v-2.3a6.84 6.84 0 000-13.5v4.47zm-5 6.22A9.03 9.03 0 014.8 7.12l1.62 1.62a6.63 6.63 0 00-1.17 2.82zm7.9 7.87v2.31A9.05 9.05 0 016.4 19.9l1.65-1.64a6.6 6.6 0 002.8 1.17zm-4.43-2.76L4.8 18.28a9.03 9.03 0 01-1.85-4.44h2.3a6.73 6.73 0 001.17 2.83z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CheckmarkIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M9.76 17.56l-4.55-4.55-1.52 1.52 6.07 6.08 13-13.02-1.51-1.52z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CompareIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M9.77 1.94h-5.6a2.24 2.24 0 00-2.22 2.25v15.65a2.24 2.24 0 002.24 2.23h5.59v2.24h2.23V-.31H9.78zm0 16.77h-5.6l5.6-6.7zM19.83 1.94h-5.6v2.25h5.6v14.53l-5.6-6.7v10.05h5.6a2.24 2.24 0 002.23-2.23V4.18a2.24 2.24 0 00-2.23-2.24z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
export const ToggleBackgroundIcon = (props: preact.JSX.HTMLAttributes) => (
|
export const ToggleBackgroundIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
<Icon {...props}>
|
<Icon {...props}>
|
||||||
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.9 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h10a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z" />
|
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.9 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h10a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z" />
|
||||||
@@ -31,6 +81,14 @@ 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" />
|
||||||
|
<circle cx="12" cy="12" r="2" />
|
||||||
|
<circle cx="12" cy="18" r="2" />
|
||||||
|
</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" />
|
||||||
@@ -72,20 +130,3 @@ export const DownloadIcon = () => (
|
|||||||
<path d="M6.6 2.7h-4v13.2h2.7A2.7 2.7 0 018 18.6a2.7 2.7 0 002.6 2.6h2.7a2.7 2.7 0 002.6-2.6 2.7 2.7 0 012.7-2.7h2.6V2.7h-4a1.3 1.3 0 110-2.7h4A2.7 2.7 0 0124 2.7v18.5a2.7 2.7 0 01-2.7 2.7H2.7A2.7 2.7 0 010 21.2V2.7A2.7 2.7 0 012.7 0h4a1.3 1.3 0 010 2.7zm4 7.4V1.3a1.3 1.3 0 112.7 0v8.8L15 8.4a1.3 1.3 0 011.9 1.8l-4 4a1.3 1.3 0 01-1.9 0l-4-4A1.3 1.3 0 019 8.4z" />
|
<path d="M6.6 2.7h-4v13.2h2.7A2.7 2.7 0 018 18.6a2.7 2.7 0 002.6 2.6h2.7a2.7 2.7 0 002.6-2.6 2.7 2.7 0 012.7-2.7h2.6V2.7h-4a1.3 1.3 0 110-2.7h4A2.7 2.7 0 0124 2.7v18.5a2.7 2.7 0 01-2.7 2.7H2.7A2.7 2.7 0 010 21.2V2.7A2.7 2.7 0 012.7 0h4a1.3 1.3 0 010 2.7zm4 7.4V1.3a1.3 1.3 0 112.7 0v8.8L15 8.4a1.3 1.3 0 011.9 1.8l-4 4a1.3 1.3 0 01-1.9 0l-4-4A1.3 1.3 0 019 8.4z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const CLIIcon = () => (
|
|
||||||
<svg viewBox="0 0 81.3 68.8">
|
|
||||||
<path
|
|
||||||
fill="none"
|
|
||||||
stroke-miterlimit="15.6"
|
|
||||||
stroke-width="6.3"
|
|
||||||
d="M3.1 3.1h75v62.5h-75zm18.8 43.8l12.5-12.5-12.5-12.5m18.7 25h18.8"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SwapIcon = () => (
|
|
||||||
<svg viewBox="0 0 18 14">
|
|
||||||
<path d="M5.5 3.6v6.8L2.1 7l3.4-3.4M7 0L0 7l7 7V0zm4 0v14l7-7-7-7z" />
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
|
|
||||||
import { get, set } from 'idb-keyval';
|
import { get, set } from 'idb-keyval';
|
||||||
import { canDecodeImageType } from 'client/lazy-app/util';
|
|
||||||
|
|
||||||
import swUrl from 'service-worker:sw';
|
import swUrl from 'service-worker:sw';
|
||||||
|
|
||||||
@@ -105,12 +104,6 @@ export async function mainAppLoaded() {
|
|||||||
// If the user has already interacted, no need to tell the service worker anything.
|
// If the user has already interacted, no need to tell the service worker anything.
|
||||||
const userInteracted = await get<boolean | undefined>('user-interacted');
|
const userInteracted = await get<boolean | undefined>('user-interacted');
|
||||||
if (userInteracted) return;
|
if (userInteracted) return;
|
||||||
await Promise.all(
|
|
||||||
['avif', 'webp'].map(async (name) => {
|
|
||||||
let isSupported = await canDecodeImageType(`image/${name}`);
|
|
||||||
await set(`supports-${name}`, isSupported);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
set('user-interacted', true);
|
set('user-interacted', true);
|
||||||
const serviceWorker = await getMostActiveServiceWorker();
|
const serviceWorker = await getMostActiveServiceWorker();
|
||||||
if (!serviceWorker) return; // Service worker not installing yet.
|
if (!serviceWorker) return; // Service worker not installing yet.
|
||||||
|
|||||||
25
src/features/preprocessors/crop/shared/meta.ts
Normal file
25
src/features/preprocessors/crop/shared/meta.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
export interface Options {
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
top: number;
|
||||||
|
bottom: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: Options = {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
};
|
||||||
13
src/features/preprocessors/crop/shared/missing-types.d.ts
vendored
Normal file
13
src/features/preprocessors/crop/shared/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../../../../missing-types.d.ts" />
|
||||||
37
src/features/preprocessors/crop/worker/crop.ts
Normal file
37
src/features/preprocessors/crop/worker/crop.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { Options } from '../shared/meta';
|
||||||
|
|
||||||
|
export default async function crop(
|
||||||
|
{ data, width, height }: ImageData,
|
||||||
|
{ top, right, bottom, left }: Options,
|
||||||
|
): Promise<ImageData> {
|
||||||
|
const newWidth = width - left - right;
|
||||||
|
const newHeight = height - top - bottom;
|
||||||
|
|
||||||
|
const cols = width * 4;
|
||||||
|
const newCols = newWidth * 4;
|
||||||
|
|
||||||
|
const pixels = new Uint8ClampedArray(data.buffer, 0, newHeight * newCols);
|
||||||
|
for (let y = 0; y < newHeight; y++) {
|
||||||
|
const x = left * 4;
|
||||||
|
const row = new Uint8ClampedArray(
|
||||||
|
data.buffer,
|
||||||
|
(top + y) * cols + x,
|
||||||
|
newCols,
|
||||||
|
);
|
||||||
|
pixels.set(row, y * newCols);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ImageData(pixels.slice(), newWidth, newHeight);
|
||||||
|
}
|
||||||
13
src/features/preprocessors/crop/worker/missing-types.d.ts
vendored
Normal file
13
src/features/preprocessors/crop/worker/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../../../../missing-types.d.ts" />
|
||||||
21
src/features/preprocessors/flip/shared/meta.ts
Normal file
21
src/features/preprocessors/flip/shared/meta.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
export interface Options {
|
||||||
|
horizontal: boolean;
|
||||||
|
vertical: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: Options = {
|
||||||
|
horizontal: false,
|
||||||
|
vertical: false,
|
||||||
|
};
|
||||||
13
src/features/preprocessors/flip/shared/missing-types.d.ts
vendored
Normal file
13
src/features/preprocessors/flip/shared/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../../../../missing-types.d.ts" />
|
||||||
68
src/features/preprocessors/flip/worker/flip.ts
Normal file
68
src/features/preprocessors/flip/worker/flip.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
import { Options } from '../shared/meta';
|
||||||
|
|
||||||
|
export default async function flip(
|
||||||
|
data: ImageData,
|
||||||
|
opts: Options,
|
||||||
|
): Promise<ImageData> {
|
||||||
|
const { vertical, horizontal } = opts;
|
||||||
|
const source = data.data;
|
||||||
|
const len = source.length;
|
||||||
|
const pixels = new Uint8ClampedArray(len);
|
||||||
|
const { width, height } = data;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
let x = 0;
|
||||||
|
let y = 0;
|
||||||
|
const cols = width * 4;
|
||||||
|
while (i < len) {
|
||||||
|
let from = vertical ? (height - y) * cols + x * 4 : i;
|
||||||
|
if (horizontal) from = from - x * 4 + cols - x * 4; // todo: reduce
|
||||||
|
|
||||||
|
pixels[i++] = source[from++];
|
||||||
|
pixels[i++] = source[from++];
|
||||||
|
pixels[i++] = source[from++];
|
||||||
|
pixels[i++] = source[from];
|
||||||
|
|
||||||
|
if (++x === width) {
|
||||||
|
x = 0;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function swap(a: number, b: number) {
|
||||||
|
let tmp = pixels[a];
|
||||||
|
pixels[a] = pixels[b];
|
||||||
|
pixels[b] = tmp;
|
||||||
|
}
|
||||||
|
function swapRgba(a: number, b: number) {
|
||||||
|
swap(a, b);
|
||||||
|
swap(a+1, b+1);
|
||||||
|
swap(a+2, b+2);
|
||||||
|
swap(a+3, b+3);
|
||||||
|
}
|
||||||
|
const COLS = data.width * 4;
|
||||||
|
// for (let y = 0, y2 = (data.height - 1); y < y2; y+=4, y2-=4) {
|
||||||
|
for (let y = 0; y < data.height; y++) {
|
||||||
|
for (let x = 0, x2 = COLS - 4; x < x2; x+=4, x2-=4) {
|
||||||
|
const offsetX = y * COLS;
|
||||||
|
const offsetY = (opts.vertical ? (data.height - y) : y) * COLS;
|
||||||
|
const flippedX = opts.horizontal ? x2 : x;
|
||||||
|
swapRgba(offsetX + x, offsetY + x2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return new ImageData(pixels, data.width, data.height);
|
||||||
|
}
|
||||||
13
src/features/preprocessors/flip/worker/missing-types.d.ts
vendored
Normal file
13
src/features/preprocessors/flip/worker/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../../../../missing-types.d.ts" />
|
||||||
@@ -273,7 +273,7 @@ export default class Intro extends Component<Props, State> {
|
|||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
class={style.loadImgContent}
|
class={style.loadImgContent}
|
||||||
style={{ visibility: __PRERENDER__ ? 'hidden' : '' }}
|
style={{ visibility: __PRERENDER__ ? 'hidden' : undefined }}
|
||||||
>
|
>
|
||||||
<button class={style.loadBtn} onClick={this.onOpenClick}>
|
<button class={style.loadBtn} onClick={this.onOpenClick}>
|
||||||
<svg viewBox="0 0 24 24" class={style.loadIcon}>
|
<svg viewBox="0 0 24 24" class={style.loadIcon}>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ html {
|
|||||||
--less-light-gray: #bcbcbc;
|
--less-light-gray: #bcbcbc;
|
||||||
--medium-light-gray: #d1d1d1;
|
--medium-light-gray: #d1d1d1;
|
||||||
--light-gray: #eaeaea;
|
--light-gray: #eaeaea;
|
||||||
|
--med-gray: #555;
|
||||||
--dark-gray: #333;
|
--dark-gray: #333;
|
||||||
--dim-text: #343a3e;
|
--dim-text: #343a3e;
|
||||||
--dark-text: #142630;
|
--dark-text: #142630;
|
||||||
|
|||||||
BIN
src/sw/tiny.avif
Normal file
BIN
src/sw/tiny.avif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 303 B |
BIN
src/sw/tiny.webp
Normal file
BIN
src/sw/tiny.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 B |
@@ -1,5 +1,6 @@
|
|||||||
import { threads, simd } from 'wasm-feature-detect';
|
import { threads, simd } from 'wasm-feature-detect';
|
||||||
import { get } from 'idb-keyval';
|
import webpDataUrl from 'data-url:./tiny.webp';
|
||||||
|
import avifDataUrl from 'data-url:./tiny.avif';
|
||||||
|
|
||||||
// Give TypeScript the correct global.
|
// Give TypeScript the correct global.
|
||||||
declare var self: ServiceWorkerGlobalScope;
|
declare var self: ServiceWorkerGlobalScope;
|
||||||
@@ -104,7 +105,7 @@ import wp2EncMtWasm from 'url:codecs/wp2/enc/wp2_enc_mt.wasm';
|
|||||||
import * as wp2Enc from 'entry-data:codecs/wp2/enc/wp2_enc';
|
import * as wp2Enc from 'entry-data:codecs/wp2/enc/wp2_enc';
|
||||||
import wp2EncWasm from 'url:codecs/wp2/enc/wp2_enc.wasm';
|
import wp2EncWasm from 'url:codecs/wp2/enc/wp2_enc.wasm';
|
||||||
|
|
||||||
export const theRest = async () => {
|
export const theRest = (async () => {
|
||||||
const [
|
const [
|
||||||
supportsThreads,
|
supportsThreads,
|
||||||
supportsSimd,
|
supportsSimd,
|
||||||
@@ -113,8 +114,15 @@ export const theRest = async () => {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
threads(),
|
threads(),
|
||||||
simd(),
|
simd(),
|
||||||
get('supports-webp'),
|
...[webpDataUrl, avifDataUrl].map(async (dataUrl) => {
|
||||||
get('supports-avif'),
|
if (!self.createImageBitmap) return false;
|
||||||
|
const response = await fetch(dataUrl);
|
||||||
|
const blob = await response.blob();
|
||||||
|
return createImageBitmap(blob).then(
|
||||||
|
() => true,
|
||||||
|
() => false,
|
||||||
|
);
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@@ -202,4 +210,4 @@ export const theRest = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [...new Set(items)];
|
return [...new Set(items)];
|
||||||
};
|
})();
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export async function cacheBasics(cacheName: string) {
|
|||||||
|
|
||||||
export async function cacheAdditionalProcessors(cacheName: string) {
|
export async function cacheAdditionalProcessors(cacheName: string) {
|
||||||
const cache = await caches.open(cacheName);
|
const cache = await caches.open(cacheName);
|
||||||
return cache.addAll(await theRest());
|
return cache.addAll(await theRest);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextMessageResolveMap = new Map<string, (() => void)[]>();
|
const nextMessageResolveMap = new Map<string, (() => void)[]>();
|
||||||
|
|||||||
Reference in New Issue
Block a user