opencv/0009-Merge-pull-request-26872-from-sturkmen72-ImageEncode.patch
Adam Williamson 9a00dd9072 Backport all post-4.11.0 PNG fixes, including big-endian fix
This backports all changes to the PNG reading code since 4.11.0,
mainly to get the fix for #2345306, PNG read fails on big-endian.
It's difficult to backport just that fix to 4.11.0 as it's built
on all the earlier changes, and the earlier changes look like
useful fixes too in any case.

See upstream https://github.com/opencv/opencv/issues/26913 and
https://github.com/opencv/opencv/pull/26915 .

Resolves: rhbz#2345306

Signed-off-by: Adam Williamson <awilliam@redhat.com>
2025-02-18 12:16:02 -08:00

367 lines
13 KiB
Diff

From 8aa1086ab475ef0040d3865e49edd6feba4eb7d3 Mon Sep 17 00:00:00 2001
From: Suleyman TURKMEN <sturkmen@hotmail.com>
Date: Tue, 4 Feb 2025 12:21:55 +0300
Subject: [PATCH 09/10] Merge pull request #26872 from
sturkmen72:ImageEncoders_revisions
Performance tests for image encoders and decoders and code cleanup #26872
### 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
- [ ] 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
---
modules/highgui/src/window_w32.cpp | 10 +-
modules/imgcodecs/perf/perf_decode_encode.cpp | 131 ++++++++++++++++++
modules/imgcodecs/src/grfmt_avif.cpp | 5 -
modules/imgcodecs/src/grfmt_avif.hpp | 1 -
modules/imgcodecs/src/grfmt_base.cpp | 8 +-
modules/imgcodecs/src/grfmt_base.hpp | 5 +-
modules/imgcodecs/src/grfmt_gif.cpp | 10 +-
modules/imgcodecs/src/grfmt_gif.hpp | 3 -
modules/imgcodecs/src/grfmt_png.cpp | 6 +-
modules/imgcodecs/src/loadsave.cpp | 2 +-
10 files changed, 155 insertions(+), 26 deletions(-)
create mode 100644 modules/imgcodecs/perf/perf_decode_encode.cpp
diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp
index 2543c81c6a..8e041c9609 100644
--- a/modules/highgui/src/window_w32.cpp
+++ b/modules/highgui/src/window_w32.cpp
@@ -2170,9 +2170,15 @@ static void showSaveDialog(CvWindow& window)
#ifdef HAVE_WEBP
"WebP files (*.webp)\0*.webp\0"
#endif
- "Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)\0*.pbm;*.pgm;*.ppm;*.pxm;*.pnm\0"
+ "Portable image format (*.pbm;*.pgm;*.ppm;*.pnm;*.pam)\0*.pbm;*.pgm;*.ppm;*.pnm;*.pam\0"
#ifdef HAVE_OPENEXR
"OpenEXR Image files (*.exr)\0*.exr\0"
+#endif
+#ifdef HAVE_AVIF
+ "AVIF files (*.avif)\0*.avif\0"
+#endif
+#ifdef HAVE_IMGCODEC_GIF
+ "Graphics Interchange Format 89a(*.gif)\0*.gif\0"
#endif
"Radiance HDR (*.hdr;*.pic)\0*.hdr;*.pic\0"
"Sun raster files (*.sr;*.ras)\0*.sr;*.ras\0"
@@ -2194,7 +2200,7 @@ static void showSaveDialog(CvWindow& window)
}
#else
CV_UNUSED(window);
- CV_LOG_WARNING("Save dialog requires enabled 'imgcodecs' module.");
+ CV_LOG_WARNING(NULL, "Save dialog requires enabled 'imgcodecs' module.");
return;
#endif
}
diff --git a/modules/imgcodecs/perf/perf_decode_encode.cpp b/modules/imgcodecs/perf/perf_decode_encode.cpp
new file mode 100644
index 0000000000..ce693cb878
--- /dev/null
+++ b/modules/imgcodecs/perf/perf_decode_encode.cpp
@@ -0,0 +1,131 @@
+// This file is part of OpenCV project.
+// 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 "perf_precomp.hpp"
+
+namespace opencv_test
+{
+
+#ifdef HAVE_PNG
+
+using namespace perf;
+
+typedef perf::TestBaseWithParam<std::string> Decode;
+typedef perf::TestBaseWithParam<std::string> Encode;
+
+const string exts[] = {
+#ifdef HAVE_AVIF
+ ".avif",
+#endif
+ ".bmp",
+#ifdef HAVE_IMGCODEC_GIF
+ ".gif",
+#endif
+#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
+ || defined(HAVE_OPENJPEG)
+ ".jp2",
+#endif
+#ifdef HAVE_JPEG
+ ".jpg",
+#endif
+#ifdef HAVE_JPEGXL
+ ".jxl",
+#endif
+ ".png",
+#ifdef HAVE_IMGCODEC_PXM
+ ".ppm",
+#endif
+#ifdef HAVE_IMGCODEC_SUNRASTER
+ ".ras",
+#endif
+#ifdef HAVE_TIFF
+ ".tiff",
+#endif
+#ifdef HAVE_WEBP
+ ".webp",
+#endif
+};
+
+const string exts_multi[] = {
+#ifdef HAVE_AVIF
+ ".avif",
+#endif
+#ifdef HAVE_IMGCODEC_GIF
+ ".gif",
+#endif
+ ".png",
+#ifdef HAVE_TIFF
+ ".tiff",
+#endif
+#ifdef HAVE_WEBP
+ ".webp",
+#endif
+};
+
+PERF_TEST_P(Decode, bgr, testing::ValuesIn(exts))
+{
+ String filename = getDataPath("perf/1920x1080.png");
+
+ Mat src = imread(filename);
+ EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
+ vector<uchar> buf;
+ EXPECT_TRUE(imencode(GetParam(), src, buf));
+
+ TEST_CYCLE() imdecode(buf, IMREAD_UNCHANGED);
+
+ SANITY_CHECK_NOTHING();
+}
+
+PERF_TEST_P(Decode, rgb, testing::ValuesIn(exts))
+{
+ String filename = getDataPath("perf/1920x1080.png");
+
+ Mat src = imread(filename);
+ EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
+ vector<uchar> buf;
+ EXPECT_TRUE(imencode(GetParam(), src, buf));
+
+ TEST_CYCLE() imdecode(buf, IMREAD_COLOR_RGB);
+
+ SANITY_CHECK_NOTHING();
+}
+
+PERF_TEST_P(Encode, bgr, testing::ValuesIn(exts))
+{
+ String filename = getDataPath("perf/1920x1080.png");
+
+ Mat src = imread(filename);
+ EXPECT_FALSE(src.empty()) << "Cannot open test image perf/1920x1080.png";
+ vector<uchar> buf;
+
+ TEST_CYCLE() imencode(GetParam(), src, buf);
+
+ std::cout << "Encoded buffer size: " << buf.size()
+ << " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
+ << (static_cast<double>(buf.size()) / (src.total() * src.channels())) * 100.0 << "%" << std::endl;
+
+ SANITY_CHECK_NOTHING();
+}
+
+PERF_TEST_P(Encode, multi, testing::ValuesIn(exts_multi))
+{
+ String filename = getDataPath("perf/1920x1080.png");
+ vector<Mat> vec;
+ EXPECT_TRUE(imreadmulti(filename, vec));
+ vec.push_back(vec.back().clone());
+ circle(vec.back(), Point(100, 100), 45, Scalar(0, 0, 255, 0), 2, LINE_AA);
+ vector<uchar> buf;
+ EXPECT_TRUE(imwrite("test" + GetParam(), vec));
+
+ TEST_CYCLE() imencode(GetParam(), vec, buf);
+
+ std::cout << "Encoded buffer size: " << buf.size()
+ << " bytes, Compression ratio: " << std::fixed << std::setprecision(2)
+ << (static_cast<double>(buf.size()) / (vec[0].total() * vec[0].channels())) * 100.0 << "%" << std::endl;
+
+ SANITY_CHECK_NOTHING();
+}
+#endif // HAVE_PNG
+
+} // namespace
diff --git a/modules/imgcodecs/src/grfmt_avif.cpp b/modules/imgcodecs/src/grfmt_avif.cpp
index d3fb500604..c35eb50306 100644
--- a/modules/imgcodecs/src/grfmt_avif.cpp
+++ b/modules/imgcodecs/src/grfmt_avif.cpp
@@ -298,11 +298,6 @@ bool AvifEncoder::isFormatSupported(int depth) const {
return (depth == CV_8U || depth == CV_16U);
}
-bool AvifEncoder::write(const Mat &img, const std::vector<int> &params) {
- std::vector<Mat> img_vec(1, img);
- return writemulti(img_vec, params);
-}
-
bool AvifEncoder::writeanimation(const Animation& animation,
const std::vector<int> &params) {
int bit_depth = 8;
diff --git a/modules/imgcodecs/src/grfmt_avif.hpp b/modules/imgcodecs/src/grfmt_avif.hpp
index 87b765619e..9f097aaf55 100644
--- a/modules/imgcodecs/src/grfmt_avif.hpp
+++ b/modules/imgcodecs/src/grfmt_avif.hpp
@@ -41,7 +41,6 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder {
~AvifEncoder() CV_OVERRIDE;
bool isFormatSupported(int depth) const CV_OVERRIDE;
- bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp
index 1e09882780..dc3d07ab78 100644
--- a/modules/imgcodecs/src/grfmt_base.cpp
+++ b/modules/imgcodecs/src/grfmt_base.cpp
@@ -140,6 +140,11 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
return true;
}
+bool BaseImageEncoder::write(const Mat &img, const std::vector<int> &params) {
+ std::vector<Mat> img_vec(1, img);
+ return writemulti(img_vec, params);
+}
+
bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
{
if(img_vec.size() > 1)
@@ -157,6 +162,7 @@ bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::ve
bool BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
{
+ CV_LOG_WARNING(NULL, "No Animation encoder for specified file extension");
return false;
}
@@ -165,7 +171,7 @@ ImageEncoder BaseImageEncoder::newEncoder() const
return ImageEncoder();
}
-void BaseImageEncoder::throwOnEror() const
+void BaseImageEncoder::throwOnError() const
{
if(!m_last_error.empty())
{
diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp
index a90bd8a3de..ae5622528c 100644
--- a/modules/imgcodecs/src/grfmt_base.hpp
+++ b/modules/imgcodecs/src/grfmt_base.hpp
@@ -202,12 +202,11 @@ public:
/**
* @brief Encode and write the image data.
- * This is a pure virtual function that must be implemented by derived classes.
* @param img The Mat object containing the image data to be encoded.
* @param params A vector of parameters controlling the encoding process (e.g., compression level).
* @return true if the image was successfully written, false otherwise.
*/
- virtual bool write(const Mat& img, const std::vector<int>& params) = 0;
+ virtual bool write(const Mat& img, const std::vector<int>& params);
/**
* @brief Encode and write multiple images (e.g., for animated formats).
@@ -236,7 +235,7 @@ public:
* @brief Throw an exception based on the last error encountered during encoding.
* This method can be used to propagate error conditions back to the caller.
*/
- virtual void throwOnEror() const;
+ virtual void throwOnError() const;
protected:
String m_description; ///< Description of the encoder (e.g., format name, capabilities).
diff --git a/modules/imgcodecs/src/grfmt_gif.cpp b/modules/imgcodecs/src/grfmt_gif.cpp
index 5a65ae04b1..b0533b644f 100644
--- a/modules/imgcodecs/src/grfmt_gif.cpp
+++ b/modules/imgcodecs/src/grfmt_gif.cpp
@@ -488,19 +488,11 @@ GifEncoder::~GifEncoder() {
close();
}
-bool GifEncoder::isFormatSupported(int depth) const {
- return depth == CV_8U;
-}
-
-bool GifEncoder::write(const Mat &img, const std::vector<int> &params) {
- std::vector<Mat> img_vec(1, img);
- return writemulti(img_vec, params);
-}
-
bool GifEncoder::writeanimation(const Animation& animation, const std::vector<int>& params) {
if (animation.frames.empty()) {
return false;
}
+ CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "GIF encoder supports only 8-bit unsigned images");
if (m_buf) {
if (!strm.open(*m_buf)) {
diff --git a/modules/imgcodecs/src/grfmt_gif.hpp b/modules/imgcodecs/src/grfmt_gif.hpp
index 8f520745ba..8552718d00 100644
--- a/modules/imgcodecs/src/grfmt_gif.hpp
+++ b/modules/imgcodecs/src/grfmt_gif.hpp
@@ -83,9 +83,6 @@ public:
GifEncoder();
~GifEncoder() CV_OVERRIDE;
- bool isFormatSupported(int depth) const CV_OVERRIDE;
-
- bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp
index f7a19c2bf5..825122304a 100644
--- a/modules/imgcodecs/src/grfmt_png.cpp
+++ b/modules/imgcodecs/src/grfmt_png.cpp
@@ -1412,6 +1412,9 @@ void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, i
bool PngEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
{
+ int frame_type = animation.frames[0].type();
+ int frame_depth = animation.frames[0].depth();
+ CV_CheckType(frame_type, frame_depth == CV_8U || frame_depth == CV_16U, "APNG decoder supports only 8 or 16 bit unsigned images");
int compression_level = 6;
int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy
bool isBilevel = false;
@@ -1435,7 +1438,8 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
}
}
- CV_UNUSED(isBilevel);
+ if (isBilevel)
+ CV_LOG_WARNING(NULL, "IMWRITE_PNG_BILEVEL parameter is not supported yet.");
uint32_t first =0;
uint32_t loops= animation.loop_count;
uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp
index ec25f8c610..37b0701c8a 100644
--- a/modules/imgcodecs/src/loadsave.cpp
+++ b/modules/imgcodecs/src/loadsave.cpp
@@ -1372,7 +1372,7 @@ bool imencode( const String& ext, InputArray _img,
else
code = encoder->writemulti(write_vec, params);
- encoder->throwOnEror();
+ encoder->throwOnError();
CV_Assert( code );
}
catch (const cv::Exception& e)
--
2.48.1