mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-11 16:26:20 +00:00
* 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>
170 lines
5.1 KiB
C++
170 lines
5.1 KiB
C++
#include <emscripten/bind.h>
|
|
#include <emscripten/threading.h>
|
|
#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 - 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;
|
|
int tileColsLog2;
|
|
// [0 - 10]
|
|
// 0 = slowest
|
|
// 10 = fastest
|
|
int speed;
|
|
// 0 = 4:0:0
|
|
// 1 = 4:2:0
|
|
// 2 = 4:2:2
|
|
// 3 = 4:4:4
|
|
int subsample;
|
|
// Extra chroma compression
|
|
bool chromaDeltaQ;
|
|
// 0-7
|
|
int sharpness;
|
|
// 0 = auto
|
|
// 1 = PSNR
|
|
// 2 = SSIM
|
|
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) {
|
|
avifResult status; // To check the return status for avif API's
|
|
|
|
int depth = 8;
|
|
avifPixelFormat format;
|
|
switch (options.subsample) {
|
|
case 0:
|
|
format = AVIF_PIXEL_FORMAT_YUV400;
|
|
break;
|
|
case 1:
|
|
format = AVIF_PIXEL_FORMAT_YUV420;
|
|
break;
|
|
case 2:
|
|
format = AVIF_PIXEL_FORMAT_YUV422;
|
|
break;
|
|
case 3:
|
|
format = AVIF_PIXEL_FORMAT_YUV444;
|
|
break;
|
|
}
|
|
|
|
bool lossless = options.quality == AVIF_QUALITY_LOSSLESS &&
|
|
(options.qualityAlpha == -1 || options.qualityAlpha == AVIF_QUALITY_LOSSLESS) &&
|
|
format == AVIF_PIXEL_FORMAT_YUV444;
|
|
|
|
// 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;
|
|
} else {
|
|
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT601;
|
|
}
|
|
|
|
uint8_t* rgba = reinterpret_cast<uint8_t*>(const_cast<char*>(buffer.data()));
|
|
|
|
avifRGBImage srcRGB;
|
|
avifRGBImageSetDefaults(&srcRGB, image.get());
|
|
srcRGB.pixels = rgba;
|
|
srcRGB.rowBytes = width * 4;
|
|
if (options.enableSharpYUV) {
|
|
srcRGB.chromaDownsampling = AVIF_CHROMA_DOWNSAMPLING_SHARP_YUV;
|
|
}
|
|
status = avifImageRGBToYUV(image.get(), &srcRGB);
|
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
|
|
|
// Create a smart pointer for the encoder
|
|
AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy);
|
|
RETURN_NULL_IF(encoder == nullptr);
|
|
|
|
if (lossless) {
|
|
encoder->quality = AVIF_QUALITY_LOSSLESS;
|
|
encoder->qualityAlpha = AVIF_QUALITY_LOSSLESS;
|
|
} else {
|
|
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.quality >= 50)) {
|
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "tune", "ssim");
|
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
|
}
|
|
|
|
if (options.chromaDeltaQ) {
|
|
status = avifEncoderSetCodecSpecificOption(encoder.get(), "color:enable-chroma-deltaq", "1");
|
|
RETURN_NULL_IF(status != AVIF_RESULT_OK);
|
|
}
|
|
|
|
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;
|
|
|
|
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));
|
|
}
|
|
|
|
avifRWDataFree(&output);
|
|
return js_result;
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(my_module) {
|
|
value_object<AvifOptions>("AvifOptions")
|
|
.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("enableSharpYUV", &AvifOptions::enableSharpYUV);
|
|
|
|
function("encode", &encode);
|
|
}
|