Commit ad9c2220 for libheif

commit ad9c2220424de12448c7a502dca82d6beedcb10b
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu May 28 12:17:44 2026 +0200

    heif_context_add_grid_image: fall back to default options when null

    add_new_grid_item() dereferenced encoding_options unconditionally
    (set_tile_encoding_options copy + image_orientation read), so passing
    a null encoding_options to the public API crashed. Match the behaviour
    of heif_context_encode_grid() and allocate a default heif_encoding_options
    struct when the caller passes nullptr.

    The test's tile-by-tile YCbCr 4:2:0 case now passes nullptr to exercise
    this path.

diff --git a/libheif/api/libheif/heif_tiling.cc b/libheif/api/libheif/heif_tiling.cc
index cc9f8171..9dbd6e53 100644
--- a/libheif/api/libheif/heif_tiling.cc
+++ b/libheif/api/libheif/heif_tiling.cc
@@ -232,12 +232,23 @@ heif_error heif_context_add_grid_image(heif_context* ctx,
     };
   }

+  // Fall back to default options when the caller passes nullptr.
+  // add_new_grid_item() copies these into ImageItem_Grid::m_tile_encoding_options,
+  // so the fallback struct only needs to live for the duration of the call.
+  heif_encoding_options* default_options = nullptr;
+  if (!encoding_options) {
+    default_options = heif_encoding_options_alloc();
+    encoding_options = default_options;
+  }
+
   auto generateGridItemResult = ImageItem_Grid::add_new_grid_item(ctx->context.get(),
                                                                   image_width,
                                                                   image_height,
                                                                   static_cast<uint16_t>(tile_rows),
                                                                   static_cast<uint16_t>(tile_columns),
                                                                   encoding_options);
+  heif_encoding_options_free(default_options);
+
   if (!generateGridItemResult) {
     return generateGridItemResult.error_struct(ctx->context.get());
   }
diff --git a/tests/encode_grid.cc b/tests/encode_grid.cc
index c39ad480..5640a23c 100644
--- a/tests/encode_grid.cc
+++ b/tests/encode_grid.cc
@@ -398,18 +398,14 @@ TEST_CASE("grid encoding tile-by-tile - uncompressed YCbCr 4:2:0 (extract_area e
   heif_context* ctx = heif_context_alloc();
   REQUIRE(ctx != nullptr);

-  // Build the grid item with an explicit overall image size — this path
-  // sidesteps the heif_context_encode_grid limitation of reading tile size
-  // via get_width(heif_channel_interleaved) (which would be 0 for planar
-  // YCbCr tiles).
-  heif_encoding_options* enc_options = heif_encoding_options_alloc();
-  REQUIRE(enc_options != nullptr);
-
+  // Build the grid item with an explicit overall image size. Pass nullptr
+  // for encoding_options to also regression-check that heif_context_add_grid_image
+  // falls back to default options rather than dereferencing a null pointer.
   heif_image_handle* grid_handle = nullptr;
   REQUIRE(heif_context_add_grid_image(ctx,
                                       kCols * kTileSize, kRows * kTileSize,
                                       kCols, kRows,
-                                      enc_options, &grid_handle).code == heif_error_Ok);
+                                      nullptr, &grid_handle).code == heif_error_Ok);
   REQUIRE(grid_handle != nullptr);

   for (uint32_t ty = 0; ty < kRows; ++ty) {
@@ -424,7 +420,6 @@ TEST_CASE("grid encoding tile-by-tile - uncompressed YCbCr 4:2:0 (extract_area e
   REQUIRE(heif_context_write_to_file(ctx, out_path.c_str()).code == heif_error_Ok);

   heif_image_handle_release(grid_handle);
-  heif_encoding_options_free(enc_options);
   for (heif_image* t : tiles) {
     heif_image_release(t);
   }