1
0
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:
Alexander Smorkalov
2025-07-17 18:23:51 +03:00
140 changed files with 2891 additions and 1208 deletions
-5
View File
@@ -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());
+4
View File
@@ -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
+4
View File
@@ -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;
+35 -4
View File
@@ -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++)
+36
View File
@@ -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> &params) {
std::vector<Mat> img_vec(1, img);
return writemulti(img_vec, params);
+28
View File
@@ -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.
+85 -35
View File
@@ -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 ));
}
+2
View File
@@ -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
+3
View File
@@ -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");
}
+18
View File
@@ -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
View File
@@ -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);
+176 -218
View File
@@ -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> &params)
}
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;
+6 -4
View File
@@ -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, &timestamp);
if (!WebPAnimDecoderGetNext(anim_decoder.get(), &buf, &timestamp))
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);
+167 -10
View File
@@ -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)
{
+47 -1
View File
@@ -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());
+240 -1
View File
@@ -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));
}}
+30
View File
@@ -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)
{
+41
View File
@@ -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
+133 -12
View File
@@ -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)