Commit 504cc8ee for libheif

commit 504cc8ee1cb5ad9fba76a328259a4e3f2e375f4a
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Thu Apr 2 19:44:11 2026 +0200

    tiff reader: support 9-15 bpp in input

diff --git a/heifio/decoder_tiff.cc b/heifio/decoder_tiff.cc
index f37464b1..baea6103 100644
--- a/heifio/decoder_tiff.cc
+++ b/heifio/decoder_tiff.cc
@@ -352,7 +352,7 @@ heif_error readMono(TIFF *tif, uint16_t bps, int output_bit_depth, heif_image **

     size_t y_stride;
     uint8_t *py = heif_image_get_plane2(*image, heif_channel_Y, &y_stride);
-    int bdShift = 16 - output_bit_depth;
+    int bdShift = bps - output_bit_depth;
     tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));

     if (output_bit_depth <= 8) {
@@ -366,7 +366,7 @@ heif_error readMono(TIFF *tif, uint16_t bps, int output_bit_depth, heif_image **
         uint16_t* src = static_cast<uint16_t*>(buf);
         uint8_t* dst = py + row * y_stride;
         for (uint32_t x = 0; x < width; x++) {
-          dst[x] = static_cast<uint8_t>(src[x] >> 8);
+          dst[x] = static_cast<uint8_t>(src[x] >> (bps - 8));
         }
       }
     }
@@ -499,7 +499,7 @@ heif_error readPixelInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasA

     size_t y_stride;
     uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride);
-    int bdShift = 16 - output_bit_depth;
+    int bdShift = bps - output_bit_depth;
     tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));

     if (output_bit_depth <= 8) {
@@ -514,13 +514,13 @@ heif_error readPixelInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasA
         uint8_t* dst = py + row * y_stride;
         if (outSpp == samplesPerPixel) {
           for (uint32_t x = 0; x < width * outSpp; x++) {
-            dst[x] = static_cast<uint8_t>(src[x] >> 8);
+            dst[x] = static_cast<uint8_t>(src[x] >> (bps - 8));
           }
         }
         else {
           for (uint32_t x = 0; x < width; x++) {
             for (uint16_t c = 0; c < outSpp; c++) {
-              dst[x * outSpp + c] = static_cast<uint8_t>(src[x * samplesPerPixel + c] >> 8);
+              dst[x * outSpp + c] = static_cast<uint8_t>(src[x * samplesPerPixel + c] >> (bps - 8));
             }
           }
         }
@@ -616,7 +616,7 @@ heif_error readBandInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasAl

     size_t y_stride;
     uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride);
-    int bdShift = 16 - output_bit_depth;
+    int bdShift = bps - output_bit_depth;
     uint8_t *buf = static_cast<uint8_t *>(_TIFFmalloc(TIFFScanlineSize(tif)));

     if (output_bit_depth <= 8) {
@@ -631,7 +631,7 @@ heif_error readBandInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasAl
           uint16_t* src = reinterpret_cast<uint16_t*>(buf);
           uint8_t* dst = py + row * y_stride + i;
           for (uint32_t x = 0; x < width; x++) {
-            dst[x * outSpp] = static_cast<uint8_t>(src[x] >> 8);
+            dst[x * outSpp] = static_cast<uint8_t>(src[x] >> (bps - 8));
           }
         }
       }
@@ -762,7 +762,7 @@ static heif_error readMonoSignedInt(TIFF* tif, uint16_t bps, heif_image** image)
     return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
   }

