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

Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2025-02-05 09:28:27 +03:00
180 changed files with 11419 additions and 1819 deletions
+16 -1
View File
@@ -23,6 +23,11 @@ if(HAVE_JPEG)
list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES})
endif()
if(HAVE_JPEGXL)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
endif()
if(HAVE_WEBP)
add_definitions(-DHAVE_WEBP)
ocv_include_directories(${WEBP_INCLUDE_DIR})
@@ -51,6 +56,12 @@ if(HAVE_TIFF)
list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES})
endif()
if(HAVE_JPEGXL)
ocv_include_directories(${JPEGXL_INCLUDE_DIRS})
message(STATUS "JPEGXL_INCLUDE_DIRS: ${JPEGXL_INCLUDE_DIRS}")
list(APPEND GRFMT_LIBS ${JPEGXL_LIBRARIES})
endif()
if(HAVE_OPENJPEG)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
@@ -78,7 +89,7 @@ if(HAVE_OPENEXR)
endif()
endif()
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG)
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG OR HAVE_JPEGXL)
ocv_include_directories(${ZLIB_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES})
endif()
@@ -88,6 +99,10 @@ if(HAVE_GDAL)
list(APPEND GRFMT_LIBS ${GDAL_LIBRARY})
endif()
if(HAVE_IMGCODEC_GIF)
add_definitions(-DHAVE_IMGCODEC_GIF)
endif()
if(HAVE_IMGCODEC_HDR)
add_definitions(-DHAVE_IMGCODEC_HDR)
endif()
@@ -111,8 +111,18 @@ enum ImwriteFlags {
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8.
IMWRITE_AVIF_SPEED = 514 //!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9.
};
IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9.
IMWRITE_JPEGXL_QUALITY = 640,//!< For JPEG XL, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. If set, distance parameter is re-calicurated from quality level automatically. This parameter request libjxl v0.10 or later.
IMWRITE_JPEGXL_EFFORT = 641,//!< For JPEG XL, encoder effort/speed level without affecting decoding speed; it is between 1 (fastest) and 10 (slowest). Default is 7.
IMWRITE_JPEGXL_DISTANCE = 642,//!< For JPEG XL, distance level for lossy compression: target max butteraugli distance, lower = higher quality, 0 = lossless; range: 0 .. 25. Default is 1.
IMWRITE_JPEGXL_DECODING_SPEED = 643,//!< For JPEG XL, decoding speed tier for the provided options; minimum is 0 (slowest to decode, best quality/density), and maximum is 4 (fastest to decode, at the cost of some quality/density). Default is 0.
IMWRITE_GIF_LOOP = 1024,//!< For GIF, it can be a loop flag from 0 to 65535. Default is 0 - loop forever.
IMWRITE_GIF_SPEED = 1025,//!< For GIF, it is between 1 (slowest) and 100 (fastest). Default is 96.
IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags.
IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0.
IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1.
IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0.
};
enum ImwriteJPEGSamplingFactorParams {
IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1
@@ -216,8 +226,50 @@ enum ImwriteHDRCompressionFlags {
IMWRITE_HDR_COMPRESSION_RLE = 1
};
//! Imwrite GIF specific values for IMWRITE_GIF_QUALITY parameter key, if larger than 3, then its related to the size of the color table.
enum ImwriteGIFCompressionFlags {
IMWRITE_GIF_FAST_NO_DITHER = 1,
IMWRITE_GIF_FAST_FLOYD_DITHER = 2,
IMWRITE_GIF_COLORTABLE_SIZE_8 = 3,
IMWRITE_GIF_COLORTABLE_SIZE_16 = 4,
IMWRITE_GIF_COLORTABLE_SIZE_32 = 5,
IMWRITE_GIF_COLORTABLE_SIZE_64 = 6,
IMWRITE_GIF_COLORTABLE_SIZE_128 = 7,
IMWRITE_GIF_COLORTABLE_SIZE_256 = 8
};
//! @} imgcodecs_flags
/** @brief Represents an animation with multiple frames.
The `Animation` struct is designed to store and manage data for animated sequences such as those from animated formats (e.g., GIF, AVIF, APNG, WebP).
It provides support for looping, background color settings, frame timing, and frame storage.
*/
struct CV_EXPORTS_W_SIMPLE Animation
{
//! Number of times the animation should loop. 0 means infinite looping.
CV_PROP_RW int loop_count;
//! Background color of the animation in BGRA format.
CV_PROP_RW Scalar bgcolor;
//! Duration for each frame in milliseconds.
CV_PROP_RW std::vector<int> durations;
//! Vector of frames, where each Mat represents a single frame.
CV_PROP_RW std::vector<Mat> frames;
/** @brief Constructs an Animation object with optional loop count and background color.
@param loopCount An integer representing the number of times the animation should loop:
- `0` (default) indicates infinite looping, meaning the animation will replay continuously.
- Positive values denote finite repeat counts, allowing the animation to play a limited number of times.
- If a negative value or a value beyond the maximum of `0xffff` (65535) is provided, it is reset to `0`
(infinite looping) to maintain valid bounds.
@param bgColor A `Scalar` object representing the background color in BGRA format:
- Defaults to `Scalar()`, indicating an empty color (usually transparent if supported).
- This background color provides a solid fill behind frames that have transparency, ensuring a consistent display appearance.
*/
Animation(int loopCount = 0, Scalar bgColor = Scalar());
};
/** @brief Loads an image from a file.
@anchor imread
@@ -229,6 +281,7 @@ returns an empty matrix.
Currently, the following file formats are supported:
- Windows bitmaps - \*.bmp, \*.dib (always supported)
- GIF files - \*.gif (always supported)
- JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section)
- JPEG 2000 files - \*.jp2 (see the *Note* section)
- Portable Network Graphics - \*.png (see the *Note* section)
@@ -304,6 +357,38 @@ The function imreadmulti loads a specified range from a multi-page image from th
*/
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
/** @example samples/cpp/tutorial_code/imgcodecs/animations.cpp
An example to show usage of cv::imreadanimation and cv::imwriteanimation functions.
Check @ref tutorial_animations "the corresponding tutorial" for more details
*/
/** @brief Loads frames from an animated image file into an Animation structure.
The function imreadanimation loads frames from an animated image file (e.g., GIF, AVIF, APNG, WEBP) into the provided Animation struct.
@param filename A string containing the path to the file.
@param animation A reference to an Animation structure where the loaded frames will be stored. It should be initialized before the function is called.
@param start The index of the first frame to load. This is optional and defaults to 0.
@param count The number of frames to load. This is optional and defaults to 32767.
@return Returns true if the file was successfully loaded and frames were extracted; returns false otherwise.
*/
CV_EXPORTS_W bool imreadanimation(const String& filename, CV_OUT Animation& animation, int start = 0, int count = INT16_MAX);
/** @brief Saves an Animation to a specified file.
The function imwriteanimation saves the provided Animation data to the specified file in an animated format.
Supported formats depend on the implementation and may include formats like GIF, AVIF, APNG, or WEBP.
@param filename The name of the file where the animation will be saved. The file extension determines the format.
@param animation A constant reference to an Animation struct containing the frames and metadata to be saved.
@param params Optional format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ...).
These parameters are used to specify additional options for the encoding process. Refer to `cv::ImwriteFlags` for details on possible parameters.
@return Returns true if the animation was successfully saved; returns false otherwise.
*/
CV_EXPORTS_W bool imwriteanimation(const String& filename, const Animation& animation, const std::vector<int>& params = std::vector<int>());
/** @brief Returns the number of images inside the given file
The function imcount returns the number of pages in a multi-page image (e.g. TIFF), the number of frames in an animation (e.g. AVIF), and 1 otherwise.
@@ -326,6 +411,10 @@ can be saved using this function, with these exceptions:
- With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved.
- All images will be converted to 32-bit float (CV_32F).
- With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved.
- JPEG XL images with an alpha channel can be saved using this function.
To do this, create 8-bit (or 16-bit, 32-bit float) 4-channel image BGRA, where the alpha channel goes last.
Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535/1.0.
- With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- PNG images with an alpha channel can be saved using this function. To do this, create
+13 -13
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.duration * 1000);
if (decoder_->image->exif.size > 0) {
m_exif.parseExif(decoder_->image->exif.data, decoder_->image->exif.size);
}
@@ -297,16 +300,11 @@ 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);
}
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 +338,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 +356,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 -6
View File
@@ -41,17 +41,12 @@ 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;
private:
bool writeToOutput(const std::vector<Mat>& img_vec,
const std::vector<int>& params);
avifEncoder* encoder_;
};
+17 -1
View File
@@ -43,6 +43,7 @@
#include "grfmt_base.hpp"
#include "bitstrm.hpp"
#include <opencv2/core/utils/logger.hpp>
namespace cv
{
@@ -139,7 +140,22 @@ 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)
{
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>& )
{
return false;
}
+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.
+6 -1
View File
@@ -208,7 +208,12 @@ bool BmpDecoder::readHeader()
// in 32 bit case alpha channel is used - so require CV_8UC4 type
m_type = iscolor ? ((m_bpp == 32 && m_rle_code != BMP_RGB) ? CV_8UC4 : CV_8UC3 ) : CV_8UC1;
m_origin = m_height > 0 ? ORIGIN_BL : ORIGIN_TL;
m_height = std::abs(m_height);
if ( m_height == std::numeric_limits<int>::min() ) {
// abs(std::numeric_limits<int>::min()) is undefined behavior.
result = false;
} else {
m_height = std::abs(m_height);
}
if( !result )
{
+20 -4
View File
@@ -235,6 +235,17 @@ bool ExrDecoder::readData( Mat& img )
( (m_iscolor && !m_ischroma) || color) ? 3 : alphasupported ? 2 : 1 ); // number of channels to read may exceed channels in output img
size_t xStride = floatsize * channelstoread;
// See https://github.com/opencv/opencv/issues/26705
// If ALGO_HINT_ACCURATE is set, read BGR and swap to RGB.
// If ALGO_HINT_APPROX is set, read RGB directly.
bool doReadRGB = m_use_rgb;
bool doPostColorSwap = false; // After decoding, swap BGR to RGB
if(m_use_rgb && (getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE) )
{
doReadRGB = false;
doPostColorSwap = true;
}
AutoBuffer<char> copy_buffer;
if( !justcopy )
@@ -373,7 +384,7 @@ bool ExrDecoder::readData( Mat& img )
if( m_iscolor )
{
if (m_use_rgb)
if (doReadRGB)
{
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
@@ -397,7 +408,7 @@ bool ExrDecoder::readData( Mat& img )
if( chromatorgb )
{
if (m_use_rgb)
if (doReadRGB)
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
else
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
@@ -424,7 +435,7 @@ bool ExrDecoder::readData( Mat& img )
{
if( chromatorgb )
{
if (m_use_rgb)
if (doReadRGB)
ChromaToRGB( (float *)buffer, 1, defaultchannels, step );
else
ChromaToBGR( (float *)buffer, 1, defaultchannels, step );
@@ -452,7 +463,7 @@ bool ExrDecoder::readData( Mat& img )
}
if( color )
{
if (m_use_rgb)
if (doReadRGB)
{
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
UpSampleY( data, defaultchannels, step / xstep, m_red->ySampling );
@@ -477,6 +488,11 @@ bool ExrDecoder::readData( Mat& img )
close();
if(doPostColorSwap)
{
cvtColor( img, img, cv::COLOR_BGR2RGB );
}
return result;
}
File diff suppressed because it is too large Load Diff
+181
View File
@@ -0,0 +1,181 @@
// 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
#ifndef OPENCV_GRFMT_GIF_HPP
#define OPENCV_GRFMT_GIF_HPP
#ifdef HAVE_IMGCODEC_GIF
#include "grfmt_base.hpp"
namespace cv
{
enum GifOpMode
{
GRFMT_GIF_Nothing = 0,
GRFMT_GIF_PreviousImage = 1,
GRFMT_GIF_Background = 2,
GRFMT_GIF_Cover = 3
};
//////////////////////////////////////////////////////////////////////
//// GIF Decoder ////
//////////////////////////////////////////////////////////////////////
class GifDecoder CV_FINAL : public BaseImageDecoder
{
public:
GifDecoder();
~GifDecoder() CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
bool readData(Mat& img) CV_OVERRIDE;
bool nextPage() CV_OVERRIDE;
void close();
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
RLByteStream m_strm;
int bgColor;
int depth;
int idx;
GifOpMode opMode;
bool hasTransparentColor;
uchar transparentColor;
int top, left, width, height;
bool hasRead;
std::vector<uchar> globalColorTable;
std::vector<uchar> localColorTable;
int lzwMinCodeSize;
int globalColorTableSize;
int localColorTableSize;
Mat lastImage;
std::vector<uchar> imgCodeStream;
struct lzwNodeD
{
int length;
uchar suffix;
std::vector<uchar> prefix;
};
void readExtensions();
void code2pixel(Mat& img, int start, int k);
bool lzwDecode();
bool getFrameCount_();
bool skipHeader();
};
//////////////////////////////////////////////////////////////////////
//// GIF Encoder ////
//////////////////////////////////////////////////////////////////////
class GifEncoder CV_FINAL : public BaseImageEncoder {
public:
GifEncoder();
~GifEncoder() CV_OVERRIDE;
bool isFormatSupported(int depth) const CV_OVERRIDE;
bool write(const Mat& img, 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:
/** Color Quantization **/
class OctreeColorQuant
{
struct OctreeNode
{
bool isLeaf;
std::shared_ptr<OctreeNode> children[8]{};
int level;
uchar index;
int leaf;
int pixelCount;
size_t redSum, greenSum, blueSum;
OctreeNode();
};
std::shared_ptr<OctreeNode> root;
std::vector<std::shared_ptr<OctreeNode>> m_nodeList[8];
int32_t m_bitLength;
int32_t m_maxColors;
int32_t m_leafCount;
uchar m_criticalTransparency;
uchar r, g, b; // color under transparent color
public:
explicit OctreeColorQuant(int maxColors = 256, int bitLength = 8, uchar criticalTransparency = 1);
int getPalette(uchar* colorTable);
uchar getLeaf(uchar red, uchar green, uchar blue);
void addMat(const Mat& img);
void addMats(const std::vector<Mat>& img_vec);
void addColor(int red, int green, int blue);
void reduceTree();
void recurseReduce(const std::shared_ptr<OctreeNode>& node);
};
enum GifDithering // normal dithering level is -1 to 2
{
GRFMT_GIF_None = 3,
GRFMT_GIF_FloydSteinberg = 4
};
WLByteStream strm;
int m_width, m_height;
int globalColorTableSize;
int localColorTableSize;
uchar opMode;
uchar criticalTransparency;
uchar transparentColor;
Vec3b transparentRGB;
int top, left, width, height;
OctreeColorQuant quantG;
OctreeColorQuant quantL;
std::vector<int16_t> lzwTable;
std::vector<uchar> imgCodeStream;
std::vector<uchar> globalColorTable;
std::vector<uchar> localColorTable;
// params
int loopCount;
int frameDelay;
int colorNum;
int bitDepth;
int dithering;
int lzwMinCodeSize, lzwMaxCodeSize;
bool fast;
bool writeFrames(const std::vector<Mat>& img_vec, const std::vector<int>& params);
bool writeHeader(const std::vector<Mat>& img_vec);
bool writeFrame(const Mat& img);
bool pixel2code(const Mat& img);
void getColorTable(const std::vector<Mat>& img_vec, bool isGlobal);
static int ditheringKernel(const Mat &img, Mat &img_, int depth, uchar transparency);
bool lzwEncode();
void close();
};
} // namespace cv
#endif // HAVE_IMGCODEC_GIF
#endif //OPENCV_GRFMT_GIF_HPP
+420
View File
@@ -0,0 +1,420 @@
// 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 "precomp.hpp"
#include "grfmt_jpegxl.hpp"
#ifdef HAVE_JPEGXL
#include <jxl/encode_cxx.h>
#include <jxl/version.h>
#include <opencv2/core/utils/logger.hpp>
namespace cv
{
/////////////////////// JpegXLDecoder ///////////////////
JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose)
{
m_signature = "\xFF\x0A";
m_decoder = nullptr;
m_buf_supported = false;
m_type = m_convert = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
JpegXLDecoder::~JpegXLDecoder()
{
close();
}
void JpegXLDecoder::close()
{
if (m_decoder)
m_decoder.release();
if (m_f)
m_f.release();
m_read_buffer = {};
m_width = m_height = 0;
m_type = m_convert = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
// see https://github.com/libjxl/libjxl/blob/v0.10.0/doc/format_overview.md
size_t JpegXLDecoder::signatureLength() const
{
return 12; // For an ISOBMFF-based container
}
bool JpegXLDecoder::checkSignature( const String& signature ) const
{
// A "naked" codestream.
if (
( signature.size() >= 2 ) &&
( memcmp( signature.c_str(), "\xFF\x0A", 2 ) == 0 )
)
{
return true;
}
// An ISOBMFF-based container.
// 0x0000_000C_4A58_4C20_0D0A_870A.
if (
( signature.size() >= 12 ) &&
( memcmp( signature.c_str(), "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12 ) == 0 )
)
{
return true;
}
return false;
}
ImageDecoder JpegXLDecoder::newDecoder() const
{
return makePtr<JpegXLDecoder>();
}
bool JpegXLDecoder::read(Mat* pimg)
{
// Open file
if (!m_f) {
m_f.reset(fopen(m_filename.c_str(), "rb"));
if (!m_f)
return false;
}
// Initialize decoder
if (!m_decoder) {
m_decoder = JxlDecoderMake(nullptr);
if (!m_decoder)
return false;
// Subscribe to the basic info event
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
if (status != JXL_DEC_SUCCESS)
return false;
}
// Set up parallel m_parallel_runner
if (!m_parallel_runner) {
m_parallel_runner = JxlThreadParallelRunnerMake(nullptr, cv::getNumThreads());
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(m_decoder.get(),
JxlThreadParallelRunner,
m_parallel_runner.get())) {
return false;
}
}
// Create buffer for reading
const size_t read_buffer_size = 16384; // 16KB chunks
if (m_read_buffer.capacity() < read_buffer_size)
m_read_buffer.resize(read_buffer_size);
// Create image if needed
if (m_type != -1 && pimg) {
pimg->create(m_height, m_width, m_type);
if (!pimg->isContinuous())
return false;
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(),
&m_format,
pimg->ptr<uint8_t>(),
pimg->total() * pimg->elemSize())) {
return false;
}
}
// Start decoding loop
do {
// Check if we need more input
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
size_t remaining = JxlDecoderReleaseInput(m_decoder.get());
// Move any remaining bytes to the beginning
if (remaining > 0)
memmove(m_read_buffer.data(), m_read_buffer.data() + m_read_buffer.size() - remaining, remaining);
// Read more data from file
size_t bytes_read = fread(m_read_buffer.data() + remaining,
1, m_read_buffer.size() - remaining, m_f.get());
if (bytes_read == 0) {
if (ferror(m_f.get())) {
CV_LOG_WARNING(NULL, "Error reading input file");
return false;
}
// If we reached EOF but decoder needs more input, file is truncated
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
CV_LOG_WARNING(NULL, "Truncated JXL file");
return false;
}
}
// Set input buffer
if (JXL_DEC_SUCCESS != JxlDecoderSetInput(m_decoder.get(),
m_read_buffer.data(),
bytes_read + remaining)) {
return false;
}
}
// Get the next decoder status
m_status = JxlDecoderProcessInput(m_decoder.get());
// Handle different decoder states
switch (m_status) {
case JXL_DEC_BASIC_INFO: {
if (m_type != -1)
return false;
JxlBasicInfo info;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info))
return false;
// total channels (Color + Alpha)
const uint32_t ncn = info.num_color_channels + info.num_extra_channels;
m_width = info.xsize;
m_height = info.ysize;
m_format = {
ncn,
JXL_TYPE_UINT8, // (temporary)
JXL_LITTLE_ENDIAN, // endianness
0 // align stride to bytes
};
if (!m_use_rgb) {
switch (ncn) {
case 3:
m_convert = cv::COLOR_RGB2BGR;
break;
case 4:
m_convert = cv::COLOR_RGBA2BGRA;
break;
default:
m_convert = -1;
}
}
if (info.exponent_bits_per_sample > 0) {
m_format.data_type = JXL_TYPE_FLOAT;
m_type = CV_MAKETYPE( CV_32F, ncn );
} else {
switch (info.bits_per_sample) {
case 8:
m_format.data_type = JXL_TYPE_UINT8;
m_type = CV_MAKETYPE( CV_8U, ncn );
break;
case 16:
m_format.data_type = JXL_TYPE_UINT16;
m_type = CV_MAKETYPE( CV_16U, ncn );
break;
default:
return false;
}
}
if (!pimg)
return true;
break;
}
case JXL_DEC_FULL_IMAGE: {
// Image is ready
if (m_convert != -1)
cv::cvtColor(*pimg, *pimg, m_convert);
break;
}
case JXL_DEC_ERROR: {
close();
return false;
}
default:
break;
}
} while (m_status != JXL_DEC_SUCCESS);
return true;
}
bool JpegXLDecoder::readHeader()
{
close();
return read(nullptr);
}
bool JpegXLDecoder::readData(Mat& img)
{
if (!m_decoder || m_width == 0 || m_height == 0)
return false;
return read(&img);
}
/////////////////////// JpegXLEncoder ///////////////////
JpegXLEncoder::JpegXLEncoder()
{
m_description = "JPEG XL files (*.jxl)";
m_buf_supported = true;
}
JpegXLEncoder::~JpegXLEncoder()
{
}
ImageEncoder JpegXLEncoder::newEncoder() const
{
return makePtr<JpegXLEncoder>();
}
bool JpegXLEncoder::isFormatSupported( int depth ) const
{
return depth == CV_8U || depth == CV_16U || depth == CV_32F;
}
bool JpegXLEncoder::write(const Mat& img, const std::vector<int>& params)
{
m_last_error.clear();
JxlEncoderPtr encoder = JxlEncoderMake(nullptr);
if (!encoder)
return false;
JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake(
/*memory_manager=*/nullptr, cv::getNumThreads());
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(encoder.get(), JxlThreadParallelRunner, runner.get()))
return false;
CV_CheckDepth(img.depth(),
( img.depth() == CV_8U || img.depth() == CV_16U || img.depth() == CV_32F ),
"JPEG XL encoder only supports CV_8U, CV_16U, CV_32F");
CV_CheckChannels(img.channels(),
( img.channels() == 1 || img.channels() == 3 || img.channels() == 4) ,
"JPEG XL encoder only supports 1, 3, 4 channels");
WLByteStream strm;
if( m_buf ) {
if( !strm.open( *m_buf ) )
return false;
}
else if( !strm.open( m_filename )) {
return false;
}
JxlBasicInfo info;
JxlEncoderInitBasicInfo(&info);
info.xsize = img.cols;
info.ysize = img.rows;
info.uses_original_profile = JXL_FALSE;
if( img.channels() == 4 )
{
info.num_color_channels = 3;
info.num_extra_channels = 1;
info.bits_per_sample =
info.alpha_bits = 8 * static_cast<int>(img.elemSize1());
info.exponent_bits_per_sample =
info.alpha_exponent_bits = img.depth() == CV_32F ? 8 : 0;
}else{
info.num_color_channels = img.channels();
info.bits_per_sample = 8 * static_cast<int>(img.elemSize1());
info.exponent_bits_per_sample = img.depth() == CV_32F ? 8 : 0;
}
if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS)
return false;
JxlDataType type = JXL_TYPE_UINT8;
if (img.depth() == CV_32F)
type = JXL_TYPE_FLOAT;
else if (img.depth() == CV_16U)
type = JXL_TYPE_UINT16;
JxlPixelFormat format = {(uint32_t)img.channels(), type, JXL_NATIVE_ENDIAN, 0};
JxlColorEncoding color_encoding = {};
JXL_BOOL is_gray(format.num_channels < 3 ? JXL_TRUE : JXL_FALSE);
JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
return false;
Mat image;
switch ( img.channels() ) {
case 3:
cv::cvtColor(img, image, cv::COLOR_BGR2RGB);
break;
case 4:
cv::cvtColor(img, image, cv::COLOR_BGRA2RGBA);
break;
case 1:
default:
if(img.isContinuous()) {
image = img;
} else {
image = img.clone(); // reconstruction as continuous image.
}
break;
}
if (!image.isContinuous())
return false;
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
// set frame settings from params if available
for( size_t i = 0; i < params.size(); i += 2 )
{
if( params[i] == IMWRITE_JPEGXL_QUALITY )
{
#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10
int quality = params[i+1];
quality = MIN(MAX(quality, 0), 100);
const float distance = JxlEncoderDistanceFromQuality(static_cast<float>(quality));
JxlEncoderSetFrameDistance(frame_settings, distance);
if (distance == 0)
JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE);
#else
CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later");
#endif
}
if( params[i] == IMWRITE_JPEGXL_DISTANCE )
{
int distance = params[i+1];
distance = MIN(MAX(distance, 0), 25);
JxlEncoderSetFrameDistance(frame_settings, distance);
if (distance == 0)
JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE);
}
if( params[i] == IMWRITE_JPEGXL_EFFORT )
{
int effort = params[i+1];
effort = MIN(MAX(effort, 1), 10);
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort);
}
if( params[i] == IMWRITE_JPEGXL_DECODING_SPEED )
{
int speed = params[i+1];
speed = MIN(MAX(speed, 0), 4);
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, speed);
}
}
if (JXL_ENC_SUCCESS !=
JxlEncoderAddImageFrame(frame_settings, &format,
static_cast<const void*>(image.ptr<uint8_t>()),
image.total() * image.elemSize())) {
return false;
}
JxlEncoderCloseInput(encoder.get());
const size_t buffer_size = 16384; // 16KB chunks
std::vector<uint8_t> compressed(buffer_size);
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
uint8_t* next_out = compressed.data();
size_t avail_out = buffer_size;
process_result = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out);
if (JXL_ENC_ERROR == process_result)
return false;
const size_t write_size = buffer_size - avail_out;
if ( strm.putBytes(compressed.data(), write_size) == false )
return false;
}
return true;
}
}
#endif
/* End of file. */
+68
View File
@@ -0,0 +1,68 @@
// 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.
#ifndef _GRFMT_JPEGXL_H_
#define _GRFMT_JPEGXL_H_
#ifdef HAVE_JPEGXL
#include "grfmt_base.hpp"
#include <jxl/decode_cxx.h>
#include <jxl/thread_parallel_runner_cxx.h>
#include <vector>
#include <memory>
// Jpeg XL codec
namespace cv
{
/**
* @brief JpegXL codec using libjxl library
*/
class JpegXLDecoder CV_FINAL : public BaseImageDecoder
{
public:
JpegXLDecoder();
virtual ~JpegXLDecoder();
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
void close();
size_t signatureLength() const CV_OVERRIDE;
bool checkSignature( const String& signature ) const CV_OVERRIDE;
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
std::unique_ptr<FILE, int (*)(FILE*)> m_f;
JxlDecoderPtr m_decoder;
JxlThreadParallelRunnerPtr m_parallel_runner;
JxlPixelFormat m_format;
int m_convert;
std::vector<uint8_t> m_read_buffer;
JxlDecoderStatus m_status;
private:
bool read(Mat* pimg);
};
class JpegXLEncoder CV_FINAL : public BaseImageEncoder
{
public:
JpegXLEncoder();
virtual ~JpegXLEncoder();
bool isFormatSupported( int depth ) const CV_OVERRIDE;
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
};
}
#endif
#endif/*_GRFMT_JPEGXL_H_*/
File diff suppressed because it is too large Load Diff
+161 -9
View File
@@ -47,34 +47,161 @@
#include "grfmt_base.hpp"
#include "bitstrm.hpp"
#include <png.h>
#include <zlib.h>
#include <vector>
namespace cv
{
struct Chunk { std::vector<unsigned char> p; };
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);
std::vector<png_bytep>& getRows() { return _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;
std::vector<png_bytep> _rows;
};
class PngDecoder CV_FINAL : public BaseImageDecoder
{
public:
PngDecoder();
virtual ~PngDecoder();
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
void close();
bool nextPage() CV_OVERRIDE;
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
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(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);
bool read_from_io(void* buffer, size_t num_bytes);
uint32_t read_chunk(Chunk& chunk);
struct PngPtrs {
public:
PngPtrs() {
png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
if (png_ptr) {
info_ptr = png_create_info_struct( png_ptr );
end_info = png_create_info_struct( png_ptr );
} else {
info_ptr = end_info = nullptr;
}
}
~PngPtrs() {
clear();
}
PngPtrs& operator=(PngPtrs&& other) {
clear();
png_ptr = other.png_ptr;
info_ptr = other.info_ptr;
end_info = other.end_info;
other.png_ptr = nullptr;
other.info_ptr = other.end_info = nullptr;
return *this;
}
void clear() {
if (png_ptr) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
png_ptr = nullptr;
info_ptr = end_info = nullptr;
}
}
png_structp getPng() const { return png_ptr; }
png_infop getInfo() const { return info_ptr; }
png_infop getEndInfo() const { return end_info; }
private:
png_structp png_ptr; // pointer to decompression structure
png_infop info_ptr; // pointer to image information structure
png_infop end_info; // pointer to one more image information structure
};
PngPtrs m_png_ptrs;
int m_bit_depth;
void* m_png_ptr; // pointer to decompression structure
void* m_info_ptr; // pointer to image information structure
void* m_end_info; // pointer to one more image information structure
FILE* m_f;
int m_color_type;
Chunk m_chunkIHDR;
int m_frame_no;
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 +211,39 @@ public:
PngEncoder();
virtual ~PngEncoder();
bool isFormatSupported( int depth ) const CV_OVERRIDE;
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
bool isFormatSupported( int depth ) const CV_OVERRIDE;
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
bool writeanimation(const Animation& animinfo, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
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);
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;
};
}
+252 -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,138 @@ 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::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;
}
}
+11 -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,7 @@ public:
~WebPEncoder() CV_OVERRIDE;
bool write(const Mat& img, const std::vector<int>& params) CV_OVERRIDE;
bool writeanimation(const Animation& animation, const std::vector<int>& params) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
};
+2
View File
@@ -45,8 +45,10 @@
#include "grfmt_base.hpp"
#include "grfmt_avif.hpp"
#include "grfmt_bmp.hpp"
#include "grfmt_gif.hpp"
#include "grfmt_sunras.hpp"
#include "grfmt_jpeg.hpp"
#include "grfmt_jpegxl.hpp"
#include "grfmt_pxm.hpp"
#include "grfmt_pfm.hpp"
#include "grfmt_tiff.hpp"
+179 -4
View File
@@ -151,14 +151,18 @@ struct ImageCodecInitializer
*/
ImageCodecInitializer()
{
#ifdef HAVE_AVIF
decoders.push_back(makePtr<AvifDecoder>());
encoders.push_back(makePtr<AvifEncoder>());
#endif
/// BMP Support
decoders.push_back( makePtr<BmpDecoder>() );
encoders.push_back( makePtr<BmpEncoder>() );
#ifdef HAVE_IMGCODEC_GIF
decoders.push_back( makePtr<GifDecoder>() );
encoders.push_back( makePtr<GifEncoder>() );
#endif
#ifdef HAVE_AVIF
decoders.push_back(makePtr<AvifDecoder>());
encoders.push_back(makePtr<AvifEncoder>());
#endif
#ifdef HAVE_IMGCODEC_HDR
decoders.push_back( makePtr<HdrDecoder>() );
encoders.push_back( makePtr<HdrEncoder>() );
@@ -206,6 +210,10 @@ struct ImageCodecInitializer
decoders.push_back( makePtr<Jpeg2KDecoder>() );
encoders.push_back( makePtr<Jpeg2KEncoder>() );
#endif
#ifdef HAVE_JPEGXL
decoders.push_back( makePtr<JpegXLDecoder>() );
encoders.push_back( makePtr<JpegXLEncoder>() );
#endif
#ifdef HAVE_OPENJPEG
decoders.push_back( makePtr<Jpeg2KJP2OpjDecoder>() );
decoders.push_back( makePtr<Jpeg2KJ2KOpjDecoder>() );
@@ -685,6 +693,117 @@ 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)
{
int duration = decoder->animation().durations.size() > 0 ? decoder->animation().durations.back() : 1000;
animation.durations.push_back(duration);
animation.frames.push_back(mat);
}
if (!decoder->nextPage())
{
break;
}
++current;
}
animation.bgcolor = decoder->animation().bgcolor;
animation.loop_count = decoder->animation().loop_count;
return success;
}
bool 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)
{
@@ -801,6 +920,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 )
{
@@ -1427,6 +1595,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. */
+5 -1
View File
@@ -137,7 +137,11 @@ uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette );
CV_INLINE bool isBigEndian( void )
{
return (((const int*)"\0\x1\x2\x3\x4\x5\x6\x7")[0] & 255) != 0;
#ifdef WORDS_BIGENDIAN
return true;
#else
return false;
#endif
}
} // namespace
+536
View File
@@ -0,0 +1,536 @@
// 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_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)
{
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_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;
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
+4 -1
View File
@@ -2,6 +2,9 @@
// 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 <string>
#include <vector>
#include "test_precomp.hpp"
namespace opencv_test { namespace {
@@ -110,7 +113,7 @@ TEST_P(Exif, exif_orientation)
}
}
const string exif_files[] =
const std::vector<std::string> exif_files
{
#ifdef HAVE_JPEG
"readwrite/testExifOrientation_1.jpg",
+7 -2
View File
@@ -35,7 +35,8 @@ TEST(Imgcodecs_EXR, readWrite_32FC1)
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
// Check generated file size to ensure that it's compressed with proper options
ASSERT_EQ(396u, getFileSize(filenameOutput));
ASSERT_LE(396u, getFileSize(filenameOutput)); // OpenEXR 2
ASSERT_LE( getFileSize(filenameOutput), 440u); // OpenEXR 3.2+
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
ASSERT_EQ(img2.type(), img.type());
ASSERT_EQ(img2.size(), img.size());
@@ -199,7 +200,11 @@ TEST(Imgcodecs_EXR, read_YC_changeDepth)
cvtColor(img_rgb, img_rgb, COLOR_RGB2BGR);
EXPECT_TRUE(cvtest::norm(img, img_rgb, NORM_INF) == 0);
// See https://github.com/opencv/opencv/issues/26705
// If ALGO_HINT_ACCURATE is set, norm should be 0.
// If ALGO_HINT_APPROX is set, norm should be 1(or 0).
EXPECT_LE(cvtest::norm(img, img_rgb, NORM_INF),
(cv::getDefaultAlgorithmHint() == ALGO_HINT_ACCURATE)?0:1);
// Cannot test writing, EXR encoder doesn't support 8U depth
}
+357
View File
@@ -0,0 +1,357 @@
// 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"
#ifdef HAVE_IMGCODEC_GIF
namespace opencv_test { namespace {
const string gifsuite_files_multi[]={
"basi3p01",
"basi3p02",
"basi3p04",
"basn3p01",
"basn3p02",
"basn3p04",
"ccwn3p08",
"ch1n3p04",
"cs3n3p08",
"cs5n3p08",
"cs8n3p08",
"g03n3p04",
"g04n3p04",
"g05n3p04",
"g07n3p04",
"g10n3p04",
"g25n3p04",
"s32i3p04",
"s32n3p04",
"tp0n3p08",
};
const string gifsuite_files_read_single[] = {
"basi3p01",
"basi3p02",
"basi3p04",
"basn3p01",
"basn3p02",
"basn3p04",
"ccwn3p08",
"cdfn2c08",
"cdhn2c08",
"cdsn2c08",
"cdun2c08",
"ch1n3p04",
"cs3n3p08",
"cs5n2c08",
"cs5n3p08",
"cs8n2c08",
"cs8n3p08",
"exif2c08",
"g03n2c08",
"g03n3p04",
"g04n2c08",
"g04n3p04",
"g05n2c08",
"g05n3p04",
"g07n2c08",
"g07n3p04",
"g10n2c08",
"g10n3p04"
};
const string gifsuite_files_read_write_suite[]={
"g25n2c08",
"g25n3p04",
"s01i3p01",
"s01n3p01",
"s02i3p01",
"s02n3p01",
"s03i3p01",
"s03n3p01",
"s04i3p01",
"s04n3p01",
"s05i3p02",
"s05n3p02",
"s06i3p02",
"s06n3p02",
"s07i3p02",
"s07n3p02",
"s08i3p02",
"s08n3p02",
"s09i3p02",
"s09n3p02",
"s32i3p04",
"s32n3p04",
"s33i3p04",
"s33n3p04",
"s34i3p04",
"s34n3p04",
"s35i3p04",
"s35n3p04",
"s36i3p04",
"s36n3p04",
"s37i3p04",
"s37n3p04",
"s38i3p04",
"s38n3p04",
"s39i3p04",
"s39n3p04",
"s40i3p04",
"s40n3p04",
"tp0n3p08",
};
const std::pair<string,int> gifsuite_files_bgra[]={
make_pair("gif_bgra1",53287),
make_pair("gif_bgra2",52651),
make_pair("gif_bgra3",54809),
make_pair("gif_bgra4",57562),
make_pair("gif_bgra5",56733),
make_pair("gif_bgra6",52110),
};
TEST(Imgcodecs_Gif, read_gif_multi)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "gifsuite/gif_multi.gif";
vector<cv::Mat> img_vec_8UC4;
ASSERT_NO_THROW(cv::imreadmulti(filename, img_vec_8UC4,0,20,IMREAD_UNCHANGED));
EXPECT_EQ(img_vec_8UC4.size(), imcount(filename));
vector<cv::Mat> img_vec_8UC3;
for(const auto & i : img_vec_8UC4){
cv::Mat img_tmp;
cvtColor(i,img_tmp,COLOR_BGRA2BGR);
img_vec_8UC3.push_back(img_tmp);
}
const long unsigned int expected_size=20;
EXPECT_EQ(img_vec_8UC3.size(),expected_size);
for(long unsigned int i=0;i<img_vec_8UC3.size();i++){
cv::Mat img=img_vec_8UC3[i];
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
cv::Mat img_png;
ASSERT_NO_THROW(img_png = imread(png_filename,IMREAD_UNCHANGED));
ASSERT_FALSE(img_png.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_png);
}
}
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_SingleFrame;
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame, read_gif_single)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "gifsuite/" + GetParam() + ".gif";
const string png_filename=root + "pngsuite/" + GetParam() + ".png";
const long unsigned int expected_size = 1;
EXPECT_EQ(expected_size, imcount(filename));
cv::Mat img_8UC4;
ASSERT_NO_THROW(img_8UC4 = cv::imread(filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_8UC4.empty());
cv::Mat img_8UC3;
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3, COLOR_BGRA2BGR));
cv::Mat img_png;
ASSERT_NO_THROW(img_png = cv::imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_png.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame,
testing::ValuesIn(gifsuite_files_read_single));
TEST(Imgcodecs_Gif, read_gif_big){
const string root = cvtest::TS::ptr()->get_data_path();
const string gif_filename = root + "gifsuite/gif_big.gif";
const string png_filename = root + "gifsuite/gif_big.png";
cv::Mat img_8UC4;
ASSERT_NO_THROW(img_8UC4 = imread(gif_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_8UC4.empty());
cv::Mat img_8UC3;
const int expected_col=1303;
const int expected_row=1391;
EXPECT_EQ(expected_col, img_8UC4.cols);
EXPECT_EQ(expected_row, img_8UC4.rows);
ASSERT_NO_THROW(cvtColor(img_8UC4, img_8UC3,COLOR_BGRA2BGR));
EXPECT_EQ(expected_col, img_8UC3.cols);
EXPECT_EQ(expected_row, img_8UC3.rows);
cv::Mat img_png;
ASSERT_NO_THROW(img_png=imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_png.empty());
cv::Mat img_png_8UC3;
ASSERT_NO_THROW(cvtColor(img_png,img_png_8UC3, COLOR_BGRA2BGR));
EXPECT_EQ(img_8UC3.size, img_png_8UC3.size);
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img_8UC3, img_png_8UC3);
}
typedef testing::TestWithParam<std::pair<string,int>> Imgcodecs_Gif_GifSuite_SingleFrame_BGRA;
TEST_P(Imgcodecs_Gif_GifSuite_SingleFrame_BGRA, read_gif_single_bgra){
const string root = cvtest::TS::ptr()->get_data_path();
const string gif_filename = root + "gifsuite/" + GetParam().first + ".gif";
const string png_filename = root + "gifsuite/" + GetParam().first + ".png";
cv::Mat gif_img;
cv::Mat png_img;
ASSERT_NO_THROW(gif_img = cv::imread(gif_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(gif_img.empty());
ASSERT_NO_THROW(png_img = cv::imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(png_img.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img, png_img);
int transparent_count = 0;
for(int i=0; i<gif_img.rows; i++){
for(int j=0; j<gif_img.cols; j++){
cv::Vec4b pixel1 = gif_img.at<cv::Vec4b>(i,j);
if((int)(pixel1[3]) == 0){
transparent_count++;
}
}
}
EXPECT_EQ(transparent_count,GetParam().second);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_SingleFrame_BGRA ,
testing::ValuesIn(gifsuite_files_bgra));
TEST(Imgcodecs_Gif,read_gif_multi_bgra){
const string root = cvtest::TS::ptr()->get_data_path();
const string gif_filename = root + "gifsuite/gif_multi_bgra.gif";
vector<cv::Mat> img_vec;
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec, IMREAD_UNCHANGED));
EXPECT_EQ(imcount(gif_filename), img_vec.size());
const int fixed_transparent_count = 53211;
for(auto & frame_count : img_vec){
int transparent_count=0;
for(int i=0; i<frame_count.rows; i++){
for(int j=0; j<frame_count.cols; j++){
cv::Vec4b pixel1 = frame_count.at<cv::Vec4b>(i,j);
if((int)(pixel1[3]) == 0){
transparent_count++;
}
}
}
EXPECT_EQ(fixed_transparent_count,transparent_count);
}
}
TEST(Imgcodecs_Gif, read_gif_special){
const string root = cvtest::TS::ptr()->get_data_path();
const string gif_filename1 = root + "gifsuite/special1.gif";
const string png_filename1 = root + "gifsuite/special1.png";
const string gif_filename2 = root + "gifsuite/special2.gif";
const string png_filename2 = root + "gifsuite/special2.png";
cv::Mat gif_img1;
ASSERT_NO_THROW(gif_img1 = cv::imread(gif_filename1,IMREAD_UNCHANGED));
ASSERT_FALSE(gif_img1.empty());
cv::Mat png_img1;
ASSERT_NO_THROW(png_img1 = cv::imread(png_filename1,IMREAD_UNCHANGED));
ASSERT_FALSE(png_img1.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img1, png_img1);
cv::Mat gif_img2;
ASSERT_NO_THROW(gif_img2 = cv::imread(gif_filename2,IMREAD_UNCHANGED));
ASSERT_FALSE(gif_img2.empty());
cv::Mat png_img2;
ASSERT_NO_THROW(png_img2 = cv::imread(png_filename2,IMREAD_UNCHANGED));
ASSERT_FALSE(png_img2.empty());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), gif_img2, png_img2);
}
TEST(Imgcodecs_Gif,write_gif_flags){
const string root = cvtest::TS::ptr()->get_data_path();
const string png_filename = root + "gifsuite/special1.png";
vector<uchar> buff;
const int expected_rows=611;
const int expected_cols=293;
Mat img_gt = Mat::ones(expected_rows, expected_cols, CV_8UC1);
vector<int> param;
param.push_back(IMWRITE_GIF_QUALITY);
param.push_back(7);
param.push_back(IMWRITE_GIF_DITHER);
param.push_back(2);
EXPECT_NO_THROW(imencode(".png", img_gt, buff, param));
Mat img;
EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang
EXPECT_FALSE(img.empty());
EXPECT_EQ(img.cols, expected_cols);
EXPECT_EQ(img.rows, expected_rows);
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), img, img_gt);
}
TEST(Imgcodecs_Gif, write_gif_big) {
const string root = cvtest::TS::ptr()->get_data_path();
const string png_filename = root + "gifsuite/gif_big.png";
const string gif_filename = cv::tempfile(".png");
cv::Mat img;
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img.empty());
EXPECT_EQ(1303, img.cols);
EXPECT_EQ(1391, img.rows);
ASSERT_NO_THROW(imwrite(gif_filename, img));
cv::Mat img_gif;
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_gif.empty());
EXPECT_EQ(1303, img_gif.cols);
EXPECT_EQ(1391, img_gif.rows);
EXPECT_EQ(0, remove(gif_filename.c_str()));
}
typedef testing::TestWithParam<string> Imgcodecs_Gif_GifSuite_Read_Write_Suite;
TEST_P(Imgcodecs_Gif_GifSuite_Read_Write_Suite ,read_gif_single)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string png_filename = root + "pngsuite/"+GetParam()+".png";
const string gif_filename = cv::tempfile(".gif");
cv::Mat img;
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img.empty());
vector<int> param;
param.push_back(IMWRITE_GIF_QUALITY);
param.push_back(8);
param.push_back(IMWRITE_GIF_DITHER);
param.push_back(3);
ASSERT_NO_THROW(imwrite(gif_filename, img, param));
cv::Mat img_gif;
ASSERT_NO_THROW(img_gif = cv::imread(gif_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img_gif.empty());
cv::Mat img_8UC3;
ASSERT_NO_THROW(cv::cvtColor(img_gif, img_8UC3, COLOR_BGRA2BGR));
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img, img_8UC3);
EXPECT_EQ(0, remove(gif_filename.c_str()));
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Gif_GifSuite_Read_Write_Suite ,
testing::ValuesIn(gifsuite_files_read_write_suite));
TEST(Imgcodecs_Gif, write_gif_multi) {
const string root = cvtest::TS::ptr()->get_data_path();
const string gif_filename = cv::tempfile(".gif");
vector<cv::Mat> img_vec;
for (long unsigned int i = 0; i < 20; i++) {
const string png_filename = root + "pngsuite/" + gifsuite_files_multi[i] + ".png";
cv::Mat img;
ASSERT_NO_THROW(img = cv::imread(png_filename, IMREAD_UNCHANGED));
ASSERT_FALSE(img.empty());
img_vec.push_back(img);
}
vector<int> param;
param.push_back(IMWRITE_GIF_QUALITY);
param.push_back(8);
param.push_back(IMWRITE_GIF_DITHER);
param.push_back(3);
ASSERT_NO_THROW(cv::imwritemulti(gif_filename, img_vec, param));
vector<cv::Mat> img_vec_gif;
ASSERT_NO_THROW(cv::imreadmulti(gif_filename, img_vec_gif));
EXPECT_EQ(img_vec.size(), img_vec_gif.size());
for (long unsigned int i = 0; i < img_vec.size(); i++) {
cv::Mat img_8UC3;
ASSERT_NO_THROW(cv::cvtColor(img_vec_gif[i], img_8UC3, COLOR_BGRA2BGR));
EXPECT_PRED_FORMAT2(cvtest::MatComparator(29, 0), img_vec[i], img_8UC3);
}
EXPECT_EQ(0, remove(gif_filename.c_str()));
}
}//opencv_test
}//namespace
#endif
+186
View File
@@ -0,0 +1,186 @@
// 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 {
#ifdef HAVE_JPEGXL
typedef tuple<perf::MatType, int> MatType_and_Distance;
typedef testing::TestWithParam<MatType_and_Distance> Imgcodecs_JpegXL_MatType;
TEST_P(Imgcodecs_JpegXL_MatType, write_read)
{
const int matType = get<0>(GetParam());
const int distanceParam = get<1>(GetParam());
cv::Scalar col;
// Jpeg XL is lossy compression.
// There may be small differences in decoding results by environments.
double th;
switch( CV_MAT_DEPTH(matType) )
{
case CV_16U:
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
th = 656; // = 65535 / 100;
break;
case CV_32F:
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
th = 1.0 / 100.0;
break;
default:
case CV_8U:
col = cv::Scalar(124, 76, 42, 192);
th = 3; // = 255 / 100 (1%);
break;
}
// If increasing distanceParam, threshold should be increased.
th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1;
bool ret = false;
string tmp_fname = cv::tempfile(".jxl");
Mat img_org(320, 480, matType, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_DISTANCE);
param.push_back(distanceParam);
EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
EXPECT_EQ(0, remove(tmp_fname.c_str()));
}
TEST_P(Imgcodecs_JpegXL_MatType, encode_decode)
{
const int matType = get<0>(GetParam());
const int distanceParam = get<1>(GetParam());
cv::Scalar col;
// Jpeg XL is lossy compression.
// There may be small differences in decoding results by environments.
double th;
// If alpha=0, libjxl modify color channels(BGR). So do not set it.
switch( CV_MAT_DEPTH(matType) )
{
case CV_16U:
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
th = 656; // = 65535 / 100;
break;
case CV_32F:
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
th = 1.0 / 100.0;
break;
default:
case CV_8U:
col = cv::Scalar(124, 76, 42, 192);
th = 3; // = 255 / 100 (1%);
break;
}
// If increasing distanceParam, threshold should be increased.
th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1;
bool ret = false;
vector<uchar> buff;
Mat img_org(320, 480, matType, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_DISTANCE);
param.push_back(distanceParam);
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
}
INSTANTIATE_TEST_CASE_P(
/**/,
Imgcodecs_JpegXL_MatType,
testing::Combine(
testing::Values(
CV_8UC1, CV_8UC3, CV_8UC4,
CV_16UC1, CV_16UC3, CV_16UC4,
CV_32FC1, CV_32FC3, CV_32FC4
),
testing::Values( // Distance
0, // Lossless
1, // Default
3, // Recomended Lossy Max
25 // Specification Max
)
) );
typedef tuple<int, int> Effort_and_Decoding_speed;
typedef testing::TestWithParam<Effort_and_Decoding_speed> Imgcodecs_JpegXL_Effort_DecodingSpeed;
TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode)
{
const int effort = get<0>(GetParam());
const int speed = get<1>(GetParam());
cv::Scalar col = cv::Scalar(124,76,42);
// Jpeg XL is lossy compression.
// There may be small differences in decoding results by environments.
double th = 3; // = 255 / 100 (1%);
bool ret = false;
vector<uchar> buff;
Mat img_org(320, 480, CV_8UC3, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_EFFORT);
param.push_back(effort);
param.push_back(IMWRITE_JPEGXL_DECODING_SPEED);
param.push_back(speed);
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
}
INSTANTIATE_TEST_CASE_P(
/**/,
Imgcodecs_JpegXL_Effort_DecodingSpeed,
testing::Combine(
testing::Values( // Effort
1, // fastest
7, // default
9 // slowest
),
testing::Values( // Decoding Speed
0, // default, slowest, and best quality/density
2,
4 // fastest, at the cost of some qulity/density
)
) );
TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image)
{
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
cv::Mat roi = src(cv::Rect(10,20,30,50));
EXPECT_FALSE(roi.isContinuous()); // uncontinued image
vector<uint8_t> buff;
vector<int> param;
bool ret = false;
EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param));
EXPECT_TRUE(ret);
}
#endif // HAVE_JPEGXL
} // namespace
} // namespace opencv_test
@@ -157,6 +157,9 @@ const string exts[] = {
#ifdef HAVE_JPEG
"jpg",
#endif
#ifdef HAVE_JPEGXL
"jxl",
#endif
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|| defined(HAVE_OPENJPEG)
"jp2",
@@ -238,6 +241,8 @@ TEST_P(Imgcodecs_Image, read_write_BGR)
double psnrThreshold = 100;
if (ext == "jpg")
psnrThreshold = 32;
if (ext == "jxl")
psnrThreshold = 30;
#if defined(HAVE_JASPER)
if (ext == "jp2")
psnrThreshold = 95;
@@ -268,6 +273,8 @@ TEST_P(Imgcodecs_Image, read_write_GRAYSCALE)
double psnrThreshold = 100;
if (ext == "jpg")
psnrThreshold = 40;
if (ext == "jxl")
psnrThreshold = 40;
#if defined(HAVE_JASPER)
if (ext == "jp2")
psnrThreshold = 70;
+20 -21
View File
@@ -1,29 +1,16 @@
// 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
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
namespace opencv_test { namespace {
#ifdef HAVE_WEBP
TEST(Imgcodecs_WebP, encode_decode_lossless_webp)
static void readFileBytes(const std::string& fname, std::vector<unsigned char>& buf)
{
const string root = cvtest::TS::ptr()->get_data_path();
string filename = root + "../cv/shared/lena.png";
cv::Mat img = cv::imread(filename);
ASSERT_FALSE(img.empty());
string output = cv::tempfile(".webp");
EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless
cv::Mat img_webp = cv::imread(output);
std::vector<unsigned char> buf;
FILE * wfile = NULL;
wfile = fopen(output.c_str(), "rb");
FILE * wfile = fopen(fname.c_str(), "rb");
if (wfile != NULL)
{
fseek(wfile, 0, SEEK_END);
@@ -39,12 +26,24 @@ TEST(Imgcodecs_WebP, encode_decode_lossless_webp)
fclose(wfile);
}
if (data_size != wfile_size)
{
EXPECT_TRUE(false);
}
EXPECT_EQ(data_size, wfile_size);
}
}
TEST(Imgcodecs_WebP, encode_decode_lossless_webp)
{
const string root = cvtest::TS::ptr()->get_data_path();
string filename = root + "../cv/shared/lena.png";
cv::Mat img = cv::imread(filename);
ASSERT_FALSE(img.empty());
string output = cv::tempfile(".webp");
EXPECT_NO_THROW(cv::imwrite(output, img)); // lossless
cv::Mat img_webp = cv::imread(output);
std::vector<unsigned char> buf;
readFileBytes(output, buf);
EXPECT_EQ(0, remove(output.c_str()));
cv::Mat decode = cv::imdecode(buf, IMREAD_COLOR);