Commit fc1b310f for libheif

commit fc1b310fdc62905629ff8b476fbb8d43c11668e8
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sat Mar 14 00:27:01 2026 +0100

    zero-fill missing tiles to avoid uninitialized memory

diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc
index 9744c1d0..bb1f4d08 100644
--- a/libheif/image-items/grid.cc
+++ b/libheif/image-items/grid.cc
@@ -24,6 +24,7 @@
 #include <cstring>
 #include <deque>
 #include <future>
+#include <mutex>
 #include <set>
 #include <algorithm>
 #include "api_structs.h"
@@ -303,6 +304,7 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
   int progress_counter = 0;
   bool cancelled = false;
   std::shared_ptr<std::vector<Error> > warnings(new std::vector<Error>());
+  std::vector<FailedTile> failed_tiles;

   for (uint32_t y = 0; y < grid.get_rows() && !cancelled; y++) {
     uint32_t x0 = 0;
@@ -319,6 +321,7 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
             heif_error_Invalid_input,
             heif_suberror_Missing_grid_images,
           });
+          failed_tiles.push_back({x0, y0});
           reference_idx++;
           x0 += tile_width;
           continue;
@@ -332,6 +335,7 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
         if (!options.strict_decoding && reference_idx != 0) {
           // Skip missing tiles (unless it's the first one).
           warnings->push_back(error);
+          failed_tiles.push_back({x0, y0});
           reference_idx++;
           x0 += tile_width;
           continue;
@@ -379,7 +383,7 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
           }
         }

-        err = decode_and_paste_tile_image(tileID, x0, y0, img, options, progress_counter, warnings, processed_ids);
+        err = decode_and_paste_tile_image(tileID, x0, y0, img, options, progress_counter, warnings, failed_tiles, processed_ids);
         if (err) {
           return err;
         }
@@ -427,7 +431,7 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
       errs.push_back(std::async(std::launch::async,
                                 &ImageItem_Grid::decode_and_paste_tile_image, this,
                                 data.tileID, data.x_origin, data.y_origin, std::ref(img), options,
-                                std::ref(progress_counter), warnings, processed_ids));
+                                std::ref(progress_counter), warnings, std::ref(failed_tiles), processed_ids));
     }

     // check for decoding errors in remaining tiles
@@ -452,6 +456,9 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem_Grid::decode_full_grid_image(c
   }

   if (img) {
+    for (const auto& tile : failed_tiles) {
+      img->zero_region(tile.x0, tile.y0, tile_width, tile_height);
+    }
     img->add_warnings(*warnings.get());
   }

@@ -475,23 +482,26 @@ Error ImageItem_Grid::decode_and_paste_tile_image(heif_item_id tileID, uint32_t
                                                   const heif_decoding_options& options,
                                                   int& progress_counter,
                                                   std::shared_ptr<std::vector<Error> > warnings,
+                                                  std::vector<FailedTile>& failed_tiles,
                                                   std::set<heif_item_id> processed_ids) const
 {
   std::shared_ptr<HeifPixelImage> tile_img;
 #if ENABLE_PARALLEL_TILE_DECODING
-  static std::mutex warningsMutex;
+  static std::mutex failedTilesMutex;
 #endif

   auto tileItem = get_context()->get_image(tileID, true);
   if (!tileItem && !options.strict_decoding) {
     // We ignore missing images.
 #if ENABLE_PARALLEL_TILE_DECODING
-    std::lock_guard<std::mutex> lock(warningsMutex);
+    std::lock_guard<std::mutex> lock(failedTilesMutex);
 #endif
-    warnings->push_back(Error{
+    warnings->emplace_back(
       heif_error_Invalid_input,
       heif_suberror_Missing_grid_images,
-    });
+      "Missing grid image"
+    );
+    failed_tiles.push_back({x0, y0});
     return progress_and_return_ok(options, progress_counter);
   }

@@ -505,9 +515,10 @@ Error ImageItem_Grid::decode_and_paste_tile_image(heif_item_id tileID, uint32_t
     if (!options.strict_decoding) {
       // We ignore broken tiles.
 #if ENABLE_PARALLEL_TILE_DECODING
-      std::lock_guard<std::mutex> lock(warningsMutex);
+      std::lock_guard<std::mutex> lock(failedTilesMutex);
 #endif
       warnings->push_back(decodeResult.error());
+      failed_tiles.push_back({x0, y0});
       return progress_and_return_ok(options, progress_counter);
     }

diff --git a/libheif/image-items/grid.h b/libheif/image-items/grid.h
index 0555d88f..032086c2 100644
--- a/libheif/image-items/grid.h
+++ b/libheif/image-items/grid.h
@@ -167,10 +167,17 @@ private:

   Result<std::shared_ptr<HeifPixelImage>> decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty, std::set<heif_item_id> processed_ids) const;

+  struct FailedTile {
+    // Top-left pixel coordinates
+    uint32_t x0;
+    uint32_t y0;
+  };
+
   Error decode_and_paste_tile_image(heif_item_id tileID, uint32_t x0, uint32_t y0,
                                     std::shared_ptr<HeifPixelImage>& inout_image,
                                     const heif_decoding_options& options, int& progress_counter,
                                     std::shared_ptr<std::vector<Error> > warnings,
+                                    std::vector<FailedTile>& failed_tiles,
                                     std::set<heif_item_id> processed_ids) const;
 };

diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc
index 3d0b8cb3..eaa2bba4 100644
--- a/libheif/pixelimage.cc
+++ b/libheif/pixelimage.cc
@@ -1255,6 +1255,41 @@ Error HeifPixelImage::copy_image_to(const std::shared_ptr<const HeifPixelImage>&
 }


+void HeifPixelImage::zero_region(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h)
+{
+  uint32_t img_w = get_width();
+  uint32_t img_h = get_height();
+  heif_chroma chroma = get_chroma_format();
+
+  std::set<enum heif_channel> channels = get_channel_set();
+
+  for (heif_channel channel : channels) {
+    uint32_t cx0 = channel_width(x0, chroma, channel);
+    uint32_t cy0 = channel_height(y0, chroma, channel);
+    uint32_t cw = channel_width(w, chroma, channel);
+    uint32_t ch = channel_height(h, chroma, channel);
+
+    // clamp to plane bounds
+    uint32_t plane_w = channel_width(img_w, chroma, channel);
+    uint32_t plane_h = channel_height(img_h, chroma, channel);
+    if (cx0 >= plane_w || cy0 >= plane_h) {
+      continue;
+    }
+    cw = std::min(cw, plane_w - cx0);
+    ch = std::min(ch, plane_h - cy0);
+
+    size_t stride = 0;
+    uint8_t* data = get_plane(channel, &stride);
+    uint32_t bytes_per_pixel = get_storage_bits_per_pixel(channel) / 8;
+    uint32_t width_bytes = cw * bytes_per_pixel;
+
+    for (uint32_t y = 0; y < ch; y++) {
+      memset(data + cx0 * bytes_per_pixel + (cy0 + y) * stride, 0, width_bytes);
+    }
+  }
+}
+
+
 Result<std::shared_ptr<HeifPixelImage>> HeifPixelImage::rotate_ccw(int angle_degrees, const heif_security_limits* limits)
 {
   // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation
diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h
index 6e14471e..56c4d7f0 100644
--- a/libheif/pixelimage.h
+++ b/libheif/pixelimage.h
@@ -508,6 +508,8 @@ public:

   Error copy_image_to(const std::shared_ptr<const HeifPixelImage>& source, uint32_t x0, uint32_t y0);

+  void zero_region(uint32_t x0, uint32_t y0, uint32_t w, uint32_t h);
+
   Result<std::shared_ptr<HeifPixelImage>> rotate_ccw(int angle_degrees, const heif_security_limits* limits);

   Result<std::shared_ptr<HeifPixelImage>> mirror_inplace(heif_transform_mirror_direction, const heif_security_limits* limits);