mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-17 19:19:47 +00:00
Compare commits
7 Commits
avif-updat
...
surma/lol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d950f756c5 | ||
|
|
003d5d125c | ||
|
|
71f341923a | ||
|
|
37cc236307 | ||
|
|
8eaa04c8af | ||
|
|
4a1bcec5af | ||
|
|
d2a656f0bb |
@@ -1,9 +1,9 @@
|
||||
# using libavif from https://github.com/AOMediaCodec/libavif
|
||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/refs/tags/v1.0.1.tar.gz
|
||||
# libavif and libaom versions are from
|
||||
# google3/third_party/libavif/METADATA
|
||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/647c3c208cf152395d777c1bf7240d2ecf7df5a9.tar.gz
|
||||
CODEC_PACKAGE = node_modules/libavif.tar.gz
|
||||
|
||||
# using libaom from https://aomedia.googlesource.com/aom
|
||||
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.7.0.tar.gz
|
||||
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.6.0.tar.gz
|
||||
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
||||
|
||||
export CODEC_DIR = node_modules/libavif
|
||||
@@ -11,6 +11,7 @@ export BUILD_DIR = node_modules/build
|
||||
export LIBAOM_DIR = node_modules/libaom
|
||||
|
||||
override CFLAGS += "-Wno-unused-macros"
|
||||
export
|
||||
|
||||
OUT_ENC_JS = enc/avif_enc.js
|
||||
OUT_NODE_ENC_JS = enc/avif_node_enc.js
|
||||
@@ -27,9 +28,10 @@ HELPER_MAKEFLAGS := -f helper.Makefile
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS)
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS) $(OUT_NODE_DEC_JS)
|
||||
|
||||
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS): ENVIRONMENT=node
|
||||
$(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
OUT_JS=$@ \
|
||||
@@ -40,9 +42,9 @@ $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLis
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
" \
|
||||
ENVIRONMENT=$(ENVIRONMENT) \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_CHROMA_DOWNSAMPLING_SHARP_YUV=ON"
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0"
|
||||
|
||||
$(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
OUT_JS=$@ \
|
||||
@@ -52,10 +54,11 @@ $(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMake
|
||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||
" \
|
||||
ENVIRONMENT=$(ENVIRONMENT) \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_CHROMA_DOWNSAMPLING_SHARP_YUV=ON" \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0" \
|
||||
OUT_FLAGS="-pthread"
|
||||
|
||||
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(OUT_NODE_DEC_JS): ENVIRONMENT=node
|
||||
$(OUT_NODE_DEC_JS) $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
OUT_JS=$@ \
|
||||
@@ -65,7 +68,7 @@ $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLis
|
||||
-DCONFIG_MULTITHREAD=0 \
|
||||
" \
|
||||
ENVIRONMENT=$(ENVIRONMENT) \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0 -DAVIF_CHROMA_DOWNSAMPLING_SHARP_YUV=0 -DAVIF_LOCAL_LIBSHARPYUV=0"
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"
|
||||
|
||||
$(CODEC_PACKAGE):
|
||||
mkdir -p $(@D)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -3,22 +3,15 @@
|
||||
#include <emscripten/val.h>
|
||||
#include "avif/avif.h"
|
||||
|
||||
#define RETURN_NULL_IF_NOT_EQUALS(val1, val2) \
|
||||
if (val1 != val2) \
|
||||
return val::null();
|
||||
#define RETURN_NULL_IF_EQUALS(val1, val2) \
|
||||
if (val1 == val2) \
|
||||
return val::null();
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
struct AvifOptions {
|
||||
// [0 - 100]
|
||||
// 0 = worst quality
|
||||
// 100 = lossless
|
||||
int quality;
|
||||
// As above, but -1 means 'use quality'
|
||||
int qualityAlpha;
|
||||
// [0 - 63]
|
||||
// 0 = lossless
|
||||
// 63 = worst quality
|
||||
int cqLevel;
|
||||
// As above, but -1 means 'use cqLevel'
|
||||
int cqAlphaLevel;
|
||||
// [0 - 6]
|
||||
// Creates 2^n tiles in that dimension
|
||||
int tileRowsLog2;
|
||||
@@ -42,15 +35,11 @@ struct AvifOptions {
|
||||
int tune;
|
||||
// 0-50
|
||||
int denoiseLevel;
|
||||
// toggles AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV
|
||||
bool enableSharpDownsampling;
|
||||
};
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
avifResult status; // To check the return status for avif API's
|
||||
|
||||
avifRWData output = AVIF_DATA_EMPTY;
|
||||
int depth = 8;
|
||||
avifPixelFormat format;
|
||||
@@ -69,12 +58,11 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool lossless = options.quality == AVIF_QUALITY_LOSSLESS &&
|
||||
(options.qualityAlpha == -1 || options.qualityAlpha == AVIF_QUALITY_LOSSLESS) &&
|
||||
bool lossless = options.cqLevel == AVIF_QUANTIZER_LOSSLESS &&
|
||||
options.cqAlphaLevel <= AVIF_QUANTIZER_LOSSLESS &&
|
||||
format == AVIF_PIXEL_FORMAT_YUV444;
|
||||
|
||||
avifImage* image = avifImageCreate(width, height, depth, format);
|
||||
RETURN_NULL_IF_EQUALS(image, NULL);
|
||||
|
||||
if (lossless) {
|
||||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
|
||||
@@ -88,49 +76,40 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
avifRGBImageSetDefaults(&srcRGB, image);
|
||||
srcRGB.pixels = rgba;
|
||||
srcRGB.rowBytes = width * 4;
|
||||
if (options.enableSharpDownsampling) {
|
||||
printf("Enabling AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV\n");
|
||||
srcRGB.chromaDownsampling = AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV;
|
||||
}
|
||||
status = avifImageRGBToYUV(image, &srcRGB);
|
||||
if (status == AVIF_RESULT_NOT_IMPLEMENTED) {
|
||||
printf("libsharpyuv not implemented methinks\n");
|
||||
}
|
||||
RETURN_NULL_IF_NOT_EQUALS(status, AVIF_RESULT_OK);
|
||||
avifImageRGBToYUV(image, &srcRGB);
|
||||
|
||||
avifEncoder* encoder = avifEncoderCreate();
|
||||
RETURN_NULL_IF_EQUALS(encoder, NULL);
|
||||
|
||||
if (lossless) {
|
||||
encoder->quality = AVIF_QUALITY_LOSSLESS;
|
||||
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
|
||||
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
|
||||
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
|
||||
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
||||
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
||||
} else {
|
||||
status = avifEncoderSetCodecSpecificOption(encoder, "sharpness",
|
||||
std::to_string(options.sharpness).c_str());
|
||||
RETURN_NULL_IF_NOT_EQUALS(status, AVIF_RESULT_OK);
|
||||
encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
|
||||
encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
|
||||
encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY;
|
||||
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY;
|
||||
avifEncoderSetCodecSpecificOption(encoder, "end-usage", "q");
|
||||
avifEncoderSetCodecSpecificOption(encoder, "cq-level", std::to_string(options.cqLevel).c_str());
|
||||
avifEncoderSetCodecSpecificOption(encoder, "sharpness",
|
||||
std::to_string(options.sharpness).c_str());
|
||||
|
||||
// Set base quality
|
||||
encoder->quality = options.quality;
|
||||
// Conditionally set alpha quality
|
||||
if (options.qualityAlpha == -1) {
|
||||
encoder->qualityAlpha = options.quality;
|
||||
} else {
|
||||
encoder->qualityAlpha = options.qualityAlpha;
|
||||
if (options.cqAlphaLevel != -1) {
|
||||
avifEncoderSetCodecSpecificOption(encoder, "alpha:cq-level",
|
||||
std::to_string(options.cqAlphaLevel).c_str());
|
||||
}
|
||||
|
||||
if (options.tune == 2 || (options.tune == 0 && options.quality >= 50)) {
|
||||
status = avifEncoderSetCodecSpecificOption(encoder, "tune", "ssim");
|
||||
RETURN_NULL_IF_NOT_EQUALS(status, AVIF_RESULT_OK);
|
||||
if (options.tune == 2 || (options.tune == 0 && options.cqLevel <= 32)) {
|
||||
avifEncoderSetCodecSpecificOption(encoder, "tune", "ssim");
|
||||
}
|
||||
|
||||
if (options.chromaDeltaQ) {
|
||||
status = avifEncoderSetCodecSpecificOption(encoder, "enable-chroma-deltaq", "1");
|
||||
RETURN_NULL_IF_NOT_EQUALS(status, AVIF_RESULT_OK);
|
||||
avifEncoderSetCodecSpecificOption(encoder, "enable-chroma-deltaq", "1");
|
||||
}
|
||||
|
||||
status = avifEncoderSetCodecSpecificOption(encoder, "color:denoise-noise-level",
|
||||
std::to_string(options.denoiseLevel).c_str());
|
||||
RETURN_NULL_IF_NOT_EQUALS(status, AVIF_RESULT_OK);
|
||||
avifEncoderSetCodecSpecificOption(encoder, "color:denoise-noise-level",
|
||||
std::to_string(options.denoiseLevel).c_str());
|
||||
}
|
||||
|
||||
encoder->maxThreads = emscripten_num_logical_cores();
|
||||
@@ -152,8 +131,8 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<AvifOptions>("AvifOptions")
|
||||
.field("quality", &AvifOptions::quality)
|
||||
.field("qualityAlpha", &AvifOptions::qualityAlpha)
|
||||
.field("cqLevel", &AvifOptions::cqLevel)
|
||||
.field("cqAlphaLevel", &AvifOptions::cqAlphaLevel)
|
||||
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
|
||||
.field("tileColsLog2", &AvifOptions::tileColsLog2)
|
||||
.field("speed", &AvifOptions::speed)
|
||||
@@ -161,8 +140,7 @@ EMSCRIPTEN_BINDINGS(my_module) {
|
||||
.field("sharpness", &AvifOptions::sharpness)
|
||||
.field("tune", &AvifOptions::tune)
|
||||
.field("denoiseLevel", &AvifOptions::denoiseLevel)
|
||||
.field("subsample", &AvifOptions::subsample)
|
||||
.field("enableSharpDownsampling", &AvifOptions::enableSharpDownsampling);
|
||||
.field("subsample", &AvifOptions::subsample);
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
||||
|
||||
5
codecs/avif/enc/avif_enc.d.ts
vendored
5
codecs/avif/enc/avif_enc.d.ts
vendored
@@ -5,16 +5,15 @@ export const enum AVIFTune {
|
||||
}
|
||||
|
||||
export interface EncodeOptions {
|
||||
quality: number;
|
||||
qualityAlpha: number;
|
||||
cqLevel: number;
|
||||
denoiseLevel: number;
|
||||
cqAlphaLevel: number;
|
||||
tileRowsLog2: number;
|
||||
tileColsLog2: number;
|
||||
speed: number;
|
||||
subsample: number;
|
||||
chromaDeltaQ: boolean;
|
||||
sharpness: number;
|
||||
enableSharpDownsampling: boolean;
|
||||
tune: AVIFTune;
|
||||
}
|
||||
|
||||
|
||||
2
codecs/avif/enc/avif_enc.js
generated
2
codecs/avif/enc/avif_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.js
generated
2
codecs/avif/enc/avif_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.worker.js
generated
2
codecs/avif/enc/avif_enc_mt.worker.js
generated
@@ -1 +1 @@
|
||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./avif_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance})}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["__emscripten_thread_exit"](result)}}catch(ex){if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["__emscripten_thread_exit"](ex.status)}}else{throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}postMessage({"cmd":"cancelDone"})}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||
"use strict";var Module={};var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./avif_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
||||
|
||||
2
codecs/avif/enc/avif_node_enc.js
generated
2
codecs/avif/enc/avif_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_node_enc_mt.js
generated
2
codecs/avif/enc/avif_node_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -10,12 +10,6 @@
|
||||
# $(LIBAVIF_FLAGS)
|
||||
# $(ENVIRONMENT)
|
||||
|
||||
# Take from libavif/ext/libsharpyuv.cmd
|
||||
WEBP_URL = https://chromium.googlesource.com/webm/libwebp
|
||||
WEBP_COMMIT = e2c85878f6a33f29948b43d3492d9cdaf801aa54
|
||||
LIBSHARPYUV_DIR = $(CODEC_DIR)/ext/libwebp
|
||||
|
||||
# $(OUT_JS) is something like "enc/avif_enc.js" or "enc/avif_enc_mt.js"
|
||||
OUT_BUILD_DIR := $(BUILD_DIR)/$(basename $(OUT_JS))
|
||||
|
||||
CODEC_BUILD_DIR := $(OUT_BUILD_DIR)/libavif
|
||||
@@ -24,9 +18,6 @@ CODEC_OUT := $(CODEC_BUILD_DIR)/libavif.a
|
||||
LIBAOM_BUILD_DIR := $(OUT_BUILD_DIR)/libaom
|
||||
LIBAOM_OUT := $(LIBAOM_BUILD_DIR)/libaom.a
|
||||
|
||||
LIBSHARPYUV_BUILD_DIR := $(OUT_BUILD_DIR)/libsharpyuvLOLOLOL
|
||||
LIBSHARPYUV_OUT := $(LIBSHARPYUV_BUILD_DIR)/libsharpyuv.a
|
||||
|
||||
OUT_WASM = $(OUT_JS:.js=.wasm)
|
||||
OUT_WORKER=$(OUT_JS:.js=.worker.js)
|
||||
|
||||
@@ -34,13 +25,6 @@ OUT_WORKER=$(OUT_JS:.js=.worker.js)
|
||||
|
||||
all: $(OUT_JS)
|
||||
|
||||
# Only add libsharpyuv as a dependency for encoders.
|
||||
# Yes, that if statement is true for encoders.
|
||||
ifneq (,$(findstring enc/, $(OUT_JS)))
|
||||
$(OUT_JS): $(LIBSHARPYUV_OUT)
|
||||
$(CODEC_OUT): $(LIBSHARPYUV_OUT)
|
||||
endif
|
||||
|
||||
$(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DIR)/include \
|
||||
@@ -55,7 +39,6 @@ $(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
||||
|
||||
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_OUT)
|
||||
emcmake cmake \
|
||||
-DCMAKE_LIBRARY_PATH=$(LIBSHARPYUV_BUILD_DIR) \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=0 \
|
||||
-DAVIF_CODEC_AOM=1 \
|
||||
@@ -84,21 +67,6 @@ $(LIBAOM_OUT): $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(LIBAOM_DIR) && \
|
||||
$(MAKE) -C $(LIBAOM_BUILD_DIR)
|
||||
|
||||
$(LIBSHARPYUV_OUT): $(LIBSHARPYUV_DIR)/CMakeLists.txt
|
||||
emcmake cmake \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-B $(LIBSHARPYUV_BUILD_DIR) \
|
||||
$(LIBSHARPYUV_DIR)
|
||||
$(MAKE) -C $(LIBSHARPYUV_BUILD_DIR) sharpyuv
|
||||
|
||||
$(LIBSHARPYUV_DIR)/CMakeLists.txt: $(CODEC_DIR)/CMakeLists.txt
|
||||
cd $(CODEC_DIR)/ext && \
|
||||
git clone $(WEBP_URL) --single-branch libwebp && \
|
||||
cd libwebp && \
|
||||
git checkout $(WEBP_COMMIT)
|
||||
|
||||
|
||||
clean:
|
||||
$(RM) $(OUT_JS) $(OUT_WASM) $(OUT_WORKER)
|
||||
$(MAKE) -C $(CODEC_BUILD_DIR) clean
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM emscripten/emsdk:2.0.34
|
||||
FROM emscripten/emsdk:2.0.23
|
||||
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
||||
ENV CFLAGS "-O3 -flto"
|
||||
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
||||
|
||||
42
codecs/qoi/Makefile
Normal file
42
codecs/qoi/Makefile
Normal file
@@ -0,0 +1,42 @@
|
||||
CODEC_URL = https://github.com/phoboslab/qoi/archive/8d35d93cdca85d2868246c2a8a80a1e2c16ba2a8.tar.gz
|
||||
|
||||
CODEC_DIR = node_modules/qoi
|
||||
CODEC_BUILD_DIR:= $(CODEC_DIR)/build
|
||||
ENVIRONMENT = worker
|
||||
|
||||
OUT_JS = enc/qoi_enc.js dec/qoi_dec.js
|
||||
OUT_WASM := $(OUT_JS:.js=.wasm)
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_JS)
|
||||
|
||||
$(filter enc/%,$(OUT_JS)): enc/qoi_enc.o
|
||||
$(filter dec/%,$(OUT_JS)): dec/qoi_dec.o
|
||||
|
||||
# ALL .js FILES
|
||||
$(OUT_JS):
|
||||
$(LD) \
|
||||
$(LDFLAGS) \
|
||||
--bind \
|
||||
-s ENVIRONMENT=$(ENVIRONMENT) \
|
||||
-s EXPORT_ES6=1 \
|
||||
-o $@ \
|
||||
$+
|
||||
|
||||
# ALL .o FILES
|
||||
%.o: %.cpp $(CODEC_DIR)
|
||||
$(CXX) -c \
|
||||
$(CXXFLAGS) \
|
||||
-I $(CODEC_DIR) \
|
||||
-o $@ \
|
||||
$<
|
||||
|
||||
# CREATE DIRECTORY
|
||||
$(CODEC_DIR):
|
||||
mkdir -p $(CODEC_DIR)
|
||||
curl -sL $(CODEC_URL) | tar xz --strip 1 -C $(CODEC_DIR)
|
||||
|
||||
clean:
|
||||
$(RM) $(OUT_JS) $(OUT_WASM)
|
||||
$(MAKE) -C $(CODEC_DIR) clean
|
||||
29
codecs/qoi/dec/qoi_dec.cpp
Normal file
29
codecs/qoi/dec/qoi_dec.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include "qoi.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
||||
thread_local const val ImageData = val::global("ImageData");
|
||||
|
||||
val decode(std::string qoiimage) {
|
||||
qoi_desc desc;
|
||||
uint8_t* rgba = (uint8_t*)qoi_decode(qoiimage.c_str(), qoiimage.length(), &desc, 4);
|
||||
|
||||
// Resultant width and height stored in descriptor
|
||||
int decodedWidth = desc.width;
|
||||
int decodedHeight = desc.height;
|
||||
|
||||
val result = ImageData.new_(
|
||||
Uint8ClampedArray.new_(typed_memory_view(4 * decodedWidth * decodedHeight, rgba)),
|
||||
decodedWidth, decodedHeight);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
function("decode", &decode);
|
||||
}
|
||||
7
codecs/qoi/dec/qoi_dec.d.ts
vendored
Normal file
7
codecs/qoi/dec/qoi_dec.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface QOIModule extends EmscriptenWasm.Module {
|
||||
decode(data: BufferSource): ImageData | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QOIModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
16
codecs/qoi/dec/qoi_dec.js
generated
Normal file
16
codecs/qoi/dec/qoi_dec.js
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/qoi/dec/qoi_dec.wasm
Normal file
BIN
codecs/qoi/dec/qoi_dec.wasm
Normal file
Binary file not shown.
45
codecs/qoi/enc/qoi_enc.cpp
Normal file
45
codecs/qoi/enc/qoi_enc.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include "qoi.h"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
struct QoiOptions {};
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
val encode(std::string buffer, int width, int height, QoiOptions options) {
|
||||
int compressedSizeInBytes;
|
||||
qoi_desc desc;
|
||||
desc.width = width;
|
||||
desc.height = height;
|
||||
desc.channels = 3;
|
||||
desc.colorspace = QOI_SRGB;
|
||||
|
||||
auto rgba_buffer = buffer.c_str();
|
||||
auto num_pixels = width * height;
|
||||
std::vector<uint8_t> rgb_buffer(num_pixels * 3);
|
||||
for(auto i = 0; i < num_pixels; i ++) {
|
||||
rgb_buffer[i*3 + 0] = rgba_buffer[i*4 +0];
|
||||
rgb_buffer[i*3 + 1] = rgba_buffer[i*4 +1];
|
||||
rgb_buffer[i*3 + 2] = rgba_buffer[i*4 +2];
|
||||
}
|
||||
|
||||
void* encodedData = qoi_encode(rgb_buffer.data(), &desc, &compressedSizeInBytes);
|
||||
if (encodedData == NULL)
|
||||
return val::null();
|
||||
|
||||
auto js_result =
|
||||
Uint8Array.new_(typed_memory_view(compressedSizeInBytes, (const uint8_t*)encodedData));
|
||||
return js_result;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<QoiOptions>("QoiOptions");
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
||||
14
codecs/qoi/enc/qoi_enc.d.ts
vendored
Normal file
14
codecs/qoi/enc/qoi_enc.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface EncodeOptions {}
|
||||
|
||||
export interface QoiModule extends EmscriptenWasm.Module {
|
||||
encode(
|
||||
data: BufferSource,
|
||||
width: number,
|
||||
height: number,
|
||||
options: EncodeOptions,
|
||||
): Uint8Array;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QoiModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
16
codecs/qoi/enc/qoi_enc.js
generated
Normal file
16
codecs/qoi/enc/qoi_enc.js
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/qoi/enc/qoi_enc.wasm
Normal file
BIN
codecs/qoi/enc/qoi_enc.wasm
Normal file
Binary file not shown.
7
codecs/qoi/package.json
Normal file
7
codecs/qoi/package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "qoi",
|
||||
"scripts": {
|
||||
"build": "../build-cpp.sh"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@ async function decodeImage(
|
||||
if (mimeType === 'image/webp2') {
|
||||
return await workerBridge.wp2Decode(signal, blob);
|
||||
}
|
||||
if (mimeType === 'image/qoi') {
|
||||
return await workerBridge.qoiDecode(signal, blob);
|
||||
}
|
||||
}
|
||||
// Otherwise fall through and try built-in decoding for a laugh.
|
||||
return await builtinDecode(signal, blob);
|
||||
|
||||
@@ -103,6 +103,7 @@ const magicNumberMapInput = [
|
||||
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
||||
[/^\xff\x0a/, 'image/jxl'],
|
||||
[/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'],
|
||||
[/^qoif/, 'image/qoi'],
|
||||
] as const;
|
||||
|
||||
export type ImageMimeTypes = typeof magicNumberMapInput[number][1];
|
||||
|
||||
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { QOIModule } from 'codecs/qoi/dec/qoi_dec';
|
||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||
|
||||
let emscriptenModule: Promise<QOIModule>;
|
||||
|
||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||
if (!emscriptenModule) {
|
||||
const decoder = await import('codecs/qoi/dec/qoi_dec');
|
||||
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||
}
|
||||
|
||||
const [module, data] = await Promise.all([
|
||||
emscriptenModule,
|
||||
blobToArrayBuffer(blob),
|
||||
]);
|
||||
|
||||
const result = module.decode(data);
|
||||
if (!result) throw new Error('Decoding error');
|
||||
return result;
|
||||
}
|
||||
@@ -38,19 +38,10 @@ interface State {
|
||||
denoiseLevel: number;
|
||||
aqMode: number;
|
||||
tune: AVIFTune;
|
||||
enableSharpDownsampling: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* AVIF quality ranges from 0 (worst) to 100 (lossless).
|
||||
* Since lossless is a separate checkbox, we cap user-inputted quality at 99
|
||||
*
|
||||
* AVIF speed ranges from 0 (slowest) to 10 (fastest).
|
||||
* We display it as 'effort' to the user since it conveys the speed-size tradeoff
|
||||
* much better: speed = 10 - effort
|
||||
*/
|
||||
const MAX_QUALITY = 100;
|
||||
const MAX_EFFORT = 10;
|
||||
const maxQuant = 63;
|
||||
const maxSpeed = 10;
|
||||
|
||||
export class Options extends Component<Props, State> {
|
||||
static getDerivedStateFromProps(
|
||||
@@ -64,30 +55,34 @@ export class Options extends Component<Props, State> {
|
||||
const { options } = props;
|
||||
|
||||
const lossless =
|
||||
options.quality === MAX_QUALITY &&
|
||||
(options.qualityAlpha == -1 || options.qualityAlpha == MAX_QUALITY) &&
|
||||
options.cqLevel === 0 &&
|
||||
options.cqAlphaLevel <= 0 &&
|
||||
options.subsample == 3;
|
||||
|
||||
const separateAlpha = options.qualityAlpha !== -1;
|
||||
const separateAlpha = options.cqAlphaLevel !== -1;
|
||||
|
||||
const quality = lossless ? defaultOptions.quality : options.quality;
|
||||
const cqLevel = lossless ? defaultOptions.cqLevel : options.cqLevel;
|
||||
|
||||
// Create default form state from options
|
||||
return {
|
||||
options,
|
||||
lossless,
|
||||
quality: quality,
|
||||
quality: maxQuant - cqLevel,
|
||||
separateAlpha,
|
||||
alphaQuality: separateAlpha ? options.qualityAlpha : options.quality,
|
||||
subsample: defaultOptions.subsample,
|
||||
alphaQuality:
|
||||
maxQuant -
|
||||
(separateAlpha ? options.cqAlphaLevel : defaultOptions.cqLevel),
|
||||
subsample:
|
||||
options.subsample === 0 || lossless
|
||||
? defaultOptions.subsample
|
||||
: options.subsample,
|
||||
tileRows: options.tileRowsLog2,
|
||||
tileCols: options.tileColsLog2,
|
||||
effort: MAX_EFFORT - options.speed,
|
||||
effort: maxSpeed - options.speed,
|
||||
chromaDeltaQ: options.chromaDeltaQ,
|
||||
sharpness: options.sharpness,
|
||||
denoiseLevel: options.denoiseLevel,
|
||||
tune: options.tune,
|
||||
enableSharpDownsampling: options.enableSharpDownsampling,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,21 +120,20 @@ export class Options extends Component<Props, State> {
|
||||
};
|
||||
|
||||
const newOptions: EncodeOptions = {
|
||||
quality: optionState.lossless ? MAX_QUALITY : optionState.quality,
|
||||
qualityAlpha:
|
||||
cqLevel: optionState.lossless ? 0 : maxQuant - optionState.quality,
|
||||
cqAlphaLevel:
|
||||
optionState.lossless || !optionState.separateAlpha
|
||||
? -1 // default AVIF alphaLevel
|
||||
: optionState.alphaQuality,
|
||||
? -1
|
||||
: maxQuant - optionState.alphaQuality,
|
||||
// Always set to 4:4:4 if lossless
|
||||
subsample: optionState.lossless ? 3 : optionState.subsample,
|
||||
tileColsLog2: optionState.tileCols,
|
||||
tileRowsLog2: optionState.tileRows,
|
||||
speed: MAX_EFFORT - optionState.effort,
|
||||
speed: maxSpeed - optionState.effort,
|
||||
chromaDeltaQ: optionState.chromaDeltaQ,
|
||||
sharpness: optionState.sharpness,
|
||||
denoiseLevel: optionState.denoiseLevel,
|
||||
tune: optionState.tune,
|
||||
enableSharpDownsampling: optionState.enableSharpDownsampling,
|
||||
};
|
||||
|
||||
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||
@@ -173,7 +167,6 @@ export class Options extends Component<Props, State> {
|
||||
sharpness,
|
||||
denoiseLevel,
|
||||
tune,
|
||||
enableSharpDownsampling,
|
||||
}: State,
|
||||
) {
|
||||
return (
|
||||
@@ -190,7 +183,7 @@ export class Options extends Component<Props, State> {
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max={MAX_QUALITY - 1} // MAX_QUALITY would mean lossless
|
||||
max="63"
|
||||
value={quality}
|
||||
onInput={this._inputChange('quality', 'number')}
|
||||
>
|
||||
@@ -218,10 +211,9 @@ export class Options extends Component<Props, State> {
|
||||
value={subsample}
|
||||
onChange={this._inputChange('subsample', 'number')}
|
||||
>
|
||||
<option value="0">4:0:0</option>
|
||||
<option value="1">4:2:0</option>
|
||||
<option value="2">4:2:2</option>
|
||||
<option value="3">4:4:4</option>
|
||||
<option value="1">Half</option>
|
||||
{/*<option value="2">4:2:2</option>*/}
|
||||
<option value="3">Off</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionToggle}>
|
||||
@@ -236,7 +228,7 @@ export class Options extends Component<Props, State> {
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max={MAX_QUALITY - 1} // MAX_QUALITY would mean lossless
|
||||
max="63"
|
||||
value={alphaQuality}
|
||||
onInput={this._inputChange(
|
||||
'alphaQuality',
|
||||
@@ -265,16 +257,6 @@ export class Options extends Component<Props, State> {
|
||||
Sharpness:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionToggle}>
|
||||
Enable Sharp YUV Downsampling
|
||||
<Checkbox
|
||||
checked={enableSharpDownsampling}
|
||||
onChange={this._inputChange(
|
||||
'enableSharpDownsampling',
|
||||
'boolean',
|
||||
)}
|
||||
/>
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
@@ -325,7 +307,7 @@ export class Options extends Component<Props, State> {
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max={MAX_EFFORT}
|
||||
max="10"
|
||||
value={effort}
|
||||
onInput={this._inputChange('effort', 'number')}
|
||||
>
|
||||
|
||||
@@ -18,8 +18,8 @@ export const label = 'AVIF';
|
||||
export const mimeType = 'image/avif';
|
||||
export const extension = 'avif';
|
||||
export const defaultOptions: EncodeOptions = {
|
||||
quality: 50,
|
||||
qualityAlpha: -1,
|
||||
cqLevel: 33,
|
||||
cqAlphaLevel: -1,
|
||||
denoiseLevel: 0,
|
||||
tileColsLog2: 0,
|
||||
tileRowsLog2: 0,
|
||||
@@ -28,5 +28,4 @@ export const defaultOptions: EncodeOptions = {
|
||||
chromaDeltaQ: false,
|
||||
sharpness: 0,
|
||||
tune: AVIFTune.auto,
|
||||
enableSharpDownsampling: false,
|
||||
};
|
||||
|
||||
23
src/features/encoders/qoi/client/index.tsx
Normal file
23
src/features/encoders/qoi/client/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { EncodeOptions } from '../shared/meta';
|
||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||
import { h, Component, Fragment } from 'preact';
|
||||
|
||||
export function encode(
|
||||
signal: AbortSignal,
|
||||
workerBridge: WorkerBridge,
|
||||
imageData: ImageData,
|
||||
options: EncodeOptions,
|
||||
) {
|
||||
return workerBridge.qoiEncode(signal, imageData, options);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
options: EncodeOptions;
|
||||
onChange(newOptions: EncodeOptions): void;
|
||||
}
|
||||
|
||||
export class Options extends Component<Props, {}> {
|
||||
render() {
|
||||
return <Fragment></Fragment>;
|
||||
}
|
||||
}
|
||||
13
src/features/encoders/qoi/client/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/client/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
19
src/features/encoders/qoi/shared/meta.ts
Normal file
19
src/features/encoders/qoi/shared/meta.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 { EncodeOptions } from 'codecs/qoi/enc/qoi_enc';
|
||||
export { EncodeOptions };
|
||||
|
||||
export const label = 'QOI';
|
||||
export const mimeType = 'image/qoi';
|
||||
export const extension = 'qoi';
|
||||
export const defaultOptions: EncodeOptions = {};
|
||||
13
src/features/encoders/qoi/shared/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/shared/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
13
src/features/encoders/qoi/worker/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/qoi/worker/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
||||
31
src/features/encoders/qoi/worker/qoiEncode.ts
Normal file
31
src/features/encoders/qoi/worker/qoiEncode.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 qoi_enc, { QoiModule } from 'codecs/qoi/enc/qoi_enc';
|
||||
import { EncodeOptions } from '../shared/meta';
|
||||
import { initEmscriptenModule } from 'features/worker-utils';
|
||||
|
||||
let emscriptenModule: Promise<QoiModule>;
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(qoi_enc);
|
||||
}
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const resultView = module.encode(data.data, data.width, data.height, options);
|
||||
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||
return resultView.buffer as ArrayBuffer;
|
||||
}
|
||||
Reference in New Issue
Block a user