Merge pull request #22404 from Kumataro:3.4-fix22388_2
authorKumataro <Kumataro@users.noreply.github.com>
Mon, 3 Oct 2022 15:24:15 +0000 (00:24 +0900)
committerGitHub <noreply@github.com>
Mon, 3 Oct 2022 15:24:15 +0000 (18:24 +0300)
* 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
modules/imgcodecs/test/test_tiff.cpp

index 04df6ff8bb39d8205096db1f18fc22dd5d28a0ab..42ddeb2aa1e1842af0ac1b153991949a5e32b4d0 100644 (file)
@@ -234,7 +234,6 @@ public:
 bool TiffDecoder::readHeader()
 {
     bool result = false;
-
     TIFF* tif = static_cast<TIFF*>(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<int>(buffer_size),
+                            static_cast<int>(TIFFScanlineSize(tif)),
+                            "buffer_size is smaller than TIFFScanlineSize(). ");
+            }
+
             AutoBuffer<uchar> _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<TiffEncoder>();
 }
 
index 063bd9ae50df3e43cd485ba8560dd4b92bd0d342..eed9eb84100e86fea040368d67d20e437af80c2a 100644 (file)
@@ -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<string, int>, ImreadMixModes > Bufsize_and_Type;
+typedef testing::TestWithParam<Bufsize_and_Type> Imgcodecs_Tiff_decode_Huge;
+
+static inline
+void PrintTo(const ImreadMixModes& val, std::ostream* os)
+{
+    PrintTo( static_cast<ImreadModes>(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<uchar>(0,          0)          );
+        EXPECT_EQ( 0xB0,   img.at<uchar>(0,          img.cols-1) );
+        EXPECT_EQ( 0xC0,   img.at<uchar>(img.rows-1, 0)          );
+        EXPECT_EQ( 0xD0,   img.at<uchar>(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<Vec3b>(0,          0)         [0] );
+        EXPECT_EQ( 0xA1,   img.at<Vec3b>(0,          0)         [1] );
+        EXPECT_EQ( 0xA0,   img.at<Vec3b>(0,          0)         [2] );
+        EXPECT_EQ( 0xB2,   img.at<Vec3b>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xB1,   img.at<Vec3b>(0,          img.cols-1)[1] );
+        EXPECT_EQ( 0xB0,   img.at<Vec3b>(0,          img.cols-1)[2] );
+        EXPECT_EQ( 0xC2,   img.at<Vec3b>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xC1,   img.at<Vec3b>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( 0xC0,   img.at<Vec3b>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( 0xD2,   img.at<Vec3b>(img.rows-1, img.cols-1)[0] );
+        EXPECT_EQ( 0xD1,   img.at<Vec3b>(img.rows-1, img.cols-1)[1] );
+        EXPECT_EQ( 0xD0,   img.at<Vec3b>(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<Vec4b>(0,          0)         [0] );
+        EXPECT_EQ( 0xA1,   img.at<Vec4b>(0,          0)         [1] );
+        EXPECT_EQ( 0xA0,   img.at<Vec4b>(0,          0)         [2] );
+        EXPECT_EQ( 0xA3,   img.at<Vec4b>(0,          0)         [3] );
+        EXPECT_EQ( 0xB2,   img.at<Vec4b>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xB1,   img.at<Vec4b>(0,          img.cols-1)[1] );
+        EXPECT_EQ( 0xB0,   img.at<Vec4b>(0,          img.cols-1)[2] );
+        EXPECT_EQ( 0xB3,   img.at<Vec4b>(0,          img.cols-1)[3] );
+        EXPECT_EQ( 0xC2,   img.at<Vec4b>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xC1,   img.at<Vec4b>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( 0xC0,   img.at<Vec4b>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( 0xC3,   img.at<Vec4b>(img.rows-1, 0)         [3] );
+        EXPECT_EQ( 0xD2,   img.at<Vec4b>(img.rows-1, img.cols-1)[0] );
+        EXPECT_EQ( 0xD1,   img.at<Vec4b>(img.rows-1, img.cols-1)[1] );
+        EXPECT_EQ( 0xD0,   img.at<Vec4b>(img.rows-1, img.cols-1)[2] );
+        EXPECT_EQ( 0xD3,   img.at<Vec4b>(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<uchar>(0,          0)          );
+        EXPECT_GE( 0xA2,   img.at<uchar>(0,          0)          );
+        EXPECT_LE( 0xB0,   img.at<uchar>(0,          img.cols-1) );
+        EXPECT_GE( 0xB2,   img.at<uchar>(0,          img.cols-1) );
+        EXPECT_LE( 0xC0,   img.at<uchar>(img.rows-1, 0)          );
+        EXPECT_GE( 0xC2,   img.at<uchar>(img.rows-1, 0)          );
+        EXPECT_LE( 0xD0,   img.at<uchar>(img.rows-1, img.cols-1) );
+        EXPECT_GE( 0xD2,   img.at<uchar>(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<Vec3b>(0,          0)         [0] );
+        EXPECT_EQ( 0xB0,   img.at<Vec3b>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xC0,   img.at<Vec3b>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xD0,   img.at<Vec3b>(img.rows-1, img.cols-1)[0] );
+        // R==G==B
+        EXPECT_EQ( img.at<Vec3b>(0,          0)          [0], img.at<Vec3b>(0,          0)         [1] );
+        EXPECT_EQ( img.at<Vec3b>(0,          0)          [0], img.at<Vec3b>(0,          0)         [2] );
+        EXPECT_EQ( img.at<Vec3b>(0,          img.cols-1) [0], img.at<Vec3b>(0,          img.cols-1)[1] );
+        EXPECT_EQ( img.at<Vec3b>(0,          img.cols-1) [0], img.at<Vec3b>(0,          img.cols-1)[2] );
+        EXPECT_EQ( img.at<Vec3b>(img.rows-1,          0) [0], img.at<Vec3b>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( img.at<Vec3b>(img.rows-1,          0) [0], img.at<Vec3b>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( img.at<Vec3b>(img.rows-1, img.cols-1) [0], img.at<Vec3b>(img.rows-1, img.cols-1)[1] );
+        EXPECT_EQ( img.at<Vec3b>(img.rows-1, img.cols-1) [0], img.at<Vec3b>(img.rows-1, img.cols-1)[2] );
+        break;
+
+    // GRAY TO GRAY
+    case MAKE_FLAG(CV_16UC1, CV_16UC1):
+        EXPECT_EQ( 0xA090, img.at<ushort>(0,          0)          );
+        EXPECT_EQ( 0xB080, img.at<ushort>(0,          img.cols-1) );
+        EXPECT_EQ( 0xC070, img.at<ushort>(img.rows-1, 0)          );
+        EXPECT_EQ( 0xD060, img.at<ushort>(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<Vec3w>(0,          0)         [0] );
+        EXPECT_EQ( 0xA191, img.at<Vec3w>(0,          0)         [1] );
+        EXPECT_EQ( 0xA090, img.at<Vec3w>(0,          0)         [2] );
+        EXPECT_EQ( 0xB282, img.at<Vec3w>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xB181, img.at<Vec3w>(0,          img.cols-1)[1] );
+        EXPECT_EQ( 0xB080, img.at<Vec3w>(0,          img.cols-1)[2] );
+        EXPECT_EQ( 0xC272, img.at<Vec3w>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xC171, img.at<Vec3w>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( 0xC070, img.at<Vec3w>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( 0xD262, img.at<Vec3w>(img.rows-1, img.cols-1)[0] );
+        EXPECT_EQ( 0xD161, img.at<Vec3w>(img.rows-1, img.cols-1)[1] );
+        EXPECT_EQ( 0xD060, img.at<Vec3w>(img.rows-1, img.cols-1)[2] );
+        break;
+
+    // RGBA TO RGBA
+    case MAKE_FLAG(CV_16UC4, CV_16UC4):
+        EXPECT_EQ( 0xA292, img.at<Vec4w>(0,          0)         [0] );
+        EXPECT_EQ( 0xA191, img.at<Vec4w>(0,          0)         [1] );
+        EXPECT_EQ( 0xA090, img.at<Vec4w>(0,          0)         [2] );
+        EXPECT_EQ( 0xA393, img.at<Vec4w>(0,          0)         [3] );
+        EXPECT_EQ( 0xB282, img.at<Vec4w>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xB181, img.at<Vec4w>(0,          img.cols-1)[1] );
+        EXPECT_EQ( 0xB080, img.at<Vec4w>(0,          img.cols-1)[2] );
+        EXPECT_EQ( 0xB383, img.at<Vec4w>(0,          img.cols-1)[3] );
+        EXPECT_EQ( 0xC272, img.at<Vec4w>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xC171, img.at<Vec4w>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( 0xC070, img.at<Vec4w>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( 0xC373, img.at<Vec4w>(img.rows-1, 0)         [3] );
+        EXPECT_EQ( 0xD262, img.at<Vec4w>(img.rows-1,img.cols-1) [0] );
+        EXPECT_EQ( 0xD161, img.at<Vec4w>(img.rows-1,img.cols-1) [1] );
+        EXPECT_EQ( 0xD060, img.at<Vec4w>(img.rows-1,img.cols-1) [2] );
+        EXPECT_EQ( 0xD363, img.at<Vec4w>(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<ushort>(0,          0) );
+        EXPECT_GE( 0xA292, img.at<ushort>(0,          0) );
+        EXPECT_LE( 0xB080, img.at<ushort>(0,          img.cols-1) );
+        EXPECT_GE( 0xB282, img.at<ushort>(0,          img.cols-1) );
+        EXPECT_LE( 0xC070, img.at<ushort>(img.rows-1, 0) );
+        EXPECT_GE( 0xC272, img.at<ushort>(img.rows-1, 0) );
+        EXPECT_LE( 0xD060, img.at<ushort>(img.rows-1, img.cols-1) );
+        EXPECT_GE( 0xD262, img.at<ushort>(img.rows-1, img.cols-1) );
+        break;
+
+    // GRAY to RGB
+    case MAKE_FLAG(CV_16UC1, CV_16UC3):
+        EXPECT_EQ( 0xA090,   img.at<Vec3w>(0,          0)         [0] );
+        EXPECT_EQ( 0xB080,   img.at<Vec3w>(0,          img.cols-1)[0] );
+        EXPECT_EQ( 0xC070,   img.at<Vec3w>(img.rows-1, 0)         [0] );
+        EXPECT_EQ( 0xD060,   img.at<Vec3w>(img.rows-1, img.cols-1)[0] );
+        // R==G==B
+        EXPECT_EQ( img.at<Vec3w>(0,          0)          [0], img.at<Vec3w>(0,          0)         [1] );
+        EXPECT_EQ( img.at<Vec3w>(0,          0)          [0], img.at<Vec3w>(0,          0)         [2] );
+        EXPECT_EQ( img.at<Vec3w>(0,          img.cols-1) [0], img.at<Vec3w>(0,          img.cols-1)[1] );
+        EXPECT_EQ( img.at<Vec3w>(0,          img.cols-1) [0], img.at<Vec3w>(0,          img.cols-1)[2] );
+        EXPECT_EQ( img.at<Vec3w>(img.rows-1,          0) [0], img.at<Vec3w>(img.rows-1, 0)         [1] );
+        EXPECT_EQ( img.at<Vec3w>(img.rows-1,          0) [0], img.at<Vec3w>(img.rows-1, 0)         [2] );
+        EXPECT_EQ( img.at<Vec3w>(img.rows-1, img.cols-1) [0], img.at<Vec3w>(img.rows-1, img.cols-1)[1] );
+        EXPECT_EQ( img.at<Vec3w>(img.rows-1, img.cols-1) [0], img.at<Vec3w>(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<uint64_t, tuple<string,int>,ImreadMixModes>( 1073479680ull, make_tuple<string,int>("CV_8UC1",  CV_8UC1),  IMREAD_MIX_COLOR ),
+    make_tuple<uint64_t, tuple<string,int>,ImreadMixModes>( 2147483648ull, make_tuple<string,int>("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<string, int> mat_types_Full[] =
+{
+    make_tuple<string, int>("CV_8UC1",  CV_8UC1),  // 8bit  GRAY
+    make_tuple<string, int>("CV_8UC3",  CV_8UC3),  // 24bit RGB
+    make_tuple<string, int>("CV_8UC4",  CV_8UC4),  // 32bit RGBA
+    make_tuple<string, int>("CV_16UC1", CV_16UC1), // 16bit GRAY
+    make_tuple<string, int>("CV_16UC3", CV_16UC3), // 48bit RGB
+    make_tuple<string, int>("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"