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

Merge branch 4.x

This commit is contained in:
Alexander Alekhin
2023-01-09 11:08:02 +00:00
880 changed files with 83958 additions and 9368 deletions
+8 -2
View File
@@ -24,6 +24,12 @@ if(HAVE_WEBP)
list(APPEND GRFMT_LIBS ${WEBP_LIBRARIES})
endif()
if(HAVE_SPNG)
add_definitions(${SPNG_DEFINITIONS})
ocv_include_directories(${SPNG_INCLUDE_DIR})
list(APPEND GRFMT_LIBS ${SPNG_LIBRARY})
endif()
if(HAVE_PNG)
add_definitions(${PNG_DEFINITIONS})
ocv_include_directories(${PNG_INCLUDE_DIR})
@@ -67,7 +73,7 @@ if(HAVE_OPENEXR)
endif()
endif()
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR)
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG)
ocv_include_directories(${ZLIB_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES})
endif()
@@ -179,7 +185,7 @@ endif()
if(TARGET opencv_test_imgcodecs AND HAVE_OPENEXR AND "$ENV{OPENCV_IO_ENABLE_OPENEXR}")
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_ENABLE_OPENEXR_TESTS=1)
endif()
if(TARGET opencv_test_imgcodecs AND HAVE_PNG AND NOT (PNG_VERSION VERSION_LESS "1.6.31"))
if(TARGET opencv_test_imgcodecs AND ((HAVE_PNG AND NOT (PNG_VERSION VERSION_LESS "1.6.31")) OR HAVE_SPNG))
# details: https://github.com/glennrp/libpng/commit/68cb0aaee3de6371b81a4613476d9b33e43e95b1
ocv_target_compile_definitions(opencv_test_imgcodecs PRIVATE OPENCV_IMGCODECS_PNG_WITH_EXIF=1)
endif()
@@ -97,7 +97,9 @@ enum ImwriteFlags {
IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1.
IMWRITE_EXR_TYPE = (3 << 4) + 0, /* 48 */ //!< override EXR storage type (FLOAT (FP32) is default)
IMWRITE_EXR_COMPRESSION = (3 << 4) + 1, /* 49 */ //!< override EXR compression type (ZIP_COMPRESSION = 3 is default)
IMWRITE_EXR_DWA_COMPRESSION_LEVEL = (3 << 4) + 2, /* 50 */ //!< override EXR DWA compression level (45 is default)
IMWRITE_WEBP_QUALITY = 64, //!< For WEBP, it can be a quality from 1 to 100 (the higher is the better). By default (without any parameter) and for quality above 100 the lossless compression is used.
IMWRITE_HDR_COMPRESSION = (5 << 4) + 0, /* 80 */ //!< specify HDR compression
IMWRITE_PAM_TUPLETYPE = 128,//!< For PAM, sets the TUPLETYPE field to the corresponding string value that is defined for the format
IMWRITE_TIFF_RESUNIT = 256,//!< For TIFF, use to specify which DPI resolution unit to set; see libtiff documentation for valid values
IMWRITE_TIFF_XDPI = 257,//!< For TIFF, use to specify the X direction DPI
@@ -160,6 +162,12 @@ enum ImwritePAMFlags {
IMWRITE_PAM_FORMAT_RGB_ALPHA = 5
};
//! Imwrite HDR specific values for IMWRITE_HDR_COMPRESSION parameter key
enum ImwriteHDRCompressionFlags {
IMWRITE_HDR_COMPRESSION_NONE = 0,
IMWRITE_HDR_COMPRESSION_RLE = 1
};
//! @} imgcodecs_flags
/** @brief Loads an image from a file.
@@ -306,6 +314,20 @@ reallocations when the function is called repeatedly for images of the same size
*/
CV_EXPORTS Mat imdecode( InputArray buf, int flags, Mat* dst);
/** @brief Reads a multi-page image from a buffer in memory.
The function imdecodemulti reads a multi-page image from the specified buffer in the memory. If the buffer is too short or
contains invalid data, the function returns false.
See cv::imreadmulti 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 flags The same flags as in cv::imread, see cv::ImreadModes.
@param mats A vector of Mat objects holding each page, if more than one.
*/
CV_EXPORTS_W bool imdecodemulti(InputArray buf, int flags, CV_OUT std::vector<Mat>& mats);
/** @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
@@ -332,6 +354,51 @@ CV_EXPORTS_W bool haveImageReader( const String& filename );
*/
CV_EXPORTS_W bool haveImageWriter( const String& filename );
/** @brief To read Multi Page images on demand
The ImageCollection class provides iterator API to read multi page images on demand. Create iterator
to the collection of the images and iterate over the collection. Decode the necessary page with operator*.
The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page,
then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page.
However, the intermediate pages are not decoded during the process, so typically it's quite fast.
This is required because multipage codecs does not support going backwards.
After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1).
If you need memory, you can use .releaseCache() method to release cached index.
The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand.
*/
class CV_EXPORTS ImageCollection {
public:
struct CV_EXPORTS iterator {
iterator(ImageCollection* col);
iterator(ImageCollection* col, int end);
Mat& operator*();
Mat* operator->();
iterator& operator++();
iterator operator++(int);
friend bool operator== (const iterator& a, const iterator& b) { return a.m_curr == b.m_curr; }
friend bool operator!= (const iterator& a, const iterator& b) { return a.m_curr != b.m_curr; }
private:
ImageCollection* m_pCollection;
int m_curr;
};
ImageCollection();
ImageCollection(const String& filename, int flags);
void init(const String& img, int flags);
size_t size() const;
const Mat& at(int index);
const Mat& operator[](int index);
void releaseCache(int index);
iterator begin();
iterator end();
class Impl;
Ptr<Impl> getImpl();
protected:
Ptr<Impl> pImpl;
};
//! @} imgcodecs
+38
View File
@@ -0,0 +1,38 @@
// 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 "perf_precomp.hpp"
namespace opencv_test
{
using namespace perf;
PERF_TEST(JPEG, Decode)
{
String filename = getDataPath("stitching/boat1.jpg");
FILE *f = fopen(filename.c_str(), "rb");
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
vector<uchar> file_buf((size_t)len);
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
fclose(f); f = NULL;
TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED);
SANITY_CHECK_NOTHING();
}
PERF_TEST(JPEG, Encode)
{
String filename = getDataPath("stitching/boat1.jpg");
cv::Mat src = imread(filename);
vector<uchar> buf;
TEST_CYCLE() imencode(".jpg", src, buf);
SANITY_CHECK_NOTHING();
}
} // namespace
+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 "perf_precomp.hpp"
namespace opencv_test
{
using namespace perf;
typedef perf::TestBaseWithParam<std::string> PNG;
PERF_TEST(PNG, decode)
{
String filename = getDataPath("perf/2560x1600.png");
FILE *f = fopen(filename.c_str(), "rb");
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
vector<uchar> file_buf((size_t)len);
EXPECT_EQ(len, (long)fread(&file_buf[0], 1, (size_t)len, f));
fclose(f); f = NULL;
TEST_CYCLE() imdecode(file_buf, IMREAD_UNCHANGED);
SANITY_CHECK_NOTHING();
}
PERF_TEST(PNG, encode)
{
String filename = getDataPath("perf/2560x1600.png");
cv::Mat src = imread(filename);
vector<uchar> buf;
TEST_CYCLE() imencode(".png", src, buf);
SANITY_CHECK_NOTHING();
}
} // namespace
+8
View File
@@ -691,6 +691,14 @@ bool ExrEncoder::write( const Mat& img, const std::vector<int>& params )
CV_Error(Error::StsBadArg, "IMWRITE_EXR_COMPRESSION is invalid or not supported");
}
}
if (params[i] == IMWRITE_EXR_DWA_COMPRESSION_LEVEL)
{
#if OPENEXR_VERSION_MAJOR >= 3
header.dwaCompressionLevel() = params[i + 1];
#else
CV_LOG_ONCE_WARNING(NULL, "Setting `IMWRITE_EXR_DWA_COMPRESSION_LEVEL` not supported in OpenEXR version " + std::to_string(OPENEXR_VERSION_MAJOR) + " (version 3 is required)");
#endif
}
}
if( channels == 3 || channels == 4 )
+16 -2
View File
@@ -141,14 +141,28 @@ bool HdrEncoder::write( const Mat& input_img, const std::vector<int>& params )
if(img.depth() != CV_32F) {
img.convertTo(img, CV_32FC3, 1/255.0f);
}
CV_Assert(params.empty() || params[0] == HDR_NONE || params[0] == HDR_RLE);
int compression = IMWRITE_HDR_COMPRESSION_RLE;
for (size_t i = 0; i + 1 < params.size(); i += 2)
{
switch (params[i])
{
case IMWRITE_HDR_COMPRESSION:
compression = params[i + 1];
break;
default:
break;
}
}
CV_Check(compression, compression == IMWRITE_HDR_COMPRESSION_NONE || compression == IMWRITE_HDR_COMPRESSION_RLE, "");
FILE *fout = fopen(m_filename.c_str(), "wb");
if(!fout) {
return false;
}
RGBE_WriteHeader(fout, img.cols, img.rows, NULL);
if(params.empty() || params[0] == HDR_RLE) {
if (compression == IMWRITE_HDR_COMPRESSION_RLE) {
RGBE_WritePixels_RLE(fout, const_cast<float*>(img.ptr<float>()), img.cols, img.rows);
} else {
RGBE_WritePixels(fout, const_cast<float*>(img.ptr<float>()), img.cols * img.rows);
-6
View File
@@ -50,12 +50,6 @@
namespace cv
{
enum HdrCompression
{
HDR_NONE = 0,
HDR_RLE = 1
};
// Radiance rgbe (.hdr) reader
class HdrDecoder CV_FINAL : public BaseImageDecoder
{
+754
View File
@@ -0,0 +1,754 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#ifdef HAVE_SPNG
/****************************************************************************************\
This part of the file implements PNG codec on base of libspng library,
in particular, this code is based on example.c from libspng
(see 3rdparty/libspng/LICENSE for copyright notice)
\****************************************************************************************/
#ifndef _LFS64_LARGEFILE
#define _LFS64_LARGEFILE 0
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 0
#endif
#include <spng.h>
#include <zlib.h>
#include "grfmt_spng.hpp"
/*
* libspng does not support RGB -> Gray conversion. In order to decode colorful images as grayscale
* we need conversion functions. In the previous png implementation(grfmt_png), the author was set
* to particular values for rgb coefficients. OpenCV icvCvt_BGR2Gray function values does not match
* 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,
uchar *gray, int gray_step,
cv::Size size, 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);
namespace cv
{
/////////////////////// SPngDecoder ///////////////////
SPngDecoder::SPngDecoder()
{
m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa";
m_color_type = 0;
m_ctx = 0;
m_f = 0;
m_buf_supported = true;
m_buf_pos = 0;
m_bit_depth = 0;
}
SPngDecoder::~SPngDecoder()
{
close();
}
ImageDecoder SPngDecoder::newDecoder() const
{
return makePtr<SPngDecoder>();
}
void SPngDecoder::close()
{
if (m_f)
{
fclose(m_f);
m_f = 0;
}
if (m_ctx)
{
struct spng_ctx *ctx = (struct spng_ctx *)m_ctx;
spng_ctx_free(ctx);
m_ctx = 0;
}
}
int SPngDecoder::readDataFromBuf(void *sp_ctx, void *user, void *dst, size_t size)
{
/*
* typedef int spng_read_fn(spng_ctx *ctx, void *user, void *dest, size_t length)
* Type definition for callback passed to spng_set_png_stream() for decoders.
* A read callback function should copy length bytes to dest and return 0 or SPNG_IO_EOF/SPNG_IO_ERROR on error.
*/
CV_UNUSED(sp_ctx);
SPngDecoder *decoder = (SPngDecoder *)(user);
CV_Assert(decoder);
const Mat &buf = decoder->m_buf;
if (decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize())
{
return SPNG_IO_ERROR;
}
memcpy(dst, decoder->m_buf.ptr() + decoder->m_buf_pos, size);
decoder->m_buf_pos += size;
return 0;
}
bool SPngDecoder::readHeader()
{
volatile bool result = false;
close();
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_IGNORE_ADLER32);
if (!ctx)
{
spng_ctx_free(ctx);
return false;
}
m_ctx = ctx;
if (!m_buf.empty())
spng_set_png_stream((struct spng_ctx *)m_ctx, (spng_rw_fn *)readDataFromBuf, this);
else
{
m_f = fopen(m_filename.c_str(), "rb");
if (m_f)
{
spng_set_png_file(ctx, m_f);
}
}
if (!m_buf.empty() || m_f)
{
struct spng_ihdr ihdr;
int ret = spng_get_ihdr(ctx, &ihdr);
if (ret == 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)
{
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:
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;
}
}
}
return result;
}
bool SPngDecoder::readData(Mat &img)
{
volatile bool result = false;
bool color = img.channels() > 1;
struct spng_ctx *png_ptr = (struct spng_ctx *)m_ctx;
if (m_ctx && m_width && m_height)
{
int fmt = SPNG_FMT_PNG;
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)
{
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;
}
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;
ret = spng_decode_image(png_ptr, nullptr, 0, fmt, SPNG_DECODE_PROGRESSIVE | decode_flags);
if (ret == SPNG_OK)
{
struct spng_row_info row_info{};
// If user wants to read image as grayscale(IMREAD_GRAYSCALE), but image format is not
// 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)
{
do
{
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);
}
}
else
{
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)
{
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)
{
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);
}
}
}
else if (color)
{ // RGB -> BGR, convert row by row if png is non-interlaced, otherwise convert image as one
int step = m_width * img.channels();
AutoBuffer<uchar *> _buffer(m_height);
uchar **buffer = _buffer.data();
for (int y = 0; y < m_height; y++)
{
buffer[y] = img.data + y * img.step;
}
if (img.channels() == 4 && m_bit_depth == 16)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0)
{
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)
{
icvCvt_RGBA2BGRA_16u_C4R(reinterpret_cast<const ushort *>(img.data), step * 2, reinterpret_cast<ushort *>(img.data), step * 2, Size(m_width, m_height));
}
}
else if (img.channels() == 4)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0)
{
icvCvt_RGBA2BGRA_8u_C4R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1));
}
} while (ret == SPNG_OK);
if (ihdr.interlace_method)
{
icvCvt_RGBA2BGRA_8u_C4R(img.data, step, img.data, step, Size(m_width, m_height));
}
}
else if (fmt == SPNG_FMT_PNG)
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0)
{
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)
{
icvCvt_RGB2BGR_16u_C3R(reinterpret_cast<const ushort *>(img.data), step,
reinterpret_cast<ushort *>(img.data), step, Size(m_width, m_height));
}
}
else
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
break;
ret = spng_decode_row(png_ptr, buffer[row_info.row_num], image_width);
if (ihdr.interlace_method == 0)
{
icvCvt_RGB2BGR_8u_C3R(buffer[row_info.row_num], 0, buffer[row_info.row_num], 0, Size(m_width, 1));
}
} while (ret == SPNG_OK);
if (ihdr.interlace_method)
{
icvCvt_RGB2BGR_8u_C3R(img.data, step, img.data, step, Size(m_width, m_height));
}
}
}
else
{
do
{
ret = spng_get_row_info(png_ptr, &row_info);
if (ret)
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)
{
ret = spng_decode_chunks(png_ptr);
if(ret == SPNG_OK) result = true;
struct spng_exif exif_s{};
ret = spng_get_exif(png_ptr, &exif_s);
if (ret == SPNG_OK)
{
if (exif_s.data && exif_s.length > 0)
{
result = m_exif.parseExif((unsigned char *)exif_s.data, exif_s.length);
}
}
}
}
}
return result;
}
/////////////////////// SPngEncoder ///////////////////
SPngEncoder::SPngEncoder()
{
m_description = "Portable Network Graphics files (*.png)";
m_buf_supported = true;
}
SPngEncoder::~SPngEncoder()
{
}
bool SPngEncoder::isFormatSupported(int depth) const
{
return depth == CV_8U || depth == CV_16U;
}
ImageEncoder SPngEncoder::newEncoder() const
{
return makePtr<SPngEncoder>();
}
int SPngEncoder::writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length)
{
CV_UNUSED(ctx);
SPngEncoder *encoder = (SPngEncoder *)(user);
CV_Assert(encoder && encoder->m_buf);
size_t cursz = encoder->m_buf->size();
encoder->m_buf->resize(cursz + length);
memcpy(&(*encoder->m_buf)[cursz], dst_src, length);
return 0;
}
bool SPngEncoder::write(const Mat &img, const std::vector<int> &params)
{
int fmt;
spng_ctx *ctx = spng_ctx_new(SPNG_CTX_ENCODER);
FILE *volatile f = 0;
int width = img.cols, height = img.rows;
int depth = img.depth(), channels = img.channels();
volatile bool result = false;
if (depth != CV_8U && depth != CV_16U)
return false;
if (ctx)
{
struct spng_ihdr ihdr = {};
ihdr.height = height;
ihdr.width = width;
int compression_level = -1; // Invalid value to allow setting 0-9 as valid
int compression_strategy = IMWRITE_PNG_STRATEGY_RLE; // Default strategy
bool isBilevel = false;
for (size_t i = 0; i < params.size(); i += 2)
{
if (params[i] == IMWRITE_PNG_COMPRESSION)
{
compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy
compression_level = params[i + 1];
compression_level = MIN(MAX(compression_level, 0), Z_BEST_COMPRESSION);
}
if (params[i] == IMWRITE_PNG_STRATEGY)
{
compression_strategy = params[i + 1];
compression_strategy = MIN(MAX(compression_strategy, 0), Z_FIXED);
}
if (params[i] == IMWRITE_PNG_BILEVEL)
{
isBilevel = params[i + 1] != 0;
}
}
fmt = channels == 1 ? SPNG_COLOR_TYPE_GRAYSCALE : channels == 3 ? SPNG_COLOR_TYPE_TRUECOLOR
: SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
ihdr.bit_depth = depth == CV_8U ? isBilevel ? 1 : 8 : 16;
ihdr.color_type = fmt;
ihdr.interlace_method = SPNG_INTERLACE_NONE;
ihdr.filter_method = SPNG_FILTER_NONE;
ihdr.compression_method = 0;
spng_set_ihdr(ctx, &ihdr);
if (m_buf)
{
spng_set_png_stream(ctx, (spng_rw_fn *)writeDataToBuf, this);
}
else
{
f = fopen(m_filename.c_str(), "wb");
if (f)
spng_set_png_file(ctx, f);
}
if (m_buf || f)
{
if (compression_level >= 0)
{
spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, compression_level);
}
else
{
spng_set_option(ctx, SPNG_FILTER_CHOICE, SPNG_FILTER_CHOICE_SUB);
spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, Z_BEST_SPEED);
}
spng_set_option(ctx, SPNG_IMG_COMPRESSION_STRATEGY, compression_strategy);
int ret;
spng_encode_chunks(ctx);
ret = spng_encode_image(ctx, nullptr, 0, SPNG_FMT_PNG, SPNG_ENCODE_PROGRESSIVE);
if (channels > 1)
{
int error;
if (ret == SPNG_OK)
{
if (depth == CV_16U)
{
AutoBuffer<ushort *> buff2;
buff2.allocate(height);
for (int y = 0; y < height; y++)
buff2[y] = reinterpret_cast<unsigned short *>(img.data + y * img.step);
AutoBuffer<ushort> _buffer;
_buffer.allocate(width * channels * 2);
for (int y = 0; y < height; y++)
{
if (channels == 3)
{
icvCvt_BGR2RGB_16u_C3R(buff2[y], 0,
_buffer.data(), 0, Size(width, 1));
}
else if (channels == 4)
{
icvCvt_BGRA2RGBA_16u_C4R(buff2[y], 0,
_buffer.data(), 0, Size(width, 1));
}
error = spng_encode_row(ctx, _buffer.data(), width * channels * 2);
if (error)
break;
}
}
else
{
AutoBuffer<uchar *> buff;
buff.allocate(height);
for (int y = 0; y < height; y++)
buff[y] = img.data + y * img.step;
AutoBuffer<uchar> _buffer;
_buffer.allocate(width * channels);
for (int y = 0; y < height; y++)
{
if (channels == 3)
{
icvCvt_BGR2RGB_8u_C3R(buff[y], 0, _buffer.data(), 0, Size(width, 1));
}
else if (channels == 4)
{
icvCvt_BGRA2RGBA_8u_C4R(buff[y], 0, _buffer.data(), 0, Size(width, 1));
}
error = spng_encode_row(ctx, _buffer.data(), width * channels);
if (error)
break;
}
}
if (error == SPNG_EOI)
{ // success
spng_encode_chunks(ctx);
ret = SPNG_OK;
}
}
}
else
{
int error;
for (int y = 0; y < height; y++)
{
error = spng_encode_row(ctx, img.data + y * img.step, width * channels * (depth == CV_16U ? 2 : 1));
if (error)
break;
}
if (error == SPNG_EOI)
{ // success
spng_encode_chunks(ctx);
ret = SPNG_OK;
}
}
if (ret == SPNG_OK)
result = true;
}
}
spng_ctx_free(ctx);
if (f)
fclose((FILE *)f);
return result;
}
}
void spngCvt_BGR2Gray_8u_C3C1R(const uchar *bgr, int bgr_step,
uchar *gray, int gray_step,
cv::Size size, 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)
{
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++, bgra += 4)
{
int t = cBGR0 * bgra[0] + 0.5869750977 * bgra[1] + cBGR2 * bgra[2];
gray[i] = (uchar)t;
}
bgra += rgba_step - size.width * 4;
}
}
void spngCvt_BGRA2Gray_16u_CnC1R(const ushort *bgr, int bgr_step,
ushort *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 += ncn)
{
int t = cBGR0 * bgr[0] + 0.5869750977 * bgr[1] + cBGR2 * bgr[2];
gray[i] = (ushort)t;
}
bgr += bgr_step - size.width * ncn;
}
}
#endif
/* End of file. */
+60
View File
@@ -0,0 +1,60 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _GRFMT_SPNG_H_
#define _GRFMT_SPNG_H_
#ifdef HAVE_SPNG
#include "grfmt_base.hpp"
#include "bitstrm.hpp"
namespace cv
{
class SPngDecoder CV_FINAL : public BaseImageDecoder
{
public:
SPngDecoder();
virtual ~SPngDecoder();
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
void close();
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
static int readDataFromBuf(void* sp_ctx, void *user, void* dst, size_t size);
int m_bit_depth;
void* m_ctx;
FILE* m_f;
int m_color_type;
size_t m_buf_pos;
};
class SPngEncoder CV_FINAL : public BaseImageEncoder
{
public:
SPngEncoder();
virtual ~SPngEncoder();
bool isFormatSupported( int depth ) const CV_OVERRIDE;
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
protected:
static int writeDataToBuf(void *ctx, void *user, void *dst_src, size_t length);
};
}
#endif
#endif/*_GRFMT_PNG_H_*/
+230 -31
View File
@@ -238,7 +238,6 @@ public:
bool TiffDecoder::readHeader()
{
bool result = false;
TIFF* tif = static_cast<TIFF*>(m_tif.get());
if (!tif)
{
@@ -312,6 +311,18 @@ bool TiffDecoder::readHeader()
result = true;
break;
}
case 4:
//support 4-bit palette.
if (photometric == PHOTOMETRIC_PALETTE)
{
CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, "");
int depth = sample_format == SAMPLEFORMAT_INT ? CV_8S : CV_8U;
m_type = CV_MAKETYPE(depth, 3);
result = true;
}
else
CV_Error(cv::Error::StsError, "bitsperpixel value is 4 should be palette.");
break;
case 8:
{
//Palette color, the value of the component is used as an index into the red,
@@ -425,18 +436,15 @@ static void fixOrientationFull(Mat &img, int orientation)
* For 8 bit some corrections are done by TIFFReadRGBAStrip/Tile already.
* Not so for 16/32/64 bit.
*/
static void fixOrientation(Mat &img, uint16 orientation, int dst_bpp)
static void fixOrientation(Mat &img, uint16 orientation, bool isOrientationFull)
{
switch(dst_bpp) {
case 8:
fixOrientationPartial(img, orientation);
break;
case 16:
case 32:
case 64:
fixOrientationFull(img, orientation);
break;
if( isOrientationFull )
{
fixOrientationFull(img, orientation);
}
else
{
fixOrientationPartial(img, orientation);
}
}
@@ -620,17 +628,7 @@ bool TiffDecoder::readData( Mat& img )
(img_orientation == ORIENTATION_BOTRIGHT || img_orientation == ORIENTATION_RIGHTBOT ||
img_orientation == ORIENTATION_BOTLEFT || img_orientation == ORIENTATION_LEFTBOT);
int wanted_channels = normalizeChannelsNumber(img.channels());
if (dst_bpp == 8)
{
char errmsg[1024];
if (!TIFFRGBAImageOK(tif, errmsg))
{
CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg);
close();
return false;
}
}
bool doReadScanline = false;
uint32 tile_width0 = m_width, tile_height0 = 0;
@@ -660,21 +658,123 @@ bool TiffDecoder::readData( Mat& img )
const uint64_t MAX_TILE_SIZE = (CV_BIG_UINT(1) << 30);
CV_CheckLE((int)ncn, 4, "");
CV_CheckLE((int)bpp, 64, "");
CV_Assert(((uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) < MAX_TILE_SIZE) && "TIFF tile size is too large: >= 1Gb");
if (dst_bpp == 8)
{
// we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA
bpp = 8;
ncn = 4;
const int _ncn = 4; // Read RGBA
const int _bpp = 8; // Read 8bit
// if buffer_size(as 32bit RGBA) >= MAX_TILE_SIZE*95%,
// we will use TIFFReadScanline function.
if (
(uint64_t)tile_width0 * tile_height0 * _ncn * std::max(1, (int)(_bpp / bitsPerByte))
>=
( (uint64_t) MAX_TILE_SIZE * 95 / 100)
)
{
uint16_t planerConfig = (uint16)-1;
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig));
doReadScanline = (!is_tiled) // no tile
&&
( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) )
&&
( ( bpp == 8 ) || ( bpp == 16 ) )
&&
(tile_height0 == (uint32_t) m_height) // single strip
&&
(
(photometric == PHOTOMETRIC_MINISWHITE)
||
(photometric == PHOTOMETRIC_MINISBLACK)
||
(photometric == PHOTOMETRIC_RGB)
)
&&
(planerConfig != PLANARCONFIG_SEPARATE);
// Currently only EXTRASAMPLE_ASSOCALPHA is supported.
if ( doReadScanline && ( ncn == 4 ) )
{
uint16_t extra_samples_num;
uint16_t *extra_samples = NULL;
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples ));
doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA );
}
}
if ( !doReadScanline )
{
// we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA
bpp = 8;
ncn = 4;
char errmsg[1024];
if (!TIFFRGBAImageOK(tif, errmsg))
{
CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg);
close();
return false;
}
}
}
else if (dst_bpp == 16)
{
// if buffer_size >= MAX_TILE_SIZE*95%,
// we will use TIFFReadScanline function.
if (
(uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte))
>=
MAX_TILE_SIZE * 95 / 100
)
{
uint16_t planerConfig = (uint16)-1;
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig));
doReadScanline = (!is_tiled) // no tile
&&
( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) )
&&
( ( bpp == 8 ) || ( bpp == 16 ) )
&&
(tile_height0 == (uint32_t) m_height) // single strip
&&
(
(photometric == PHOTOMETRIC_MINISWHITE)
||
(photometric == PHOTOMETRIC_MINISBLACK)
||
(photometric == PHOTOMETRIC_RGB)
)
&&
(planerConfig != PLANARCONFIG_SEPARATE);
// Currently only EXTRASAMPLE_ASSOCALPHA is supported.
if ( doReadScanline && ( ncn == 4 ) )
{
uint16_t extra_samples_num;
uint16_t *extra_samples = NULL;
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples ));
doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA );
}
}
}
else if (dst_bpp == 32 || dst_bpp == 64)
{
CV_Assert(ncn == img.channels());
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP));
}
if ( doReadScanline )
{
// Read each scanlines.
tile_height0 = 1;
}
const size_t src_buffer_bytes_per_row = divUp(static_cast<size_t>(ncn * tile_width0 * bpp), static_cast<size_t>(bitsPerByte));
const size_t src_buffer_size = tile_height0 * src_buffer_bytes_per_row;
CV_CheckLT(src_buffer_size, MAX_TILE_SIZE, "buffer_size is too large: >= 1Gb");
const size_t src_buffer_unpacked_bytes_per_row = divUp(static_cast<size_t>(ncn * tile_width0 * dst_bpp), static_cast<size_t>(bitsPerByte));
const size_t src_buffer_unpacked_size = tile_height0 * src_buffer_unpacked_bytes_per_row;
const bool needsUnpacking = (bpp < dst_bpp);
@@ -682,8 +782,20 @@ bool TiffDecoder::readData( Mat& img )
uchar* src_buffer = _src_buffer.data();
AutoBuffer<uchar> _src_buffer_unpacked(needsUnpacking ? src_buffer_unpacked_size : 0);
uchar* src_buffer_unpacked = needsUnpacking ? _src_buffer_unpacked.data() : nullptr;
if ( doReadScanline )
{
CV_CheckGE(src_buffer_size,
static_cast<size_t>(TIFFScanlineSize(tif)),
"src_buffer_size is smaller than TIFFScanlineSize().");
}
int tileidx = 0;
#define MAKE_FLAG(a,b) ( (a << 8) | b )
const int convert_flag = MAKE_FLAG( ncn, wanted_channels );
const bool isNeedConvert16to8 = ( doReadScanline ) && ( bpp == 16 ) && ( dst_bpp == 8);
for (int y = 0; y < m_height; y += (int)tile_height0)
{
int tile_height = std::min((int)tile_height0, m_height - y);
@@ -699,7 +811,29 @@ bool TiffDecoder::readData( Mat& img )
case 8:
{
uchar* bstart = src_buffer;
if (!is_tiled)
if (doReadScanline)
{
CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)src_buffer, y) >= 0);
if ( isNeedConvert16to8 )
{
// Convert buffer image from 16bit to 8bit.
int ix;
for ( ix = 0 ; ix < tile_width * ncn - 4; ix += 4 )
{
src_buffer[ ix ] = src_buffer[ ix * 2 + 1 ];
src_buffer[ ix + 1 ] = src_buffer[ ix * 2 + 3 ];
src_buffer[ ix + 2 ] = src_buffer[ ix * 2 + 5 ];
src_buffer[ ix + 3 ] = src_buffer[ ix * 2 + 7 ];
}
for ( ; ix < tile_width * ncn ; ix ++ )
{
src_buffer[ ix ] = src_buffer[ ix * 2 + 1];
}
}
}
else if (!is_tiled)
{
CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)src_buffer));
}
@@ -710,9 +844,65 @@ bool TiffDecoder::readData( Mat& img )
bstart += (tile_height0 - tile_height) * tile_width0 * 4;
}
uchar* img_line_buffer = (uchar*) img.ptr(y, 0);
for (int i = 0; i < tile_height; i++)
{
if (color)
if (doReadScanline)
{
switch ( convert_flag )
{
case MAKE_FLAG( 1, 1 ): // GRAY to GRAY
memcpy( (void*) img_line_buffer,
(void*) bstart,
tile_width * sizeof(uchar) );
break;
case MAKE_FLAG( 1, 3 ): // GRAY to BGR
icvCvt_Gray2BGR_8u_C1C3R( bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1) );
break;
case MAKE_FLAG( 3, 1): // RGB to GRAY
icvCvt_BGR2Gray_8u_C3C1R( bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1) );
break;
case MAKE_FLAG( 3, 3 ): // RGB to BGR
icvCvt_BGR2RGB_8u_C3R( bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1) );
break;
case MAKE_FLAG( 4, 1 ): // RGBA to GRAY
icvCvt_BGRA2Gray_8u_C4C1R( bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1) );
break;
case MAKE_FLAG( 4, 3 ): // RGBA to BGR
icvCvt_BGRA2BGR_8u_C4C3R( bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1), 2 );
break;
case MAKE_FLAG( 4, 4 ): // RGBA to BGRA
icvCvt_BGRA2RGBA_8u_C4R(bstart, 0,
img_line_buffer, 0,
Size(tile_width, 1) );
break;
default:
CV_LOG_ONCE_ERROR(NULL, "OpenCV TIFF(line " << __LINE__ << "): Unsupported convertion :"
<< " bpp = " << bpp << " ncn = " << (int)ncn
<< " wanted_channels =" << wanted_channels );
break;
}
#undef MAKE_FLAG
}
else if (color)
{
if (wanted_channels == 4)
{
@@ -741,7 +931,11 @@ bool TiffDecoder::readData( Mat& img )
case 16:
{
if (!is_tiled)
if (doReadScanline)
{
CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32*)src_buffer, y) >= 0);
}
else if (!is_tiled)
{
CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)src_buffer, src_buffer_size) >= 0);
}
@@ -862,7 +1056,11 @@ bool TiffDecoder::readData( Mat& img )
}
if (bpp < dst_bpp)
img *= (1<<(dst_bpp-bpp));
fixOrientation(img, img_orientation, dst_bpp);
// If TIFFReadRGBA* function is used -> fixOrientationPartial().
// Otherwise -> fixOrientationFull().
fixOrientation(img, img_orientation,
( ( dst_bpp != 8 ) && ( !doReadScanline ) ) );
}
if (m_hdr && depth >= CV_32F)
@@ -887,6 +1085,7 @@ TiffEncoder::~TiffEncoder()
ImageEncoder TiffEncoder::newEncoder() const
{
cv_tiffSetErrorHandler();
return makePtr<TiffEncoder>();
}
+2
View File
@@ -126,6 +126,8 @@ bool WebPDecoder::readHeader()
WebPBitstreamFeatures features;
if (VP8_STATUS_OK == WebPGetFeatures(header, sizeof(header), &features))
{
CV_CheckEQ(features.has_animation, 0, "WebP backend does not support animated webp images");
m_width = features.width;
m_height = features.height;
+1
View File
@@ -49,6 +49,7 @@
#include "grfmt_pxm.hpp"
#include "grfmt_pfm.hpp"
#include "grfmt_tiff.hpp"
#include "grfmt_spng.hpp"
#include "grfmt_png.hpp"
#include "grfmt_jpeg2000.hpp"
#include "grfmt_jpeg2000_openjpeg.hpp"
+411 -52
View File
@@ -54,6 +54,8 @@
#include <cerrno>
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/core/utils/configuration.private.hpp>
#include <opencv2/imgcodecs.hpp>
/****************************************************************************************\
@@ -167,7 +169,10 @@ struct ImageCodecInitializer
decoders.push_back( makePtr<TiffDecoder>() );
encoders.push_back( makePtr<TiffEncoder>() );
#endif
#ifdef HAVE_PNG
#ifdef HAVE_SPNG
decoders.push_back( makePtr<SPngDecoder>() );
encoders.push_back( makePtr<SPngEncoder>() );
#elif defined(HAVE_PNG)
decoders.push_back( makePtr<PngDecoder>() );
encoders.push_back( makePtr<PngEncoder>() );
#endif
@@ -658,57 +663,14 @@ bool imreadmulti(const String& filename, std::vector<Mat>& mats, int start, int
static
size_t imcount_(const String& filename, int flags)
{
/// Search for the relevant decoder to handle the imagery
ImageDecoder decoder;
#ifdef HAVE_GDAL
if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
decoder = GdalDecoder().newDecoder();
try{
ImageCollection collection(filename, flags);
return collection.size();
} catch(cv::Exception const& e) {
// Reading header or finding decoder for the filename is failed
std::cerr << "imcount_('" << filename << "'): can't read header or can't find decoder: " << e.what() << std::endl << std::flush;
}
else {
#else
CV_UNUSED(flags);
#endif
decoder = findDecoder(filename);
#ifdef HAVE_GDAL
}
#endif
/// if no decoder was found, return nothing.
if (!decoder) {
return 0;
}
/// set the filename in the driver
decoder->setSource(filename);
// read the header to make sure it succeeds
try
{
// read the header to make sure it succeeds
if (!decoder->readHeader())
return 0;
}
catch (const cv::Exception& e)
{
std::cerr << "imcount_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush;
return 0;
}
catch (...)
{
std::cerr << "imcount_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush;
return 0;
}
size_t result = 1;
while (decoder->nextPage())
{
++result;
}
return result;
return 0;
}
size_t imcount(const String& filename, int flags)
@@ -754,7 +716,9 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
}
encoder->setDestination( filename );
CV_Assert(params.size() <= CV_IO_MAX_IMAGE_PARAMS*2);
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), "");
bool code = false;
try
{
@@ -967,6 +931,157 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst )
return *dst;
}
static bool
imdecodemulti_(const Mat& buf, int flags, std::vector<Mat>& mats, int start, int count)
{
CV_Assert(!buf.empty());
CV_Assert(buf.isContinuous());
CV_Assert(buf.checkVector(1, CV_8U) > 0);
Mat buf_row = buf.reshape(1, 1); // decoders expects single row, avoid issues with vector columns
String filename;
ImageDecoder decoder = findDecoder(buf_row);
if (!decoder)
return 0;
if (count < 0) {
count = std::numeric_limits<int>::max();
}
if (!decoder->setSource(buf_row))
{
filename = tempfile();
FILE* f = fopen(filename.c_str(), "wb");
if (!f)
return 0;
size_t bufSize = buf_row.total() * buf.elemSize();
if (fwrite(buf_row.ptr(), 1, bufSize, f) != bufSize)
{
fclose(f);
CV_Error(Error::StsError, "failed to write image data to temporary file");
}
if (fclose(f) != 0)
{
CV_Error(Error::StsError, "failed to write image data to temporary file");
}
decoder->setSource(filename);
}
// read the header to make sure it succeeds
bool success = false;
try
{
// read the header to make sure it succeeds
if (decoder->readHeader())
success = true;
}
catch (const cv::Exception& e)
{
std::cerr << "imreadmulti_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush;
}
catch (...)
{
std::cerr << "imreadmulti_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush;
}
int current = start;
while (success && current > 0)
{
if (!decoder->nextPage())
{
success = false;
break;
}
--current;
}
if (!success)
{
decoder.release();
if (!filename.empty())
{
if (0 != remove(filename.c_str()))
{
std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush;
}
}
return 0;
}
while (current < count)
{
// grab the decoded type
int type = decoder->type();
if ((flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED)
{
if ((flags & IMREAD_ANYDEPTH) == 0)
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
if ((flags & IMREAD_COLOR) != 0 ||
((flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1))
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
}
// established the required input image size
Size size = validateInputImageSize(Size(decoder->width(), decoder->height()));
// read the image data
Mat mat(size.height, size.width, type);
success = false;
try
{
if (decoder->readData(mat))
success = true;
}
catch (const cv::Exception& e)
{
std::cerr << "imreadmulti_('" << filename << "'): can't read data: " << e.what() << std::endl << std::flush;
}
catch (...)
{
std::cerr << "imreadmulti_('" << filename << "'): can't read data: unknown exception" << std::endl << std::flush;
}
if (!success)
break;
// optionally rotate the data if EXIF' orientation flag says so
if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED)
{
ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat);
}
mats.push_back(mat);
if (!decoder->nextPage())
{
break;
}
++current;
}
if (!filename.empty())
{
if (0 != remove(filename.c_str()))
{
std::cerr << "unable to remove temporary file:" << filename << std::endl << std::flush;
}
}
if (!success)
mats.clear();
return !mats.empty();
}
bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector<Mat>& mats)
{
CV_TRACE_FUNCTION();
Mat buf = _buf.getMat();
return imdecodemulti_(buf, flags, mats, 0, -1);
}
bool imencode( const String& ext, InputArray _image,
std::vector<uchar>& buf, const std::vector<int>& params )
{
@@ -990,6 +1105,9 @@ bool imencode( const String& ext, InputArray _image,
image = temp;
}
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), "");
bool code;
if( encoder->setDestination(buf) )
{
@@ -1032,6 +1150,247 @@ bool haveImageWriter( const String& filename )
return !encoder.empty();
}
class ImageCollection::Impl {
public:
Impl() = default;
Impl(const std::string& filename, int flags);
void init(String const& filename, int flags);
size_t size() const;
Mat& at(int index);
Mat& operator[](int index);
void releaseCache(int index);
ImageCollection::iterator begin(ImageCollection* ptr);
ImageCollection::iterator end(ImageCollection* ptr);
Mat read();
int width() const;
int height() const;
bool readHeader();
Mat readData();
bool advance();
int currentIndex() const;
void reset();
private:
String m_filename;
int m_flags{};
std::size_t m_size{};
int m_width{};
int m_height{};
int m_current{};
std::vector<cv::Mat> m_pages;
ImageDecoder m_decoder;
};
ImageCollection::Impl::Impl(std::string const& filename, int flags) {
this->init(filename, flags);
}
void ImageCollection::Impl::init(String const& filename, int flags) {
m_filename = filename;
m_flags = flags;
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(filename);
#ifdef HAVE_GDAL
}
#endif
CV_Assert(m_decoder);
m_decoder->setSource(filename);
CV_Assert(m_decoder->readHeader());
// count the pages of the image collection
size_t count = 1;
while(m_decoder->nextPage()) count++;
m_size = count;
m_pages.resize(m_size);
// Reinitialize the decoder because we advanced to the last page while counting the pages of the image
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(m_filename);
#ifdef HAVE_GDAL
}
#endif
m_decoder->setSource(m_filename);
m_decoder->readHeader();
}
size_t ImageCollection::Impl::size() const { return m_size; }
Mat ImageCollection::Impl::read() {
auto result = this->readHeader();
if(!result) {
return {};
}
return this->readData();
}
int ImageCollection::Impl::width() const {
return m_width;
}
int ImageCollection::Impl::height() const {
return m_height;
}
bool ImageCollection::Impl::readHeader() {
bool status = m_decoder->readHeader();
m_width = m_decoder->width();
m_height = m_decoder->height();
return status;
}
// readHeader must be called before calling this method
Mat ImageCollection::Impl::readData() {
int type = m_decoder->type();
if ((m_flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && m_flags != IMREAD_UNCHANGED) {
if ((m_flags & IMREAD_ANYDEPTH) == 0)
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
if ((m_flags & IMREAD_COLOR) != 0 ||
((m_flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1))
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
}
// established the required input image size
Size size = validateInputImageSize(Size(m_width, m_height));
Mat mat(size.height, size.width, type);
bool success = false;
try {
if (m_decoder->readData(mat))
success = true;
}
catch (const cv::Exception &e) {
std::cerr << "ImageCollection class: can't read data: " << e.what() << std::endl << std::flush;
}
catch (...) {
std::cerr << "ImageCollection class:: can't read data: unknown exception" << std::endl << std::flush;
}
if (!success)
return cv::Mat();
if ((m_flags & IMREAD_IGNORE_ORIENTATION) == 0 && m_flags != IMREAD_UNCHANGED) {
ApplyExifOrientation(m_decoder->getExifTag(ORIENTATION), mat);
}
return mat;
}
bool ImageCollection::Impl::advance() { ++m_current; return m_decoder->nextPage(); }
int ImageCollection::Impl::currentIndex() const { return m_current; }
ImageCollection::iterator ImageCollection::Impl::begin(ImageCollection* ptr) { return ImageCollection::iterator(ptr); }
ImageCollection::iterator ImageCollection::Impl::end(ImageCollection* ptr) { return ImageCollection::iterator(ptr, static_cast<int>(this->size())); }
void ImageCollection::Impl::reset() {
m_current = 0;
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(m_filename);
#ifdef HAVE_GDAL
}
#endif
m_decoder->setSource(m_filename);
m_decoder->readHeader();
}
Mat& ImageCollection::Impl::at(int index) {
CV_Assert(index >= 0 && size_t(index) < m_size);
return operator[](index);
}
Mat& ImageCollection::Impl::operator[](int index) {
if(m_pages.at(index).empty()) {
// We can't go backward in multi images. If the page is not in vector yet,
// go back to first page and advance until the desired page and read it into memory
if(m_current != index) {
reset();
for(int i = 0; i != index && advance(); ++i) {}
}
m_pages[index] = read();
}
return m_pages[index];
}
void ImageCollection::Impl::releaseCache(int index) {
CV_Assert(index >= 0 && size_t(index) < m_size);
m_pages[index].release();
}
/* ImageCollection API*/
ImageCollection::ImageCollection() : pImpl(new Impl()) {}
ImageCollection::ImageCollection(const std::string& filename, int flags) : pImpl(new Impl(filename, flags)) {}
void ImageCollection::init(const String& img, int flags) { pImpl->init(img, flags); }
size_t ImageCollection::size() const { return pImpl->size(); }
const Mat& ImageCollection::at(int index) { return pImpl->at(index); }
const Mat& ImageCollection::operator[](int index) { return pImpl->operator[](index); }
void ImageCollection::releaseCache(int index) { pImpl->releaseCache(index); }
Ptr<ImageCollection::Impl> ImageCollection::getImpl() { return pImpl; }
/* Iterator API */
ImageCollection::iterator ImageCollection::begin() { return pImpl->begin(this); }
ImageCollection::iterator ImageCollection::end() { return pImpl->end(this); }
ImageCollection::iterator::iterator(ImageCollection* col) : m_pCollection(col), m_curr(0) {}
ImageCollection::iterator::iterator(ImageCollection* col, int end) : m_pCollection(col), m_curr(end) {}
Mat& ImageCollection::iterator::operator*() {
CV_Assert(m_pCollection);
return m_pCollection->getImpl()->operator[](m_curr);
}
Mat* ImageCollection::iterator::operator->() {
CV_Assert(m_pCollection);
return &m_pCollection->getImpl()->operator[](m_curr);
}
ImageCollection::iterator& ImageCollection::iterator::operator++() {
if(m_pCollection->pImpl->currentIndex() == m_curr) {
m_pCollection->pImpl->advance();
}
m_curr++;
return *this;
}
ImageCollection::iterator ImageCollection::iterator::operator++(int) {
iterator tmp = *this;
++(*this);
return tmp;
}
}
/* End of file. */
+33 -9
View File
@@ -211,7 +211,7 @@ TEST_P(Imgcodecs_ExtSize, write_imageseq)
const string all_exts[] =
{
#ifdef HAVE_PNG
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
".png",
#endif
#ifdef HAVE_TIFF
@@ -343,21 +343,45 @@ TEST(Imgcodecs_Hdr, regression)
Mat img_no_rle = imread(name_no_rle, -1);
ASSERT_FALSE(img_no_rle.empty()) << "Could not open " << name_no_rle;
double min = 0.0, max = 1.0;
minMaxLoc(abs(img_rle - img_no_rle), &min, &max);
ASSERT_FALSE(max > DBL_EPSILON);
EXPECT_EQ(cvtest::norm(img_rle, img_no_rle, NORM_INF), 0.0);
string tmp_file_name = tempfile(".hdr");
vector<int>param(1);
vector<int> param(2);
param[0] = IMWRITE_HDR_COMPRESSION;
for(int i = 0; i < 2; i++) {
param[0] = i;
param[1] = i;
imwrite(tmp_file_name, img_rle, param);
Mat written_img = imread(tmp_file_name, -1);
ASSERT_FALSE(written_img.empty()) << "Could not open " << tmp_file_name;
minMaxLoc(abs(img_rle - written_img), &min, &max);
ASSERT_FALSE(max > DBL_EPSILON);
EXPECT_EQ(cvtest::norm(written_img, img_rle, NORM_INF), 0.0);
}
remove(tmp_file_name.c_str());
}
TEST(Imgcodecs_Hdr, regression_imencode)
{
string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/";
string name = folder + "rle.hdr";
Mat img_ref = imread(name, -1);
ASSERT_FALSE(img_ref.empty()) << "Could not open " << name;
vector<int> params(2);
params[0] = IMWRITE_HDR_COMPRESSION;
{
vector<uchar> buf;
params[1] = IMWRITE_HDR_COMPRESSION_NONE;
imencode(".hdr", img_ref, buf, params);
Mat img = imdecode(buf, -1);
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
}
{
vector<uchar> buf;
params[1] = IMWRITE_HDR_COMPRESSION_RLE;
imencode(".hdr", img_ref, buf, params);
Mat img = imdecode(buf, -1);
EXPECT_EQ(cvtest::norm(img_ref, img, NORM_INF), 0.0);
}
}
#endif
#ifdef HAVE_IMGCODEC_PXM
+220 -1
View File
@@ -5,7 +5,7 @@
namespace opencv_test { namespace {
#ifdef HAVE_PNG
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
TEST(Imgcodecs_Png, write_big)
{
@@ -186,6 +186,225 @@ const string exif_files[] =
INSTANTIATE_TEST_CASE_P(ExifFiles, Imgcodecs_PNG_Exif,
testing::ValuesIn(exif_files));
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite;
TEST_P(Imgcodecs_Png_PngSuite, decode)
{
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());
Mat src = imread(filename, IMREAD_UNCHANGED);
Mat gt;
fs.getFirstTopLevelNode() >> gt;
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), src, gt);
}
const string pngsuite_files[] =
{
"basi0g01",
"basi0g02",
"basi0g04",
"basi0g08",
"basi0g16",
"basi2c08",
"basi2c16",
"basi3p01",
"basi3p02",
"basi3p04",
"basi3p08",
"basi4a08",
"basi4a16",
"basi6a08",
"basi6a16",
"basn0g01",
"basn0g02",
"basn0g04",
"basn0g08",
"basn0g16",
"basn2c08",
"basn2c16",
"basn3p01",
"basn3p02",
"basn3p04",
"basn3p08",
"basn4a08",
"basn4a16",
"basn6a08",
"basn6a16",
"bgai4a08",
"bgai4a16",
"bgan6a08",
"bgan6a16",
"bgbn4a08",
"bggn4a16",
"bgwn6a08",
"bgyn6a16",
"ccwn2c08",
"ccwn3p08",
"cdfn2c08",
"cdhn2c08",
"cdsn2c08",
"cdun2c08",
"ch1n3p04",
"ch2n3p08",
"cm0n0g04",
"cm7n0g04",
"cm9n0g04",
"cs3n2c16",
"cs3n3p08",
"cs5n2c08",
"cs5n3p08",
"cs8n2c08",
"cs8n3p08",
"ct0n0g04",
"ct1n0g04",
"cten0g04",
"ctfn0g04",
"ctgn0g04",
"cthn0g04",
"ctjn0g04",
"ctzn0g04",
"exif2c08",
"f00n0g08",
"f00n2c08",
"f01n0g08",
"f01n2c08",
"f02n0g08",
"f02n2c08",
"f03n0g08",
"f03n2c08",
"f04n0g08",
"f04n2c08",
"f99n0g04",
"g03n0g16",
"g03n2c08",
"g03n3p04",
"g04n0g16",
"g04n2c08",
"g04n3p04",
"g05n0g16",
"g05n2c08",
"g05n3p04",
"g07n0g16",
"g07n2c08",
"g07n3p04",
"g10n0g16",
"g10n2c08",
"g10n3p04",
"g25n0g16",
"g25n2c08",
"g25n3p04",
"oi1n0g16",
"oi1n2c16",
"oi2n0g16",
"oi2n2c16",
"oi4n0g16",
"oi4n2c16",
"oi9n0g16",
"oi9n2c16",
"pp0n2c16",
"pp0n6a08",
"ps1n0g08",
"ps1n2c16",
"ps2n0g08",
"ps2n2c16",
"s01i3p01",
"s01n3p01",
"s02i3p01",
"s02n3p01",
"s03i3p01",
"s03n3p01",
"s04i3p01",
"s04n3p01",
"s05i3p02",
"s05n3p02",
"s06i3p02",
"s06n3p02",
"s07i3p02",
"s07n3p02",
"s08i3p02",
"s08n3p02",
"s09i3p02",
"s09n3p02",
"s32i3p04",
"s32n3p04",
"s33i3p04",
"s33n3p04",
"s34i3p04",
"s34n3p04",
"s35i3p04",
"s35n3p04",
"s36i3p04",
"s36n3p04",
"s37i3p04",
"s37n3p04",
"s38i3p04",
"s38n3p04",
"s39i3p04",
"s39n3p04",
"s40i3p04",
"s40n3p04",
"tbbn0g04",
"tbbn2c16",
"tbbn3p08",
"tbgn2c16",
"tbgn3p08",
"tbrn2c08",
"tbwn0g16",
"tbwn3p08",
"tbyn3p08",
"tm3n3p02",
"tp0n0g08",
"tp0n2c08",
"tp0n3p08",
"tp1n3p08",
"z00n2c08",
"z03n2c08",
"z06n2c08",
"z09n2c08",
};
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite,
testing::ValuesIn(pngsuite_files));
typedef testing::TestWithParam<string> Imgcodecs_Png_PngSuite_Corrupted;
TEST_P(Imgcodecs_Png_PngSuite_Corrupted, decode)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "pngsuite/" + GetParam() + ".png";
Mat src = imread(filename, IMREAD_UNCHANGED);
// Corrupted files should not be read
EXPECT_TRUE(src.empty());
}
const string pngsuite_files_corrupted[] = {
"xc1n0g08",
"xc9n2c08",
"xcrn0g04",
"xcsn0g01",
"xd0n2c08",
"xd3n2c08",
"xd9n2c08",
"xdtn0g01",
"xhdn0g08",
"xlfn0g04",
"xs1n0g01",
"xs2n0g01",
"xs4n0g01",
"xs7n0g01",
};
INSTANTIATE_TEST_CASE_P(/*nothing*/, Imgcodecs_Png_PngSuite_Corrupted,
testing::ValuesIn(pngsuite_files_corrupted));
#endif // HAVE_PNG
}} // namespace
+199 -2
View File
@@ -28,7 +28,7 @@ const tuple<string, Size> images[] =
#ifdef HAVE_JPEG
make_tuple<string, Size>("../cv/imgproc/stuff.jpg", Size(640, 480)),
#endif
#ifdef HAVE_PNG
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
make_tuple<string, Size>("../cv/shared/pic1.png", Size(400, 300)),
#endif
make_tuple<string, Size>("../highgui/readwrite/ordinary.bmp", Size(480, 272)),
@@ -148,7 +148,7 @@ typedef string Ext;
typedef testing::TestWithParam<Ext> Imgcodecs_Image;
const string exts[] = {
#ifdef HAVE_PNG
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
"png",
#endif
#ifdef HAVE_TIFF
@@ -303,4 +303,201 @@ TEST(Imgcodecs_Image, write_umat)
EXPECT_EQ(0, remove(dst_name.c_str()));
}
TEST(Imgcodecs_Image, multipage_collection_size)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
ImageCollection collection(filename, IMREAD_ANYCOLOR);
EXPECT_EQ((std::size_t)6, collection.size());
}
TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto collectionBegin = collection.begin();
for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin)
{
double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF);
EXPECT_EQ(0., diff);
}
}
TEST(Imgcodecs_Image, multipage_collection_two_iterator)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto firstIter = collection.begin();
auto secondIter = collection.begin();
// Decode all odd pages then decode even pages -> 1, 0, 3, 2 ...
firstIter++;
for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) {
Mat mat = *firstIter;
double diff = cv::norm(mat, imread(page_files[i]), NORM_INF);
EXPECT_EQ(0., diff);
Mat evenMat = *secondIter;
diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF);
EXPECT_EQ(0., diff);
}
}
TEST(Imgcodecs_Image, multipage_collection_operator_plusplus)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
// operator++ test
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto firstIter = collection.begin();
auto secondIter = firstIter++;
// firstIter points to second page, secondIter points to first page
double diff = cv::norm(*firstIter, *secondIter, NORM_INF);
EXPECT_NE(diff, 0.);
}
TEST(Imgcodecs_Image, multipage_collection_backward_decoding)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
EXPECT_EQ((size_t)6, collection.size());
// backward decoding -> 5,4,3,2,1,0
for(int i = (int)collection.size() - 1; i >= 0; --i)
{
cv::Mat ithPage = imread(page_files[i]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(collection[i], ithPage, NORM_INF);
EXPECT_EQ(diff, 0.);
}
for(int i = 0; i < (int)collection.size(); ++i)
{
collection.releaseCache(i);
}
double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF);
EXPECT_EQ(diff, 0.);
}
TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
size_t index = 0;
for(auto &i: collection)
{
cv::Mat ithPage = imread(page_files[index]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(i, ithPage, NORM_INF);
EXPECT_EQ(0., diff);
++index;
}
EXPECT_EQ(index, collection.size());
index = 0;
for(auto &&i: collection)
{
cv::Mat ithPage = imread(page_files[index]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(i, ithPage, NORM_INF);
EXPECT_EQ(0., diff);
++index;
}
EXPECT_EQ(index, collection.size());
}
TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
ImageCollection imcol(filename, IMREAD_ANYCOLOR);
auto it0 = imcol.begin(), it1 = it0, it2 = it0;
vector<Mat> img(6);
for (int i = 0; i < 6; i++) {
img[i] = *it0;
it0->release();
++it0;
}
for (int i = 0; i < 3; i++) {
++it2;
}
for (int i = 0; i < 3; i++) {
auto img2 = *it2;
auto img1 = *it1;
++it2;
++it1;
EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0);
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
}
}
TEST(Imgcodecs_Params, imwrite_regression_22752)
{
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
vector<int> params;
params.push_back(IMWRITE_JPEG_QUALITY);
// params.push_back(100)); // Forget it.
EXPECT_ANY_THROW(cv::imwrite("test.jpg", img, params)); // parameters size or missing JPEG codec
}
TEST(Imgcodecs_Params, imencode_regression_22752)
{
const Mat img(16, 16, CV_8UC3, cv::Scalar::all(0));
vector<int> params;
params.push_back(IMWRITE_JPEG_QUALITY);
// params.push_back(100)); // Forget it.
vector<uchar> buf;
EXPECT_ANY_THROW(cv::imencode("test.jpg", img, buf, params)); // parameters size or missing JPEG codec
}
}} // namespace
+475
View File
@@ -2,6 +2,8 @@
// 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 "opencv2/core/utils/logger.hpp"
#include "opencv2/core/utils/configuration.private.hpp"
namespace opencv_test { namespace {
@@ -46,6 +48,432 @@ TEST(Imgcodecs_Tiff, decode_tile16384x16384)
EXPECT_EQ(0, remove(file4.c_str()));
}
//==================================================================================================
// See https://github.com/opencv/opencv/issues/22388
/**
* Dummy enum to show combination of IMREAD_*.
*/
enum ImreadMixModes
{
IMREAD_MIX_UNCHANGED = IMREAD_UNCHANGED ,
IMREAD_MIX_GRAYSCALE = IMREAD_GRAYSCALE ,
IMREAD_MIX_COLOR = IMREAD_COLOR ,
IMREAD_MIX_GRAYSCALE_ANYDEPTH = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH ,
IMREAD_MIX_GRAYSCALE_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYCOLOR,
IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR = IMREAD_GRAYSCALE | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR,
IMREAD_MIX_COLOR_ANYDEPTH = IMREAD_COLOR | IMREAD_ANYDEPTH ,
IMREAD_MIX_COLOR_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYCOLOR,
IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR = IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR
};
typedef tuple< uint64_t, tuple<string, int>, ImreadMixModes > Bufsize_and_Type;
typedef testing::TestWithParam<Bufsize_and_Type> Imgcodecs_Tiff_decode_Huge;
static inline
void PrintTo(const ImreadMixModes& val, std::ostream* os)
{
PrintTo( static_cast<ImreadModes>(val), os );
}
TEST_P(Imgcodecs_Tiff_decode_Huge, regression)
{
// Get test parameters
const uint64_t buffer_size = get<0>(GetParam());
const string mat_type_string = get<0>(get<1>(GetParam()));
const int mat_type = get<1>(get<1>(GetParam()));
const int imread_mode = get<2>(GetParam());
// Detect data file
const string req_filename = cv::format("readwrite/huge-tiff/%s_%zu.tif", mat_type_string.c_str(), (size_t)buffer_size);
const string filename = findDataFile( req_filename );
// Preparation process for test
{
// Convert from mat_type and buffer_size to tiff file information.
const uint64_t width = 32768;
int ncn = CV_MAT_CN(mat_type);
int depth = ( CV_MAT_DEPTH(mat_type) == CV_16U) ? 2 : 1; // 16bit or 8 bit
const uint64_t height = (uint64_t) buffer_size / width / ncn / depth;
const uint64_t base_scanline_size = (uint64_t) width * ncn * depth;
const uint64_t base_strip_size = (uint64_t) base_scanline_size * height;
// To avoid exception about pixel size, check it.
static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30);
uint64_t pixels = (uint64_t) width * height;
if ( pixels > CV_IO_MAX_IMAGE_PIXELS )
{
throw SkipTestException( cv::format("Test is skipped( pixels(%zu) > CV_IO_MAX_IMAGE_PIXELS(%zu) )",
(size_t)pixels, CV_IO_MAX_IMAGE_PIXELS) );
}
// If buffer_size >= 1GB * 95%, TIFFReadScanline() is used.
const uint64_t BUFFER_SIZE_LIMIT_FOR_READS_CANLINE = (uint64_t) 1024*1024*1024*95/100;
const bool doReadScanline = ( base_strip_size >= BUFFER_SIZE_LIMIT_FOR_READS_CANLINE );
// Update ncn and depth for destination Mat.
switch ( imread_mode )
{
case IMREAD_UNCHANGED:
break;
case IMREAD_GRAYSCALE:
ncn = 1;
depth = 1;
break;
case IMREAD_GRAYSCALE | IMREAD_ANYDEPTH:
ncn = 1;
break;
case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR:
ncn = (ncn == 1)?1:3;
depth = 1;
break;
case IMREAD_GRAYSCALE | IMREAD_ANYCOLOR | IMREAD_ANYDEPTH:
ncn = (ncn == 1)?1:3;
break;
case IMREAD_COLOR:
ncn = 3;
depth = 1;
break;
case IMREAD_COLOR | IMREAD_ANYDEPTH:
ncn = 3;
break;
case IMREAD_COLOR | IMREAD_ANYCOLOR:
ncn = 3;
depth = 1;
break;
case IMREAD_COLOR | IMREAD_ANYDEPTH | IMREAD_ANYCOLOR:
ncn = 3;
break;
default:
break;
}
// Memory usage for Destination Mat
const uint64_t memory_usage_cvmat = (uint64_t) width * ncn * depth * height;
// Memory usage for Work memory in libtiff.
uint64_t memory_usage_tiff = 0;
if ( ( depth == 1 ) && ( !doReadScanline ) )
{
// TIFFReadRGBA*() request to allocate RGBA(32bit) buffer.
memory_usage_tiff = (uint64_t)
width *
4 * // ncn = RGBA
1 * // dst_bpp = 8 bpp
height;
}
else
{
// TIFFReadEncodedStrip() or TIFFReadScanline() request to allocate strip memory.
memory_usage_tiff = base_strip_size;
}
// Memory usage for Work memory in imgcodec/grfmt_tiff.cpp
const uint64_t memory_usage_work =
( doReadScanline ) ? base_scanline_size // for TIFFReadScanline()
: base_strip_size; // for TIFFReadRGBA*() or TIFFReadEncodedStrip()
// Total memory usage.
const uint64_t memory_usage_total =
memory_usage_cvmat + // Destination Mat
memory_usage_tiff + // Work memory in libtiff
memory_usage_work; // Work memory in imgcodecs
// Output memory usage log.
CV_LOG_DEBUG(NULL, cv::format("OpenCV TIFF-test: memory usage info : mat(%zu), libtiff(%zu), work(%zu) -> total(%zu)",
(size_t)memory_usage_cvmat, (size_t)memory_usage_tiff, (size_t)memory_usage_work, (size_t)memory_usage_total) );
// Add test tags.
if ( memory_usage_total >= (uint64_t) 6144 * 1024 * 1024 )
{
applyTestTag( CV_TEST_TAG_MEMORY_14GB, CV_TEST_TAG_VERYLONG );
}
else if ( memory_usage_total >= (uint64_t) 2048 * 1024 * 1024 )
{
applyTestTag( CV_TEST_TAG_MEMORY_6GB, CV_TEST_TAG_VERYLONG );
}
else if ( memory_usage_total >= (uint64_t) 1024 * 1024 * 1024 )
{
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG );
}
else if ( memory_usage_total >= (uint64_t) 512 * 1024 * 1024 )
{
applyTestTag( CV_TEST_TAG_MEMORY_1GB );
}
else if ( memory_usage_total >= (uint64_t) 200 * 1024 * 1024 )
{
applyTestTag( CV_TEST_TAG_MEMORY_512MB );
}
else
{
// do nothing.
}
}
// TEST Main
cv::Mat img;
ASSERT_NO_THROW( img = cv::imread(filename, imread_mode) );
ASSERT_FALSE(img.empty());
/**
* Test marker pixels at each corners.
*
* 0xAn,0x00 ... 0x00, 0xBn
* 0x00,0x00 ... 0x00, 0x00
* : : : :
* 0x00,0x00 ... 0x00, 0x00
* 0xCn,0x00 .., 0x00, 0xDn
*
*/
#define MAKE_FLAG(from_type, to_type) (((uint64_t)from_type << 32 ) | to_type )
switch ( MAKE_FLAG(mat_type, img.type() ) )
{
// GRAY TO GRAY
case MAKE_FLAG(CV_8UC1, CV_8UC1):
case MAKE_FLAG(CV_16UC1, CV_8UC1):
EXPECT_EQ( 0xA0, img.at<uchar>(0, 0) );
EXPECT_EQ( 0xB0, img.at<uchar>(0, img.cols-1) );
EXPECT_EQ( 0xC0, img.at<uchar>(img.rows-1, 0) );
EXPECT_EQ( 0xD0, img.at<uchar>(img.rows-1, img.cols-1) );
break;
// RGB/RGBA TO BGR
case MAKE_FLAG(CV_8UC3, CV_8UC3):
case MAKE_FLAG(CV_8UC4, CV_8UC3):
case MAKE_FLAG(CV_16UC3, CV_8UC3):
case MAKE_FLAG(CV_16UC4, CV_8UC3):
EXPECT_EQ( 0xA2, img.at<Vec3b>(0, 0) [0] );
EXPECT_EQ( 0xA1, img.at<Vec3b>(0, 0) [1] );
EXPECT_EQ( 0xA0, img.at<Vec3b>(0, 0) [2] );
EXPECT_EQ( 0xB2, img.at<Vec3b>(0, img.cols-1)[0] );
EXPECT_EQ( 0xB1, img.at<Vec3b>(0, img.cols-1)[1] );
EXPECT_EQ( 0xB0, img.at<Vec3b>(0, img.cols-1)[2] );
EXPECT_EQ( 0xC2, img.at<Vec3b>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xC1, img.at<Vec3b>(img.rows-1, 0) [1] );
EXPECT_EQ( 0xC0, img.at<Vec3b>(img.rows-1, 0) [2] );
EXPECT_EQ( 0xD2, img.at<Vec3b>(img.rows-1, img.cols-1)[0] );
EXPECT_EQ( 0xD1, img.at<Vec3b>(img.rows-1, img.cols-1)[1] );
EXPECT_EQ( 0xD0, img.at<Vec3b>(img.rows-1, img.cols-1)[2] );
break;
// RGBA TO BGRA
case MAKE_FLAG(CV_8UC4, CV_8UC4):
case MAKE_FLAG(CV_16UC4, CV_8UC4):
EXPECT_EQ( 0xA2, img.at<Vec4b>(0, 0) [0] );
EXPECT_EQ( 0xA1, img.at<Vec4b>(0, 0) [1] );
EXPECT_EQ( 0xA0, img.at<Vec4b>(0, 0) [2] );
EXPECT_EQ( 0xA3, img.at<Vec4b>(0, 0) [3] );
EXPECT_EQ( 0xB2, img.at<Vec4b>(0, img.cols-1)[0] );
EXPECT_EQ( 0xB1, img.at<Vec4b>(0, img.cols-1)[1] );
EXPECT_EQ( 0xB0, img.at<Vec4b>(0, img.cols-1)[2] );
EXPECT_EQ( 0xB3, img.at<Vec4b>(0, img.cols-1)[3] );
EXPECT_EQ( 0xC2, img.at<Vec4b>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xC1, img.at<Vec4b>(img.rows-1, 0) [1] );
EXPECT_EQ( 0xC0, img.at<Vec4b>(img.rows-1, 0) [2] );
EXPECT_EQ( 0xC3, img.at<Vec4b>(img.rows-1, 0) [3] );
EXPECT_EQ( 0xD2, img.at<Vec4b>(img.rows-1, img.cols-1)[0] );
EXPECT_EQ( 0xD1, img.at<Vec4b>(img.rows-1, img.cols-1)[1] );
EXPECT_EQ( 0xD0, img.at<Vec4b>(img.rows-1, img.cols-1)[2] );
EXPECT_EQ( 0xD3, img.at<Vec4b>(img.rows-1, img.cols-1)[3] );
break;
// RGB/RGBA to GRAY
case MAKE_FLAG(CV_8UC3, CV_8UC1):
case MAKE_FLAG(CV_8UC4, CV_8UC1):
case MAKE_FLAG(CV_16UC3, CV_8UC1):
case MAKE_FLAG(CV_16UC4, CV_8UC1):
EXPECT_LE( 0xA0, img.at<uchar>(0, 0) );
EXPECT_GE( 0xA2, img.at<uchar>(0, 0) );
EXPECT_LE( 0xB0, img.at<uchar>(0, img.cols-1) );
EXPECT_GE( 0xB2, img.at<uchar>(0, img.cols-1) );
EXPECT_LE( 0xC0, img.at<uchar>(img.rows-1, 0) );
EXPECT_GE( 0xC2, img.at<uchar>(img.rows-1, 0) );
EXPECT_LE( 0xD0, img.at<uchar>(img.rows-1, img.cols-1) );
EXPECT_GE( 0xD2, img.at<uchar>(img.rows-1, img.cols-1) );
break;
// GRAY to BGR
case MAKE_FLAG(CV_8UC1, CV_8UC3):
case MAKE_FLAG(CV_16UC1, CV_8UC3):
EXPECT_EQ( 0xA0, img.at<Vec3b>(0, 0) [0] );
EXPECT_EQ( 0xB0, img.at<Vec3b>(0, img.cols-1)[0] );
EXPECT_EQ( 0xC0, img.at<Vec3b>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xD0, img.at<Vec3b>(img.rows-1, img.cols-1)[0] );
// R==G==B
EXPECT_EQ( img.at<Vec3b>(0, 0) [0], img.at<Vec3b>(0, 0) [1] );
EXPECT_EQ( img.at<Vec3b>(0, 0) [0], img.at<Vec3b>(0, 0) [2] );
EXPECT_EQ( img.at<Vec3b>(0, img.cols-1) [0], img.at<Vec3b>(0, img.cols-1)[1] );
EXPECT_EQ( img.at<Vec3b>(0, img.cols-1) [0], img.at<Vec3b>(0, img.cols-1)[2] );
EXPECT_EQ( img.at<Vec3b>(img.rows-1, 0) [0], img.at<Vec3b>(img.rows-1, 0) [1] );
EXPECT_EQ( img.at<Vec3b>(img.rows-1, 0) [0], img.at<Vec3b>(img.rows-1, 0) [2] );
EXPECT_EQ( img.at<Vec3b>(img.rows-1, img.cols-1) [0], img.at<Vec3b>(img.rows-1, img.cols-1)[1] );
EXPECT_EQ( img.at<Vec3b>(img.rows-1, img.cols-1) [0], img.at<Vec3b>(img.rows-1, img.cols-1)[2] );
break;
// GRAY TO GRAY
case MAKE_FLAG(CV_16UC1, CV_16UC1):
EXPECT_EQ( 0xA090, img.at<ushort>(0, 0) );
EXPECT_EQ( 0xB080, img.at<ushort>(0, img.cols-1) );
EXPECT_EQ( 0xC070, img.at<ushort>(img.rows-1, 0) );
EXPECT_EQ( 0xD060, img.at<ushort>(img.rows-1, img.cols-1) );
break;
// RGB/RGBA TO BGR
case MAKE_FLAG(CV_16UC3, CV_16UC3):
case MAKE_FLAG(CV_16UC4, CV_16UC3):
EXPECT_EQ( 0xA292, img.at<Vec3w>(0, 0) [0] );
EXPECT_EQ( 0xA191, img.at<Vec3w>(0, 0) [1] );
EXPECT_EQ( 0xA090, img.at<Vec3w>(0, 0) [2] );
EXPECT_EQ( 0xB282, img.at<Vec3w>(0, img.cols-1)[0] );
EXPECT_EQ( 0xB181, img.at<Vec3w>(0, img.cols-1)[1] );
EXPECT_EQ( 0xB080, img.at<Vec3w>(0, img.cols-1)[2] );
EXPECT_EQ( 0xC272, img.at<Vec3w>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xC171, img.at<Vec3w>(img.rows-1, 0) [1] );
EXPECT_EQ( 0xC070, img.at<Vec3w>(img.rows-1, 0) [2] );
EXPECT_EQ( 0xD262, img.at<Vec3w>(img.rows-1, img.cols-1)[0] );
EXPECT_EQ( 0xD161, img.at<Vec3w>(img.rows-1, img.cols-1)[1] );
EXPECT_EQ( 0xD060, img.at<Vec3w>(img.rows-1, img.cols-1)[2] );
break;
// RGBA TO RGBA
case MAKE_FLAG(CV_16UC4, CV_16UC4):
EXPECT_EQ( 0xA292, img.at<Vec4w>(0, 0) [0] );
EXPECT_EQ( 0xA191, img.at<Vec4w>(0, 0) [1] );
EXPECT_EQ( 0xA090, img.at<Vec4w>(0, 0) [2] );
EXPECT_EQ( 0xA393, img.at<Vec4w>(0, 0) [3] );
EXPECT_EQ( 0xB282, img.at<Vec4w>(0, img.cols-1)[0] );
EXPECT_EQ( 0xB181, img.at<Vec4w>(0, img.cols-1)[1] );
EXPECT_EQ( 0xB080, img.at<Vec4w>(0, img.cols-1)[2] );
EXPECT_EQ( 0xB383, img.at<Vec4w>(0, img.cols-1)[3] );
EXPECT_EQ( 0xC272, img.at<Vec4w>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xC171, img.at<Vec4w>(img.rows-1, 0) [1] );
EXPECT_EQ( 0xC070, img.at<Vec4w>(img.rows-1, 0) [2] );
EXPECT_EQ( 0xC373, img.at<Vec4w>(img.rows-1, 0) [3] );
EXPECT_EQ( 0xD262, img.at<Vec4w>(img.rows-1,img.cols-1) [0] );
EXPECT_EQ( 0xD161, img.at<Vec4w>(img.rows-1,img.cols-1) [1] );
EXPECT_EQ( 0xD060, img.at<Vec4w>(img.rows-1,img.cols-1) [2] );
EXPECT_EQ( 0xD363, img.at<Vec4w>(img.rows-1,img.cols-1) [3] );
break;
// RGB/RGBA to GRAY
case MAKE_FLAG(CV_16UC3, CV_16UC1):
case MAKE_FLAG(CV_16UC4, CV_16UC1):
EXPECT_LE( 0xA090, img.at<ushort>(0, 0) );
EXPECT_GE( 0xA292, img.at<ushort>(0, 0) );
EXPECT_LE( 0xB080, img.at<ushort>(0, img.cols-1) );
EXPECT_GE( 0xB282, img.at<ushort>(0, img.cols-1) );
EXPECT_LE( 0xC070, img.at<ushort>(img.rows-1, 0) );
EXPECT_GE( 0xC272, img.at<ushort>(img.rows-1, 0) );
EXPECT_LE( 0xD060, img.at<ushort>(img.rows-1, img.cols-1) );
EXPECT_GE( 0xD262, img.at<ushort>(img.rows-1, img.cols-1) );
break;
// GRAY to RGB
case MAKE_FLAG(CV_16UC1, CV_16UC3):
EXPECT_EQ( 0xA090, img.at<Vec3w>(0, 0) [0] );
EXPECT_EQ( 0xB080, img.at<Vec3w>(0, img.cols-1)[0] );
EXPECT_EQ( 0xC070, img.at<Vec3w>(img.rows-1, 0) [0] );
EXPECT_EQ( 0xD060, img.at<Vec3w>(img.rows-1, img.cols-1)[0] );
// R==G==B
EXPECT_EQ( img.at<Vec3w>(0, 0) [0], img.at<Vec3w>(0, 0) [1] );
EXPECT_EQ( img.at<Vec3w>(0, 0) [0], img.at<Vec3w>(0, 0) [2] );
EXPECT_EQ( img.at<Vec3w>(0, img.cols-1) [0], img.at<Vec3w>(0, img.cols-1)[1] );
EXPECT_EQ( img.at<Vec3w>(0, img.cols-1) [0], img.at<Vec3w>(0, img.cols-1)[2] );
EXPECT_EQ( img.at<Vec3w>(img.rows-1, 0) [0], img.at<Vec3w>(img.rows-1, 0) [1] );
EXPECT_EQ( img.at<Vec3w>(img.rows-1, 0) [0], img.at<Vec3w>(img.rows-1, 0) [2] );
EXPECT_EQ( img.at<Vec3w>(img.rows-1, img.cols-1) [0], img.at<Vec3w>(img.rows-1, img.cols-1)[1] );
EXPECT_EQ( img.at<Vec3w>(img.rows-1, img.cols-1) [0], img.at<Vec3w>(img.rows-1, img.cols-1)[2] );
break;
// No supported.
// (1) 8bit to 16bit
case MAKE_FLAG(CV_8UC1, CV_16UC1):
case MAKE_FLAG(CV_8UC1, CV_16UC3):
case MAKE_FLAG(CV_8UC1, CV_16UC4):
case MAKE_FLAG(CV_8UC3, CV_16UC1):
case MAKE_FLAG(CV_8UC3, CV_16UC3):
case MAKE_FLAG(CV_8UC3, CV_16UC4):
case MAKE_FLAG(CV_8UC4, CV_16UC1):
case MAKE_FLAG(CV_8UC4, CV_16UC3):
case MAKE_FLAG(CV_8UC4, CV_16UC4):
// (2) GRAY/RGB TO RGBA
case MAKE_FLAG(CV_8UC1, CV_8UC4):
case MAKE_FLAG(CV_8UC3, CV_8UC4):
case MAKE_FLAG(CV_16UC1, CV_8UC4):
case MAKE_FLAG(CV_16UC3, CV_8UC4):
case MAKE_FLAG(CV_16UC1, CV_16UC4):
case MAKE_FLAG(CV_16UC3, CV_16UC4):
default:
FAIL() << cv::format("Unknown test pattern: from = %d ( %d, %d) to = %d ( %d, %d )",
mat_type, (int)CV_MAT_CN(mat_type ), ( CV_MAT_DEPTH(mat_type )==CV_16U)?16:8,
img.type(), (int)CV_MAT_CN(img.type() ), ( CV_MAT_DEPTH(img.type() )==CV_16U)?16:8);
break;
}
#undef MAKE_FLAG
}
// Basic Test
const Bufsize_and_Type Imgcodecs_Tiff_decode_Huge_list_basic[] =
{
make_tuple<uint64_t, tuple<string,int>,ImreadMixModes>( 1073479680ull, make_tuple<string,int>("CV_8UC1", CV_8UC1), IMREAD_MIX_COLOR ),
make_tuple<uint64_t, tuple<string,int>,ImreadMixModes>( 2147483648ull, make_tuple<string,int>("CV_16UC4", CV_16UC4), IMREAD_MIX_COLOR ),
};
INSTANTIATE_TEST_CASE_P(Imgcodecs_Tiff, Imgcodecs_Tiff_decode_Huge,
testing::ValuesIn( Imgcodecs_Tiff_decode_Huge_list_basic )
);
// Full Test
/**
* Test lists for combination of IMREAD_*.
*/
const ImreadMixModes all_modes_Huge_Full[] =
{
IMREAD_MIX_UNCHANGED,
IMREAD_MIX_GRAYSCALE,
IMREAD_MIX_GRAYSCALE_ANYDEPTH,
IMREAD_MIX_GRAYSCALE_ANYCOLOR,
IMREAD_MIX_GRAYSCALE_ANYDEPTH_ANYCOLOR,
IMREAD_MIX_COLOR,
IMREAD_MIX_COLOR_ANYDEPTH,
IMREAD_MIX_COLOR_ANYCOLOR,
IMREAD_MIX_COLOR_ANYDEPTH_ANYCOLOR,
};
const uint64_t huge_buffer_sizes_decode_Full[] =
{
1048576ull, // 1 * 1024 * 1024
1073479680ull, // 1024 * 1024 * 1024 - 32768 * 4 * 2
1073741824ull, // 1024 * 1024 * 1024
2147483648ull, // 2048 * 1024 * 1024
};
const tuple<string, int> mat_types_Full[] =
{
make_tuple<string, int>("CV_8UC1", CV_8UC1), // 8bit GRAY
make_tuple<string, int>("CV_8UC3", CV_8UC3), // 24bit RGB
make_tuple<string, int>("CV_8UC4", CV_8UC4), // 32bit RGBA
make_tuple<string, int>("CV_16UC1", CV_16UC1), // 16bit GRAY
make_tuple<string, int>("CV_16UC3", CV_16UC3), // 48bit RGB
make_tuple<string, int>("CV_16UC4", CV_16UC4), // 64bit RGBA
};
INSTANTIATE_TEST_CASE_P(DISABLED_Imgcodecs_Tiff_Full, Imgcodecs_Tiff_decode_Huge,
testing::Combine(
testing::ValuesIn(huge_buffer_sizes_decode_Full),
testing::ValuesIn(mat_types_Full),
testing::ValuesIn(all_modes_Huge_Full)
)
);
//==================================================================================================
TEST(Imgcodecs_Tiff, write_read_16bit_big_little_endian)
{
// see issue #2601 "16-bit Grayscale TIFF Load Failures Due to Buffer Underflow and Endianness"
@@ -359,6 +787,16 @@ TEST(Imgcodecs_Tiff, read_palette_color_image)
ASSERT_EQ(CV_8UC3, img.type());
}
TEST(Imgcodecs_Tiff, read_4_bit_palette_color_image)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filenameInput = root + "readwrite/4-bit_palette_color.tif";
const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED);
ASSERT_FALSE(img.empty());
ASSERT_EQ(CV_8UC3, img.type());
}
TEST(Imgcodecs_Tiff, readWrite_predictor)
{
/* see issue #21871
@@ -432,6 +870,43 @@ TEST_P(Imgcodecs_Tiff_Modes, decode_multipage)
}
}
TEST_P(Imgcodecs_Tiff_Modes, decode_multipage_use_memory_buffer)
{
const int mode = GetParam();
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
"readwrite/multipage_p1.tif",
"readwrite/multipage_p2.tif",
"readwrite/multipage_p3.tif",
"readwrite/multipage_p4.tif",
"readwrite/multipage_p5.tif",
"readwrite/multipage_p6.tif"
};
const size_t page_count = sizeof(page_files) / sizeof(page_files[0]);
vector<Mat> pages;
FILE* fp = fopen(filename.c_str(), "rb");
ASSERT_TRUE(fp != NULL);
fseek(fp, 0, SEEK_END);
long pos = ftell(fp);
std::vector<uchar> buf;
buf.resize((size_t)pos);
fseek(fp, 0, SEEK_SET);
buf.resize(fread(&buf[0], 1, buf.size(), fp));
fclose(fp);
bool res = imdecodemulti(buf, mode, pages);
ASSERT_TRUE(res == true);
ASSERT_EQ(page_count, pages.size());
for (size_t i = 0; i < page_count; i++)
{
const Mat page = imread(root + page_files[i], mode);
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), page, pages[i]);
}
}
const int all_modes[] =
{
IMREAD_UNCHANGED,