Skip to content

Commit

Permalink
chore: implement upstream libavif upgrade changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jamsinclair committed Jan 7, 2024
1 parent 48d63d3 commit 4cb9664
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 61 deletions.
76 changes: 59 additions & 17 deletions packages/avif/codec/Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# libavif and libaom versions are from
# https://docs.google.com/document/d/1wEEA5rRU7wT54k41u3qyZIZHDCJArIMzLuzsrLAwaK8/edit
CODEC_URL = https://github.com/AOMediaCodec/libavif/archive/1c39e772c2c0d687691dd4b589a12c323f5f767d.tar.gz
CODEC_PACKAGE = node_modules/libavif.tar.gz
# using libavif from https://github.com/AOMediaCodec/libavif
LIBAVIF_URL = https://github.com/AOMediaCodec/libavif/archive/refs/tags/v1.0.1.tar.gz
LIBAVIF_PACKAGE = node_modules/libavif.tar.gz

LIBAOM_URL = https://aomedia.googlesource.com/aom/+archive/v3.1.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

export CODEC_DIR = node_modules/libavif
export BUILD_DIR = node_modules/build
export LIBAOM_DIR = node_modules/libaom

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_MT_JS = enc/avif_enc_mt.js
Expand All @@ -27,7 +32,8 @@ HELPER_MAKEFLAGS := -f helper.Makefile

all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS)

$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
# ST-Encoding
$(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt $(LIBSHARPYUV)
$(MAKE) \
$(HELPER_MAKEFLAGS) \
OUT_JS=$@ \
Expand All @@ -38,9 +44,10 @@ $(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"
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON"

$(OUT_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) \
$(HELPER_MAKEFLAGS) \
OUT_JS=$@ \
Expand All @@ -50,9 +57,10 @@ $(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" \
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0 -DAVIF_LOCAL_LIBSHARPYUV=ON" \
OUT_FLAGS="-pthread"

# Decoding
$(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
$(MAKE) \
$(HELPER_MAKEFLAGS) \
Expand All @@ -65,22 +73,56 @@ $(OUT_DEC_JS): $(OUT_DEC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLis
ENVIRONMENT=$(ENVIRONMENT) \
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_ENCODE=0"

$(CODEC_PACKAGE):
mkdir -p $(@D)
curl -sL $(CODEC_URL) -o $@
# LIBAOM EXTRACTION SECTION

# Download the libaom tarball
$(LIBAOM_PACKAGE):
mkdir -p $(@D)
curl -sL $(LIBAOM_URL) -o $@

$(CODEC_DIR)/CMakeLists.txt: $(CODEC_PACKAGE)
mkdir -p $(@D)
tar xzm --strip 1 -C $(@D) -f $(CODEC_PACKAGE)

# Extract libaom from the tarball
$(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
mkdir -p $(@D)
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:
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_JS) clean
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_MT_JS) clean
Expand Down
103 changes: 63 additions & 40 deletions packages/avif/codec/enc/avif_enc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@
#include <emscripten/val.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 AvifImagePtr = std::unique_ptr<avifImage, decltype(&avifImageDestroy)>;
using AvifEncoderPtr = std::unique_ptr<avifEncoder, decltype(&avifEncoderDestroy)>;

struct AvifOptions {
// [0 - 63]
// 0 = lossless
// 63 = worst quality
int cqLevel;
// As above, but -1 means 'use cqLevel'
int cqAlphaLevel;
// [0 - 100]
// 0 = worst quality
// 100 = lossless
int quality;
// As above, but -1 means 'use quality'
int qualityAlpha;
// [0 - 6]
// Creates 2^n tiles in that dimension
int tileRowsLog2;
Expand All @@ -35,12 +47,15 @@ struct AvifOptions {
int tune;
// 0-50
int denoiseLevel;
// toggles AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV
bool enableSharpYUV;
};

thread_local const val Uint8Array = val::global("Uint8Array");

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;
avifPixelFormat format;
switch (options.subsample) {
Expand All @@ -58,11 +73,13 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
break;
}

bool lossless = options.cqLevel == AVIF_QUANTIZER_LOSSLESS &&
options.cqAlphaLevel <= AVIF_QUANTIZER_LOSSLESS &&
bool lossless = options.quality == AVIF_QUALITY_LOSSLESS &&
(options.qualityAlpha == -1 || options.qualityAlpha == AVIF_QUALITY_LOSSLESS) &&
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) {
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
Expand All @@ -73,74 +90,80 @@ val encode(std::string buffer, int width, int height, AvifOptions options) {
uint8_t* rgba = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer.data()));

avifRGBImage srcRGB;
avifRGBImageSetDefaults(&srcRGB, image);
avifRGBImageSetDefaults(&srcRGB, image.get());
srcRGB.pixels = rgba;
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) {
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
encoder->minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS;
encoder->quality = AVIF_QUALITY_LOSSLESS;
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
} else {
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());

if (options.cqAlphaLevel != -1) {
avifEncoderSetCodecSpecificOption(encoder, "alpha:cq-level",
std::to_string(options.cqAlphaLevel).c_str());
status = avifEncoderSetCodecSpecificOption(encoder.get(), "sharpness",
std::to_string(options.sharpness).c_str());
RETURN_NULL_IF(status != AVIF_RESULT_OK);

// 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.tune == 2 || (options.tune == 0 && options.cqLevel <= 32)) {
avifEncoderSetCodecSpecificOption(encoder, "tune", "ssim");
if (options.tune == 2 || (options.tune == 0 && options.quality >= 50)) {
status = avifEncoderSetCodecSpecificOption(encoder.get(), "tune", "ssim");
RETURN_NULL_IF(status != AVIF_RESULT_OK);
}

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",
std::to_string(options.denoiseLevel).c_str());
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:denoise-noise-level",
std::to_string(options.denoiseLevel).c_str());
RETURN_NULL_IF(status != AVIF_RESULT_OK);
}

encoder->maxThreads = emscripten_num_logical_cores();
encoder->tileRowsLog2 = options.tileRowsLog2;
encoder->tileColsLog2 = options.tileColsLog2;
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();
if (encodeResult == AVIF_RESULT_OK) {
js_result = Uint8Array.new_(typed_memory_view(output.size, output.data));
}

avifImageDestroy(image);
avifEncoderDestroy(encoder);
avifRWDataFree(&output);
return js_result;
}

