Merge pull request #30165 from chacha21:tiff_compression_level

Adding IMWRITE_TIFF_COMPRESSION_LEVEL - #30165

For some compression schemes (DEFLATE, ZSTD), libtiff supports a TIFFTAG_ZIPQUALITY. This PR adds an IMWRITE_TIFF_COMPRESSION_LEVEL param to set it.

- [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
- [ ] 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.
- [ ] The feature is well documented and sample code can be built with the project CMake

I don't know what kind of automatic tests could be relevant.

I just checked that 
1) the param is honoured
2) for some basic mats, level 1 gives lower compression than level 9
3) level -1 (default) gives a compression ratio between levels 1 and 9 (actually seems to be 6, as expected)

But I am not sure that 2) and 3) are generally guaranteed by the compression scheme for any data, so is it relevant to make a test assert on that ?
This commit is contained in:
Pierre Chatelier
2026-10-10 11:31:47 +02:00
committed by GitHub
parent 67ede46c65
commit 802cf95aa3
3 changed files with 134 additions and 1 deletions

View File

@@ -110,6 +110,7 @@ enum ImwriteFlags {
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. The compression scheme can be specified by this flag; the default is LZW compression, except for 32F depth where it is NONE
IMWRITE_TIFF_COMPRESSION_LEVEL = 260,//!< For TIFF, if IMWRITE_TIFF_COMPRESSION was used and supports it, sets a compression level. Valid range and meaning depends on the compression scheme. It is currently 1-9 for DEFLATE, 0-9 for LZMA, 1-22 for ZSTD, 1-100 for WEBP. -1 can be used as default.
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.

View File

@@ -1336,7 +1336,7 @@ TiffEncoder::TiffEncoder()
{
m_description = "TIFF Files (*.tiff;*.tif)";
m_buf_supported = true;
m_supported_encode_key = {IMWRITE_TIFF_RESUNIT, IMWRITE_TIFF_XDPI, IMWRITE_TIFF_YDPI, IMWRITE_TIFF_COMPRESSION, IMWRITE_TIFF_ROWSPERSTRIP, IMWRITE_TIFF_PREDICTOR};
m_supported_encode_key = {IMWRITE_TIFF_RESUNIT, IMWRITE_TIFF_XDPI, IMWRITE_TIFF_YDPI, IMWRITE_TIFF_COMPRESSION, IMWRITE_TIFF_ROWSPERSTRIP, IMWRITE_TIFF_PREDICTOR, IMWRITE_TIFF_COMPRESSION_LEVEL};
}
TiffEncoder::~TiffEncoder()
@@ -1477,6 +1477,8 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
const int predictor_default_32F = IMWRITE_TIFF_PREDICTOR_FLOATINGPOINT;
const int predictor_default = IMWRITE_TIFF_PREDICTOR_HORIZONTAL;
int compression = -1;
int compressionLevel = -1;
std::pair<int, int> compressionLevelValidRange = {-1, -1};
int predictor = -1;
int resUnit = -1, dpiX = -1, dpiY = -1;
@@ -1521,6 +1523,45 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
break;
}
}
switch (compression) {
case IMWRITE_TIFF_COMPRESSION_DEFLATE:
case IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE:
compressionLevelValidRange = std::make_pair(1, 9);
break;
case IMWRITE_TIFF_COMPRESSION_LZMA:
compressionLevelValidRange = std::make_pair(0, 9);
break;
case IMWRITE_TIFF_COMPRESSION_ZSTD:
compressionLevelValidRange = std::make_pair(1, 22);
break;
case IMWRITE_TIFF_COMPRESSION_WEBP:
compressionLevelValidRange = std::make_pair(1, 100);
break;
default:
break;
}
if (readParam(params, IMWRITE_TIFF_COMPRESSION_LEVEL, compressionLevel))
{
const bool isValidCompression =
(compression == IMWRITE_TIFF_COMPRESSION_DEFLATE) ||
(compression == IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE) ||
(compression == IMWRITE_TIFF_COMPRESSION_LZMA) ||
(compression == IMWRITE_TIFF_COMPRESSION_ZSTD) ||
(compression == IMWRITE_TIFF_COMPRESSION_WEBP);
const bool isValidValue = (compressionLevel == -1) ||
((compressionLevel >= compressionLevelValidRange.first) && (compressionLevel <= compressionLevelValidRange.second));
if (!isValidCompression) {
CV_LOG_WARNING(nullptr, cv::format("IMWRITE_TIFF_COMPRESSION_LEVEL can only be used for DEFLATE, LZMA, ZSTD or WEBP compression. It is ignored."));
compressionLevel = -1;
}
else if (!isValidValue) {
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_TIFF_COMPRESSION_LEVEL=%d must be in the range %d-%d (or -1 for default). It is set to default.",
compressionLevel, compression,
compressionLevelValidRange.first, compressionLevelValidRange.second));
compressionLevel = -1;
}
}
if(readParam(params, IMWRITE_TIFF_PREDICTOR, predictor))
{
switch(predictor) {
@@ -1675,6 +1716,40 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitsPerChannel));
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, page_compression));
if (compressionLevel >= 0)
{
switch(page_compression)
{
case IMWRITE_TIFF_COMPRESSION_DEFLATE:
case IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE:
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ZIPQUALITY, compressionLevel));
break;
case IMWRITE_TIFF_COMPRESSION_LZMA:
#ifdef TIFFTAG_LZMAPRESET // libtiff 4.0.0+
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_LZMAPRESET, compressionLevel));
#else
CV_LOG_WARNING(nullptr, cv::format("The LZMA compression(%d) is not supported in that build of the TIFF library. It is ignored.", page_compression));
#endif
break;
case IMWRITE_TIFF_COMPRESSION_ZSTD:
#ifdef TIFFTAG_ZSTD_LEVEL // libtiff 4.0.10+
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ZSTD_LEVEL, compressionLevel));
#else
CV_LOG_WARNING(nullptr, cv::format("The ZSTD compression(%d) is not supported in that build of the TIFF library. It is ignored.", page_compression));
#endif
break;
case IMWRITE_TIFF_COMPRESSION_WEBP:
#ifdef TIFFTAG_WEBP_LEVEL // libtiff 4.0.10+
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_WEBP_LEVEL, compressionLevel));
#else
CV_LOG_WARNING(nullptr, cv::format("The WEBP compression(%d) is not supported in that build of the TIFF library. It is ignored.", page_compression));
#endif
break;
default:
CV_LOG_WARNING(nullptr, cv::format("Unexpected page_compression %d", page_compression));
break;
}//end switch(page_compression)
}//end if (compressionLevel >= 0)
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, colorspace));
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, channels));
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG));

