diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index 6a389fd471..42227f3788 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -215,6 +215,26 @@ The function imreadmulti loads a multi-page image from the specified file into a */ CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int flags = IMREAD_ANYCOLOR); +/** @brief Loads a of images of a multi-page image from a file. + +The function imreadmulti loads a specified range from a multi-page image from the specified file into a vector of Mat objects. +@param filename Name of file to be loaded. +@param start Start index of the image to load +@param count Count number of images to load +@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR. +@param mats A vector of Mat objects holding each page, if more than one. +@sa cv::imread +*/ +CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector& mats, int start, int count, int flags = IMREAD_ANYCOLOR); + +/** @brief Returns the number of images inside the give file + +The function imcount will return the number of pages in a multi-page image, or 1 for single-page images +@param filename Name of file to be loaded. +@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR. +*/ +CV_EXPORTS_W size_t imcount(const String& filename, int flags = IMREAD_ANYCOLOR); + /** @brief Saves an image to a specified file. The function imwrite saves the image to the specified file. The image format is chosen based on the diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 350042cd7d..28d8ff285b 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -495,25 +495,19 @@ imread_( const String& filename, int flags, Mat& mat ) } -/** -* Read an image into memory and return the information -* -* @param[in] filename File to load -* @param[in] flags Flags -* @param[in] mats Reference to C++ vector object to hold the images -* -*/ static bool -imreadmulti_(const String& filename, int flags, std::vector& mats) +imreadmulti_(const String& filename, int flags, std::vector& mats, int start, int count) { /// Search for the relevant decoder to handle the imagery ImageDecoder decoder; + CV_CheckGE(start, 0, "Start index cannont be < 0"); + #ifdef HAVE_GDAL - if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL){ + if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) { decoder = GdalDecoder().newDecoder(); } - else{ + else { #endif decoder = findDecoder(filename); #ifdef HAVE_GDAL @@ -521,10 +515,14 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) #endif /// if no decoder was found, return nothing. - if (!decoder){ + if (!decoder) { return 0; } + if (count < 0) { + count = std::numeric_limits::max(); + } + /// set the filename in the driver decoder->setSource(filename); @@ -532,7 +530,7 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) try { // read the header to make sure it succeeds - if( !decoder->readHeader() ) + if (!decoder->readHeader()) return 0; } catch (const cv::Exception& e) @@ -546,11 +544,22 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) return 0; } - for (;;) + int current = start; + + while (current > 0) + { + if (!decoder->nextPage()) + { + return false; + } + --current; + } + + 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_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED) { if ((flags & IMREAD_ANYDEPTH) == 0) type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); @@ -585,7 +594,7 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) break; // optionally rotate the data if EXIF' orientation flag says so - if( (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) + if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED) { ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); } @@ -595,6 +604,7 @@ imreadmulti_(const String& filename, int flags, std::vector& mats) { break; } + ++current; } return !mats.empty(); @@ -636,9 +646,81 @@ bool imreadmulti(const String& filename, std::vector& mats, int flags) { CV_TRACE_FUNCTION(); - return imreadmulti_(filename, flags, mats); + return imreadmulti_(filename, flags, mats, 0, -1); } + +bool imreadmulti(const String& filename, std::vector& mats, int start, int count, int flags) +{ + CV_TRACE_FUNCTION(); + + return imreadmulti_(filename, flags, mats, start, count); +} + +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(); + } + 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; +} + +size_t imcount(const String& filename, int flags) +{ + CV_TRACE_FUNCTION(); + + return imcount_(filename, flags); +} + + static bool imwrite_( const String& filename, const std::vector& img_vec, const std::vector& params, bool flipv ) { diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 2c6fb6249b..dec38014aa 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -358,6 +358,94 @@ TEST(Imgcodecs_Tiff, decode_black_and_write_image_pr17275_default) EXPECT_EQ(CV_8UC3, img.type()) << cv::typeToString(img.type()); } +TEST(Imgcodecs_Tiff, count_multipage) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + { + const string filename = root + "readwrite/multipage.tif"; + ASSERT_EQ((size_t)6, imcount(filename)); + } + { + const string filename = root + "readwrite/test32FC3_raw.tiff"; + ASSERT_EQ((size_t)1, imcount(filename)); + } +} + +TEST(Imgcodecs_Tiff, read_multipage_indexed) +{ + 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 int page_count = sizeof(page_files) / sizeof(page_files[0]); + vector single_pages; + for (int i = 0; i < page_count; i++) + { + // imread and imreadmulti have different default values for the flag + const Mat page = imread(root + page_files[i], IMREAD_ANYCOLOR); + single_pages.push_back(page); + } + ASSERT_EQ((size_t)page_count, single_pages.size()); + + { + SCOPED_TRACE("Edge Cases"); + vector multi_pages; + bool res = imreadmulti(filename, multi_pages, 0, 0); + // If we asked for 0 images and we successfully read 0 images should this be false ? + ASSERT_TRUE(res == false); + ASSERT_EQ((size_t)0, multi_pages.size()); + res = imreadmulti(filename, multi_pages, 0, 123123); + ASSERT_TRUE(res == true); + ASSERT_EQ((size_t)6, multi_pages.size()); + } + + { + SCOPED_TRACE("Read all with indices"); + vector multi_pages; + bool res = imreadmulti(filename, multi_pages, 0, 6); + ASSERT_TRUE(res == true); + ASSERT_EQ((size_t)page_count, multi_pages.size()); + for (int i = 0; i < page_count; i++) + { + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[i], single_pages[i]); + } + } + + { + SCOPED_TRACE("Read one by one"); + vector multi_pages; + for (int i = 0; i < page_count; i++) + { + bool res = imreadmulti(filename, multi_pages, i, 1); + ASSERT_TRUE(res == true); + ASSERT_EQ((size_t)1, multi_pages.size()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i]); + multi_pages.clear(); + } + } + + { + SCOPED_TRACE("Read multiple at a time"); + vector multi_pages; + for (int i = 0; i < page_count/2; i++) + { + bool res = imreadmulti(filename, multi_pages, i*2, 2); + ASSERT_TRUE(res == true); + ASSERT_EQ((size_t)2, multi_pages.size()); + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i * 2]) << i; + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[1], single_pages[i * 2 + 1]); + multi_pages.clear(); + } + } +} + + #endif }} // namespace