From 723670c33d748f481ebead9d7f12a6a83382b043 Mon Sep 17 00:00:00 2001 From: Pierre Chatelier Date: Fri, 10 Apr 2026 14:16:29 +0200 Subject: [PATCH] Merge pull request #28785 from chacha21:tiff_32F_compression Allow TIFF compression schemes for 32F#28785 Related to [https://github.com/opencv/opencv/issues/28775](https://github.com/opencv/opencv/issues/28775) Previously, only 32FC3+SGILOG could be specified for TIFF encoding. But when floats use quantization, compression schemes can be efficient even on 32F data. This PR will allow them. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [X] The PR is proposed to the proper branch - [X] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [X] The feature is well documented and sample code can be built with the project CMake I just don't know what kind of accuracy/performance tests should be added. --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 9 +- modules/imgcodecs/src/grfmt_tiff.cpp | 83 ++++++++++++++++--- modules/imgcodecs/src/grfmt_tiff.hpp | 1 + modules/imgcodecs/test/test_tiff.cpp | 43 +++++++++- 4 files changed, 121 insertions(+), 15 deletions(-) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 6c59d55ec3..bb6fbeb998 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -109,7 +109,7 @@ enum ImwriteFlags { IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set. See ImwriteTiffResolutionUnitFlags. Default is IMWRITE_TIFF_RESOLUTION_UNIT_INCH. IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI IMWRITE_TIFF_YDPI = 258,//!< For TIFF, use to specify the Y direction DPI - IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See cv::ImwriteTiffCompressionFlags. Note, for images whose depth is CV_32F, only libtiff's SGILOG compression scheme is used. For other supported depths, the compression scheme can be specified by this flag; LZW compression is the default. + IMWRITE_TIFF_COMPRESSION = 259,//!< For TIFF, use to specify the image compression scheme. See cv::ImwriteTiffCompressionFlags. The compression scheme can be specified by this flag; the default is LZW compression, except for 32F depth where it is NONE IMWRITE_TIFF_ROWSPERSTRIP = 278,//!< For TIFF, use to specify the number of rows per strip. IMWRITE_TIFF_PREDICTOR = 317,//!< For TIFF, use to specify predictor. See cv::ImwriteTiffPredictorFlags. Default is IMWRITE_TIFF_PREDICTOR_HORIZONTAL . IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000. @@ -540,8 +540,11 @@ can be saved using this function, with these exceptions: 32-bit signed (CV_32S), 32-bit float (CV_32F) and 64-bit float (CV_64F) images can be saved. - Multiple images (vector of Mat) can be saved in TIFF format (see the code sample below). - - 32-bit float 3-channel (CV_32FC3) TIFF images will be saved - using the LogLuv high dynamic range encoding (4 bytes per pixel) + - 32-bit float 3-channel (CV_32FC3) TIFF images can be saved + using the LogLuv high dynamic range encoding (4 bytes per pixel) through TIFF_COMPRESSION_SGILOG or + (3 bytes per pixel) through TIFF_COMPRESSION_SGILOG24. + - Other compression schemes (LZW...) are supported as well for 32F depth, but the efficiency might not + be very good for the floating-point representation bit patterns. - With GIF encoder, 8-bit unsigned (CV_8U) images can be saved. - GIF images with an alpha channel can be saved using this function. To achieve this, create an 8-bit 4-channel (CV_8UC4) BGRA image, ensuring the alpha channel is the last component. diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 902e061e31..f47e4722bb 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -1312,8 +1312,12 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect cv::Ptr tif_cleanup(tif, cv_tiffCloseHandle); //Settings that matter to all images - int compression = IMWRITE_TIFF_COMPRESSION_LZW; - int predictor = IMWRITE_TIFF_PREDICTOR_HORIZONTAL; + const int compression_default_32F = IMWRITE_TIFF_COMPRESSION_NONE; + const int compression_default = IMWRITE_TIFF_COMPRESSION_LZW; + const int predictor_default_32F = IMWRITE_TIFF_PREDICTOR_FLOATINGPOINT; + const int predictor_default = IMWRITE_TIFF_PREDICTOR_HORIZONTAL; + int compression = -1; + int predictor = -1; int resUnit = -1, dpiX = -1, dpiY = -1; if(readParam(params, IMWRITE_TIFF_COMPRESSION, compression)) @@ -1420,14 +1424,37 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PAGENUMBER, page, img_vec.size())); } - if (type == CV_32FC3 && compression == COMPRESSION_SGILOG) - { - if (!write_32FC3_SGILOG(img, tif)) - return false; - continue; - } + const bool is32F = (depth == CV_32F); + int page_compression = + (compression < 0) ? + (is32F ? compression_default_32F : compression_default) : + compression; + int page_predictor = + (predictor < 0) ? + (is32F ? predictor_default_32F : predictor_default) : + predictor; - int page_compression = compression; + if ((page_compression == COMPRESSION_SGILOG) || (page_compression == COMPRESSION_SGILOG24)) + { + if (depth != CV_32F) + CV_Error(cv::Error::StsError, "SGILOG requires 32F"); + else if ((page_compression == COMPRESSION_SGILOG24) && (type != CV_32FC3)) + CV_Error(cv::Error::StsError, "SGILOG24 requires 32FC3"); + else if ((page_compression == COMPRESSION_SGILOG) && (type != CV_32FC1) && (type != CV_32FC3)) + CV_Error(cv::Error::StsError, "SGILOG requires 32FC1 or 32FC3"); + else if ((page_compression == COMPRESSION_SGILOG) && (type == CV_32FC3)) + { + if (!write_32FC3_SGILOG(img, tif)) + return false; + continue; + } + else + { + if (!write_32F_SGILOG(img, tif, page_compression)) + return false; + continue; + } + } int bitsPerChannel = -1; uint16_t sample_format = SAMPLEFORMAT_INT; @@ -1460,7 +1487,6 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect case CV_32F: { bitsPerChannel = 32; - page_compression = COMPRESSION_NONE; sample_format = SAMPLEFORMAT_IEEEFP; break; } @@ -1498,7 +1524,7 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect if (page_compression == COMPRESSION_LZW || page_compression == COMPRESSION_ADOBE_DEFLATE || page_compression == COMPRESSION_DEFLATE) { - CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PREDICTOR, page_predictor)); } if (resUnit >= RESUNIT_NONE && resUnit <= RESUNIT_CENTIMETER) @@ -1583,6 +1609,41 @@ bool TiffEncoder::write_32FC3_SGILOG(const Mat& _img, void* tif_) return true; } +bool TiffEncoder::write_32F_SGILOG(const Mat& _img, void* tif_, int compression) +{ + TIFF* tif = (TIFF*)tif_; + CV_Assert(tif); + const int nChannels = _img.channels(); + CV_Assert( + ((compression == COMPRESSION_SGILOG) && ((nChannels == 1) || (nChannels == 3))) || + ((compression == COMPRESSION_SGILOG24) && (nChannels == 3)) + ); + + Mat img; + if (nChannels == 1) + img = _img; + else + cvtColor(_img, img, COLOR_BGR2XYZ); + + //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, img.cols)); + //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, img.rows)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, nChannels)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 32)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, + (nChannels == 1) ? PHOTOMETRIC_LOGL : PHOTOMETRIC_LOGLUV)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1)); + const int strip_size = nChannels * img.cols; + for (int i = 0; i < img.rows; i++) + { + CV_TIFF_CHECK_CALL(TIFFWriteEncodedStrip(tif, i, (tdata_t)img.ptr(i), strip_size * sizeof(float)) != (tsize_t)-1); + } + CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif)); + return true; +} + bool TiffEncoder::writemulti(const std::vector& img_vec, const std::vector& params) { return writeLibTiff(img_vec, params); diff --git a/modules/imgcodecs/src/grfmt_tiff.hpp b/modules/imgcodecs/src/grfmt_tiff.hpp index 0d1f511372..d1dd0e3392 100644 --- a/modules/imgcodecs/src/grfmt_tiff.hpp +++ b/modules/imgcodecs/src/grfmt_tiff.hpp @@ -134,6 +134,7 @@ public: protected: bool writeLibTiff( const std::vector& img_vec, const std::vector& params ); bool write_32FC3_SGILOG(const Mat& img, void* tif); + bool write_32F_SGILOG(const Mat& img, void* tif, int compression); private: TiffEncoder(const TiffEncoder &); // copy disabled diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index af995158b4..400e9a9a15 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -934,7 +934,7 @@ Imgcodes_Tiff_TypeAndComp all_types[] = { { CV_16UC1, true }, { CV_16UC3, true }, { CV_16UC4, true }, { CV_16SC1, true }, { CV_16SC3, true }, { CV_16SC4, true }, { CV_32SC1, true }, { CV_32SC3, true }, { CV_32SC4, true }, - { CV_32FC1, false }, { CV_32FC3, false }, { CV_32FC4, false }, // No compression + { CV_32FC1, true }, { CV_32FC3, true }, { CV_32FC4, true }, { CV_64FC1, false }, { CV_64FC3, false }, { CV_64FC4, false } // No compression }; @@ -1293,6 +1293,47 @@ TEST(Imgcodecs_Tiff, read_junk) { ASSERT_TRUE(img.empty()); } + +typedef int Imgcodecs_Tiff_32F_Compressions_32F_Values; +typedef testing::TestWithParam Imgcodecs_Tiff_32F_Compressions_32F; + +TEST_P(Imgcodecs_Tiff_32F_Compressions_32F, compressions_32F) +{ + const int compression = GetParam(); + + const Size size(64, 64); + Mat src = Mat(size, CV_32FC1); + cv::randu(src, cv::Scalar::all(0.), cv::Scalar::all(1.)); + + std::vector params; + if (compression > 0) + { + params.push_back(IMWRITE_TIFF_COMPRESSION); + params.push_back(compression); + } + + std::vector encoded_data; + imencode(".tiff", src, encoded_data, params); + + Mat dst; + imdecode(encoded_data, IMREAD_UNCHANGED, &dst); + + EXPECT_LE(cvtest::norm(src, dst, NORM_INF), 1e-6); +} + +const int Imgcodecs_Tiff_32F_Compressions_32F_All_Values[] = +{ + -1,//will mean "default" + IMWRITE_TIFF_COMPRESSION_NONE, + IMWRITE_TIFF_COMPRESSION_LZW, + //IMWRITE_TIFF_COMPRESSION_LZMA,//might not be configured + //IMWRITE_TIFF_COMPRESSION_ZSTD,//might not be configured + //IMWRITE_TIFF_COMPRESSION_DEFLATE,//deprecated + IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE, +}; + +INSTANTIATE_TEST_CASE_P(compressions_32F, Imgcodecs_Tiff_32F_Compressions_32F, testing::ValuesIn(Imgcodecs_Tiff_32F_Compressions_32F_All_Values)); + #endif }} // namespace