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:
@@ -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
|
||||
|
||||
@@ -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> ¶ms) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
return writeToOutput(img_vec, params);
|
||||
return writemulti(img_vec, params);
|
||||
}
|
||||
|
||||
bool AvifEncoder::writemulti(const std::vector<Mat> &img_vec,
|
||||
const std::vector<int> ¶ms) {
|
||||
return writeToOutput(img_vec, params);
|
||||
}
|
||||
|
||||
bool AvifEncoder::writeToOutput(const std::vector<Mat> &img_vec,
|
||||
const std::vector<int> ¶ms) {
|
||||
bool AvifEncoder::writeanimation(const Animation& animation,
|
||||
const std::vector<int> ¶ms) {
|
||||
int bit_depth = 8;
|
||||
int speed = AVIF_SPEED_FASTEST;
|
||||
for (size_t i = 0; i < params.size(); i += 2) {
|
||||
@@ -340,12 +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) {
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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. */
|
||||
@@ -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_*/
|
||||
+1343
-98
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -44,17 +44,15 @@
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "grfmt_webp.hpp"
|
||||
|
||||
#include "opencv2/imgproc.hpp"
|
||||
|
||||
#include <opencv2/core/utils/logger.hpp>
|
||||
#include <opencv2/core/utils/configuration.private.hpp>
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
#include <webp/demux.h>
|
||||
#include <webp/mux.h>
|
||||
|
||||
namespace cv
|
||||
{
|
||||
@@ -67,12 +65,18 @@ static const size_t WEBP_HEADER_SIZE = 32;
|
||||
WebPDecoder::WebPDecoder()
|
||||
{
|
||||
m_buf_supported = true;
|
||||
channels = 0;
|
||||
fs_size = 0;
|
||||
m_has_animation = false;
|
||||
m_previous_timestamp = 0;
|
||||
}
|
||||
|
||||
WebPDecoder::~WebPDecoder() {}
|
||||
|
||||
void WebPDecoder::UniquePtrDeleter::operator()(WebPAnimDecoder* decoder) const
|
||||
{
|
||||
WebPAnimDecoderDelete(decoder);
|
||||
}
|
||||
|
||||
size_t WebPDecoder::signatureLength() const
|
||||
{
|
||||
return WEBP_HEADER_SIZE;
|
||||
@@ -102,6 +106,11 @@ ImageDecoder WebPDecoder::newDecoder() const
|
||||
|
||||
bool WebPDecoder::readHeader()
|
||||
{
|
||||
if (m_has_animation)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t header[WEBP_HEADER_SIZE] = { 0 };
|
||||
if (m_buf.empty())
|
||||
{
|
||||
@@ -124,28 +133,49 @@ bool WebPDecoder::readHeader()
|
||||
}
|
||||
|
||||
WebPBitstreamFeatures features;
|
||||
if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features))
|
||||
if (VP8_STATUS_OK < WebPGetFeatures(header, sizeof(header), &features)) return false;
|
||||
|
||||
m_has_animation = features.has_animation == 1;
|
||||
|
||||
if (m_has_animation)
|
||||
{
|
||||
CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images");
|
||||
|
||||
m_width = features.width;
|
||||
m_height = features.height;
|
||||
|
||||
if (features.has_alpha)
|
||||
if (m_buf.empty())
|
||||
{
|
||||
m_type = CV_8UC4;
|
||||
channels = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_type = CV_8UC3;
|
||||
channels = 3;
|
||||
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
|
||||
data.create(1, validateToInt(fs_size), CV_8UC1);
|
||||
fs.read((char*)data.ptr(), fs_size);
|
||||
CV_Assert(fs && "Can't read file data");
|
||||
fs.close();
|
||||
}
|
||||
|
||||
return true;
|
||||
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
|
||||
|
||||
WebPData webp_data;
|
||||
webp_data.bytes = (const uint8_t*)data.ptr();
|
||||
webp_data.size = data.total();
|
||||
|
||||
WebPAnimDecoderOptions dec_options;
|
||||
WebPAnimDecoderOptionsInit(&dec_options);
|
||||
|
||||
dec_options.color_mode = m_use_rgb ? MODE_RGBA : MODE_BGRA;
|
||||
anim_decoder.reset(WebPAnimDecoderNew(&webp_data, &dec_options));
|
||||
CV_Assert(anim_decoder.get() && "Error parsing image");
|
||||
|
||||
WebPAnimInfo anim_info;
|
||||
WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info);
|
||||
m_animation.loop_count = anim_info.loop_count;
|
||||
|
||||
m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF;
|
||||
m_animation.bgcolor[1] = (anim_info.bgcolor >> 16) & 0xFF;
|
||||
m_animation.bgcolor[2] = (anim_info.bgcolor >> 8) & 0xFF;
|
||||
m_animation.bgcolor[3] = anim_info.bgcolor & 0xFF;
|
||||
m_frame_count = anim_info.frame_count;
|
||||
}
|
||||
m_width = features.width;
|
||||
m_height = features.height;
|
||||
m_type = features.has_alpha ? CV_8UC4 : CV_8UC3;
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPDecoder::readData(Mat &img)
|
||||
@@ -155,7 +185,7 @@ bool WebPDecoder::readData(Mat &img)
|
||||
CV_CheckEQ(img.cols, m_width, "");
|
||||
CV_CheckEQ(img.rows, m_height, "");
|
||||
|
||||
if (m_buf.empty())
|
||||
if (data.empty())
|
||||
{
|
||||
fs.seekg(0, std::ios::beg); CV_Assert(fs && "File stream error");
|
||||
data.create(1, validateToInt(fs_size), CV_8UC1);
|
||||
@@ -165,70 +195,96 @@ bool WebPDecoder::readData(Mat &img)
|
||||
}
|
||||
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
|
||||
|
||||
Mat read_img;
|
||||
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
|
||||
if (img.type() != m_type || img.cols != m_width || img.rows != m_height)
|
||||
{
|
||||
Mat read_img;
|
||||
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
|
||||
if (img.type() != m_type)
|
||||
read_img.create(m_height, m_width, m_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_img = img; // copy header
|
||||
}
|
||||
|
||||
uchar* out_data = read_img.ptr();
|
||||
size_t out_data_size = read_img.dataend - out_data;
|
||||
|
||||
uchar* res_ptr = NULL;
|
||||
|
||||
if (m_has_animation)
|
||||
{
|
||||
uint8_t* buf;
|
||||
int timestamp;
|
||||
|
||||
WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp);
|
||||
Mat tmp(Size(m_width, m_height), CV_8UC4, buf);
|
||||
|
||||
if (img.type() == CV_8UC1)
|
||||
{
|
||||
read_img.create(m_height, m_width, m_type);
|
||||
cvtColor(tmp, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
else
|
||||
if (img.type() == CV_8UC3)
|
||||
{
|
||||
read_img = img; // copy header
|
||||
}
|
||||
|
||||
uchar* out_data = read_img.ptr();
|
||||
size_t out_data_size = read_img.dataend - out_data;
|
||||
|
||||
uchar *res_ptr = NULL;
|
||||
if (channels == 3)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC3, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
else if (channels == 4)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC4, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
|
||||
if (res_ptr != out_data)
|
||||
return false;
|
||||
|
||||
if (read_img.data == img.data && img.type() == m_type)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
else if (img.type() == CV_8UC1)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
else if (img.type() == CV_8UC3 && m_type == CV_8UC4)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
else if (img.type() == CV_8UC4 && m_type == CV_8UC3)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGR2BGRA);
|
||||
cvtColor(tmp, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
tmp.copyTo(img);
|
||||
|
||||
m_animation.durations.push_back(timestamp - m_previous_timestamp);
|
||||
m_previous_timestamp = timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_type == CV_8UC3)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC3, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
else if (m_type == CV_8UC4)
|
||||
{
|
||||
CV_CheckTypeEQ(read_img.type(), CV_8UC4, "");
|
||||
if (m_use_rgb)
|
||||
res_ptr = WebPDecodeRGBAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
else
|
||||
res_ptr = WebPDecodeBGRAInto(data.ptr(), data.total(), out_data,
|
||||
(int)out_data_size, (int)read_img.step);
|
||||
}
|
||||
|
||||
if (res_ptr != out_data)
|
||||
return false;
|
||||
|
||||
if (read_img.data == img.data && img.type() == m_type)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
else if (img.type() == CV_8UC1)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGR2GRAY);
|
||||
}
|
||||
else if (img.type() == CV_8UC3 && m_type == CV_8UC4)
|
||||
{
|
||||
cvtColor(read_img, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(Error::StsInternal, "");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebPDecoder::nextPage()
|
||||
{
|
||||
// Prepare the next page, if any.
|
||||
return WebPAnimDecoderHasMoreFrames(anim_decoder.get()) > 0;
|
||||
}
|
||||
|
||||
WebPEncoder::WebPEncoder()
|
||||
{
|
||||
m_description = "WebP files (*.webp)";
|
||||
@@ -312,23 +368,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user