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);
+}