-  int bytesPerSample = bps / 8;
+  int bytesPerSample = bps > 8 ? 2 : 1;
   tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));
   for (uint32_t row = 0; row < height; row++) {
     if (TIFFReadScanline(tif, buf, row, 0) < 0) {
@@ -832,9 +832,9 @@ static heif_error validateTiffFormat(TIFF* tif, uint16_t& samplesPerPixel, uint1
     }
   }
   else if (sampleFormat == SAMPLEFORMAT_INT) {
-    if (bps != 8 && bps != 16) {
+    if (bps < 8 || bps > 16) {
       return {heif_error_Invalid_input, heif_suberror_Unspecified,
-              "Only 8 and 16 bits per sample are supported for signed integer TIFF."};
+              "Only 8 to 16 bits per sample are supported for signed integer TIFF."};
     }
     if (samplesPerPixel != 1) {
       return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
@@ -842,9 +842,9 @@ static heif_error validateTiffFormat(TIFF* tif, uint16_t& samplesPerPixel, uint1
     }
   }
   else if (sampleFormat == SAMPLEFORMAT_UINT) {
-    if (bps != 8 && bps != 16) {
+    if (bps < 8 || bps > 16) {
       return {heif_error_Invalid_input, heif_suberror_Unspecified,
-              "Only 8 and 16 bits per sample are supported."};
+              "Only 8 to 16 bits per sample are supported."};
     }
   }
   else {
@@ -1113,7 +1113,7 @@ static heif_error readTiledContiguous(TIFF* tif, uint32_t width, uint32_t height
       return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
     }

-    int bytesPerSample = bps / 8;
+    int bytesPerSample = bps > 8 ? 2 : 1;
     tmsize_t tile_buf_size = TIFFTileSize(tif);
     std::vector<uint8_t> tile_buf(tile_buf_size);

@@ -1203,20 +1203,20 @@ static heif_error readTiledContiguous(TIFF* tif, uint32_t width, uint32_t height
           uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + (size_t)row * tile_width * samplesPerPixel * 2);
           if (outSpp == samplesPerPixel) {
             for (uint32_t x = 0; x < actual_w * outSpp; x++) {
-              dst[x] = static_cast<uint8_t>(src[x] >> 8);
+              dst[x] = static_cast<uint8_t>(src[x] >> (bps - 8));
             }
           }
           else {
             for (uint32_t x = 0; x < actual_w; x++) {
               for (uint16_t c = 0; c < outSpp; c++) {
-                dst[x * outSpp + c] = static_cast<uint8_t>(src[x * samplesPerPixel + c] >> 8);
+                dst[x * outSpp + c] = static_cast<uint8_t>(src[x * samplesPerPixel + c] >> (bps - 8));
               }
             }
           }
         }
       }
       else {
-        int bdShift = 16 - output_bit_depth;
+        int bdShift = bps - output_bit_depth;
         for (uint32_t row = 0; row < actual_h; row++) {
           uint16_t* dst = reinterpret_cast<uint16_t*>(out_plane + ((size_t)ty * tile_height + row) * out_stride
                                                       + (size_t)tx * tile_width * outSpp * 2);
@@ -1314,12 +1314,12 @@ static heif_error readTiledSeparate(TIFF* tif, uint32_t width, uint32_t height,
             uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + (size_t)tx * tile_width * outSpp + s;
             uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + (size_t)row * tile_width * 2);
             for (uint32_t x = 0; x < actual_w; x++) {
-              dst[x * outSpp] = static_cast<uint8_t>(src[x] >> 8);
+              dst[x * outSpp] = static_cast<uint8_t>(src[x] >> (bps - 8));
             }
           }
         }
         else {
-          int bdShift = 16 - output_bit_depth;
+          int bdShift = bps - output_bit_depth;
           for (uint32_t row = 0; row < actual_h; row++) {
             uint16_t* dst = reinterpret_cast<uint16_t*>(out_plane + ((size_t)ty * tile_height + row) * out_stride) + (size_t)tx * tile_width * outSpp + s;
             uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + (size_t)row * tile_width * 2);
@@ -1362,7 +1362,7 @@ heif_error loadTIFF(const char* filename, int output_bit_depth, InputImage *inpu

   // For 8-bit source, always produce 8-bit output (ignore output_bit_depth).
   // For float, use 32-bit. For signed int, preserve original bit depth.
-  int effectiveOutputBitDepth = isFloat ? 32 : (isSignedInt ? bps : ((bps <= 8) ? 8 : output_bit_depth));
+  int effectiveOutputBitDepth = isFloat ? 32 : (isSignedInt ? bps : ((bps <= 8) ? 8 : ((bps < 16) ? bps : output_bit_depth)));

   struct heif_image* image = nullptr;

@@ -1706,7 +1706,7 @@ heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_de
       return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
     }

-    int bytesPerSample = m_bits_per_sample / 8;
+    int bytesPerSample = m_bits_per_sample > 8 ? 2 : 1;
     tmsize_t tile_buf_size = TIFFTileSize(tif);
     std::vector<uint8_t> tile_buf(tile_buf_size);

@@ -1778,20 +1778,20 @@ heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_de
         uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + row * m_tile_width * m_samples_per_pixel * 2);
         if (outSpp == m_samples_per_pixel) {
           for (uint32_t x = 0; x < actual_w * outSpp; x++) {
-            dst[x] = static_cast<uint8_t>(src[x] >> 8);
+            dst[x] = static_cast<uint8_t>(src[x] >> (m_bits_per_sample - 8));
           }
         }
         else {
           for (uint32_t x = 0; x < actual_w; x++) {
             for (uint16_t c = 0; c < outSpp; c++) {
-              dst[x * outSpp + c] = static_cast<uint8_t>(src[x * m_samples_per_pixel + c] >> 8);
+              dst[x * outSpp + c] = static_cast<uint8_t>(src[x * m_samples_per_pixel + c] >> (m_bits_per_sample - 8));
             }
           }
         }
       }
     }
     else {
-      int bdShift = 16 - output_bit_depth;
+      int bdShift = m_bits_per_sample - output_bit_depth;
       for (uint32_t row = 0; row < actual_h; row++) {
         uint16_t* dst = reinterpret_cast<uint16_t*>(out_plane + row * out_stride);
         uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + row * m_tile_width * m_samples_per_pixel * 2);
@@ -1840,12 +1840,12 @@ heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_de
           uint8_t* dst = out_plane + row * out_stride + s;
           uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + row * m_tile_width * 2);
           for (uint32_t x = 0; x < actual_w; x++) {
-            dst[x * outSpp] = static_cast<uint8_t>(src[x] >> 8);
+            dst[x * outSpp] = static_cast<uint8_t>(src[x] >> (m_bits_per_sample - 8));
           }
         }
       }
       else {
-        int bdShift = 16 - output_bit_depth;
+        int bdShift = m_bits_per_sample - output_bit_depth;
         for (uint32_t row = 0; row < actual_h; row++) {
           uint16_t* dst = reinterpret_cast<uint16_t*>(out_plane + row * out_stride) + s;
           uint16_t* src = reinterpret_cast<uint16_t*>(tile_buf.data() + row * m_tile_width * 2);