mirror of
https://github.com/zebrajr/opencv.git
synced 2025-12-06 12:19:50 +01:00
Merge pull request #25715 from sturkmen72:apng_support
Animated PNG Support #25715 Continues https://github.com/opencv/opencv/pull/25608 ### 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 - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
d39aae6bdf
commit
8bc65a1d13
|
|
@ -243,7 +243,7 @@ bool AvifDecoder::readData(Mat &img) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_animation.durations.push_back(decoder_->imageTiming.durationInTimescales);
|
m_animation.durations.push_back(decoder_->imageTiming.duration * 1000);
|
||||||
|
|
||||||
if (decoder_->image->exif.size > 0) {
|
if (decoder_->image->exif.size > 0) {
|
||||||
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
|
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -47,26 +47,98 @@
|
||||||
|
|
||||||
#include "grfmt_base.hpp"
|
#include "grfmt_base.hpp"
|
||||||
#include "bitstrm.hpp"
|
#include "bitstrm.hpp"
|
||||||
|
#include <png.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
namespace cv
|
namespace cv
|
||||||
{
|
{
|
||||||
|
|
||||||
|
struct Chunk { unsigned char* p; uint32_t size; };
|
||||||
|
struct OP { unsigned char* p; uint32_t size; int x, y, w, h, valid, filters; };
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char r, g, b;
|
||||||
|
} rgb;
|
||||||
|
|
||||||
|
class APNGFrame {
|
||||||
|
public:
|
||||||
|
|
||||||
|
APNGFrame();
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~APNGFrame();
|
||||||
|
|
||||||
|
bool setMat(const cv::Mat& src, unsigned delayNum = 1, unsigned delayDen = 1000);
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
unsigned char* getPixels() const { return _pixels; }
|
||||||
|
void setPixels(unsigned char* pixels);
|
||||||
|
|
||||||
|
unsigned int getWidth() const { return _width; }
|
||||||
|
void setWidth(unsigned int width);
|
||||||
|
|
||||||
|
unsigned int getHeight() const { return _height; }
|
||||||
|
void setHeight(unsigned int height);
|
||||||
|
|
||||||
|
unsigned char getColorType() const { return _colorType; }
|
||||||
|
void setColorType(unsigned char colorType);
|
||||||
|
|
||||||
|
rgb* getPalette() { return _palette; }
|
||||||
|
void setPalette(const rgb* palette);
|
||||||
|
|
||||||
|
unsigned char* getTransparency() { return _transparency; }
|
||||||
|
void setTransparency(const unsigned char* transparency);
|
||||||
|
|
||||||
|
int getPaletteSize() const { return _paletteSize; }
|
||||||
|
void setPaletteSize(int paletteSize);
|
||||||
|
|
||||||
|
int getTransparencySize() const { return _transparencySize; }
|
||||||
|
void setTransparencySize(int transparencySize);
|
||||||
|
|
||||||
|
unsigned int getDelayNum() const { return _delayNum; }
|
||||||
|
void setDelayNum(unsigned int delayNum);
|
||||||
|
|
||||||
|
unsigned int getDelayDen() const { return _delayDen; }
|
||||||
|
void setDelayDen(unsigned int delayDen);
|
||||||
|
|
||||||
|
unsigned char** getRows() const { return _rows; }
|
||||||
|
void setRows(unsigned char** rows);
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char* _pixels;
|
||||||
|
unsigned int _width;
|
||||||
|
unsigned int _height;
|
||||||
|
unsigned char _colorType;
|
||||||
|
rgb _palette[256];
|
||||||
|
unsigned char _transparency[256];
|
||||||
|
int _paletteSize;
|
||||||
|
int _transparencySize;
|
||||||
|
unsigned int _delayNum;
|
||||||
|
unsigned int _delayDen;
|
||||||
|
unsigned char** _rows;
|
||||||
|
};
|
||||||
|
|
||||||
class PngDecoder CV_FINAL : public BaseImageDecoder
|
class PngDecoder CV_FINAL : public BaseImageDecoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
PngDecoder();
|
PngDecoder();
|
||||||
virtual ~PngDecoder();
|
virtual ~PngDecoder();
|
||||||
|
|
||||||
bool readData( Mat& img ) CV_OVERRIDE;
|
bool readData( Mat& img ) CV_OVERRIDE;
|
||||||
bool readHeader() CV_OVERRIDE;
|
bool readHeader() CV_OVERRIDE;
|
||||||
void close();
|
bool nextPage() CV_OVERRIDE;
|
||||||
|
|
||||||
ImageDecoder newDecoder() const CV_OVERRIDE;
|
ImageDecoder newDecoder() const CV_OVERRIDE;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size);
|
static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size);
|
||||||
|
static void info_fn(png_structp png_ptr, png_infop info_ptr);
|
||||||
|
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(unsigned char** rows_dst, unsigned char** rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, int channels);
|
||||||
|
size_t read_from_io(void* _Buffer, size_t _ElementSize, size_t _ElementCount);
|
||||||
|
uint32_t read_chunk(Chunk& chunk);
|
||||||
|
|
||||||
int m_bit_depth;
|
int m_bit_depth;
|
||||||
void* m_png_ptr; // pointer to decompression structure
|
void* m_png_ptr; // pointer to decompression structure
|
||||||
|
|
@ -74,7 +146,25 @@ protected:
|
||||||
void* m_end_info; // pointer to one more image information structure
|
void* m_end_info; // pointer to one more image information structure
|
||||||
FILE* m_f;
|
FILE* m_f;
|
||||||
int m_color_type;
|
int m_color_type;
|
||||||
|
Chunk m_chunkIHDR;
|
||||||
|
int m_frame_no;
|
||||||
size_t m_buf_pos;
|
size_t m_buf_pos;
|
||||||
|
std::vector<Chunk> m_chunksInfo;
|
||||||
|
APNGFrame frameRaw;
|
||||||
|
APNGFrame frameNext;
|
||||||
|
APNGFrame frameCur;
|
||||||
|
Mat m_mat_raw;
|
||||||
|
Mat m_mat_next;
|
||||||
|
uint32_t w0;
|
||||||
|
uint32_t h0;
|
||||||
|
uint32_t x0;
|
||||||
|
uint32_t y0;
|
||||||
|
uint32_t delay_num;
|
||||||
|
uint32_t delay_den;
|
||||||
|
uint32_t dop;
|
||||||
|
uint32_t bop;
|
||||||
|
bool m_is_fcTL_loaded;
|
||||||
|
bool m_is_IDAT_loaded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,14 +174,40 @@ public:
|
||||||
PngEncoder();
|
PngEncoder();
|
||||||
virtual ~PngEncoder();
|
virtual ~PngEncoder();
|
||||||
|
|
||||||
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
bool isFormatSupported( int depth ) const CV_OVERRIDE;
|
||||||
bool write( const Mat& img, const std::vector<int>& params ) 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;
|
ImageEncoder newEncoder() const CV_OVERRIDE;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void writeDataToBuf(void* png_ptr, uchar* src, size_t size);
|
static void writeDataToBuf(void* png_ptr, unsigned char* src, size_t size);
|
||||||
static void flushBuf(void* png_ptr);
|
static void flushBuf(void* png_ptr);
|
||||||
|
size_t write_to_io(void const* _Buffer, size_t _ElementSize, size_t _ElementCount, FILE* _Stream);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void writeChunk(FILE* f, const char* name, unsigned char* data, uint32_t length);
|
||||||
|
void writeIDATs(FILE* f, int frame, unsigned char* data, uint32_t length, uint32_t idat_size);
|
||||||
|
void processRect(unsigned char* row, int rowbytes, int bpp, int stride, int h, unsigned char* rows);
|
||||||
|
void deflateRectFin(unsigned char* zbuf, uint32_t* zsize, int bpp, int stride, unsigned char* rows, int zbuf_size, int n);
|
||||||
|
void deflateRectOp(unsigned char* pdata, int x, int y, int w, int h, int bpp, int stride, int zbuf_size, int n);
|
||||||
|
bool getRect(uint32_t w, uint32_t h, unsigned char* pimage1, unsigned char* pimage2, unsigned char* ptemp, uint32_t bpp, uint32_t stride, int zbuf_size, uint32_t has_tcolor, uint32_t tcolor, int n);
|
||||||
|
|
||||||
|
AutoBuffer<unsigned char> op_zbuf1;
|
||||||
|
AutoBuffer<unsigned char> op_zbuf2;
|
||||||
|
AutoBuffer<unsigned char> row_buf;
|
||||||
|
AutoBuffer<unsigned char> sub_row;
|
||||||
|
AutoBuffer<unsigned char> up_row;
|
||||||
|
AutoBuffer<unsigned char> avg_row;
|
||||||
|
AutoBuffer<unsigned char> paeth_row;
|
||||||
|
z_stream op_zstream1;
|
||||||
|
z_stream op_zstream2;
|
||||||
|
OP op[6];
|
||||||
|
rgb palette[256];
|
||||||
|
unsigned char trns[256];
|
||||||
|
uint32_t palsize, trnssize;
|
||||||
|
uint32_t next_seq_num;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -776,7 +776,8 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat
|
||||||
|
|
||||||
if (current >= start)
|
if (current >= start)
|
||||||
{
|
{
|
||||||
animation.durations.push_back(decoder->animation().durations[decoder->animation().durations.size() - 1]);
|
int duration = decoder->animation().durations.size() > 0 ? decoder->animation().durations.back() : 1000;
|
||||||
|
animation.durations.push_back(duration);
|
||||||
animation.frames.push_back(mat);
|
animation.frames.push_back(mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
436
modules/imgcodecs/test/test_animation.cpp
Normal file
436
modules/imgcodecs/test/test_animation.cpp
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
// This file is part of OpenCV project.
|
||||||
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||||
|
// of this distribution and at http://opencv.org/license.html.
|
||||||
|
|
||||||
|
#include "test_precomp.hpp"
|
||||||
|
|
||||||
|
namespace opencv_test { namespace {
|
||||||
|
|
||||||
|
static void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
|
||||||
|
{
|
||||||
|
FILE * wfile = fopen(fname.c_str(), "rb");
|
||||||
|
if (wfile != NULL)
|
||||||
|
{
|
||||||
|
fseek(wfile, 0, SEEK_END);
|
||||||
|
size_t wfile_size = ftell(wfile);
|
||||||
|
fseek(wfile, 0, SEEK_SET);
|
||||||
|
|
||||||
|
buf.resize(wfile_size);
|
||||||
|
|
||||||
|
size_t data_size = fread(&buf[0], 1, wfile_size, wfile);
|
||||||
|
|
||||||
|
if(wfile)
|
||||||
|
{
|
||||||
|
fclose(wfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(data_size, wfile_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fillFrames(Animation& animation, bool hasAlpha, int n = 14)
|
||||||
|
{
|
||||||
|
// 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, animation));
|
||||||
|
EXPECT_EQ(1000, animation.durations.back());
|
||||||
|
|
||||||
|
if (!hasAlpha)
|
||||||
|
cvtColor(animation.frames[0], animation.frames[0], COLOR_BGRA2BGR);
|
||||||
|
|
||||||
|
animation.loop_count = 0xffff; // 0xffff is the maximum value to set.
|
||||||
|
|
||||||
|
// Add the first frame with a duration value of 400 milliseconds.
|
||||||
|
int duration = 80;
|
||||||
|
animation.durations[0] = duration * 5;
|
||||||
|
Mat image = animation.frames[0].clone();
|
||||||
|
putText(animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||||
|
|
||||||
|
// Define a region of interest (ROI)
|
||||||
|
Rect roi(2, 16, 26, 16);
|
||||||
|
|
||||||
|
// Modify the ROI in n iterations to simulate slight changes in animation frames.
|
||||||
|
for (int i = 1; i < n; i++)
|
||||||
|
{
|
||||||
|
roi.x++;
|
||||||
|
roi.width -= 2;
|
||||||
|
RNG rng = theRNG();
|
||||||
|
for (int x = roi.x; x < roi.x + roi.width; x++)
|
||||||
|
for (int y = roi.y; y < roi.y + roi.height; y++)
|
||||||
|
{
|
||||||
|
if (hasAlpha)
|
||||||
|
{
|
||||||
|
Vec4b& pixel = image.at<Vec4b>(y, x);
|
||||||
|
if (pixel[3] > 0)
|
||||||
|
{
|
||||||
|
if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||||
|
if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||||
|
if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||||
|
pixel[3] -= (uchar)rng.uniform(2, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vec3b& pixel = image.at<Vec3b>(y, x);
|
||||||
|
if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(2, 5);
|
||||||
|
if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(2, 5);
|
||||||
|
if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(2, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the duration and add the modified frame to the animation.
|
||||||
|
duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly).
|
||||||
|
animation.frames.push_back(image.clone());
|
||||||
|
putText(animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
||||||
|
animation.durations.push_back(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add two identical frames with the same duration.
|
||||||
|
if (animation.frames.size() > 1 && animation.frames.size() < 20)
|
||||||
|
{
|
||||||
|
animation.durations.push_back(++duration);
|
||||||
|
animation.frames.push_back(animation.frames.back());
|
||||||
|
animation.durations.push_back(++duration);
|
||||||
|
animation.frames.push_back(animation.frames.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_WEBP
|
||||||
|
|
||||||
|
TEST(Imgcodecs_WebP, imwriteanimation_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation, l_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||||
|
s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose.
|
||||||
|
|
||||||
|
// Create a temporary output filename for saving the animation.
|
||||||
|
string output = cv::tempfile(".webp");
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||||
|
// and the duration value for the last frame is handled by libwebp.
|
||||||
|
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());
|
||||||
|
|
||||||
|
// 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(s_animation.durations[i], l_animation.durations[i]);
|
||||||
|
|
||||||
|
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[14], NORM_INF));
|
||||||
|
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||||
|
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_WebP, imwriteanimation_rgb)
|
||||||
|
{
|
||||||
|
Animation s_animation, l_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||||
|
|
||||||
|
// Create a temporary output filename for saving the animation.
|
||||||
|
string output = cv::tempfile(".webp");
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
||||||
|
// and the duration value for the last frame is handled by libwebp.
|
||||||
|
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());
|
||||||
|
|
||||||
|
// Verify that the durations of frames match.
|
||||||
|
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||||
|
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||||
|
|
||||||
|
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_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0);
|
||||||
|
EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0);
|
||||||
|
EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0);
|
||||||
|
|
||||||
|
// Verify whether the imread function successfully loads the first frame
|
||||||
|
Mat frame = imread(output, IMREAD_COLOR);
|
||||||
|
EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0);
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_WebP, imwritemulti_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||||
|
|
||||||
|
string output = cv::tempfile(".webp");
|
||||||
|
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
ASSERT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||||
|
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||||
|
EXPECT_EQ(4, s_animation.frames[0].channels());
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_WebP, imwritemulti_rgb)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||||
|
|
||||||
|
string output = cv::tempfile(".webp");
|
||||||
|
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||||
|
EXPECT_EQ(s_animation.frames.size() - 2, read_frames.size());
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_WebP, imencode_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||||
|
|
||||||
|
std::vector<uchar> buf;
|
||||||
|
vector<Mat> apng_frames;
|
||||||
|
|
||||||
|
// Test encoding and decoding the images in memory (without saving to disk).
|
||||||
|
EXPECT_TRUE(imencode(".webp", s_animation.frames, buf));
|
||||||
|
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, apng_frames));
|
||||||
|
EXPECT_EQ(s_animation.frames.size() - 2, apng_frames.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_WEBP
|
||||||
|
|
||||||
|
#ifdef HAVE_PNG
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imwriteanimation_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation, l_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(s_animation.durations[i], l_animation.durations[i]);
|
||||||
|
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||||
|
}
|
||||||
|
|
||||||
|
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[14], NORM_INF));
|
||||||
|
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
||||||
|
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], 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> 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;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||||
|
|
||||||
|
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));
|
||||||
|
EXPECT_EQ(l_animation.frames.size(), s_animation.frames.size() - 2);
|
||||||
|
for (size_t i = 0; i < l_animation.frames.size() - 1; i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], l_animation.frames[i], NORM_INF));
|
||||||
|
}
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imwritemulti_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true));
|
||||||
|
|
||||||
|
string output = cv::tempfile(".png");
|
||||||
|
EXPECT_EQ(true, imwrite(output, s_animation.frames));
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
EXPECT_EQ(true, imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||||
|
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||||
|
EXPECT_EQ(imcount(output), read_frames.size());
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imwritemulti_rgb)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||||
|
|
||||||
|
string output = cv::tempfile(".png");
|
||||||
|
ASSERT_TRUE(imwrite(output, s_animation.frames));
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
ASSERT_TRUE(imreadmulti(output, read_frames));
|
||||||
|
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < read_frames.size(); i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imwritemulti_gray)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, false));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < s_animation.frames.size(); i++)
|
||||||
|
{
|
||||||
|
cvtColor(s_animation.frames[i], s_animation.frames[i], COLOR_BGR2GRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
string output = cv::tempfile(".png");
|
||||||
|
EXPECT_TRUE(imwrite(output, s_animation.frames));
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
EXPECT_TRUE(imreadmulti(output, read_frames));
|
||||||
|
EXPECT_EQ(1, read_frames[0].channels());
|
||||||
|
read_frames.clear();
|
||||||
|
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_UNCHANGED));
|
||||||
|
EXPECT_EQ(1, read_frames[0].channels());
|
||||||
|
read_frames.clear();
|
||||||
|
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_COLOR));
|
||||||
|
EXPECT_EQ(3, read_frames[0].channels());
|
||||||
|
read_frames.clear();
|
||||||
|
EXPECT_TRUE(imreadmulti(output, read_frames, IMREAD_GRAYSCALE));
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < read_frames.size(); i++)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(0, cvtest::norm(s_animation.frames[i], read_frames[i], NORM_INF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imwriteanimation_bgcolor)
|
||||||
|
{
|
||||||
|
Animation s_animation, l_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||||
|
s_animation.bgcolor = Scalar(50, 100, 150, 128); // different values for test purpose.
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Check that the background color match between saved and loaded animations.
|
||||||
|
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true, 2));
|
||||||
|
s_animation.bgcolor = Scalar();
|
||||||
|
|
||||||
|
output = cv::tempfile(".png");
|
||||||
|
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
||||||
|
EXPECT_TRUE(imreadanimation(output, l_animation));
|
||||||
|
EXPECT_EQ(l_animation.bgcolor, s_animation.bgcolor);
|
||||||
|
|
||||||
|
EXPECT_EQ(0, remove(output.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Imgcodecs_APNG, imencode_rgba)
|
||||||
|
{
|
||||||
|
Animation s_animation;
|
||||||
|
EXPECT_TRUE(fillFrames(s_animation, true, 3));
|
||||||
|
|
||||||
|
std::vector<uchar> buf;
|
||||||
|
vector<Mat> read_frames;
|
||||||
|
// Test encoding and decoding the images in memory (without saving to disk).
|
||||||
|
EXPECT_TRUE(imencode(".png", s_animation.frames, buf));
|
||||||
|
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, read_frames));
|
||||||
|
EXPECT_EQ(read_frames.size(), s_animation.frames.size() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_PNG
|
||||||
|
|
||||||
|
}} // namespace
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of OpenCV project.
|
// This file is part of OpenCV project.
|
||||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||||
// of this distribution and at http://opencv.org/license.html
|
// of this distribution and at http://opencv.org/license.html.
|
||||||
|
|
||||||
#include "test_precomp.hpp"
|
#include "test_precomp.hpp"
|
||||||
|
|
||||||
namespace opencv_test { namespace {
|
namespace opencv_test { namespace {
|
||||||
|
|
@ -113,234 +114,6 @@ TEST(Imgcodecs_WebP, encode_decode_with_alpha_webp)
|
||||||
EXPECT_EQ(512, img_webp_bgr.rows);
|
EXPECT_EQ(512, img_webp_bgr.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Imgcodecs_WebP, load_save_animation_rgba)
|
|
||||||
{
|
|
||||||
RNG rng = theRNG();
|
|
||||||
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
// Create an Animation object using the default constructor.
|
|
||||||
// This initializes the loop count to 0 (infinite looping), background color to 0 (transparent)
|
|
||||||
Animation l_animation;
|
|
||||||
|
|
||||||
// Create an Animation object with custom parameters.
|
|
||||||
int loop_count = 0xffff; // 0xffff is the maximum value to set.
|
|
||||||
Scalar bgcolor(125, 126, 127, 128); // different values for test purpose.
|
|
||||||
Animation s_animation(loop_count, bgcolor);
|
|
||||||
|
|
||||||
// Load the image file with alpha channel (IMREAD_UNCHANGED).
|
|
||||||
Mat image = imread(filename, IMREAD_UNCHANGED);
|
|
||||||
ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename;
|
|
||||||
|
|
||||||
// Add the first frame with a duration value of 500 milliseconds.
|
|
||||||
int duration = 100;
|
|
||||||
s_animation.durations.push_back(duration * 5);
|
|
||||||
s_animation.frames.push_back(image.clone()); // Store the first frame.
|
|
||||||
putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
|
||||||
|
|
||||||
// Define a region of interest (ROI) in the loaded image for manipulation.
|
|
||||||
Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image.
|
|
||||||
|
|
||||||
// Modify the ROI in 13 iterations to simulate slight changes in animation frames.
|
|
||||||
for (int i = 1; i < 14; i++)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < roi.rows; x++)
|
|
||||||
for (int y = 0; y < roi.cols; y++)
|
|
||||||
{
|
|
||||||
// Apply random changes to pixel values to create animation variations.
|
|
||||||
Vec4b& pixel = roi.at<Vec4b>(x, y);
|
|
||||||
if (pixel[3] > 0)
|
|
||||||
{
|
|
||||||
if (pixel[0] > 10) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel.
|
|
||||||
if (pixel[1] > 10) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel.
|
|
||||||
if (pixel[2] > 10) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel.
|
|
||||||
pixel[3] -= (uchar)rng.uniform(2, 5); // Reduce alpha channel.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the duration and add the modified frame to the animation.
|
|
||||||
duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly).
|
|
||||||
s_animation.frames.push_back(image.clone());
|
|
||||||
putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add two identical frames with the same duration.
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
s_animation.frames.push_back(s_animation.frames[13].clone());
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
s_animation.frames.push_back(s_animation.frames[13].clone());
|
|
||||||
|
|
||||||
// Create a temporary output filename for saving the animation.
|
|
||||||
string output = cv::tempfile(".webp");
|
|
||||||
|
|
||||||
// Write the animation to a .webp file and verify success.
|
|
||||||
EXPECT_TRUE(imwriteanimation(output, s_animation));
|
|
||||||
imwriteanimation("output.webp", s_animation);
|
|
||||||
|
|
||||||
// Read the animation back and compare with the original.
|
|
||||||
EXPECT_TRUE(imreadanimation(output, l_animation));
|
|
||||||
|
|
||||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
|
||||||
// and the duration value for the last frame is handled by libwebp.
|
|
||||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
|
||||||
|
|
||||||
// Verify that the number of frames matches the expected count.
|
|
||||||
EXPECT_EQ(imcount(output), expected_frame_count);
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), expected_frame_count);
|
|
||||||
|
|
||||||
// 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(s_animation.durations[i], l_animation.durations[i]);
|
|
||||||
|
|
||||||
EXPECT_TRUE(imreadanimation(output, l_animation, 5, 3));
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3);
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
|
||||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF));
|
|
||||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF));
|
|
||||||
EXPECT_EQ(0, cvtest::norm(l_animation.frames[7], l_animation.frames[16], 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());
|
|
||||||
|
|
||||||
webp_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, webp_frames));
|
|
||||||
|
|
||||||
// Expect all frames written as multi-page image
|
|
||||||
expected_frame_count = 14;
|
|
||||||
EXPECT_EQ(expected_frame_count, webp_frames.size());
|
|
||||||
|
|
||||||
// Test encoding and decoding the images in memory (without saving to disk).
|
|
||||||
webp_frames.clear();
|
|
||||||
EXPECT_TRUE(imencode(".webp", s_animation.frames, buf));
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Imgcodecs_WebP, load_save_animation_rgb)
|
|
||||||
{
|
|
||||||
RNG rng = theRNG();
|
|
||||||
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
// Create an Animation object using the default constructor.
|
|
||||||
// This initializes the loop count to 0 (infinite looping), background color to 0 (transparent)
|
|
||||||
Animation l_animation;
|
|
||||||
|
|
||||||
// Create an Animation object with custom parameters.
|
|
||||||
int loop_count = 0xffff; // 0xffff is the maximum value to set.
|
|
||||||
Scalar bgcolor(125, 126, 127, 128); // different values for test purpose.
|
|
||||||
Animation s_animation(loop_count, bgcolor);
|
|
||||||
|
|
||||||
// Load the image file without alpha channel
|
|
||||||
Mat image = imread(filename);
|
|
||||||
ASSERT_FALSE(image.empty()) << "Failed to load image: " << filename;
|
|
||||||
|
|
||||||
// Add the first frame with a duration value of 500 milliseconds.
|
|
||||||
int duration = 100;
|
|
||||||
s_animation.durations.push_back(duration * 5);
|
|
||||||
s_animation.frames.push_back(image.clone()); // Store the first frame.
|
|
||||||
putText(s_animation.frames[0], "0", Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
|
||||||
|
|
||||||
// Define a region of interest (ROI) in the loaded image for manipulation.
|
|
||||||
Mat roi = image(Rect(0, 16, 32, 16)); // Select a subregion of the image.
|
|
||||||
|
|
||||||
// Modify the ROI in 13 iterations to simulate slight changes in animation frames.
|
|
||||||
for (int i = 1; i < 14; i++)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < roi.rows; x++)
|
|
||||||
for (int y = 0; y < roi.cols; y++)
|
|
||||||
{
|
|
||||||
// Apply random changes to pixel values to create animation variations.
|
|
||||||
Vec3b& pixel = roi.at<Vec3b>(x, y);
|
|
||||||
if (pixel[0] > 50) pixel[0] -= (uchar)rng.uniform(3, 10); // Reduce blue channel.
|
|
||||||
if (pixel[1] > 50) pixel[1] -= (uchar)rng.uniform(3, 10); // Reduce green channel.
|
|
||||||
if (pixel[2] > 50) pixel[2] -= (uchar)rng.uniform(3, 10); // Reduce red channel.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the duration and add the modified frame to the animation.
|
|
||||||
duration += rng.uniform(2, 10); // Increase duration with random value (to be sure different duration values saved correctly).
|
|
||||||
s_animation.frames.push_back(image.clone());
|
|
||||||
putText(s_animation.frames[i], format("%d", i), Point(5, 28), FONT_HERSHEY_SIMPLEX, .5, Scalar(100, 255, 0, 255), 2);
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add two identical frames with the same duration.
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
s_animation.frames.push_back(s_animation.frames[13].clone());
|
|
||||||
s_animation.durations.push_back(duration);
|
|
||||||
s_animation.frames.push_back(s_animation.frames[13].clone());
|
|
||||||
|
|
||||||
// Create a temporary output filename for saving the animation.
|
|
||||||
string output = cv::tempfile(".webp");
|
|
||||||
|
|
||||||
// Write the animation to a .webp file and verify success.
|
|
||||||
EXPECT_EQ(true, imwriteanimation(output, s_animation));
|
|
||||||
|
|
||||||
// Read the animation back and compare with the original.
|
|
||||||
EXPECT_EQ(true, imreadanimation(output, l_animation));
|
|
||||||
|
|
||||||
// Since the last frames are identical, WebP optimizes by storing only one of them,
|
|
||||||
// and the duration value for the last frame is handled by libwebp.
|
|
||||||
size_t expected_frame_count = s_animation.frames.size() - 2;
|
|
||||||
|
|
||||||
// Verify that the number of frames matches the expected count.
|
|
||||||
EXPECT_EQ(imcount(output), expected_frame_count);
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), expected_frame_count);
|
|
||||||
|
|
||||||
// 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(s_animation.durations[i], l_animation.durations[i]);
|
|
||||||
|
|
||||||
EXPECT_EQ(true, imreadanimation(output, l_animation, 5, 3));
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), expected_frame_count + 3);
|
|
||||||
EXPECT_EQ(l_animation.frames.size(), l_animation.durations.size());
|
|
||||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[5], l_animation.frames[14], NORM_INF) == 0);
|
|
||||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[6], l_animation.frames[15], NORM_INF) == 0);
|
|
||||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[7], l_animation.frames[16], NORM_INF) == 0);
|
|
||||||
|
|
||||||
// Verify whether the imread function successfully loads the first frame
|
|
||||||
Mat frame = imread(output, IMREAD_COLOR);
|
|
||||||
EXPECT_TRUE(cvtest::norm(l_animation.frames[0], frame, NORM_INF) == 0);
|
|
||||||
|
|
||||||
std::vector<uchar> buf;
|
|
||||||
readFileBytes(output, buf);
|
|
||||||
|
|
||||||
vector<Mat> webp_frames;
|
|
||||||
EXPECT_TRUE(imdecodemulti(buf, IMREAD_UNCHANGED, webp_frames));
|
|
||||||
EXPECT_EQ(webp_frames.size(), expected_frame_count);
|
|
||||||
|
|
||||||
// Clean up by removing the temporary file.
|
|
||||||
EXPECT_EQ(0, remove(output.c_str()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // HAVE_WEBP
|
#endif // HAVE_WEBP
|
||||||
|
|
||||||
}} // namespace
|
}} // namespace
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user