diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 18b04d5687..a95a4ccdb4 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -306,6 +306,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& 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 diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index daad280fec..10c4dcad2d 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -929,6 +929,157 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst ) return *dst; } +static bool +imdecodemulti_(const Mat& buf, int flags, std::vector& 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::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& mats) +{ + CV_TRACE_FUNCTION(); + + Mat buf = _buf.getMat(); + return imdecodemulti_(buf, flags, mats, 0, -1); +} + bool imencode( const String& ext, InputArray _image, std::vector& buf, const std::vector& params ) { diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 76a39c0fae..dc8624dd31 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -442,6 +442,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 pages; + + FILE* fp = fopen(filename.c_str(), "rb"); + ASSERT_TRUE(fp != NULL); + fseek(fp, 0, SEEK_END); + long pos = ftell(fp); + + std::vector 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,