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 1 month 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. 78
      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. 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 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 buf Output buffer resized to fit the compressed image.
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags. @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, CV_OUT std::vector<uchar>& buf,
const std::vector<int>& params = std::vector<int>()); 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. /** @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. The function haveImageReader checks if OpenCV is capable of reading the specified file.

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

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

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

@ -314,4 +314,27 @@ TEST(Imgcodecs_EXR, read_RGBA_unchanged)
EXPECT_EQ(0, remove(filenameOutput.c_str())); 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 }} // namespace

@ -520,8 +520,78 @@ TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0); 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 #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) TEST(Imgcodecs_Params, imwrite_regression_22752)
{ {

Loading…
Cancel
Save