mirror of
https://github.com/opencv/opencv.git
synced 2025-08-01 02:18:01 +08:00
Merge pull request #26688 from sturkmen72:gif-png-webp-avif
Animated GIF APNG WEBP AVIF revisions
This commit is contained in:
commit
ff18c9cc79
@ -303,21 +303,6 @@ bool AvifEncoder::write(const Mat &img, const std::vector<int> ¶ms) {
|
||||
return writemulti(img_vec, params);
|
||||
}
|
||||
|
||||
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec,
|
||||
const std::vector<int> ¶ms) {
|
||||
|
||||
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::writeanimation(const Animation& animation,
|
||||
const std::vector<int> ¶ms) {
|
||||
int bit_depth = 8;
|
||||
|
@ -41,11 +41,7 @@ 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 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;
|
||||
|
@ -43,6 +43,7 @@
|
||||
|
||||
#include "grfmt_base.hpp"
|
||||
#include "bitstrm.hpp"
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
@ -139,9 +140,19 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::writemulti(const std::vector<Mat>&, const std::vector<int>& )
|
||||
bool BaseImageEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
|
||||
{
|
||||
return false;
|
||||
if(img_vec.size() > 1)
|
||||
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 BaseImageEncoder::writeanimation(const Animation&, const std::vector<int>& )
|
||||
|
@ -219,7 +219,7 @@ void GifDecoder::readExtensions() {
|
||||
len = (uchar)m_strm.getByte();
|
||||
CV_Assert(len == 4);
|
||||
auto flags = (uchar)m_strm.getByte();
|
||||
m_strm.getWord(); // delay time, not used
|
||||
m_animation.durations.push_back(m_strm.getWord() * 10); // delay time
|
||||
opMode = (GifOpMode)((flags & 0x1C) >> 2);
|
||||
hasTransparentColor = flags & 0x01;
|
||||
transparentColor = (uchar)m_strm.getByte();
|
||||
@ -407,6 +407,10 @@ bool GifDecoder::getFrameCount_() {
|
||||
while (len) {
|
||||
m_strm.skip(len);
|
||||
len = m_strm.getByte();
|
||||
if (len == 3 && m_strm.getByte() == 1)
|
||||
{
|
||||
m_animation.loop_count = m_strm.getWord();
|
||||
}
|
||||
}
|
||||
} else if (!(type ^ 0x2C)) {
|
||||
// skip image data
|
||||
@ -490,16 +494,11 @@ bool GifEncoder::isFormatSupported(int depth) const {
|
||||
|
||||
bool GifEncoder::write(const Mat &img, const std::vector<int> ¶ms) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
return writeFrames(img_vec, params);
|
||||
return writemulti(img_vec, params);
|
||||
}
|
||||
|
||||
bool GifEncoder::writemulti(const std::vector<Mat> &img_vec, const std::vector<int> ¶ms) {
|
||||
return writeFrames(img_vec, params);
|
||||
}
|
||||
|
||||
bool GifEncoder::writeFrames(const std::vector<Mat>& img_vec,
|
||||
const std::vector<int>& params) {
|
||||
if (img_vec.empty()) {
|
||||
bool GifEncoder::writeanimation(const Animation& animation, const std::vector<int>& params) {
|
||||
if (animation.frames.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -511,6 +510,8 @@ bool GifEncoder::writeFrames(const std::vector<Mat>& img_vec,
|
||||
return false;
|
||||
}
|
||||
|
||||
loopCount = animation.loop_count;
|
||||
|
||||
// confirm the params
|
||||
for (size_t i = 0; i < params.size(); i += 2) {
|
||||
switch (params[i]) {
|
||||
@ -561,13 +562,13 @@ bool GifEncoder::writeFrames(const std::vector<Mat>& img_vec,
|
||||
if (fast) {
|
||||
const uchar transparent = 0x92; // 1001_0010: the middle of the color table
|
||||
if (dithering == GRFMT_GIF_None) {
|
||||
img_vec_ = img_vec;
|
||||
img_vec_ = animation.frames;
|
||||
transparentColor = transparent;
|
||||
} else {
|
||||
localColorTableSize = 0;
|
||||
int transRGB;
|
||||
const int depth = 3 << 8 | 3 << 4 | 2; // r:g:b = 3:3:2
|
||||
for (auto &img: img_vec) {
|
||||
for (auto &img: animation.frames) {
|
||||
Mat img_(img.size(), img.type());
|
||||
transRGB = ditheringKernel(img, img_, depth, criticalTransparency);
|
||||
if (transRGB >= 0) {
|
||||
@ -583,13 +584,13 @@ bool GifEncoder::writeFrames(const std::vector<Mat>& img_vec,
|
||||
} else if (dithering != GRFMT_GIF_None) {
|
||||
int depth = (int)floor(log2(colorNum) / 3) + dithering;
|
||||
depth = depth << 8 | depth << 4 | depth;
|
||||
for (auto &img : img_vec) {
|
||||
for (auto &img : animation.frames) {
|
||||
Mat img_(img.size(), img.type());
|
||||
ditheringKernel(img, img_, depth, criticalTransparency);
|
||||
img_vec_.push_back(img_);
|
||||
}
|
||||
} else {
|
||||
img_vec_ = img_vec;
|
||||
img_vec_ = animation.frames;
|
||||
}
|
||||
bool result = writeHeader(img_vec_);
|
||||
if (!result) {
|
||||
@ -597,8 +598,9 @@ bool GifEncoder::writeFrames(const std::vector<Mat>& img_vec,
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &img : img_vec_) {
|
||||
result = writeFrame(img);
|
||||
for (size_t i = 0; i < img_vec_.size(); i++) {
|
||||
frameDelay = cvRound(animation.durations[i] / 10);
|
||||
result = writeFrame(img_vec_[i]);
|
||||
}
|
||||
|
||||
strm.putByte(0x3B); // trailer
|
||||
|
@ -86,9 +86,7 @@ public:
|
||||
bool isFormatSupported(int depth) const 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;
|
||||
|
||||
|
@ -152,8 +152,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen)
|
||||
|
||||
if (!src.empty())
|
||||
{
|
||||
png_uint_32 rowbytes = src.cols * src.channels();
|
||||
|
||||
png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels();
|
||||
_width = src.cols;
|
||||
_height = src.rows;
|
||||
_colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
@ -389,7 +388,7 @@ bool PngDecoder::readData( Mat& img )
|
||||
{
|
||||
if (m_frame_count > 1)
|
||||
{
|
||||
Mat mat_cur = Mat(img.rows, img.cols, m_type);
|
||||
Mat mat_cur = Mat::zeros(img.rows, img.cols, m_type);
|
||||
uint32_t id = 0;
|
||||
uint32_t j = 0;
|
||||
uint32_t imagesize = m_width * m_height * mat_cur.channels();
|
||||
@ -437,7 +436,7 @@ bool PngDecoder::readData( Mat& img )
|
||||
if (dop == 2)
|
||||
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
||||
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur.channels());
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
||||
if (delay_den < 1000)
|
||||
delay_num = cvRound(1000.0 / delay_den);
|
||||
m_animation.durations.push_back(delay_num);
|
||||
@ -495,7 +494,7 @@ bool PngDecoder::readData( Mat& img )
|
||||
{
|
||||
if (processing_finish())
|
||||
{
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur.channels());
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
||||
if (delay_den < 1000)
|
||||
delay_num = cvRound(1000.0 / delay_den);
|
||||
m_animation.durations.push_back(delay_num);
|
||||
@ -606,42 +605,82 @@ bool PngDecoder::nextPage() {
|
||||
return ++m_frame_no < (int)m_frame_count;
|
||||
}
|
||||
|
||||
void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels)
|
||||
void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char _bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img)
|
||||
{
|
||||
uint32_t i, j;
|
||||
int u, v, al;
|
||||
int channels = img.channels();
|
||||
if (img.depth() == CV_16U)
|
||||
cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) {
|
||||
for (int j = range.start; j < range.end; j++) {
|
||||
uint16_t* sp = reinterpret_cast<uint16_t*>(rows_src[j]);
|
||||
uint16_t* dp = reinterpret_cast<uint16_t*>(rows_dst[j + y]) + x * channels;
|
||||
|
||||
for (j = 0; j < h; j++)
|
||||
{
|
||||
if (_bop == 0) {
|
||||
// Overwrite mode: copy source row directly to destination
|
||||
memcpy(dp, sp, w * channels * sizeof(uint16_t));
|
||||
}
|
||||
else {
|
||||
// Blending mode
|
||||
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
||||
if (sp[3] == 65535) { // Fully opaque in 16-bit (max value)
|
||||
memcpy(dp, sp, channels * sizeof(uint16_t));
|
||||
}
|
||||
else if (sp[3] != 0) { // Partially transparent
|
||||
if (dp[3] != 0) { // Both source and destination have alpha
|
||||
uint32_t u = sp[3] * 65535; // 16-bit max
|
||||
uint32_t v = (65535 - sp[3]) * dp[3];
|
||||
uint32_t al = u + v;
|
||||
dp[0] = static_cast<uint16_t>((sp[0] * u + dp[0] * v) / al); // Red
|
||||
dp[1] = static_cast<uint16_t>((sp[1] * u + dp[1] * v) / al); // Green
|
||||
dp[2] = static_cast<uint16_t>((sp[2] * u + dp[2] * v) / al); // Blue
|
||||
dp[3] = static_cast<uint16_t>(al / 65535); // Alpha
|
||||
}
|
||||
else {
|
||||
// If destination alpha is 0, copy source pixel
|
||||
memcpy(dp, sp, channels * sizeof(uint16_t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
else
|
||||
cv::parallel_for_(cv::Range(0, h), [&](const cv::Range& range) {
|
||||
for (int j = range.start; j < range.end; j++) {
|
||||
unsigned char* sp = rows_src[j];
|
||||
unsigned char* dp = rows_dst[j + y] + x * channels;
|
||||
|
||||
if (_bop == 0)
|
||||
if (_bop == 0) {
|
||||
// Overwrite mode: copy source row directly to destination
|
||||
memcpy(dp, sp, w * channels);
|
||||
else
|
||||
for (i = 0; i < w; i++, sp += 4, dp += 4)
|
||||
{
|
||||
if (sp[3] == 255)
|
||||
memcpy(dp, sp, 4);
|
||||
else
|
||||
if (sp[3] != 0)
|
||||
{
|
||||
if (dp[3] != 0)
|
||||
{
|
||||
u = sp[3] * 255;
|
||||
v = (255 - sp[3]) * dp[3];
|
||||
al = u + v;
|
||||
dp[0] = (sp[0] * u + dp[0] * v) / al;
|
||||
dp[1] = (sp[1] * u + dp[1] * v) / al;
|
||||
dp[2] = (sp[2] * u + dp[2] * v) / al;
|
||||
dp[3] = al / 255;
|
||||
}
|
||||
else
|
||||
memcpy(dp, sp, 4);
|
||||
else {
|
||||
// Blending mode
|
||||
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
||||
if (sp[3] == 255) {
|
||||
// Fully opaque: copy source pixel directly
|
||||
memcpy(dp, sp, channels);
|
||||
}
|
||||
else if (sp[3] != 0) {
|
||||
// Alpha blending
|
||||
if (dp[3] != 0) {
|
||||
int u = sp[3] * 255;
|
||||
int v = (255 - sp[3]) * dp[3];
|
||||
int al = u + v;
|
||||
dp[0] = (sp[0] * u + dp[0] * v) / al; // Red
|
||||
dp[1] = (sp[1] * u + dp[1] * v) / al; // Green
|
||||
dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue
|
||||
dp[3] = al / 255; // Alpha
|
||||
}
|
||||
else {
|
||||
// If destination alpha is 0, copy source pixel
|
||||
memcpy(dp, sp, channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
size_t PngDecoder::read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount)
|
||||
{
|
||||
@ -742,7 +781,6 @@ bool PngDecoder::processing_finish()
|
||||
void PngDecoder::info_fn(png_structp png_ptr, png_infop info_ptr)
|
||||
{
|
||||
png_set_expand(png_ptr);
|
||||
png_set_strip_16(png_ptr);
|
||||
(void)png_set_interlace_handling(png_ptr);
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
}
|
||||
@ -1352,21 +1390,6 @@ void PngEncoder::deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, i
|
||||
deflateEnd(&fin_zstream);
|
||||
}
|
||||
|
||||
bool PngEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
|
||||
{
|
||||
CV_Assert(img_vec[0].depth() == CV_8U);
|
||||
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 PngEncoder::writeanimation(const Animation& animation, const std::vector<int>& params)
|
||||
{
|
||||
int compression_level = 6;
|
||||
@ -1433,6 +1456,8 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
if (animation.frames[i].channels() == 3)
|
||||
cvtColor(animation.frames[i], tmpframes[i], COLOR_BGR2RGB);
|
||||
|
||||
if (tmpframes[i].depth() != CV_8U)
|
||||
tmpframes[i].convertTo(tmpframes[i], CV_8U, 1.0 / 255);
|
||||
apngFrame.setMat(tmpframes[i], animation.durations[i]);
|
||||
|
||||
if (i > 0 && !getRect(width, height, frames.back().getPixels(), apngFrame.getPixels(), over1.data(), bpp, rowbytes, 0, 0, 0, 3))
|
||||
|
@ -136,7 +136,7 @@ protected:
|
||||
static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass);
|
||||
bool processing_start(void* frame_ptr, const Mat& img);
|
||||
bool processing_finish();
|
||||
void compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels);
|
||||
void compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img);
|
||||
size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount);
|
||||
uint32_t read_chunk(Chunk& chunk);
|
||||
|
||||
@ -176,7 +176,6 @@ public:
|
||||
|
||||
bool isFormatSupported( int depth ) const 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& animinfo, const std::vector<int>& params) CV_OVERRIDE;
|
||||
|
||||
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||
|
@ -392,20 +392,6 @@ bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
|
||||
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");
|
||||
|
@ -90,7 +90,6 @@ 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;
|
||||
|
@ -99,6 +99,61 @@ static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14)
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef HAVE_IMGCODEC_GIF
|
||||
|
||||
TEST(Imgcodecs_Gif, imwriteanimation_rgba)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
s_animation.bgcolor = Scalar(0, 0, 0, 0); // TO DO not implemented yet.
|
||||
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".gif");
|
||||
|
||||
// Write the animation to a .webp file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size();
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
// Check that the background color and loop count match between saved and loaded animations.
|
||||
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor); // written as BGRA order
|
||||
EXPECT_EQ(l_animation.loop_count, s_animation.loop_count);
|
||||
|
||||
// Verify that the durations of frames match.
|
||||
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||
EXPECT_EQ(cvRound(s_animation.durations[i] / 10), cvRound(l_animation.durations[i] / 10));
|
||||
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
||||
EXPECT_EQ(expected_frame_count + 3, l_animation.frames.size());
|
||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[16], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[17], NORM_INF));
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[18], NORM_INF));
|
||||
|
||||
// Verify whether the imread function successfully loads the first frame
|
||||
Mat frame = imread(output, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[0], frame, NORM_INF));
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> webp_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
#endif // HAVE_IMGCODEC_GIF
|
||||
|
||||
#ifdef HAVE_WEBP
|
||||
|
||||
TEST(Imgcodecs_WebP, imwriteanimation_rgba)
|
||||
@ -305,6 +360,51 @@ TEST(Imgcodecs_APNG, imwriteanimation_rgba)
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgba16u)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||
|
||||
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||
{
|
||||
s_animation.frames[i].convertTo(s_animation.frames[i], CV_16U, 255);
|
||||
}
|
||||
// Create a temporary output filename for saving the animation.
|
||||
string output = cv::tempfile(".png");
|
||||
|
||||
// Write the animation to a .png file and verify success.
|
||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||
|
||||
// Read the animation back and compare with the original.
|
||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||
|
||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
||||
|
||||
// Verify that the number of frames matches the expected count.
|
||||
EXPECT_EQ(expected_frame_count, imcount(output));
|
||||
EXPECT_EQ(expected_frame_count, l_animation.frames.size());
|
||||
|
||||
std::vector<uchar> buf;
|
||||
readFileBytes(output, buf);
|
||||
vector<Mat> apng_frames;
|
||||
|
||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
apng_frames.clear();
|
||||
// Test saving the animation frames as individual still images.
|
||||
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||
|
||||
// Read back the still images into a vector of Mats.
|
||||
EXPECT_TRUE(imreadmulti(output, apng_frames));
|
||||
|
||||
// Expect all frames written as multi-page image
|
||||
EXPECT_EQ(expected_frame_count, apng_frames.size());
|
||||
|
||||
// Clean up by removing the temporary file.
|
||||
EXPECT_EQ(0, remove(output.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, imwriteanimation_rgb)
|
||||
{
|
||||
Animation s_animation, l_animation;
|
||||
|
Loading…
Reference in New Issue
Block a user