mirror of
https://github.com/opencv/opencv.git
synced 2026-07-31 08:13:04 +04:00
Merge branch 4.x
This commit is contained in:
@@ -23,11 +23,6 @@ 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})
|
||||
|
||||
@@ -251,6 +251,15 @@ enum ImwriteGIFCompressionFlags {
|
||||
IMWRITE_GIF_COLORTABLE_SIZE_256 = 8
|
||||
};
|
||||
|
||||
enum ImageMetadataType
|
||||
{
|
||||
IMAGE_METADATA_UNKNOWN = -1,
|
||||
IMAGE_METADATA_EXIF = 0,
|
||||
IMAGE_METADATA_XMP = 1,
|
||||
IMAGE_METADATA_ICCP = 2,
|
||||
IMAGE_METADATA_MAX = 2
|
||||
};
|
||||
|
||||
//! @} imgcodecs_flags
|
||||
|
||||
/** @brief Represents an animation with multiple frames.
|
||||
@@ -277,6 +286,8 @@ struct CV_EXPORTS_W_SIMPLE Animation
|
||||
CV_PROP_RW std::vector<int> durations;
|
||||
//! Vector of frames, where each Mat represents a single frame.
|
||||
CV_PROP_RW std::vector<Mat> frames;
|
||||
//! image that can be used for the format in addition to the animation or if animation is not supported in the reader (like in PNG).
|
||||
CV_PROP_RW Mat still_image;
|
||||
|
||||
/** @brief Constructs an Animation object with optional loop count and background color.
|
||||
|
||||
@@ -358,6 +369,17 @@ The image passing through the img parameter can be pre-allocated. The memory is
|
||||
*/
|
||||
CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR );
|
||||
|
||||
/** @brief Reads an image from a file together with associated metadata.
|
||||
|
||||
The function imreadWithMetadata reads image from the specified file. It does the same thing as imread, but additionally reads metadata if the corresponding file contains any.
|
||||
@param filename Name of the file to be loaded.
|
||||
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
|
||||
@param flags Flag that can take values of cv::ImreadModes
|
||||
*/
|
||||
CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @brief Loads a multi-page image from a file.
|
||||
|
||||
The function imreadmulti loads a multi-page image from the specified file into a vector of Mat objects.
|
||||
@@ -462,7 +484,7 @@ filename extension (see cv::imread for the list of extensions). In general, only
|
||||
single-channel or 3-channel (with 'BGR' channel order) images
|
||||
can be saved using this function, with these exceptions:
|
||||
|
||||
- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved.
|
||||
- With OpenEXR encoder, only 32-bit float (CV_32F) images can be saved. More than 4 channels can be saved. (imread can load it then.)
|
||||
- 8-bit unsigned (CV_8U) images are not supported.
|
||||
- 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).
|
||||
@@ -507,6 +529,20 @@ It also demonstrates how to save multiple images in a TIFF file:
|
||||
CV_EXPORTS_W bool imwrite( const String& filename, InputArray img,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Saves an image to a specified file with metadata
|
||||
|
||||
The function imwriteWithMetadata saves the image to the specified file. It does the same thing as imwrite, but additionally writes metadata if the corresponding format supports it.
|
||||
@param filename Name of the file. As with imwrite, image format is determined by the file extension.
|
||||
@param img (Mat or vector of Mat) Image or Images to be saved.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags
|
||||
*/
|
||||
CV_EXPORTS_W bool imwriteWithMetadata( const String& filename, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays& metadata,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
//! @brief multi-image overload for bindings
|
||||
CV_WRAP static inline
|
||||
bool imwritemulti(const String& filename, InputArrayOfArrays img,
|
||||
@@ -528,6 +564,22 @@ See cv::imread for the list of supported formats and flags description.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imdecode( InputArray buf, int flags );
|
||||
|
||||
/** @brief Reads an image from a buffer in memory together with associated metadata.
|
||||
|
||||
The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or
|
||||
contains invalid data, the function returns an empty matrix ( Mat::data==NULL ).
|
||||
|
||||
See cv::imread for the list of supported formats and flags description.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param buf Input array or vector of bytes.
|
||||
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
|
||||
@param flags The same flags as in cv::imread, see cv::ImreadModes.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR );
|
||||
|
||||
/** @overload
|
||||
@param buf Input array or vector of bytes.
|
||||
@param flags The same flags as in cv::imread, see cv::ImreadModes.
|
||||
@@ -566,6 +618,24 @@ CV_EXPORTS_W bool imencode( const String& ext, InputArray img,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes an image into a memory buffer.
|
||||
|
||||
The function imencode compresses the image and stores it in the memory buffer that is resized to fit the
|
||||
result. See cv::imwrite for the list of supported formats and flags description.
|
||||
|
||||
@param ext File extension that defines the output format. Must include a leading period.
|
||||
@param img Image to be compressed.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param buf Output buffer resized to fit the compressed image.
|
||||
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencodeWithMetadata( const String& ext, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays metadata,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes array of images into a memory buffer.
|
||||
|
||||
The function is analog to cv::imencode for in-memory multi-page image compression.
|
||||
@@ -589,7 +659,7 @@ This can be useful for verifying support for a given image format before attempt
|
||||
@return true if an image reader for the specified file is available and the file can be opened, false otherwise.
|
||||
|
||||
@note The function checks the availability of image codecs that are either built into OpenCV or dynamically loaded.
|
||||
It does not check for the actual existence of the file but rather the ability to read the specified file type.
|
||||
It does not load the image codec implementation and decode data, but uses signature check.
|
||||
If the file cannot be opened or the format is unsupported, the function will return false.
|
||||
|
||||
@sa cv::haveImageWriter, cv::imread, cv::imdecode
|
||||
|
||||
@@ -15,6 +15,10 @@ import java.util.List;
|
||||
public class ImgcodecsTest extends OpenCVTestCase {
|
||||
|
||||
public void testAnimation() {
|
||||
if (!Imgcodecs.haveImageWriter("*.apng")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mat src = Imgcodecs.imread(OpenCVTestRunner.LENA_PATH, Imgcodecs.IMREAD_REDUCED_COLOR_4);
|
||||
assertFalse(src.empty());
|
||||
|
||||
|
||||
@@ -94,6 +94,10 @@ ExifEntry_t ExifReader::getTag(const ExifTagName tag) const
|
||||
return entry;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& ExifReader::getData() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parsing the exif data buffer and prepare (internal) exif directory
|
||||
|
||||
@@ -175,6 +175,10 @@ public:
|
||||
*/
|
||||
ExifEntry_t getTag( const ExifTagName tag ) const;
|
||||
|
||||
/**
|
||||
* @brief Get the whole exif buffer
|
||||
*/
|
||||
const std::vector<unsigned char>& getData() const;
|
||||
|
||||
private:
|
||||
std::vector<unsigned char> m_data;
|
||||
|
||||
@@ -68,8 +68,8 @@ avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *ma
|
||||
return avifImageYUVToRGB(image, &rgba);
|
||||
}
|
||||
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
||||
int bit_depth) {
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_depth,
|
||||
const std::vector<std::vector<uchar> >& metadata) {
|
||||
CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U);
|
||||
|
||||
const int width = img.cols;
|
||||
@@ -112,6 +112,33 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
||||
result->yuvRange = AVIF_RANGE_FULL;
|
||||
}
|
||||
|
||||
if (!metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = metadata[IMAGE_METADATA_EXIF];
|
||||
const std::vector<uchar>& metadata_xmp = metadata[IMAGE_METADATA_XMP];
|
||||
const std::vector<uchar>& metadata_iccp = metadata[IMAGE_METADATA_ICCP];
|
||||
#if AVIF_VERSION_MAJOR >= 1
|
||||
if ((!metadata_exif.empty() &&
|
||||
avifImageSetMetadataExif(result, (const uint8_t *)metadata_exif.data(),
|
||||
metadata_exif.size()) != AVIF_RESULT_OK) ||
|
||||
(!metadata_xmp.empty() &&
|
||||
avifImageSetMetadataXMP(result, (const uint8_t *)metadata_xmp.data(),
|
||||
metadata_xmp.size()) != AVIF_RESULT_OK) ||
|
||||
(!metadata_iccp.empty() &&
|
||||
avifImageSetProfileICC(result, (const uint8_t *)metadata_iccp.data(),
|
||||
metadata_iccp.size()) != AVIF_RESULT_OK)) {
|
||||
avifImageDestroy(result);
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
if (!metadata_exif.empty())
|
||||
avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size());
|
||||
if (!metadata_xmp.empty())
|
||||
avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size());
|
||||
if (!metadata_iccp.empty())
|
||||
avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
avifRGBImage rgba;
|
||||
avifRGBImageSetDefaults(&rgba, result);
|
||||
if (img.channels() == 3) {
|
||||
@@ -120,7 +147,7 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
||||
CV_Assert(img.channels() == 4);
|
||||
rgba.format = AVIF_RGB_FORMAT_BGRA;
|
||||
}
|
||||
rgba.rowBytes = img.step[0];
|
||||
rgba.rowBytes = (uint32_t)img.step[0];
|
||||
rgba.depth = bit_depth;
|
||||
rgba.pixels =
|
||||
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(img.data));
|
||||
@@ -287,6 +314,10 @@ bool AvifDecoder::nextPage() {
|
||||
AvifEncoder::AvifEncoder() {
|
||||
m_description = "AVIF files (*.avif)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true;
|
||||
encoder_ = avifEncoderCreate();
|
||||
}
|
||||
|
||||
@@ -349,7 +380,7 @@ bool AvifEncoder::writeanimation(const Animation& animation,
|
||||
img.channels() == 1 || img.channels() == 3 || img.channels() == 4,
|
||||
"AVIF only supports 1, 3, 4 channels");
|
||||
|
||||
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth));
|
||||
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth, m_metadata));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < images.size(); i++)
|
||||
|
||||
@@ -58,11 +58,30 @@ BaseImageDecoder::BaseImageDecoder()
|
||||
m_frame_count = 1;
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const
|
||||
{
|
||||
if (type == IMAGE_METADATA_EXIF)
|
||||
return !m_exif.getData().empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const
|
||||
{
|
||||
if (type == IMAGE_METADATA_EXIF) {
|
||||
const std::vector<unsigned char>& exif = m_exif.getData();
|
||||
if (!exif.empty()) {
|
||||
Mat exifmat(1, (int)exif.size(), CV_8U, (void*)exif.data());
|
||||
return exifmat;
|
||||
}
|
||||
}
|
||||
return Mat();
|
||||
}
|
||||
|
||||
ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const
|
||||
{
|
||||
return m_exif.getTag(tag);
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::setSource( const String& filename )
|
||||
{
|
||||
m_filename = filename;
|
||||
@@ -140,6 +159,23 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::addMetadata(ImageMetadataType type, const Mat& metadata)
|
||||
{
|
||||
CV_Assert_N(type >= IMAGE_METADATA_EXIF, type <= IMAGE_METADATA_MAX);
|
||||
if (metadata.empty())
|
||||
return true;
|
||||
size_t itype = (size_t)type;
|
||||
if (itype >= m_support_metadata.size() || !m_support_metadata[itype])
|
||||
return false;
|
||||
if (m_metadata.empty())
|
||||
m_metadata.resize((size_t)IMAGE_METADATA_MAX+1);
|
||||
CV_Assert(metadata.elemSize() == 1);
|
||||
CV_Assert(metadata.isContinuous());
|
||||
const unsigned char* data = metadata.ptr<unsigned char>();
|
||||
m_metadata[itype].assign(data, data + metadata.total());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::write(const Mat &img, const std::vector<int> ¶ms) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
return writemulti(img_vec, params);
|
||||
|
||||
@@ -58,12 +58,31 @@ public:
|
||||
*/
|
||||
size_t getFrameCount() const { return m_frame_count; }
|
||||
|
||||
/**
|
||||
* @brief Set the internal m_frame_count variable to 1.
|
||||
*/
|
||||
void resetFrameCount() { m_frame_count = 1; }
|
||||
|
||||
/**
|
||||
* @brief Get the type of the image (e.g., color format, depth).
|
||||
* @return The type of the image.
|
||||
*/
|
||||
virtual int type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Checks whether file contains metadata of the certain type.
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual bool haveMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves metadata (if any) of the certain kind.
|
||||
* If there is no such metadata, the method returns empty array.
|
||||
*
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual Mat getMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Fetch a specific EXIF tag from the image's metadata.
|
||||
* @param tag The EXIF tag to retrieve.
|
||||
@@ -200,6 +219,13 @@ public:
|
||||
*/
|
||||
virtual bool setDestination(std::vector<uchar>& buf);
|
||||
|
||||
/**
|
||||
* @brief Sets the metadata to write together with the image data
|
||||
* @param type The type of metadata to add
|
||||
* @param metadata The packed metadata (Exif, XMP, ...)
|
||||
*/
|
||||
virtual bool addMetadata(ImageMetadataType type, const Mat& metadata);
|
||||
|
||||
/**
|
||||
* @brief Encode and write the image data.
|
||||
* @param img The Mat object containing the image data to be encoded.
|
||||
@@ -238,6 +264,8 @@ public:
|
||||
virtual void throwOnError() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::vector<unsigned char> > m_metadata; // see IMAGE_METADATA_...
|
||||
std::vector<bool> m_support_metadata;
|
||||
String m_description; ///< Description of the encoder (e.g., format name, capabilities).
|
||||
String m_filename; ///< Destination file name for encoded data.
|
||||
std::vector<uchar>* m_buf; ///< Pointer to the buffer for encoded data if using memory-based destination.
|
||||
|
||||
@@ -97,7 +97,8 @@ ExrDecoder::ExrDecoder()
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
m_native_depth = false;
|
||||
|
||||
m_multispectral = false;
|
||||
m_channels = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +120,7 @@ void ExrDecoder::close()
|
||||
|
||||
int ExrDecoder::type() const
|
||||
{
|
||||
return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), ((m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1));
|
||||
return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), (m_multispectral ? m_channels : (m_iscolor && m_hasalpha) ? 4 : m_iscolor ? 3 : m_hasalpha ? 2 : 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +149,7 @@ bool ExrDecoder::readHeader()
|
||||
m_green = channels.findChannel( "G" );
|
||||
m_blue = channels.findChannel( "B" );
|
||||
m_alpha = channels.findChannel( "A" );
|
||||
m_multispectral = channels.findChannel( "0" ) != nullptr;
|
||||
|
||||
if( m_alpha ) // alpha channel supported in RGB, Y, and YC scenarios
|
||||
m_hasalpha = true;
|
||||
@@ -158,6 +160,23 @@ bool ExrDecoder::readHeader()
|
||||
m_ischroma = false;
|
||||
result = true;
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
m_channels = 0;
|
||||
for( auto it = channels.begin(); it != channels.end(); it++ )
|
||||
m_channels++;
|
||||
|
||||
m_iscolor = true; // ??? false
|
||||
m_ischroma = false;
|
||||
m_hasalpha = false;
|
||||
result = m_channels <= CV_CN_MAX;
|
||||
|
||||
for ( int i = 1; result && i < m_channels; i++ ) // channel 0 was found previously
|
||||
{
|
||||
const Channel *ch = channels.findChannel( std::to_string(i) );
|
||||
result = ch && ch->xSampling == 1 && ch->ySampling == 1; // subsampling is not supported
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_green = channels.findChannel( "Y" );
|
||||
@@ -193,8 +212,9 @@ bool ExrDecoder::readHeader()
|
||||
bool ExrDecoder::readData( Mat& img )
|
||||
{
|
||||
m_native_depth = CV_MAT_DEPTH(type()) == img.depth();
|
||||
bool multispectral = img.channels() > 4;
|
||||
bool color = img.channels() > 2; // output mat has 3+ channels; Y or YA are the 1 and 2 channel scenario
|
||||
bool alphasupported = ( img.channels() % 2 == 0 ); // even number of channels indicates alpha
|
||||
bool alphasupported = !multispectral && ( img.channels() % 2 == 0 ); // even number of channels indicates alpha
|
||||
int channels = 0;
|
||||
uchar* data = img.ptr();
|
||||
size_t step = img.step;
|
||||
@@ -210,10 +230,17 @@ bool ExrDecoder::readData( Mat& img )
|
||||
const size_t floatsize = sizeof(float);
|
||||
size_t xstep = m_native_depth ? floatsize : 1; // 4 bytes if native depth (FLOAT), otherwise converting to 1 byte U8 depth
|
||||
size_t ystep = 0;
|
||||
const int channelstoread = ( (m_iscolor && alphasupported) ? 4 :
|
||||
const int channelstoread = ( multispectral ? img.channels() : (m_iscolor && alphasupported) ? 4 :
|
||||
( (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;
|
||||
|
||||
if ( m_multispectral ) // possible gray/RGB conversions
|
||||
{
|
||||
CV_CheckChannelsEQ(img.channels(), CV_MAT_CN(type()), "OpenCV EXR decoder needs more number of channels for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYCOLOR needed
|
||||
CV_CheckDepthEQ(img.depth(), CV_MAT_DEPTH(type()), "OpenCV EXR decoder supports CV_32F depth only for multispectral images. Use cv::IMREAD_UNCHANGED mode for imread."); // IMREAD_ANYDEPTH needed
|
||||
}
|
||||
CV_Assert( multispectral == m_multispectral && (!multispectral || justcopy) ); // should be true after previous checks
|
||||
|
||||
// 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.
|
||||
@@ -291,6 +318,15 @@ bool ExrDecoder::readData( Mat& img )
|
||||
xsample[0] = m_green->xSampling;
|
||||
}
|
||||
}
|
||||
else if( m_multispectral )
|
||||
{
|
||||
for ( int i = 0; i < m_channels; i++ )
|
||||
{
|
||||
frame.insert( std::to_string(i), Slice( m_type,
|
||||
buffer - m_datawindow.min.x * xStride - m_datawindow.min.y * ystep + (floatsize * i),
|
||||
xStride, ystep, 1, 1, 0.0 ));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue )
|
||||
@@ -361,39 +397,42 @@ bool ExrDecoder::readData( Mat& img )
|
||||
{
|
||||
m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y );
|
||||
|
||||
if( m_iscolor )
|
||||
if( !m_multispectral )
|
||||
{
|
||||
if (doReadRGB)
|
||||
if( m_iscolor )
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if (doReadRGB)
|
||||
{
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_blue->xSampling, m_blue->ySampling );
|
||||
if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data + xstep, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) )
|
||||
UpSample( data + 2 * xstep, channelstoread, step / xstep, m_red->xSampling, m_red->ySampling );
|
||||
}
|
||||
}
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) )
|
||||
UpSample( data, channelstoread, step / xstep, m_green->xSampling, m_green->ySampling );
|
||||
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
|
||||
else
|
||||
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
|
||||
if( chromatorgb )
|
||||
{
|
||||
if (doReadRGB)
|
||||
ChromaToRGB( (float *)data, m_height, channelstoread, step / xstep );
|
||||
else
|
||||
ChromaToBGR( (float *)data, m_height, channelstoread, step / xstep );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else // m_multispectral should be false
|
||||
{
|
||||
uchar *out = data;
|
||||
int x, y;
|
||||
@@ -782,13 +821,19 @@ bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
header.channels().insert( "B", Channel( type ) );
|
||||
//printf("bunt\n");
|
||||
}
|
||||
else
|
||||
else if( channels == 1 || channels == 2 )
|
||||
{
|
||||
header.channels().insert( "Y", Channel( type ) );
|
||||
//printf("gray\n");
|
||||
}
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
header.channels().insert( std::to_string(i), Channel( type ) );
|
||||
//printf("multi-channel\n");
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 )
|
||||
if( channels % 2 == 0 && channels <= 4)
|
||||
{ // even number of channels indicates Alpha
|
||||
header.channels().insert( "A", Channel( type ) );
|
||||
}
|
||||
@@ -821,10 +866,15 @@ bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
frame.insert( "G", Slice( type, buffer + size, size * channels, bufferstep ));
|
||||
frame.insert( "R", Slice( type, buffer + size * 2, size * channels, bufferstep ));
|
||||
}
|
||||
else
|
||||
else if( channels == 1 || channels == 2 )
|
||||
frame.insert( "Y", Slice( type, buffer, size * channels, bufferstep ));
|
||||
else if( channels > 4 )
|
||||
{
|
||||
for ( int i = 0; i < channels; i++ )
|
||||
frame.insert( std::to_string(i), Slice( type, buffer + size * i, size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
if( channels % 2 == 0 )
|
||||
if( channels % 2 == 0 && channels <= 4 )
|
||||
{ // even channel count indicates Alpha channel
|
||||
frame.insert( "A", Slice( type, buffer + size * (channels - 1), size * channels, bufferstep ));
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ protected:
|
||||
bool m_iscolor;
|
||||
bool m_isfloat;
|
||||
bool m_hasalpha;
|
||||
bool m_multispectral;
|
||||
int m_channels;
|
||||
|
||||
private:
|
||||
ExrDecoder(const ExrDecoder &); // copy disabled
|
||||
|
||||
@@ -408,6 +408,9 @@ bool GdalDecoder::readData( Mat& img ){
|
||||
case GCI_AlphaBand:
|
||||
color = 3;
|
||||
break;
|
||||
case GCI_Undefined:
|
||||
color = c;
|
||||
break;
|
||||
default:
|
||||
CV_Error(cv::Error::StsError, "Invalid/unsupported mode");
|
||||
}
|
||||
|
||||
@@ -600,6 +600,8 @@ JpegEncoder::JpegEncoder()
|
||||
{
|
||||
m_description = "JPEG files (*.jpeg;*.jpg;*.jpe)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -815,6 +817,22 @@ bool JpegEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
|
||||
jpeg_start_compress( &cinfo, TRUE );
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
size_t exif_size = metadata_exif.size();
|
||||
if (exif_size > 0u) {
|
||||
const char app1_exif_prefix[] = {'E', 'x', 'i', 'f', '\0', '\0'};
|
||||
size_t app1_exif_prefix_size = sizeof(app1_exif_prefix);
|
||||
size_t data_size = exif_size + app1_exif_prefix_size;
|
||||
|
||||
std::vector<uchar> metadata_app1(data_size);
|
||||
uchar* data = metadata_app1.data();
|
||||
memcpy(data, app1_exif_prefix, app1_exif_prefix_size);
|
||||
memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size);
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size);
|
||||
}
|
||||
}
|
||||
|
||||
if( doDirectWrite )
|
||||
{
|
||||
for( int y = 0; y < height; y++ )
|
||||
|
||||
+142
-110
@@ -156,7 +156,7 @@ bool APNGFrame::setMat(const cv::Mat& src, unsigned delayNum, unsigned delayDen)
|
||||
|
||||
if (!src.empty())
|
||||
{
|
||||
png_uint_32 rowbytes = src.depth() == CV_16U ? src.cols * src.channels() * 2 : src.cols * src.channels();
|
||||
png_uint_32 rowbytes = src.cols * (uint32_t)src.elemSize();
|
||||
_width = src.cols;
|
||||
_height = src.rows;
|
||||
_colorType = src.channels() == 1 ? PNG_COLOR_TYPE_GRAY : src.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
@@ -416,14 +416,17 @@ bool PngDecoder::readData( Mat& img )
|
||||
|
||||
if (m_frame_no == 0)
|
||||
{
|
||||
if (m_mat_raw.empty())
|
||||
{
|
||||
if (m_f)
|
||||
fseek(m_f, -8, SEEK_CUR);
|
||||
else
|
||||
m_buf_pos -= 8;
|
||||
}
|
||||
m_mat_raw = Mat(img.rows, img.cols, m_type);
|
||||
m_mat_next = Mat(img.rows, img.cols, m_type);
|
||||
frameRaw.setMat(m_mat_raw);
|
||||
frameNext.setMat(m_mat_next);
|
||||
if (m_f)
|
||||
fseek(m_f, -8, SEEK_CUR);
|
||||
else
|
||||
m_buf_pos -= 8;
|
||||
}
|
||||
else
|
||||
m_mat_next.copyTo(mat_cur);
|
||||
@@ -433,9 +436,6 @@ bool PngDecoder::readData( Mat& img )
|
||||
if (!processing_start((void*)&frameRaw, mat_cur))
|
||||
return false;
|
||||
|
||||
if(setjmp(png_jmpbuf(m_png_ptr)))
|
||||
return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
id = read_chunk(chunk);
|
||||
@@ -446,53 +446,56 @@ bool PngDecoder::readData( Mat& img )
|
||||
{
|
||||
if (!m_is_fcTL_loaded)
|
||||
{
|
||||
m_is_fcTL_loaded = true;
|
||||
w0 = m_width;
|
||||
h0 = m_height;
|
||||
}
|
||||
|
||||
if (processing_finish())
|
||||
{
|
||||
if (dop == 2)
|
||||
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
||||
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
||||
if (!delay_den)
|
||||
delay_den = 100;
|
||||
m_animation.durations.push_back(cvRound(1000.*delay_num/delay_den));
|
||||
|
||||
if (mat_cur.channels() == img.channels())
|
||||
{
|
||||
if (mat_cur.depth() == CV_16U && img.depth() == CV_8U)
|
||||
mat_cur.convertTo(img, CV_8U, 1. / 255);
|
||||
else
|
||||
mat_cur.copyTo(img);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat mat_cur_scaled;
|
||||
if (mat_cur.depth() == CV_16U && img.depth() == CV_8U)
|
||||
mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255);
|
||||
else
|
||||
mat_cur_scaled = mat_cur;
|
||||
|
||||
if (img.channels() == 1)
|
||||
cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY);
|
||||
else if (img.channels() == 3)
|
||||
cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
|
||||
if (dop != 2)
|
||||
{
|
||||
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
||||
if (dop == 1)
|
||||
for (j = 0; j < h0; j++)
|
||||
memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels());
|
||||
}
|
||||
m_mat_raw.copyTo(m_animation.still_image);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
if (processing_finish())
|
||||
{
|
||||
if (dop == 2)
|
||||
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
||||
|
||||
if (x0 + w0 > frameCur.getWidth() || y0 + h0 > frameCur.getHeight())
|
||||
return false;
|
||||
|
||||
compose_frame(frameCur.getRows(), frameRaw.getRows(), bop, x0, y0, w0, h0, mat_cur);
|
||||
if (!delay_den)
|
||||
delay_den = 100;
|
||||
m_animation.durations.push_back(cvRound(1000. * delay_num / delay_den));
|
||||
|
||||
if (mat_cur.channels() == img.channels())
|
||||
{
|
||||
if (mat_cur.depth() == CV_16U && img.depth() == CV_8U)
|
||||
mat_cur.convertTo(img, CV_8U, 1. / 255);
|
||||
else
|
||||
mat_cur.copyTo(img);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat mat_cur_scaled;
|
||||
if (mat_cur.depth() == CV_16U && img.depth() == CV_8U)
|
||||
mat_cur.convertTo(mat_cur_scaled, CV_8U, 1. / 255);
|
||||
else
|
||||
mat_cur_scaled = mat_cur;
|
||||
|
||||
if (img.channels() == 1)
|
||||
cvtColor(mat_cur_scaled, img, COLOR_BGRA2GRAY);
|
||||
else if (img.channels() == 3)
|
||||
cvtColor(mat_cur_scaled, img, COLOR_BGRA2BGR);
|
||||
}
|
||||
|
||||
if (dop != 2)
|
||||
{
|
||||
memcpy(frameNext.getPixels(), frameCur.getPixels(), imagesize);
|
||||
if (dop == 1)
|
||||
for (j = 0; j < h0; j++)
|
||||
memset(frameNext.getRows()[y0 + j] + x0 * img.channels(), 0, w0 * img.channels());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
w0 = png_get_uint_32(&chunk.p[12]);
|
||||
@@ -508,14 +511,18 @@ bool PngDecoder::readData( Mat& img )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Asking for blend over with no alpha is invalid.
|
||||
if (bop == 1 && mat_cur.channels() != 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&m_chunkIHDR.p[8], &chunk.p[12], 8);
|
||||
return true;
|
||||
|
||||
if (m_is_fcTL_loaded)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
m_is_fcTL_loaded = true;
|
||||
ClearPngPtr();
|
||||
if (!processing_start((void*)&frameRaw, mat_cur))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (id == id_IDAT)
|
||||
{
|
||||
@@ -650,8 +657,8 @@ void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vect
|
||||
const size_t elem_size = img.elemSize();
|
||||
if (_bop == 0) {
|
||||
// Overwrite mode: copy source row directly to destination
|
||||
for(uint32_t j = 0; j < h; ++j) {
|
||||
std::memcpy(rows_dst[j + y] + x * elem_size,rows_src[j], w * elem_size);
|
||||
for (uint32_t j = 0; j < h; ++j) {
|
||||
std::memcpy(rows_dst[j + y] + x * elem_size, rows_src[j], w * elem_size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -665,23 +672,24 @@ void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vect
|
||||
|
||||
// Blending mode
|
||||
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
||||
if (channels < 4 || sp[3] == 65535) { // Fully opaque in 16-bit (max value)
|
||||
uint16_t alpha = sp[3];
|
||||
|
||||
if (channels < 4 || alpha == 65535 || dp[3] == 0) {
|
||||
// Fully opaque OR destination fully transparent: direct copy
|
||||
memcpy(dp, sp, elem_size);
|
||||
continue;
|
||||
}
|
||||
else if (sp[3] != 0) { // Partially transparent
|
||||
if (dp[3] != 0) { // Both source and destination have alpha
|
||||
uint32_t u = sp[3] * 65535; // 16-bit max
|
||||
uint32_t v = (65535 - sp[3]) * dp[3];
|
||||
uint32_t al = u + v;
|
||||
dp[0] = static_cast<uint16_t>((sp[0] * u + dp[0] * v) / al); // Red
|
||||
dp[1] = static_cast<uint16_t>((sp[1] * u + dp[1] * v) / al); // Green
|
||||
dp[2] = static_cast<uint16_t>((sp[2] * u + dp[2] * v) / al); // Blue
|
||||
dp[3] = static_cast<uint16_t>(al / 65535); // Alpha
|
||||
}
|
||||
else {
|
||||
// If destination alpha is 0, copy source pixel
|
||||
memcpy(dp, sp, elem_size);
|
||||
}
|
||||
|
||||
if (alpha != 0) {
|
||||
// Alpha blending
|
||||
uint64_t u = static_cast<uint64_t>(alpha) * 65535;
|
||||
uint64_t v = static_cast<uint64_t>(65535 - alpha) * dp[3];
|
||||
uint64_t al = u + v;
|
||||
|
||||
dp[0] = static_cast<uint16_t>((sp[0] * u + dp[0] * v) / al); // Red
|
||||
dp[1] = static_cast<uint16_t>((sp[1] * u + dp[1] * v) / al); // Green
|
||||
dp[2] = static_cast<uint16_t>((sp[2] * u + dp[2] * v) / al); // Blue
|
||||
dp[3] = static_cast<uint16_t>(al / 65535); // Alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -694,25 +702,24 @@ void PngDecoder::compose_frame(std::vector<png_bytep>& rows_dst, const std::vect
|
||||
|
||||
// Blending mode
|
||||
for (unsigned int i = 0; i < w; i++, sp += channels, dp += channels) {
|
||||
if (channels < 4 || sp[3] == 255) {
|
||||
// Fully opaque: copy source pixel directly
|
||||
uint8_t alpha = sp[3];
|
||||
|
||||
if (channels < 4 || alpha == 255 || dp[3] == 0) {
|
||||
// Fully opaque OR destination fully transparent: direct copy
|
||||
memcpy(dp, sp, elem_size);
|
||||
continue;
|
||||
}
|
||||
else if (sp[3] != 0) {
|
||||
|
||||
if (alpha != 0) {
|
||||
// Alpha blending
|
||||
if (dp[3] != 0) {
|
||||
int u = sp[3] * 255;
|
||||
int v = (255 - sp[3]) * dp[3];
|
||||
int al = u + v;
|
||||
dp[0] = (sp[0] * u + dp[0] * v) / al; // Red
|
||||
dp[1] = (sp[1] * u + dp[1] * v) / al; // Green
|
||||
dp[2] = (sp[2] * u + dp[2] * v) / al; // Blue
|
||||
dp[3] = al / 255; // Alpha
|
||||
}
|
||||
else {
|
||||
// If destination alpha is 0, copy source pixel
|
||||
memcpy(dp, sp, elem_size);
|
||||
}
|
||||
uint32_t u = alpha * 255;
|
||||
uint32_t v = (255 - alpha) * dp[3];
|
||||
uint32_t al = u + v;
|
||||
|
||||
dp[0] = static_cast<uint8_t>((sp[0] * u + dp[0] * v) / al); // Red
|
||||
dp[1] = static_cast<uint8_t>((sp[1] * u + dp[1] * v) / al); // Green
|
||||
dp[2] = static_cast<uint8_t>((sp[2] * u + dp[2] * v) / al); // Blue
|
||||
dp[3] = static_cast<uint8_t>(al / 255); // Alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -845,6 +852,8 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_
|
||||
{
|
||||
CV_UNUSED(pass);
|
||||
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
|
||||
if(row_num >= frame->getHeight())
|
||||
return;
|
||||
png_progressive_combine_row(png_ptr, frame->getRows()[row_num], new_row);
|
||||
}
|
||||
|
||||
@@ -852,8 +861,10 @@ void PngDecoder::row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_
|
||||
|
||||
PngEncoder::PngEncoder()
|
||||
{
|
||||
m_description = "Portable Network Graphics files (*.png)";
|
||||
m_description = "Portable Network Graphics files (*.png;*.apng)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX+1, false);
|
||||
m_support_metadata[IMAGE_METADATA_EXIF] = true;
|
||||
op_zstream1.zalloc = NULL;
|
||||
op_zstream2.zalloc = NULL;
|
||||
next_seq_num = 0;
|
||||
@@ -985,6 +996,16 @@ bool PngEncoder::write( const Mat& img, const std::vector<int>& params )
|
||||
for( y = 0; y < height; y++ )
|
||||
buffer[y] = img.data + y*img.step;
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
if (!exif.empty()) {
|
||||
writeChunk(f, "eXIf", exif.data(), (uint32_t)exif.size());
|
||||
}
|
||||
// [TODO] add xmp and icc. They need special handling,
|
||||
// see https://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files and
|
||||
// https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html.
|
||||
}
|
||||
|
||||
png_write_image( png_ptr, buffer.data() );
|
||||
png_write_end( png_ptr, info_ptr );
|
||||
|
||||
@@ -1483,7 +1504,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
|
||||
if (m_isBilevel)
|
||||
CV_LOG_WARNING(NULL, "IMWRITE_PNG_BILEVEL parameter is not supported yet.");
|
||||
uint32_t first =0;
|
||||
|
||||
uint32_t loops= animation.loop_count;
|
||||
uint32_t coltype= animation.frames[0].channels() == 1 ? PNG_COLOR_TYPE_GRAY : animation.frames[0].channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
|
||||
@@ -1568,7 +1589,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
buf_IHDR[11] = 0;
|
||||
buf_IHDR[12] = 0;
|
||||
|
||||
png_save_uint_32(buf_acTL, num_frames - first);
|
||||
png_save_uint_32(buf_acTL, num_frames);
|
||||
png_save_uint_32(buf_acTL + 4, loops);
|
||||
|
||||
writeToStreamOrBuffer(header, 8, m_f);
|
||||
@@ -1577,8 +1598,6 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
|
||||
if (num_frames > 1)
|
||||
writeChunk(m_f, "acTL", buf_acTL, 8);
|
||||
else
|
||||
first = 0;
|
||||
|
||||
if (palsize > 0)
|
||||
writeChunk(m_f, "PLTE", (unsigned char*)(&palette), palsize * 3);
|
||||
@@ -1634,19 +1653,32 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
|
||||
for (j = 0; j < 6; j++)
|
||||
op[j].valid = 0;
|
||||
|
||||
if (!animation.still_image.empty() && num_frames > 1)
|
||||
{
|
||||
CV_Assert(animation.still_image.type() == animation.frames[0].type() && animation.still_image.size() == animation.frames[0].size());
|
||||
APNGFrame apngFrame;
|
||||
Mat tmp;
|
||||
if (animation.still_image.depth() == CV_16U)
|
||||
{
|
||||
animation.still_image.convertTo(tmp, CV_8U, 1.0 / 255);
|
||||
}
|
||||
else
|
||||
tmp = animation.still_image;
|
||||
|
||||
if (tmp.channels() > 2)
|
||||
cvtColor(tmp, tmp, COLOR_BGRA2RGBA);
|
||||
apngFrame.setMat(tmp);
|
||||
|
||||
deflateRectOp(apngFrame.getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0);
|
||||
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0);
|
||||
writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size);
|
||||
}
|
||||
|
||||
deflateRectOp(frames[0].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0);
|
||||
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0);
|
||||
|
||||
if (first)
|
||||
{
|
||||
writeIDATs(m_f, 0, zbuf.data(), zsize, idat_size);
|
||||
for (j = 0; j < 6; j++)
|
||||
op[j].valid = 0;
|
||||
deflateRectOp(frames[1].getPixels(), x0, y0, w0, h0, bpp, rowbytes, zbuf_size, 0);
|
||||
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, 0);
|
||||
}
|
||||
|
||||
for (i = first; i < num_frames - 1; i++)
|
||||
for (i = 0; i < num_frames - 1; i++)
|
||||
{
|
||||
uint32_t op_min;
|
||||
int op_best;
|
||||
@@ -1673,7 +1705,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
}
|
||||
|
||||
/* dispose = previous */
|
||||
if (i > first)
|
||||
if (i > 0)
|
||||
getRect(width, height, rest.data(), frames[i + 1].getPixels(), over3.data(), bpp, rowbytes, zbuf_size, has_tcolor, tcolor, 2);
|
||||
|
||||
op_min = op[0].size;
|
||||
@@ -1699,9 +1731,9 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
png_save_uint_16(buf_fcTL + 22, frames[i].getDelayDen());
|
||||
buf_fcTL[24] = dop;
|
||||
buf_fcTL[25] = bop;
|
||||
writeChunk(m_f, "fcTL", buf_fcTL, 26);
|
||||
|
||||
writeIDATs(m_f, i, zbuf.data(), zsize, idat_size);
|
||||
writeChunk(m_f, "fcTL", buf_fcTL, 26);
|
||||
writeIDATs(m_f, animation.still_image.empty() ? i : 1, zbuf.data(), zsize, idat_size);
|
||||
|
||||
/* process apng dispose - begin */
|
||||
if (dop != 2)
|
||||
@@ -1728,7 +1760,7 @@ bool PngEncoder::writeanimation(const Animation& animation, const std::vector<in
|
||||
deflateRectFin(zbuf.data(), &zsize, bpp, rowbytes, rows.data(), zbuf_size, op_best);
|
||||
}
|
||||
|
||||
if (num_frames > 1)
|
||||
if (num_frames > 1 /* don't write fcTL chunk if animation has only one frame */)
|
||||
{
|
||||
png_save_uint_32(buf_fcTL, next_seq_num++);
|
||||
png_save_uint_32(buf_fcTL + 4, w0);
|
||||
|
||||
@@ -31,18 +31,18 @@
|
||||
* with these values. (png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 );) For this codec implementation,
|
||||
* slightly modified versions are implemented in the below of this page.
|
||||
*/
|
||||
void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int _swap_rb);
|
||||
|
||||
void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step,
|
||||
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int _swap_rb);
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
ushort *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb);
|
||||
|
||||
namespace cv
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@ int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t siz
|
||||
|
||||
bool SPngDecoder::readHeader()
|
||||
{
|
||||
volatile bool result = false;
|
||||
bool result = false;
|
||||
close();
|
||||
|
||||
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
|
||||
@@ -136,40 +136,36 @@ bool SPngDecoder::readHeader()
|
||||
if (!m_buf.empty() || m_f)
|
||||
{
|
||||
struct spng_ihdr ihdr;
|
||||
int ret = spng_get_ihdr(ctx, &ihdr);
|
||||
|
||||
if (ret == SPNG_OK)
|
||||
if (spng_get_ihdr(ctx, &ihdr) == SPNG_OK)
|
||||
{
|
||||
m_width = static_cast<int>(ihdr.width);
|
||||
m_height = static_cast<int>(ihdr.height);
|
||||
m_color_type = ihdr.color_type;
|
||||
m_bit_depth = ihdr.bit_depth;
|
||||
|
||||
if (ihdr.bit_depth <= 8 || ihdr.bit_depth == 16)
|
||||
int num_trans;
|
||||
switch (ihdr.color_type)
|
||||
{
|
||||
int num_trans;
|
||||
switch (ihdr.color_type)
|
||||
{
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR:
|
||||
case SPNG_COLOR_TYPE_INDEXED:
|
||||
struct spng_trns trns;
|
||||
num_trans = !spng_get_trns(ctx, &trns);
|
||||
if (num_trans > 0)
|
||||
m_type = CV_8UC4;
|
||||
else
|
||||
m_type = CV_8UC3;
|
||||
break;
|
||||
case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA:
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR:
|
||||
case SPNG_COLOR_TYPE_INDEXED:
|
||||
struct spng_trns trns;
|
||||
num_trans = !spng_get_trns(ctx, &trns);
|
||||
if (num_trans > 0)
|
||||
m_type = CV_8UC4;
|
||||
break;
|
||||
default:
|
||||
m_type = CV_8UC1;
|
||||
}
|
||||
if (ihdr.bit_depth == 16)
|
||||
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
|
||||
result = true;
|
||||
else
|
||||
m_type = CV_8UC3;
|
||||
break;
|
||||
case SPNG_COLOR_TYPE_GRAYSCALE_ALPHA:
|
||||
case SPNG_COLOR_TYPE_TRUECOLOR_ALPHA:
|
||||
m_type = CV_8UC4;
|
||||
break;
|
||||
default:
|
||||
m_type = CV_8UC1;
|
||||
}
|
||||
if (ihdr.bit_depth == 16)
|
||||
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,98 +174,86 @@ bool SPngDecoder::readHeader()
|
||||
|
||||
bool SPngDecoder::readData(Mat &img)
|
||||
{
|
||||
volatile bool result = false;
|
||||
bool color = img.channels() > 1;
|
||||
|
||||
struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx;
|
||||
bool result = false;
|
||||
|
||||
if (m_ctx && m_width && m_height)
|
||||
{
|
||||
int fmt = SPNG_FMT_PNG;
|
||||
struct spng_ctx* png_ptr = (struct spng_ctx*)m_ctx;
|
||||
bool color = img.channels() > 1;
|
||||
int fmt = img.channels() == 4 ? m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8 : SPNG_FMT_PNG;
|
||||
int decode_flags = img.channels() == 4 ? SPNG_DECODE_TRNS : 0;
|
||||
|
||||
struct spng_trns trns;
|
||||
int have_trns = spng_get_trns((struct spng_ctx *)m_ctx, &trns);
|
||||
|
||||
int decode_flags = 0;
|
||||
if (have_trns == SPNG_OK)
|
||||
{
|
||||
decode_flags = SPNG_DECODE_TRNS;
|
||||
}
|
||||
if (img.channels() == 4)
|
||||
{
|
||||
if (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR ||
|
||||
m_color_type == SPNG_COLOR_TYPE_INDEXED ||
|
||||
m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)
|
||||
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
|
||||
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE)
|
||||
fmt = m_bit_depth == 16 ? SPNG_FMT_GA16 : SPNG_FMT_GA8;
|
||||
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA)
|
||||
{
|
||||
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
|
||||
}
|
||||
else
|
||||
fmt = SPNG_FMT_RGBA8;
|
||||
}
|
||||
if (img.channels() == 3)
|
||||
if (img.type() == CV_8UC3)
|
||||
{
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
if ((m_color_type == SPNG_COLOR_TYPE_GRAYSCALE || m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA) &&
|
||||
m_bit_depth == 16)
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
else if (m_bit_depth == 16)
|
||||
fmt = SPNG_FMT_PNG;
|
||||
}
|
||||
else if (img.channels() == 1)
|
||||
{
|
||||
if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth <= 8)
|
||||
fmt = SPNG_FMT_G8;
|
||||
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE && m_bit_depth == 16)
|
||||
{
|
||||
if (img.depth() == CV_8U || img.depth() == CV_8S)
|
||||
{
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt = SPNG_FMT_PNG;
|
||||
}
|
||||
}
|
||||
else if (m_color_type == SPNG_COLOR_TYPE_INDEXED ||
|
||||
m_color_type == SPNG_COLOR_TYPE_TRUECOLOR)
|
||||
{
|
||||
if (img.depth() == CV_8U || img.depth() == CV_8S)
|
||||
{
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8;
|
||||
}
|
||||
}
|
||||
else if (m_color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || fmt == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA)
|
||||
{
|
||||
if (img.depth() == CV_8U || img.depth() == CV_8S)
|
||||
{
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt = m_bit_depth == 16 ? SPNG_FMT_RGBA16 : SPNG_FMT_RGBA8;
|
||||
}
|
||||
}
|
||||
else
|
||||
fmt = SPNG_FMT_RGB8;
|
||||
fmt = img.depth() == CV_16U ? SPNG_FMT_RGBA16 : SPNG_FMT_RGB8;
|
||||
}
|
||||
|
||||
if (fmt == SPNG_FMT_PNG && m_bit_depth == 16 && m_color_type >= SPNG_COLOR_TYPE_GRAYSCALE_ALPHA)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
|
||||
return false;
|
||||
cvtColor(tmp, img, m_use_rgb ? COLOR_RGBA2RGB : COLOR_RGBA2BGR);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct spng_ihdr ihdr;
|
||||
spng_get_ihdr(png_ptr, &ihdr);
|
||||
|
||||
size_t image_width, image_size = 0;
|
||||
int ret = spng_decoded_image_size(png_ptr, fmt, &image_size);
|
||||
struct spng_ihdr ihdr;
|
||||
spng_get_ihdr(png_ptr, &ihdr);
|
||||
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
image_width = image_size / m_height;
|
||||
|
||||
if (!color && fmt == SPNG_FMT_RGB8 && m_bit_depth == 16 && (m_color_type == SPNG_COLOR_TYPE_TRUECOLOR || m_color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA))
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), SPNG_FMT_RGBA16, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_16u28u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
|
||||
img.data, (int)img.step1(), Size(m_width, m_height), 4, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!color && ihdr.interlace_method && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA16))
|
||||
{
|
||||
if (fmt == SPNG_FMT_RGBA16)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16UC4);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, tmp.total() * tmp.elemSize(), fmt, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_16u_CnC1R(reinterpret_cast<const ushort*>(tmp.data), (int)tmp.step1(),
|
||||
reinterpret_cast<ushort*>(img.data), (int)img.step1(), Size(m_width, m_height), 4, 2);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_8UC3);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, fmt, 0))
|
||||
return false;
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(tmp.data, (int)tmp.step1(), img.data, (int)img.step1(), Size(m_width, m_height), 3, 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (fmt == SPNG_FMT_PNG && img.elemSize() * m_width / 3 == image_width)
|
||||
{
|
||||
Mat tmp(m_height, m_width, CV_16U);
|
||||
if (SPNG_OK != spng_decode_image(png_ptr, tmp.data, image_size, SPNG_FMT_PNG, 0))
|
||||
return false;
|
||||
cvtColor(tmp, img, COLOR_GRAY2BGR);
|
||||
return true;
|
||||
}
|
||||
|
||||
ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags);
|
||||
if (ret == SPNG_OK)
|
||||
{
|
||||
@@ -279,88 +263,46 @@ bool SPngDecoder::readData(Mat &img)
|
||||
// decode image then convert to grayscale
|
||||
if (!color && (fmt == SPNG_FMT_RGB8 || fmt == SPNG_FMT_RGBA8 || fmt == SPNG_FMT_RGBA16))
|
||||
{
|
||||
if (ihdr.interlace_method == 0)
|
||||
AutoBuffer<unsigned char> buffer;
|
||||
buffer.allocate(image_width);
|
||||
if (fmt == SPNG_FMT_RGB8)
|
||||
{
|
||||
AutoBuffer<unsigned char> buffer;
|
||||
buffer.allocate(image_width);
|
||||
if (fmt == SPNG_FMT_RGB8)
|
||||
do
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGR2Gray_8u_C3C1R(
|
||||
buffer.data(),
|
||||
0,
|
||||
img.data + row_info.row_num * img.step,
|
||||
0, Size(m_width, 1), 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA8)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_8u_C4C1R(
|
||||
buffer.data(),
|
||||
0,
|
||||
img.data + row_info.row_num * img.step,
|
||||
0, Size(m_width, 1), 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA16)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_16u_CnC1R(
|
||||
reinterpret_cast<const ushort *>(buffer.data()), 0,
|
||||
reinterpret_cast<ushort *>(img.data + row_info.row_num * img.step),
|
||||
0, Size(m_width, 1),
|
||||
4, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 3, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else
|
||||
else if (fmt == SPNG_FMT_RGBA8)
|
||||
{
|
||||
AutoBuffer<unsigned char> imageBuffer(image_size);
|
||||
ret = spng_decode_image(png_ptr, imageBuffer.data(), image_size, fmt, 0);
|
||||
int step = m_width * img.channels();
|
||||
if (fmt == SPNG_FMT_RGB8)
|
||||
do
|
||||
{
|
||||
spngCvt_BGR2Gray_8u_C3C1R(
|
||||
imageBuffer.data(),
|
||||
step,
|
||||
img.data,
|
||||
step, Size(m_width, m_height), 2);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA8)
|
||||
{
|
||||
spngCvt_BGRA2Gray_8u_C4C1R(
|
||||
imageBuffer.data(),
|
||||
step,
|
||||
img.data,
|
||||
step, Size(m_width, m_height), 2);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA16)
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_8u_CnC1R(buffer.data(), 0, img.data + row_info.row_num * img.step, 0, Size(m_width, 1), 4, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
else if (fmt == SPNG_FMT_RGBA16)
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer.data(), image_width);
|
||||
spngCvt_BGRA2Gray_16u_CnC1R(
|
||||
reinterpret_cast<const ushort *>(imageBuffer.data()), step / 3,
|
||||
reinterpret_cast<ushort *>(img.data),
|
||||
step / 3, Size(m_width, m_height),
|
||||
4, 2);
|
||||
}
|
||||
reinterpret_cast<const ushort*>(buffer.data()), 0,
|
||||
reinterpret_cast<ushort*>(img.data + row_info.row_num * img.step),
|
||||
0, Size(m_width, 1), 4, 2);
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
}
|
||||
else if (color)
|
||||
@@ -383,9 +325,8 @@ bool SPngDecoder::readData(Mat &img)
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort *>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort *>(buffer[row_info.row_num]), 0,
|
||||
Size(m_width, 1));
|
||||
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
@@ -414,6 +355,8 @@ bool SPngDecoder::readData(Mat &img)
|
||||
}
|
||||
else if (fmt == SPNG_FMT_PNG)
|
||||
{
|
||||
AutoBuffer<unsigned char> bufcn4;
|
||||
bufcn4.allocate(image_width);
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
@@ -421,16 +364,17 @@ bool SPngDecoder::readData(Mat &img)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
|
||||
|
||||
if (ihdr.interlace_method == 0 && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort *>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort *>(buffer[row_info.row_num]), 0, Size(m_width, 1));
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(buffer[row_info.row_num]), 0,
|
||||
reinterpret_cast<ushort*>(buffer[row_info.row_num]), 0, Size(m_width, 1));
|
||||
}
|
||||
} while (ret == SPNG_OK);
|
||||
if (ihdr.interlace_method && !m_use_rgb)
|
||||
{
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort *>(img.data), step,
|
||||
reinterpret_cast<ushort *>(img.data), step, Size(m_width, m_height));
|
||||
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort*>(img.data), step,
|
||||
reinterpret_cast<ushort*>(img.data), step, Size(m_width, m_height));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -454,7 +398,6 @@ bool SPngDecoder::readData(Mat &img)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
ret = spng_get_row_info(png_ptr, &row_info);
|
||||
@@ -462,8 +405,8 @@ bool SPngDecoder::readData(Mat &img)
|
||||
break;
|
||||
|
||||
ret = spng_decode_row(png_ptr, img.data + row_info.row_num * image_width, image_width);
|
||||
|
||||
} while (ret == SPNG_OK);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == SPNG_EOI)
|
||||
@@ -687,45 +630,32 @@ bool SPngEncoder::write(const Mat &img, const std::vector<int> ¶ms)
|
||||
|
||||
}
|
||||
|
||||
void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int _swap_rb)
|
||||
void spngCvt_BGRA2Gray_8u_CnC1R(const uchar *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb)
|
||||
{
|
||||
int i;
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
double cBGR0 = 0.1140441895;
|
||||
double cBGR2 = 0.2989807129;
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
for (i = 0; i < size.width; i++, bgr += 3)
|
||||
{
|
||||
int t = static_cast<int>(cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2]);
|
||||
gray[i] = (uchar)t;
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * 3;
|
||||
}
|
||||
}
|
||||
|
||||
void spngCvt_BGRA2Gray_8u_C4C1R(const uchar *bgra, int rgba_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int _swap_rb)
|
||||
{
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
double cBGR0 = 0.1140441895;
|
||||
double cBGR1 = 0.5869750977;
|
||||
double cBGR2 = 0.2989807129;
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
for (int i = 0; i < size.width; i++, bgra += 4)
|
||||
for (i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
gray[i] = cv::saturate_cast<uchar>(cBGR0 * bgra[0] + cBGR1 * bgra[1] + cBGR2 * bgra[2]);
|
||||
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
|
||||
{
|
||||
gray[i] = (uchar)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]) >> 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
gray[i] = bgr[0];
|
||||
}
|
||||
}
|
||||
|
||||
bgra += rgba_step - size.width * 4;
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -735,15 +665,43 @@ void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
{
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
double cBGR0 = 0.1140441895;
|
||||
double cBGR1 = 0.5869750977;
|
||||
double cBGR2 = 0.2989807129;
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
for (int i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
gray[i] = (ushort)(cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2]);
|
||||
if (bgr[0] != bgr[1] || bgr[0] != bgr[2])
|
||||
{
|
||||
gray[i] = (ushort)((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15);
|
||||
}
|
||||
else
|
||||
{
|
||||
gray[i] = bgr[0];
|
||||
}
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
}
|
||||
}
|
||||
|
||||
void spngCvt_BGRA2Gray_16u28u_CnC1R(const ushort *bgr, int bgr_step,
|
||||
uchar *gray, int gray_step,
|
||||
cv::Size size, int ncn, int _swap_rb)
|
||||
{
|
||||
int cBGR0 = 3737;
|
||||
int cBGR1 = 19234;
|
||||
int cBGR2 = 9797;
|
||||
if (_swap_rb)
|
||||
std::swap(cBGR0, cBGR2);
|
||||
|
||||
for (; size.height--; gray += gray_step)
|
||||
{
|
||||
for (int i = 0; i < size.width; i++, bgr += ncn)
|
||||
{
|
||||
gray[i] = static_cast<uchar>(((cBGR0 * bgr[0] + cBGR1 * bgr[1] + cBGR2 * bgr[2] + 16384) >> 15) >> 8);
|
||||
}
|
||||
|
||||
bgr += bgr_step - size.width * ncn;
|
||||
|
||||
@@ -155,14 +155,16 @@ bool WebPDecoder::readHeader()
|
||||
webp_data.size = data.total();
|
||||
|
||||
WebPAnimDecoderOptions dec_options;
|
||||
WebPAnimDecoderOptionsInit(&dec_options);
|
||||
if (!WebPAnimDecoderOptionsInit(&dec_options))
|
||||
CV_Error(Error::StsInternal, "Failed to initialize animated WebP decoding 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);
|
||||
if (!WebPAnimDecoderGetInfo(anim_decoder.get(), &anim_info))
|
||||
CV_Error(Error::StsInternal, "Failed to get animated WebP information");
|
||||
m_animation.loop_count = anim_info.loop_count;
|
||||
|
||||
m_animation.bgcolor[0] = (anim_info.bgcolor >> 24) & 0xFF;
|
||||
@@ -216,7 +218,8 @@ bool WebPDecoder::readData(Mat &img)
|
||||
uint8_t* buf;
|
||||
int timestamp;
|
||||
|
||||
WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp);
|
||||
if (!WebPAnimDecoderGetNext(anim_decoder.get(), &buf, ×tamp))
|
||||
CV_Error(Error::StsInternal, "Failed to decode animated WebP frame");
|
||||
Mat tmp(Size(m_width, m_height), CV_8UC4, buf);
|
||||
|
||||
if (img.type() == CV_8UC1)
|
||||
@@ -446,7 +449,6 @@ bool WebPEncoder::writeanimation(const Animation& animation, const std::vector<i
|
||||
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);
|
||||
|
||||
@@ -98,6 +98,9 @@ static inline int calcType(int type, int flags)
|
||||
if( (flags & IMREAD_ANYDEPTH) == 0 )
|
||||
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
|
||||
|
||||
//if( (flags & IMREAD_ANYCOLOR) != 0 /*&& CV_MAT_CN(type) > 1*/ )
|
||||
// type = CV_MAKETYPE(CV_MAT_DEPTH(type), CV_MAT_CN(type));
|
||||
//else if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 )
|
||||
if( (flags & IMREAD_COLOR) != 0 || (flags & IMREAD_COLOR_RGB) != 0 ||
|
||||
((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) )
|
||||
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
|
||||
@@ -410,6 +413,76 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img)
|
||||
}
|
||||
}
|
||||
|
||||
static void readMetadata(ImageDecoder& decoder,
|
||||
std::vector<int>* metadata_types,
|
||||
OutputArrayOfArrays metadata)
|
||||
{
|
||||
if (!metadata_types)
|
||||
return;
|
||||
int kind = metadata.kind();
|
||||
void* obj = metadata.getObj();
|
||||
std::vector<Mat>* matvector = nullptr;
|
||||
std::vector<std::vector<uchar> >* vecvector = nullptr;
|
||||
if (kind == _InputArray::STD_VECTOR_MAT) {
|
||||
matvector = (std::vector<Mat>*)obj;
|
||||
} else if (kind == _InputArray::STD_VECTOR_VECTOR) {
|
||||
int elemtype = metadata.type(0);
|
||||
CV_Assert(elemtype == CV_8UC1 || elemtype == CV_8SC1);
|
||||
vecvector = (std::vector<std::vector<uint8_t> >*)obj;
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg,
|
||||
"unsupported metadata type, should be a vector of matrices or vector of byte vectors");
|
||||
}
|
||||
std::vector<Mat> src_metadata;
|
||||
for (int m = (int)IMAGE_METADATA_EXIF; m <= (int)IMAGE_METADATA_MAX; m++) {
|
||||
Mat mm = decoder->getMetadata((ImageMetadataType)m);
|
||||
if (!mm.empty()) {
|
||||
CV_Assert(mm.isContinuous());
|
||||
CV_Assert(mm.elemSize() == 1u);
|
||||
metadata_types->push_back(m);
|
||||
src_metadata.push_back(mm);
|
||||
}
|
||||
}
|
||||
size_t nmetadata = metadata_types->size();
|
||||
if (matvector) {
|
||||
matvector->resize(nmetadata);
|
||||
for (size_t m = 0; m < nmetadata; m++)
|
||||
src_metadata[m].copyTo(matvector->at(m));
|
||||
} else {
|
||||
vecvector->resize(nmetadata);
|
||||
for (size_t m = 0; m < nmetadata; m++) {
|
||||
const Mat& mm = src_metadata[m];
|
||||
const uchar* data = (uchar*)mm.data;
|
||||
vecvector->at(m).assign(data, data + mm.total());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char* metadataTypeToString(ImageMetadataType type)
|
||||
{
|
||||
return type == IMAGE_METADATA_EXIF ? "Exif" :
|
||||
type == IMAGE_METADATA_XMP ? "XMP" :
|
||||
type == IMAGE_METADATA_ICCP ? "ICC Profile" : "???";
|
||||
}
|
||||
|
||||
static void addMetadata(ImageEncoder& encoder,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata)
|
||||
{
|
||||
size_t nmetadata_chunks = metadata_types.size();
|
||||
for (size_t i = 0; i < nmetadata_chunks; i++) {
|
||||
ImageMetadataType metadata_type = (ImageMetadataType)metadata_types[i];
|
||||
bool ok = encoder->addMetadata(metadata_type, metadata.getMat((int)i));
|
||||
if (!ok) {
|
||||
std::string desc = encoder->getDescription();
|
||||
CV_LOG_WARNING(NULL, "Imgcodecs: metadata of type '"
|
||||
<< metadataTypeToString(metadata_type)
|
||||
<< "' is not supported when encoding '"
|
||||
<< desc << "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image into memory and return the information
|
||||
*
|
||||
@@ -419,11 +492,15 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img)
|
||||
*
|
||||
*/
|
||||
static bool
|
||||
imread_( const String& filename, int flags, OutputArray mat )
|
||||
imread_( const String& filename, int flags, OutputArray mat,
|
||||
std::vector<int>* metadata_types, OutputArrayOfArrays metadata)
|
||||
{
|
||||
/// Search for the relevant decoder to handle the imagery
|
||||
ImageDecoder decoder;
|
||||
|
||||
if (metadata_types)
|
||||
metadata_types->clear();
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
if(flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL ){
|
||||
decoder = GdalDecoder().newDecoder();
|
||||
@@ -501,13 +578,16 @@ imread_( const String& filename, int flags, OutputArray mat )
|
||||
Mat real_mat = mat.getMat();
|
||||
const void * original_ptr = real_mat.data;
|
||||
bool success = false;
|
||||
decoder->resetFrameCount(); // this is needed for PngDecoder. it should be called before decoder->readData()
|
||||
try
|
||||
{
|
||||
if (decoder->readData(real_mat))
|
||||
{
|
||||
CV_CheckTrue((decoder->getFrameCount() > 1) || original_ptr == real_mat.data, "Internal imread issue");
|
||||
CV_CheckTrue(original_ptr == real_mat.data, "Internal imread issue");
|
||||
success = true;
|
||||
}
|
||||
|
||||
readMetadata(decoder, metadata_types, metadata);
|
||||
}
|
||||
catch (const cv::Exception& e)
|
||||
{
|
||||
@@ -661,7 +741,24 @@ Mat imread( const String& filename, int flags )
|
||||
Mat img;
|
||||
|
||||
/// load the data
|
||||
imread_( filename, flags, img );
|
||||
imread_( filename, flags, img, nullptr, noArray() );
|
||||
|
||||
/// return a reference to the data
|
||||
return img;
|
||||
}
|
||||
|
||||
Mat imreadWithMetadata( const String& filename,
|
||||
std::vector<int>& metadata_types,
|
||||
OutputArrayOfArrays metadata,
|
||||
int flags )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
/// create the basic container
|
||||
Mat img;
|
||||
|
||||
/// load the data
|
||||
imread_( filename, flags, img, &metadata_types, metadata );
|
||||
|
||||
/// return a reference to the data
|
||||
return img;
|
||||
@@ -672,7 +769,7 @@ void imread( const String& filename, OutputArray dst, int flags )
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
/// load the data
|
||||
imread_(filename, flags, dst);
|
||||
imread_(filename, flags, dst, nullptr, noArray());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -800,6 +897,7 @@ imreadanimation_(const String& filename, int flags, int start, int count, Animat
|
||||
}
|
||||
animation.bgcolor = decoder->animation().bgcolor;
|
||||
animation.loop_count = decoder->animation().loop_count;
|
||||
animation.still_image = decoder->animation().still_image;
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -910,6 +1008,7 @@ static bool imdecodeanimation_(InputArray buf, int flags, int start, int count,
|
||||
}
|
||||
animation.bgcolor = decoder->animation().bgcolor;
|
||||
animation.loop_count = decoder->animation().loop_count;
|
||||
animation.still_image = decoder->animation().still_image;
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -943,6 +1042,8 @@ size_t imcount(const String& filename, int flags)
|
||||
|
||||
|
||||
static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
const std::vector<int>& params, bool flipv )
|
||||
{
|
||||
bool isMultiImg = img_vec.size() > 1;
|
||||
@@ -957,7 +1058,12 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
||||
Mat image = img_vec[page];
|
||||
CV_Assert(!image.empty());
|
||||
|
||||
#ifdef HAVE_OPENEXR
|
||||
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 || encoder.dynamicCast<ExrEncoder>() );
|
||||
#else
|
||||
CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 );
|
||||
#endif
|
||||
|
||||
|
||||
Mat temp;
|
||||
if( !encoder->isFormatSupported(image.depth()) )
|
||||
@@ -978,6 +1084,7 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
||||
}
|
||||
|
||||
encoder->setDestination( filename );
|
||||
addMetadata(encoder, metadata_types, metadata);
|
||||
|
||||
CV_Check(params.size(), (params.size() & 1) == 0, "Encoding 'params' must be key-value pairs");
|
||||
CV_CheckLE(params.size(), (size_t)(CV_IO_MAX_IMAGE_PARAMS*2), "");
|
||||
@@ -1034,7 +1141,26 @@ bool imwrite( const String& filename, InputArray _img,
|
||||
img_vec.push_back(_img.getMat());
|
||||
|
||||
CV_Assert(!img_vec.empty());
|
||||
return imwrite_(filename, img_vec, params, false);
|
||||
return imwrite_(filename, img_vec, {}, noArray(), params, false);
|
||||
}
|
||||
|
||||
bool imwriteWithMetadata( const String& filename, InputArray _img,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
const std::vector<int>& params )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
CV_Assert(!_img.empty());
|
||||
|
||||
std::vector<Mat> img_vec;
|
||||
if (_img.isMatVector() || _img.isUMatVector())
|
||||
_img.getMatVector(img_vec);
|
||||
else
|
||||
img_vec.push_back(_img.getMat());
|
||||
|
||||
CV_Assert(!img_vec.empty());
|
||||
return imwrite_(filename, img_vec, metadata_types, metadata, params, false);
|
||||
}
|
||||
|
||||
static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector<int>& params)
|
||||
@@ -1119,8 +1245,13 @@ bool imencodeanimation(const String& ext, const Animation& animation, std::vecto
|
||||
}
|
||||
|
||||
static bool
|
||||
imdecode_( const Mat& buf, int flags, Mat& mat )
|
||||
imdecode_( const Mat& buf, int flags, Mat& mat,
|
||||
std::vector<int>* metadata_types,
|
||||
OutputArrayOfArrays metadata )
|
||||
{
|
||||
if (metadata_types)
|
||||
metadata_types->clear();
|
||||
|
||||
CV_Assert(!buf.empty());
|
||||
CV_Assert(buf.isContinuous());
|
||||
CV_Assert(buf.checkVector(1, CV_8U) > 0);
|
||||
@@ -1210,6 +1341,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat )
|
||||
{
|
||||
if (decoder->readData(mat))
|
||||
success = true;
|
||||
readMetadata(decoder, metadata_types, metadata);
|
||||
}
|
||||
catch (const cv::Exception& e)
|
||||
{
|
||||
@@ -1253,7 +1385,7 @@ Mat imdecode( InputArray _buf, int flags )
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
if (!imdecode_(buf, flags, img))
|
||||
if (!imdecode_(buf, flags, img, nullptr, noArray()))
|
||||
img.release();
|
||||
|
||||
return img;
|
||||
@@ -1265,12 +1397,24 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst )
|
||||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
dst = dst ? dst : &img;
|
||||
if (imdecode_(buf, flags, *dst))
|
||||
if (imdecode_(buf, flags, *dst, nullptr, noArray()))
|
||||
return *dst;
|
||||
else
|
||||
return cv::Mat();
|
||||
}
|
||||
|
||||
Mat imdecodeWithMetadata( InputArray _buf, std::vector<int>& metadata_types,
|
||||
OutputArrayOfArrays metadata, int flags )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
if (!imdecode_(buf, flags, img, &metadata_types, metadata))
|
||||
img.release();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
static bool
|
||||
imdecodemulti_(const Mat& buf, int flags, std::vector<Mat>& mats, int start, int count)
|
||||
{
|
||||
@@ -1426,8 +1570,10 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector<Mat>& mats, co
|
||||
}
|
||||
}
|
||||
|
||||
bool imencode( const String& ext, InputArray _img,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params )
|
||||
bool imencodeWithMetadata( const String& ext, InputArray _img,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
@@ -1452,7 +1598,11 @@ bool imencode( const String& ext, InputArray _img,
|
||||
CV_Assert(!image.empty());
|
||||
|
||||
const int channels = image.channels();
|
||||
#ifdef HAVE_OPENEXR
|
||||
CV_Assert( channels == 1 || channels == 3 || channels == 4 || encoder.dynamicCast<ExrEncoder>() );
|
||||
#else
|
||||
CV_Assert( channels == 1 || channels == 3 || channels == 4 );
|
||||
#endif
|
||||
|
||||
Mat temp;
|
||||
if( !encoder->isFormatSupported(image.depth()) )
|
||||
@@ -1477,6 +1627,7 @@ bool imencode( const String& ext, InputArray _img,
|
||||
code = encoder->setDestination(filename);
|
||||
CV_Assert( code );
|
||||
}
|
||||
addMetadata(encoder, metadata_types, metadata);
|
||||
|
||||
try {
|
||||
if (!isMultiImg)
|
||||
@@ -1513,6 +1664,12 @@ bool imencode( const String& ext, InputArray _img,
|
||||
return code;
|
||||
}
|
||||
|
||||
bool imencode( const String& ext, InputArray img,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params_ )
|
||||
{
|
||||
return imencodeWithMetadata(ext, img, {}, noArray(), buf, params_);
|
||||
}
|
||||
|
||||
bool imencodemulti( const String& ext, InputArrayOfArrays imgs,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params)
|
||||
{
|
||||
|
||||
@@ -636,6 +636,52 @@ TEST(Imgcodecs_APNG, imencode_animation)
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_has_hidden_frame)
|
||||
{
|
||||
// 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 + "readwrite/033.png";
|
||||
Animation animation1, animation2, animation3;
|
||||
|
||||
imreadanimation(filename, animation1);
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation1.frames.size());
|
||||
|
||||
std::vector<unsigned char> buf;
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation2));
|
||||
|
||||
EXPECT_FALSE(animation2.still_image.empty());
|
||||
EXPECT_EQ(animation1.frames.size(), animation2.frames.size());
|
||||
|
||||
animation1.frames.erase(animation1.frames.begin());
|
||||
animation1.durations.erase(animation1.durations.begin());
|
||||
EXPECT_TRUE(imencodeanimation(".png", animation1, buf));
|
||||
EXPECT_TRUE(imdecodeanimation(buf, animation3));
|
||||
|
||||
EXPECT_FALSE(animation1.still_image.empty());
|
||||
EXPECT_TRUE(animation3.still_image.empty());
|
||||
EXPECT_EQ((size_t)1, animation3.frames.size());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_APNG, animation_imread_preview)
|
||||
{
|
||||
// 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 + "readwrite/034.png";
|
||||
cv::Mat imread_result;
|
||||
cv::imread(filename, imread_result, cv::IMREAD_UNCHANGED);
|
||||
EXPECT_FALSE(imread_result.empty());
|
||||
|
||||
Animation animation;
|
||||
ASSERT_TRUE(imreadanimation(filename, animation));
|
||||
EXPECT_FALSE(animation.still_image.empty());
|
||||
EXPECT_EQ((size_t)2, animation.frames.size());
|
||||
|
||||
EXPECT_EQ(0, cv::norm(animation.still_image, imread_result, cv::NORM_INF));
|
||||
}
|
||||
|
||||
#endif // HAVE_PNG
|
||||
|
||||
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
|
||||
@@ -676,7 +722,7 @@ TEST(Imgcodecs_APNG, imread_animation_16u)
|
||||
img = imread(filename, IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_TRUE(img.type() == CV_16UC1);
|
||||
EXPECT_EQ(19519, img.at<ushort>(0, 0));
|
||||
EXPECT_EQ(19517, img.at<ushort>(0, 0));
|
||||
|
||||
img = imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH);
|
||||
ASSERT_FALSE(img.empty());
|
||||
|
||||
@@ -148,7 +148,246 @@ const std::vector<std::string> exif_files
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif,
|
||||
testing::ValuesIn(exif_files));
|
||||
testing::ValuesIn(exif_files));
|
||||
|
||||
static Mat makeCirclesImage(Size size, int type, int nbits)
|
||||
{
|
||||
Mat img(size, type);
|
||||
img.setTo(Scalar::all(0));
|
||||
RNG& rng = theRNG();
|
||||
int maxval = (int)(1 << nbits);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = rng.uniform(0, img.cols);
|
||||
int y = rng.uniform(0, img.rows);
|
||||
int radius = rng.uniform(5, std::min(img.cols, img.rows)/5);
|
||||
int b = rng.uniform(0, maxval);
|
||||
int g = rng.uniform(0, maxval);
|
||||
int r = rng.uniform(0, maxval);
|
||||
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
TEST(Imgcodecs_Avif, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
|
||||
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
|
||||
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int avif_nbits = 10;
|
||||
int avif_speed = 10;
|
||||
int avif_quality = 85;
|
||||
int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U;
|
||||
int imgtype = CV_MAKETYPE(imgdepth, 3);
|
||||
const string outputname = cv::tempfile(".avif");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_AVIF_DEPTH, avif_nbits,
|
||||
IMWRITE_AVIF_SPEED, avif_speed,
|
||||
IMWRITE_AVIF_QUALITY, avif_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 1500);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
#endif // HAVE_AVIF
|
||||
|
||||
TEST(Imgcodecs_Jpeg, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
|
||||
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
|
||||
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int jpeg_quality = 95;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".jpeg");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_JPEG_QUALITY, jpeg_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 80);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
|
||||
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
|
||||
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_EQ(mse, 0); // png is lossless
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern)
|
||||
{
|
||||
size_t plen = pattern.size();
|
||||
for (size_t i = 0; i + plen <= exif_size; i++) {
|
||||
if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
typedef std::tuple<std::string, size_t, std::string, size_t> ReadExif_Sanity_Params;
|
||||
typedef testing::TestWithParam<ReadExif_Sanity_Params> ReadExif_Sanity;
|
||||
|
||||
TEST_P(ReadExif_Sanity, Check)
|
||||
{
|
||||
std::string filename = get<0>(GetParam());
|
||||
size_t exif_size = get<1>(GetParam());
|
||||
std::string pattern = get<2>(GetParam());
|
||||
size_t ploc = get<3>(GetParam());
|
||||
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
filename = root + filename;
|
||||
|
||||
std::vector<int> metadata_types;
|
||||
std::vector<Mat> metadata;
|
||||
Mat img = imreadWithMetadata(filename, metadata_types, metadata, 1);
|
||||
|
||||
EXPECT_EQ(img.type(), CV_8UC3);
|
||||
ASSERT_GE(metadata_types.size(), 1u);
|
||||
EXPECT_EQ(metadata_types.size(), metadata.size());
|
||||
const Mat& exif = metadata[IMAGE_METADATA_EXIF];
|
||||
EXPECT_EQ(exif.type(), CV_8U);
|
||||
EXPECT_EQ(exif.total(), exif_size);
|
||||
ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes
|
||||
// (the header + IDF0 with at least 1 entry).
|
||||
EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M');
|
||||
EXPECT_EQ(exif.data[0], exif.data[1]);
|
||||
EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc);
|
||||
}
|
||||
|
||||
static const std::vector<ReadExif_Sanity_Params> exif_sanity_params
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
{"readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120},
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
{"readwrite/testExifOrientation_5.png", 112, "ExifTool", 102},
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
{"readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120},
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity,
|
||||
testing::ValuesIn(exif_sanity_params));
|
||||
|
||||
}}
|
||||
|
||||
@@ -71,6 +71,36 @@ TEST(Imgcodecs_EXR, readWrite_32FC3)
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC7)
|
||||
{ // 0-6 channels (multispectral)
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filenameInput = root + "readwrite/test32FC7.exr";
|
||||
const string filenameOutput = cv::tempfile(".exr");
|
||||
#ifndef GENERATE_DATA
|
||||
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
|
||||
#else
|
||||
const Size sz(3, 5);
|
||||
Mat img(sz, CV_32FC7);
|
||||
img.at<cv::Vec<float, 7>>(0, 0)[0] = 101.125;
|
||||
img.at<cv::Vec<float, 7>>(2, 1)[3] = 203.500;
|
||||
img.at<cv::Vec<float, 7>>(4, 2)[6] = 305.875;
|
||||
ASSERT_TRUE(cv::imwrite(filenameInput, img));
|
||||
#endif
|
||||
ASSERT_FALSE(img.empty());
|
||||
ASSERT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
|
||||
ASSERT_TRUE(cv::imwrite(filenameOutput, img));
|
||||
const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.type(), img.type());
|
||||
EXPECT_EQ(img2.size(), img.size());
|
||||
EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3);
|
||||
EXPECT_EQ(0, remove(filenameOutput.c_str()));
|
||||
const Mat img3 = cv::imread(filenameInput, IMREAD_GRAYSCALE);
|
||||
ASSERT_TRUE(img3.empty());
|
||||
const Mat img4 = cv::imread(filenameInput, IMREAD_COLOR);
|
||||
ASSERT_TRUE(img4.empty());
|
||||
}
|
||||
|
||||
|
||||
TEST(Imgcodecs_EXR, readWrite_32FC1_half)
|
||||
{
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
// 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"
|
||||
#include "test_common.hpp"
|
||||
|
||||
namespace opencv_test { namespace {
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
|
||||
static void test_gdal_read(const string filename, bool required = true) {
|
||||
const string path = cvtest::findDataFile(filename);
|
||||
Mat img;
|
||||
ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR));
|
||||
if(!required && img.empty())
|
||||
{
|
||||
throw SkipTestException("GDAL is built wihout required back-end support");
|
||||
}
|
||||
ASSERT_FALSE(img.empty());
|
||||
EXPECT_EQ(3, img.cols);
|
||||
EXPECT_EQ(5, img.rows);
|
||||
EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
|
||||
EXPECT_EQ(101.125, (img.at<Vec<float, 7>>(0, 0)[0]));
|
||||
EXPECT_EQ(203.500, (img.at<Vec<float, 7>>(2, 1)[3]));
|
||||
EXPECT_EQ(305.875, (img.at<Vec<float, 7>>(4, 2)[6]));
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_envi)
|
||||
{
|
||||
test_gdal_read("../cv/gdal/envi_test.raw");
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_gdal, read_fits)
|
||||
{
|
||||
// .fit test is optional because GDAL may be built wihtout CFITSIO library support
|
||||
test_gdal_read("../cv/gdal/fits_test.fit", false);
|
||||
}
|
||||
|
||||
#endif // HAVE_GDAL
|
||||
|
||||
}} // namespace
|
||||
@@ -150,19 +150,107 @@ TEST(Imgcodecs_Png, decode_regression27295)
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
EXPECT_TRUE(fs.isOpened());
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
|
||||
// Declare matrices for ground truth in different imread flag combinations
|
||||
Mat gt_0, gt_1, gt_2, gt_3, gt_256, gt_258;
|
||||
|
||||
// Handle grayscale 8-bit and 16-bit images
|
||||
if (gt.channels() == 1)
|
||||
{
|
||||
gt.copyTo(gt_2); // For IMREAD_ANYDEPTH
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2; // For IMREAD_GRAYSCALE
|
||||
|
||||
cvtColor(gt_2, gt_3, COLOR_GRAY2BGR); // For IMREAD_COLOR | IMREAD_ANYDEPTH
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3; // For IMREAD_COLOR
|
||||
|
||||
gt_256 = gt_1; // For IMREAD_COLOR_RGB
|
||||
gt_258 = gt_3; // For IMREAD_COLOR_RGB | IMREAD_ANYDEPTH
|
||||
}
|
||||
|
||||
// Handle color images (3 or 4 channels) with 8-bit and 16-bit depth
|
||||
if (gt.channels() > 1)
|
||||
{
|
||||
// Convert to grayscale
|
||||
cvtColor(gt, gt_2, COLOR_BGRA2GRAY);
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_2.convertTo(gt_0, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_0 = gt_2;
|
||||
|
||||
// Convert to 3-channel BGR
|
||||
if (gt.channels() == 3)
|
||||
gt.copyTo(gt_3);
|
||||
else
|
||||
cvtColor(gt, gt_3, COLOR_BGRA2BGR);
|
||||
|
||||
if (gt.depth() == CV_16U)
|
||||
gt_3.convertTo(gt_1, CV_8U, 1. / 256);
|
||||
else
|
||||
gt_1 = gt_3;
|
||||
|
||||
// Convert to RGB for IMREAD_COLOR_RGB variants
|
||||
cvtColor(gt_1, gt_256, COLOR_BGR2RGB);
|
||||
cvtColor(gt_3, gt_258, COLOR_BGR2RGB);
|
||||
}
|
||||
|
||||
// Perform comparisons with different imread flags
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_GRAYSCALE), gt_0);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR), gt_1);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(4, 0), imread(filename, IMREAD_ANYDEPTH), gt_2);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), gt_3);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(1, 0), imread(filename, IMREAD_COLOR_RGB), gt_256);
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), gt_258);
|
||||
|
||||
// Uncomment this block to write out the decoded images for visual/manual inspection
|
||||
// or for regenerating expected ground truth PNGs (for example, after changing decoder logic).
|
||||
#if 0
|
||||
imwrite(filename + "_0.png", imread(filename, IMREAD_GRAYSCALE));
|
||||
imwrite(filename + "_1.png", imread(filename, IMREAD_COLOR));
|
||||
imwrite(filename + "_2.png", imread(filename, IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_3.png", imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH));
|
||||
imwrite(filename + "_256.png", imread(filename, IMREAD_COLOR_RGB));
|
||||
imwrite(filename + "_258.png", imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH));
|
||||
#endif
|
||||
|
||||
// Uncomment this block to verify that saved images (from above) load identically
|
||||
// when read back with IMREAD_UNCHANGED. Helps ensure write-read symmetry.
|
||||
#if 0
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_GRAYSCALE), imread(filename + "_0.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR), imread(filename + "_1.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_ANYDEPTH), imread(filename + "_2.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR | IMREAD_ANYDEPTH), imread(filename + "_3.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB), imread(filename + "_256.png", IMREAD_UNCHANGED));
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), imread(filename, IMREAD_COLOR_RGB | IMREAD_ANYDEPTH), imread(filename + "_258.png", IMREAD_UNCHANGED));
|
||||
#endif
|
||||
}
|
||||
|
||||
const string pngsuite_files[] =
|
||||
@@ -243,23 +331,13 @@ const string pngsuite_files[] =
|
||||
"f04n2c08",
|
||||
"f99n0g04",
|
||||
"g03n0g16",
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n0g16",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n0g16",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n0g16",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g10n0g16",
|
||||
"g10n2c08",
|
||||
"g10n3p04",
|
||||
"g25n0g16",
|
||||
"g25n2c08",
|
||||
"g25n3p04",
|
||||
"oi1n0g16",
|
||||
"oi1n2c16",
|
||||
"oi2n0g16",
|
||||
@@ -333,6 +411,49 @@ const string pngsuite_files[] =
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite,
|
||||
testing::ValuesIn(pngsuite_files));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Gamma;
|
||||
|
||||
// Parameterized test for decoding PNG files from the PNGSuite test set
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Gamma, decode)
|
||||
{
|
||||
// Construct full paths for the PNG image and corresponding ground truth XML file
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
const string filename = root + "pngsuite/" + GetParam() + ".png";
|
||||
const string xml_filename = root + "pngsuite/" + GetParam() + ".xml";
|
||||
|
||||
// Load the XML file containing the ground truth data
|
||||
FileStorage fs(xml_filename, FileStorage::READ);
|
||||
ASSERT_TRUE(fs.isOpened()); // Ensure the file was opened successfully
|
||||
|
||||
// Load the image using IMREAD_UNCHANGED to preserve original format
|
||||
Mat src = imread(filename, IMREAD_UNCHANGED);
|
||||
ASSERT_FALSE(src.empty()); // Ensure the image was loaded successfully
|
||||
|
||||
// Load the ground truth matrix from XML
|
||||
Mat gt;
|
||||
fs.getFirstTopLevelNode() >> gt;
|
||||
|
||||
// Compare the image loaded with IMREAD_UNCHANGED to the ground truth
|
||||
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
|
||||
}
|
||||
|
||||
const string pngsuite_files_gamma[] =
|
||||
{
|
||||
"g03n2c08",
|
||||
"g03n3p04",
|
||||
"g04n2c08",
|
||||
"g04n3p04",
|
||||
"g05n2c08",
|
||||
"g05n3p04",
|
||||
"g07n2c08",
|
||||
"g07n3p04",
|
||||
"g25n2c08",
|
||||
"g25n3p04"
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Gamma,
|
||||
testing::ValuesIn(pngsuite_files_gamma));
|
||||
|
||||
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Corrupted;
|
||||
|
||||
TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode)
|
||||
|
||||
Reference in New Issue
Block a user