From 2f79b1b0877b194f461de0fb73ee4e7ab01a90fa Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 4 Oct 2022 00:24:15 +0900 Subject: [PATCH] Merge pull request #22404 from Kumataro:3.4-fix22388_2 * imgcodecs: tiff: Reduce memory usage to read 16bit image. * imgcodecs: tiff: Reduce memory usage to read 8bit images * imgcodecs: tiff: split basic test and full test. * imgcodecs: tiff: fix to warning C4244 * imgcodecs: tiff: fix to warning C4244 --- modules/imgcodecs/src/grfmt_tiff.cpp | 249 ++++++++++++++-- modules/imgcodecs/test/test_tiff.cpp | 428 +++++++++++++++++++++++++++ 2 files changed, 646 insertions(+), 31 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 04df6ff8bb..42ddeb2aa1 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -234,7 +234,6 @@ public: bool TiffDecoder::readHeader() { bool result = false; - TIFF* tif = static_cast(m_tif.get()); if (!tif) { @@ -390,18 +389,15 @@ static void fixOrientationFull(Mat &img, int orientation) * For 8 bit some corrections are done by TIFFReadRGBAStrip/Tile already. * Not so for 16/32/64 bit. */ -static void fixOrientation(Mat &img, uint16 orientation, int dst_bpp) +static void fixOrientation(Mat &img, uint16 orientation, bool isOrientationFull) { - switch(dst_bpp) { - case 8: - fixOrientationPartial(img, orientation); - break; - - case 16: - case 32: - case 64: - fixOrientationFull(img, orientation); - break; + if( isOrientationFull ) + { + fixOrientationFull(img, orientation); + } + else + { + fixOrientationPartial(img, orientation); } } @@ -440,17 +436,7 @@ bool TiffDecoder::readData( Mat& img ) (img_orientation == ORIENTATION_BOTRIGHT || img_orientation == ORIENTATION_RIGHTBOT || img_orientation == ORIENTATION_BOTLEFT || img_orientation == ORIENTATION_LEFTBOT); int wanted_channels = normalizeChannelsNumber(img.channels()); - - if (dst_bpp == 8) - { - char errmsg[1024]; - if (!TIFFRGBAImageOK(tif, errmsg)) - { - CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); - close(); - return false; - } - } + bool doReadScanline = false; uint32 tile_width0 = m_width, tile_height0 = 0; @@ -480,25 +466,139 @@ bool TiffDecoder::readData( Mat& img ) const uint64_t MAX_TILE_SIZE = (CV_BIG_UINT(1) << 30); CV_CheckLE((int)ncn, 4, ""); CV_CheckLE((int)bpp, 64, ""); - CV_Assert(((uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) < MAX_TILE_SIZE) && "TIFF tile size is too large: >= 1Gb"); if (dst_bpp == 8) { - // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA - bpp = 8; - ncn = 4; + const int _ncn = 4; // Read RGBA + const int _bpp = 8; // Read 8bit + + // if buffer_size(as 32bit RGBA) >= MAX_TILE_SIZE*95%, + // we will use TIFFReadScanline function. + + if ( + (uint64_t)tile_width0 * tile_height0 * _ncn * std::max(1, (int)(_bpp / bitsPerByte)) + >= + ( (uint64_t) MAX_TILE_SIZE * 95 / 100) + ) + { + uint16_t planerConfig = (uint16)-1; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig)); + + doReadScanline = (!is_tiled) // no tile + && + ( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) ) + && + ( ( bpp == 8 ) || ( bpp == 16 ) ) + && + (tile_height0 == (uint32_t) m_height) // single strip + && + ( + (photometric == PHOTOMETRIC_MINISWHITE) + || + (photometric == PHOTOMETRIC_MINISBLACK) + || + (photometric == PHOTOMETRIC_RGB) + ) + && + (planerConfig != PLANARCONFIG_SEPARATE); + + // Currently only EXTRASAMPLE_ASSOCALPHA is supported. + if ( doReadScanline && ( ncn == 4 ) ) + { + uint16_t extra_samples_num; + uint16_t *extra_samples = NULL; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples )); + doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA ); + } + } + + if ( !doReadScanline ) + { + // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA + bpp = 8; + ncn = 4; + + char errmsg[1024]; + if (!TIFFRGBAImageOK(tif, errmsg)) + { + CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); + close(); + return false; + } + } + } + else if (dst_bpp == 16) + { + // if buffer_size >= MAX_TILE_SIZE*95%, + // we will use TIFFReadScanline function. + if ( + (uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) + >= + MAX_TILE_SIZE * 95 / 100 + ) + { + uint16_t planerConfig = (uint16)-1; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig)); + + doReadScanline = (!is_tiled) // no tile + && + ( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) ) + && + ( ( bpp == 8 ) || ( bpp == 16 ) ) + && + (tile_height0 == (uint32_t) m_height) // single strip + && + ( + (photometric == PHOTOMETRIC_MINISWHITE) + || + (photometric == PHOTOMETRIC_MINISBLACK) + || + (photometric == PHOTOMETRIC_RGB) + ) + && + (planerConfig != PLANARCONFIG_SEPARATE); + + // Currently only EXTRASAMPLE_ASSOCALPHA is supported. + if ( doReadScanline && ( ncn == 4 ) ) + { + uint16_t extra_samples_num; + uint16_t *extra_samples = NULL; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples )); + doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA ); + } + } } else if (dst_bpp == 32 || dst_bpp == 64) { CV_Assert(ncn == img.channels()); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)); } + + if ( doReadScanline ) + { + // Read each scanlines. + tile_height0 = 1; + } + const size_t buffer_size = (bpp / bitsPerByte) * ncn * tile_height0 * tile_width0; + CV_CheckLT( buffer_size, MAX_TILE_SIZE, "buffer_size is too large: >= 1Gb"); + + if ( doReadScanline ) + { + CV_CheckGE( static_cast(buffer_size), + static_cast(TIFFScanlineSize(tif)), + "buffer_size is smaller than TIFFScanlineSize(). "); + } + AutoBuffer _buffer(buffer_size); uchar* buffer = _buffer.data(); ushort* buffer16 = (ushort*)buffer; int tileidx = 0; + #define MAKE_FLAG(a,b) ( (a << 8) | b ) + const int convert_flag = MAKE_FLAG( ncn, wanted_channels ); + const bool isNeedConvert16to8 = ( doReadScanline ) && ( bpp == 16 ) && ( dst_bpp == 8); + for (int y = 0; y < m_height; y += (int)tile_height0) { int tile_height = std::min((int)tile_height0, m_height - y); @@ -514,7 +614,29 @@ bool TiffDecoder::readData( Mat& img ) case 8: { uchar* bstart = buffer; - if (!is_tiled) + if (doReadScanline) + { + CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)buffer, y) >= 0); + + if ( isNeedConvert16to8 ) + { + // Convert buffer image from 16bit to 8bit. + int ix; + for ( ix = 0 ; ix < tile_width * ncn - 4; ix += 4 ) + { + buffer[ ix ] = buffer[ ix * 2 + 1 ]; + buffer[ ix + 1 ] = buffer[ ix * 2 + 3 ]; + buffer[ ix + 2 ] = buffer[ ix * 2 + 5 ]; + buffer[ ix + 3 ] = buffer[ ix * 2 + 7 ]; + } + + for ( ; ix < tile_width * ncn ; ix ++ ) + { + buffer[ ix ] = buffer[ ix * 2 + 1]; + } + } + } + else if (!is_tiled) { CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)buffer)); } @@ -525,9 +647,65 @@ bool TiffDecoder::readData( Mat& img ) bstart += (tile_height0 - tile_height) * tile_width0 * 4; } + uchar* img_line_buffer = (uchar*) img.ptr(y, 0); + for (int i = 0; i < tile_height; i++) { - if (color) + if (doReadScanline) + { + switch ( convert_flag ) + { + case MAKE_FLAG( 1, 1 ): // GRAY to GRAY + memcpy( (void*) img_line_buffer, + (void*) bstart, + tile_width * sizeof(uchar) ); + break; + + case MAKE_FLAG( 1, 3 ): // GRAY to BGR + icvCvt_Gray2BGR_8u_C1C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 3, 1): // RGB to GRAY + icvCvt_BGR2Gray_8u_C3C1R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 3, 3 ): // RGB to BGR + icvCvt_BGR2RGB_8u_C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 4, 1 ): // RGBA to GRAY + icvCvt_BGRA2Gray_8u_C4C1R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + case MAKE_FLAG( 4, 3 ): // RGBA to BGR + icvCvt_BGRA2BGR_8u_C4C3R( bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1), 2 ); + break; + + case MAKE_FLAG( 4, 4 ): // RGBA to BGRA + icvCvt_BGRA2RGBA_8u_C4R(bstart, 0, + img_line_buffer, 0, + Size(tile_width, 1) ); + break; + + default: + CV_LOG_ONCE_ERROR(NULL, "OpenCV TIFF(line " << __LINE__ << "): Unsupported convertion :" + << " bpp = " << bpp << " ncn = " << (int)ncn + << " wanted_channels =" << wanted_channels ); + break; + } + #undef MAKE_FLAG + } + else if (color) { if (wanted_channels == 4) { @@ -556,7 +734,11 @@ bool TiffDecoder::readData( Mat& img ) case 16: { - if (!is_tiled) + if (doReadScanline) + { + CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)buffer, y) >= 0); + } + else if (!is_tiled) { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); } @@ -655,7 +837,11 @@ bool TiffDecoder::readData( Mat& img ) } // for x } // for y } - fixOrientation(img, img_orientation, dst_bpp); + + // If TIFFReadRGBA* function is used -> fixOrientationPartial(). + // Otherwise -> fixOrientationFull(). + fixOrientation(img, img_orientation, + ( ( dst_bpp != 8 ) && ( !doReadScanline ) ) ); } if (m_hdr && depth >= CV_32F) @@ -680,6 +866,7 @@ TiffEncoder::~TiffEncoder() ImageEncoder TiffEncoder::newEncoder() const { + cv_tiffSetErrorHandler(); return makePtr(); } diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 063bd9ae50..eed9eb8410 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -2,6 +2,8 @@ // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html #include "test_precomp.hpp" +#include "opencv2/core/utils/logger.hpp" +#include "opencv2/core/utils/configuration.private.hpp" namespace opencv_test { namespace { @@ -46,6 +48,432 @@ TEST(Imgcodecs_Tiff, decode_tile16384x16384) EXPECT_EQ(0, remove(file4.c_str())); } +//================================================================================================== +// See https://github.com/opencv/opencv/issues/22388 + +/** + * Dummy enum to show combination of IMREAD_*. + */ +enum ImreadMixModes +{ + IMREAD_MIX_UNCHANGED = IMREAD_UNCHANGED , + IMREAD_MIX_GRAYSCALE = IMREAD_GRAYSCALE , + IMREAD_MIX_COLOR = IMREAD_COLOR , + IMREAD_MIX_GRAYSCALE_ANYDEPTH = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH , + IMREAD_MIX_GRAYSCALE_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYCOLOR, + IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH = IMREAD_COLOR | IMREAD_ANYDEPTH , + IMREAD_MIX_COLOR_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR +}; + +typedef tuple< uint64_t, tuple, ImreadMixModes > Bufsize_and_Type; +typedef testing::TestWithParam Imgcodecs_Tiff_decode_Huge; + +static inline +void PrintTo(const ImreadMixModes& val, std::ostream* os) +{ + PrintTo( static_cast(val), os ); +} + +TEST_P(Imgcodecs_Tiff_decode_Huge, regression) +{ + // Get test parameters + const uint64_t buffer_size = get<0>(GetParam()); + const string mat_type_string = get<0>(get<1>(GetParam())); + const int mat_type = get<1>(get<1>(GetParam())); + const int imread_mode = get<2>(GetParam()); + + // Detect data file + const string req_filename = cv::format("readwrite/huge-tiff/%s_%llu.tif", mat_type_string.c_str(), buffer_size); + const string filename = findDataFile( req_filename ); + + // Preparation process for test + { + // Convert from mat_type and buffer_size to tiff file information. + const uint64_t width = 32768; + int ncn = CV_MAT_CN(mat_type); + int depth = ( CV_MAT_DEPTH(mat_type) == CV_16U) ? 2 : 1; // 16bit or 8 bit + const uint64_t height = (uint64_t) buffer_size / width / ncn / depth; + const uint64_t base_scanline_size = (uint64_t) width * ncn * depth; + const uint64_t base_strip_size = (uint64_t) base_scanline_size * height; + + // To avoid exception about pixel size, check it. + static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30); + uint64_t pixels = (uint64_t) width * height; + if ( pixels > CV_IO_MAX_IMAGE_PIXELS ) + { + throw SkipTestException( cv::format("Test is skipped( pixels(%lu) > CV_IO_MAX_IMAGE_PIXELS(%lu) )", + pixels, CV_IO_MAX_IMAGE_PIXELS ) ); + } + + // If buffer_size >= 1GB * 95%, TIFFReadScanline() is used. + const uint64_t BUFFER_SIZE_LIMIT_FOR_READS_CANLINE = (uint64_t) 1024*1024*1024*95/100; + const bool doReadScanline = ( base_strip_size >= BUFFER_SIZE_LIMIT_FOR_READS_CANLINE ); + + // Update ncn and depth for destination Mat. + switch ( imread_mode ) + { + case IMREAD_UNCHANGED: + break; + case IMREAD_GRAYSCALE: + ncn = 1; + depth = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYDEPTH: + ncn = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR: + ncn = (ncn == 1)?1:3; + depth = 1; + break; + case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH: + ncn = (ncn == 1)?1:3; + break; + case IMREAD_COLOR: + ncn = 3; + depth = 1; + break; + case IMREAD_COLOR | IMREAD_ANYDEPTH: + ncn = 3; + break; + case IMREAD_COLOR | IMREAD_ANYCOLOR: + ncn = 3; + depth = 1; + break; + case IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR: + ncn = 3; + break; + default: + break; + } + + // Memory usage for Destination Mat + const uint64_t memory_usage_cvmat = (uint64_t) width * ncn * depth * height; + + // Memory usage for Work memory in libtiff. + uint64_t memory_usage_tiff = 0; + if ( ( depth == 1 ) && ( !doReadScanline ) ) + { + // TIFFReadRGBA*() request to allocate RGBA(32bit) buffer. + memory_usage_tiff = (uint64_t) + width * + 4 * // ncn = RGBA + 1 * // dst_bpp = 8 bpp + height; + } + else + { + // TIFFReadEncodedStrip() or TIFFReadScanline() request to allocate strip memory. + memory_usage_tiff = base_strip_size; + } + + // Memory usage for Work memory in imgcodec/grfmt_tiff.cpp + const uint64_t memory_usage_work = + ( doReadScanline ) ? base_scanline_size // for TIFFReadScanline() + : base_strip_size; // for TIFFReadRGBA*() or TIFFReadEncodedStrip() + + // Total memory usage. + const uint64_t memory_usage_total = + memory_usage_cvmat + // Destination Mat + memory_usage_tiff + // Work memory in libtiff + memory_usage_work; // Work memory in imgcodecs + + // Output memory usage log. + CV_LOG_DEBUG(NULL, cv::format("OpenCV TIFF-test(line %d):memory usage info : mat(%llu), libtiff(%llu), work(%llu) -> total(%llu)", + __LINE__, memory_usage_cvmat, memory_usage_tiff, memory_usage_work, memory_usage_total) ); + + // Add test tags. + if ( memory_usage_total >= (uint64_t) 6144 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_14GB, CV_TEST_TAG_VERYLONG ); + } + else if ( memory_usage_total >= (uint64_t) 2048 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_6GB, CV_TEST_TAG_VERYLONG ); + } + else if ( memory_usage_total >= (uint64_t) 1024 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG ); + } + else if ( memory_usage_total >= (uint64_t) 512 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_1GB ); + } + else if ( memory_usage_total >= (uint64_t) 200 * 1024 * 1024 ) + { + applyTestTag( CV_TEST_TAG_MEMORY_512MB ); + } + else + { + // do nothing. + } + } + + // TEST Main + + cv::Mat img; + ASSERT_NO_THROW( img = cv::imread(filename, imread_mode) ); + ASSERT_FALSE(img.empty()); + + /** + * Test marker pixels at each corners. + * + * 0xAn,0x00 ... 0x00, 0xBn + * 0x00,0x00 ... 0x00, 0x00 + * : : : : + * 0x00,0x00 ... 0x00, 0x00 + * 0xCn,0x00 .., 0x00, 0xDn + * + */ + +#define MAKE_FLAG(from_type, to_type) (((uint64_t)from_type << 32 ) | to_type ) + + switch ( MAKE_FLAG(mat_type, img.type() ) ) + { + // GRAY TO GRAY + case MAKE_FLAG(CV_8UC1, CV_8UC1): + case MAKE_FLAG(CV_16UC1, CV_8UC1): + EXPECT_EQ( 0xA0, img.at(0, 0) ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1) ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1) ); + break; + + // RGB/RGBA TO BGR + case MAKE_FLAG(CV_8UC3, CV_8UC3): + case MAKE_FLAG(CV_8UC4, CV_8UC3): + case MAKE_FLAG(CV_16UC3, CV_8UC3): + case MAKE_FLAG(CV_16UC4, CV_8UC3): + EXPECT_EQ( 0xA2, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA1, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA0, img.at(0, 0) [2] ); + EXPECT_EQ( 0xB2, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB1, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xC2, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC1, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xD2, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD1, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[2] ); + break; + + // RGBA TO BGRA + case MAKE_FLAG(CV_8UC4, CV_8UC4): + case MAKE_FLAG(CV_16UC4, CV_8UC4): + EXPECT_EQ( 0xA2, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA1, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA0, img.at(0, 0) [2] ); + EXPECT_EQ( 0xA3, img.at(0, 0) [3] ); + EXPECT_EQ( 0xB2, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB1, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xB3, img.at(0, img.cols-1)[3] ); + EXPECT_EQ( 0xC2, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC1, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xC3, img.at(img.rows-1, 0) [3] ); + EXPECT_EQ( 0xD2, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD1, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[2] ); + EXPECT_EQ( 0xD3, img.at(img.rows-1, img.cols-1)[3] ); + break; + + // RGB/RGBA to GRAY + case MAKE_FLAG(CV_8UC3, CV_8UC1): + case MAKE_FLAG(CV_8UC4, CV_8UC1): + case MAKE_FLAG(CV_16UC3, CV_8UC1): + case MAKE_FLAG(CV_16UC4, CV_8UC1): + EXPECT_LE( 0xA0, img.at(0, 0) ); + EXPECT_GE( 0xA2, img.at(0, 0) ); + EXPECT_LE( 0xB0, img.at(0, img.cols-1) ); + EXPECT_GE( 0xB2, img.at(0, img.cols-1) ); + EXPECT_LE( 0xC0, img.at(img.rows-1, 0) ); + EXPECT_GE( 0xC2, img.at(img.rows-1, 0) ); + EXPECT_LE( 0xD0, img.at(img.rows-1, img.cols-1) ); + EXPECT_GE( 0xD2, img.at(img.rows-1, img.cols-1) ); + break; + + // GRAY to BGR + case MAKE_FLAG(CV_8UC1, CV_8UC3): + case MAKE_FLAG(CV_16UC1, CV_8UC3): + EXPECT_EQ( 0xA0, img.at(0, 0) [0] ); + EXPECT_EQ( 0xB0, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xC0, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xD0, img.at(img.rows-1, img.cols-1)[0] ); + // R==G==B + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [1] ); + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [2] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[1] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[2] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[2] ); + break; + + // GRAY TO GRAY + case MAKE_FLAG(CV_16UC1, CV_16UC1): + EXPECT_EQ( 0xA090, img.at(0, 0) ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1) ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1) ); + break; + + // RGB/RGBA TO BGR + case MAKE_FLAG(CV_16UC3, CV_16UC3): + case MAKE_FLAG(CV_16UC4, CV_16UC3): + EXPECT_EQ( 0xA292, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA191, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA090, img.at(0, 0) [2] ); + EXPECT_EQ( 0xB282, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB181, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xC272, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC171, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xD262, img.at(img.rows-1, img.cols-1)[0] ); + EXPECT_EQ( 0xD161, img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1)[2] ); + break; + + // RGBA TO RGBA + case MAKE_FLAG(CV_16UC4, CV_16UC4): + EXPECT_EQ( 0xA292, img.at(0, 0) [0] ); + EXPECT_EQ( 0xA191, img.at(0, 0) [1] ); + EXPECT_EQ( 0xA090, img.at(0, 0) [2] ); + EXPECT_EQ( 0xA393, img.at(0, 0) [3] ); + EXPECT_EQ( 0xB282, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xB181, img.at(0, img.cols-1)[1] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[2] ); + EXPECT_EQ( 0xB383, img.at(0, img.cols-1)[3] ); + EXPECT_EQ( 0xC272, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xC171, img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( 0xC373, img.at(img.rows-1, 0) [3] ); + EXPECT_EQ( 0xD262, img.at(img.rows-1,img.cols-1) [0] ); + EXPECT_EQ( 0xD161, img.at(img.rows-1,img.cols-1) [1] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1,img.cols-1) [2] ); + EXPECT_EQ( 0xD363, img.at(img.rows-1,img.cols-1) [3] ); + break; + + // RGB/RGBA to GRAY + case MAKE_FLAG(CV_16UC3, CV_16UC1): + case MAKE_FLAG(CV_16UC4, CV_16UC1): + EXPECT_LE( 0xA090, img.at(0, 0) ); + EXPECT_GE( 0xA292, img.at(0, 0) ); + EXPECT_LE( 0xB080, img.at(0, img.cols-1) ); + EXPECT_GE( 0xB282, img.at(0, img.cols-1) ); + EXPECT_LE( 0xC070, img.at(img.rows-1, 0) ); + EXPECT_GE( 0xC272, img.at(img.rows-1, 0) ); + EXPECT_LE( 0xD060, img.at(img.rows-1, img.cols-1) ); + EXPECT_GE( 0xD262, img.at(img.rows-1, img.cols-1) ); + break; + + // GRAY to RGB + case MAKE_FLAG(CV_16UC1, CV_16UC3): + EXPECT_EQ( 0xA090, img.at(0, 0) [0] ); + EXPECT_EQ( 0xB080, img.at(0, img.cols-1)[0] ); + EXPECT_EQ( 0xC070, img.at(img.rows-1, 0) [0] ); + EXPECT_EQ( 0xD060, img.at(img.rows-1, img.cols-1)[0] ); + // R==G==B + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [1] ); + EXPECT_EQ( img.at(0, 0) [0], img.at(0, 0) [2] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[1] ); + EXPECT_EQ( img.at(0, img.cols-1) [0], img.at(0, img.cols-1)[2] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [1] ); + EXPECT_EQ( img.at(img.rows-1, 0) [0], img.at(img.rows-1, 0) [2] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[1] ); + EXPECT_EQ( img.at(img.rows-1, img.cols-1) [0], img.at(img.rows-1, img.cols-1)[2] ); + break; + + // No supported. + // (1) 8bit to 16bit + case MAKE_FLAG(CV_8UC1, CV_16UC1): + case MAKE_FLAG(CV_8UC1, CV_16UC3): + case MAKE_FLAG(CV_8UC1, CV_16UC4): + case MAKE_FLAG(CV_8UC3, CV_16UC1): + case MAKE_FLAG(CV_8UC3, CV_16UC3): + case MAKE_FLAG(CV_8UC3, CV_16UC4): + case MAKE_FLAG(CV_8UC4, CV_16UC1): + case MAKE_FLAG(CV_8UC4, CV_16UC3): + case MAKE_FLAG(CV_8UC4, CV_16UC4): + // (2) GRAY/RGB TO RGBA + case MAKE_FLAG(CV_8UC1, CV_8UC4): + case MAKE_FLAG(CV_8UC3, CV_8UC4): + case MAKE_FLAG(CV_16UC1, CV_8UC4): + case MAKE_FLAG(CV_16UC3, CV_8UC4): + case MAKE_FLAG(CV_16UC1, CV_16UC4): + case MAKE_FLAG(CV_16UC3, CV_16UC4): + default: + FAIL() << cv::format("Unknown test pattern: from = %d ( %d, %d) to = %d ( %d, %d )", + mat_type, (int)CV_MAT_CN(mat_type ), ( CV_MAT_DEPTH(mat_type )==CV_16U)?16:8, + img.type(), (int)CV_MAT_CN(img.type() ), ( CV_MAT_DEPTH(img.type() )==CV_16U)?16:8); + break; + } + +#undef MAKE_FLAG +} + +// Basic Test +const Bufsize_and_Type Imgcodecs_Tiff_decode_Huge_list_basic[] = +{ + make_tuple,ImreadMixModes>( 1073479680ull, make_tuple("CV_8UC1", CV_8UC1), IMREAD_MIX_COLOR ), + make_tuple,ImreadMixModes>( 2147483648ull, make_tuple("CV_16UC4", CV_16UC4), IMREAD_MIX_COLOR ), +}; + +INSTANTIATE_TEST_CASE_P(Imgcodecs_Tiff, Imgcodecs_Tiff_decode_Huge, + testing::ValuesIn( Imgcodecs_Tiff_decode_Huge_list_basic ) +); + +// Full Test + +/** + * Test lists for combination of IMREAD_*. + */ +const ImreadMixModes all_modes_Huge_Full[] = +{ + IMREAD_MIX_UNCHANGED, + IMREAD_MIX_GRAYSCALE, + IMREAD_MIX_GRAYSCALE_ANYDEPTH, + IMREAD_MIX_GRAYSCALE_ANYCOLOR, + IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR, + IMREAD_MIX_COLOR, + IMREAD_MIX_COLOR_ANYDEPTH, + IMREAD_MIX_COLOR_ANYCOLOR, + IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR, +}; + +const uint64_t huge_buffer_sizes_decode_Full[] = +{ + 1048576ull, // 1 * 1024 * 1024 + 1073479680ull, // 1024 * 1024 * 1024 - 32768 * 4 * 2 + 1073741824ull, // 1024 * 1024 * 1024 + 2147483648ull, // 2048 * 1024 * 1024 +}; + +const tuple mat_types_Full[] = +{ + make_tuple("CV_8UC1", CV_8UC1), // 8bit GRAY + make_tuple("CV_8UC3", CV_8UC3), // 24bit RGB + make_tuple("CV_8UC4", CV_8UC4), // 32bit RGBA + make_tuple("CV_16UC1", CV_16UC1), // 16bit GRAY + make_tuple("CV_16UC3", CV_16UC3), // 48bit RGB + make_tuple("CV_16UC4", CV_16UC4), // 64bit RGBA +}; + +INSTANTIATE_TEST_CASE_P(DISABLED_Imgcodecs_Tiff_Full, Imgcodecs_Tiff_decode_Huge, + testing::Combine( + testing::ValuesIn(huge_buffer_sizes_decode_Full), + testing::ValuesIn(mat_types_Full), + testing::ValuesIn(all_modes_Huge_Full) + ) +); + + +//================================================================================================== + TEST(Imgcodecs_Tiff, write_read_16bit_big_little_endian) { // see issue #2601 "16-bit Grayscale TIFF Load Failures Due to Buffer Underflow and Endianness" -- GitLab