diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 36cf17e1e3..2941284e84 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -325,6 +325,9 @@ bool TiffDecoder::readHeader() result = true; break; } + case 10: + case 12: + case 14: case 16: { CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, ""); @@ -347,7 +350,7 @@ bool TiffDecoder::readHeader() result = true; break; default: - CV_Error(cv::Error::StsError, "Invalid bitsperpixel value read from TIFF header! Must be 1, 8, 16, 32 or 64."); + CV_Error(cv::Error::StsError, "Invalid bitsperpixel value read from TIFF header! Must be 1, 8, 10, 12, 14, 16, 32 or 64."); } } } @@ -437,6 +440,147 @@ static void fixOrientation(Mat &img, uint16 orientation, int dst_bpp) } } +static void _unpack10To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements) +{ + //5*8b=4*10b : 5 src for 4 dst + constexpr const size_t packedBitsCount = 10; + constexpr const size_t packedBitsMask = ((1<(srcEnd-src)/srcElementsPerPacket), + (static_cast(dstEnd-dst)/dstElementsPerPacket) + }); + union { + uint64_t u64; + uint8_t u8[8]; + } buf = {0}; + for(size_t i = 0 ; i(buf.u64 & packedBitsMask); + buf.u64 >>= packedBitsCount; + } + dst += dstElementsPerPacket; + } + size_t remainingDstElements = std::min( + expectedDstElements-fullPacketsCount*dstElementsPerPacket, + static_cast(dstEnd-dst) + ); + bool stop = !remainingDstElements; + while(!stop) + { + for(size_t j = 0 ; j((buf.u64 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask); + } + }//end while(!stop) +} +//end _unpack10To16() + +static void _unpack12To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements) +{ + //3*8b=2*12b : 3 src for 2 dst + constexpr const size_t packedBitsCount = 12; + constexpr const size_t packedBitsMask = ((1<(srcEnd-src)/srcElementsPerPacket), + (static_cast(dstEnd-dst)/dstElementsPerPacket) + }); + union { + uint32_t u32; + uint8_t u8[4]; + } buf = {0}; + for(size_t i = 0 ; i(buf.u32 & packedBitsMask); + buf.u32 >>= packedBitsCount; + } + dst += dstElementsPerPacket; + } + size_t remainingDstElements = std::min( + expectedDstElements-fullPacketsCount*dstElementsPerPacket, + static_cast(dstEnd-dst) + ); + bool stop = !remainingDstElements; + while(!stop) + { + for(size_t j = 0 ; j((buf.u32 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask); + } + }//end while(!stop) +} +//end _unpack12To16() + +static void _unpack14To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements) +{ + //7*8b=4*14b : 7 src for 4 dst + constexpr const size_t packedBitsCount = 14; + constexpr const size_t packedBitsMask = ((1<(srcEnd-src)/srcElementsPerPacket), + (static_cast(dstEnd-dst)/dstElementsPerPacket) + }); + union { + uint64_t u64; + uint8_t u8[8]; + } buf = {0}; + for(size_t i = 0 ; i(buf.u64 & packedBitsMask); + buf.u64 >>= packedBitsCount; + } + dst += dstElementsPerPacket; + } + size_t remainingDstElements = std::min( + expectedDstElements-fullPacketsCount*dstElementsPerPacket, + static_cast(dstEnd-dst) + ); + bool stop = !remainingDstElements; + while(!stop) + { + for(size_t j = 0 ; j((buf.u64 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask); + } + }//end while(!stop) +} +//end _unpack14To16() + bool TiffDecoder::readData( Mat& img ) { int type = img.type(); @@ -470,7 +614,7 @@ bool TiffDecoder::readData( Mat& img ) CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn)); uint16 img_orientation = ORIENTATION_TOPLEFT; CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ORIENTATION, &img_orientation)); - const int bitsPerByte = 8; + constexpr const int bitsPerByte = 8; int dst_bpp = (int)(img.elemSize1() * bitsPerByte); bool vert_flip = dst_bpp == 8 && (img_orientation == ORIENTATION_BOTRIGHT || img_orientation == ORIENTATION_RIGHTBOT || @@ -529,10 +673,15 @@ bool TiffDecoder::readData( Mat& img ) CV_Assert(ncn == img.channels()); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)); } - const size_t buffer_size = (bpp / bitsPerByte) * ncn * tile_height0 * tile_width0; - AutoBuffer _buffer(buffer_size); - uchar* buffer = _buffer.data(); - ushort* buffer16 = (ushort*)buffer; + const size_t src_buffer_bytes_per_row = divUp(static_cast(ncn * tile_width0 * bpp), static_cast(bitsPerByte)); + const size_t src_buffer_size = tile_height0 * src_buffer_bytes_per_row; + const size_t src_buffer_unpacked_bytes_per_row = divUp(static_cast(ncn * tile_width0 * dst_bpp), static_cast(bitsPerByte)); + const size_t src_buffer_unpacked_size = tile_height0 * src_buffer_unpacked_bytes_per_row; + const bool needsUnpacking = (bpp < dst_bpp); + AutoBuffer _src_buffer(src_buffer_size); + uchar* src_buffer = _src_buffer.data(); + AutoBuffer _src_buffer_unpacked(needsUnpacking ? src_buffer_unpacked_size : 0); + uchar* src_buffer_unpacked = needsUnpacking ? _src_buffer_unpacked.data() : nullptr; int tileidx = 0; for (int y = 0; y < m_height; y += (int)tile_height0) @@ -549,14 +698,14 @@ bool TiffDecoder::readData( Mat& img ) { case 8: { - uchar* bstart = buffer; + uchar* bstart = src_buffer; if (!is_tiled) { - CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)buffer)); + CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)src_buffer)); } else { - CV_TIFF_CHECK_CALL(TIFFReadRGBATile(tif, x, y, (uint32*)buffer)); + CV_TIFF_CHECK_CALL(TIFFReadRGBATile(tif, x, y, (uint32*)src_buffer)); // Tiles fill the buffer from the bottom up bstart += (tile_height0 - tile_height) * tile_width0 * 4; } @@ -594,28 +743,48 @@ bool TiffDecoder::readData( Mat& img ) { if (!is_tiled) { - CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)src_buffer, src_buffer_size) >= 0); } else { - CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, (uint32*)src_buffer, src_buffer_size) >= 0); } for (int i = 0; i < tile_height; i++) { + ushort* buffer16 = (ushort*)(src_buffer+i*src_buffer_bytes_per_row); + if (needsUnpacking) + { + const uchar* src_packed = src_buffer+i*src_buffer_bytes_per_row; + uchar* dst_unpacked = src_buffer_unpacked+i*src_buffer_unpacked_bytes_per_row; + if (bpp == 10) + _unpack10To16(src_packed, src_packed+src_buffer_bytes_per_row, + (ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row), + ncn * tile_width0); + else if (bpp == 12) + _unpack12To16(src_packed, src_packed+src_buffer_bytes_per_row, + (ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row), + ncn * tile_width0); + else if (bpp == 14) + _unpack14To16(src_packed, src_packed+src_buffer_bytes_per_row, + (ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row), + ncn * tile_width0); + buffer16 = (ushort*)dst_unpacked; + } + if (color) { if (ncn == 1) { CV_CheckEQ(wanted_channels, 3, ""); - icvCvt_Gray2BGR_16u_C1C3R(buffer16 + i*tile_width0*ncn, 0, + icvCvt_Gray2BGR_16u_C1C3R(buffer16, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } else if (ncn == 3) { CV_CheckEQ(wanted_channels, 3, ""); - icvCvt_RGB2BGR_16u_C3R(buffer16 + i*tile_width0*ncn, 0, + icvCvt_RGB2BGR_16u_C3R(buffer16, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } @@ -623,14 +792,14 @@ bool TiffDecoder::readData( Mat& img ) { if (wanted_channels == 4) { - icvCvt_BGRA2RGBA_16u_C4R(buffer16 + i*tile_width0*ncn, 0, + icvCvt_BGRA2RGBA_16u_C4R(buffer16, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } else { CV_CheckEQ(wanted_channels, 3, "TIFF-16bpp: BGR/BGRA images are supported only"); - icvCvt_BGRA2BGR_16u_C4C3R(buffer16 + i*tile_width0*ncn, 0, + icvCvt_BGRA2BGR_16u_C4C3R(buffer16, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1), 2); } @@ -646,12 +815,12 @@ bool TiffDecoder::readData( Mat& img ) if( ncn == 1 ) { memcpy(img.ptr(img_y + i, x), - buffer16 + i*tile_width0*ncn, + buffer16, tile_width*sizeof(ushort)); } else { - icvCvt_BGRA2Gray_16u_CnC1R(buffer16 + i*tile_width0*ncn, 0, + icvCvt_BGRA2Gray_16u_CnC1R(buffer16, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1), ncn, 2); } @@ -665,14 +834,14 @@ bool TiffDecoder::readData( Mat& img ) { if( !is_tiled ) { - CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, buffer, buffer_size) >= 0); + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, src_buffer, src_buffer_size) >= 0); } else { - CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, buffer, buffer_size) >= 0); + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, src_buffer, src_buffer_size) >= 0); } - Mat m_tile(Size(tile_width0, tile_height0), CV_MAKETYPE((dst_bpp == 32) ? (depth == CV_32S ? CV_32S : CV_32F) : CV_64F, ncn), buffer); + Mat m_tile(Size(tile_width0, tile_height0), CV_MAKETYPE((dst_bpp == 32) ? (depth == CV_32S ? CV_32S : CV_32F) : CV_64F, ncn), src_buffer); Rect roi_tile(0, 0, tile_width, tile_height); Rect roi_img(x, img_y, tile_width, tile_height); if (!m_hdr && ncn == 3) @@ -691,6 +860,8 @@ bool TiffDecoder::readData( Mat& img ) } // for x } // for y } + if (bpp < dst_bpp) + img *= (1<<(dst_bpp-bpp)); fixOrientation(img, img_orientation, dst_bpp); } diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 1c6e4a6b29..28e084d5b0 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -117,6 +117,116 @@ TEST(Imgcodecs_Tiff, decode_tile_remainder) // What about 32, 64 bit? } +TEST(Imgcodecs_Tiff, decode_10_12_14) +{ + /* see issue #21700 + */ + const string root = cvtest::TS::ptr()->get_data_path(); + + const double maxDiff = 256;//samples do not have the exact same values because of the tool that created them + cv::Mat tmp; + double diff = 0; + + cv::Mat img8UC1 = imread(root + "readwrite/pattern_8uc1.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img8UC1.empty()); + ASSERT_EQ(img8UC1.type(), CV_8UC1); + + cv::Mat img8UC3 = imread(root + "readwrite/pattern_8uc3.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img8UC3.empty()); + ASSERT_EQ(img8UC3.type(), CV_8UC3); + + cv::Mat img8UC4 = imread(root + "readwrite/pattern_8uc4.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img8UC4.empty()); + ASSERT_EQ(img8UC4.type(), CV_8UC4); + + cv::Mat img16UC1 = imread(root + "readwrite/pattern_16uc1.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img16UC1.empty()); + ASSERT_EQ(img16UC1.type(), CV_16UC1); + ASSERT_EQ(img8UC1.size(), img16UC1.size()); + img8UC1.convertTo(tmp, img16UC1.type(), (1U<<(16-8))); + diff = cv::norm(tmp.reshape(1), img16UC1.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img16UC3 = imread(root + "readwrite/pattern_16uc3.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img16UC3.empty()); + ASSERT_EQ(img16UC3.type(), CV_16UC3); + ASSERT_EQ(img8UC3.size(), img16UC3.size()); + img8UC3.convertTo(tmp, img16UC3.type(), (1U<<(16-8))); + diff = cv::norm(tmp.reshape(1), img16UC3.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img16UC4 = imread(root + "readwrite/pattern_16uc4.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img16UC4.empty()); + ASSERT_EQ(img16UC4.type(), CV_16UC4); + ASSERT_EQ(img8UC4.size(), img16UC4.size()); + img8UC4.convertTo(tmp, img16UC4.type(), (1U<<(16-8))); + diff = cv::norm(tmp.reshape(1), img16UC4.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img10UC1 = imread(root + "readwrite/pattern_10uc1.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img10UC1.empty()); + ASSERT_EQ(img10UC1.type(), CV_16UC1); + ASSERT_EQ(img10UC1.size(), img16UC1.size()); + diff = cv::norm(img10UC1.reshape(1), img16UC1.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img10UC3 = imread(root + "readwrite/pattern_10uc3.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img10UC3.empty()); + ASSERT_EQ(img10UC3.type(), CV_16UC3); + ASSERT_EQ(img10UC3.size(), img16UC3.size()); + diff = cv::norm(img10UC3.reshape(1), img16UC3.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img10UC4 = imread(root + "readwrite/pattern_10uc4.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img10UC4.empty()); + ASSERT_EQ(img10UC4.type(), CV_16UC4); + ASSERT_EQ(img10UC4.size(), img16UC4.size()); + diff = cv::norm(img10UC4.reshape(1), img16UC4.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img12UC1 = imread(root + "readwrite/pattern_12uc1.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img12UC1.empty()); + ASSERT_EQ(img12UC1.type(), CV_16UC1); + ASSERT_EQ(img12UC1.size(), img16UC1.size()); + diff = cv::norm(img12UC1.reshape(1), img16UC1.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img12UC3 = imread(root + "readwrite/pattern_12uc3.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img12UC3.empty()); + ASSERT_EQ(img12UC3.type(), CV_16UC3); + ASSERT_EQ(img12UC3.size(), img16UC3.size()); + diff = cv::norm(img12UC3.reshape(1), img16UC3.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img12UC4 = imread(root + "readwrite/pattern_12uc4.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img12UC4.empty()); + ASSERT_EQ(img12UC4.type(), CV_16UC4); + ASSERT_EQ(img12UC4.size(), img16UC4.size()); + diff = cv::norm(img12UC4.reshape(1), img16UC4.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img14UC1 = imread(root + "readwrite/pattern_14uc1.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img14UC1.empty()); + ASSERT_EQ(img14UC1.type(), CV_16UC1); + ASSERT_EQ(img14UC1.size(), img16UC1.size()); + diff = cv::norm(img14UC1.reshape(1), img16UC1.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img14UC3 = imread(root + "readwrite/pattern_14uc3.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img14UC3.empty()); + ASSERT_EQ(img14UC3.type(), CV_16UC3); + ASSERT_EQ(img14UC3.size(), img16UC3.size()); + diff = cv::norm(img14UC3.reshape(1), img16UC3.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); + + cv::Mat img14UC4 = imread(root + "readwrite/pattern_14uc4.tif", cv::IMREAD_UNCHANGED); + ASSERT_FALSE(img14UC4.empty()); + ASSERT_EQ(img14UC4.type(), CV_16UC4); + ASSERT_EQ(img14UC4.size(), img16UC4.size()); + diff = cv::norm(img14UC4.reshape(1), img16UC4.reshape(1), cv::NORM_INF); + ASSERT_LE(diff, maxDiff); +} + TEST(Imgcodecs_Tiff, decode_infinite_rowsperstrip) { const uchar sample_data[142] = {