diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 9063aafe2c..6e317f585b 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -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& buf, const std::vector& params = std::vector()); +/** @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& buf, + const std::vector& params = std::vector()); + /** @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. diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index e2184663aa..ccc6579a01 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -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,9 +848,9 @@ bool TiffDecoder::readData( Mat& img ) switch ( convert_flag ) { case MAKE_FLAG( 1, 1 ): // GRAY to GRAY - memcpy( (void*) img_line_buffer, - (void*) bstart, - tile_width * sizeof(uchar) ); + std::memcpy( (void*) img_line_buffer, + (void*) bstart, + tile_width * sizeof(uchar) ); break; case MAKE_FLAG( 1, 3 ): // GRAY to BGR @@ -867,9 +867,9 @@ bool TiffDecoder::readData( Mat& img ) case MAKE_FLAG( 3, 3 ): // RGB to BGR if (m_use_rgb) - memcpy( (void*) img_line_buffer, - (void*) bstart, - tile_width * sizeof(uchar) ); + std::memcpy( (void*) img_line_buffer, + (void*) bstart, + tile_width * sizeof(uchar) ); else icvCvt_BGR2RGB_8u_C3R( bstart, 0, img_line_buffer, 0, @@ -979,7 +979,7 @@ bool TiffDecoder::readData( Mat& img ) { CV_CheckEQ(wanted_channels, 3, ""); if (m_use_rgb) - memcpy(buffer16, img.ptr(img_y + i, x), tile_width * sizeof(ushort)); + std::memcpy(buffer16, img.ptr(img_y + i, x), tile_width * sizeof(ushort)); else icvCvt_RGB2BGR_16u_C3R(buffer16, 0, img.ptr(img_y + i, x), 0, @@ -1011,9 +1011,9 @@ bool TiffDecoder::readData( Mat& img ) CV_CheckEQ(wanted_channels, 1, ""); if( ncn == 1 ) { - memcpy(img.ptr(img_y + i, x), - buffer16, - tile_width*sizeof(ushort)); + std::memcpy(img.ptr(img_y + i, x), + buffer16, + tile_width*sizeof(ushort)); } else { @@ -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(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& img_vec, const std::vect { case 1: { - memcpy(buffer, img.ptr(y), scanlineSize); + std::memcpy(buffer, img.ptr(y), scanlineSize); break; } diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 8eedb0d907..31a0166d83 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -723,6 +723,7 @@ static bool imwrite_( const String& filename, const std::vector& 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& 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& 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& 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,27 +1124,44 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector& mats, co } } -bool imencode( const String& ext, InputArray _image, +bool imencode( const String& ext, InputArray _img, std::vector& buf, const std::vector& 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" ); - if( !encoder->isFormatSupported(image.depth()) ) + std::vector 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 write_vec; + for (size_t page = 0; page < img_vec.size(); page++) { - CV_Assert( encoder->isFormatSupported(CV_8U) ); + Mat image = img_vec[page]; + CV_Assert(!image.empty()); + + const int channels = image.channels(); + CV_Assert( channels == 1 || channels == 3 || channels == 4 ); + Mat temp; - image.convertTo(temp, CV_8U); - image = 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; + } + + write_vec.push_back(image); } #if CV_VERSION_MAJOR < 5 && defined(HAVE_IMGCODEC_HDR) @@ -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) ) - { - code = encoder->write(image, params); - encoder->throwOnEror(); - CV_Assert( code ); - } - else + bool code = false; + String filename; + if( !encoder->setDestination(buf) ) { - String filename = tempfile(); + filename = tempfile(); code = encoder->setDestination(filename); CV_Assert( code ); + } + + try { + if (!isMultiImg) + code = encoder->write(write_vec[0], params); + else + 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& buf, const std::vector& params) +{ + return imencode(ext, imgs, buf, params); +} + bool haveImageReader( const String& filename ) { ImageDecoder decoder = cv::findDecoder(filename); diff --git a/modules/imgcodecs/test/test_avif.cpp b/modules/imgcodecs/test/test_avif.cpp index ba623110c0..d94e5d458c 100644 --- a/modules/imgcodecs/test/test_avif.cpp +++ b/modules/imgcodecs/test/test_avif.cpp @@ -161,14 +161,14 @@ TEST_P(Imgcodecs_Avif_Image_EncodeDecodeSuite, imencode_imdecode) { // Encode. std::vector 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. diff --git a/modules/imgcodecs/test/test_exr.impl.hpp b/modules/imgcodecs/test/test_exr.impl.hpp index c8cda11a63..32984ff731 100644 --- a/modules/imgcodecs/test/test_exr.impl.hpp +++ b/modules/imgcodecs/test/test_exr.impl.hpp @@ -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 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 diff --git a/modules/imgcodecs/test/test_read_write.cpp b/modules/imgcodecs/test/test_read_write.cpp index 255f819a9a..7dfd02c67c 100644 --- a/modules/imgcodecs/test/test_read_write.cpp +++ b/modules/imgcodecs/test/test_read_write.cpp @@ -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 imgs; + const cv::Mat img(100, 100, CV_8UC1, cv::Scalar::all(0)); + imgs.push_back(img); + std::vector 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 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) {