-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update libavif #1381
Update libavif #1381
Changes from 21 commits
47f8746
f8804b1
82658c7
642ac6d
96da59f
a5a3f63
f1f4030
3ed34e1
625eee2
1b3f791
f27d524
47cd47e
25ea8b9
0413c66
ab79caf
41c4b30
faa1467
fd53823
aad51d3
699b9e0
4cdc246
7099d5e
aa89d7c
795e395
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,31 @@ | |
#include <emscripten/val.h> | ||
#include "avif/avif.h" | ||
|
||
#include <string> | ||
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#define RETURN_NULL_IF_NOT_EQUALS(val1, val2) \ | ||
do { \ | ||
if (val1 != val2) \ | ||
return val::null(); \ | ||
} while (false) | ||
#define RETURN_NULL_IF_EQUALS(val1, val2) \ | ||
do { \ | ||
if (val1 == val2) \ | ||
return val::null(); \ | ||
} while (false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional: You can merge these two macros into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's so much better than the current macro LOL, thank you! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I probably would not use a macro that saves only one line. But some people like to use this kind of macro so that the main flow of the code is not cluttered with error handling code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep that was my line of thinking. I'm pretty sure we'll be repeating that line again for other optimizations, so it's better to just have a macro for it. |
||
|
||
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 | ||
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
int quality; | ||
// As above, but -1 means 'use quality' | ||
int qualityAlpha; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a follow-up pull request, we should look into using the new I suggest Squoosh set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha, I'll try to add it to this PR or at least make raise an issue for it. I assume There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless this requires just simple changes, I suggest filing an issue for this and implementing this in a follow-up pull request. The review of this pull request has been going on for a while. We need to watch out for "code review fatigue." You understand
I do think all the automatic tiling algorithms have some common elements, such as imposing a minimum tile area or tile width and height, and potentially imposing a maximum number of tiles. But I am not sure if you need to mention this. You can, however, say that the use of tiles makes it easier for the decoder to decode an AVIF image with multiple threads in parallel. |
||
// [0 - 6] | ||
// Creates 2^n tiles in that dimension | ||
int tileRowsLog2; | ||
|
@@ -35,11 +51,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) { | ||
avifResult status; // To check the return status for avif API's | ||
|
||
avifRWData output = AVIF_DATA_EMPTY; | ||
int depth = 8; | ||
avifPixelFormat format; | ||
|
@@ -58,11 +78,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 generated image | ||
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
AvifImagePtr image(avifImageCreate(width, height, depth, format), avifImageDestroy); | ||
RETURN_NULL_IF_EQUALS(image, nullptr); | ||
|
||
if (lossless) { | ||
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; | ||
|
@@ -73,74 +95,79 @@ 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_NOT_EQUALS(status, AVIF_RESULT_OK); | ||
|
||
avifEncoder* encoder = avifEncoderCreate(); | ||
// Create a smart pointer for the encoder | ||
AvifEncoderPtr encoder(avifEncoderCreate(), avifEncoderDestroy); | ||
RETURN_NULL_IF_EQUALS(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()); | ||
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
RETURN_NULL_IF_NOT_EQUALS(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_NOT_EQUALS(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_NOT_EQUALS(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_NOT_EQUALS(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); | ||
avifResult encodeResult = avifEncoderWrite(encoder.get(), image.get(), &output); | ||
aryanpingle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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); | ||
} |
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed you pass
-DCONFIG_AV1_HIGHBITDEPTH=0
to libaom (e.g., in line 47). Do you only support bit depth 8 when encoding AVIF?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, for all codecs on Squoosh.