mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 17:27:09 +00:00
Update libavif (#1381)
* Update libavif (v1.0.0-main) * Update libavif for improved compression and speed * v1.0.0 deprecates usage of min and max-quantizers; we use `quality` and `qualityAlpha` instead * Renamed `maxSpeed` to `MAX_EFFORT` for better readability * Update libavif (v1.0.1-main) * Refactor variable names for clarity * Update libaom (v3.7.0) * Add checks for API return values * Rename variables for readability Changes `cqlevel` to `quality`, and `cqAlphaLevel` to `qualityAlpha` * Minor patches in logic * Minor patches in lossless calculation * Add chroma subsampling options to AVIF * Add skeleton for sharp downsampling param * Try to use libsharpyuv * Encoder working, decoder isnt * Make sure sharpyuv is disabled for decoder * Add AVIF_LOCAL flags for sharp yuv * Get AVIF sharp YUV working * Clean up AVIF makefiles * AVIF: Make sharpyuv conditional on subsample * AVIF: Flags to speed up sharpyuv build * AVIF: Minor refactoring in enc.cpp * AVIF: Minor refactoring & renaming * AVIF: Use smart pointers to prevent memory leaks * AVIF: Minor refactoring * AVIF: Revert defaultoptions logic change --------- Co-authored-by: Surma <surma@surma.dev> Co-authored-by: Jake Archibald <jaffathecake@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# libavif and libaom versions are from
|
# using libavif from https://github.com/AOMediaCodec/libavif
|
||||||
# google3/third_party/libavif/METADATA
|
LIBAVIF_URL = https://github.com/AOMediaCodec/libavif/archive/refs/tags/v1.0.1.tar.gz
|
||||||
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/647c3c208cf152395d777c1bf7240d2ecf7df5a9.tar.gz
|
LIBAVIF_PACKAGE = node_modules/libavif.tar.gz
|
||||||
CODEC_PACKAGE = node_modules/libavif.tar.gz
|
|
||||||
|
|
||||||
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.6.0.tar.gz
|
# using libaom from https://aomedia.googlesource.com/aom
|
||||||
|
LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.7.0.tar.gz
|
||||||
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
LIBAOM_PACKAGE = node_modules/libaom.tar.gz
|
||||||
|
|
||||||
export CODEC_DIR = node_modules/libavif
|
export CODEC_DIR = node_modules/libavif
|
||||||
@@ -11,7 +11,12 @@ export BUILD_DIR = node_modules/build
|
|||||||
export LIBAOM_DIR = node_modules/libaom
|
export LIBAOM_DIR = node_modules/libaom
|
||||||
|
|
||||||
override CFLAGS += "-Wno-unused-macros"
|
override CFLAGS += "-Wno-unused-macros"
|
||||||
export
|
|
||||||
|
# We must build libsharpyuv from a specific libwebp commit
|
||||||
|
# See libavif/ext/libsharpyuv.cmd for more detail
|
||||||
|
LIBWEBP_URL_WITH_SHARPYUV = https://chromium.googlesource.com/webm/libwebp/+archive/e2c85878f6a33f29948b43d3492d9cdaf801aa54.tar.gz
|
||||||
|
LIBWEBP_DIR := $(CODEC_DIR)/ext/libwebp
|
||||||
|
export LIBSHARPYUV := $(LIBWEBP_DIR)/build/libsharpyuv.a
|
||||||
|
|
||||||
OUT_ENC_JS = enc/avif_enc.js
|
OUT_ENC_JS = enc/avif_enc.js
|
||||||
OUT_NODE_ENC_JS = enc/avif_node_enc.js
|
OUT_NODE_ENC_JS = enc/avif_node_enc.js
|
||||||
@@ -28,10 +33,10 @@ HELPER_MAKEFLAGS := -f helper.Makefile
|
|||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS) $(OUT_NODE_DEC_JS)
|
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS)
|
||||||
|
|
||||||
$(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS): ENVIRONMENT=node
|
# ST-Encoding
|
||||||
$(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt $(LIBSHARPYUV)
|
||||||
$(MAKE) \
|
$(MAKE) \
|
||||||
$(HELPER_MAKEFLAGS) \
|
$(HELPER_MAKEFLAGS) \
|
||||||
OUT_JS=$@ \
|
OUT_JS=$@ \
|
||||||
@@ -42,9 +47,10 @@ $(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(L
|
|||||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||||
" \
|
" \
|
||||||
ENVIRONMENT=$(ENVIRONMENT) \
|
ENVIRONMENT=$(ENVIRONMENT) \
|
||||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0"
|
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON"
|
||||||
|
|
||||||
$(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
# MT-Encoding
|
||||||
|
$(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt $(LIBSHARPYUV)
|
||||||
$(MAKE) \
|
$(MAKE) \
|
||||||
$(HELPER_MAKEFLAGS) \
|
$(HELPER_MAKEFLAGS) \
|
||||||
OUT_JS=$@ \
|
OUT_JS=$@ \
|
||||||
@@ -54,11 +60,11 @@ $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.t
|
|||||||
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
-DCONFIG_AV1_HIGHBITDEPTH=0 \
|
||||||
" \
|
" \
|
||||||
ENVIRONMENT=$(ENVIRONMENT) \
|
ENVIRONMENT=$(ENVIRONMENT) \
|
||||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0" \
|
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON" \
|
||||||
OUT_FLAGS="-pthread"
|
OUT_FLAGS="-pthread"
|
||||||
|
|
||||||
$(OUT_NODE_DEC_JS): ENVIRONMENT=node
|
# Decoding
|
||||||
$(OUT_NODE_DEC_JS) $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||||
$(MAKE) \
|
$(MAKE) \
|
||||||
$(HELPER_MAKEFLAGS) \
|
$(HELPER_MAKEFLAGS) \
|
||||||
OUT_JS=$@ \
|
OUT_JS=$@ \
|
||||||
@@ -70,22 +76,56 @@ $(OUT_NODE_DEC_JS) $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(L
|
|||||||
ENVIRONMENT=$(ENVIRONMENT) \
|
ENVIRONMENT=$(ENVIRONMENT) \
|
||||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"
|
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"
|
||||||
|
|
||||||
$(CODEC_PACKAGE):
|
# LIBAOM EXTRACTION SECTION
|
||||||
mkdir -p $(@D)
|
|
||||||
curl -sL $(CODEC_URL) -o $@
|
|
||||||
|
|
||||||
|
# Download the libaom tarball
|
||||||
$(LIBAOM_PACKAGE):
|
$(LIBAOM_PACKAGE):
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
curl -sL $(LIBAOM_URL) -o $@
|
curl -sL $(LIBAOM_URL) -o $@
|
||||||
|
|
||||||
$(CODEC_DIR)/CMakeLists.txt: $(CODEC_PACKAGE)
|
# Extract libaom from the tarball
|
||||||
mkdir -p $(@D)
|
|
||||||
tar xzm --strip 1 -C $(@D) -f $(CODEC_PACKAGE)
|
|
||||||
|
|
||||||
$(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
|
$(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
|
||||||
mkdir -p $(@D)
|
mkdir -p $(@D)
|
||||||
tar xzm -C $(@D) -f $(LIBAOM_PACKAGE)
|
tar xzm -C $(@D) -f $(LIBAOM_PACKAGE)
|
||||||
|
|
||||||
|
# LIBAVIF EXTRACTION SECTION
|
||||||
|
|
||||||
|
# Download the libavif tarball
|
||||||
|
$(LIBAVIF_PACKAGE):
|
||||||
|
mkdir -p $(@D)
|
||||||
|
curl -sL $(LIBAVIF_URL) -o $@
|
||||||
|
|
||||||
|
# Extract libavif from the tarball
|
||||||
|
$(CODEC_DIR)/CMakeLists.txt: $(LIBAVIF_PACKAGE)
|
||||||
|
mkdir -p $(@D)
|
||||||
|
tar xzm --strip 1 -C $(@D) -f $(LIBAVIF_PACKAGE)
|
||||||
|
|
||||||
|
# Create libavif/ext/libwebp
|
||||||
|
$(LIBWEBP_DIR)/CMakeLists.txt: $(CODEC_DIR)/CMakeLists.txt
|
||||||
|
mkdir -p $(LIBWEBP_DIR)
|
||||||
|
curl -sL $(LIBWEBP_URL_WITH_SHARPYUV) \
|
||||||
|
| tar xzm -C $(LIBWEBP_DIR)
|
||||||
|
|
||||||
|
# Make libsharpyuv.a
|
||||||
|
$(LIBSHARPYUV): $(LIBWEBP_DIR)/CMakeLists.txt
|
||||||
|
mkdir -p $(@D)
|
||||||
|
emcmake cmake \
|
||||||
|
-DWEBP_BUILD_ANIM_UTILS=OFF \
|
||||||
|
-DWEBP_BUILD_CWEBP=OFF \
|
||||||
|
-DWEBP_BUILD_DWEBP=OFF \
|
||||||
|
-DWEBP_BUILD_GIF2WEBP=OFF \
|
||||||
|
-DWEBP_BUILD_IMG2WEBP=OFF \
|
||||||
|
-DWEBP_BUILD_VWEBP=OFF \
|
||||||
|
-DWEBP_BUILD_WEBPINFO=OFF \
|
||||||
|
-DWEBP_BUILD_LIBWEBPMUX=OFF \
|
||||||
|
-DWEBP_BUILD_WEBPMUX=OFF \
|
||||||
|
-DWEBP_BUILD_EXTRAS=OFF \
|
||||||
|
-DBUILD_SHARED_LIBS=OFF \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-S $(LIBWEBP_DIR) \
|
||||||
|
-B $(@D)
|
||||||
|
$(MAKE) -C $(@D) sharpyuv
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_JS) clean
|
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_JS) clean
|
||||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_MT_JS) clean
|
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_MT_JS) clean
|
||||||
|
|||||||
2
codecs/avif/dec/avif_dec.js
generated
2
codecs/avif/dec/avif_dec.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -3,15 +3,27 @@
|
|||||||
#include <emscripten/val.h>
|
#include <emscripten/val.h>
|
||||||
#include "avif/avif.h"
|
#include "avif/avif.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define RETURN_NULL_IF(expression) \
|
||||||
|
do { \
|
||||||
|
if (expression) \
|
||||||
|
return val::null(); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
using namespace emscripten;
|
using namespace emscripten;
|
||||||
|
|
||||||
|
using AvifImagePtr = std::unique_ptr<avifImage, decltype(&avifImageDestroy)>;
|
||||||
|
using AvifEncoderPtr = std::unique_ptr<avifEncoder, decltype(&avifEncoderDestroy)>;
|
||||||
|
|
||||||
struct AvifOptions {
|
struct AvifOptions {
|
||||||
// [0 - 63]
|
// [0 - 100]
|
||||||
// 0 = lossless
|
// 0 = worst quality
|
||||||
// 63 = worst quality
|
// 100 = lossless
|
||||||
int cqLevel;
|
int quality;
|
||||||
// As above, but -1 means 'use cqLevel'
|
// As above, but -1 means 'use quality'
|
||||||
int cqAlphaLevel;
|
int qualityAlpha;
|
||||||
// [0 - 6]
|
// [0 - 6]
|
||||||
// Creates 2^n tiles in that dimension
|
// Creates 2^n tiles in that dimension
|
||||||
int tileRowsLog2;
|
int tileRowsLog2;
|
||||||
@@ -35,12 +47,15 @@ struct AvifOptions {
|
|||||||
int tune;
|
int tune;
|
||||||
// 0-50
|
// 0-50
|
||||||
int denoiseLevel;
|
int denoiseLevel;
|
||||||
|
// toggles AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV
|
||||||
|
bool enableSharpYUV;
|
||||||
};
|
};
|
||||||
|
|
||||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||||
|
|
||||||
val encode(std::string buffer, int width, int height, AvifOptions options) {
|
val encode(std::string buffer, int width, int height, AvifOptions options) {
|
||||||
avifRWData output = AVIF_DATA_EMPTY;
|
avifResult status; // To check the return status for avif API's
|
||||||
|
|
||||||
int depth = 8;
|
int depth = 8;
|
||||||
avifPixelFormat format;
|
avifPixelFormat format;
|
||||||
switch (options.subsample) {
|
switch (options.subsample) {
|
||||||
@@ -58,11 +73,13 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lossless = options.cqLevel == AVIF_QUANTIZER_LOSSLESS &&
|
bool lossless = options.quality == AVIF_QUALITY_LOSSLESS &&
|
||||||
options.cqAlphaLevel <= AVIF_QUANTIZER_LOSSLESS &&
|
(options.qualityAlpha == -1 || options.qualityAlpha == AVIF_QUALITY_LOSSLESS) &&
|
||||||
format == AVIF_PIXEL_FORMAT_YUV444;
|
format == AVIF_PIXEL_FORMAT_YUV444;
|
||||||
|
|
||||||
avifImage* image = avifImageCreate(width, height, depth, format);
|
// Smart pointer for the input image in YUV format
|
||||||
|
AvifImagePtr image(avifImageCreate(width, height, depth, format), avifImageDestroy);
|
||||||
|
RETURN_NULL_IF(image == nullptr);
|
||||||
|
|
||||||
if (lossless) {
|
if (lossless) {
|
||||||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
|
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
|
||||||
@@ -73,43 +90,49 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
|||||||
uint8_t* rgba = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer.data()));
|
uint8_t* rgba = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer.data()));
|
||||||
|
|
||||||
avifRGBImage srcRGB;
|
avifRGBImage srcRGB;
|
||||||
avifRGBImageSetDefaults(&srcRGB, image);
|
avifRGBImageSetDefaults(&srcRGB, image.get());
|
||||||
srcRGB.pixels = rgba;
|
srcRGB.pixels = rgba;
|
||||||
srcRGB.rowBytes = width * 4;
|
srcRGB.rowBytes = width * 4;
|
||||||
avifImageRGBToYUV(image, &srcRGB);
|
if (options.enableSharpYUV) {
|
||||||
|
srcRGB.chromaDownsampling = AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV;
|
||||||
|
}
|
||||||
|
status = avifImageRGBToYUV(image.get(), &srcRGB);
|
||||||
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
||||||
|
|
||||||
avifEncoder* encoder = avifEncoderCreate();
|
// Create a smart pointer for the encoder
|
||||||
|
AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
|
||||||
|
RETURN_NULL_IF(encoder == nullptr);
|
||||||
|
|
||||||
if (lossless) {
|
if (lossless) {
|
||||||
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
|
encoder->quality = AVIF_QUALITY_LOSSLESS;
|
||||||
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
|
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
|
||||||
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
|
||||||
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
|
|
||||||
} else {
|
} else {
|
||||||
encoder->minQuantizer = AVIF_QUANTIZER_BEST_QUALITY;
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "sharpness",
|
||||||
encoder->maxQuantizer = AVIF_QUANTIZER_WORST_QUALITY;
|
std::to_string(options.sharpness).c_str());
|
||||||
encoder->minQuantizerAlpha = AVIF_QUANTIZER_BEST_QUALITY;
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
||||||
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());
|
|
||||||
|
|
||||||
if (options.cqAlphaLevel != -1) {
|
// Set base quality
|
||||||
avifEncoderSetCodecSpecificOption(encoder, "alpha:cq-level",
|
encoder->quality = options.quality;
|
||||||
std::to_string(options.cqAlphaLevel).c_str());
|
// Conditionally set alpha quality
|
||||||
|
if (options.qualityAlpha == -1) {
|
||||||
|
encoder->qualityAlpha = options.quality;
|
||||||
|
} else {
|
||||||
|
encoder->qualityAlpha = options.qualityAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.tune == 2 || (options.tune == 0 && options.cqLevel <= 32)) {
|
if (options.tune == 2 || (options.tune == 0 && options.quality >= 50)) {
|
||||||
avifEncoderSetCodecSpecificOption(encoder, "tune", "ssim");
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "tune", "ssim");
|
||||||
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.chromaDeltaQ) {
|
if (options.chromaDeltaQ) {
|
||||||
avifEncoderSetCodecSpecificOption(encoder, "enable-chroma-deltaq", "1");
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:enable-chroma-deltaq", "1");
|
||||||
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
avifEncoderSetCodecSpecificOption(encoder, "color:denoise-noise-level",
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:denoise-noise-level",
|
||||||
std::to_string(options.denoiseLevel).c_str());
|
std::to_string(options.denoiseLevel).c_str());
|
||||||
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder->maxThreads = emscripten_num_logical_cores();
|
encoder->maxThreads = emscripten_num_logical_cores();
|
||||||
@@ -117,22 +140,21 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
|
|||||||
encoder->tileColsLog2 = options.tileColsLog2;
|
encoder->tileColsLog2 = options.tileColsLog2;
|
||||||
encoder->speed = options.speed;
|
encoder->speed = options.speed;
|
||||||
|
|
||||||
avifResult encodeResult = avifEncoderWrite(encoder, image, &output);
|
avifRWData output = AVIF_DATA_EMPTY;
|
||||||
|
avifResult encodeResult = avifEncoderWrite(encoder.get(), image.get(), &output);
|
||||||
auto js_result = val::null();
|
auto js_result = val::null();
|
||||||
if (encodeResult == AVIF_RESULT_OK) {
|
if (encodeResult == AVIF_RESULT_OK) {
|
||||||
js_result = Uint8Array.new_(typed_memory_view(output.size, output.data));
|
js_result = Uint8Array.new_(typed_memory_view(output.size, output.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
avifImageDestroy(image);
|
|
||||||
avifEncoderDestroy(encoder);
|
|
||||||
avifRWDataFree(&output);
|
avifRWDataFree(&output);
|
||||||
return js_result;
|
return js_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(my_module) {
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
value_object<AvifOptions>("AvifOptions")
|
value_object<AvifOptions>("AvifOptions")
|
||||||
.field("cqLevel", &AvifOptions::cqLevel)
|
.field("quality", &AvifOptions::quality)
|
||||||
.field("cqAlphaLevel", &AvifOptions::cqAlphaLevel)
|
.field("qualityAlpha", &AvifOptions::qualityAlpha)
|
||||||
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
|
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
|
||||||
.field("tileColsLog2", &AvifOptions::tileColsLog2)
|
.field("tileColsLog2", &AvifOptions::tileColsLog2)
|
||||||
.field("speed", &AvifOptions::speed)
|
.field("speed", &AvifOptions::speed)
|
||||||
@@ -140,7 +162,8 @@ EMSCRIPTEN_BINDINGS(my_module) {
|
|||||||
.field("sharpness", &AvifOptions::sharpness)
|
.field("sharpness", &AvifOptions::sharpness)
|
||||||
.field("tune", &AvifOptions::tune)
|
.field("tune", &AvifOptions::tune)
|
||||||
.field("denoiseLevel", &AvifOptions::denoiseLevel)
|
.field("denoiseLevel", &AvifOptions::denoiseLevel)
|
||||||
.field("subsample", &AvifOptions::subsample);
|
.field("subsample", &AvifOptions::subsample)
|
||||||
|
.field("enableSharpYUV", &AvifOptions::enableSharpYUV);
|
||||||
|
|
||||||
function("encode", &encode);
|
function("encode", &encode);
|
||||||
}
|
}
|
||||||
|
|||||||
5
codecs/avif/enc/avif_enc.d.ts
vendored
5
codecs/avif/enc/avif_enc.d.ts
vendored
@@ -5,15 +5,16 @@ export const enum AVIFTune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EncodeOptions {
|
export interface EncodeOptions {
|
||||||
cqLevel: number;
|
quality: number;
|
||||||
|
qualityAlpha: number;
|
||||||
denoiseLevel: number;
|
denoiseLevel: number;
|
||||||
cqAlphaLevel: number;
|
|
||||||
tileRowsLog2: number;
|
tileRowsLog2: number;
|
||||||
tileColsLog2: number;
|
tileColsLog2: number;
|
||||||
speed: number;
|
speed: number;
|
||||||
subsample: number;
|
subsample: number;
|
||||||
chromaDeltaQ: boolean;
|
chromaDeltaQ: boolean;
|
||||||
sharpness: number;
|
sharpness: number;
|
||||||
|
enableSharpYUV: boolean;
|
||||||
tune: AVIFTune;
|
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};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}};
|
"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}};
|
||||||
|
|||||||
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,8 +10,11 @@
|
|||||||
# $(LIBAVIF_FLAGS)
|
# $(LIBAVIF_FLAGS)
|
||||||
# $(ENVIRONMENT)
|
# $(ENVIRONMENT)
|
||||||
|
|
||||||
|
# $(OUT_JS) is something like "enc/avif_enc.js" or "enc/avif_enc_mt.js"
|
||||||
|
# so $(OUT_BUILD_DIR) will be "node_modules/build/enc/avif_enc[_mt]"
|
||||||
OUT_BUILD_DIR := $(BUILD_DIR)/$(basename $(OUT_JS))
|
OUT_BUILD_DIR := $(BUILD_DIR)/$(basename $(OUT_JS))
|
||||||
|
|
||||||
|
# We're making libavif and libaom for every node_modules/[enc|dec]/
|
||||||
CODEC_BUILD_DIR := $(OUT_BUILD_DIR)/libavif
|
CODEC_BUILD_DIR := $(OUT_BUILD_DIR)/libavif
|
||||||
CODEC_OUT := $(CODEC_BUILD_DIR)/libavif.a
|
CODEC_OUT := $(CODEC_BUILD_DIR)/libavif.a
|
||||||
|
|
||||||
@@ -25,6 +28,13 @@ OUT_WORKER=$(OUT_JS:.js=.worker.js)
|
|||||||
|
|
||||||
all: $(OUT_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)
|
||||||
|
$(CODEC_OUT): $(LIBSHARPYUV)
|
||||||
|
endif
|
||||||
|
|
||||||
$(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
$(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
||||||
$(CXX) \
|
$(CXX) \
|
||||||
-I $(CODEC_DIR)/include \
|
-I $(CODEC_DIR)/include \
|
||||||
@@ -32,6 +42,7 @@ $(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
|||||||
$(LDFLAGS) \
|
$(LDFLAGS) \
|
||||||
$(OUT_FLAGS) \
|
$(OUT_FLAGS) \
|
||||||
--bind \
|
--bind \
|
||||||
|
-s ERROR_ON_UNDEFINED_SYMBOLS=0 \
|
||||||
-s ENVIRONMENT=$(ENVIRONMENT) \
|
-s ENVIRONMENT=$(ENVIRONMENT) \
|
||||||
-s EXPORT_ES6=1 \
|
-s EXPORT_ES6=1 \
|
||||||
-o $@ \
|
-o $@ \
|
||||||
@@ -39,6 +50,7 @@ $(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
|
|||||||
|
|
||||||
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_OUT)
|
$(CODEC_OUT): $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_OUT)
|
||||||
emcmake cmake \
|
emcmake cmake \
|
||||||
|
-DCMAKE_LIBRARY_PATH=$(LIBSHARPYUV_BUILD_DIR) \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DBUILD_SHARED_LIBS=0 \
|
-DBUILD_SHARED_LIBS=0 \
|
||||||
-DAVIF_CODEC_AOM=1 \
|
-DAVIF_CODEC_AOM=1 \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM emscripten/emsdk:2.0.23
|
FROM emscripten/emsdk:2.0.34
|
||||||
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
RUN apt-get update && apt-get install -qqy autoconf libtool pkg-config
|
||||||
ENV CFLAGS "-O3 -flto"
|
ENV CFLAGS "-O3 -flto"
|
||||||
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
ENV CXXFLAGS "${CFLAGS} -std=c++17"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EncodeOptions, defaultOptions, AVIFTune } from '../shared/meta';
|
import { EncodeOptions, AVIFTune, defaultOptions } from '../shared/meta';
|
||||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
|
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
|
||||||
@@ -36,12 +36,20 @@ interface State {
|
|||||||
effort: number;
|
effort: number;
|
||||||
sharpness: number;
|
sharpness: number;
|
||||||
denoiseLevel: number;
|
denoiseLevel: number;
|
||||||
aqMode: number;
|
|
||||||
tune: AVIFTune;
|
tune: AVIFTune;
|
||||||
|
enableSharpYUV: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxQuant = 63;
|
/**
|
||||||
const maxSpeed = 10;
|
* 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;
|
||||||
|
|
||||||
export class Options extends Component<Props, State> {
|
export class Options extends Component<Props, State> {
|
||||||
static getDerivedStateFromProps(
|
static getDerivedStateFromProps(
|
||||||
@@ -55,34 +63,28 @@ export class Options extends Component<Props, State> {
|
|||||||
const { options } = props;
|
const { options } = props;
|
||||||
|
|
||||||
const lossless =
|
const lossless =
|
||||||
options.cqLevel === 0 &&
|
options.quality === MAX_QUALITY &&
|
||||||
options.cqAlphaLevel <= 0 &&
|
(options.qualityAlpha == -1 || options.qualityAlpha == MAX_QUALITY) &&
|
||||||
options.subsample == 3;
|
options.subsample == 3;
|
||||||
|
|
||||||
const separateAlpha = options.cqAlphaLevel !== -1;
|
const separateAlpha = options.qualityAlpha !== -1;
|
||||||
|
|
||||||
const cqLevel = lossless ? defaultOptions.cqLevel : options.cqLevel;
|
|
||||||
|
|
||||||
// Create default form state from options
|
// Create default form state from options
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
lossless,
|
lossless,
|
||||||
quality: maxQuant - cqLevel,
|
quality: lossless ? defaultOptions.quality : options.quality,
|
||||||
separateAlpha,
|
separateAlpha,
|
||||||
alphaQuality:
|
alphaQuality: separateAlpha ? options.qualityAlpha : options.quality,
|
||||||
maxQuant -
|
subsample: options.subsample,
|
||||||
(separateAlpha ? options.cqAlphaLevel : defaultOptions.cqLevel),
|
|
||||||
subsample:
|
|
||||||
options.subsample === 0 || lossless
|
|
||||||
? defaultOptions.subsample
|
|
||||||
: options.subsample,
|
|
||||||
tileRows: options.tileRowsLog2,
|
tileRows: options.tileRowsLog2,
|
||||||
tileCols: options.tileColsLog2,
|
tileCols: options.tileColsLog2,
|
||||||
effort: maxSpeed - options.speed,
|
effort: MAX_EFFORT - options.speed,
|
||||||
chromaDeltaQ: options.chromaDeltaQ,
|
chromaDeltaQ: options.chromaDeltaQ,
|
||||||
sharpness: options.sharpness,
|
sharpness: options.sharpness,
|
||||||
denoiseLevel: options.denoiseLevel,
|
denoiseLevel: options.denoiseLevel,
|
||||||
tune: options.tune,
|
tune: options.tune,
|
||||||
|
enableSharpYUV: options.enableSharpYUV,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,20 +122,21 @@ export class Options extends Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const newOptions: EncodeOptions = {
|
const newOptions: EncodeOptions = {
|
||||||
cqLevel: optionState.lossless ? 0 : maxQuant - optionState.quality,
|
quality: optionState.lossless ? MAX_QUALITY : optionState.quality,
|
||||||
cqAlphaLevel:
|
qualityAlpha:
|
||||||
optionState.lossless || !optionState.separateAlpha
|
optionState.lossless || !optionState.separateAlpha
|
||||||
? -1
|
? -1 // Set qualityAlpha to quality
|
||||||
: maxQuant - optionState.alphaQuality,
|
: optionState.alphaQuality,
|
||||||
// Always set to 4:4:4 if lossless
|
// Always set to 4:4:4 if lossless
|
||||||
subsample: optionState.lossless ? 3 : optionState.subsample,
|
subsample: optionState.lossless ? 3 : optionState.subsample,
|
||||||
tileColsLog2: optionState.tileCols,
|
tileColsLog2: optionState.tileCols,
|
||||||
tileRowsLog2: optionState.tileRows,
|
tileRowsLog2: optionState.tileRows,
|
||||||
speed: maxSpeed - optionState.effort,
|
speed: MAX_EFFORT - optionState.effort,
|
||||||
chromaDeltaQ: optionState.chromaDeltaQ,
|
chromaDeltaQ: optionState.chromaDeltaQ,
|
||||||
sharpness: optionState.sharpness,
|
sharpness: optionState.sharpness,
|
||||||
denoiseLevel: optionState.denoiseLevel,
|
denoiseLevel: optionState.denoiseLevel,
|
||||||
tune: optionState.tune,
|
tune: optionState.tune,
|
||||||
|
enableSharpYUV: optionState.enableSharpYUV,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||||
@@ -167,6 +170,7 @@ export class Options extends Component<Props, State> {
|
|||||||
sharpness,
|
sharpness,
|
||||||
denoiseLevel,
|
denoiseLevel,
|
||||||
tune,
|
tune,
|
||||||
|
enableSharpYUV,
|
||||||
}: State,
|
}: State,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -183,7 +187,7 @@ export class Options extends Component<Props, State> {
|
|||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
min="0"
|
min="0"
|
||||||
max="63"
|
max={MAX_QUALITY - 1} // MAX_QUALITY would mean lossless
|
||||||
value={quality}
|
value={quality}
|
||||||
onInput={this._inputChange('quality', 'number')}
|
onInput={this._inputChange('quality', 'number')}
|
||||||
>
|
>
|
||||||
@@ -211,11 +215,26 @@ export class Options extends Component<Props, State> {
|
|||||||
value={subsample}
|
value={subsample}
|
||||||
onChange={this._inputChange('subsample', 'number')}
|
onChange={this._inputChange('subsample', 'number')}
|
||||||
>
|
>
|
||||||
<option value="1">Half</option>
|
<option value="0">4:0:0</option>
|
||||||
{/*<option value="2">4:2:2</option>*/}
|
<option value="1">4:2:0</option>
|
||||||
<option value="3">Off</option>
|
<option value="2">4:2:2</option>
|
||||||
|
<option value="3">4:4:4</option>
|
||||||
</Select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
|
<Expander>
|
||||||
|
{subsample === 1 && (
|
||||||
|
<label class={style.optionToggle}>
|
||||||
|
Sharp YUV Downsampling
|
||||||
|
<Checkbox
|
||||||
|
checked={enableSharpYUV}
|
||||||
|
onChange={this._inputChange(
|
||||||
|
'enableSharpYUV',
|
||||||
|
'boolean',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</Expander>
|
||||||
<label class={style.optionToggle}>
|
<label class={style.optionToggle}>
|
||||||
Separate alpha quality
|
Separate alpha quality
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -228,7 +247,7 @@ export class Options extends Component<Props, State> {
|
|||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
min="0"
|
min="0"
|
||||||
max="63"
|
max={MAX_QUALITY - 1} // MAX_QUALITY would mean lossless
|
||||||
value={alphaQuality}
|
value={alphaQuality}
|
||||||
onInput={this._inputChange(
|
onInput={this._inputChange(
|
||||||
'alphaQuality',
|
'alphaQuality',
|
||||||
@@ -307,7 +326,7 @@ export class Options extends Component<Props, State> {
|
|||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max={MAX_EFFORT}
|
||||||
value={effort}
|
value={effort}
|
||||||
onInput={this._inputChange('effort', 'number')}
|
onInput={this._inputChange('effort', 'number')}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export const label = 'AVIF';
|
|||||||
export const mimeType = 'image/avif';
|
export const mimeType = 'image/avif';
|
||||||
export const extension = 'avif';
|
export const extension = 'avif';
|
||||||
export const defaultOptions: EncodeOptions = {
|
export const defaultOptions: EncodeOptions = {
|
||||||
cqLevel: 33,
|
quality: 50,
|
||||||
cqAlphaLevel: -1,
|
qualityAlpha: -1,
|
||||||
denoiseLevel: 0,
|
denoiseLevel: 0,
|
||||||
tileColsLog2: 0,
|
tileColsLog2: 0,
|
||||||
tileRowsLog2: 0,
|
tileRowsLog2: 0,
|
||||||
@@ -28,4 +28,5 @@ export const defaultOptions: EncodeOptions = {
|
|||||||
chromaDeltaQ: false,
|
chromaDeltaQ: false,
|
||||||
sharpness: 0,
|
sharpness: 0,
|
||||||
tune: AVIFTune.auto,
|
tune: AVIFTune.auto,
|
||||||
|
enableSharpYUV: false,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user