mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-11 16:26:20 +00:00
Merge v2 codecs (#844)
* wip * doh, whitespace * Updating emscripten, restoring export name * Updating oxipng * Build wasm * Fix oxipng; upgrade Rust * More v2-codec integration * AVIF now working * Non-working JXL * Build hqx with Rust 1.40; refactor build-rust*.sh * Set web target * wp2 wip * wp2 decode options * Better logo height when loading the logo into squoosh * Build oxi * JAKE IS AN IDIOT * wip oxipng * Fixing case sensitive imports * adding log * another log * Abort tasks when compress component removed * Adding progressive option to JXL * Fix bug going to & from original image * Exposing epf in jxl * logs * Bypass initial CSS plugin * Revert "logs" * Adding root * Fix for finding TSC on Windows * Use spawn again * Converting to module paths * Remove spawnP * silly * oops * logs * Fixing glob paths in CSS plugin * Path normalising * Normalise paths for CSS plugin * Normalise again * Use correct func * Adding lossless mode and near lossless (but hidden in UI) * Removing useless comments * Some logging * Update JXL to v0.1. (#846) * Rebuild JXL * Adding slight loss option Co-authored-by: Ingvar Stepanyan <rreverser@google.com> Co-authored-by: Luca Versari <veluca93@gmail.com>
This commit is contained in:
@@ -1,139 +1,84 @@
|
||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/v0.8.1.tar.gz
|
||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/31d7c6d1e32cf467ac24fb8c7a76c4902a4c00db.tar.gz
|
||||
CODEC_PACKAGE = node_modules/libavif.tar.gz
|
||||
|
||||
CODEC_ENC_DIR = node_modules/libavif-enc
|
||||
CODEC_ENC_BUILD_DIR := $(CODEC_ENC_DIR)/build
|
||||
CODEC_ENC_OUT := $(CODEC_ENC_BUILD_DIR)/libavif.a
|
||||
|
||||
CODEC_DEC_DIR = node_modules/libavif-dec
|
||||
CODEC_DEC_BUILD_DIR := $(CODEC_DEC_DIR)/build
|
||||
CODEC_DEC_OUT := $(CODEC_DEC_BUILD_DIR)/libavif.a
|
||||
|
||||
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v2.0.0.tar.gz
|
||||
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
||||
|
||||
LIBAOM_ENC_DIR := $(CODEC_ENC_DIR)/ext/aom
|
||||
LIBAOM_ENC_BUILD_DIR := $(LIBAOM_ENC_DIR)/build.libavif
|
||||
LIBAOM_ENC_OUT := $(LIBAOM_ENC_BUILD_DIR)/libaom.a
|
||||
|
||||
LIBAOM_DEC_DIR := $(CODEC_DEC_DIR)/ext/aom
|
||||
LIBAOM_DEC_BUILD_DIR := $(LIBAOM_DEC_DIR)/build.libavif
|
||||
LIBAOM_DEC_OUT := $(LIBAOM_DEC_BUILD_DIR)/libaom.a
|
||||
export CODEC_DIR = node_modules/libavif
|
||||
BUILD_DIR := node_modules/build
|
||||
ENC_BUILD_DIR := $(BUILD_DIR)/enc
|
||||
ENC_MT_BUILD_DIR := $(BUILD_DIR)/enc-mt
|
||||
DEC_BUILD_DIR := $(BUILD_DIR)/dec
|
||||
export LIBAOM_DIR = node_modules/libaom
|
||||
|
||||
OUT_ENC_JS = enc/avif_enc.js
|
||||
OUT_ENC_CPP = $(OUT_ENC_JS:.js=.cpp)
|
||||
OUT_ENC_WASM = $(OUT_ENC_JS:.js=.wasm)
|
||||
|
||||
OUT_ENC_MT_JS = enc/avif_enc_mt.js
|
||||
OUT_DEC_JS = dec/avif_dec.js
|
||||
OUT_DEC_CPP = $(OUT_DEC_JS:.js=.cpp)
|
||||
OUT_DEC_WASM = $(OUT_DEC_JS:.js=.wasm)
|
||||
|
||||
# ERROR_ON_UNDEFINED_SYMBOLS=0 is needed to seperate the encoder and decoder
|
||||
EMSCRIPTEN_FLAGS = ${CXXFLAGS} \
|
||||
${LDFLAGS} \
|
||||
--bind \
|
||||
--closure 1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s MODULARIZE=1 \
|
||||
-s TEXTDECODER=2 \
|
||||
-s ENVIRONMENT='worker' \
|
||||
-s EXPORT_ES6=1 \
|
||||
-s ERROR_ON_UNDEFINED_SYMBOLS=0
|
||||
OUT_ENC_CPP = enc/avif_enc.cpp
|
||||
OUT_DEC_CPP = dec/avif_dec.cpp
|
||||
|
||||
CODEC_EMCMAKE = emcmake cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=0 \
|
||||
-DAVIF_CODEC_AOM=1 \
|
||||
-DAVIF_LOCAL_AOM=1 \
|
||||
../
|
||||
|
||||
LIBAOM_FLAGS = -DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_CCACHE=0 \
|
||||
-DAOM_TARGET_CPU=generic \
|
||||
-DENABLE_DOCS=0 \
|
||||
-DENABLE_TESTS=0 \
|
||||
-DENABLE_EXAMPLES=0 \
|
||||
-DENABLE_TOOLS=0 \
|
||||
-DCONFIG_ACCOUNTING=1 \
|
||||
-DCONFIG_INSPECTION=0 \
|
||||
-DCONFIG_MULTITHREAD=0 \
|
||||
-DCONFIG_RUNTIME_CPU_DETECT=0 \
|
||||
-DCONFIG_WEBM_IO=0
|
||||
HELPER_MAKEFLAGS := -f helper.Makefile
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS)
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS)
|
||||
|
||||
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(LIBAOM_ENC_OUT) $(CODEC_ENC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_ENC_DIR)/include \
|
||||
${EMSCRIPTEN_FLAGS} \
|
||||
-o $@ \
|
||||
$+
|
||||
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
BUILD_DIR=$(ENC_BUILD_DIR) \
|
||||
OUT_JS=$@ \
|
||||
OUT_CPP=$< \
|
||||
LIBAOM_FLAGS="\
|
||||
-DCONFIG_AV1_DECODER=0 \
|
||||
-DCONFIG_MULTITHREAD=0 \
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
" \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0"
|
||||
|
||||
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(LIBAOM_DEC_OUT) $(CODEC_DEC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DEC_DIR)/include \
|
||||
${EMSCRIPTEN_FLAGS} \
|
||||
-o $@ \
|
||||
$+
|
||||
$(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
BUILD_DIR=$(ENC_MT_BUILD_DIR) \
|
||||
OUT_JS=$@ \
|
||||
OUT_CPP=$< \
|
||||
LIBAOM_FLAGS="\
|
||||
-DCONFIG_AV1_DECODER=0 \
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
" \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0" \
|
||||
OUT_FLAGS="-pthread"
|
||||
|
||||
$(CODEC_ENC_OUT): $(CODEC_ENC_DIR)/CMakeLists.txt $(LIBAOM_ENC_OUT)
|
||||
mkdir -p $(CODEC_ENC_BUILD_DIR)
|
||||
cd $(CODEC_ENC_BUILD_DIR) && \
|
||||
$(CODEC_EMCMAKE) && \
|
||||
$(MAKE)
|
||||
|
||||
$(CODEC_DEC_OUT): $(CODEC_DEC_DIR)/CMakeLists.txt $(LIBAOM_DEC_OUT)
|
||||
mkdir -p $(CODEC_DEC_BUILD_DIR)
|
||||
cd $(CODEC_DEC_BUILD_DIR) && \
|
||||
$(CODEC_EMCMAKE) && \
|
||||
$(MAKE)
|
||||
|
||||
$(LIBAOM_ENC_OUT): $(LIBAOM_ENC_DIR)/CMakeLists.txt
|
||||
mkdir -p $(LIBAOM_ENC_BUILD_DIR)
|
||||
cd $(LIBAOM_ENC_BUILD_DIR) && \
|
||||
emcmake cmake \
|
||||
$(LIBAOM_FLAGS) \
|
||||
-DCONFIG_AV1_DECODER=0 \
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
../ && \
|
||||
$(MAKE)
|
||||
|
||||
$(LIBAOM_DEC_OUT): $(LIBAOM_DEC_DIR)/CMakeLists.txt
|
||||
mkdir -p $(LIBAOM_DEC_BUILD_DIR)
|
||||
cd $(LIBAOM_DEC_BUILD_DIR) && \
|
||||
emcmake cmake \
|
||||
$(LIBAOM_FLAGS) \
|
||||
-DCONFIG_AV1_ENCODER=0 \
|
||||
../ && \
|
||||
$(MAKE)
|
||||
|
||||
$(CODEC_ENC_DIR)/CMakeLists.txt: $(CODEC_ENC_DIR)
|
||||
$(CODEC_DEC_DIR)/CMakeLists.txt: $(CODEC_DEC_DIR)
|
||||
|
||||
$(LIBAOM_ENC_DIR)/CMakeLists.txt: $(LIBAOM_ENC_DIR)
|
||||
$(LIBAOM_DEC_DIR)/CMakeLists.txt: $(LIBAOM_DEC_DIR)
|
||||
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
BUILD_DIR=$(DEC_BUILD_DIR) \
|
||||
OUT_JS=$@ \
|
||||
OUT_CPP=$< \
|
||||
LIBAOM_FLAGS="\
|
||||
-DCONFIG_AV1_ENCODER=0 \
|
||||
-DCONFIG_MULTITHREAD=0 \
|
||||
" \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"
|
||||
|
||||
$(CODEC_PACKAGE):
|
||||
mkdir -p $(dir $@)
|
||||
mkdir -p $(@D)
|
||||
curl -sL $(CODEC_URL) -o $@
|
||||
|
||||
$(LIBAOM_PACKAGE):
|
||||
mkdir -p $(dir $@)
|
||||
mkdir -p $(@D)
|
||||
curl -sL $(LIBAOM_URL) -o $@
|
||||
|
||||
$(CODEC_ENC_DIR) $(CODEC_DEC_DIR): $(CODEC_PACKAGE)
|
||||
mkdir -p $@
|
||||
tar xz --strip 1 -C $@ -f $(CODEC_PACKAGE)
|
||||
$(CODEC_DIR)/CMakeLists.txt: $(CODEC_PACKAGE)
|
||||
mkdir -p $(@D)
|
||||
tar xzm --strip 1 -C $(@D) -f $(CODEC_PACKAGE)
|
||||
|
||||
$(LIBAOM_ENC_DIR) $(LIBAOM_DEC_DIR): $(LIBAOM_PACKAGE)
|
||||
mkdir -p $@
|
||||
tar xz -C $@ -f $(LIBAOM_PACKAGE)
|
||||
$(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
|
||||
mkdir -p $(@D)
|
||||
tar xzm -C $(@D) -f $(LIBAOM_PACKAGE)
|
||||
|
||||
clean:
|
||||
$(RM) $(LIBAOM_PACKAGE) $(CODEC_PACKAGE) $(OUT_ENC_JS) $(OUT_ENC_WASM) $(OUT_DEC_JS) $(OUT_DEC_WASM)
|
||||
$(MAKE) -C $(CODEC_ENC_BUILD_DIR) clean
|
||||
$(MAKE) -C $(CODEC_DEC_BUILD_DIR) clean
|
||||
$(MAKE) -C $(LIBAOM_ENC_BUILD_DIR) clean
|
||||
$(MAKE) -C $(LIBAOM_DEC_BUILD_DIR) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) BUILD_DIR=$(ENC_BUILD_DIR) OUT_JS=$(OUT_ENC_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) BUILD_DIR=$(ENC_MT_BUILD_DIR) OUT_JS=$(OUT_ENC_MT_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) BUILD_DIR=$(DEC_BUILD_DIR) OUT_JS=$(OUT_DEC_JS) clean
|
||||
|
||||
@@ -8,15 +8,10 @@ thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
||||
thread_local const val ImageData = val::global("ImageData");
|
||||
|
||||
val decode(std::string avifimage) {
|
||||
// point raw.data and raw.size to the contents of an .avif(s)
|
||||
avifROData raw = {
|
||||
.data = (uint8_t*)avifimage.c_str(),
|
||||
.size = avifimage.length()
|
||||
};
|
||||
|
||||
avifImage* image = avifImageCreateEmpty();
|
||||
avifDecoder* decoder = avifDecoderCreate();
|
||||
avifResult decodeResult = avifDecoderRead(decoder, image, &raw);
|
||||
avifResult decodeResult =
|
||||
avifDecoderReadMemory(decoder, image, (uint8_t*)avifimage.c_str(), avifimage.length());
|
||||
// image is an independent copy of decoded data, decoder may be destroyed here
|
||||
avifDecoderDestroy(decoder);
|
||||
|
||||
@@ -25,7 +20,8 @@ val decode(std::string avifimage) {
|
||||
if (decodeResult == AVIF_RESULT_OK) {
|
||||
// Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
|
||||
avifRGBImage rgb;
|
||||
avifRGBImageSetDefaults(&rgb, image); // Defaults to AVIF_RGB_FORMAT_RGBA which is what we want.
|
||||
avifRGBImageSetDefaults(&rgb,
|
||||
image); // Defaults to AVIF_RGB_FORMAT_RGBA which is what we want.
|
||||
rgb.depth = 8; // Does not need to match image->depth. We always want 8-bit pixels.
|
||||
|
||||
avifRGBImageAllocatePixels(&rgb);
|
||||
@@ -33,7 +29,9 @@ val decode(std::string avifimage) {
|
||||
|
||||
// We want to create a *copy* of the decoded data to be owned by the JavaScript side.
|
||||
// For that, we perform `new Uint8Array(wasmMemBuffer, wasmPtr, wasmSize).slice()`:
|
||||
result = ImageData.new_(Uint8ClampedArray.new_(typed_memory_view(rgb.rowBytes * rgb.height, rgb.pixels)), rgb.width, rgb.height);
|
||||
result = ImageData.new_(
|
||||
Uint8ClampedArray.new_(typed_memory_view(rgb.rowBytes * rgb.height, rgb.pixels)), rgb.width,
|
||||
rgb.height);
|
||||
|
||||
// Now we can safely free the RGB pixels:
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/threading.h>
|
||||
#include <emscripten/val.h>
|
||||
#include "avif/avif.h"
|
||||
|
||||
@@ -50,13 +51,10 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
|
||||
avifImage* image = avifImageCreate(width, height, depth, format);
|
||||
|
||||
if (
|
||||
options.maxQuantizer == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.minQuantizer == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.minQuantizerAlpha == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.maxQuantizerAlpha == AVIF_QUANTIZER_LOSSLESS &&
|
||||
format == AVIF_PIXEL_FORMAT_YUV444
|
||||
) {
|
||||
if (options.maxQuantizer == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.minQuantizer == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.minQuantizerAlpha == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.maxQuantizerAlpha == AVIF_QUANTIZER_LOSSLESS && format == AVIF_PIXEL_FORMAT_YUV444) {
|
||||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
|
||||
} else {
|
||||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709;
|
||||
@@ -71,7 +69,7 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
avifImageRGBToYUV(image, &srcRGB);
|
||||
|
||||
avifEncoder* encoder = avifEncoderCreate();
|
||||
encoder->maxThreads = 1;
|
||||
encoder->maxThreads = emscripten_num_logical_cores();
|
||||
encoder->minQuantizer = options.minQuantizer;
|
||||
encoder->maxQuantizer = options.maxQuantizer;
|
||||
encoder->minQuantizerAlpha = options.minQuantizerAlpha;
|
||||
|
||||
2
codecs/avif/enc/avif_enc_mt.d.ts
vendored
Normal file
2
codecs/avif/enc/avif_enc_mt.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './avif_enc';
|
||||
export { default } from './avif_enc';
|
||||
3268
codecs/avif/enc/avif_enc_mt.js
Normal file
3268
codecs/avif/enc/avif_enc_mt.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codecs/avif/enc/avif_enc_mt.wasm
Executable file
BIN
codecs/avif/enc/avif_enc_mt.wasm
Executable file
Binary file not shown.
103
codecs/avif/enc/avif_enc_mt.worker.js
Normal file
103
codecs/avif/enc/avif_enc_mt.worker.js
Normal file
@@ -0,0 +1,103 @@
|
||||
var threadInfoStruct = 0;
|
||||
var selfThreadId = 0;
|
||||
var parentThreadId = 0;
|
||||
var initializedJS = false;
|
||||
var Module = {};
|
||||
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: selfThreadId });
|
||||
}
|
||||
var err = threadPrintErr;
|
||||
this.alert = threadAlert;
|
||||
Module['instantiateWasm'] = function (info, receiveInstance) {
|
||||
var instance = new WebAssembly.Instance(Module['wasmModule'], info);
|
||||
Module['wasmModule'] = null;
|
||||
receiveInstance(instance);
|
||||
return instance.exports;
|
||||
};
|
||||
this.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;
|
||||
import(e.data.urlOrBlob)
|
||||
.then(function (avif_enc_mt) {
|
||||
return avif_enc_mt.default(Module);
|
||||
})
|
||||
.then(function (instance) {
|
||||
Module = instance;
|
||||
postMessage({ cmd: 'loaded' });
|
||||
});
|
||||
} 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;
|
||||
threadInfoStruct = e.data.threadInfoStruct;
|
||||
Module['registerPthreadPtr'](
|
||||
threadInfoStruct,
|
||||
/*isMainBrowserThread=*/ 0,
|
||||
/*isMainRuntimeThread=*/ 0,
|
||||
);
|
||||
selfThreadId = e.data.selfThreadId;
|
||||
parentThreadId = e.data.parentThreadId;
|
||||
var max = e.data.stackBase;
|
||||
var top = e.data.stackBase + e.data.stackSize;
|
||||
Module['establishStackSpace'](top, max);
|
||||
Module['_emscripten_tls_init']();
|
||||
Module['PThread'].receiveObjectTransfer(e.data);
|
||||
Module['PThread'].setThreadStatus(Module['_pthread_self'](), 1);
|
||||
if (!initializedJS) {
|
||||
Module['___embind_register_native_and_builtin_types']();
|
||||
initializedJS = true;
|
||||
}
|
||||
try {
|
||||
var result = Module['dynCall']('ii', e.data.start_routine, [
|
||||
e.data.arg,
|
||||
]);
|
||||
if (!Module['getNoExitRuntime']()) Module['PThread'].threadExit(result);
|
||||
} catch (ex) {
|
||||
if (ex === 'Canceled!') {
|
||||
Module['PThread'].threadCancel();
|
||||
} else if (ex != 'unwind') {
|
||||
Atomics.store(
|
||||
Module['HEAPU32'],
|
||||
(threadInfoStruct + 4) >> /*C_STRUCTS.pthread.threadExitCode*/ 2,
|
||||
ex instanceof Module['ExitStatus'] ? ex.status : -2,
|
||||
);
|
||||
/*A custom entry specific to Emscripten denoting that the thread crashed.*/ Atomics.store(
|
||||
Module['HEAPU32'],
|
||||
(threadInfoStruct + 0) >> /*C_STRUCTS.pthread.threadStatus*/ 2,
|
||||
1,
|
||||
);
|
||||
Module['_emscripten_futex_wake'](
|
||||
threadInfoStruct + 0,
|
||||
/*C_STRUCTS.pthread.threadStatus*/ 2147483647,
|
||||
);
|
||||
if (!(ex instanceof Module['ExitStatus'])) throw ex;
|
||||
}
|
||||
}
|
||||
} else if (e.data.cmd === 'cancel') {
|
||||
if (threadInfoStruct) {
|
||||
Module['PThread'].threadCancel();
|
||||
}
|
||||
} else if (e.data.target === 'setimmediate') {
|
||||
} else if (e.data.cmd === 'processThreadQueue') {
|
||||
if (threadInfoStruct) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
75
codecs/avif/helper.Makefile
Normal file
75
codecs/avif/helper.Makefile
Normal file
@@ -0,0 +1,75 @@
|
||||
# This is a helper Makefile for building LibAVIF + LibAOM with given params.
|
||||
#
|
||||
# Params that must be supplied by the caller:
|
||||
# $(CODEC_DIR)
|
||||
# $(LIBAOM_DIR)
|
||||
# $(BUILD_DIR)
|
||||
# $(OUT_JS)
|
||||
# $(OUT_CPP)
|
||||
# $(LIBAOM_FLAGS)
|
||||
# $(LIBAVIF_FLAGS)
|
||||
|
||||
CODEC_BUILD_DIR := $(BUILD_DIR)/libavif
|
||||
CODEC_OUT := $(CODEC_BUILD_DIR)/libavif.a
|
||||
|
||||
LIBAOM_BUILD_DIR := $(BUILD_DIR)/libaom
|
||||
LIBAOM_OUT := $(LIBAOM_BUILD_DIR)/libaom.a
|
||||
|
||||
OUT_WASM = $(OUT_JS:.js=.wasm)
|
||||
OUT_WORKER=$(OUT_JS:.js=.worker.js)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_JS)
|
||||
|
||||
$(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DIR)/include \
|
||||
$(CXXFLAGS) \
|
||||
$(LDFLAGS) \
|
||||
$(OUT_FLAGS) \
|
||||
--bind \
|
||||
--closure 1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s MODULARIZE=1 \
|
||||
-s TEXTDECODER=2 \
|
||||
-s ENVIRONMENT='worker' \
|
||||
-s EXPORT_ES6=1 \
|
||||
-s EXPORT_NAME="$(basename $(@F))" \
|
||||
-o $@ \
|
||||
$+
|
||||
|
||||
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_OUT)
|
||||
emcmake cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=0 \
|
||||
-DAVIF_CODEC_AOM=1 \
|
||||
-DAOM_LIBRARY=$(LIBAOM_OUT) \
|
||||
-DAOM_INCLUDE_DIR=$(LIBAOM_DIR) \
|
||||
$(LIBAVIF_FLAGS) \
|
||||
-B $(CODEC_BUILD_DIR) \
|
||||
$(CODEC_DIR) && \
|
||||
$(MAKE) -C $(CODEC_BUILD_DIR)
|
||||
|
||||
$(LIBAOM_OUT): $(LIBAOM_DIR)/CMakeLists.txt
|
||||
emcmake cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DENABLE_CCACHE=0 \
|
||||
-DAOM_TARGET_CPU=generic \
|
||||
-DENABLE_DOCS=0 \
|
||||
-DENABLE_TESTS=0 \
|
||||
-DENABLE_EXAMPLES=0 \
|
||||
-DENABLE_TOOLS=0 \
|
||||
-DCONFIG_ACCOUNTING=1 \
|
||||
-DCONFIG_INSPECTION=0 \
|
||||
-DCONFIG_RUNTIME_CPU_DETECT=0 \
|
||||
-DCONFIG_WEBM_IO=0 \
|
||||
$(LIBAOM_FLAGS) \
|
||||
-B $(LIBAOM_BUILD_DIR) \
|
||||
$(LIBAOM_DIR) && \
|
||||
$(MAKE) -C $(LIBAOM_BUILD_DIR)
|
||||
|
||||
clean:
|
||||
$(RM) $(OUT_JS) $(OUT_WASM) $(OUT_WORKER)
|
||||
$(MAKE) -C $(CODEC_BUILD_DIR) clean
|
||||
$(MAKE) -C $(LIBAOM_BUILD_DIR) clean
|
||||
@@ -1,4 +1,10 @@
|
||||
set -e
|
||||
|
||||
docker build -t squoosh-rust - < ../rust.Dockerfile
|
||||
docker run --rm -v $PWD:/src squoosh-rust "$@"
|
||||
if [ ! -z "$RUST_IMG" ]
|
||||
then
|
||||
# Get part after ":" (https://stackoverflow.com/a/15149278/439965).
|
||||
IMG_SUFFIX=-${RUST_IMG#*:}
|
||||
fi
|
||||
IMG_NAME=squoosh-rust$IMG_SUFFIX
|
||||
docker build -t $IMG_NAME --build-arg RUST_IMG - < ../rust.Dockerfile
|
||||
docker run --rm -v $PWD:/src $IMG_NAME "$@"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM emscripten/emsdk:2.0.3
|
||||
FROM emscripten/emsdk:2.0.8
|
||||
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
||||
ENV CFLAGS "-Os -flto"
|
||||
ENV CFLAGS "-O3 -flto"
|
||||
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
||||
ENV LDFLAGS "${CFLAGS}"
|
||||
ENV LDFLAGS "${CFLAGS} -s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency"
|
||||
# Build and cache standard libraries with these flags
|
||||
RUN emcc ${CXXFLAGS} --bind -xc++ /dev/null -o /dev/null
|
||||
WORKDIR /src
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# This is intentionally an old version of Rust. Newer versions
|
||||
# generate _significantly_ bigger Wasm binaries.
|
||||
FROM rust:1.37
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
RUN mkdir /opt/binaryen && \
|
||||
curl -L https://github.com/WebAssembly/binaryen/releases/download/1.38.32/binaryen-1.38.32-x86-linux.tar.gz | tar -xzf - -C /opt/binaryen --strip 1
|
||||
|
||||
ENV PATH="/opt/binaryen:${PATH}"
|
||||
WORKDIR /src
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "============================================="
|
||||
echo "Compiling wasm"
|
||||
echo "============================================="
|
||||
(
|
||||
wasm-pack build --target web -- --verbose --locked
|
||||
rm pkg/.gitignore
|
||||
)
|
||||
echo "============================================="
|
||||
echo "Compiling wasm done"
|
||||
echo "============================================="
|
||||
|
||||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
echo "Did you update your docker image?"
|
||||
echo "Run \`docker pull ubuntu\`"
|
||||
echo "Run \`docker pull rust\`"
|
||||
echo "Run \`docker build -t squoosh-hqx .\`"
|
||||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "hqx",
|
||||
"scripts": {
|
||||
"build:image": "docker build -t squoosh-hqx - < Dockerfile",
|
||||
"build": "docker run --rm -v $(pwd):/src squoosh-hqx ./build.sh"
|
||||
"build": "RUST_IMG=rust:1.40 ../build-rust.sh"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
codecs/imagequant/imagequant.wasm
Normal file → Executable file
BIN
codecs/imagequant/imagequant.wasm
Normal file → Executable file
Binary file not shown.
59
codecs/jxl/Makefile
Normal file
59
codecs/jxl/Makefile
Normal file
@@ -0,0 +1,59 @@
|
||||
CODEC_URL = https://gitlab.com/wg1/jpeg-xl.git
|
||||
CODEC_VERSION = v0.1
|
||||
CODEC_DIR = node_modules/jxl
|
||||
CODEC_BUILD_DIR := $(CODEC_DIR)/build
|
||||
CODEC_OUT := $(CODEC_BUILD_DIR)/lib/libjxl.a
|
||||
|
||||
OUT_JS = enc/jxl_enc.js dec/jxl_dec.js
|
||||
OUT_WASM = $(OUT_JS:.js=.wasm)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_JS)
|
||||
|
||||
%.js: %.cpp $(LIBAOM_OUT) $(CODEC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DIR) \
|
||||
-I $(CODEC_DIR)/lib \
|
||||
-I $(CODEC_DIR)/lib/include \
|
||||
-I $(CODEC_BUILD_DIR)/lib/include \
|
||||
-I $(CODEC_DIR)/third_party/highway \
|
||||
-I $(CODEC_DIR)/third_party/skcms \
|
||||
-I $(CODEC_DIR)/third_party/brunsli \
|
||||
-I $(CODEC_DIR)/third_party/brunsli/c/include \
|
||||
${CXXFLAGS} \
|
||||
${LDFLAGS} \
|
||||
--bind \
|
||||
--closure 1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s MODULARIZE=1 \
|
||||
-s TEXTDECODER=2 \
|
||||
-s ENVIRONMENT='worker' \
|
||||
-s EXPORT_ES6=1 \
|
||||
-s EXPORT_NAME="$(basename $(@F))" \
|
||||
-o $@ \
|
||||
$+ \
|
||||
$(CODEC_BUILD_DIR)/artifacts/libbrunslienc-static.bc \
|
||||
$(CODEC_BUILD_DIR)/artifacts/libbrunslicommon-static.bc \
|
||||
$(CODEC_BUILD_DIR)/artifacts/libbrunslidec-static.bc \
|
||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlidec-static.a \
|
||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlienc-static.a \
|
||||
$(CODEC_BUILD_DIR)/third_party/brotli/libbrotlicommon-static.a \
|
||||
$(CODEC_BUILD_DIR)/third_party/libskcms.a \
|
||||
$(CODEC_BUILD_DIR)/third_party/highway/libhwy.a
|
||||
|
||||
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt
|
||||
mkdir -p $(CODEC_BUILD_DIR)
|
||||
cd $(CODEC_BUILD_DIR) && \
|
||||
emcmake cmake ../ && \
|
||||
$(MAKE) jxl-static
|
||||
|
||||
$(CODEC_DIR)/CMakeLists.txt: $(CODEC_DIR)
|
||||
|
||||
$(CODEC_DIR):
|
||||
mkdir -p $@
|
||||
git clone $(CODEC_URL) --recursive -j`nproc` --depth 1 --branch $(CODEC_VERSION) $@
|
||||
|
||||
clean:
|
||||
$(RM) $(OUT_JS) $(OUT_WASM)
|
||||
$(MAKE) -C $(CODEC_BUILD_DIR) clean
|
||||
72
codecs/jxl/dec/jxl_dec.cpp
Normal file
72
codecs/jxl/dec/jxl_dec.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <jxl/decode.h>
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
||||
#include "skcms.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
||||
thread_local const val ImageData = val::global("ImageData");
|
||||
|
||||
// R, G, B, A
|
||||
#define COMPONENTS_PER_PIXEL 4
|
||||
|
||||
#define EXPECT_TRUE(a) \
|
||||
if (!(a)) { \
|
||||
return val::null(); \
|
||||
}
|
||||
|
||||
#define EXPECT_EQ(a, b) EXPECT_TRUE((a) == (b));
|
||||
|
||||
val decode(std::string data) {
|
||||
std::unique_ptr<JxlDecoder,
|
||||
std::integral_constant<decltype(&JxlDecoderDestroy), JxlDecoderDestroy>>
|
||||
dec(JxlDecoderCreate(nullptr));
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSubscribeEvents(
|
||||
dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE));
|
||||
|
||||
auto next_in = (const uint8_t*)data.c_str();
|
||||
auto avail_in = data.size();
|
||||
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
||||
JxlBasicInfo info;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec.get(), &info));
|
||||
size_t pixel_count = info.xsize * info.ysize;
|
||||
size_t component_count = pixel_count * COMPONENTS_PER_PIXEL;
|
||||
|
||||
EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
||||
static const JxlPixelFormat format = {COMPONENTS_PER_PIXEL, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
|
||||
size_t icc_size;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetICCProfileSize(dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_DATA, &icc_size));
|
||||
std::vector<uint8_t> icc_profile(icc_size);
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderGetColorAsICCProfile(dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
icc_profile.data(), icc_profile.size()));
|
||||
|
||||
auto float_pixels = std::make_unique<float[]>(component_count);
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec.get(), &format, float_pixels.get(),
|
||||
component_count * sizeof(float)));
|
||||
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec.get(), &next_in, &avail_in));
|
||||
|
||||
auto byte_pixels = std::make_unique<uint8_t[]>(component_count);
|
||||
// Convert to sRGB.
|
||||
skcms_ICCProfile jxl_profile;
|
||||
EXPECT_TRUE(skcms_Parse(icc_profile.data(), icc_profile.size(), &jxl_profile));
|
||||
EXPECT_TRUE(skcms_Transform(
|
||||
float_pixels.get(), skcms_PixelFormat_RGBA_ffff,
|
||||
info.alpha_premultiplied ? skcms_AlphaFormat_PremulAsEncoded : skcms_AlphaFormat_Unpremul,
|
||||
&jxl_profile, byte_pixels.get(), skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul,
|
||||
skcms_sRGB_profile(), pixel_count));
|
||||
|
||||
return ImageData.new_(
|
||||
Uint8ClampedArray.new_(typed_memory_view(component_count, byte_pixels.get())), info.xsize,
|
||||
info.ysize);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
function("decode", &decode);
|
||||
}
|
||||
7
codecs/jxl/dec/jxl_dec.d.ts
vendored
Normal file
7
codecs/jxl/dec/jxl_dec.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface JXLModule extends EmscriptenWasm.Module {
|
||||
decode(data: BufferSource): ImageData | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<JXLModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
1195
codecs/jxl/dec/jxl_dec.js
Normal file
1195
codecs/jxl/dec/jxl_dec.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codecs/jxl/dec/jxl_dec.wasm
Executable file
BIN
codecs/jxl/dec/jxl_dec.wasm
Executable file
Binary file not shown.
115
codecs/jxl/enc/jxl_enc.cpp
Normal file
115
codecs/jxl/enc/jxl_enc.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include "lib/jxl/enc_file.h"
|
||||
#include "lib/jxl/external_image.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
struct JXLOptions {
|
||||
// 1 = slowest
|
||||
// 7 = fastest
|
||||
int speed;
|
||||
float quality;
|
||||
bool progressive;
|
||||
int epf;
|
||||
int nearLossless;
|
||||
bool lossyPalette;
|
||||
};
|
||||
|
||||
val encode(std::string image, int width, int height, JXLOptions options) {
|
||||
jxl::CompressParams cparams;
|
||||
jxl::PassesEncoderState passes_enc_state;
|
||||
jxl::CodecInOut io;
|
||||
jxl::PaddedBytes bytes;
|
||||
jxl::ImageBundle* main = &io.Main();
|
||||
|
||||
cparams.epf = options.epf;
|
||||
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed);
|
||||
cparams.near_lossless = options.nearLossless;
|
||||
|
||||
if (options.lossyPalette) {
|
||||
cparams.lossy_palette = true;
|
||||
cparams.palette_colors = 0;
|
||||
cparams.options.predictor = jxl::Predictor::Zero;
|
||||
}
|
||||
|
||||
// Reduce memory usage of tree learning for lossless data.
|
||||
// TODO(veluca93): this is a mitigation for excessive memory usage in the JXL encoder.
|
||||
cparams.options.nb_repeats = 0.1;
|
||||
|
||||
float quality = options.quality;
|
||||
|
||||
// Quality settings roughly match libjpeg qualities.
|
||||
if (quality < 7 || quality == 100) {
|
||||
cparams.modular_mode = true;
|
||||
// Internal modular quality to roughly match VarDCT size.
|
||||
cparams.quality_pair.first = cparams.quality_pair.second =
|
||||
std::min(35 + (quality - 7) * 3.0f, 100.0f);
|
||||
} else {
|
||||
cparams.modular_mode = false;
|
||||
if (quality >= 30) {
|
||||
cparams.butteraugli_distance = 0.1 + (100 - quality) * 0.09;
|
||||
} else {
|
||||
cparams.butteraugli_distance = 6.4 + pow(2.5, (30 - quality) / 5.0f) / 6.25f;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.progressive) {
|
||||
cparams.qprogressive_mode = true;
|
||||
cparams.progressive_dc = 1;
|
||||
cparams.responsive = 1;
|
||||
}
|
||||
|
||||
if (cparams.modular_mode) {
|
||||
if (cparams.quality_pair.first != 100 || cparams.quality_pair.second != 100) {
|
||||
cparams.color_transform = jxl::ColorTransform::kXYB;
|
||||
} else {
|
||||
cparams.color_transform = jxl::ColorTransform::kNone;
|
||||
}
|
||||
}
|
||||
|
||||
if (cparams.near_lossless) {
|
||||
// Near-lossless assumes -R 0
|
||||
cparams.responsive = 0;
|
||||
cparams.modular_mode = true;
|
||||
}
|
||||
|
||||
io.metadata.m.SetAlphaBits(8);
|
||||
if (!io.metadata.size.Set(width, height)) {
|
||||
return val::null();
|
||||
}
|
||||
|
||||
uint8_t* inBuffer = (uint8_t*)image.c_str();
|
||||
|
||||
auto result = jxl::ConvertImage(
|
||||
jxl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(image.data()), image.size()), width,
|
||||
height, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_alpha=*/8, /*bits_per_sample=*/8,
|
||||
/*big_endian=*/false, /*flipped_y=*/false, /*pool=*/nullptr, main);
|
||||
|
||||
if (!result) {
|
||||
return val::null();
|
||||
}
|
||||
|
||||
auto js_result = val::null();
|
||||
if (EncodeFile(cparams, &io, &passes_enc_state, &bytes)) {
|
||||
js_result = Uint8Array.new_(typed_memory_view(bytes.size(), bytes.data()));
|
||||
}
|
||||
|
||||
return js_result;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<JXLOptions>("JXLOptions")
|
||||
.field("speed", &JXLOptions::speed)
|
||||
.field("quality", &JXLOptions::quality)
|
||||
.field("progressive", &JXLOptions::progressive)
|
||||
.field("nearLossless", &JXLOptions::nearLossless)
|
||||
.field("lossyPalette", &JXLOptions::lossyPalette)
|
||||
.field("epf", &JXLOptions::epf);
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
||||
21
codecs/jxl/enc/jxl_enc.d.ts
vendored
Normal file
21
codecs/jxl/enc/jxl_enc.d.ts
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
export interface EncodeOptions {
|
||||
speed: number;
|
||||
quality: number;
|
||||
progressive: boolean;
|
||||
epf: number;
|
||||
nearLossless: number;
|
||||
lossyPalette: boolean;
|
||||
}
|
||||
|
||||
export interface JXLModule extends EmscriptenWasm.Module {
|
||||
encode(
|
||||
data: BufferSource,
|
||||
width: number,
|
||||
height: number,
|
||||
options: EncodeOptions,
|
||||
): Uint8Array | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<JXLModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
1606
codecs/jxl/enc/jxl_enc.js
Normal file
1606
codecs/jxl/enc/jxl_enc.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codecs/jxl/enc/jxl_enc.wasm
Executable file
BIN
codecs/jxl/enc/jxl_enc.wasm
Executable file
Binary file not shown.
6
codecs/jxl/package.json
Normal file
6
codecs/jxl/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "jxl",
|
||||
"scripts": {
|
||||
"build": "../build-cpp.sh"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
BIN
codecs/mozjpeg_enc/mozjpeg_enc.wasm
Normal file → Executable file
BIN
codecs/mozjpeg_enc/mozjpeg_enc.wasm
Normal file → Executable file
Binary file not shown.
299
codecs/oxipng/Cargo.lock
generated
299
codecs/oxipng/Cargo.lock
generated
@@ -1,16 +1,22 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.1.0"
|
||||
name = "adler"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
|
||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
@@ -38,9 +44,9 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.2.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431"
|
||||
checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -50,9 +56,9 @@ checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.58"
|
||||
version = "1.0.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
|
||||
checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@@ -60,6 +66,12 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cloudflare-zlib"
|
||||
version = "0.2.5"
|
||||
@@ -78,6 +90,18 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab"
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "1.8.1"
|
||||
@@ -89,58 +113,57 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.3"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
|
||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.8.2"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
|
||||
checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"const_fn",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"maybe-uninit",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"cfg-if 1.0.0",
|
||||
"const_fn",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
@@ -156,27 +179,34 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.15"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
|
||||
checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.7"
|
||||
version = "0.23.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2397fc43bd5648b7117aabb3c5e62d0e62c194826ec77b0b4d0c41e62744635"
|
||||
checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
@@ -185,11 +215,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.4.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
|
||||
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
@@ -210,39 +241,42 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.72"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9f8082297d534141b30c8d39e9b1773713ab50fdbe4ff30f750d063b3bfd701"
|
||||
checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
|
||||
|
||||
[[package]]
|
||||
name = "libdeflater"
|
||||
version = "0.2.0"
|
||||
name = "libdeflate-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66dca08b13369865b2f6dca1dd05f833985cbe6c12a676b04d55f78b85e80246"
|
||||
checksum = "21e39efa87b84db3e13ff4e2dfac1e57220abcbd7fe8ec44d238f7f4f787cc1f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.8"
|
||||
name = "libdeflater"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||
checksum = "a4810980d791f26d470e2d7d91a3d4d22aa3a4b709fb7e9c5e43ee54f83a01f2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libdeflate-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
version = "2.0.0"
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.5.5"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
|
||||
checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -257,10 +291,20 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.43"
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
|
||||
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
@@ -268,9 +312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.41"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
@@ -279,9 +323,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
@@ -290,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
@@ -308,42 +352,59 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oxipng"
|
||||
version = "3.0.0"
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fd695858078338d73862ff3755f820eff0bf4f3304e4b52f22aba53463183a"
|
||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||
|
||||
[[package]]
|
||||
name = "oxipng"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea40b366cecfce76ee3b082e7e6567b82cdef75644a22442ca8584bc666ff4eb"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
"byteorder",
|
||||
"cloudflare-zlib",
|
||||
"crc",
|
||||
"crossbeam-channel",
|
||||
"image",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"libdeflater",
|
||||
"log",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.4.3",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"zopfli",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.6"
|
||||
version = "0.16.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c150bf7479fafe3dd8740dbe48cc33b2a3efb7b0fe3483aced8bbc39f6d0238d"
|
||||
checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.18"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
@@ -359,9 +420,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.3.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
|
||||
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
@@ -371,12 +432,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.7.1"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
|
||||
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
@@ -384,33 +445,63 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.20"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ef54b45ae131327a88597e2463fee4098ad6c88ba7b6af4b3987db8aad4098"
|
||||
checksum = "287f3c3f8236abb92d8b7e36797f19159df4b58f0a658cc3fb6dd3004b1f3bd3"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65c94201b44764d6d1f7e37c15a8289ed55e546c1762c7f1d57f616966e0c181"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ef146c2ad5e5f4b037cd6ce2ebb775401729b19a82040c1beac9d36c7d1428"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "squoosh-oxipng"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
"once_cell",
|
||||
"oxipng",
|
||||
"rayon",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.34"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b"
|
||||
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -418,10 +509,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "1.7.0"
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
@@ -431,19 +522,19 @@ checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.64"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a634620115e4a229108b71bde263bb4220c483b3f07f5ba514ee8d15064c4c2"
|
||||
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cfg-if 0.1.10",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.64"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e53963b583d18a5aa3aaae4b4c1cb535218246131ba22a71f05b518098571df"
|
||||
checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@@ -456,9 +547,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.64"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fcfd5ef6eec85623b4c6e844293d4516470d8f19cd72d0d12246017eb9060b8"
|
||||
checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -466,9 +557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.64"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9adff9ee0e94b926ca81b57f57f86d5545cdcb1d259e21ec9bdd95b901754c75"
|
||||
checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -479,18 +570,6 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.64"
|
||||
version = "0.2.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7b90ea6c632dd06fd765d44542e234d5e63d9bb917ecd64d79778a13bd79ae"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4079b79464426ade2a1b0177fb0ce8396ba6b4084267407e333573c666073964"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"typed-arena",
|
||||
]
|
||||
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
|
||||
|
||||
@@ -5,14 +5,23 @@ authors = ["Ingvar Stepanyan <me@rreverser.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = ["-O", "--no-validation"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
oxipng = { version = "3.0.0", default-features = false }
|
||||
wasm-bindgen = "0.2.64"
|
||||
log = { version = "0.4", features = ["release_max_level_off"] }
|
||||
oxipng = { version = "4.0.0", default-features = false, features = ["libdeflater"] }
|
||||
wasm-bindgen = "0.2.68"
|
||||
log = { version = "0.4.11", features = ["release_max_level_off"] }
|
||||
rayon = { version = "1.5.0", optional = true }
|
||||
once_cell = { version = "1.5.2", optional = true }
|
||||
crossbeam-channel = { version = "0.5.0", optional = true }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
|
||||
[features]
|
||||
parallel = ["oxipng/parallel", "rayon", "crossbeam-channel", "once_cell"]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM rust
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
RUN mkdir /opt/wabt && \
|
||||
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.17/wabt-1.0.17-ubuntu.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
||||
|
||||
RUN mkdir /opt/wasi-sdk && \
|
||||
curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-11/wasi-sdk-11.0-linux.tar.gz | tar -xzf - -C /opt/wasi-sdk --strip 1
|
||||
|
||||
ENV PATH="/opt/wabt/bin:/opt/wasi-sdk/bin:${PATH}"
|
||||
WORKDIR /src
|
||||
8
codecs/oxipng/build.sh
Executable file
8
codecs/oxipng/build.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
rm -rf pkg,{-parallel}
|
||||
wasm-pack build --target web
|
||||
RUSTFLAGS='-C target-feature=+atomics,+bulk-memory' wasm-pack build -t web -d pkg-parallel -- -Z build-std=panic_abort,std --features=parallel
|
||||
rm pkg{,-parallel}/.gitignore
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oxipng",
|
||||
"scripts": {
|
||||
"build": "../build-rust.sh"
|
||||
"build": "RUST_IMG=rustlang/rust:8bb115b1090d ../build-rust.sh ./build.sh"
|
||||
}
|
||||
}
|
||||
|
||||
5
codecs/oxipng/pkg-parallel/README.md
Normal file
5
codecs/oxipng/pkg-parallel/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# OxiPNG
|
||||
|
||||
- Source: <https://github.com/shssoichiro/oxipng>
|
||||
- Version: v3.0.0
|
||||
- License: MIT
|
||||
15
codecs/oxipng/pkg-parallel/package.json
Normal file
15
codecs/oxipng/pkg-parallel/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "squoosh-oxipng",
|
||||
"collaborators": [
|
||||
"Ingvar Stepanyan <me@rreverser.com>"
|
||||
],
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"squoosh_oxipng_bg.wasm",
|
||||
"squoosh_oxipng.js",
|
||||
"squoosh_oxipng.d.ts"
|
||||
],
|
||||
"module": "squoosh_oxipng.js",
|
||||
"types": "squoosh_oxipng.d.ts",
|
||||
"sideEffects": false
|
||||
}
|
||||
53
codecs/oxipng/pkg-parallel/squoosh_oxipng.d.ts
vendored
Normal file
53
codecs/oxipng/pkg-parallel/squoosh_oxipng.d.ts
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {number} level
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function optimise(data: Uint8Array, level: number): Uint8Array;
|
||||
/**
|
||||
* @param {number} num
|
||||
* @returns {any}
|
||||
*/
|
||||
export function worker_initializer(num: number): any;
|
||||
/**
|
||||
*/
|
||||
export function start_main_thread(): void;
|
||||
/**
|
||||
*/
|
||||
export function start_worker_thread(): void;
|
||||
|
||||
export type InitInput =
|
||||
| RequestInfo
|
||||
| URL
|
||||
| Response
|
||||
| BufferSource
|
||||
| WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly optimise: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly malloc: (a: number) => number;
|
||||
readonly free: (a: number) => void;
|
||||
readonly worker_initializer: (a: number) => number;
|
||||
readonly start_main_thread: () => void;
|
||||
readonly start_worker_thread: () => void;
|
||||
readonly __wbindgen_export_0: WebAssembly.Memory;
|
||||
readonly __wbindgen_malloc: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||
* @param {WebAssembly.Memory} maybe_memory
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function init(
|
||||
module_or_path?: InitInput | Promise<InitInput>,
|
||||
maybe_memory?: WebAssembly.Memory,
|
||||
): Promise<InitOutput>;
|
||||
196
codecs/oxipng/pkg-parallel/squoosh_oxipng.js
Normal file
196
codecs/oxipng/pkg-parallel/squoosh_oxipng.js
Normal file
@@ -0,0 +1,196 @@
|
||||
let wasm;
|
||||
let memory;
|
||||
|
||||
const heap = new Array(32).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
let cachedTextDecoder = new TextDecoder('utf-8', {
|
||||
ignoreBOM: true,
|
||||
fatal: true,
|
||||
});
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
|
||||
let cachegetUint8Memory0 = null;
|
||||
function getUint8Memory0() {
|
||||
if (
|
||||
cachegetUint8Memory0 === null ||
|
||||
cachegetUint8Memory0.buffer !== wasm.__wbindgen_export_0.buffer
|
||||
) {
|
||||
cachegetUint8Memory0 = new Uint8Array(wasm.__wbindgen_export_0.buffer);
|
||||
}
|
||||
return cachegetUint8Memory0;
|
||||
}
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().slice(ptr, ptr + len));
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
function passArray8ToWasm0(arg, malloc) {
|
||||
const ptr = malloc(arg.length * 1);
|
||||
getUint8Memory0().set(arg, ptr / 1);
|
||||
WASM_VECTOR_LEN = arg.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachegetInt32Memory0 = null;
|
||||
function getInt32Memory0() {
|
||||
if (
|
||||
cachegetInt32Memory0 === null ||
|
||||
cachegetInt32Memory0.buffer !== wasm.__wbindgen_export_0.buffer
|
||||
) {
|
||||
cachegetInt32Memory0 = new Int32Array(wasm.__wbindgen_export_0.buffer);
|
||||
}
|
||||
return cachegetInt32Memory0;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {number} level
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function optimise(data, level) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_export_1.value - 16;
|
||||
wasm.__wbindgen_export_1.value = retptr;
|
||||
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
wasm.optimise(retptr, ptr0, len0, level);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v1;
|
||||
} finally {
|
||||
wasm.__wbindgen_export_1.value += 16;
|
||||
}
|
||||
}
|
||||
|
||||
function getObject(idx) {
|
||||
return heap[idx];
|
||||
}
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 36) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @param {number} num
|
||||
* @returns {any}
|
||||
*/
|
||||
export function worker_initializer(num) {
|
||||
var ret = wasm.worker_initializer(num);
|
||||
return takeObject(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export function start_main_thread() {
|
||||
wasm.start_main_thread();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export function start_worker_thread() {
|
||||
wasm.start_worker_thread();
|
||||
}
|
||||
|
||||
async function load(module, imports, maybe_memory) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
memory = imports.wbg.memory = new WebAssembly.Memory({
|
||||
initial: 17,
|
||||
maximum: 16384,
|
||||
shared: true,
|
||||
});
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn(
|
||||
'`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n',
|
||||
e,
|
||||
);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
memory = imports.wbg.memory = maybe_memory;
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function init(input, maybe_memory) {
|
||||
if (typeof input === 'undefined') {
|
||||
input = import.meta.url.replace(/\.js$/, '_bg.wasm');
|
||||
}
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_module = function () {
|
||||
var ret = init.__wbindgen_wasm_module;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_memory = function () {
|
||||
var ret = wasm.__wbindgen_export_0;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_of_6510501edc06d65e = function (arg0, arg1) {
|
||||
var ret = Array.of(takeObject(arg0), takeObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function (arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
|
||||
if (
|
||||
typeof input === 'string' ||
|
||||
(typeof Request === 'function' && input instanceof Request) ||
|
||||
(typeof URL === 'function' && input instanceof URL)
|
||||
) {
|
||||
input = fetch(input);
|
||||
}
|
||||
|
||||
const { instance, module } = await load(await input, imports, maybe_memory);
|
||||
|
||||
wasm = instance.exports;
|
||||
init.__wbindgen_wasm_module = module;
|
||||
wasm.__wbindgen_start();
|
||||
return wasm;
|
||||
}
|
||||
|
||||
export default init;
|
||||
BIN
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm
Normal file
BIN
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm
Normal file
Binary file not shown.
12
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm.d.ts
vendored
Normal file
12
codecs/oxipng/pkg-parallel/squoosh_oxipng_bg.wasm.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function optimise(a: number, b: number, c: number, d: number): void;
|
||||
export function malloc(a: number): number;
|
||||
export function free(a: number): void;
|
||||
export function worker_initializer(a: number): number;
|
||||
export function start_main_thread(): void;
|
||||
export function start_worker_thread(): void;
|
||||
export const __wbindgen_export_0: WebAssembly.Memory;
|
||||
export function __wbindgen_malloc(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number): void;
|
||||
export function __wbindgen_start(): void;
|
||||
@@ -51,14 +51,20 @@ function getArrayU8FromWasm0(ptr, len) {
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function optimise(data, level) {
|
||||
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
wasm.optimise(8, ptr0, len0, level);
|
||||
var r0 = getInt32Memory0()[8 / 4 + 0];
|
||||
var r1 = getInt32Memory0()[8 / 4 + 1];
|
||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v1;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_export_0.value - 16;
|
||||
wasm.__wbindgen_export_0.value = retptr;
|
||||
var ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
wasm.optimise(retptr, ptr0, len0, level);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var v1 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v1;
|
||||
} finally {
|
||||
wasm.__wbindgen_export_0.value += 16;
|
||||
}
|
||||
}
|
||||
|
||||
async function load(module, imports) {
|
||||
|
||||
Binary file not shown.
1
codecs/oxipng/rust-toolchain
Normal file
1
codecs/oxipng/rust-toolchain
Normal file
@@ -0,0 +1 @@
|
||||
nightly
|
||||
@@ -1,9 +1,12 @@
|
||||
mod malloc_shim;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use oxipng::AlphaOptim;
|
||||
|
||||
#[wasm_bindgen(catch)]
|
||||
mod malloc_shim;
|
||||
|
||||
#[cfg(feature = "parallel")]
|
||||
pub mod parallel;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn optimise(data: &[u8], level: u8) -> Vec<u8> {
|
||||
let mut options = oxipng::Options::from_preset(level);
|
||||
options.alphas.insert(AlphaOptim::Black);
|
||||
|
||||
61
codecs/oxipng/src/parallel.rs
Normal file
61
codecs/oxipng/src/parallel.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use crossbeam_channel::{Sender, Receiver, bounded};
|
||||
use once_cell::sync::OnceCell;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = Array, js_name = of)]
|
||||
fn array_of_2(a: JsValue, b: JsValue) -> JsValue;
|
||||
}
|
||||
|
||||
// This is one of the parts that work around Chromium incorrectly implementing postMessage:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1075645
|
||||
//
|
||||
// rayon::ThreadPoolBuilder (used below) executes spawn handler to populate the worker pool,
|
||||
// and then blocks the current thread until each worker unblocks its (opaque) lock.
|
||||
//
|
||||
// Normally, we could use postMessage directly inside the spawn handler to
|
||||
// post module + memory + threadPtr to each worker, and the block the current thread.
|
||||
//
|
||||
// However, that bug means that postMessage is currently delayed until the next event loop,
|
||||
// which will never spin since we block the current thread, and so the other workers will
|
||||
// never be able to unblock us.
|
||||
//
|
||||
// To work around this problem, we:
|
||||
// 1) Expose `worker_initializer` that returns module + memory pair (without threadPtr)
|
||||
// that workers can be initialised with to become native threads.
|
||||
// JavaScript can postMessage this pair in advance, and asynchronously wait for workers
|
||||
// to acknowledge the receipt.
|
||||
// 2) Create a global communication channel on the Rust side using crossbeam.
|
||||
// It will be used to send threadPtr to the pre-initialised workers
|
||||
// instead of postMessage.
|
||||
// 3) Provide a separate `start_main_thread` that expects all workers to be ready,
|
||||
// and just uses the provided channel to send `threadPtr`s using the
|
||||
// shared memory and blocks the current thread until they're all grabbed.
|
||||
// 4) Provide a `worker_initializer` that is expected to be invoked from various workers,
|
||||
// reads one `threadPtr` from the shared channel and starts running it.
|
||||
static CHANNEL: OnceCell<(Sender<rayon::ThreadBuilder>, Receiver<rayon::ThreadBuilder>)> = OnceCell::new();
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn worker_initializer(num: usize) -> JsValue {
|
||||
CHANNEL.get_or_init(|| bounded(num));
|
||||
array_of_2(wasm_bindgen::module(), wasm_bindgen::memory())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn start_main_thread() {
|
||||
let (sender, _) = CHANNEL.get().unwrap();
|
||||
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(sender.capacity().unwrap())
|
||||
.spawn_handler(|thread| Ok(sender.send(thread).unwrap_throw()))
|
||||
.build_global()
|
||||
.unwrap_throw()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn start_worker_thread() {
|
||||
let (_, receiver) = CHANNEL.get().unwrap();
|
||||
receiver.recv().unwrap_throw().run()
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,9 +1,13 @@
|
||||
FROM emscripten/emsdk:1.39.19 AS wasm-tools
|
||||
ARG RUST_IMG=rust:1.47
|
||||
|
||||
FROM emscripten/emsdk:2.0.8 AS wasm-tools
|
||||
WORKDIR /opt/wasm-tools
|
||||
RUN wget -qO- https://github.com/rustwasm/wasm-pack/releases/download/v0.9.1/wasm-pack-v0.9.1-x86_64-unknown-linux-musl.tar.gz | tar -xzf - --strip 1
|
||||
|
||||
FROM rust:1.46.0-slim-buster AS rust
|
||||
FROM $RUST_IMG AS rust
|
||||
ARG RUST_IMG
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
RUN if [[ $RUST_IMG = rustlang/rust:* ]] ; then rustup component add rust-src ; fi
|
||||
COPY --from=wasm-tools /emsdk/upstream/bin/wasm-opt /emsdk/upstream/bin/clang /usr/local/bin/
|
||||
COPY --from=wasm-tools /emsdk/upstream/lib/ /usr/local/lib/
|
||||
COPY --from=wasm-tools /emsdk/upstream/emscripten/system/include/libc/ /wasm32/include/
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<script type="module">
|
||||
import webp_dec from './webp_dec.js';
|
||||
|
||||
<!doctype html>
|
||||
<script src='webp_dec.js'></script>
|
||||
<script>
|
||||
async function loadFile(src) {
|
||||
const resp = await fetch(src);
|
||||
return await resp.arrayBuffer();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const module = await webp_dec();
|
||||
webp_dec().then(async module => {
|
||||
console.log('Version:', module.version().toString(16));
|
||||
const image = await loadFile('../../example.webp');
|
||||
const imageData = module.decode(image);
|
||||
@@ -18,7 +16,5 @@
|
||||
document.body.appendChild(canvas);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
|
||||
main();
|
||||
});
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
codecs/webp/dec/webp_dec.wasm
Normal file → Executable file
BIN
codecs/webp/dec/webp_dec.wasm
Normal file → Executable file
Binary file not shown.
@@ -1,12 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<script type="module">
|
||||
import webp_enc from './webp_enc.js';
|
||||
|
||||
<!doctype html>
|
||||
<script src='webp_enc.js'></script>
|
||||
<script>
|
||||
async function loadImage(src) {
|
||||
// Load image
|
||||
const img = document.createElement('img');
|
||||
img.src = src;
|
||||
await new Promise((resolve) => (img.onload = resolve));
|
||||
await new Promise(resolve => img.onload = resolve);
|
||||
// Make canvas same size as image
|
||||
const canvas = document.createElement('canvas');
|
||||
[canvas.width, canvas.height] = [img.width, img.height];
|
||||
@@ -16,9 +15,7 @@
|
||||
return ctx.getImageData(0, 0, img.width, img.height);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const module = await webp_enc();
|
||||
|
||||
webp_enc().then(async module => {
|
||||
console.log('Version:', module.version().toString(16));
|
||||
const image = await loadImage('../../example.png');
|
||||
const result = module.encode(image.data, image.width, image.height, {
|
||||
@@ -51,13 +48,11 @@
|
||||
use_sharp_yuv: 0,
|
||||
});
|
||||
console.log('size', result.length);
|
||||
const blob = new Blob([result], { type: 'image/webp' });
|
||||
const blob = new Blob([result], {type: 'image/webp'});
|
||||
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
const img = document.createElement('img');
|
||||
img.src = blobURL;
|
||||
document.body.appendChild(img);
|
||||
}
|
||||
|
||||
main();
|
||||
});
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
codecs/webp/enc/webp_enc.wasm
Normal file → Executable file
BIN
codecs/webp/enc/webp_enc.wasm
Normal file → Executable file
Binary file not shown.
50
codecs/wp2/Makefile
Normal file
50
codecs/wp2/Makefile
Normal file
@@ -0,0 +1,50 @@
|
||||
CODEC_URL = https://chromium.googlesource.com/codecs/libwebp2/+archive/c90b5b476004c9a98731ae1c175cebab5de50fbf.tar.gz
|
||||
CODEC_DIR = node_modules/wp2
|
||||
CODEC_BUILD_DIR := $(CODEC_DIR)/.build
|
||||
CODEC_OUT := $(CODEC_BUILD_DIR)/libwebp2.a
|
||||
|
||||
OUT_JS = enc/wp2_enc.js dec/wp2_dec.js
|
||||
OUT_WASM = $(OUT_JS:.js=.wasm)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_JS)
|
||||
|
||||
%.js: %.cpp $(CODEC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DIR) \
|
||||
${CXXFLAGS} \
|
||||
${LDFLAGS} \
|
||||
--bind \
|
||||
--closure 1 \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s MODULARIZE=1 \
|
||||
-s TEXTDECODER=2 \
|
||||
-s ENVIRONMENT='worker' \
|
||||
-s EXPORT_ES6=1 \
|
||||
-s EXPORT_NAME="$(basename $(@F))" \
|
||||
-o $@ \
|
||||
$+
|
||||
|
||||
$(CODEC_OUT): $(CODEC_BUILD_DIR)/Makefile
|
||||
cd $(CODEC_BUILD_DIR) && \
|
||||
$(MAKE)
|
||||
|
||||
$(CODEC_BUILD_DIR)/Makefile: $(CODEC_DIR)/CMakeLists.txt
|
||||
mkdir -p $(CODEC_BUILD_DIR)
|
||||
cd $(CODEC_BUILD_DIR) && \
|
||||
emcmake cmake \
|
||||
-DWP2_ENABLE_SIMD=0 \
|
||||
-DWP2_BUILD_TESTS=0 \
|
||||
-DWP2_ENABLE_TESTS=0 \
|
||||
-DWP2_BUILD_EXAMPLES=0 \
|
||||
-DWP2_BUILD_EXTRAS=0 \
|
||||
../
|
||||
|
||||
$(CODEC_DIR)/CMakeLists.txt:
|
||||
mkdir -p $(CODEC_DIR)
|
||||
curl -sL $(CODEC_URL) | tar xz -C $(CODEC_DIR)
|
||||
|
||||
clean:
|
||||
$(RM) $(OUT_JS) $(OUT_WASM)
|
||||
$(MAKE) -C $(CODEC_BUILD_DIR) clean
|
||||
17
codecs/wp2/dec/README.md
Normal file
17
codecs/wp2/dec/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# WebP2 decoder
|
||||
|
||||
- Source: <https://chromium.googlesource.com/codecs/libwebp2>
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Docker
|
||||
|
||||
## Example
|
||||
|
||||
N/A
|
||||
|
||||
## API
|
||||
|
||||
### `ImageData decode(uint8_t* image_buffer, int image_width, int image_height)`
|
||||
|
||||
Decodes the given WebP2 buffer into raw RGBA represented as an `ImageData`.
|
||||
24
codecs/wp2/dec/wp2_dec.cpp
Normal file
24
codecs/wp2/dec/wp2_dec.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <cstdio>
|
||||
#include "src/wp2/decode.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
||||
thread_local const val ImageData = val::global("ImageData");
|
||||
|
||||
val decode(std::string image_in) {
|
||||
WP2::ArgbBuffer buffer(WP2_rgbA_32);
|
||||
WP2Status status = WP2::Decode(image_in, &buffer);
|
||||
if (status != WP2_STATUS_OK) {
|
||||
return val::null();
|
||||
}
|
||||
return ImageData.new_(
|
||||
Uint8ClampedArray.new_(typed_memory_view(buffer.stride * buffer.height, buffer.GetRow8(0))),
|
||||
buffer.width, buffer.height);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
function("decode", &decode);
|
||||
}
|
||||
7
codecs/wp2/dec/wp2_dec.d.ts
vendored
Normal file
7
codecs/wp2/dec/wp2_dec.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface WP2Module extends EmscriptenWasm.Module {
|
||||
decode(data: BufferSource): ImageData | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WP2Module>;
|
||||
|
||||
export default moduleFactory;
|
||||
1185
codecs/wp2/dec/wp2_dec.js
Normal file
1185
codecs/wp2/dec/wp2_dec.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codecs/wp2/dec/wp2_dec.wasm
Executable file
BIN
codecs/wp2/dec/wp2_dec.wasm
Executable file
Binary file not shown.
17
codecs/wp2/enc/README.md
Normal file
17
codecs/wp2/enc/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# WebP2 encoder
|
||||
|
||||
- Source: <https://chromium.googlesource.com/codecs/libwebp2>
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Docker
|
||||
|
||||
## Example
|
||||
|
||||
N/A
|
||||
|
||||
## API
|
||||
|
||||
### `UInt8Array encode(uint8_t* image_buffer, int image_width, int image_height, WP2::EncoderConfig config)`
|
||||
|
||||
Encodes the given image with given dimension to WebP2.
|
||||
38
codecs/wp2/enc/wp2_enc.cpp
Normal file
38
codecs/wp2/enc/wp2_enc.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <cstdio>
|
||||
#include "src/wp2/encode.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
val encode(std::string image_in, int image_width, int image_height, WP2::EncoderConfig config) {
|
||||
uint8_t* image_buffer = (uint8_t*)image_in.c_str();
|
||||
WP2::ArgbBuffer src = WP2::ArgbBuffer();
|
||||
WP2Status status =
|
||||
src.Import(WP2_rgbA_32, // Format. WP2_RGBA_32 is the same but NOT premultiplied alpha
|
||||
image_width, image_height, image_buffer, 4 * image_width);
|
||||
if (status != WP2_STATUS_OK) {
|
||||
return val::null();
|
||||
}
|
||||
|
||||
WP2::MemoryWriter memory_writer;
|
||||
status = WP2::Encode(src, &memory_writer, config);
|
||||
if (status != WP2_STATUS_OK) {
|
||||
return val::null();
|
||||
}
|
||||
|
||||
return Uint8Array.new_(typed_memory_view(memory_writer.size_, memory_writer.mem_));
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<WP2::EncoderConfig>("WP2EncoderConfig")
|
||||
.field("quality", &WP2::EncoderConfig::quality)
|
||||
.field("alpha_quality", &WP2::EncoderConfig::alpha_quality)
|
||||
.field("speed", &WP2::EncoderConfig::speed)
|
||||
.field("pass", &WP2::EncoderConfig::pass)
|
||||
.field("sns", &WP2::EncoderConfig::sns);
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
||||
20
codecs/wp2/enc/wp2_enc.d.ts
vendored
Normal file
20
codecs/wp2/enc/wp2_enc.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface EncodeOptions {
|
||||
quality: number;
|
||||
alpha_quality: number;
|
||||
speed: number;
|
||||
pass: number;
|
||||
sns: number;
|
||||
}
|
||||
|
||||
export interface WP2Module extends EmscriptenWasm.Module {
|
||||
encode(
|
||||
data: BufferSource,
|
||||
width: number,
|
||||
height: number,
|
||||
options: EncodeOptions,
|
||||
): Uint8Array | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WP2Module>;
|
||||
|
||||
export default moduleFactory;
|
||||
1277
codecs/wp2/enc/wp2_enc.js
Normal file
1277
codecs/wp2/enc/wp2_enc.js
Normal file
File diff suppressed because it is too large
Load Diff
BIN
codecs/wp2/enc/wp2_enc.wasm
Executable file
BIN
codecs/wp2/enc/wp2_enc.wasm
Executable file
Binary file not shown.
6
codecs/wp2/package.json
Normal file
6
codecs/wp2/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "wp2",
|
||||
"scripts": {
|
||||
"build": "../build-cpp.sh"
|
||||
}
|
||||
}
|
||||
1
emscripten-types.d.ts
vendored
1
emscripten-types.d.ts
vendored
@@ -10,6 +10,7 @@ declare namespace EmscriptenWasm {
|
||||
// Options object for modularized Emscripten files. Shoe-horned by @surma.
|
||||
// FIXME: This an incomplete definition!
|
||||
interface ModuleOpts {
|
||||
mainScriptUrlOrBlob?: string;
|
||||
noInitialRun?: boolean;
|
||||
locateFile?: (url: string) => string;
|
||||
onRuntimeInitialized?: () => void;
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
import { promises as fsp, readFileSync } from 'fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { promisify } from 'util';
|
||||
import { parse as parsePath, resolve as resolvePath, dirname } from 'path';
|
||||
import {
|
||||
parse as parsePath,
|
||||
resolve as resolvePath,
|
||||
dirname,
|
||||
normalize as nomalizePath,
|
||||
} from 'path';
|
||||
|
||||
import postcss from 'postcss';
|
||||
import postCSSNested from 'postcss-nested';
|
||||
@@ -55,10 +60,15 @@ export default function (resolveFileUrl) {
|
||||
hashToId = new Map();
|
||||
pathToResult = new Map();
|
||||
|
||||
const cssPaths = await globP('src/**/*.css', {
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
});
|
||||
const cssPaths = (
|
||||
await globP('src/**/*.css', {
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
})
|
||||
).map((cssPath) =>
|
||||
// glob() returns windows paths with a forward slash. Normalise it:
|
||||
nomalizePath(cssPath),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
cssPaths.map(async (path) => {
|
||||
@@ -73,6 +83,7 @@ export default function (resolveFileUrl) {
|
||||
getJSON(_, json) {
|
||||
moduleJSON = json;
|
||||
},
|
||||
root: '',
|
||||
}),
|
||||
postCSSUrl({
|
||||
url: ({ relativePath, url }) => {
|
||||
@@ -146,7 +157,7 @@ export default function (resolveFileUrl) {
|
||||
async load(id) {
|
||||
if (id === appendCssModule) return appendCssSource;
|
||||
if (id.startsWith(sourcePrefix)) {
|
||||
const path = id.slice(sourcePrefix.length);
|
||||
const path = nomalizePath(id.slice(sourcePrefix.length));
|
||||
|
||||
if (!pathToResult.has(path)) {
|
||||
throw Error(`Cannot find ${path} in pathToResult`);
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
*/
|
||||
import { promisify } from 'util';
|
||||
import * as path from 'path';
|
||||
import { promises as fsp } from 'fs';
|
||||
|
||||
import { posix } from 'path';
|
||||
import glob from 'glob';
|
||||
import { promises as fsp } from 'fs';
|
||||
|
||||
const globP = promisify(glob);
|
||||
const autoGenComment =
|
||||
@@ -33,7 +33,7 @@ export default function () {
|
||||
const workerBasePath = path.join(process.cwd(), 'src', 'features-worker');
|
||||
|
||||
const featuresWorkerTsNames = workerImports.map((tsImport) => [
|
||||
path.relative(workerBasePath, tsImport),
|
||||
path.relative(workerBasePath, tsImport).split(path.sep).join(posix.sep),
|
||||
path.basename(tsImport),
|
||||
]);
|
||||
|
||||
@@ -78,7 +78,10 @@ export default function () {
|
||||
);
|
||||
|
||||
const featuresWorkerBridgeTsNames = workerImports.map((tsImport) => [
|
||||
path.relative(workerBridgeBasePath, tsImport),
|
||||
path
|
||||
.relative(workerBridgeBasePath, tsImport)
|
||||
.split(path.sep)
|
||||
.join(posix.sep),
|
||||
path.basename(tsImport),
|
||||
]);
|
||||
|
||||
@@ -165,7 +168,10 @@ export default function () {
|
||||
previousJoinedMetas = joinedMetas;
|
||||
|
||||
const getTsName = (tsImport) => [
|
||||
path.relative(featureMetaBasePath, tsImport),
|
||||
path
|
||||
.relative(featureMetaBasePath, tsImport)
|
||||
.split(path.sep)
|
||||
.join(posix.sep),
|
||||
path.basename(tsImport.slice(0, -'/shared/meta'.length)),
|
||||
];
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { promisify } from 'util';
|
||||
|
||||
import path from 'path';
|
||||
import { posix } from 'path';
|
||||
import glob from 'glob';
|
||||
|
||||
const globP = promisify(glob);
|
||||
@@ -36,9 +37,9 @@ export default function initialCssPlugin() {
|
||||
// Sort the matches so the parentmost items appear first.
|
||||
// This is a bit of a hack, but it means the util stuff appears in the cascade first.
|
||||
const sortedMatches = matches
|
||||
.map((match) => match.split(path.sep))
|
||||
.map((match) => path.normalize(match).split(path.sep))
|
||||
.sort((a, b) => a.length - b.length)
|
||||
.map((match) => path.join(...match));
|
||||
.map((match) => posix.join(...match));
|
||||
|
||||
const imports = sortedMatches
|
||||
.map((id, i) => `import css${i} from 'css:${id}';\n`)
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
if (!self.<%- amdFunctionName %>) {
|
||||
const singleRequire = async name => {
|
||||
if (name === 'require') return require;
|
||||
let url = name.slice(1) + '.js';
|
||||
let url;
|
||||
if (name.startsWith(location.origin)) {
|
||||
url = name.slice(location.origin.length);
|
||||
} else {
|
||||
url = name.slice(1) + '.js';
|
||||
}
|
||||
if (!url.startsWith('/c/')) {
|
||||
url = '/c' + url;
|
||||
}
|
||||
@@ -77,7 +82,7 @@ if (!self.<%- amdFunctionName %>) {
|
||||
})
|
||||
).then(deps => {
|
||||
const facValue = factory(...deps);
|
||||
if(!exports.default) {
|
||||
if (!exports.default) {
|
||||
exports.default = facValue;
|
||||
}
|
||||
return exports;
|
||||
|
||||
@@ -17,9 +17,12 @@ import { promisify } from 'util';
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import glob from 'glob';
|
||||
import { sync as whichSync } from 'which';
|
||||
|
||||
const globP = promisify(glob);
|
||||
|
||||
const tscPath = whichSync('tsc');
|
||||
|
||||
const extRe = /\.tsx?$/;
|
||||
|
||||
function loadConfig(mainPath) {
|
||||
@@ -59,7 +62,7 @@ export default function simpleTS(mainPath, { noBuild, watch } = {}) {
|
||||
}
|
||||
tsBuildDone = Promise.resolve().then(async () => {
|
||||
await new Promise((resolve) => {
|
||||
const proc = spawn('tsc', args, {
|
||||
const proc = spawn(tscPath, args, {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
@@ -75,7 +78,7 @@ export default function simpleTS(mainPath, { noBuild, watch } = {}) {
|
||||
|
||||
if (watch) {
|
||||
tsBuildDone.then(() => {
|
||||
spawn('tsc', [...args, '--watch', '--preserveWatchOutput'], {
|
||||
spawn(tscPath, [...args, '--watch', '--preserveWatchOutput'], {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
});
|
||||
@@ -124,6 +127,15 @@ export default function simpleTS(mainPath, { noBuild, watch } = {}) {
|
||||
relative(process.cwd(), id),
|
||||
).replace(extRe, '.js');
|
||||
|
||||
console.log(
|
||||
`simple-ts mapping`,
|
||||
id,
|
||||
'to',
|
||||
newId,
|
||||
'with outDir',
|
||||
config.options.outDir,
|
||||
);
|
||||
|
||||
return fsp.readFile(newId, { encoding: 'utf8' });
|
||||
},
|
||||
};
|
||||
|
||||
8273
package-lock.json
generated
8273
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,8 @@
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"serve": "^11.3.2",
|
||||
"typescript": "^4.0.5"
|
||||
"typescript": "^4.0.5",
|
||||
"which": "^2.0.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -49,5 +50,8 @@
|
||||
"*.{js,css,json,md,ts,tsx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"wasm-feature-detect": "^1.2.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export default async function ({ watch }) {
|
||||
{ resolveFileUrl, resolveImportMeta },
|
||||
clientBundlePlugin(
|
||||
{
|
||||
external: ['worker_threads'],
|
||||
plugins: [
|
||||
{ resolveFileUrl, resolveImportMeta },
|
||||
OMT({ loader: await omtLoaderPromise }),
|
||||
@@ -115,7 +116,7 @@ export default async function ({ watch }) {
|
||||
commonjs(),
|
||||
resolve(),
|
||||
replace({ __PRERENDER__: false, __PRODUCTION__: isProduction }),
|
||||
terser({ module: true }),
|
||||
isProduction ? terser({ module: true }) : {},
|
||||
],
|
||||
preserveEntrySignatures: false,
|
||||
},
|
||||
@@ -124,6 +125,9 @@ export default async function ({ watch }) {
|
||||
format: 'amd',
|
||||
chunkFileNames: jsFileName,
|
||||
entryFileNames: jsFileName,
|
||||
// This is needed because emscripten's workers use 'this', so they trigger all kinds of interop things,
|
||||
// such as double-wrapping objects in { default }.
|
||||
interop: false,
|
||||
},
|
||||
resolveFileUrl,
|
||||
),
|
||||
|
||||
@@ -156,11 +156,11 @@ export default class Options extends Component<Props, State> {
|
||||
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
||||
{supportedEncoderMap ? (
|
||||
<Select
|
||||
value={encoderState ? encoderState.type : ''}
|
||||
value={encoderState ? encoderState.type : 'identity'}
|
||||
onChange={this.onEncoderTypeChange}
|
||||
large
|
||||
>
|
||||
<option value="">Original Image</option>
|
||||
<option value="identity">Original Image</option>
|
||||
{Object.entries(supportedEncoderMap).map(([type, encoder]) => (
|
||||
<option value={type}>{encoder.meta.label}</option>
|
||||
))}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
CopyAcrossIconProps,
|
||||
} from 'client/lazy-app/icons';
|
||||
import 'shared/initial-app/custom-els/loading-spinner';
|
||||
import { SourceImage } from '../../compress';
|
||||
import { SourceImage } from '../';
|
||||
|
||||
interface Props {
|
||||
loading: boolean;
|
||||
|
||||
@@ -27,7 +27,7 @@ import Options from './Options';
|
||||
import ResultCache from './result-cache';
|
||||
import { cleanMerge, cleanSet } from '../util/clean-modify';
|
||||
import './custom-els/MultiPanel';
|
||||
import Results from './results';
|
||||
import Results from './Results';
|
||||
import WorkerBridge from '../worker-bridge';
|
||||
import { resize } from 'features/processors/resize/client';
|
||||
import type SnackBarElement from 'shared/initial-app/custom-els/snack-bar';
|
||||
@@ -101,6 +101,12 @@ async function decodeImage(
|
||||
if (mimeType === 'image/webp') {
|
||||
return await workerBridge.webpDecode(signal, blob);
|
||||
}
|
||||
if (mimeType === 'image/jpegxl') {
|
||||
return await workerBridge.jxlDecode(signal, blob);
|
||||
}
|
||||
if (mimeType === 'image/webp2') {
|
||||
return await workerBridge.wp2Decode(signal, blob);
|
||||
}
|
||||
// If it's not one of those types, fall through and try built-in decoding for a laugh.
|
||||
}
|
||||
return await abortable(signal, builtinDecode(blob));
|
||||
@@ -360,6 +366,10 @@ export default class Compress extends Component<Props, State> {
|
||||
|
||||
componentWillUnmount(): void {
|
||||
updateDocumentTitle();
|
||||
this.mainAbortController.abort();
|
||||
for (const controller of this.sideAbortControllers) {
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State): void {
|
||||
@@ -495,6 +505,8 @@ export default class Compress extends Component<Props, State> {
|
||||
const needsProcessing =
|
||||
needsPreprocessing ||
|
||||
!latestSideJob.processorState ||
|
||||
// If we're going to or from 'original image' we should reprocess
|
||||
!!latestSideJob.encoderState !== !!sideJobStates[i].encoderState ||
|
||||
!processorStateEquivalent(
|
||||
latestSideJob.processorState,
|
||||
sideJobStates[i].processorState,
|
||||
|
||||
@@ -148,7 +148,9 @@ const magicNumberToMimeType = new Map<RegExp, string>([
|
||||
[/^II*/, 'image/tiff'],
|
||||
[/^MM\x00*/, 'image/tiff'],
|
||||
[/^RIFF....WEBPVP8[LX ]/, 'image/webp'],
|
||||
[/^\xF4\xFF\x6F/, 'image/webp2'],
|
||||
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
||||
[/^\xff\x0a/, 'image/jpegxl'],
|
||||
]);
|
||||
|
||||
export async function sniffMimeType(blob: Blob): Promise<string> {
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
|
||||
/c/*
|
||||
Cache-Control: max-age=31536000
|
||||
|
||||
# COOP+COEP for WebAssembly threads.
|
||||
/*
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
|
||||
32
src/features/decoders/jxl/worker/jxlDecode.ts
Normal file
32
src/features/decoders/jxl/worker/jxlDecode.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import 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';
|
||||
|
||||
let emscriptenModule: Promise<JXLModule>;
|
||||
|
||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(jxlDecoder, wasmUrl);
|
||||
}
|
||||
|
||||
const [module, data] = await Promise.all([
|
||||
emscriptenModule,
|
||||
blobToArrayBuffer(blob),
|
||||
]);
|
||||
|
||||
const result = module.decode(data);
|
||||
if (!result) throw new Error('Decoding error');
|
||||
return result;
|
||||
}
|
||||
32
src/features/decoders/wp2/worker/wp2Decode.ts
Normal file
32
src/features/decoders/wp2/worker/wp2Decode.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import 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';
|
||||
|
||||
let emscriptenModule: Promise<WP2Module>;
|
||||
|
||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(wp2Decoder, wasmUrl);
|
||||
}
|
||||
|
||||
const [module, data] = await Promise.all([
|
||||
emscriptenModule,
|
||||
blobToArrayBuffer(blob),
|
||||
]);
|
||||
|
||||
const result = module.decode(data);
|
||||
if (!result) throw new Error('Decoding error');
|
||||
return result;
|
||||
}
|
||||
@@ -10,20 +10,34 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import avifEncoder, { AVIFModule } from 'codecs/avif/enc/avif_enc';
|
||||
import type { AVIFModule } from 'codecs/avif/enc/avif_enc';
|
||||
import type { EncodeOptions } from '../shared/meta';
|
||||
import wasmUrl from 'url:codecs/avif/enc/avif_enc.wasm';
|
||||
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 { threads } from 'wasm-feature-detect';
|
||||
|
||||
let emscriptenModule: Promise<AVIFModule>;
|
||||
|
||||
async function init() {
|
||||
if (await threads()) {
|
||||
const avifEncoder = await import('codecs/avif/enc/avif_enc_mt');
|
||||
return initEmscriptenModule<AVIFModule>(
|
||||
avifEncoder.default,
|
||||
wasmUrlWithMT,
|
||||
workerUrl,
|
||||
);
|
||||
}
|
||||
const avifEncoder = await import('codecs/avif/enc/avif_enc.js');
|
||||
return initEmscriptenModule(avifEncoder.default, wasmUrlWithoutMT);
|
||||
}
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(avifEncoder, wasmUrl);
|
||||
}
|
||||
if (!emscriptenModule) emscriptenModule = init();
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.encode(data.data, data.width, data.height, options);
|
||||
|
||||
189
src/features/encoders/jxl/client/index.tsx
Normal file
189
src/features/encoders/jxl/client/index.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import { EncodeOptions } from '../shared/meta';
|
||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||
import { h, Component } from 'preact';
|
||||
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
|
||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
workerBridge: WorkerBridge,
|
||||
imageData: ImageData,
|
||||
options: EncodeOptions,
|
||||
) => workerBridge.jxlEncode(signal, imageData, options);
|
||||
|
||||
interface Props {
|
||||
options: EncodeOptions;
|
||||
onChange(newOptions: EncodeOptions): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
options: EncodeOptions;
|
||||
effort: number;
|
||||
quality: number;
|
||||
progressive: boolean;
|
||||
edgePreservingFilter: number;
|
||||
lossless: boolean;
|
||||
slightLoss: boolean;
|
||||
}
|
||||
|
||||
const maxSpeed = 7;
|
||||
|
||||
export class Options extends Component<Props, State> {
|
||||
static getDerivedStateFromProps(
|
||||
props: Props,
|
||||
state: State,
|
||||
): Partial<State> | null {
|
||||
if (state.options && shallowEqual(state.options, props.options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { options } = props;
|
||||
|
||||
// Create default form state from options
|
||||
return {
|
||||
options,
|
||||
effort: maxSpeed - options.speed,
|
||||
quality: options.quality,
|
||||
progressive: options.progressive,
|
||||
edgePreservingFilter: options.epf,
|
||||
lossless: options.quality === 100,
|
||||
slightLoss: options.lossyPalette,
|
||||
};
|
||||
}
|
||||
|
||||
// The rest of the defaults are set in getDerivedStateFromProps
|
||||
state: State = {
|
||||
lossless: false,
|
||||
} as State;
|
||||
|
||||
private _inputChangeCallbacks = new Map<string, (event: Event) => void>();
|
||||
|
||||
private _inputChange = (prop: keyof State, type: 'number' | 'boolean') => {
|
||||
// Cache the callback for performance
|
||||
if (!this._inputChangeCallbacks.has(prop)) {
|
||||
this._inputChangeCallbacks.set(prop, (event: Event) => {
|
||||
const formEl = event.target as HTMLInputElement | HTMLSelectElement;
|
||||
const newVal =
|
||||
type === 'boolean'
|
||||
? 'checked' in formEl
|
||||
? formEl.checked
|
||||
: !!formEl.value
|
||||
: Number(formEl.value);
|
||||
|
||||
const newState: Partial<State> = {
|
||||
[prop]: newVal,
|
||||
};
|
||||
|
||||
const optionState = {
|
||||
...this.state,
|
||||
...newState,
|
||||
};
|
||||
|
||||
const newOptions: EncodeOptions = {
|
||||
speed: maxSpeed - optionState.effort,
|
||||
quality: optionState.lossless ? 100 : optionState.quality,
|
||||
progressive: optionState.progressive,
|
||||
epf: optionState.edgePreservingFilter,
|
||||
nearLossless: 0,
|
||||
lossyPalette: optionState.lossless ? optionState.slightLoss : false,
|
||||
};
|
||||
|
||||
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||
newState.options = newOptions;
|
||||
|
||||
this.setState(newState);
|
||||
|
||||
this.props.onChange(newOptions);
|
||||
});
|
||||
}
|
||||
|
||||
return this._inputChangeCallbacks.get(prop)!;
|
||||
};
|
||||
|
||||
render(
|
||||
{}: Props,
|
||||
{
|
||||
effort,
|
||||
quality,
|
||||
progressive,
|
||||
edgePreservingFilter,
|
||||
lossless,
|
||||
slightLoss,
|
||||
}: State,
|
||||
) {
|
||||
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
||||
// gathering the data.
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
name="lossless"
|
||||
checked={lossless}
|
||||
onChange={this._inputChange('lossless', 'boolean')}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
<Expander>
|
||||
{lossless && (
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
name="slightLoss"
|
||||
checked={slightLoss}
|
||||
onChange={this._inputChange('slightLoss', 'boolean')}
|
||||
/>
|
||||
Slight loss
|
||||
</label>
|
||||
)}
|
||||
</Expander>
|
||||
<Expander>
|
||||
{!lossless && (
|
||||
<div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="99.9"
|
||||
step="0.1"
|
||||
value={quality}
|
||||
onInput={this._inputChange('quality', 'number')}
|
||||
>
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="3"
|
||||
value={edgePreservingFilter}
|
||||
onInput={this._inputChange('edgePreservingFilter', 'number')}
|
||||
>
|
||||
Edge preserving filter:
|
||||
</Range>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
name="progressive"
|
||||
checked={progressive}
|
||||
onChange={this._inputChange('progressive', 'boolean')}
|
||||
/>
|
||||
Progressive rendering
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max={maxSpeed - 1}
|
||||
value={effort}
|
||||
onInput={this._inputChange('effort', 'number')}
|
||||
>
|
||||
Effort:
|
||||
</Range>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
import type { EncodeOptions } from 'codecs/jxl/enc/jxl_enc';
|
||||
|
||||
export { EncodeOptions };
|
||||
|
||||
export const label = 'JPEG XL (beta)';
|
||||
export const mimeType = 'image/jpegxl';
|
||||
export const extension = 'jxl';
|
||||
export const defaultOptions: EncodeOptions = {
|
||||
speed: 5,
|
||||
quality: 50,
|
||||
progressive: false,
|
||||
epf: 2,
|
||||
nearLossless: 0,
|
||||
lossyPalette: false,
|
||||
};
|
||||
33
src/features/encoders/jxl/worker/jxlEncode.ts
Normal file
33
src/features/encoders/jxl/worker/jxlEncode.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import jxlEncoder, { JXLModule } from 'codecs/jxl/enc/jxl_enc';
|
||||
import wasmUrl from 'url:codecs/jxl/enc/jxl_enc.wasm';
|
||||
import { initEmscriptenModule } from 'features/worker-utils';
|
||||
import type { EncodeOptions } from '../shared/meta';
|
||||
|
||||
let emscriptenModule: Promise<JXLModule>;
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(jxlEncoder, wasmUrl);
|
||||
}
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.encode(data.data, data.width, data.height, options);
|
||||
if (!result) throw new Error('Encoding error.');
|
||||
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||
return result.buffer as ArrayBuffer;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
@@ -10,17 +10,76 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import initOxiWasm, { optimise } from 'codecs/oxipng/pkg';
|
||||
import oxiWasmUrl from 'url:codecs/oxipng/pkg/squoosh_oxipng_bg.wasm';
|
||||
import initOxiWasmST, {
|
||||
optimise as optimiseST,
|
||||
} from 'codecs/oxipng/pkg/squoosh_oxipng';
|
||||
import initOxiWasmMT, {
|
||||
worker_initializer,
|
||||
start_main_thread,
|
||||
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 { threads } from 'wasm-feature-detect';
|
||||
import workerURL from 'omt:./sub-worker';
|
||||
import type { WorkerInit } from './sub-worker';
|
||||
|
||||
let wasmReady: Promise<unknown>;
|
||||
function initWorker(worker: Worker, workerInit: WorkerInit) {
|
||||
return new Promise<void>((resolve) => {
|
||||
worker.postMessage(workerInit);
|
||||
worker.addEventListener('message', () => resolve(), { once: true });
|
||||
});
|
||||
}
|
||||
|
||||
async function initMT() {
|
||||
const num = navigator.hardwareConcurrency;
|
||||
|
||||
// First, let browser fetch and spawn Workers for our pool in the background.
|
||||
// This is fairly expensive, so we want to start it as early as possible.
|
||||
const workers = Array.from({ length: num }, () => new Worker(workerURL));
|
||||
|
||||
// Meanwhile, asynchronously compile, instantiate and initialise Wasm on our main thread.
|
||||
await initOxiWasmMT(oxiWasmUrlMT);
|
||||
|
||||
// Get module+memory from the Wasm instance.
|
||||
//
|
||||
// Ideally we wouldn't go via Wasm bindings here, since both are just JS variables, but memory is
|
||||
// currently not exposed on the Wasm instance correctly by wasm-bindgen.
|
||||
const workerInit: WorkerInit = worker_initializer(num);
|
||||
|
||||
// Once done, we want to send module+memory to each Worker so that they instantiate Wasm too.
|
||||
// While doing so, we need to wait for Workers to acknowledge that they have received our message.
|
||||
// Ideally this shouldn't be necessary, but Chromium currently doesn't conform to the spec:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1075645
|
||||
//
|
||||
// If we didn't do this ping-pong game, the `start_main_thread` below would block the current
|
||||
// thread on an atomic before even *sending* the `postMessage` containing memory,
|
||||
// so Workers would never be able to unblock us back.
|
||||
await Promise.all(workers.map((worker) => initWorker(worker, workerInit)));
|
||||
|
||||
// Finally, instantiate rayon pool - this will use shared Wasm memory to send tasks to the
|
||||
// Workers and then block until they're all ready.
|
||||
start_main_thread();
|
||||
|
||||
return optimiseMT;
|
||||
}
|
||||
|
||||
async function initST() {
|
||||
await initOxiWasmST(oxiWasmUrlST);
|
||||
return optimiseST;
|
||||
}
|
||||
|
||||
let wasmReady: Promise<typeof optimiseMT | typeof optimiseST>;
|
||||
|
||||
export default async function encode(
|
||||
data: ArrayBuffer,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!wasmReady) wasmReady = initOxiWasm(oxiWasmUrl);
|
||||
await wasmReady;
|
||||
if (!wasmReady) {
|
||||
wasmReady = (await threads()) ? initMT() : initST();
|
||||
}
|
||||
|
||||
const optimise = await wasmReady;
|
||||
return optimise(new Uint8Array(data), options.level).buffer;
|
||||
}
|
||||
|
||||
25
src/features/encoders/oxiPNG/worker/sub-worker/index.ts
Normal file
25
src/features/encoders/oxiPNG/worker/sub-worker/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import initOxiPNG, {
|
||||
start_worker_thread,
|
||||
} from 'codecs/oxipng/pkg-parallel/squoosh_oxipng';
|
||||
|
||||
export type WorkerInit = [WebAssembly.Module, WebAssembly.Memory];
|
||||
|
||||
addEventListener(
|
||||
'message',
|
||||
async (event) => {
|
||||
// Tell the "main" thread that we've received the message.
|
||||
//
|
||||
// At this point, the "main" thread can run Wasm that
|
||||
// will synchronously block waiting on other atomics.
|
||||
//
|
||||
// Note that we don't need to wait for Wasm instantiation here - it's
|
||||
// better to start main thread as early as possible, and then it blocks
|
||||
// on a shared atomic anyway until Worker is fully ready.
|
||||
// @ts-ignore
|
||||
postMessage(null);
|
||||
|
||||
await initOxiPNG(...(event.data as WorkerInit));
|
||||
start_worker_thread();
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
113
src/features/encoders/wp2/client/index.tsx
Normal file
113
src/features/encoders/wp2/client/index.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { EncodeOptions } from '../shared/meta';
|
||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||
import { h, Component } from 'preact';
|
||||
import { inputFieldValueAsNumber, preventDefault } from 'client/lazy-app/util';
|
||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
workerBridge: WorkerBridge,
|
||||
imageData: ImageData,
|
||||
options: EncodeOptions,
|
||||
) => workerBridge.wp2Encode(signal, imageData, options);
|
||||
|
||||
interface Props {
|
||||
options: EncodeOptions;
|
||||
onChange(newOptions: EncodeOptions): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showAdvanced: boolean;
|
||||
}
|
||||
|
||||
export class Options extends Component<Props, State> {
|
||||
state: State = {
|
||||
showAdvanced: false,
|
||||
};
|
||||
|
||||
private onChange = (event: Event) => {
|
||||
const form = (event.currentTarget as HTMLInputElement).closest(
|
||||
'form',
|
||||
) as HTMLFormElement;
|
||||
const { options } = this.props;
|
||||
const newOptions: EncodeOptions = {
|
||||
quality: inputFieldValueAsNumber(form.quality, options.quality),
|
||||
alpha_quality: inputFieldValueAsNumber(
|
||||
form.alpha_quality,
|
||||
options.alpha_quality,
|
||||
),
|
||||
speed: inputFieldValueAsNumber(form.speed, options.speed),
|
||||
pass: inputFieldValueAsNumber(form.pass, options.pass),
|
||||
sns: inputFieldValueAsNumber(form.sns, options.sns),
|
||||
};
|
||||
this.props.onChange(newOptions);
|
||||
};
|
||||
|
||||
render({ options }: Props) {
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="quality"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="alpha_quality"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.alpha_quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Alpha Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="speed"
|
||||
min="0"
|
||||
max="9"
|
||||
step="1"
|
||||
value={options.speed}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Speed:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="pass"
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
value={options.pass}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Pass:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="sns"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.sns}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Spatial noise shaping:
|
||||
</Range>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,4 +10,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
import type { EncodeOptions } from 'codecs/wp2/enc/wp2_enc';
|
||||
|
||||
export { EncodeOptions };
|
||||
|
||||
export const label = 'WebP v2 (unstable)';
|
||||
export const mimeType = 'image/webp2';
|
||||
export const extension = 'wp2';
|
||||
export const defaultOptions: EncodeOptions = {
|
||||
quality: 75,
|
||||
alpha_quality: 100,
|
||||
speed: 5,
|
||||
pass: 1,
|
||||
sns: 50,
|
||||
};
|
||||
33
src/features/encoders/wp2/worker/wp2Encode.ts
Normal file
33
src/features/encoders/wp2/worker/wp2Encode.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import wp2Encoder, { WP2Module } from 'codecs/wp2/enc/wp2_enc';
|
||||
import wasmUrl from 'url:codecs/wp2/enc/wp2_enc.wasm';
|
||||
import { initEmscriptenModule } from 'features/worker-utils';
|
||||
import type { EncodeOptions } from '../shared/meta';
|
||||
|
||||
let emscriptenModule: Promise<WP2Module>;
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(wp2Encoder, wasmUrl);
|
||||
}
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.encode(data.data, data.width, data.height, options);
|
||||
if (!result) throw new Error('Encoding error.');
|
||||
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||
return result.buffer as ArrayBuffer;
|
||||
}
|
||||
@@ -13,11 +13,16 @@
|
||||
export function initEmscriptenModule<T extends EmscriptenWasm.Module>(
|
||||
moduleFactory: EmscriptenWasm.ModuleFactory<T>,
|
||||
wasmUrl: string,
|
||||
workerUrl?: string,
|
||||
): Promise<T> {
|
||||
return moduleFactory({
|
||||
// Just to be safe, don't automatically invoke any wasm functions
|
||||
noInitialRun: true,
|
||||
locateFile: () => wasmUrl,
|
||||
locateFile: (url: string) => {
|
||||
if (url.endsWith('.wasm')) return wasmUrl;
|
||||
if (url.endsWith('.worker.js')) return workerUrl!;
|
||||
throw Error('Unknown url in locateFile ' + url);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 10 KiB |
@@ -5,6 +5,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/features/**/worker/**/*",
|
||||
"src/features/**/sub-worker/**/*",
|
||||
"src/features/**/shared/**/*",
|
||||
"src/features/worker-utils/**/*",
|
||||
"src/features-worker/**/*",
|
||||
|
||||
Reference in New Issue
Block a user