Commit d3772c0f for libheif
commit d3772c0f9a009ee19c6b3650934345a4acb5b4fd
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Mon Sep 21 15:10:18 2026 +0200
kvazaar: configure the input format through config_parse()
kvazaar git master (cbdcc31a, 2026-07-27) keeps the encoder chroma format
in new kvz_config fields that default to 4:2:0 and are only updated by the
"input-format" parser. Writing config->input_format directly leaves them
at 4:2:0, so every monochrome encode, including every alpha auxiliary
image, dereferences the NULL chroma planes of the 4:0:0 input picture.
Released kvazaar versions derive the chroma format from input_format and
are not affected.
Set the input format through api->config_parse(), which is how kvazaar's
own CLI configures the encoder and works on every version. Chroma formats
a kvazaar build does not support are rejected by the parser and now return
an error instead of running the encoder in a mode it was not built for.
Reported by @twn39 in #1915, whose regression test this includes.
diff --git a/libheif/plugins/encoder_kvazaar.cc b/libheif/plugins/encoder_kvazaar.cc
index 7affa6d2..dcad1eb8 100644
--- a/libheif/plugins/encoder_kvazaar.cc
+++ b/libheif/plugins/encoder_kvazaar.cc
@@ -525,17 +525,26 @@ static heif_error kvazaar_start_sequence_encoding_intern(void* encoder_raw, cons
config->framerate_num = framerate_num;
config->framerate_denom = framerate_denom;
+ // Set the input format through config_parse() instead of writing config->input_format directly.
+ // kvazaar git master (since July 2026) keeps the encoder chroma format in separate kvz_config
+ // fields that default to 4:2:0 and are only updated by the "input-format" parser. A direct
+ // assignment leaves them at 4:2:0, and the encoder then dereferences the NULL chroma planes of
+ // a 4:0:0 input picture. Released kvazaar versions derive the chroma format from input_format,
+ // so the parser works for them as well. The parser also rejects the chroma formats a kvazaar
+ // build does not support (4:2:2 and 4:4:4 in released versions), which becomes an error here
+ // instead of an encoder running in a mode it was not built for.
+ const char* input_format;
if (isGreyscale) {
- config->input_format = KVZ_FORMAT_P400;
+ input_format = "P400";
}
else if (chroma == heif_chroma_420) {
- config->input_format = KVZ_FORMAT_P420;
+ input_format = "P420";
}
else if (chroma == heif_chroma_422) {
- config->input_format = KVZ_FORMAT_P422;
+ input_format = "P422";
}
else if (chroma == heif_chroma_444) {
- config->input_format = KVZ_FORMAT_P444;
+ input_format = "P444";
}
else {
return heif_error{
@@ -545,6 +554,14 @@ static heif_error kvazaar_start_sequence_encoding_intern(void* encoder_raw, cons
};
}
+ if (!api->config_parse(config, "input-format", input_format)) {
+ return heif_error{
+ heif_error_Encoder_plugin_error,
+ heif_suberror_Unsupported_image_type,
+ kError_unsupported_chroma
+ };
+ }
+
heif_color_profile_nclx* nclx = nullptr;
heif_error err = heif_image_get_nclx_color_profile(image, &nclx);
diff --git a/tests/encode.cc b/tests/encode.cc
index 7c4c6653..8136a604 100644
--- a/tests/encode.cc
+++ b/tests/encode.cc
@@ -206,3 +206,51 @@ TEST_CASE("x265 rejects oversized images", "[heif_encoder]") {
heif_encoder_release(enc);
heif_context_free(ctx);
}
+
+
+TEST_CASE("kvazaar encodes monochrome images", "[heif_encoder]") {
+ // Regression test for PR #1915. kvazaar git master keeps the encoder chroma format in
+ // kvz_config fields that only the "input-format" parser updates. Setting input_format
+ // directly left the encoder at 4:2:0 while the 4:0:0 input picture had no chroma planes,
+ // which crashed every monochrome encode, including every alpha auxiliary image.
+
+ const heif_encoder_descriptor* descriptor = nullptr;
+ int n = heif_get_encoder_descriptors(heif_compression_HEVC, "kvazaar", &descriptor, 1);
+ if (n == 0) {
+ SKIP("kvazaar encoder not available, skipping test");
+ }
+
+ heif_context* ctx = heif_context_alloc();
+ heif_encoder* enc = nullptr;
+ heif_error err = heif_context_get_encoder(ctx, descriptor, &enc);
+ REQUIRE(err.code == heif_error_Ok);
+
+ const int w = 128;
+ const int h = 128;
+ heif_image* img = nullptr;
+ err = heif_image_create(w, h, heif_colorspace_monochrome, heif_chroma_monochrome, &img);
+ REQUIRE(err.code == heif_error_Ok);
+ err = heif_image_add_plane(img, heif_channel_Y, w, h, 8);
+ REQUIRE(err.code == heif_error_Ok);
+
+ size_t stride;
+ uint8_t* plane = heif_image_get_plane2(img, heif_channel_Y, &stride);
+ REQUIRE(plane != nullptr);
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ plane[y * stride + x] = (uint8_t) (x + y);
+ }
+ }
+
+ heif_image_handle* handle = nullptr;
+ err = heif_context_encode_image(ctx, img, enc, nullptr, &handle);
+ REQUIRE(err.code == heif_error_Ok);
+ REQUIRE(handle != nullptr);
+ REQUIRE(heif_image_handle_get_width(handle) == w);
+ REQUIRE(heif_image_handle_get_height(handle) == h);
+
+ heif_image_handle_release(handle);
+ heif_image_release(img);
+ heif_encoder_release(enc);
+ heif_context_free(ctx);
+}