mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-14 09:39:15 +00:00
Compare commits
23 Commits
minimal-ru
...
libsquoosh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e04053e4c | ||
|
|
255dfa434a | ||
|
|
0bef05bcd4 | ||
|
|
00cfdafdf3 | ||
|
|
92f52319da | ||
|
|
023304803f | ||
|
|
6b08cd2355 | ||
|
|
db1a5138e6 | ||
|
|
a547491146 | ||
|
|
6e427f9208 | ||
|
|
0d35fbd349 | ||
|
|
4e901c714c | ||
|
|
b74788e036 | ||
|
|
14c3d190e9 | ||
|
|
eeaa19589e | ||
|
|
35b8c56f1a | ||
|
|
816d1f92fd | ||
|
|
4091f2efec | ||
|
|
821d14c6ab | ||
|
|
a72ca46531 | ||
|
|
fafcf97f0c | ||
|
|
de4eb9c8f7 | ||
|
|
16a53caa48 |
10
codecs/avif/enc/avif_enc.js
generated
10
codecs/avif/enc/avif_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.js
generated
2
codecs/avif/enc/avif_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.worker.js
generated
2
codecs/avif/enc/avif_enc_mt.worker.js
generated
@@ -1 +1 @@
|
|||||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){var imports={};imports["wasm"]=e.data.wasmModule;imports["wasmMemory"]=e.data.wasmMemory;imports["buffer"]=imports["wasmMemory"].buffer;imports["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./avif_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./avif_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ ENV LDFLAGS "${CFLAGS} \
|
|||||||
-s ALLOW_MEMORY_GROWTH=1 \
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
-s TEXTDECODER=2 \
|
-s TEXTDECODER=2 \
|
||||||
-s NODEJS_CATCH_EXIT=0 -s NODEJS_CATCH_REJECTION=0 \
|
-s NODEJS_CATCH_EXIT=0 -s NODEJS_CATCH_REJECTION=0 \
|
||||||
-s MINIMAL_RUNTIME=1 \
|
|
||||||
"
|
"
|
||||||
# Build and cache standard libraries with these flags + Embind.
|
# Build and cache standard libraries with these flags + Embind.
|
||||||
RUN emcc ${CXXFLAGS} ${LDFLAGS} --bind -xc++ /dev/null -o /dev/null
|
RUN emcc ${CXXFLAGS} ${LDFLAGS} --bind -xc++ /dev/null -o /dev/null
|
||||||
|
|||||||
10
codecs/imagequant/imagequant.js
generated
10
codecs/imagequant/imagequant.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/imagequant/imagequant_node.js
generated
11
codecs/imagequant/imagequant_node.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
CODEC_URL = https://github.com/libjxl/libjxl.git
|
CODEC_URL = https://github.com/libjxl/libjxl.git
|
||||||
CODEC_VERSION = 3f76321a7cbcf4f452894dc173eb7be60f508ecb
|
CODEC_VERSION = v0.5
|
||||||
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
|
||||||
|
|||||||
10
codecs/jxl/dec/jxl_dec.js
generated
10
codecs/jxl/dec/jxl_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/jxl/dec/jxl_node_dec.js
generated
11
codecs/jxl/dec/jxl_node_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -10,15 +10,13 @@ using namespace emscripten;
|
|||||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||||
|
|
||||||
struct JXLOptions {
|
struct JXLOptions {
|
||||||
// 1 = slowest
|
int effort;
|
||||||
// 7 = fastest
|
|
||||||
int speed;
|
|
||||||
float quality;
|
float quality;
|
||||||
bool progressive;
|
bool progressive;
|
||||||
int epf;
|
int epf;
|
||||||
int nearLossless;
|
|
||||||
bool lossyPalette;
|
bool lossyPalette;
|
||||||
size_t decodingSpeedTier;
|
size_t decodingSpeedTier;
|
||||||
|
float photonNoiseIso;
|
||||||
};
|
};
|
||||||
|
|
||||||
val encode(std::string image, int width, int height, JXLOptions options) {
|
val encode(std::string image, int width, int height, JXLOptions options) {
|
||||||
@@ -33,11 +31,14 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
|||||||
pool_ptr = &pool;
|
pool_ptr = &pool;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
cparams.epf = options.epf;
|
size_t st = 10 - options.effort;
|
||||||
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed);
|
cparams.speed_tier = jxl::SpeedTier(st);
|
||||||
cparams.decoding_speed_tier = options.decodingSpeedTier;
|
|
||||||
|
|
||||||
if (options.lossyPalette || options.nearLossless) {
|
cparams.epf = options.epf;
|
||||||
|
cparams.decoding_speed_tier = options.decodingSpeedTier;
|
||||||
|
cparams.photon_noise_iso = options.photonNoiseIso;
|
||||||
|
|
||||||
|
if (options.lossyPalette) {
|
||||||
cparams.lossy_palette = true;
|
cparams.lossy_palette = true;
|
||||||
cparams.palette_colors = 0;
|
cparams.palette_colors = 0;
|
||||||
cparams.options.predictor = jxl::Predictor::Zero;
|
cparams.options.predictor = jxl::Predictor::Zero;
|
||||||
@@ -106,12 +107,12 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
|||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(my_module) {
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
value_object<JXLOptions>("JXLOptions")
|
value_object<JXLOptions>("JXLOptions")
|
||||||
.field("speed", &JXLOptions::speed)
|
.field("effort", &JXLOptions::effort)
|
||||||
.field("quality", &JXLOptions::quality)
|
.field("quality", &JXLOptions::quality)
|
||||||
.field("progressive", &JXLOptions::progressive)
|
.field("progressive", &JXLOptions::progressive)
|
||||||
.field("nearLossless", &JXLOptions::nearLossless)
|
|
||||||
.field("lossyPalette", &JXLOptions::lossyPalette)
|
.field("lossyPalette", &JXLOptions::lossyPalette)
|
||||||
.field("decodingSpeedTier", &JXLOptions::decodingSpeedTier)
|
.field("decodingSpeedTier", &JXLOptions::decodingSpeedTier)
|
||||||
|
.field("photonNoiseIso", &JXLOptions::photonNoiseIso)
|
||||||
.field("epf", &JXLOptions::epf);
|
.field("epf", &JXLOptions::epf);
|
||||||
|
|
||||||
function("encode", &encode);
|
function("encode", &encode);
|
||||||
|
|||||||
4
codecs/jxl/enc/jxl_enc.d.ts
vendored
4
codecs/jxl/enc/jxl_enc.d.ts
vendored
@@ -1,11 +1,11 @@
|
|||||||
export interface EncodeOptions {
|
export interface EncodeOptions {
|
||||||
speed: number;
|
effort: number;
|
||||||
quality: number;
|
quality: number;
|
||||||
progressive: boolean;
|
progressive: boolean;
|
||||||
epf: number;
|
epf: number;
|
||||||
nearLossless: number;
|
|
||||||
lossyPalette: boolean;
|
lossyPalette: boolean;
|
||||||
decodingSpeedTier: number;
|
decodingSpeedTier: number;
|
||||||
|
photonNoiseIso: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JXLModule extends EmscriptenWasm.Module {
|
export interface JXLModule extends EmscriptenWasm.Module {
|
||||||
|
|||||||
10
codecs/jxl/enc/jxl_enc.js
generated
10
codecs/jxl/enc/jxl_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/jxl/enc/jxl_enc_mt.js
generated
2
codecs/jxl/enc/jxl_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/jxl/enc/jxl_enc_mt.worker.js
generated
2
codecs/jxl/enc/jxl_enc_mt.worker.js
generated
@@ -1 +1 @@
|
|||||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){var imports={};imports["wasm"]=e.data.wasmModule;imports["wasmMemory"]=e.data.wasmMemory;imports["buffer"]=imports["wasmMemory"].buffer;imports["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./jxl_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./jxl_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||||
|
|||||||
2
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
2
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/jxl/enc/jxl_enc_mt_simd.worker.js
generated
2
codecs/jxl/enc/jxl_enc_mt_simd.worker.js
generated
@@ -1 +1 @@
|
|||||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){var imports={};imports["wasm"]=e.data.wasmModule;imports["wasmMemory"]=e.data.wasmMemory;imports["buffer"]=imports["wasmMemory"].buffer;imports["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./jxl_enc_mt_simd.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./jxl_enc_mt_simd.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||||
|
|||||||
11
codecs/jxl/enc/jxl_node_enc.js
generated
11
codecs/jxl/enc/jxl_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/mozjpeg/dec/mozjpeg_node_dec.js
generated
11
codecs/mozjpeg/dec/mozjpeg_node_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -158,6 +158,11 @@ val encode(std::string image_in, int image_width, int image_height, MozJpegOptio
|
|||||||
if (!opts.auto_subsample && opts.color_space == JCS_YCbCr) {
|
if (!opts.auto_subsample && opts.color_space == JCS_YCbCr) {
|
||||||
cinfo.comp_info[0].h_samp_factor = opts.chroma_subsample;
|
cinfo.comp_info[0].h_samp_factor = opts.chroma_subsample;
|
||||||
cinfo.comp_info[0].v_samp_factor = opts.chroma_subsample;
|
cinfo.comp_info[0].v_samp_factor = opts.chroma_subsample;
|
||||||
|
|
||||||
|
if (opts.chroma_subsample > 2) {
|
||||||
|
// Otherwise encoding fails.
|
||||||
|
jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opts.baseline && opts.progressive) {
|
if (!opts.baseline && opts.progressive) {
|
||||||
|
|||||||
10
codecs/mozjpeg/enc/mozjpeg_enc.js
generated
10
codecs/mozjpeg/enc/mozjpeg_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/mozjpeg/enc/mozjpeg_node_enc.js
generated
11
codecs/mozjpeg/enc/mozjpeg_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/visdif/visdif.js
generated
11
codecs/visdif/visdif.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
10
codecs/webp/dec/webp_dec.js
generated
10
codecs/webp/dec/webp_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/webp/dec/webp_node_dec.js
generated
11
codecs/webp/dec/webp_node_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
10
codecs/webp/enc/webp_enc.js
generated
10
codecs/webp/enc/webp_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
10
codecs/webp/enc/webp_enc_simd.js
generated
10
codecs/webp/enc/webp_enc_simd.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/webp/enc/webp_node_enc.js
generated
11
codecs/webp/enc/webp_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
10
codecs/wp2/dec/wp2_dec.js
generated
10
codecs/wp2/dec/wp2_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
11
codecs/wp2/dec/wp2_node_dec.js
generated
11
codecs/wp2/dec/wp2_node_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
10
codecs/wp2/enc/wp2_enc.js
generated
10
codecs/wp2/enc/wp2_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/wp2/enc/wp2_enc_mt.js
generated
2
codecs/wp2/enc/wp2_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/wp2/enc/wp2_enc_mt.worker.js
generated
2
codecs/wp2/enc/wp2_enc_mt.worker.js
generated
@@ -1 +1 @@
|
|||||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){var imports={};imports["wasm"]=e.data.wasmModule;imports["wasmMemory"]=e.data.wasmMemory;imports["buffer"]=imports["wasmMemory"].buffer;imports["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./wp2_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./wp2_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||||
|
|||||||
2
codecs/wp2/enc/wp2_enc_mt_simd.js
generated
2
codecs/wp2/enc/wp2_enc_mt_simd.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/wp2/enc/wp2_enc_mt_simd.worker.js
generated
2
codecs/wp2/enc/wp2_enc_mt_simd.worker.js
generated
@@ -1 +1 @@
|
|||||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){var imports={};imports["wasm"]=e.data.wasmModule;imports["wasmMemory"]=e.data.wasmMemory;imports["buffer"]=imports["wasmMemory"].buffer;imports["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./wp2_enc_mt_simd.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);Module["PThread"].threadExit(result)}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./wp2_enc_mt_simd.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||||
|
|||||||
11
codecs/wp2/enc/wp2_node_enc.js
generated
11
codecs/wp2/enc/wp2_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -20,6 +20,7 @@ const allSrcPlaceholder = 'CLIENT_BUNDLE_PLUGIN_ALL_SRC';
|
|||||||
|
|
||||||
export function getDependencies(clientOutput, item) {
|
export function getDependencies(clientOutput, item) {
|
||||||
const crawlDependencies = new Set([item.fileName]);
|
const crawlDependencies = new Set([item.fileName]);
|
||||||
|
const referencedFiles = new Set();
|
||||||
|
|
||||||
for (const fileName of crawlDependencies) {
|
for (const fileName of crawlDependencies) {
|
||||||
const chunk = clientOutput.find((v) => v.fileName === fileName);
|
const chunk = clientOutput.find((v) => v.fileName === fileName);
|
||||||
@@ -27,11 +28,24 @@ export function getDependencies(clientOutput, item) {
|
|||||||
for (const dep of chunk.imports) {
|
for (const dep of chunk.imports) {
|
||||||
crawlDependencies.add(dep);
|
crawlDependencies.add(dep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const dep of chunk.referencedFiles) {
|
||||||
|
referencedFiles.add(dep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't add self as dependency
|
// Don't add self as dependency
|
||||||
crawlDependencies.delete(item.fileName);
|
crawlDependencies.delete(item.fileName);
|
||||||
|
|
||||||
|
// Merge referencedFiles as regular deps. They need to be in the same Set as
|
||||||
|
// some JS files might appear in both lists and need to be deduped too.
|
||||||
|
//
|
||||||
|
// Didn't do this as part of the main loop since their `chunk` can't have
|
||||||
|
// nested deps and sometimes might be missing altogether, depending on type.
|
||||||
|
for (const dep of referencedFiles) {
|
||||||
|
crawlDependencies.add(dep);
|
||||||
|
}
|
||||||
|
|
||||||
return [...crawlDependencies];
|
return [...crawlDependencies];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +153,9 @@ export default function (inputOptions, outputOptions, resolveFileUrl) {
|
|||||||
if (property.startsWith(allSrcPlaceholder)) {
|
if (property.startsWith(allSrcPlaceholder)) {
|
||||||
const allModules = [
|
const allModules = [
|
||||||
clientEntry,
|
clientEntry,
|
||||||
...dependencies.map((name) =>
|
...dependencies
|
||||||
clientOutput.find((item) => item.fileName === name),
|
.map((name) => clientOutput.find((item) => item.fileName === name))
|
||||||
),
|
.filter((item) => item.code),
|
||||||
];
|
];
|
||||||
|
|
||||||
const inlineDefines = [
|
const inlineDefines = [
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ const prefix = 'entry-data:';
|
|||||||
const mainNamePlaceholder = 'ENTRY_DATA_PLUGIN_MAIN_NAME';
|
const mainNamePlaceholder = 'ENTRY_DATA_PLUGIN_MAIN_NAME';
|
||||||
const dependenciesPlaceholder = 'ENTRY_DATA_PLUGIN_DEPS';
|
const dependenciesPlaceholder = 'ENTRY_DATA_PLUGIN_DEPS';
|
||||||
const placeholderRe = /(ENTRY_DATA_PLUGIN_(?:MAIN_NAME|DEPS))(\d+)/g;
|
const placeholderRe = /(ENTRY_DATA_PLUGIN_(?:MAIN_NAME|DEPS))(\d+)/g;
|
||||||
const filenamePrefix = 'static/';
|
|
||||||
|
/** @param {string} fileName */
|
||||||
|
export function fileNameToURL(fileName) {
|
||||||
|
return fileName.replace(/^static\//, '/');
|
||||||
|
}
|
||||||
|
|
||||||
export default function entryDataPlugin() {
|
export default function entryDataPlugin() {
|
||||||
/** @type {string} */
|
/** @type {number} */
|
||||||
let exportCounter;
|
let exportCounter;
|
||||||
/** @type {Map<number, string>} */
|
/** @type {Map<number, string>} */
|
||||||
let counterToIdMap;
|
let counterToIdMap;
|
||||||
@@ -69,15 +73,11 @@ export default function entryDataPlugin() {
|
|||||||
if (!chunk) throw Error(`Cannot find ${id}`);
|
if (!chunk) throw Error(`Cannot find ${id}`);
|
||||||
|
|
||||||
if (placeholder === mainNamePlaceholder) {
|
if (placeholder === mainNamePlaceholder) {
|
||||||
return JSON.stringify(
|
return JSON.stringify(fileNameToURL(chunk.fileName));
|
||||||
chunk.fileName.slice(filenamePrefix.length),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify(
|
return JSON.stringify(
|
||||||
getDependencies(chunks, chunk).map((item) =>
|
getDependencies(chunks, chunk).map((filename) => fileNameToURL(filename)),
|
||||||
item.slice(filenamePrefix.length),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
2248
libsquoosh/package-lock.json
generated
2248
libsquoosh/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,8 @@
|
|||||||
"/build/*"
|
"/build/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c"
|
"build": "rollup -c",
|
||||||
|
"test": "uvu -r ts-node/register test"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
||||||
@@ -34,7 +35,9 @@
|
|||||||
"@types/node": "^15.6.1",
|
"@types/node": "^15.6.1",
|
||||||
"rollup": "^2.46.0",
|
"rollup": "^2.46.0",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
|
"ts-node": "^10.2.1",
|
||||||
"typescript": "^4.1.3",
|
"typescript": "^4.1.3",
|
||||||
|
"uvu": "^0.5.1",
|
||||||
"which": "^2.0.2"
|
"which": "^2.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { builtinModules } from 'module';
|
|||||||
|
|
||||||
/** @type {import('rollup').RollupOptions} */
|
/** @type {import('rollup').RollupOptions} */
|
||||||
export default {
|
export default {
|
||||||
input: 'src/index.js',
|
input: 'src/index.ts',
|
||||||
output: {
|
output: {
|
||||||
dir: 'build',
|
dir: 'build',
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import { cpus } from 'os';
|
|||||||
hardwareConcurrency: cpus().length,
|
hardwareConcurrency: cpus().length,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface DecodeModule extends EmscriptenWasm.Module {
|
||||||
|
decode: (data: Uint8Array) => ImageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecodeModuleFactory = EmscriptenWasm.ModuleFactory<DecodeModule>;
|
||||||
|
|
||||||
interface RotateModuleInstance {
|
interface RotateModuleInstance {
|
||||||
exports: {
|
exports: {
|
||||||
memory: WebAssembly.Memory;
|
memory: WebAssembly.Memory;
|
||||||
@@ -33,7 +39,7 @@ interface ResizeInstantiateOptions {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// Needed for being able to use ImageData as type in codec types
|
// Needed for being able to use ImageData as type in codec types
|
||||||
type ImageData = typeof import('./image_data.js');
|
type ImageData = import('./image_data.js').default;
|
||||||
// Needed for being able to assign to `globalThis.ImageData`
|
// Needed for being able to assign to `globalThis.ImageData`
|
||||||
var ImageData: ImageData['constructor'];
|
var ImageData: ImageData['constructor'];
|
||||||
}
|
}
|
||||||
@@ -41,18 +47,21 @@ declare global {
|
|||||||
import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js';
|
import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js';
|
||||||
|
|
||||||
// MozJPEG
|
// MozJPEG
|
||||||
|
import type { MozJPEGModule as MozJPEGEncodeModule } from '../../codecs/mozjpeg/enc/mozjpeg_enc';
|
||||||
import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js';
|
import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js';
|
||||||
import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm';
|
import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm';
|
||||||
import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js';
|
import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js';
|
||||||
import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm';
|
import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm';
|
||||||
|
|
||||||
// WebP
|
// WebP
|
||||||
|
import type { WebPModule as WebPEncodeModule } from '../../codecs/webp/enc/webp_enc';
|
||||||
import webpEnc from '../../codecs/webp/enc/webp_node_enc.js';
|
import webpEnc from '../../codecs/webp/enc/webp_node_enc.js';
|
||||||
import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm';
|
import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm';
|
||||||
import webpDec from '../../codecs/webp/dec/webp_node_dec.js';
|
import webpDec from '../../codecs/webp/dec/webp_node_dec.js';
|
||||||
import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm';
|
import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm';
|
||||||
|
|
||||||
// AVIF
|
// AVIF
|
||||||
|
import type { AVIFModule as AVIFEncodeModule } from '../../codecs/avif/enc/avif_enc';
|
||||||
import avifEnc from '../../codecs/avif/enc/avif_node_enc.js';
|
import avifEnc from '../../codecs/avif/enc/avif_node_enc.js';
|
||||||
import avifEncWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc.wasm';
|
import avifEncWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc.wasm';
|
||||||
import avifEncMt from '../../codecs/avif/enc/avif_node_enc_mt.js';
|
import avifEncMt from '../../codecs/avif/enc/avif_node_enc_mt.js';
|
||||||
@@ -62,12 +71,14 @@ import avifDec from '../../codecs/avif/dec/avif_node_dec.js';
|
|||||||
import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm';
|
import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm';
|
||||||
|
|
||||||
// JXL
|
// JXL
|
||||||
|
import type { JXLModule as JXLEncodeModule } from '../../codecs/jxl/enc/jxl_enc';
|
||||||
import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js';
|
import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js';
|
||||||
import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm';
|
import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm';
|
||||||
import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js';
|
import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js';
|
||||||
import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm';
|
import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm';
|
||||||
|
|
||||||
// WP2
|
// WP2
|
||||||
|
import type { WP2Module as WP2EncodeModule } from '../../codecs/wp2/enc/wp2_enc';
|
||||||
import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js';
|
import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js';
|
||||||
import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm';
|
import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm';
|
||||||
import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js';
|
import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js';
|
||||||
@@ -257,15 +268,20 @@ export const preprocessors = {
|
|||||||
numRotations: 0,
|
numRotations: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
export const codecs = {
|
export const codecs = {
|
||||||
mozjpeg: {
|
mozjpeg: {
|
||||||
name: 'MozJPEG',
|
name: 'MozJPEG',
|
||||||
extension: 'jpg',
|
extension: 'jpg',
|
||||||
detectors: [/^\xFF\xD8\xFF/],
|
detectors: [/^\xFF\xD8\xFF/],
|
||||||
dec: () => instantiateEmscriptenWasm(mozDec, mozDecWasm),
|
dec: () =>
|
||||||
enc: () => instantiateEmscriptenWasm(mozEnc, mozEncWasm),
|
instantiateEmscriptenWasm(mozDec as DecodeModuleFactory, mozDecWasm),
|
||||||
|
enc: () =>
|
||||||
|
instantiateEmscriptenWasm(
|
||||||
|
mozEnc as EmscriptenWasm.ModuleFactory<MozJPEGEncodeModule>,
|
||||||
|
mozEncWasm,
|
||||||
|
),
|
||||||
defaultEncoderOptions: {
|
defaultEncoderOptions: {
|
||||||
quality: 75,
|
quality: 75,
|
||||||
baseline: false,
|
baseline: false,
|
||||||
@@ -294,8 +310,13 @@ export const codecs = {
|
|||||||
name: 'WebP',
|
name: 'WebP',
|
||||||
extension: 'webp',
|
extension: 'webp',
|
||||||
detectors: [/^RIFF....WEBPVP8[LX ]/s],
|
detectors: [/^RIFF....WEBPVP8[LX ]/s],
|
||||||
dec: () => instantiateEmscriptenWasm(webpDec, webpDecWasm),
|
dec: () =>
|
||||||
enc: () => instantiateEmscriptenWasm(webpEnc, webpEncWasm),
|
instantiateEmscriptenWasm(webpDec as DecodeModuleFactory, webpDecWasm),
|
||||||
|
enc: () =>
|
||||||
|
instantiateEmscriptenWasm(
|
||||||
|
webpEnc as EmscriptenWasm.ModuleFactory<WebPEncodeModule>,
|
||||||
|
webpEncWasm,
|
||||||
|
),
|
||||||
defaultEncoderOptions: {
|
defaultEncoderOptions: {
|
||||||
quality: 75,
|
quality: 75,
|
||||||
target_size: 0,
|
target_size: 0,
|
||||||
@@ -335,16 +356,20 @@ export const codecs = {
|
|||||||
name: 'AVIF',
|
name: 'AVIF',
|
||||||
extension: 'avif',
|
extension: 'avif',
|
||||||
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
|
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
|
||||||
dec: () => instantiateEmscriptenWasm(avifDec, avifDecWasm),
|
dec: () =>
|
||||||
|
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm),
|
||||||
enc: async () => {
|
enc: async () => {
|
||||||
if (await threads()) {
|
if (await threads()) {
|
||||||
return instantiateEmscriptenWasm(
|
return instantiateEmscriptenWasm(
|
||||||
avifEncMt,
|
avifEncMt as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
|
||||||
avifEncMtWasm,
|
avifEncMtWasm,
|
||||||
avifEncMtWorker,
|
avifEncMtWorker,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return instantiateEmscriptenWasm(avifEnc, avifEncWasm);
|
return instantiateEmscriptenWasm(
|
||||||
|
avifEnc as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
|
||||||
|
avifEncWasm,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
defaultEncoderOptions: {
|
defaultEncoderOptions: {
|
||||||
cqLevel: 33,
|
cqLevel: 33,
|
||||||
@@ -368,8 +393,13 @@ export const codecs = {
|
|||||||
name: 'JPEG-XL',
|
name: 'JPEG-XL',
|
||||||
extension: 'jxl',
|
extension: 'jxl',
|
||||||
detectors: [/^\xff\x0a/],
|
detectors: [/^\xff\x0a/],
|
||||||
dec: () => instantiateEmscriptenWasm(jxlDec, jxlDecWasm),
|
dec: () =>
|
||||||
enc: () => instantiateEmscriptenWasm(jxlEnc, jxlEncWasm),
|
instantiateEmscriptenWasm(jxlDec as DecodeModuleFactory, jxlDecWasm),
|
||||||
|
enc: () =>
|
||||||
|
instantiateEmscriptenWasm(
|
||||||
|
jxlEnc as EmscriptenWasm.ModuleFactory<JXLEncodeModule>,
|
||||||
|
jxlEncWasm,
|
||||||
|
),
|
||||||
defaultEncoderOptions: {
|
defaultEncoderOptions: {
|
||||||
speed: 4,
|
speed: 4,
|
||||||
quality: 75,
|
quality: 75,
|
||||||
@@ -389,8 +419,13 @@ export const codecs = {
|
|||||||
name: 'WebP2',
|
name: 'WebP2',
|
||||||
extension: 'wp2',
|
extension: 'wp2',
|
||||||
detectors: [/^\xF4\xFF\x6F/],
|
detectors: [/^\xF4\xFF\x6F/],
|
||||||
dec: () => instantiateEmscriptenWasm(wp2Dec, wp2DecWasm),
|
dec: () =>
|
||||||
enc: () => instantiateEmscriptenWasm(wp2Enc, wp2EncWasm),
|
instantiateEmscriptenWasm(wp2Dec as DecodeModuleFactory, wp2DecWasm),
|
||||||
|
enc: () =>
|
||||||
|
instantiateEmscriptenWasm(
|
||||||
|
wp2Enc as EmscriptenWasm.ModuleFactory<WP2EncodeModule>,
|
||||||
|
wp2EncWasm,
|
||||||
|
),
|
||||||
defaultEncoderOptions: {
|
defaultEncoderOptions: {
|
||||||
quality: 75,
|
quality: 75,
|
||||||
alpha_quality: 75,
|
alpha_quality: 75,
|
||||||
@@ -421,7 +456,7 @@ export const codecs = {
|
|||||||
await oxipngPromise;
|
await oxipngPromise;
|
||||||
return {
|
return {
|
||||||
encode: (
|
encode: (
|
||||||
buffer: Uint8Array,
|
buffer: Uint8ClampedArray | ArrayBuffer,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
opts: { level: number },
|
opts: { level: number },
|
||||||
@@ -444,4 +479,4 @@ export const codecs = {
|
|||||||
max: 1,
|
max: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
} as const;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function instantiateEmscriptenWasm<T extends EmscriptenWasm.Module>(
|
|||||||
// These will have changed in the bundling process and
|
// These will have changed in the bundling process and
|
||||||
// we need to inject them here.
|
// we need to inject them here.
|
||||||
if (requestPath.endsWith('.wasm')) return pathify(path);
|
if (requestPath.endsWith('.wasm')) return pathify(path);
|
||||||
if (requestPath.endsWith('.worker.js')) return new URL(workerJS).pathname;
|
if (requestPath.endsWith('.worker.js')) return pathify(workerJS);
|
||||||
return requestPath;
|
return requestPath;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ import { promises as fsp } from 'fs';
|
|||||||
import { codecs as encoders, preprocessors } from './codecs.js';
|
import { codecs as encoders, preprocessors } from './codecs.js';
|
||||||
import WorkerPool from './worker_pool.js';
|
import WorkerPool from './worker_pool.js';
|
||||||
import { autoOptimize } from './auto-optimizer.js';
|
import { autoOptimize } from './auto-optimizer.js';
|
||||||
|
import type ImageData from './image_data';
|
||||||
|
|
||||||
export { ImagePool, encoders, preprocessors };
|
export { ImagePool, encoders, preprocessors };
|
||||||
|
type EncoderKey = keyof typeof encoders;
|
||||||
|
type PreprocessorKey = keyof typeof preprocessors;
|
||||||
|
type FileLike = Buffer | ArrayBuffer | string | ArrayBufferView;
|
||||||
|
|
||||||
async function decodeFile({ file }) {
|
async function decodeFile({
|
||||||
|
file,
|
||||||
|
}: {
|
||||||
|
file: FileLike;
|
||||||
|
}): Promise<{ bitmap: ImageData; size: number }> {
|
||||||
let buffer;
|
let buffer;
|
||||||
if (ArrayBuffer.isView(file)) {
|
if (ArrayBuffer.isView(file)) {
|
||||||
buffer = Buffer.from(file.buffer);
|
buffer = Buffer.from(file.buffer);
|
||||||
@@ -16,8 +24,9 @@ async function decodeFile({ file }) {
|
|||||||
} else if (file instanceof ArrayBuffer) {
|
} else if (file instanceof ArrayBuffer) {
|
||||||
buffer = Buffer.from(file);
|
buffer = Buffer.from(file);
|
||||||
file = 'Binary blob';
|
file = 'Binary blob';
|
||||||
} else if (file instanceof Buffer) {
|
} else if ((file as unknown) instanceof Buffer) {
|
||||||
buffer = file;
|
// TODO: Check why we need type assertions here.
|
||||||
|
buffer = (file as unknown) as Buffer;
|
||||||
file = 'Binary blob';
|
file = 'Binary blob';
|
||||||
} else if (typeof file === 'string') {
|
} else if (typeof file === 'string') {
|
||||||
buffer = await fsp.readFile(file);
|
buffer = await fsp.readFile(file);
|
||||||
@@ -28,23 +37,33 @@ async function decodeFile({ file }) {
|
|||||||
const firstChunkString = Array.from(firstChunk)
|
const firstChunkString = Array.from(firstChunk)
|
||||||
.map((v) => String.fromCodePoint(v))
|
.map((v) => String.fromCodePoint(v))
|
||||||
.join('');
|
.join('');
|
||||||
const key = Object.entries(encoders).find(([name, { detectors }]) =>
|
const key = Object.entries(encoders).find(([_name, { detectors }]) =>
|
||||||
detectors.some((detector) => detector.exec(firstChunkString)),
|
detectors.some((detector) => detector.exec(firstChunkString)),
|
||||||
)?.[0];
|
)?.[0] as EncoderKey | undefined;
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw Error(`${file} has an unsupported format`);
|
throw Error(`${file} has an unsupported format`);
|
||||||
}
|
}
|
||||||
const rgba = (await encoders[key].dec()).decode(new Uint8Array(buffer));
|
const encoder = encoders[key];
|
||||||
|
const mod = await encoder.dec();
|
||||||
|
const rgba = mod.decode(new Uint8Array(buffer));
|
||||||
return {
|
return {
|
||||||
bitmap: rgba,
|
bitmap: rgba,
|
||||||
size: buffer.length,
|
size: buffer.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preprocessImage({ preprocessorName, options, image }) {
|
async function preprocessImage({
|
||||||
|
preprocessorName,
|
||||||
|
options,
|
||||||
|
image,
|
||||||
|
}: {
|
||||||
|
preprocessorName: PreprocessorKey;
|
||||||
|
options: any;
|
||||||
|
image: { bitmap: ImageData };
|
||||||
|
}) {
|
||||||
const preprocessor = await preprocessors[preprocessorName].instantiate();
|
const preprocessor = await preprocessors[preprocessorName].instantiate();
|
||||||
image.bitmap = await preprocessor(
|
image.bitmap = await preprocessor(
|
||||||
image.bitmap.data,
|
Uint8Array.from(image.bitmap.data),
|
||||||
image.bitmap.width,
|
image.bitmap.width,
|
||||||
image.bitmap.height,
|
image.bitmap.height,
|
||||||
options,
|
options,
|
||||||
@@ -58,26 +77,39 @@ async function encodeImage({
|
|||||||
encConfig,
|
encConfig,
|
||||||
optimizerButteraugliTarget,
|
optimizerButteraugliTarget,
|
||||||
maxOptimizerRounds,
|
maxOptimizerRounds,
|
||||||
|
}: {
|
||||||
|
bitmap: ImageData;
|
||||||
|
encName: EncoderKey;
|
||||||
|
encConfig: any;
|
||||||
|
optimizerButteraugliTarget: number;
|
||||||
|
maxOptimizerRounds: number;
|
||||||
}) {
|
}) {
|
||||||
let binary;
|
let binary: Uint8Array;
|
||||||
let optionsUsed = encConfig;
|
let optionsUsed = encConfig;
|
||||||
const encoder = await encoders[encName].enc();
|
const encoder = await encoders[encName].enc();
|
||||||
if (encConfig === 'auto') {
|
if (encConfig === 'auto') {
|
||||||
const optionToOptimize = encoders[encName].autoOptimize.option;
|
const optionToOptimize = encoders[encName].autoOptimize.option;
|
||||||
const decoder = await encoders[encName].dec();
|
const decoder = await encoders[encName].dec();
|
||||||
const encode = (bitmapIn, quality) =>
|
const encode = (bitmapIn: ImageData, quality: number) =>
|
||||||
encoder.encode(
|
encoder.encode(
|
||||||
bitmapIn.data,
|
bitmapIn.data,
|
||||||
bitmapIn.width,
|
bitmapIn.width,
|
||||||
bitmapIn.height,
|
bitmapIn.height,
|
||||||
Object.assign({}, encoders[encName].defaultEncoderOptions, {
|
Object.assign({}, encoders[encName].defaultEncoderOptions as any, {
|
||||||
[optionToOptimize]: quality,
|
[optionToOptimize]: quality,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const decode = (binary) => decoder.decode(binary);
|
const decode = (binary: Uint8Array) => decoder.decode(binary);
|
||||||
|
const nonNullEncode = (bitmap: ImageData, quality: number): Uint8Array => {
|
||||||
|
const result = encode(bitmap, quality);
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('There was an error while encoding');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
const { binary: optimizedBinary, quality } = await autoOptimize(
|
const { binary: optimizedBinary, quality } = await autoOptimize(
|
||||||
bitmapIn,
|
bitmapIn,
|
||||||
encode,
|
nonNullEncode,
|
||||||
decode,
|
decode,
|
||||||
{
|
{
|
||||||
min: encoders[encName].autoOptimize.min,
|
min: encoders[encName].autoOptimize.min,
|
||||||
@@ -92,12 +124,18 @@ async function encodeImage({
|
|||||||
[optionToOptimize]: Math.round(quality * 10000) / 10000,
|
[optionToOptimize]: Math.round(quality * 10000) / 10000,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
binary = encoder.encode(
|
const result = encoder.encode(
|
||||||
bitmapIn.data.buffer,
|
bitmapIn.data.buffer,
|
||||||
bitmapIn.width,
|
bitmapIn.width,
|
||||||
bitmapIn.height,
|
bitmapIn.height,
|
||||||
encConfig,
|
encConfig,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('There was an error while encoding');
|
||||||
|
}
|
||||||
|
|
||||||
|
binary = result;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
optionsUsed,
|
optionsUsed,
|
||||||
@@ -107,10 +145,15 @@ async function encodeImage({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// both decoding and encoding go through the worker pool
|
type EncodeParams = { operation: 'encode' } & Parameters<typeof encodeImage>[0];
|
||||||
function handleJob(params) {
|
type DecodeParams = { operation: 'decode' } & Parameters<typeof decodeFile>[0];
|
||||||
const { operation } = params;
|
type PreprocessParams = { operation: 'preprocess' } & Parameters<
|
||||||
switch (operation) {
|
typeof preprocessImage
|
||||||
|
>[0];
|
||||||
|
type JobMessage = EncodeParams | DecodeParams | PreprocessParams;
|
||||||
|
|
||||||
|
function handleJob(params: JobMessage) {
|
||||||
|
switch (params.operation) {
|
||||||
case 'encode':
|
case 'encode':
|
||||||
return encodeImage(params);
|
return encodeImage(params);
|
||||||
case 'decode':
|
case 'decode':
|
||||||
@@ -118,7 +161,7 @@ function handleJob(params) {
|
|||||||
case 'preprocess':
|
case 'preprocess':
|
||||||
return preprocessImage(params);
|
return preprocessImage(params);
|
||||||
default:
|
default:
|
||||||
throw Error(`Invalid job "${operation}"`);
|
throw Error(`Invalid job "${(params as any).operation}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +169,12 @@ function handleJob(params) {
|
|||||||
* Represents an ingested image.
|
* Represents an ingested image.
|
||||||
*/
|
*/
|
||||||
class Image {
|
class Image {
|
||||||
constructor(workerPool, file) {
|
public file: FileLike;
|
||||||
|
public workerPool: WorkerPool<JobMessage, any>;
|
||||||
|
public decoded: Promise<{ bitmap: ImageData }>;
|
||||||
|
public encodedWith: { [key: string]: any };
|
||||||
|
|
||||||
|
constructor(workerPool: WorkerPool<JobMessage, any>, file: FileLike) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.workerPool = workerPool;
|
this.workerPool = workerPool;
|
||||||
this.decoded = workerPool.dispatchJob({ operation: 'decode', file });
|
this.decoded = workerPool.dispatchJob({ operation: 'decode', file });
|
||||||
@@ -143,14 +191,15 @@ class Image {
|
|||||||
if (!Object.keys(preprocessors).includes(name)) {
|
if (!Object.keys(preprocessors).includes(name)) {
|
||||||
throw Error(`Invalid preprocessor "${name}"`);
|
throw Error(`Invalid preprocessor "${name}"`);
|
||||||
}
|
}
|
||||||
|
const preprocessorName = name as PreprocessorKey;
|
||||||
const preprocessorOptions = Object.assign(
|
const preprocessorOptions = Object.assign(
|
||||||
{},
|
{},
|
||||||
preprocessors[name].defaultOptions,
|
preprocessors[preprocessorName].defaultOptions,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
this.decoded = this.workerPool.dispatchJob({
|
this.decoded = this.workerPool.dispatchJob({
|
||||||
operation: 'preprocess',
|
operation: 'preprocess',
|
||||||
preprocessorName: name,
|
preprocessorName,
|
||||||
image: await this.decoded,
|
image: await this.decoded,
|
||||||
options: preprocessorOptions,
|
options: preprocessorOptions,
|
||||||
});
|
});
|
||||||
@@ -161,14 +210,22 @@ class Image {
|
|||||||
/**
|
/**
|
||||||
* Define one or several encoders to use on the image.
|
* Define one or several encoders to use on the image.
|
||||||
* @param {object} encodeOptions - An object with encoders to use, and their settings.
|
* @param {object} encodeOptions - An object with encoders to use, and their settings.
|
||||||
* @returns {Promise<undefined>} - A promise that resolves when the image has been encoded with all the specified encoders.
|
* @returns {Promise<void>} - A promise that resolves when the image has been encoded with all the specified encoders.
|
||||||
*/
|
*/
|
||||||
async encode(encodeOptions = {}) {
|
async encode(
|
||||||
|
encodeOptions: {
|
||||||
|
optimizerButteraugliTarget?: number;
|
||||||
|
maxOptimizerRounds?: number;
|
||||||
|
} & {
|
||||||
|
[key in EncoderKey]?: any; // any is okay for now
|
||||||
|
} = {},
|
||||||
|
): Promise<void> {
|
||||||
const { bitmap } = await this.decoded;
|
const { bitmap } = await this.decoded;
|
||||||
for (const [encName, options] of Object.entries(encodeOptions)) {
|
for (const [name, options] of Object.entries(encodeOptions)) {
|
||||||
if (!Object.keys(encoders).includes(encName)) {
|
if (!Object.keys(encoders).includes(name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const encName = name as EncoderKey;
|
||||||
const encRef = encoders[encName];
|
const encRef = encoders[encName];
|
||||||
const encConfig =
|
const encConfig =
|
||||||
typeof options === 'string'
|
typeof options === 'string'
|
||||||
@@ -193,28 +250,30 @@ class Image {
|
|||||||
* A pool where images can be ingested and squooshed.
|
* A pool where images can be ingested and squooshed.
|
||||||
*/
|
*/
|
||||||
class ImagePool {
|
class ImagePool {
|
||||||
|
public workerPool: WorkerPool<JobMessage, any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new pool.
|
* Create a new pool.
|
||||||
* @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system.
|
* @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system.
|
||||||
*/
|
*/
|
||||||
constructor(threads) {
|
constructor(threads: number) {
|
||||||
this.workerPool = new WorkerPool(threads || cpus().length, __filename);
|
this.workerPool = new WorkerPool(threads || cpus().length, __filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ingest an image into the image pool.
|
* Ingest an image into the image pool.
|
||||||
* @param {string | Buffer | URL | object} image - The image or path to the image that should be ingested and decoded.
|
* @param {FileLike} image - The image or path to the image that should be ingested and decoded.
|
||||||
* @returns {Image} - A custom class reference to the decoded image.
|
* @returns {Image} - A custom class reference to the decoded image.
|
||||||
*/
|
*/
|
||||||
ingestImage(image) {
|
ingestImage(image: FileLike): Image {
|
||||||
return new Image(this.workerPool, image);
|
return new Image(this.workerPool, image);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the underlying image processing pipeline. The already processed images will still be there, but no new processing can start.
|
* Closes the underlying image processing pipeline. The already processed images will still be there, but no new processing can start.
|
||||||
* @returns {Promise<undefined>} - A promise that resolves when the underlying pipeline has closed.
|
* @returns {Promise<void>} - A promise that resolves when the underlying pipeline has closed.
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close(): Promise<void> {
|
||||||
await this.workerPool.join();
|
await this.workerPool.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,26 +7,19 @@ function uuid() {
|
|||||||
).join('');
|
).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function jobPromise(worker, msg) {
|
interface Job<I> {
|
||||||
return new Promise((resolve, reject) => {
|
msg: I;
|
||||||
const id = uuid();
|
resolve: Function;
|
||||||
worker.postMessage({ msg, id });
|
reject: Function;
|
||||||
worker.on('message', function f({ error, result, id: rid }) {
|
|
||||||
if (rid !== id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
worker.off('message', f);
|
|
||||||
resolve(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WorkerPool {
|
export default class WorkerPool<I, O> {
|
||||||
constructor(numWorkers, workerFile) {
|
public numWorkers: number;
|
||||||
|
public jobQueue: TransformStream<Job<I>, Job<I>>;
|
||||||
|
public workerQueue: TransformStream<Worker, Worker>;
|
||||||
|
public done: Promise<void>;
|
||||||
|
|
||||||
|
constructor(numWorkers: number, workerFile: string) {
|
||||||
this.numWorkers = numWorkers;
|
this.numWorkers = numWorkers;
|
||||||
this.jobQueue = new TransformStream();
|
this.jobQueue = new TransformStream();
|
||||||
this.workerQueue = new TransformStream();
|
this.workerQueue = new TransformStream();
|
||||||
@@ -48,9 +41,14 @@ export default class WorkerPool {
|
|||||||
await this._terminateAll();
|
await this._terminateAll();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Reader did not return any value');
|
||||||
|
}
|
||||||
|
|
||||||
const { msg, resolve, reject } = value;
|
const { msg, resolve, reject } = value;
|
||||||
const worker = await this._nextWorker();
|
const worker = await this._nextWorker();
|
||||||
jobPromise(worker, msg)
|
this.jobPromise(worker, msg)
|
||||||
.then((result) => resolve(result))
|
.then((result) => resolve(result))
|
||||||
.catch((reason) => reject(reason))
|
.catch((reason) => reject(reason))
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -66,6 +64,10 @@ export default class WorkerPool {
|
|||||||
const reader = this.workerQueue.readable.getReader();
|
const reader = this.workerQueue.readable.getReader();
|
||||||
const { value } = await reader.read();
|
const { value } = await reader.read();
|
||||||
reader.releaseLock();
|
reader.releaseLock();
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('No worker left');
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +84,7 @@ export default class WorkerPool {
|
|||||||
await this.done;
|
await this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchJob(msg) {
|
dispatchJob(msg: I): Promise<O> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const writer = this.jobQueue.writable.getWriter();
|
const writer = this.jobQueue.writable.getWriter();
|
||||||
writer.write({ msg, resolve, reject });
|
writer.write({ msg, resolve, reject });
|
||||||
@@ -90,14 +92,32 @@ export default class WorkerPool {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static useThisThreadAsWorker(cb) {
|
private jobPromise(worker: Worker, msg: I) {
|
||||||
parentPort.on('message', async (data) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = uuid();
|
||||||
|
worker.postMessage({ msg, id });
|
||||||
|
worker.on('message', function f({ error, result, id: rid }) {
|
||||||
|
if (rid !== id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worker.off('message', f);
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static useThisThreadAsWorker<I, O>(cb: (msg: I) => O) {
|
||||||
|
parentPort!.on('message', async (data) => {
|
||||||
const { msg, id } = data;
|
const { msg, id } = data;
|
||||||
try {
|
try {
|
||||||
const result = await cb(msg);
|
const result = await cb(msg);
|
||||||
parentPort.postMessage({ result, id });
|
parentPort!.postMessage({ result, id });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
parentPort.postMessage({ error: e.message, id });
|
parentPort!.postMessage({ error: e.message, id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
43
libsquoosh/test/index.test.ts
Normal file
43
libsquoosh/test/index.test.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { test } from 'uvu';
|
||||||
|
import * as assert from 'uvu/assert';
|
||||||
|
import { ImagePool } from '..';
|
||||||
|
|
||||||
|
let imagePool: ImagePool;
|
||||||
|
|
||||||
|
test.after.each(async () => {
|
||||||
|
if (imagePool) {
|
||||||
|
try {
|
||||||
|
await imagePool.close();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
imagePool = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('smoke test', async () => {
|
||||||
|
imagePool = new ImagePool(1);
|
||||||
|
|
||||||
|
const imagePath = path.resolve(__dirname, '../../icon-large-maskable.png');
|
||||||
|
const image = imagePool.ingestImage(imagePath);
|
||||||
|
|
||||||
|
const { bitmap } = await image.decoded;
|
||||||
|
assert.equal(bitmap.width, 1024);
|
||||||
|
|
||||||
|
await image.preprocess({
|
||||||
|
resize: {
|
||||||
|
enabled: true,
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await image.encode({
|
||||||
|
mozjpeg: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { size } = await image.encodedWith.mozjpeg;
|
||||||
|
// resulting image is 1554b
|
||||||
|
assert.ok(size > 500);
|
||||||
|
assert.ok(size < 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.run();
|
||||||
@@ -5,5 +5,12 @@
|
|||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"allowJs": true
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "../codecs/**/*"]
|
"include": ["src/**/*", "../codecs/**/*"],
|
||||||
|
"ts-node": {
|
||||||
|
"transpileOnly": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs"
|
||||||
|
},
|
||||||
|
"include": ["tests/**/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
missing-types.d.ts
vendored
5
missing-types.d.ts
vendored
@@ -44,6 +44,11 @@ declare module 'data-url:*' {
|
|||||||
export default url;
|
export default url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module 'service-worker:*' {
|
||||||
|
const url: string;
|
||||||
|
export default url;
|
||||||
|
}
|
||||||
|
|
||||||
declare var ga: {
|
declare var ga: {
|
||||||
(...args: any[]): void;
|
(...args: any[]): void;
|
||||||
q: any[];
|
q: any[];
|
||||||
|
|||||||
40
package-lock.json
generated
40
package-lock.json
generated
@@ -32,7 +32,7 @@
|
|||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"mime-types": "^2.1.28",
|
"mime-types": "^2.1.28",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"pointer-tracker": "^2.4.0",
|
"pointer-tracker": "^2.5.3",
|
||||||
"postcss": "^7.0.35",
|
"postcss": "^7.0.35",
|
||||||
"postcss-modules": "^3.2.2",
|
"postcss-modules": "^3.2.2",
|
||||||
"postcss-nested": "^4.2.3",
|
"postcss-nested": "^4.2.3",
|
||||||
@@ -48,6 +48,20 @@
|
|||||||
"which": "^2.0.2"
|
"which": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../pointer-tracker": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"extraneous": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"husky": "^4.2.5",
|
||||||
|
"lint-staged": "^10.2.11",
|
||||||
|
"prettier": "^2.0.5",
|
||||||
|
"rollup": "^2.23.1",
|
||||||
|
"rollup-plugin-terser": "^7.0.0",
|
||||||
|
"rollup-plugin-typescript2": "^0.27.2",
|
||||||
|
"typescript": "^3.9.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.10.4",
|
"version": "7.10.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||||
@@ -3871,9 +3885,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pointer-tracker": {
|
"node_modules/pointer-tracker": {
|
||||||
"version": "2.4.0",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.5.3.tgz",
|
||||||
"integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==",
|
"integrity": "sha512-LiJUeIbzk4dXq678YeyrZ++mdY17q4n/2sBHfU9wIuvmSzdiPgMvmvWN2g8mY4J7YwYOIrqrZUWP/MfFHVwYtg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
@@ -8976,9 +8990,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@surma/rollup-plugin-off-main-thread": {
|
"@surma/rollup-plugin-off-main-thread": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.2.tgz",
|
||||||
"integrity": "sha512-7OU8wfyv18YPWVmecg2/0Jh+pm3lQbvPhIWHd1YQpoxPKPW/vsDNGBaCnMKsZbz29RjgCoXKugAjyagPncgdEw==",
|
"integrity": "sha512-dOD6nGZ79RmWKDRQuC7SOGXMvDkkLwBogu+epfVFMKiy2kOUtLZkb8wV/ettuMt37YJAJKYCKUmxSbZL2LkUQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
@@ -11946,9 +11960,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pointer-tracker": {
|
"pointer-tracker": {
|
||||||
"version": "2.4.0",
|
"version": "2.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.5.3.tgz",
|
||||||
"integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==",
|
"integrity": "sha512-LiJUeIbzk4dXq678YeyrZ++mdY17q4n/2sBHfU9wIuvmSzdiPgMvmvWN2g8mY4J7YwYOIrqrZUWP/MfFHVwYtg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"postcss": {
|
"postcss": {
|
||||||
@@ -15980,9 +15994,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"wasm-feature-detect": {
|
"wasm-feature-detect": {
|
||||||
"version": "1.2.9",
|
"version": "1.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||||
"integrity": "sha512-2E9/gtLVLpv2wnZDyYv8WY2dR9gHbmyv5uhZsnOcMSzqc78aGZpKQORPNcnrPwAU23nFUo7GAwKuoTAWRgsJ7Q=="
|
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
||||||
},
|
},
|
||||||
"which": {
|
"which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
"@rollup/plugin-commonjs": "^17.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^11.1.0",
|
"@rollup/plugin-node-resolve": "^11.1.0",
|
||||||
"@rollup/plugin-replace": "^2.3.4",
|
"@rollup/plugin-replace": "^2.3.4",
|
||||||
"@surma/rollup-plugin-off-main-thread": "^2.2.1",
|
"@surma/rollup-plugin-off-main-thread": "^2.2.2",
|
||||||
"@types/dedent": "^0.7.0",
|
"@types/dedent": "^0.7.0",
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/node": "^14.14.7",
|
"@types/node": "^14.14.7",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"mime-types": "^2.1.28",
|
"mime-types": "^2.1.28",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"pointer-tracker": "^2.4.0",
|
"pointer-tracker": "^2.5.3",
|
||||||
"postcss": "^7.0.35",
|
"postcss": "^7.0.35",
|
||||||
"postcss-modules": "^3.2.2",
|
"postcss-modules": "^3.2.2",
|
||||||
"postcss-nested": "^4.2.3",
|
"postcss-nested": "^4.2.3",
|
||||||
@@ -58,6 +58,6 @@
|
|||||||
"*.rs": "rustfmt"
|
"*.rs": "rustfmt"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"wasm-feature-detect": "^1.2.9"
|
"wasm-feature-detect": "^1.2.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import commonjs from '@rollup/plugin-commonjs';
|
|||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import OMT from '@surma/rollup-plugin-off-main-thread';
|
import OMT from '@surma/rollup-plugin-off-main-thread';
|
||||||
import replace from '@rollup/plugin-replace';
|
import replace from '@rollup/plugin-replace';
|
||||||
|
import { importMetaAssets } from '@web/rollup-plugin-import-meta-assets';
|
||||||
|
|
||||||
import simpleTS from './lib/simple-ts';
|
import simpleTS from './lib/simple-ts';
|
||||||
import clientBundlePlugin from './lib/client-bundle-plugin';
|
import clientBundlePlugin from './lib/client-bundle-plugin';
|
||||||
@@ -31,11 +32,11 @@ 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 dataURLPlugin from './lib/data-url-plugin';
|
||||||
|
import entryDataPlugin, { fileNameToURL } from './lib/entry-data-plugin';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import entryDataPlugin from './lib/entry-data-plugin';
|
|
||||||
|
|
||||||
function resolveFileUrl({ fileName }) {
|
function resolveFileUrl({ fileName }) {
|
||||||
return JSON.stringify(fileName.replace(/^static\//, '/'));
|
return JSON.stringify(fileNameToURL(fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveImportMetaUrlInStaticBuild(property, { moduleId }) {
|
function resolveImportMetaUrlInStaticBuild(property, { moduleId }) {
|
||||||
@@ -70,7 +71,7 @@ export default async function ({ watch }) {
|
|||||||
|
|
||||||
await del('.tmp/build');
|
await del('.tmp/build');
|
||||||
|
|
||||||
const isProduction = !watch && !process.env.DEBUG;
|
const isProduction = !watch;
|
||||||
|
|
||||||
const tsPluginInstance = simpleTS('.', {
|
const tsPluginInstance = simpleTS('.', {
|
||||||
watch,
|
watch,
|
||||||
@@ -118,6 +119,7 @@ export default async function ({ watch }) {
|
|||||||
plugins: [
|
plugins: [
|
||||||
{ resolveFileUrl },
|
{ resolveFileUrl },
|
||||||
OMT({ loader: await omtLoaderPromise }),
|
OMT({ loader: await omtLoaderPromise }),
|
||||||
|
importMetaAssets(),
|
||||||
serviceWorkerPlugin({
|
serviceWorkerPlugin({
|
||||||
output: 'static/serviceworker.js',
|
output: 'static/serviceworker.js',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const REFLECTED_ATTRIBUTES = [
|
|||||||
'disabled',
|
'disabled',
|
||||||
];
|
];
|
||||||
|
|
||||||
function getPrescision(value: string): number {
|
function getPrecision(value: string): number {
|
||||||
const afterDecimal = value.split('.')[1];
|
const afterDecimal = value.split('.')[1];
|
||||||
return afterDecimal ? afterDecimal.length : 0;
|
return afterDecimal ? afterDecimal.length : 0;
|
||||||
}
|
}
|
||||||
@@ -112,18 +112,24 @@ class RangeInputElement extends HTMLElement {
|
|||||||
this.dispatchEvent(retargetted);
|
this.dispatchEvent(retargetted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _getDisplayValue(value: number): string {
|
||||||
|
if (value >= 10000) return (value / 1000).toFixed(1) + 'k';
|
||||||
|
|
||||||
|
const labelPrecision =
|
||||||
|
Number(this.labelPrecision) || getPrecision(this.step) || 0;
|
||||||
|
return labelPrecision
|
||||||
|
? value.toFixed(labelPrecision)
|
||||||
|
: Math.round(value).toString();
|
||||||
|
}
|
||||||
|
|
||||||
private _update = () => {
|
private _update = () => {
|
||||||
// Not connected?
|
// Not connected?
|
||||||
if (!this._valueDisplay) return;
|
if (!this._valueDisplay) return;
|
||||||
const value = Number(this.value) || 0;
|
const value = Number(this.value) || 0;
|
||||||
const min = Number(this.min) || 0;
|
const min = Number(this.min) || 0;
|
||||||
const max = Number(this.max) || 100;
|
const max = Number(this.max) || 100;
|
||||||
const labelPrecision =
|
|
||||||
Number(this.labelPrecision) || getPrescision(this.step) || 0;
|
|
||||||
const percent = (100 * (value - min)) / (max - min);
|
const percent = (100 * (value - min)) / (max - min);
|
||||||
const displayValue = labelPrecision
|
const displayValue = this._getDisplayValue(value);
|
||||||
? value.toFixed(labelPrecision)
|
|
||||||
: Math.round(value).toString();
|
|
||||||
|
|
||||||
this._valueDisplay!.textContent = displayValue;
|
this._valueDisplay!.textContent = displayValue;
|
||||||
this.style.setProperty('--value-percent', percent + '%');
|
this.style.setProperty('--value-percent', percent + '%');
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
text-decoration-style: dotted;
|
text-decoration-style: dotted;
|
||||||
text-decoration-color: var(--main-theme-color);
|
text-decoration-color: var(--main-theme-color);
|
||||||
text-underline-position: under;
|
text-underline-position: under;
|
||||||
width: 48px;
|
width: 54px;
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 5px;
|
left: 5px;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.options-scroller {
|
.options-scroller {
|
||||||
--horizontal-padding: 15px;
|
--horizontal-padding: 15px;
|
||||||
border-radius: var(--scroller-radius);
|
border-radius: var(--scroller-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
/* At smaller widths, the multi-panel handles the scrolling */
|
/* At smaller widths, the multi-panel handles the scrolling */
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PointerTracker, { Pointer } from 'pointer-tracker';
|
import PointerTracker, { Pointer } from 'pointer-tracker';
|
||||||
import 'add-css:./styles.css';
|
import 'add-css:./styles.css';
|
||||||
|
import { isSafari } from 'client/lazy-app/util';
|
||||||
|
|
||||||
interface Point {
|
interface Point {
|
||||||
clientX: number;
|
clientX: number;
|
||||||
@@ -81,6 +82,7 @@ function createPoint(): SVGPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MIN_SCALE = 0.01;
|
const MIN_SCALE = 0.01;
|
||||||
|
const MAX_SCALE = 100000;
|
||||||
|
|
||||||
export default class PinchZoom extends HTMLElement {
|
export default class PinchZoom extends HTMLElement {
|
||||||
// The element that we'll transform.
|
// The element that we'll transform.
|
||||||
@@ -104,14 +106,23 @@ export default class PinchZoom extends HTMLElement {
|
|||||||
const pointerTracker: PointerTracker = new PointerTracker(this, {
|
const pointerTracker: PointerTracker = new PointerTracker(this, {
|
||||||
start: (pointer, event) => {
|
start: (pointer, event) => {
|
||||||
// We only want to track 2 pointers at most
|
// We only want to track 2 pointers at most
|
||||||
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl)
|
if (
|
||||||
|
pointerTracker.currentPointers.length === 2 ||
|
||||||
|
!this._positioningEl
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
move: (previousPointers) => {
|
move: (previousPointers) => {
|
||||||
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
this._onPointerMove(previousPointers, pointerTracker.currentPointers);
|
||||||
},
|
},
|
||||||
|
// Unfortunately Safari on iOS has a bug where pointer event capturing
|
||||||
|
// doesn't work in some cases, and we hit those cases due to our event
|
||||||
|
// retargeting in pinch-zoom.
|
||||||
|
// https://bugs.webkit.org/show_bug.cgi?id=220196
|
||||||
|
avoidPointerEvents: isSafari,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addEventListener('wheel', (event) => this._onWheel(event));
|
this.addEventListener('wheel', (event) => this._onWheel(event));
|
||||||
@@ -244,6 +255,9 @@ export default class PinchZoom extends HTMLElement {
|
|||||||
// Avoid scaling to zero
|
// Avoid scaling to zero
|
||||||
if (scale < MIN_SCALE) return;
|
if (scale < MIN_SCALE) return;
|
||||||
|
|
||||||
|
// Avoid scaling to very large values
|
||||||
|
if (scale > MAX_SCALE) return;
|
||||||
|
|
||||||
// Return if there's no change
|
// Return if there's no change
|
||||||
if (scale === this.scale && x === this.x && y === this.y) return;
|
if (scale === this.scale && x === this.x && y === this.y) return;
|
||||||
|
|
||||||
@@ -296,9 +310,13 @@ export default class PinchZoom extends HTMLElement {
|
|||||||
deltaY *= 15;
|
deltaY *= 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomingOut = deltaY > 0;
|
||||||
|
|
||||||
// ctrlKey is true when pinch-zooming on a trackpad.
|
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||||
const divisor = ctrlKey ? 100 : 300;
|
const divisor = ctrlKey ? 100 : 300;
|
||||||
const scaleDiff = 1 - deltaY / divisor;
|
// when zooming out, invert the delta and the ratio to keep zoom stable
|
||||||
|
const ratio = 1 - (zoomingOut ? -deltaY : deltaY) / divisor;
|
||||||
|
const scaleDiff = zoomingOut ? 1 / ratio : ratio;
|
||||||
|
|
||||||
this._applyChange({
|
this._applyChange({
|
||||||
scaleDiff,
|
scaleDiff,
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import './custom-els/PinchZoom';
|
|||||||
import './custom-els/TwoUp';
|
import './custom-els/TwoUp';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
import { shallowEqual } from '../../util';
|
import { shallowEqual, isSafari } from '../../util';
|
||||||
import {
|
import {
|
||||||
|
ToggleAliasingIcon,
|
||||||
|
ToggleAliasingActiveIcon,
|
||||||
ToggleBackgroundIcon,
|
ToggleBackgroundIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
RemoveIcon,
|
RemoveIcon,
|
||||||
@@ -19,7 +21,6 @@ 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 { drawDataToCanvas } from 'client/lazy-app/util/canvas';
|
import { drawDataToCanvas } from 'client/lazy-app/util/canvas';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
preprocessorState?: PreprocessorState;
|
preprocessorState?: PreprocessorState;
|
||||||
@@ -35,6 +36,7 @@ interface State {
|
|||||||
scale: number;
|
scale: number;
|
||||||
editingScale: boolean;
|
editingScale: boolean;
|
||||||
altBackground: boolean;
|
altBackground: boolean;
|
||||||
|
aliasing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scaleToOpts: ScaleToOpts = {
|
const scaleToOpts: ScaleToOpts = {
|
||||||
@@ -49,6 +51,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
scale: 1,
|
scale: 1,
|
||||||
editingScale: false,
|
editingScale: false,
|
||||||
altBackground: false,
|
altBackground: false,
|
||||||
|
aliasing: false,
|
||||||
};
|
};
|
||||||
canvasLeft?: HTMLCanvasElement;
|
canvasLeft?: HTMLCanvasElement;
|
||||||
canvasRight?: HTMLCanvasElement;
|
canvasRight?: HTMLCanvasElement;
|
||||||
@@ -145,6 +148,12 @@ 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 toggleAliasing = () => {
|
||||||
|
this.setState((state) => ({
|
||||||
|
aliasing: !state.aliasing,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
private toggleBackground = () => {
|
private toggleBackground = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
altBackground: !this.state.altBackground,
|
altBackground: !this.state.altBackground,
|
||||||
@@ -255,7 +264,7 @@ export default class Output extends Component<Props, State> {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
{ mobileView, leftImgContain, rightImgContain, source }: Props,
|
{ mobileView, leftImgContain, rightImgContain, source }: Props,
|
||||||
{ scale, editingScale, altBackground }: State,
|
{ scale, editingScale, altBackground, aliasing }: State,
|
||||||
) {
|
) {
|
||||||
const leftDraw = this.leftDrawable();
|
const leftDraw = this.leftDrawable();
|
||||||
const rightDraw = this.rightDrawable();
|
const rightDraw = this.rightDrawable();
|
||||||
@@ -275,7 +284,11 @@ export default class Output extends Component<Props, State> {
|
|||||||
onTouchStartCapture={this.onRetargetableEvent}
|
onTouchStartCapture={this.onRetargetableEvent}
|
||||||
onTouchEndCapture={this.onRetargetableEvent}
|
onTouchEndCapture={this.onRetargetableEvent}
|
||||||
onTouchMoveCapture={this.onRetargetableEvent}
|
onTouchMoveCapture={this.onRetargetableEvent}
|
||||||
onPointerDownCapture={this.onRetargetableEvent}
|
onPointerDownCapture={
|
||||||
|
// We avoid pointer events in our PinchZoom due to a Safari bug.
|
||||||
|
// That means we also need to avoid them here too, else we end up preventing the fallback mouse events.
|
||||||
|
isSafari ? undefined : this.onRetargetableEvent
|
||||||
|
}
|
||||||
onMouseDownCapture={this.onRetargetableEvent}
|
onMouseDownCapture={this.onRetargetableEvent}
|
||||||
onWheelCapture={this.onRetargetableEvent}
|
onWheelCapture={this.onRetargetableEvent}
|
||||||
>
|
>
|
||||||
@@ -285,7 +298,9 @@ export default class Output extends Component<Props, State> {
|
|||||||
ref={linkRef(this, 'pinchZoomLeft')}
|
ref={linkRef(this, 'pinchZoomLeft')}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
class={style.pinchTarget}
|
class={`${style.pinchTarget} ${
|
||||||
|
aliasing ? style.pixelated : ''
|
||||||
|
}`}
|
||||||
ref={linkRef(this, 'canvasLeft')}
|
ref={linkRef(this, 'canvasLeft')}
|
||||||
width={leftDraw && leftDraw.width}
|
width={leftDraw && leftDraw.width}
|
||||||
height={leftDraw && leftDraw.height}
|
height={leftDraw && leftDraw.height}
|
||||||
@@ -301,7 +316,9 @@ export default class Output extends Component<Props, State> {
|
|||||||
ref={linkRef(this, 'pinchZoomRight')}
|
ref={linkRef(this, 'pinchZoomRight')}
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
class={style.pinchTarget}
|
class={`${style.pinchTarget} ${
|
||||||
|
aliasing ? style.pixelated : ''
|
||||||
|
}`}
|
||||||
ref={linkRef(this, 'canvasRight')}
|
ref={linkRef(this, 'canvasRight')}
|
||||||
width={rightDraw && rightDraw.width}
|
width={rightDraw && rightDraw.width}
|
||||||
height={rightDraw && rightDraw.height}
|
height={rightDraw && rightDraw.height}
|
||||||
@@ -345,10 +362,31 @@ export default class Output extends Component<Props, State> {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class={style.buttonGroup}>
|
<div class={style.buttonGroup}>
|
||||||
<button class={style.firstButton} onClick={this.onRotateClick}>
|
<button
|
||||||
|
class={style.firstButton}
|
||||||
|
onClick={this.onRotateClick}
|
||||||
|
title="Rotate"
|
||||||
|
>
|
||||||
<RotateIcon />
|
<RotateIcon />
|
||||||
</button>
|
</button>
|
||||||
<button class={style.lastButton} onClick={this.toggleBackground}>
|
{!isSafari && (
|
||||||
|
<button
|
||||||
|
class={style.button}
|
||||||
|
onClick={this.toggleAliasing}
|
||||||
|
title="Toggle smoothing"
|
||||||
|
>
|
||||||
|
{aliasing ? (
|
||||||
|
<ToggleAliasingActiveIcon />
|
||||||
|
) : (
|
||||||
|
<ToggleAliasingIcon />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
class={style.lastButton}
|
||||||
|
onClick={this.toggleBackground}
|
||||||
|
title="Toggle background"
|
||||||
|
>
|
||||||
{altBackground ? (
|
{altBackground ? (
|
||||||
<ToggleBackgroundActiveIcon />
|
<ToggleBackgroundActiveIcon />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -86,8 +86,7 @@
|
|||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus {
|
&:focus-visible {
|
||||||
/* box-shadow: 0 0 0 2px var(--hot-pink); */
|
|
||||||
box-shadow: 0 0 0 2px #fff;
|
box-shadow: 0 0 0 2px #fff;
|
||||||
outline: none;
|
outline: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -161,3 +160,8 @@ input.zoom {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pixelated {
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,25 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const ToggleAliasingIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<circle
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ToggleAliasingActiveIcon = (props: preact.JSX.HTMLAttributes) => (
|
||||||
|
<Icon {...props}>
|
||||||
|
<path d="M12 3h5v2h2v2h2v5h-2V9h-2V7h-2V5h-3V3M21 12v5h-2v2h-2v2h-5v-2h3v-2h2v-2h2v-3h2M12 21H7v-2H5v-2H3v-5h2v3h2v2h2v2h3v2M3 12V7h2V5h2V3h5v2H9v2H7v2H5v3H3" />
|
||||||
|
</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" />
|
||||||
|
|||||||
@@ -14,6 +14,11 @@
|
|||||||
import * as WebCodecs from '../util/web-codecs';
|
import * as WebCodecs from '../util/web-codecs';
|
||||||
import { drawableToImageData } from './canvas';
|
import { drawableToImageData } from './canvas';
|
||||||
|
|
||||||
|
/** If render engine is Safari */
|
||||||
|
export const isSafari =
|
||||||
|
/Safari\//.test(navigator.userAgent) &&
|
||||||
|
!/Chrom(e|ium)\//.test(navigator.userAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two objects, returning a boolean indicating if
|
* Compare two objects, returning a boolean indicating if
|
||||||
* they have the same properties and strictly equal values.
|
* they have the same properties and strictly equal values.
|
||||||
|
|||||||
5
src/client/missing-types.d.ts
vendored
5
src/client/missing-types.d.ts
vendored
@@ -19,9 +19,4 @@ interface Navigator {
|
|||||||
|
|
||||||
declare module 'add-css:*' {}
|
declare module 'add-css:*' {}
|
||||||
|
|
||||||
declare module 'service-worker:*' {
|
|
||||||
const url: string;
|
|
||||||
export default url;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'preact/debug' {}
|
declare module 'preact/debug' {}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type { AVIFModule } from 'codecs/avif/dec/avif_dec';
|
import type { AVIFModule } from 'codecs/avif/dec/avif_dec';
|
||||||
import wasmUrl from 'url:codecs/avif/dec/avif_dec.wasm';
|
|
||||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||||
|
|
||||||
let emscriptenModule: Promise<AVIFModule>;
|
let emscriptenModule: Promise<AVIFModule>;
|
||||||
@@ -19,7 +18,7 @@ let emscriptenModule: Promise<AVIFModule>;
|
|||||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
const decoder = await import('codecs/avif/dec/avif_dec');
|
const decoder = await import('codecs/avif/dec/avif_dec');
|
||||||
emscriptenModule = initEmscriptenModule(decoder.default, wasmUrl);
|
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [module, data] = await Promise.all([
|
const [module, data] = await Promise.all([
|
||||||
|
|||||||
@@ -11,14 +11,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import jxlDecoder, { JXLModule } from 'codecs/jxl/dec/jxl_dec';
|
import jxlDecoder, { JXLModule } from 'codecs/jxl/dec/jxl_dec';
|
||||||
import wasmUrl from 'url:codecs/jxl/dec/jxl_dec.wasm';
|
|
||||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||||
|
|
||||||
let emscriptenModule: Promise<JXLModule>;
|
let emscriptenModule: Promise<JXLModule>;
|
||||||
|
|
||||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
emscriptenModule = initEmscriptenModule(jxlDecoder, wasmUrl);
|
emscriptenModule = initEmscriptenModule(jxlDecoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [module, data] = await Promise.all([
|
const [module, data] = await Promise.all([
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type { WebPModule } from 'codecs/webp/dec/webp_dec';
|
import type { WebPModule } from 'codecs/webp/dec/webp_dec';
|
||||||
import wasmUrl from 'url:codecs/webp/dec/webp_dec.wasm';
|
|
||||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||||
|
|
||||||
let emscriptenModule: Promise<WebPModule>;
|
let emscriptenModule: Promise<WebPModule>;
|
||||||
@@ -19,7 +18,7 @@ let emscriptenModule: Promise<WebPModule>;
|
|||||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
const decoder = await import('codecs/webp/dec/webp_dec');
|
const decoder = await import('codecs/webp/dec/webp_dec');
|
||||||
emscriptenModule = initEmscriptenModule(decoder.default, wasmUrl);
|
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [module, data] = await Promise.all([
|
const [module, data] = await Promise.all([
|
||||||
|
|||||||
@@ -11,14 +11,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import wp2Decoder, { WP2Module } from 'codecs/wp2/dec/wp2_dec';
|
import wp2Decoder, { WP2Module } from 'codecs/wp2/dec/wp2_dec';
|
||||||
import wasmUrl from 'url:codecs/wp2/dec/wp2_dec.wasm';
|
|
||||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||||
|
|
||||||
let emscriptenModule: Promise<WP2Module>;
|
let emscriptenModule: Promise<WP2Module>;
|
||||||
|
|
||||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
emscriptenModule = initEmscriptenModule(wp2Decoder, wasmUrl);
|
emscriptenModule = initEmscriptenModule(wp2Decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [module, data] = await Promise.all([
|
const [module, data] = await Promise.all([
|
||||||
|
|||||||
@@ -12,9 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
import type { AVIFModule } from 'codecs/avif/enc/avif_enc';
|
import type { AVIFModule } from 'codecs/avif/enc/avif_enc';
|
||||||
import type { EncodeOptions } from '../shared/meta';
|
import type { EncodeOptions } from '../shared/meta';
|
||||||
import wasmUrlWithoutMT from 'url:codecs/avif/enc/avif_enc.wasm';
|
|
||||||
import wasmUrlWithMT from 'url:codecs/avif/enc/avif_enc_mt.wasm';
|
|
||||||
import workerUrl from 'omt:codecs/avif/enc/avif_enc_mt.worker.js';
|
|
||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
import { threads } from 'wasm-feature-detect';
|
import { threads } from 'wasm-feature-detect';
|
||||||
|
|
||||||
@@ -23,14 +20,10 @@ let emscriptenModule: Promise<AVIFModule>;
|
|||||||
async function init() {
|
async function init() {
|
||||||
if (await threads()) {
|
if (await threads()) {
|
||||||
const avifEncoder = await import('codecs/avif/enc/avif_enc_mt');
|
const avifEncoder = await import('codecs/avif/enc/avif_enc_mt');
|
||||||
return initEmscriptenModule<AVIFModule>(
|
return initEmscriptenModule<AVIFModule>(avifEncoder.default);
|
||||||
avifEncoder.default,
|
|
||||||
wasmUrlWithMT,
|
|
||||||
workerUrl,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const avifEncoder = await import('codecs/avif/enc/avif_enc.js');
|
const avifEncoder = await import('codecs/avif/enc/avif_enc.js');
|
||||||
return initEmscriptenModule(avifEncoder.default, wasmUrlWithoutMT);
|
return initEmscriptenModule(avifEncoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function encode(
|
export default async function encode(
|
||||||
|
|||||||
@@ -29,10 +29,9 @@ interface State {
|
|||||||
slightLoss: boolean;
|
slightLoss: boolean;
|
||||||
autoEdgePreservingFilter: boolean;
|
autoEdgePreservingFilter: boolean;
|
||||||
decodingSpeedTier: number;
|
decodingSpeedTier: number;
|
||||||
|
photonNoiseIso: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSpeed = 7;
|
|
||||||
|
|
||||||
export class Options extends Component<Props, State> {
|
export class Options extends Component<Props, State> {
|
||||||
static getDerivedStateFromProps(
|
static getDerivedStateFromProps(
|
||||||
props: Props,
|
props: Props,
|
||||||
@@ -47,7 +46,7 @@ export class Options extends Component<Props, State> {
|
|||||||
// Create default form state from options
|
// Create default form state from options
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
effort: maxSpeed - options.speed,
|
effort: options.effort,
|
||||||
quality: options.quality,
|
quality: options.quality,
|
||||||
progressive: options.progressive,
|
progressive: options.progressive,
|
||||||
edgePreservingFilter: options.epf === -1 ? 2 : options.epf,
|
edgePreservingFilter: options.epf === -1 ? 2 : options.epf,
|
||||||
@@ -55,6 +54,7 @@ export class Options extends Component<Props, State> {
|
|||||||
slightLoss: options.lossyPalette,
|
slightLoss: options.lossyPalette,
|
||||||
autoEdgePreservingFilter: options.epf === -1,
|
autoEdgePreservingFilter: options.epf === -1,
|
||||||
decodingSpeedTier: options.decodingSpeedTier,
|
decodingSpeedTier: options.decodingSpeedTier,
|
||||||
|
photonNoiseIso: options.photonNoiseIso,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,15 +87,15 @@ export class Options extends Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newOptions: EncodeOptions = {
|
const newOptions: EncodeOptions = {
|
||||||
speed: maxSpeed - optionState.effort,
|
effort: optionState.effort,
|
||||||
quality: optionState.lossless ? 100 : optionState.quality,
|
quality: optionState.lossless ? 100 : optionState.quality,
|
||||||
progressive: optionState.progressive,
|
progressive: optionState.progressive,
|
||||||
epf: optionState.autoEdgePreservingFilter
|
epf: optionState.autoEdgePreservingFilter
|
||||||
? -1
|
? -1
|
||||||
: optionState.edgePreservingFilter,
|
: optionState.edgePreservingFilter,
|
||||||
nearLossless: 0,
|
|
||||||
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
|
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
|
||||||
decodingSpeedTier: optionState.decodingSpeedTier,
|
decodingSpeedTier: optionState.decodingSpeedTier,
|
||||||
|
photonNoiseIso: optionState.photonNoiseIso,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||||
@@ -121,6 +121,7 @@ export class Options extends Component<Props, State> {
|
|||||||
slightLoss,
|
slightLoss,
|
||||||
autoEdgePreservingFilter,
|
autoEdgePreservingFilter,
|
||||||
decodingSpeedTier,
|
decodingSpeedTier,
|
||||||
|
photonNoiseIso,
|
||||||
}: State,
|
}: State,
|
||||||
) {
|
) {
|
||||||
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
||||||
@@ -164,7 +165,6 @@ export class Options extends Component<Props, State> {
|
|||||||
<label class={style.optionToggle}>
|
<label class={style.optionToggle}>
|
||||||
Auto edge filter
|
Auto edge filter
|
||||||
<Checkbox
|
<Checkbox
|
||||||
name="autoEdgeFilter"
|
|
||||||
checked={autoEdgePreservingFilter}
|
checked={autoEdgePreservingFilter}
|
||||||
onChange={this._inputChange(
|
onChange={this._inputChange(
|
||||||
'autoEdgePreservingFilter',
|
'autoEdgePreservingFilter',
|
||||||
@@ -199,6 +199,17 @@ export class Options extends Component<Props, State> {
|
|||||||
Optimise for decoding speed (worse compression):
|
Optimise for decoding speed (worse compression):
|
||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
|
min="0"
|
||||||
|
max="50000"
|
||||||
|
step="100"
|
||||||
|
value={photonNoiseIso}
|
||||||
|
onInput={this._inputChange('photonNoiseIso', 'number')}
|
||||||
|
>
|
||||||
|
Noise equivalent to ISO:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Expander>
|
</Expander>
|
||||||
@@ -212,8 +223,8 @@ export class Options extends Component<Props, State> {
|
|||||||
</label>
|
</label>
|
||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
min="0"
|
min="3"
|
||||||
max={maxSpeed - 1}
|
max="9"
|
||||||
value={effort}
|
value={effort}
|
||||||
onInput={this._inputChange('effort', 'number')}
|
onInput={this._inputChange('effort', 'number')}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export const label = 'JPEG XL (beta)';
|
|||||||
export const mimeType = 'image/jxl';
|
export const mimeType = 'image/jxl';
|
||||||
export const extension = 'jxl';
|
export const extension = 'jxl';
|
||||||
export const defaultOptions: EncodeOptions = {
|
export const defaultOptions: EncodeOptions = {
|
||||||
speed: 4,
|
effort: 7,
|
||||||
quality: 75,
|
quality: 75,
|
||||||
progressive: false,
|
progressive: false,
|
||||||
epf: -1,
|
epf: -1,
|
||||||
nearLossless: 0,
|
|
||||||
lossyPalette: false,
|
lossyPalette: false,
|
||||||
decodingSpeedTier: 0,
|
decodingSpeedTier: 0,
|
||||||
|
photonNoiseIso: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,31 +16,19 @@ import type { EncodeOptions } from '../shared/meta';
|
|||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
import { threads, simd } from 'wasm-feature-detect';
|
import { threads, simd } from 'wasm-feature-detect';
|
||||||
|
|
||||||
import wasmUrl from 'url:codecs/jxl/enc/jxl_enc.wasm';
|
|
||||||
|
|
||||||
import wasmUrlWithMT from 'url:codecs/jxl/enc/jxl_enc_mt.wasm';
|
|
||||||
import workerUrl from 'omt:codecs/jxl/enc/jxl_enc_mt.worker.js';
|
|
||||||
|
|
||||||
import wasmUrlWithMTAndSIMD from 'url:codecs/jxl/enc/jxl_enc_mt_simd.wasm';
|
|
||||||
import workerUrlWithSIMD from 'omt:codecs/jxl/enc/jxl_enc_mt_simd.worker.js';
|
|
||||||
|
|
||||||
let emscriptenModule: Promise<JXLModule>;
|
let emscriptenModule: Promise<JXLModule>;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (await threads()) {
|
if (await threads()) {
|
||||||
if (await simd()) {
|
if (await simd()) {
|
||||||
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt_simd');
|
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt_simd');
|
||||||
return initEmscriptenModule(
|
return initEmscriptenModule(jxlEncoder.default);
|
||||||
jxlEncoder.default,
|
|
||||||
wasmUrlWithMTAndSIMD,
|
|
||||||
workerUrlWithSIMD,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt');
|
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc_mt');
|
||||||
return initEmscriptenModule(jxlEncoder.default, wasmUrlWithMT, workerUrl);
|
return initEmscriptenModule(jxlEncoder.default);
|
||||||
}
|
}
|
||||||
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc');
|
const jxlEncoder = await import('codecs/jxl/enc/jxl_enc');
|
||||||
return initEmscriptenModule(jxlEncoder.default, wasmUrl);
|
return initEmscriptenModule(jxlEncoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function encode(
|
export default async function encode(
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
import mozjpeg_enc, { MozJPEGModule } from 'codecs/mozjpeg/enc/mozjpeg_enc';
|
import mozjpeg_enc, { MozJPEGModule } from 'codecs/mozjpeg/enc/mozjpeg_enc';
|
||||||
import { EncodeOptions } from '../shared/meta';
|
import { EncodeOptions } from '../shared/meta';
|
||||||
import wasmUrl from 'url:codecs/mozjpeg/enc/mozjpeg_enc.wasm';
|
|
||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
|
|
||||||
let emscriptenModule: Promise<MozJPEGModule>;
|
let emscriptenModule: Promise<MozJPEGModule>;
|
||||||
@@ -22,7 +21,7 @@ export default async function encode(
|
|||||||
options: EncodeOptions,
|
options: EncodeOptions,
|
||||||
): Promise<ArrayBuffer> {
|
): Promise<ArrayBuffer> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
emscriptenModule = initEmscriptenModule(mozjpeg_enc, wasmUrl);
|
emscriptenModule = initEmscriptenModule(mozjpeg_enc);
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = await emscriptenModule;
|
const module = await emscriptenModule;
|
||||||
|
|||||||
@@ -10,30 +10,27 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import initOxiWasmST, {
|
|
||||||
optimise as optimiseST,
|
|
||||||
} from 'codecs/oxipng/pkg/squoosh_oxipng';
|
|
||||||
import initOxiWasmMT, {
|
|
||||||
initThreadPool,
|
|
||||||
optimise as optimiseMT,
|
|
||||||
} from 'codecs/oxipng/pkg-parallel/squoosh_oxipng';
|
|
||||||
import oxiWasmUrlST from 'url:codecs/oxipng/pkg/squoosh_oxipng_bg.wasm';
|
|
||||||
import oxiWasmUrlMT from 'url:codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm';
|
|
||||||
import { EncodeOptions } from '../shared/meta';
|
import { EncodeOptions } from '../shared/meta';
|
||||||
import { threads } from 'wasm-feature-detect';
|
import { threads } from 'wasm-feature-detect';
|
||||||
|
|
||||||
async function initMT() {
|
async function initMT() {
|
||||||
await initOxiWasmMT(oxiWasmUrlMT);
|
const { default: init, initThreadPool, optimise } = await import(
|
||||||
|
'codecs/oxipng/pkg-parallel/squoosh_oxipng'
|
||||||
|
);
|
||||||
|
await init();
|
||||||
await initThreadPool(navigator.hardwareConcurrency);
|
await initThreadPool(navigator.hardwareConcurrency);
|
||||||
return optimiseMT;
|
return optimise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initST() {
|
async function initST() {
|
||||||
await initOxiWasmST(oxiWasmUrlST);
|
const { default: init, optimise } = await import(
|
||||||
return optimiseST;
|
'codecs/oxipng/pkg/squoosh_oxipng'
|
||||||
|
);
|
||||||
|
await init();
|
||||||
|
return optimise;
|
||||||
}
|
}
|
||||||
|
|
||||||
let wasmReady: Promise<typeof optimiseMT | typeof optimiseST>;
|
let wasmReady: ReturnType<typeof initMT | typeof initST>;
|
||||||
|
|
||||||
export default async function encode(
|
export default async function encode(
|
||||||
data: ArrayBuffer,
|
data: ArrayBuffer,
|
||||||
|
|||||||
@@ -16,18 +16,15 @@ import type { EncodeOptions } from '../shared/meta';
|
|||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
import { simd } from 'wasm-feature-detect';
|
import { simd } from 'wasm-feature-detect';
|
||||||
|
|
||||||
import wasmUrl from 'url:codecs/webp/enc/webp_enc.wasm';
|
|
||||||
import wasmUrlWithSIMD from 'url:codecs/webp/enc/webp_enc_simd.wasm';
|
|
||||||
|
|
||||||
let emscriptenModule: Promise<WebPModule>;
|
let emscriptenModule: Promise<WebPModule>;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (await simd()) {
|
if (await simd()) {
|
||||||
const webpEncoder = await import('codecs/webp/enc/webp_enc_simd');
|
const webpEncoder = await import('codecs/webp/enc/webp_enc_simd');
|
||||||
return initEmscriptenModule(webpEncoder.default, wasmUrlWithSIMD);
|
return initEmscriptenModule(webpEncoder.default);
|
||||||
}
|
}
|
||||||
const webpEncoder = await import('codecs/webp/enc/webp_enc');
|
const webpEncoder = await import('codecs/webp/enc/webp_enc');
|
||||||
return initEmscriptenModule(webpEncoder.default, wasmUrl);
|
return initEmscriptenModule(webpEncoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function encode(
|
export default async function encode(
|
||||||
|
|||||||
@@ -16,31 +16,19 @@ import type { EncodeOptions } from '../shared/meta';
|
|||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
import { threads, simd } from 'wasm-feature-detect';
|
import { threads, simd } from 'wasm-feature-detect';
|
||||||
|
|
||||||
import wasmUrl from 'url:codecs/wp2/enc/wp2_enc.wasm';
|
|
||||||
|
|
||||||
import wasmUrlWithMT from 'url:codecs/wp2/enc/wp2_enc_mt.wasm';
|
|
||||||
import workerUrl from 'omt:codecs/wp2/enc/wp2_enc_mt.worker.js';
|
|
||||||
|
|
||||||
import wasmUrlWithMTAndSIMD from 'url:codecs/wp2/enc/wp2_enc_mt_simd.wasm';
|
|
||||||
import workerUrlWithSIMD from 'omt:codecs/wp2/enc/wp2_enc_mt_simd.worker.js';
|
|
||||||
|
|
||||||
let emscriptenModule: Promise<WP2Module>;
|
let emscriptenModule: Promise<WP2Module>;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (await threads()) {
|
if (await threads()) {
|
||||||
if (await simd()) {
|
if (await simd()) {
|
||||||
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc_mt_simd');
|
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc_mt_simd');
|
||||||
return initEmscriptenModule(
|
return initEmscriptenModule(wp2Encoder.default);
|
||||||
wp2Encoder.default,
|
|
||||||
wasmUrlWithMTAndSIMD,
|
|
||||||
workerUrlWithSIMD,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc_mt');
|
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc_mt');
|
||||||
return initEmscriptenModule(wp2Encoder.default, wasmUrlWithMT, workerUrl);
|
return initEmscriptenModule(wp2Encoder.default);
|
||||||
}
|
}
|
||||||
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc');
|
const wp2Encoder = await import('codecs/wp2/enc/wp2_enc');
|
||||||
return initEmscriptenModule(wp2Encoder.default, wasmUrl);
|
return initEmscriptenModule(wp2Encoder.default);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function encode(
|
export default async function encode(
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import imagequant, { QuantizerModule } from 'codecs/imagequant/imagequant';
|
import imagequant, { QuantizerModule } from 'codecs/imagequant/imagequant';
|
||||||
import wasmUrl from 'url:codecs/imagequant/imagequant.wasm';
|
|
||||||
import { initEmscriptenModule } from 'features/worker-utils';
|
import { initEmscriptenModule } from 'features/worker-utils';
|
||||||
import { Options } from '../shared/meta';
|
import { Options } from '../shared/meta';
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ export default async function process(
|
|||||||
opts: Options,
|
opts: Options,
|
||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
if (!emscriptenModule) {
|
if (!emscriptenModule) {
|
||||||
emscriptenModule = initEmscriptenModule(imagequant, wasmUrl);
|
emscriptenModule = initEmscriptenModule(imagequant);
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = await emscriptenModule;
|
const module = await emscriptenModule;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { WorkerResizeOptions } from '../shared/meta';
|
import type { WorkerResizeOptions } from '../shared/meta';
|
||||||
import { getContainOffsets } from '../shared/util';
|
import { getContainOffsets } from '../shared/util';
|
||||||
import initResizeWasm, { resize as wasmResize } from 'codecs/resize/pkg';
|
import initResizeWasm, { resize as wasmResize } from 'codecs/resize/pkg';
|
||||||
import resizeWasmUrl from 'url:codecs/resize/pkg/squoosh_resize_bg.wasm';
|
|
||||||
import hqxWasmUrl from 'url:codecs/hqx/pkg/squooshhqx_bg.wasm';
|
|
||||||
import initHqxWasm, { resize as wasmHqx } from 'codecs/hqx/pkg';
|
import initHqxWasm, { resize as wasmHqx } from 'codecs/hqx/pkg';
|
||||||
|
|
||||||
interface HqxResizeOptions extends WorkerResizeOptions {
|
interface HqxResizeOptions extends WorkerResizeOptions {
|
||||||
@@ -63,7 +61,7 @@ async function hqx(
|
|||||||
opts: HqxResizeOptions,
|
opts: HqxResizeOptions,
|
||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
if (!hqxWasmReady) {
|
if (!hqxWasmReady) {
|
||||||
hqxWasmReady = initHqxWasm(hqxWasmUrl);
|
hqxWasmReady = initHqxWasm();
|
||||||
}
|
}
|
||||||
|
|
||||||
await hqxWasmReady;
|
await hqxWasmReady;
|
||||||
@@ -96,7 +94,7 @@ export default async function resize(
|
|||||||
let input = data;
|
let input = data;
|
||||||
|
|
||||||
if (!resizeWasmReady) {
|
if (!resizeWasmReady) {
|
||||||
resizeWasmReady = initResizeWasm(resizeWasmUrl);
|
resizeWasmReady = initResizeWasm();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optsIsHqxOpts(opts)) {
|
if (optsIsHqxOpts(opts)) {
|
||||||
|
|||||||
@@ -10,38 +10,12 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
export function initEmscriptenModule<T extends EmscriptenWasm.Module>(
|
||||||
// @ts-ignore
|
|
||||||
self.asm = ''; // lol
|
|
||||||
// @ts-ignore
|
|
||||||
self.UTF32ToString = (...v) => console.log("u322s", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
// @ts-ignore
|
|
||||||
self.stringToUTF32 = (...v) => console.log("s2u32", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
// @ts-ignore
|
|
||||||
self.lengthBytesUTF32 = (...v) => console.log("lb32", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
// @ts-ignore
|
|
||||||
self.UTF16ToString = (...v) => console.log("u162s", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
// @ts-ignore
|
|
||||||
self.stringToUTF16 = (...v) => console.log("s2u16", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
// @ts-ignore
|
|
||||||
self.lengthBytesUTF16 = (...v) => console.log("lb16", v); //v => [...new Uint32Array(v)].map(v => String.fromCharCode(v)).join("")
|
|
||||||
export async function initEmscriptenModule<T extends EmscriptenWasm.Module>(
|
|
||||||
moduleFactory: EmscriptenWasm.ModuleFactory<T>,
|
moduleFactory: EmscriptenWasm.ModuleFactory<T>,
|
||||||
wasmUrl: string,
|
|
||||||
workerUrl?: string,
|
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return moduleFactory({
|
return moduleFactory({
|
||||||
// @ts-ignore
|
|
||||||
wasm: await fetch(wasmUrl).then(r => r.arrayBuffer()),
|
|
||||||
// Just to be safe, don't automatically invoke any wasm functions
|
// Just to be safe, don't automatically invoke any wasm functions
|
||||||
noInitialRun: true,
|
noInitialRun: true,
|
||||||
locateFile: (url: string) => {
|
|
||||||
// This is probably unused?
|
|
||||||
console.log("CALL TO LOCATEFILE", url);
|
|
||||||
if (url.endsWith('.wasm')) return wasmUrl;
|
|
||||||
if (url.endsWith('.worker.js')) return workerUrl!;
|
|
||||||
throw Error('Unknown url in locateFile ' + url);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
src/shared/prerendered-app/Intro/SlideOnScroll/index.tsx
Normal file
51
src/shared/prerendered-app/Intro/SlideOnScroll/index.tsx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { h, Component, RenderableProps } from 'preact';
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
interface State {}
|
||||||
|
|
||||||
|
export default class SlideOnScroll extends Component<Props, State> {
|
||||||
|
private observer?: IntersectionObserver;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
|
||||||
|
const base = this.base as HTMLElement;
|
||||||
|
let wasOutOfView = false;
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver(
|
||||||
|
(entries, observer) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
wasOutOfView = true;
|
||||||
|
base.style.opacity = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only transition in if the element was at some point out of view.
|
||||||
|
if (wasOutOfView) {
|
||||||
|
base.style.opacity = '';
|
||||||
|
base.animate(
|
||||||
|
{ offset: 0, opacity: '0', transform: 'translateY(40px)' },
|
||||||
|
{ duration: 300, easing: 'ease' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 },
|
||||||
|
);
|
||||||
|
|
||||||
|
this.observer.observe(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
// Have to manually disconnect due to memory leaks in browsers.
|
||||||
|
// One day we'll be able to remove this, and the private property.
|
||||||
|
// https://twitter.com/jaffathecake/status/1405437361643790337
|
||||||
|
if (this.observer) this.observer.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
render({ children }: RenderableProps<Props>) {
|
||||||
|
return <div>{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user