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:
@@ -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> ¶ms) {
|
||||
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> ¶ms) {
|
||||
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> ¶ms) {
|
||||
bool AvifEncoder::writeanimation(const Animation& animation,
|
||||
const std::vector<int> ¶ms) {
|
||||
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) {
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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, ×tamp);
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user