View File

@@ -1830,6 +1830,63 @@ TEST(Imgcodecs_Tiff, decode_planar_separate_multipage)
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), page1, pages[1]);
}
// https://github.com/opencv/opencv/pull/30165
typedef tuple< int, int, int > Method_minLevel_maxLevel;
typedef testing::TestWithParam<Method_minLevel_maxLevel> Imgcodecs_Tiff_compress_level;
TEST_P(Imgcodecs_Tiff_compress_level, normal)
{
// Get test parameters
const int compressMethod = get<0>(GetParam());
const int minLevel = get<1>(GetParam());
const int maxLevel = get<2>(GetParam());
//build a dummy image that can be heavily compressed
cv::Mat src = cv::Mat(cv::Size(640, 480), CV_8UC3);
src.forEach<cv::Vec3b>([&](cv::Vec3b& pixel, const int* pos) {
const int y = pos[0];
const int x = pos[1];
const uchar value = static_cast<uchar>(y*src.cols+x);
pixel = cv::Vec3b(value, value, value);
});
std::vector<uint8_t> bufNone;
std::vector<int> paramsNone = { IMWRITE_TIFF_COMPRESSION, IMWRITE_TIFF_COMPRESSION_NONE};
ASSERT_NO_THROW(ASSERT_TRUE(imencode(".tiff", src, bufNone, paramsNone)));
std::vector<uint8_t> bufDef;
std::vector<int> paramsDef = {IMWRITE_TIFF_COMPRESSION, compressMethod};
ASSERT_NO_THROW(ASSERT_TRUE(imencode(".tiff", src, bufDef, paramsDef)));
std::vector<uint8_t> bufMin;
std::vector<int> paramsMin = {IMWRITE_TIFF_COMPRESSION, compressMethod, IMWRITE_TIFF_COMPRESSION_LEVEL, minLevel};
ASSERT_NO_THROW(ASSERT_TRUE(imencode(".tiff", src, bufMin, paramsMin)));
std::vector<uint8_t> bufMax;
std::vector<int> paramsMax = {IMWRITE_TIFF_COMPRESSION, compressMethod, IMWRITE_TIFF_COMPRESSION_LEVEL, maxLevel};
ASSERT_NO_THROW(ASSERT_TRUE(imencode(".tiff", src, bufMax, paramsMax)));
ASSERT_LT(bufDef.size(), bufNone.size());
ASSERT_LE(bufDef.size(), bufMin.size());
ASSERT_LE(bufMax.size(), bufDef.size());
ASSERT_NE(bufMax.size(), bufMin.size());
}
const Method_minLevel_maxLevel supportedMethod_minLevel_maxLevel[] =
{
make_tuple<int, int, int>(IMWRITE_TIFF_COMPRESSION_DEFLATE, 1, 9),
make_tuple<int, int, int>(IMWRITE_TIFF_COMPRESSION_ADOBE_DEFLATE, 1, 9),
// make_tuple<int, int, int>(IMWRITE_TIFF_COMPRESSION_LZMA, 0, 9),
// make_tuple<int, int, int>(IMWRITE_TIFF_COMPRESSION_ZSTD, 1, 22),
// make_tuple<int, int, int>(IMWRITE_TIFF_COMPRESSION_WEBP, 1, 100),
};
INSTANTIATE_TEST_CASE_P(Imgcodecs_Tiff, Imgcodecs_Tiff_compress_level,
testing::ValuesIn(supportedMethod_minLevel_maxLevel)
);
#endif
}} // namespace