mirror of
https://github.com/opencv/opencv.git
synced 2025-08-05 22:19:14 +08:00
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:
parent
bbcdbca872
commit
7481cb50b5
@ -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.
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user