From 7481cb50b536e2e35afc3583adf309b56313059c Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov <2536374+asmorkalov@users.noreply.github.com> Date: Wed, 12 Mar 2025 18:10:06 +0300 Subject: [PATCH] Merge pull request #27013 from asmorkalov:as/imencode_animation Test for in-memory animation encoding and decoding #27013 Tests for https://github.com/opencv/opencv/pull/26964 ### 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 - [ ] 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 --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 33 ++++ modules/imgcodecs/src/loadsave.cpp | 142 ++++++++++++++++++ modules/imgcodecs/test/test_animation.cpp | 48 ++++++ 3 files changed, 223 insertions(+) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 458e68483e..5bf850b69a 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -388,6 +388,19 @@ The function imreadanimation loads frames from an animated image file (e.g., GIF */ CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX); +/** @brief Loads frames from an animated image buffer into an Animation structure. + +The function imdecodeanimation loads frames from an animated image buffer (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct. + +@param buf A reference to an InputArray containing the image buffer. +@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called. +@param start The index of the first frame to load. This is optional and defaults to 0. +@param count The number of frames to load. This is optional and defaults to 32767. + +@return Returns true if the buffer was successfully loaded and frames were extracted; returns false otherwise. +*/ +CV_EXPORTS_W bool imdecodeanimation(InputArray buf, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX); + /** @brief Saves an Animation to a specified file. The function imwriteanimation saves the provided Animation data to the specified file in an animated format. @@ -402,6 +415,26 @@ These parameters are used to specify additional options for the encoding process */ CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector& params = std::vector()); +/** @brief Encodes an Animation to a memory buffer. + +The function imencodeanimation encodes the provided Animation data into a memory +buffer in an animated format. Supported formats depend on the implementation and +may include formats like GIF, AVIF, APNG, or WEBP. + +@param ext The file extension that determines the format of the encoded data. +@param animation A constant reference to an Animation struct containing the +frames and metadata to be encoded. +@param buf A reference to a vector of unsigned chars where the encoded data will +be stored. +@param params Optional format-specific parameters encoded as pairs (paramId_1, +paramValue_1, paramId_2, paramValue_2, ...). These parameters are used to +specify additional options for the encoding process. Refer to `cv::ImwriteFlags` +for details on possible parameters. + +@return Returns true if the animation was successfully encoded; returns false otherwise. +*/ +CV_EXPORTS_W bool imencodeanimation(const String& ext, const Animation& animation, CV_OUT std::vector& buf, const std::vector& params = std::vector()); + /** @brief Returns the number of images inside the given file The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise. diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 42693effd9..0bf90f2aa2 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -811,6 +811,116 @@ bool imreadanimation(const String& filename, CV_OUT Animation& animation, int st return imreadanimation_(filename, IMREAD_UNCHANGED, start, count, animation); } +static bool imdecodeanimation_(InputArray buf, int flags, int start, int count, Animation& animation) +{ + bool success = false; + if (start < 0) { + start = 0; + } + if (count < 0) { + count = INT16_MAX; + } + + /// Search for the relevant decoder to handle the imagery + ImageDecoder decoder; + decoder = findDecoder(buf.getMat()); + + /// if no decoder was found, return false. + if (!decoder) { + CV_LOG_WARNING(NULL, "Decoder for buffer not found!\n"); + return false; + } + + /// set the filename in the driver + decoder->setSource(buf.getMat()); + // read the header to make sure it succeeds + try + { + // read the header to make sure it succeeds + if (!decoder->readHeader()) + return false; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imdecodeanimation_(): can't read header: " << e.what()); + return false; + } + catch (...) + { + CV_LOG_ERROR(NULL, "imdecodeanimation_(): can't read header: unknown exception"); + return false; + } + + int current = 0; + int frame_count = (int)decoder->getFrameCount(); + count = count + start > frame_count ? frame_count - start : count; + + uint64 pixels = (uint64)decoder->width() * (uint64)decoder->height() * (uint64)(count + 4); + if (pixels > CV_IO_MAX_IMAGE_PIXELS) { + CV_LOG_WARNING(NULL, "\nyou are trying to read " << pixels << + " bytes that exceed CV_IO_MAX_IMAGE_PIXELS.\n"); + return false; + } + + while (current < start + count) + { + // grab the decoded type + const int type = calcType(decoder->type(), flags); + + // established the required input image size + Size size = validateInputImageSize(Size(decoder->width(), decoder->height())); + + // read the image data + Mat mat(size.height, size.width, type); + success = false; + try + { + if (decoder->readData(mat)) + success = true; + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imreadanimation_: can't read data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imreadanimation_: can't read data: unknown exception"); + } + if (!success) + break; + + // optionally rotate the data if EXIF' orientation flag says so + if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) + { + ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); + } + + if (current >= start) + { + int duration = decoder->animation().durations.size() > 0 ? decoder->animation().durations.back() : 1000; + animation.durations.push_back(duration); + animation.frames.push_back(mat); + } + + if (!decoder->nextPage()) + { + break; + } + ++current; + } + animation.bgcolor = decoder->animation().bgcolor; + animation.loop_count = decoder->animation().loop_count; + + return success; +} + +bool imdecodeanimation(InputArray buf, Animation& animation, int start, int count) +{ + CV_TRACE_FUNCTION(); + + return imdecodeanimation_(buf, IMREAD_UNCHANGED, start, count, animation); +} + static size_t imcount_(const String& filename, int flags) { @@ -994,6 +1104,38 @@ bool imwriteanimation(const String& filename, const Animation& animation, const return imwriteanimation_(filename, animation, params); } +static bool imencodeanimation_(const String& ext, const Animation& animation, std::vector& buf, const std::vector& params) +{ + ImageEncoder encoder = findEncoder(ext); + if (!encoder) + CV_Error(Error::StsError, "could not find a writer for the specified extension"); + + encoder->setDestination(buf); + + bool code = false; + try + { + code = encoder->writeanimation(animation, params); + } + catch (const cv::Exception& e) + { + CV_LOG_ERROR(NULL, "imencodeanimation_('" << ext << "'): can't write data: " << e.what()); + } + catch (...) + { + CV_LOG_ERROR(NULL, "imencodeanimation_('" << ext << "'): can't write data: unknown exception"); + } + + return code; +} + +bool imencodeanimation(const String& ext, const Animation& animation, std::vector& buf, const std::vector& params) +{ + CV_Assert(!animation.frames.empty()); + CV_Assert(animation.frames.size() == animation.durations.size()); + return imencodeanimation_(ext, animation, buf, params); +} + static bool imdecode_( const Mat& buf, int flags, Mat& mat ) { diff --git a/modules/imgcodecs/test/test_animation.cpp b/modules/imgcodecs/test/test_animation.cpp index 5bfb3dc231..e566bcbd49 100644 --- a/modules/imgcodecs/test/test_animation.cpp +++ b/modules/imgcodecs/test/test_animation.cpp @@ -588,6 +588,54 @@ INSTANTIATE_TEST_CASE_P(/**/, Imgcodecs_ImageCollection, testing::ValuesIn(exts_multi)); +TEST(Imgcodecs_APNG, imdecode_animation) +{ + Animation gt_animation, mem_animation; + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/tp1n3p08.png"; + + EXPECT_TRUE(imreadanimation(filename, gt_animation)); + EXPECT_EQ(1000, gt_animation.durations.back()); + + std::vector buf; + readFileBytes(filename, buf); + EXPECT_TRUE(imdecodeanimation(buf, mem_animation)); + + EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size()); + EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor); + EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count); + for (size_t i = 0; i < gt_animation.frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF)); + EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]); + } +} + +TEST(Imgcodecs_APNG, imencode_animation) +{ + Animation gt_animation, mem_animation; + // Set the path to the test image directory and filename for loading. + const string root = cvtest::TS::ptr()->get_data_path(); + const string filename = root + "pngsuite/tp1n3p08.png"; + + EXPECT_TRUE(imreadanimation(filename, gt_animation)); + EXPECT_EQ(1000, gt_animation.durations.back()); + + std::vector buf; + EXPECT_TRUE(imencodeanimation(".png", gt_animation, buf)); + EXPECT_TRUE(imdecodeanimation(buf, mem_animation)); + + EXPECT_EQ(mem_animation.frames.size(), gt_animation.frames.size()); + EXPECT_EQ(mem_animation.bgcolor, gt_animation.bgcolor); + EXPECT_EQ(mem_animation.loop_count, gt_animation.loop_count); + for (size_t i = 0; i < gt_animation.frames.size(); i++) + { + EXPECT_EQ(0, cvtest::norm(mem_animation.frames[i], gt_animation.frames[i], NORM_INF)); + EXPECT_EQ(mem_animation.durations[i], gt_animation.durations[i]); + } +} + #endif // HAVE_PNG }} // namespace