Merge pull request #26211 from Kumataro:fix26207

imgcodecs: implement imencodemulti() #26211

Close #26207
### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [x] The feature is well documented and sample code can be built with the project CMake
pull/24927/merge
Kumataro 4 weeks ago committed by GitHub
parent 3919f33e21
commit 35dbf32227
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 16
      modules/imgcodecs/include/opencv2/imgcodecs.hpp
  2. 26
      modules/imgcodecs/src/grfmt_tiff.cpp
  3. 80
      modules/imgcodecs/src/loadsave.cpp
  4. 10
      modules/imgcodecs/test/test_avif.cpp
  5. 23
      modules/imgcodecs/test/test_exr.impl.hpp
  6. 70
      modules/imgcodecs/test/test_read_write.cpp

@ -405,7 +405,7 @@ The function imencode compresses the image and stores it in the memory buffer th
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 written.
@param img Image to be compressed.
@param buf Output buffer resized to fit the compressed image.
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
*/
@ -413,6 +413,20 @@ 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 array of images into a memory buffer.
The function is analog to cv::imencode for in-memory multi-page image compression.
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 imgs Vector of images to be written.
@param buf Output buffer resized to fit the compressed data.
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
*/
CV_EXPORTS_W bool imencodemulti( const String& ext, InputArrayOfArrays imgs,
CV_OUT std::vector<uchar>& buf,
const std::vector<int>& params = std::vector<int>());
/** @brief Checks if the specified image file can be decoded by OpenCV.
The function haveImageReader checks if OpenCV is capable of reading the specified file.

@ -171,7 +171,7 @@ public:
{
n = size - pos;
}
memcpy(buffer, buf.ptr() + pos, n);
std::memcpy(buffer, buf.ptr() + pos, n);
helper->m_buf_pos += n;
return n;
}
@ -848,7 +848,7 @@ bool TiffDecoder::readData( Mat& img )
switch ( convert_flag )
{
case MAKE_FLAG( 1, 1 ): // GRAY to GRAY
memcpy( (void*) img_line_buffer,
std::memcpy( (void*) img_line_buffer,
(void*) bstart,
tile_width * sizeof(uchar) );
break;
@ -867,7 +867,7 @@ bool TiffDecoder::readData( Mat& img )
case MAKE_FLAG( 3, 3 ): // RGB to BGR
if (m_use_rgb)
memcpy( (void*) img_line_buffer,
std::memcpy( (void*) img_line_buffer,
(void*) bstart,
tile_width * sizeof(uchar) );
else
@ -979,7 +979,7 @@ bool TiffDecoder::readData( Mat& img )
{
CV_CheckEQ(wanted_channels, 3, "");
if (m_use_rgb)
memcpy(buffer16, img.ptr<ushort>(img_y + i, x), tile_width * sizeof(ushort));
std::memcpy(buffer16, img.ptr<ushort>(img_y + i, x), tile_width * sizeof(ushort));
else
icvCvt_RGB2BGR_16u_C3R(buffer16, 0,
img.ptr<ushort>(img_y + i, x), 0,
@ -1011,7 +1011,7 @@ bool TiffDecoder::readData( Mat& img )
CV_CheckEQ(wanted_channels, 1, "");
if( ncn == 1 )
{
memcpy(img.ptr<ushort>(img_y + i, x),
std::memcpy(img.ptr<ushort>(img_y + i, x),
buffer16,
tile_width*sizeof(ushort));
}
@ -1118,10 +1118,16 @@ public:
/*map=*/0, /*unmap=*/0 );
}
static tmsize_t read( thandle_t /*handle*/, void* /*buffer*/, tmsize_t /*n*/ )
static tmsize_t read( thandle_t handle, void* buffer, tmsize_t n )
{
// Not used for encoding.
return 0;
// Used for imencodemulti() to stores multi-images.
TiffEncoderBufHelper *helper = reinterpret_cast<TiffEncoderBufHelper*>(handle);
size_t begin = (size_t)helper->m_buf_pos;
size_t end = begin + n;
CV_CheckGT( helper->m_buf->size(), end , "do not be over-run buffer");
std::memcpy(buffer, &(*helper->m_buf)[begin], n);
helper->m_buf_pos = end;
return n;
}
static tmsize_t write( thandle_t handle, void* buffer, tmsize_t n )
@ -1133,7 +1139,7 @@ public:
{
helper->m_buf->resize(end);
}
memcpy(&(*helper->m_buf)[begin], buffer, n);
std::memcpy(&(*helper->m_buf)[begin], buffer, n);
helper->m_buf_pos = end;
return n;
}
@ -1350,7 +1356,7 @@ bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vect
{
case 1:
{
memcpy(buffer, img.ptr(y), scanlineSize);
std::memcpy(buffer, img.ptr(y), scanlineSize);
break;
}

@ -723,6 +723,7 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
Mat temp;
if( !encoder->isFormatSupported(image.depth()) )
{
CV_LOG_ONCE_WARNING(NULL, "Unsupported depth image for selected encoder is fallbacked to CV_8U.");
CV_Assert( encoder->isFormatSupported(CV_8U) );
image.convertTo( temp, CV_8U );
image = temp;
@ -787,10 +788,12 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
catch (const cv::Exception& e)
{
CV_LOG_ERROR(NULL, "imwrite_('" << filename << "'): can't write data: " << e.what());
code = false;
}
catch (...)
{
CV_LOG_ERROR(NULL, "imwrite_('" << filename << "'): can't write data: unknown exception");
code = false;
}
return code;
@ -978,7 +981,7 @@ imdecodemulti_(const Mat& buf, int flags, std::vector<Mat>& mats, int start, int
ImageDecoder decoder = findDecoder(buf_row);
if (!decoder)
return 0;
return false;
// Try to decode image by RGB instead of BGR.
if (flags & IMREAD_COLOR_RGB && flags != IMREAD_UNCHANGED)
@ -995,7 +998,7 @@ imdecodemulti_(const Mat& buf, int flags, std::vector<Mat>& mats, int start, int
filename = tempfile();
FILE* f = fopen(filename.c_str(), "wb");
if (!f)
return 0;
return false;
size_t bufSize = buf_row.total() * buf.elemSize();
if (fwrite(buf_row.ptr(), 1, bufSize, f) != bufSize)
{
@ -1121,29 +1124,46 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector<Mat>& mats, co
}
}
bool imencode( const String& ext, InputArray _image,
bool imencode( const String& ext, InputArray _img,
std::vector<uchar>& buf, const std::vector<int>& params_ )
{
CV_TRACE_FUNCTION();
Mat image = _image.getMat();
CV_Assert(!image.empty());
int channels = image.channels();
CV_Assert( channels == 1 || channels == 3 || channels == 4 );
ImageEncoder encoder = findEncoder( ext );
if( !encoder )
CV_Error( Error::StsError, "could not find encoder for the specified extension" );
std::vector<Mat> img_vec;
CV_Assert(!_img.empty());
if (_img.isMatVector() || _img.isUMatVector())
_img.getMatVector(img_vec);
else
img_vec.push_back(_img.getMat());
CV_Assert(!img_vec.empty());
const bool isMultiImg = img_vec.size() > 1;
std::vector<Mat> write_vec;
for (size_t page = 0; page < img_vec.size(); page++)
{
Mat image = img_vec[page];
CV_Assert(!image.empty());
const int channels = image.channels();
CV_Assert( channels == 1 || channels == 3 || channels == 4 );
Mat temp;
if( !encoder->isFormatSupported(image.depth()) )
{
CV_LOG_ONCE_WARNING(NULL, "Unsupported depth image for selected encoder is fallbacked to CV_8U.");
CV_Assert( encoder->isFormatSupported(CV_8U) );
Mat temp;
image.convertTo(temp, CV_8U);
image.convertTo( temp, CV_8U );
image = temp;
}
write_vec.push_back(image);
}
#if CV_VERSION_MAJOR < 5 && defined(HAVE_IMGCODEC_HDR)
bool fixed = false;
std::vector<int> params_pair(2);
@ -1166,23 +1186,37 @@ bool imencode( const String& ext, InputArray _image,
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) )
bool code = false;
String filename;
if( !encoder->setDestination(buf) )
{
code = encoder->write(image, params);
encoder->throwOnEror();
filename = tempfile();
code = encoder->setDestination(filename);
CV_Assert( code );
}
try {
if (!isMultiImg)
code = encoder->write(write_vec[0], params);
else
{
String filename = tempfile();
code = encoder->setDestination(filename);
CV_Assert( code );
code = encoder->writemulti(write_vec, params);
code = encoder->write(image, params);
encoder->throwOnEror();
CV_Assert( code );
}
catch (const cv::Exception& e)
{
CV_LOG_ERROR(NULL, "imencode(): can't encode data: " << e.what());
code = false;
}
catch (...)
{
CV_LOG_ERROR(NULL, "imencode(): can't encode data: unknown exception");
code = false;
}
if( !filename.empty() && code )
{
FILE* f = fopen( filename.c_str(), "rb" );
CV_Assert(f != 0);
fseek( f, 0, SEEK_END );
@ -1196,6 +1230,12 @@ bool imencode( const String& ext, InputArray _image,
return code;
}
bool imencodemulti( const String& ext, InputArrayOfArrays imgs,
std::vector<uchar>& buf, const std::vector<int>& params)
{
return imencode(ext, imgs, buf, params);
}
bool haveImageReader( const String& filename )
{
ImageDecoder decoder = cv::findDecoder(filename);

@ -161,14 +161,14 @@ TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) {
// Encode.
std::vector<unsigned char> buf;
if (!IsBitDepthValid()) {
EXPECT_THROW(cv::imencode(".avif", img_original, buf, encoding_params_),
cv::Exception);
return;
}
bool result = true;
EXPECT_NO_THROW(
result = cv::imencode(".avif", img_original, buf, encoding_params_););
if (!IsBitDepthValid()) {
EXPECT_FALSE(result);
return;
}
EXPECT_TRUE(result);
// Read back.

@ -314,4 +314,27 @@ TEST(Imgcodecs_EXR, read_RGBA_unchanged)
EXPECT_EQ(0, remove(filenameOutput.c_str()));
}
// See https://github.com/opencv/opencv/pull/26211
// ( related with https://github.com/opencv/opencv/issues/26207 )
TEST(Imgcodecs_EXR, imencode_regression_26207_extra)
{
// CV_8U is not supported depth for EXR Encoder.
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
std::vector<uchar> buf;
bool ret = false;
EXPECT_ANY_THROW(ret = imencode(".exr", src, buf));
EXPECT_FALSE(ret);
}
TEST(Imgcodecs_EXR, imwrite_regression_26207_extra)
{
// CV_8U is not supported depth for EXR Encoder.
const cv::Mat src(100, 100, CV_8UC1, cv::Scalar::all(0));
const string filename = cv::tempfile(".exr");
bool ret = false;
EXPECT_ANY_THROW(ret = imwrite(filename, src));
EXPECT_FALSE(ret);
remove(filename.c_str());
}
}} // namespace

@ -520,8 +520,78 @@ TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
}
}
// See https://github.com/opencv/opencv/issues/26207
TEST(Imgcodecs, imencodemulti_regression_26207)
{
vector<Mat> imgs;
const cv::Mat img(100, 100, CV_8UC1, cv::Scalar::all(0));
imgs.push_back(img);
std::vector<uchar> buf;
bool ret = false;
// Encode single image
EXPECT_NO_THROW(ret = imencode(".tiff", img, buf));
EXPECT_TRUE(ret);
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
EXPECT_TRUE(ret);
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
EXPECT_TRUE(ret);
// Encode multiple images
imgs.push_back(img.clone());
EXPECT_NO_THROW(ret = imencode(".tiff", imgs, buf));
EXPECT_TRUE(ret);
EXPECT_NO_THROW(ret = imencodemulti(".tiff", imgs, buf));
EXPECT_TRUE(ret);
// Count stored images from buffer.
// imcount() doesn't support buffer, so encoded buffer outputs to file temporary.
const size_t len = buf.size();
const string filename = cv::tempfile(".tiff");
FILE *f = fopen(filename.c_str(), "wb");
EXPECT_NE(f, nullptr);
EXPECT_EQ(len, fwrite(&buf[0], 1, len, f));
fclose(f);
EXPECT_EQ(2, (int)imcount(filename));
EXPECT_EQ(0, remove(filename.c_str()));
}
#endif
// See https://github.com/opencv/opencv/pull/26211
// ( related with https://github.com/opencv/opencv/issues/26207 )
TEST(Imgcodecs, imencode_regression_26207_extra)
{
// CV_32F is not supported depth for BMP Encoder.
// Encoded buffer contains CV_8U image which is fallbacked.
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
std::vector<uchar> buf;
bool ret = false;
EXPECT_NO_THROW(ret = imencode(".bmp", src, buf));
EXPECT_TRUE(ret);
cv::Mat dst;
EXPECT_NO_THROW(dst = imdecode(buf, IMREAD_GRAYSCALE));
EXPECT_FALSE(dst.empty());
EXPECT_EQ(CV_8UC1, dst.type());
}
TEST(Imgcodecs, imwrite_regression_26207_extra)
{
// CV_32F is not supported depth for BMP Encoder.
// Encoded buffer contains CV_8U image which is fallbacked.
const cv::Mat src(100, 100, CV_32FC1, cv::Scalar::all(0));
const string filename = cv::tempfile(".bmp");
bool ret = false;
EXPECT_NO_THROW(ret = imwrite(filename, src));
EXPECT_TRUE(ret);
cv::Mat dst;
EXPECT_NO_THROW(dst = imread(filename, IMREAD_GRAYSCALE));
EXPECT_FALSE(dst.empty());
EXPECT_EQ(CV_8UC1, dst.type());
EXPECT_EQ(0, remove(filename.c_str()));
}
TEST(Imgcodecs_Params, imwrite_regression_22752)
{

Loading…
Cancel
Save