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
This commit is contained in:
Alexander Smorkalov 2025-03-12 18:10:06 +03:00 committed by GitHub
parent bbcdbca872
commit 7481cb50b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 223 additions and 0 deletions

View File

@ -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<int>& params = std::vector<int>());
/** @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<uchar>& buf, const std::vector<int>& params = std::vector<int>());
/** @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.

View File

@ -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<uchar>& buf, const std::vector<int>& 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<uchar>& buf, const std::vector<int>& 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 )
{

View File

@ -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<unsigned char> 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<unsigned char> 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