Commit 993b63df for libheif
commit 993b63dfb4f4514f928cedd476e4dc390f30562d
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Mon Mar 2 12:44:20 2026 +0100
tiff reader: add support for signed integers
diff --git a/heifio/decoder_tiff.cc b/heifio/decoder_tiff.cc
index aaf24e9f..1aa23e57 100644
--- a/heifio/decoder_tiff.cc
+++ b/heifio/decoder_tiff.cc
@@ -587,6 +587,55 @@ static heif_error readMonoFloat(TIFF* tif, heif_image** image)
return heif_error_ok;
}
+
+
+static heif_error readMonoSignedInt(TIFF* tif, uint16_t bps, heif_image** image)
+{
+ uint32_t width, height;
+ heif_error err = getImageWidthAndHeight(tif, width, height);
+ if (err.code != heif_error_Ok) {
+ return err;
+ }
+
+ err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, image);
+ if (err.code != heif_error_Ok) {
+ return err;
+ }
+
+ uint32_t component_idx;
+ err = heif_image_add_component(*image, (int)width, (int)height,
+ heif_uncompressed_component_type_monochrome,
+ heif_channel_datatype_signed_integer, bps, &component_idx);
+ if (err.code != heif_error_Ok) {
+ heif_image_release(*image);
+ *image = nullptr;
+ return err;
+ }
+
+ size_t stride;
+ uint8_t* plane;
+ if (bps == 8) {
+ plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int8(*image, component_idx, &stride));
+ }
+ else {
+ plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int16(*image, component_idx, &stride));
+ }
+ if (!plane) {
+ heif_image_release(*image);
+ *image = nullptr;
+ return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
+ }
+
+ int bytesPerSample = bps / 8;
+ tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif));
+ for (uint32_t row = 0; row < height; row++) {
+ TIFFReadScanline(tif, buf, row, 0);
+ memcpy(plane + row * stride, buf, width * bytesPerSample);
+ }
+ _TIFFfree(buf);
+
+ return heif_error_ok;
+}
#endif
@@ -641,6 +690,16 @@ static heif_error validateTiffFormat(TIFF* tif, uint16_t& samplesPerPixel, uint1
"Only monochrome floating point TIFF is supported."};
}
}
+ else if (sampleFormat == SAMPLEFORMAT_INT) {
+ 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."};
+ }
+ if (samplesPerPixel != 1) {
+ return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
+ "Only monochrome signed integer TIFF is supported."};
+ }
+ }
else if (sampleFormat == SAMPLEFORMAT_UINT) {
if (bps != 8 && bps != 16) {
return {heif_error_Invalid_input, heif_suberror_Unspecified,
@@ -722,6 +781,73 @@ static heif_error readTiledContiguous(TIFF* tif, uint32_t width, uint32_t height
#endif
}
+ bool isSignedInt = (sampleFormat == SAMPLEFORMAT_INT);
+
+ if (isSignedInt) {
+#if WITH_UNCOMPRESSED_CODEC
+ heif_error err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, out_image);
+ if (err.code != heif_error_Ok) return err;
+
+ uint32_t component_idx;
+ err = heif_image_add_component(*out_image, (int)width, (int)height,
+ heif_uncompressed_component_type_monochrome,
+ heif_channel_datatype_signed_integer, bps, &component_idx);
+ if (err.code != heif_error_Ok) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return err;
+ }
+
+ size_t out_stride;
+ uint8_t* out_plane;
+ if (bps == 8) {
+ out_plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int8(*out_image, component_idx, &out_stride));
+ }
+ else {
+ out_plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int16(*out_image, component_idx, &out_stride));
+ }
+ if (!out_plane) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
+ }
+
+ int bytesPerSample = bps / 8;
+ tmsize_t tile_buf_size = TIFFTileSize(tif);
+ std::vector<uint8_t> tile_buf(tile_buf_size);
+
+ uint32_t n_cols = (width + tile_width - 1) / tile_width;
+ uint32_t n_rows = (height + tile_height - 1) / tile_height;
+
+ for (uint32_t ty = 0; ty < n_rows; ty++) {
+ for (uint32_t tx = 0; tx < n_cols; tx++) {
+ tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, 0),
+ tile_buf.data(), tile_buf_size);
+ if (read < 0) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"};
+ }
+
+ uint32_t actual_w = std::min(tile_width, width - tx * tile_width);
+ uint32_t actual_h = std::min(tile_height, height - ty * tile_height);
+
+ for (uint32_t row = 0; row < actual_h; row++) {
+ uint8_t* dst = out_plane + (ty * tile_height + row) * out_stride
+ + tx * tile_width * bytesPerSample;
+ uint8_t* src = tile_buf.data() + row * tile_width * bytesPerSample;
+ memcpy(dst, src, actual_w * bytesPerSample);
+ }
+ }
+ }
+
+ return heif_error_ok;
+#else
+ return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
+ "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
+#endif
+ }
+
uint16_t outSpp = (samplesPerPixel == 4 && !hasAlpha) ? 3 : samplesPerPixel;
int effectiveBitDepth = (bps <= 8) ? 8 : output_bit_depth;
heif_chroma chroma = get_heif_chroma(outSpp, effectiveBitDepth);
@@ -826,15 +952,15 @@ static heif_error readTiledSeparate(TIFF* tif, uint32_t width, uint32_t height,
uint16_t bps, int output_bit_depth,
uint16_t sampleFormat, heif_image** out_image)
{
- // For mono float, separate layout is the same as contiguous (1 sample)
- if (sampleFormat == SAMPLEFORMAT_IEEEFP) {
+ // For mono float/signed int, separate layout is the same as contiguous (1 sample)
+ if (sampleFormat == SAMPLEFORMAT_IEEEFP || sampleFormat == SAMPLEFORMAT_INT) {
#if WITH_UNCOMPRESSED_CODEC
return readTiledContiguous(tif, width, height, tile_width, tile_height,
samplesPerPixel, hasAlpha, bps, output_bit_depth,
sampleFormat, out_image);
#else
return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
- "Floating point TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
+ "Float/signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
#endif
}
@@ -925,9 +1051,11 @@ heif_error loadTIFF(const char* filename, int output_bit_depth, InputImage *inpu
if (err.code != heif_error_Ok) return err;
bool isFloat = (sampleFormat == SAMPLEFORMAT_IEEEFP);
+ bool isSignedInt = (sampleFormat == SAMPLEFORMAT_INT);
// For 8-bit source, always produce 8-bit output (ignore output_bit_depth).
- int effectiveOutputBitDepth = isFloat ? 32 : ((bps <= 8) ? 8 : 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));
struct heif_image* image = nullptr;
@@ -959,6 +1087,14 @@ heif_error loadTIFF(const char* filename, int output_bit_depth, InputImage *inpu
#else
return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
"Floating point TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
+#endif
+ }
+ else if (isSignedInt) {
+#if WITH_UNCOMPRESSED_CODEC
+ err = readMonoSignedInt(tif, bps, &image);
+#else
+ return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
+ "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
#endif
}
else {
@@ -1178,6 +1314,60 @@ heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_de
#endif
}
+ if (m_sample_format == SAMPLEFORMAT_INT) {
+#if WITH_UNCOMPRESSED_CODEC
+ heif_error err = heif_image_create((int)actual_w, (int)actual_h, heif_colorspace_nonvisual, heif_chroma_undefined, out_image);
+ if (err.code != heif_error_Ok) return err;
+
+ uint32_t component_idx;
+ err = heif_image_add_component(*out_image, (int)actual_w, (int)actual_h,
+ heif_uncompressed_component_type_monochrome,
+ heif_channel_datatype_signed_integer, m_bits_per_sample, &component_idx);
+ if (err.code != heif_error_Ok) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return err;
+ }
+
+ size_t out_stride;
+ uint8_t* out_plane;
+ if (m_bits_per_sample == 8) {
+ out_plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int8(*out_image, component_idx, &out_stride));
+ }
+ else {
+ out_plane = reinterpret_cast<uint8_t*>(heif_image_get_component_int16(*out_image, component_idx, &out_stride));
+ }
+ if (!out_plane) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"};
+ }
+
+ int bytesPerSample = m_bits_per_sample / 8;
+ tmsize_t tile_buf_size = TIFFTileSize(tif);
+ std::vector<uint8_t> tile_buf(tile_buf_size);
+
+ tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, 0),
+ tile_buf.data(), tile_buf_size);
+ if (read < 0) {
+ heif_image_release(*out_image);
+ *out_image = nullptr;
+ return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"};
+ }
+
+ for (uint32_t row = 0; row < actual_h; row++) {
+ uint8_t* dst = out_plane + row * out_stride;
+ uint8_t* src = tile_buf.data() + row * m_tile_width * bytesPerSample;
+ memcpy(dst, src, actual_w * bytesPerSample);
+ }
+
+ return heif_error_ok;
+#else
+ return {heif_error_Unsupported_feature, heif_suberror_Unspecified,
+ "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."};
+#endif
+ }
+
int effectiveBitDepth = (m_bits_per_sample <= 8) ? 8 : output_bit_depth;
uint16_t outSpp = (m_samples_per_pixel == 4 && !m_has_alpha) ? 3 : m_samples_per_pixel;
heif_chroma chroma = get_heif_chroma(outSpp, effectiveBitDepth);