imgcodecs(tiff): support reading images with more than 4 channels

This commit is contained in:
Ijtihed Kilani
2026-07-14 07:22:23 +03:00
parent 7f9f6acb83
commit 119dd753e9
2 changed files with 62 additions and 3 deletions

View File

@@ -236,8 +236,10 @@ bool TiffDecoder::checkSignature( const String& signature ) const
int TiffDecoder::normalizeChannelsNumber(int channels) const
{
CV_Check(channels, channels >= 1 && channels <= 4, "Unsupported number of channels");
return channels;
CV_Check(channels, channels >= 1, "Unsupported number of channels");
// TIFFs may have >4 samples per pixel (CMYK, CMYK + alpha, ...); libtiff's RGBA
// reader collapses them to 4 channels, so clamp instead of rejecting.
return std::min(channels, 4);
}
ImageDecoder TiffDecoder::newDecoder() const
@@ -744,7 +746,10 @@ bool TiffDecoder::readData( Mat& img )
CV_Assert((int)tile_width0 > 0 && (int)tile_width0 <= TILE_MAX_WIDTH);
CV_Assert((int)tile_height0 > 0 && (int)tile_height0 <= TILE_MAX_HEIGHT);
const uint64_t MAX_TILE_SIZE = (CV_BIG_UINT(1) << 30);
CV_CheckLE((int)ncn, 4, "");
// 8-bit images are read via libtiff's RGBA reader, which handles >4 samples
// per pixel; deeper depths read raw samples and support at most 4 channels.
if (dst_bpp != 8)
CV_CheckLE((int)ncn, 4, "");
CV_CheckLE((int)bpp, 64, "");
if (dst_bpp == 8)

View File

@@ -1293,6 +1293,60 @@ TEST(Imgcodecs_Tiff, read_junk) {
ASSERT_TRUE(img.empty());
}
TEST(Imgcodecs_Tiff, read_multi_channel_tiff_26771)
{
// Minimal 4x4, 8-bit, uncompressed, contiguous TIFF with 5 samples per pixel
// (PHOTOMETRIC_SEPARATED / CMYK + one extra sample), reproducing issue #26771.
// libtiff collapses the samples to RGBA, so the image must be readable via the
// usual color/grayscale/unchanged paths instead of throwing on the channel count.
const unsigned char tiff[] = {
0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x01,
0x03, 0x00, 0x05, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x03, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x11, 0x01,
0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x15, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x16, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x01,
0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x1c, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x52, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00,
0x00, 0x00, 0x00, 0x0a, 0xff, 0x28, 0x00, 0x14, 0x0a, 0xff, 0x50, 0x00,
0x28, 0x0a, 0xff, 0x78, 0x00, 0x3c, 0x0a, 0xff, 0x00, 0x28, 0x14, 0x0a,
0xff, 0x28, 0x28, 0x28, 0x0a, 0xff, 0x50, 0x28, 0x3c, 0x0a, 0xff, 0x78,
0x28, 0x50, 0x0a, 0xff, 0x00, 0x50, 0x28, 0x0a, 0xff, 0x28, 0x50, 0x3c,
0x0a, 0xff, 0x50, 0x50, 0x50, 0x0a, 0xff, 0x78, 0x50, 0x64, 0x0a, 0xff,
0x00, 0x78, 0x3c, 0x0a, 0xff, 0x28, 0x78, 0x50, 0x0a, 0xff, 0x50, 0x78,
0x64, 0x0a, 0xff, 0x78, 0x78, 0x78, 0x0a, 0xff
};
const Mat buf(1, (int)sizeof(tiff), CV_8UC1, (void*)tiff);
Mat color;
ASSERT_NO_THROW(color = imdecode(buf, IMREAD_COLOR));
ASSERT_FALSE(color.empty());
EXPECT_EQ(4, color.cols);
EXPECT_EQ(4, color.rows);
EXPECT_EQ(3, color.channels());
Mat gray;
ASSERT_NO_THROW(gray = imdecode(buf, IMREAD_GRAYSCALE));
ASSERT_FALSE(gray.empty());
EXPECT_EQ(Size(4, 4), gray.size());
EXPECT_EQ(1, gray.channels());
Mat unchanged;
ASSERT_NO_THROW(unchanged = imdecode(buf, IMREAD_UNCHANGED));
ASSERT_FALSE(unchanged.empty());
EXPECT_EQ(Size(4, 4), unchanged.size());
// A >4-channel TIFF is decoded via libtiff's RGBA reader, so the result has 4 channels.
EXPECT_EQ(4, unchanged.channels());
double mn = 0.0, mx = 0.0;
minMaxLoc(unchanged, &mn, &mx);
EXPECT_LT(mn, mx);
}
typedef int Imgcodecs_Tiff_32F_Compressions_32F_Values;
typedef testing::TestWithParam<Imgcodecs_Tiff_32F_Compressions_32F_Values> Imgcodecs_Tiff_32F_Compressions_32F;