Commit 9908fd73 for libheif

commit 9908fd733b9e4fa5b5a77e1a842a1a6ad9c0cf50
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Oct 9 22:51:23 2026 +0200

    rotate_ccw: compute block ends without wrapping, drop a duplicate include, fix test ownership

    Follow-ups to #1934:

    - `start + block` wrapped for plane sizes close to 2^32, which is reachable
      with the security limits disabled, and the block loops then restarted at 0.
      Compute the block end by subtraction and step to it.
    - `<bit>` was included twice.
    - `rotate_ccw()` calls `shared_from_this()` for some inputs, so the test image
      must be owned by a shared_ptr, like the other plane tests do.

diff --git a/libheif/image/pixelimage.cc b/libheif/image/pixelimage.cc
index 1fe9b5f7..e3735d19 100644
--- a/libheif/image/pixelimage.cc
+++ b/libheif/image/pixelimage.cc
@@ -30,7 +30,6 @@
 #include <utility>
 #include <limits>
 #include <algorithm>
-#include <bit>
 #include <map>
 #include <string>
 #include <sstream>
@@ -1775,6 +1774,12 @@ void HeifPixelImage::ComponentStorage::rotate_ccw(int angle_degrees,
   // rotated in blocks of 64x64 samples, whose rows stay in the cache while the block is copied.
   constexpr uint32_t block = 64;

+  // End of the block that starts at `start` in a dimension of `size` samples. `start + block`
+  // would wrap for sizes close to 2^32 (reachable with the security limits disabled).
+  auto block_end = [](uint32_t start, uint32_t size) {
+    return size - start > block ? start + block : size;
+  };
+
   // 8-bit samples move as 8x8 tiles, read as eight 64-bit words and transposed in registers. The
   // columns and rows at the edges that do not fill a tile are left to the loop below.
   uint32_t w8 = 0;
@@ -1782,10 +1787,10 @@ void HeifPixelImage::ComponentStorage::rotate_ccw(int angle_degrees,
   if constexpr (sizeof(T) == 1 && std::endian::native == std::endian::little) {
     w8 = w & ~7U;
     h8 = h & ~7U;
-    for (uint32_t y0 = 0; y0 < w8; y0 += block) {
-      for (uint32_t x0 = 0; x0 < h8; x0 += block) {
-        for (uint32_t y = y0; y < std::min(y0 + block, w8); y += 8) {
-          for (uint32_t x = x0; x < std::min(x0 + block, h8); x += 8) {
+    for (uint32_t y0 = 0; y0 < w8; y0 = block_end(y0, w8)) {
+      for (uint32_t x0 = 0; x0 < h8; x0 = block_end(x0, h8)) {
+        for (uint32_t y = y0; y < block_end(y0, w8); y += 8) {
+          for (uint32_t x = x0; x < block_end(x0, h8); x += 8) {
             // Output rows y to y+7, columns x to x+7.
             uint64_t r[8];
             if (angle_degrees == 270) {
@@ -1811,10 +1816,10 @@ void HeifPixelImage::ComponentStorage::rotate_ccw(int angle_degrees,
     }
   }

-  for (uint32_t y0 = 0; y0 < w; y0 += block) {
-    uint32_t y1 = std::min(y0 + block, w);
-    for (uint32_t x0 = 0; x0 < h; x0 += block) {
-      uint32_t x1 = std::min(x0 + block, h);
+  for (uint32_t y0 = 0; y0 < w; y0 = block_end(y0, w)) {
+    uint32_t y1 = block_end(y0, w);
+    for (uint32_t x0 = 0; x0 < h; x0 = block_end(x0, h)) {
+      uint32_t x1 = block_end(x0, h);
       for (uint32_t y = y0; y < y1; y++) {
         T* out_row = out_data + y * out_stride;
         uint32_t x_start = std::max(x0, y < w8 ? h8 : 0);
diff --git a/tests/rotate_planes.cc b/tests/rotate_planes.cc
index 56a1047c..a7a60549 100644
--- a/tests/rotate_planes.cc
+++ b/tests/rotate_planes.cc
@@ -47,13 +47,15 @@ static void check_rotation(uint32_t w, uint32_t h, int angle)
   CAPTURE(sizeof(T) * 8, w, h, angle);
   auto* limits = heif_get_global_security_limits();

-  HeifPixelImage image;
-  image.create(w, h, heif_colorspace_monochrome, heif_chroma_monochrome);
-  REQUIRE(image.add_channel(heif_channel_Y, w, h, sizeof(T) * 8, limits,
-                            heif_component_datatype_unsigned_integer).error_code == heif_error_Ok);
+  // rotate_ccw() calls shared_from_this() for some inputs, so the image must be owned by a
+  // shared_ptr.
+  auto image = std::make_shared<HeifPixelImage>();
+  image->create(w, h, heif_colorspace_monochrome, heif_chroma_monochrome);
+  REQUIRE(image->add_channel(heif_channel_Y, w, h, sizeof(T) * 8, limits,
+                             heif_component_datatype_unsigned_integer).error_code == heif_error_Ok);

   size_t in_stride;
-  T* in = image.get_channel_memory<T>(heif_channel_Y, &in_stride);
+  T* in = image->get_channel_memory<T>(heif_channel_Y, &in_stride);
   in_stride /= sizeof(T);
   for (uint32_t y = 0; y < h; y++) {
     for (uint32_t x = 0; x < w; x++) {
@@ -61,7 +63,7 @@ static void check_rotation(uint32_t w, uint32_t h, int angle)
     }
   }

-  auto rotated = image.rotate_ccw(angle, limits);
+  auto rotated = image->rotate_ccw(angle, limits);
   REQUIRE(rotated.error().error_code == heif_error_Ok);
   std::shared_ptr<HeifPixelImage> out_image = *rotated;