1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

Merge pull request #25608 from sturkmen72:animated_webp_support

Animated WebP Support #25608

related issues #24855 #22569 

### 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
- [x] 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:
Suleyman TURKMEN
2024-12-20 13:06:28 +03:00
committed by GitHub
parent 0903061589
commit d9a139f9e8
15 changed files with 1006 additions and 123 deletions
+24 -9
View File
@@ -11,6 +11,7 @@
#include <memory>
#include <opencv2/core/utils/configuration.private.hpp>
#include <opencv2/core/utils/logger.hpp>
#include "opencv2/imgproc.hpp"
#include "grfmt_avif.hpp"
@@ -242,6 +243,8 @@ bool AvifDecoder::readData(Mat &img) {
return false;
}
m_animation.durations.push_back(decoder_->imageTiming.durationInTimescales);
if (decoder_->image->exif.size > 0) {
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
}
@@ -297,16 +300,26 @@ bool AvifEncoder::isFormatSupported(int depth) const {
bool AvifEncoder::write(const Mat &img, const std::vector<int> &params) {
std::vector<Mat> img_vec(1, img);
return writeToOutput(img_vec, params);
return writemulti(img_vec, params);
}
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec,
const std::vector<int> &params) {
return writeToOutput(img_vec, params);
CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration.");
Animation animation;
animation.frames = img_vec;
for (size_t i = 0; i < animation.frames.size(); i++)
{
animation.durations.push_back(1000);
}
return writeanimation(animation, params);
}
bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
const std::vector<int> &params) {
bool AvifEncoder::writeanimation(const Animation& animation,
const std::vector<int> &params) {
int bit_depth = 8;
int speed = AVIF_SPEED_FASTEST;
for (size_t i = 0; i < params.size(); i += 2) {
@@ -340,12 +353,12 @@ bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
#endif
encoder_->speed = speed;
const avifAddImageFlags flag = (img_vec.size() == 1)
const avifAddImageFlags flag = (animation.frames.size() == 1)
? AVIF_ADD_IMAGE_FLAG_SINGLE
: AVIF_ADD_IMAGE_FLAG_NONE;
std::vector<AvifImageUniquePtr> images;
std::vector<cv::Mat> imgs_scaled;
for (const cv::Mat &img : img_vec) {
for (const cv::Mat &img : animation.frames) {
CV_CheckType(
img.type(),
(bit_depth == 8 && img.depth() == CV_8U) ||
@@ -358,13 +371,15 @@ bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth));
}
for (const AvifImageUniquePtr &image : images) {
for (size_t i = 0; i < images.size(); i++)
{
OPENCV_AVIF_CHECK_STATUS(
avifEncoderAddImage(encoder_, image.get(), /*durationInTimescale=*/1,
flag),
avifEncoderAddImage(encoder_, images[i].get(), animation.durations[i], flag),
encoder_);
}
encoder_->timescale = 1000;
OPENCV_AVIF_CHECK_STATUS(avifEncoderFinish(encoder_, output.get()), encoder_);
if (m_buf) {
+1 -2
View File
@@ -46,12 +46,11 @@ class AvifEncoder CV_FINAL : public BaseImageEncoder {
bool writemulti(const std::vector<Mat>& img_vec,
const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
private:
bool writeToOutput(const std::vector<Mat>& img_vec,
const std::vector<int>& params);
avifEncoder* encoder_;
};
+5
View File
@@ -144,6 +144,11 @@ bool BaseImageEncoder::writemulti(const std::vector<Mat>&, const std::vector<int
return false;
}
bool BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
{
return false;
}
ImageEncoder BaseImageEncoder::newEncoder() const
{
return ImageEncoder();
+5
View File
@@ -133,6 +133,8 @@ public:
*/
virtual bool checkSignature(const String& signature) const;
const Animation& animation() const { return m_animation; };
/**
* @brief Create and return a new instance of the derived image decoder.
* @return A new ImageDecoder object.
@@ -151,6 +153,7 @@ protected:
bool m_use_rgb; ///< Flag indicating whether to decode the image in RGB order.
ExifReader m_exif; ///< Object for reading EXIF metadata from the image.
size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images).
Animation m_animation;
};
@@ -215,6 +218,8 @@ public:
*/
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);
virtual bool writeanimation(const Animation& animation, const std::vector<int>& params);
/**
* @brief Get a description of the image encoder (e.g., the format it supports).
* @return A string describing the encoder.
+266 -81
View File
@@ -44,17 +44,15 @@
#include "precomp.hpp"
#include <webp/decode.h>
#include <webp/encode.h>
#include <stdio.h>
#include <limits.h>
#include "grfmt_webp.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/core/utils/configuration.private.hpp>
#include <webp/decode.h>
#include <webp/encode.h>
#include <webp/demux.h>
#include <webp/mux.h>
namespace cv
{
@@ -67,12 +65,18 @@ static const size_t WEBP_HEADER_SIZE = 32;
WebPDecoder::WebPDecoder()
{
m_buf_supported = true;
channels = 0;
fs_size = 0;
m_has_animation = false;
m_previous_timestamp = 0;
}
WebPDecoder::~WebPDecoder() {}
void WebPDecoder::UniquePtrDeleter::operator()(WebPAnimDecoder* decoder) const
{
WebPAnimDecoderDelete(decoder);
}
size_t WebPDecoder::signatureLength() const
{
return WEBP_HEADER_SIZE;
@@ -102,6 +106,11 @@ ImageDecoder WebPDecoder::newDecoder() const
bool WebPDecoder::readHeader()
{
if (m_has_animation)
{
return true;
}
uint8_t header[WEBP_HEADER_SIZE] = { 0 };
if (m_buf.empty())
{
@@ -124,28 +133,49 @@ bool WebPDecoder::readHeader()
}
WebPBitstreamFeatures features;
if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features))
if (VP8_STATUS_OK < WebPGetFeatures(header, sizeof(header), &features)) return false;
m_has_animation = features.has_animation == 1;
if (m_has_animation)
{
CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images");
m_width = features.width;
m_height = features.height;
if (features.has_alpha)
if (m_buf.empty())
{
m_type = CV_8UC4;
channels = 4;
}
else
{
m_type = CV_8UC3;
channels = 3;
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
data.create(1, validateToInt(fs_size), CV_8UC1);
fs.read((char*)data.ptr(), fs_size);
CV_Assert(fs && "Can't read file data");
fs.close();
}
return true;
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
WebPData webp_data;
webp_data.bytes = (const uint8_t*)data.ptr();
webp_data.size = data.total();
WebPAnimDecoderOptions dec_options;
WebPAnimDecoderOptionsInit(&dec_options);
dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA;
anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options));
CV_Assert(anim_decoder.get() && "Error parsing image");
WebPAnimInfo anim_info;
WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info);
m_animation.loop_count = anim_info.loop_count;
m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF;
m_animation.bgcolor[1] = (anim_info.bgcolor >> 16) & 0xFF;
m_animation.bgcolor[2] = (anim_info.bgcolor >> 8) & 0xFF;
m_animation.bgcolor[3] = anim_info.bgcolor & 0xFF;
m_frame_count = anim_info.frame_count;
}
m_width = features.width;
m_height = features.height;
m_type = features.has_alpha ? CV_8UC4 : CV_8UC3;
return false;
return true;
}
bool WebPDecoder::readData(Mat &img)
@@ -155,7 +185,7 @@ bool WebPDecoder::readData(Mat &img)
CV_CheckEQ(img.cols, m_width, "");
CV_CheckEQ(img.rows, m_height, "");
if (m_buf.empty())
if (data.empty())
{
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
data.create(1, validateToInt(fs_size), CV_8UC1);
@@ -165,70 +195,96 @@ bool WebPDecoder::readData(Mat &img)
}
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
Mat read_img;
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
if (img.type() != m_type || img.cols != m_width || img.rows != m_height)
{
Mat read_img;
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
if (img.type() != m_type)
read_img.create(m_height, m_width, m_type);
}
else
{
read_img = img; // copy header
}
uchar* out_data = read_img.ptr();
size_t out_data_size = read_img.dataend - out_data;
uchar* res_ptr = NULL;
if (m_has_animation)
{
uint8_t* buf;
int timestamp;
WebPAnimDecoderGetNext(anim_decoder.get(), &buf, &timestamp);
Mat tmp(Size(m_width, m_height), CV_8UC4, buf);
if (img.type() == CV_8UC1)
{
read_img.create(m_height, m_width, m_type);
cvtColor(tmp, img, COLOR_BGR2GRAY);
}
else
if (img.type() == CV_8UC3)
{
read_img = img; // copy header
}
uchar* out_data = read_img.ptr();
size_t out_data_size = read_img.dataend - out_data;
uchar *res_ptr = NULL;
if (channels == 3)
{
CV_CheckTypeEQ(read_img.type(), CV_8UC3, "");
if (m_use_rgb)
res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
else
res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
}
else if (channels == 4)
{
CV_CheckTypeEQ(read_img.type(), CV_8UC4, "");
if (m_use_rgb)
res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
else
res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
}
if (res_ptr != out_data)
return false;
if (read_img.data == img.data && img.type() == m_type)
{
// nothing
}
else if (img.type() == CV_8UC1)
{
cvtColor(read_img, img, COLOR_BGR2GRAY);
}
else if (img.type() == CV_8UC3 && m_type == CV_8UC4)
{
cvtColor(read_img, img, COLOR_BGRA2BGR);
}
else if (img.type() == CV_8UC4 && m_type == CV_8UC3)
{
cvtColor(read_img, img, COLOR_BGR2BGRA);
cvtColor(tmp, img, COLOR_BGRA2BGR);
}
else
{
CV_Error(Error::StsInternal, "");
}
tmp.copyTo(img);
m_animation.durations.push_back(timestamp - m_previous_timestamp);
m_previous_timestamp = timestamp;
return true;
}
if (m_type == CV_8UC3)
{
CV_CheckTypeEQ(read_img.type(), CV_8UC3, "");
if (m_use_rgb)
res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
else
res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
}
else if (m_type == CV_8UC4)
{
CV_CheckTypeEQ(read_img.type(), CV_8UC4, "");
if (m_use_rgb)
res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
else
res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data,
(int)out_data_size, (int)read_img.step);
}
if (res_ptr != out_data)
return false;
if (read_img.data == img.data && img.type() == m_type)
{
// nothing
}
else if (img.type() == CV_8UC1)
{
cvtColor(read_img, img, COLOR_BGR2GRAY);
}
else if (img.type() == CV_8UC3 && m_type == CV_8UC4)
{
cvtColor(read_img, img, COLOR_BGRA2BGR);
}
else
{
CV_Error(Error::StsInternal, "");
}
return true;
}
bool WebPDecoder::nextPage()
{
// Prepare the next page, if any.
return WebPAnimDecoderHasMoreFrames(anim_decoder.get()) > 0;
}
WebPEncoder::WebPEncoder()
{
m_description = "WebP files (*.webp)";
@@ -312,23 +368,152 @@ bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
#endif
CV_Assert(size > 0);
size_t bytes_written = 0;
if (m_buf)
{
m_buf->resize(size);
memcpy(&(*m_buf)[0], out, size);
bytes_written = size;
}
else
{
FILE *fd = fopen(m_filename.c_str(), "wb");
if (fd != NULL)
{
fwrite(out, size, sizeof(uint8_t), fd);
bytes_written = fwrite(out, sizeof(uint8_t), size, fd);
if (size != bytes_written)
{
CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, size));
}
fclose(fd); fd = NULL;
}
}
return size > 0;
return (size > 0) && (bytes_written == size);
}
bool WebPEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
{
CV_LOG_INFO(NULL, "Multi page image will be written as animation with 1 second frame duration.");
Animation animation;
animation.frames = img_vec;
for (size_t i = 0; i < animation.frames.size(); i++)
{
animation.durations.push_back(1000);
}
return writeanimation(animation, params);
}
bool WebPEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
{
CV_CheckDepthEQ(animation.frames[0].depth(), CV_8U, "WebP codec supports only 8-bit unsigned images");
int ok = 0;
int timestamp = 0;
const int width = animation.frames[0].cols, height = animation.frames[0].rows;
WebPAnimEncoderOptions anim_config;
WebPConfig config;
WebPPicture pic;
WebPData webp_data;
WebPDataInit(&webp_data);
if (!WebPAnimEncoderOptionsInit(&anim_config) ||
!WebPConfigInit(&config) ||
!WebPPictureInit(&pic)) {
CV_LOG_ERROR(NULL, "Library version mismatch!\n");
WebPDataClear(&webp_data);
return false;
}
int bgvalue = (static_cast<int>(animation.bgcolor[0]) & 0xFF) << 24 |
(static_cast<int>(animation.bgcolor[1]) & 0xFF) << 16 |
(static_cast<int>(animation.bgcolor[2]) & 0xFF) << 8 |
(static_cast<int>(animation.bgcolor[3]) & 0xFF);
anim_config.anim_params.bgcolor = bgvalue;
anim_config.anim_params.loop_count = animation.loop_count;
if (params.size() > 1)
{
if (params[0] == IMWRITE_WEBP_QUALITY)
{
config.lossless = 0;
config.quality = static_cast<float>(params[1]);
if (config.quality < 1.0f)
{
config.quality = 1.0f;
}
if (config.quality >= 100.0f)
{
config.lossless = 1;
}
}
anim_config.minimize_size = 0;
}
std::unique_ptr<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> anim_encoder(
WebPAnimEncoderNew(width, height, &anim_config), WebPAnimEncoderDelete);
pic.width = width;
pic.height = height;
pic.use_argb = 1;
pic.argb_stride = width;
WebPEncode(&config, &pic);
bool is_input_rgba = animation.frames[0].channels() == 4;
Size canvas_size = Size(animation.frames[0].cols,animation.frames[0].rows);
for (size_t i = 0; i < animation.frames.size(); i++)
{
Mat argb;
CV_Assert(canvas_size == Size(animation.frames[i].cols,animation.frames[i].rows));
if (is_input_rgba)
pic.argb = (uint32_t*)animation.frames[i].data;
else
{
cvtColor(animation.frames[i], argb, COLOR_BGR2BGRA);
pic.argb = (uint32_t*)argb.data;
}
ok = WebPAnimEncoderAdd(anim_encoder.get(), &pic, timestamp, &config);
timestamp += animation.durations[i];
}
// add a last fake frame to signal the last duration
ok = ok & WebPAnimEncoderAdd(anim_encoder.get(), NULL, timestamp, NULL);
ok = ok & WebPAnimEncoderAssemble(anim_encoder.get(), &webp_data);
size_t bytes_written = 0;
if (ok)
{
if (m_buf)
{
m_buf->resize(webp_data.size);
memcpy(&(*m_buf)[0], webp_data.bytes, webp_data.size);
bytes_written = webp_data.size;
}
else
{
FILE* fd = fopen(m_filename.c_str(), "wb");
if (fd != NULL)
{
bytes_written = fwrite(webp_data.bytes, sizeof(uint8_t), webp_data.size, fd);
if (webp_data.size != bytes_written)
{
CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, webp_data.size));
}
fclose(fd); fd = NULL;
}
}
}
bool status = (ok > 0) && (webp_data.size == bytes_written);
// free resources
WebPDataClear(&webp_data);
return status;
}
}
+12 -1
View File
@@ -49,6 +49,8 @@
#include <fstream>
struct WebPAnimDecoder;
namespace cv
{
@@ -61,6 +63,7 @@ public:
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
bool nextPage() CV_OVERRIDE;
size_t signatureLength() const CV_OVERRIDE;
bool checkSignature( const String& signature) const CV_OVERRIDE;
@@ -68,10 +71,16 @@ public:
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
struct UniquePtrDeleter {
void operator()(WebPAnimDecoder* decoder) const;
};
std::ifstream fs;
size_t fs_size;
Mat data;
int channels;
std::unique_ptr<WebPAnimDecoder, UniquePtrDeleter> anim_decoder;
bool m_has_animation;
int m_previous_timestamp;
};
class WebPEncoder CV_FINAL : public BaseImageEncoder
@@ -81,6 +90,8 @@ public:
~WebPEncoder() CV_OVERRIDE;
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
};
+166
View File
@@ -689,6 +689,116 @@ bool imreadmulti(const String& filename, std::vector<Mat>& mats, int start, int
return imreadmulti_(filename, flags, mats, start, count);
}
static bool
imreadanimation_(const String& filename, 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(filename);
/// if no decoder was found, return false.
if (!decoder) {
CV_LOG_WARNING(NULL, "Decoder for " << filename << " not found!\n");
return false;
}
/// set the filename in the driver
decoder->setSource(filename);
// 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, "imreadanimation_('" << filename << "'): can't read header: " << e.what());
return false;
}
catch (...)
{
CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): 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_('" << filename << "'): can't read data: " << e.what());
}
catch (...)
{
CV_LOG_ERROR(NULL, "imreadanimation_('" << filename << "'): 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)
{
animation.durations.push_back(decoder->animation().durations[decoder->animation().durations.size() - 1]);
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 imreadanimation(const String& filename, CV_OUT Animation& animation, int start, int count)
{
CV_TRACE_FUNCTION();
return imreadanimation_(filename, IMREAD_UNCHANGED, start, count, animation);
}
static
size_t imcount_(const String& filename, int flags)
{
@@ -823,6 +933,55 @@ bool imwrite( const String& filename, InputArray _img,
return imwrite_(filename, img_vec, params, false);
}
static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector<int>& params)
{
ImageEncoder encoder = findEncoder(filename);
if (!encoder)
CV_Error(Error::StsError, "could not find a writer for the specified extension");
encoder->setDestination(filename);
bool code = false;
try
{
code = encoder->writeanimation(animation, params);
if (!code)
{
FILE* f = fopen(filename.c_str(), "wb");
if (!f)
{
if (errno == EACCES)
{
CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't open file for writing: permission denied");
}
}
else
{
fclose(f);
remove(filename.c_str());
}
}
}
catch (const cv::Exception& e)
{
CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: " << e.what());
}
catch (...)
{
CV_LOG_ERROR(NULL, "imwriteanimation_('" << filename << "'): can't write data: unknown exception");
}
return code;
}
bool imwriteanimation(const String& filename, const Animation& animation, const std::vector<int>& params)
{
CV_Assert(!animation.frames.empty());
CV_Assert(animation.frames.size() == animation.durations.size());
return imwriteanimation_(filename, animation, params);
}
static bool
imdecode_( const Mat& buf, int flags, Mat& mat )
{
@@ -1468,6 +1627,13 @@ ImageCollection::iterator ImageCollection::iterator::operator++(int) {
return tmp;
}
Animation::Animation(int loopCount, Scalar bgColor)
: loop_count(loopCount), bgcolor(bgColor)
{
if (loopCount < 0 || loopCount > 0xffff)
this->loop_count = 0; // loop_count should be non-negative
}
}
/* End of file. */