EMSCRIPTEN_BINDINGS(my_module) {
value_object<AvifOptions>("AvifOptions")
.field("cqLevel", &AvifOptions::cqLevel)
.field("cqAlphaLevel", &AvifOptions::cqAlphaLevel)
.field("quality", &AvifOptions::quality)
.field("qualityAlpha", &AvifOptions::qualityAlpha)
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
.field("tileColsLog2", &AvifOptions::tileColsLog2)
.field("speed", &AvifOptions::speed)
.field("chromaDeltaQ", &AvifOptions::chromaDeltaQ)
.field("sharpness", &AvifOptions::sharpness)
.field("tune", &AvifOptions::tune)
.field("denoiseLevel", &AvifOptions::denoiseLevel)
.field("subsample", &AvifOptions::subsample);
.field("subsample", &AvifOptions::subsample)
.field("enableSharpYUV", &AvifOptions::enableSharpYUV);

function("encode", &encode);
}
14 changes: 13 additions & 1 deletion packages/avif/codec/helper.Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
# $(LIBAVIF_FLAGS)
# $(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))

# We're making libavif and libaom for every node_modules/[enc|dec]/
CODEC_BUILD_DIR := $(OUT_BUILD_DIR)/libavif
CODEC_OUT := $(CODEC_BUILD_DIR)/libavif.a

Expand All @@ -25,13 +28,21 @@ 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)
$(CODEC_OUT): $(LIBSHARPYUV)
endif

$(OUT_JS): $(OUT_CPP) $(LIBAOM_OUT) $(CODEC_OUT)
$(CXX) \
-I $(CODEC_DIR)/include \
$(CXXFLAGS) \
$(LDFLAGS) \
$(OUT_FLAGS) \
--bind \
-s ERROR_ON_UNDEFINED_SYMBOLS=0 \
-s ENVIRONMENT=$(ENVIRONMENT) \
-s EXPORT_ES6=1 \
-s DYNAMIC_EXECUTION=0 \
Expand All @@ -41,6 +52,7 @@ $(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 \
Expand Down Expand Up @@ -72,4 +84,4 @@ $(LIBAOM_OUT): $(LIBAOM_DIR)/CMakeLists.txt
clean:
$(RM) $(OUT_JS) $(OUT_WASM) $(OUT_WORKER)
$(MAKE) -C $(CODEC_BUILD_DIR) clean
$(MAKE) -C $(LIBAOM_BUILD_DIR) clean
$(MAKE) -C $(LIBAOM_BUILD_DIR) clean
4 changes: 2 additions & 2 deletions packages/avif/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const label = 'AVIF';
export const mimeType = 'image/avif';
export const extension = 'avif';
export const defaultOptions: EncodeOptions = {
cqLevel: 33,
cqAlphaLevel: -1,
quality: 50,
qualityAlpha: -1,
denoiseLevel: 0,
tileColsLog2: 0,
tileRowsLog2: 0,
Expand Down
2 changes: 1 addition & 1 deletion tools/cpp.dockerfile
Original file line number Diff line number Diff line change
@@ -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
ENV CFLAGS "-O3 -flto"
ENV CXXFLAGS "${CFLAGS} -std=c++17"
Expand Down

0 comments on commit 4cb9664

Please sign in to comment.