From ecf359b8c9664b12c2b348a56cfb280334d02aa7 Mon Sep 17 00:00:00 2001 From: Ashod Nakashian Date: Mon, 29 Dec 2014 10:50:03 -0500 Subject: [PATCH 1/3] Support for multipage decoding in BaseImageDecoder and implemented in TiffDecoder. --- modules/imgcodecs/src/grfmt_base.hpp | 3 +++ modules/imgcodecs/src/grfmt_tiff.cpp | 19 ++++++++++++++----- modules/imgcodecs/src/grfmt_tiff.hpp | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 8a534daa8d..dcb75b0dcd 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -70,6 +70,9 @@ public: virtual bool readHeader() = 0; virtual bool readData( Mat& img ) = 0; + /// Called after readData to advance to the next page, if any. + virtual bool nextPage() { return false; } + virtual size_t signatureLength() const; virtual bool checkSignature( const String& signature ) const; virtual ImageDecoder newDecoder() const; diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 7ec76c8483..7046276553 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -118,10 +118,13 @@ bool TiffDecoder::readHeader() { bool result = false; - close(); - // TIFFOpen() mode flags are different to fopen(). A 'b' in mode "rb" has no effect when reading. - // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html - TIFF* tif = TIFFOpen( m_filename.c_str(), "r" ); + TIFF* tif = static_cast(m_tif); + if (!m_tif) + { + // TIFFOpen() mode flags are different to fopen(). A 'b' in mode "rb" has no effect when reading. + // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html + tif = TIFFOpen(m_filename.c_str(), "r"); + } if( tif ) { @@ -182,6 +185,13 @@ bool TiffDecoder::readHeader() return result; } +bool TiffDecoder::nextPage() +{ + // Prepare the next page, if any. + return m_tif && + TIFFReadDirectory(static_cast(m_tif)) && + readHeader(); +} bool TiffDecoder::readData( Mat& img ) { @@ -413,7 +423,6 @@ bool TiffDecoder::readData( Mat& img ) } } - close(); return result; } diff --git a/modules/imgcodecs/src/grfmt_tiff.hpp b/modules/imgcodecs/src/grfmt_tiff.hpp index ec29fbc3ec..f019082569 100644 --- a/modules/imgcodecs/src/grfmt_tiff.hpp +++ b/modules/imgcodecs/src/grfmt_tiff.hpp @@ -100,6 +100,7 @@ public: bool readHeader(); bool readData( Mat& img ); void close(); + bool nextPage(); size_t signatureLength() const; bool checkSignature( const String& signature ) const; From 61ca38103c690580e7eb1d1a670c592a792673e0 Mon Sep 17 00:00:00 2001 From: Ashod Nakashian Date: Mon, 29 Dec 2014 10:50:42 -0500 Subject: [PATCH 2/3] Added imreadmulti API to read multi-paged images into a vector of Mat. --- .../imgcodecs/include/opencv2/imgcodecs.hpp | 10 ++ modules/imgcodecs/src/loadsave.cpp | 93 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index f8c6900b26..a22d3dca76 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -134,6 +134,16 @@ returns an empty matrix ( Mat::data==NULL ). Currently, the following file forma */ CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR ); +/** @brief Loads a multi-page image from a file. (see imread for details.) + +@param filename Name of file to be loaded. +@param flags Flags specifying the color type of a loaded image (see imread). + Defaults to IMREAD_ANYCOLOR, as each page may be different. +@param mats A vector of Mat objects holding each page, if more than one. + +*/ +CV_EXPORTS_W bool imreadmulti(const String& filename, std::vector& mats, int flags = IMREAD_ANYCOLOR); + /** @brief Saves an image to a specified file. @param filename Name of the file. diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 73598716d3..c06b42aded 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -320,6 +320,84 @@ imread_( const String& filename, int flags, int hdrtype, Mat* mat=0 ) hdrtype == LOAD_IMAGE ? (void*)image : (void*)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) +{ + /// Search for the relevant decoder to handle the imagery + ImageDecoder decoder; + +#ifdef HAVE_GDAL + if ((flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL){ + decoder = GdalDecoder().newDecoder(); + } + else{ +#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 + if (!decoder->readHeader()) + return 0; + + for (;;) + { + // grab the decoded type + int type = decoder->type(); + if (flags != -1) + { + if ((flags & CV_LOAD_IMAGE_ANYDEPTH) == 0) + type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); + + if ((flags & CV_LOAD_IMAGE_COLOR) != 0 || + ((flags & CV_LOAD_IMAGE_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. + CvSize size; + size.width = decoder->width(); + size.height = decoder->height(); + + Mat mat; + mat.create(size.height, size.width, type); + + // read the image data + if (!decoder->readData(mat)) + { + break; + } + + mats.push_back(mat); + if (!decoder->nextPage()) + { + break; + } + } + + return !mats.empty(); +} + /** * Read an image * @@ -340,6 +418,21 @@ Mat imread( const String& filename, int flags ) return img; } +/** +* Read a multi-page image +* +* This function merely calls the actual implementation above and returns itself. +* +* @param[in] filename File to load +* @param[in] mats Reference to C++ vector object to hold the images +* @param[in] flags Flags you wish to set. +* +*/ +bool imreadmulti(const String& filename, std::vector& mats, int flags) +{ + return imreadmulti_(filename, flags, mats); +} + static bool imwrite_( const String& filename, const Mat& image, const std::vector& params, bool flipv ) { From 473964806cb76d3a81d1f53b59e5c1031a55e796 Mon Sep 17 00:00:00 2001 From: Ashod Nakashian Date: Mon, 29 Dec 2014 10:51:27 -0500 Subject: [PATCH 3/3] Added imread and imreadmulti regression tests. --- modules/imgcodecs/src/grfmt_tiff.cpp | 2 +- modules/imgcodecs/test/test_grfmt.cpp | 102 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 7046276553..021ff2788c 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -189,7 +189,7 @@ bool TiffDecoder::nextPage() { // Prepare the next page, if any. return m_tif && - TIFFReadDirectory(static_cast(m_tif)) && + TIFFReadDirectory(static_cast(m_tif)) && readHeader(); } diff --git a/modules/imgcodecs/test/test_grfmt.cpp b/modules/imgcodecs/test/test_grfmt.cpp index 451a3a1e40..d3f21f16b3 100644 --- a/modules/imgcodecs/test/test_grfmt.cpp +++ b/modules/imgcodecs/test/test_grfmt.cpp @@ -45,6 +45,68 @@ using namespace cv; using namespace std; +static +bool mats_equal(const Mat& lhs, const Mat& rhs) +{ + if (lhs.channels() != rhs.channels() || + lhs.depth() != rhs.depth() || + lhs.size().height != rhs.size().height || + lhs.size().width != rhs.size().width) + { + return false; + } + + Mat diff = (lhs != rhs); + const Scalar s = sum(diff); + for (int i = 0; i < s.channels; ++i) + { + if (s[i] != 0) + { + return false; + } + } + + return true; +} + +static +bool imread_compare(const string& filepath, int flags = IMREAD_COLOR) +{ + vector pages; + if (!imreadmulti(filepath, pages, flags) || + pages.empty()) + { + return false; + } + + const Mat single = imread(filepath, flags); + return mats_equal(single, pages[0]); +} + +TEST(Imgcodecs_imread, regression) +{ + const char* const filenames[] = + { + "color_palette_alpha.png", + "multipage.tif", + "rle.hdr", + "ordinary.bmp", + "rle8.bmp", + "test_1_c1.jpg" + }; + + const string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/"; + + for (size_t i = 0; i < sizeof(filenames) / sizeof(filenames[0]); ++i) + { + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_UNCHANGED)); + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_GRAYSCALE)); + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_COLOR)); + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_ANYDEPTH)); + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_ANYCOLOR)); + ASSERT_TRUE(imread_compare(folder + string(filenames[i]), IMREAD_LOAD_GDAL)); + } +} class CV_GrfmtWriteBigImageTest : public cvtest::BaseTest { @@ -591,6 +653,46 @@ TEST(Imgcodecs_Tiff, decode_tile_remainder) CV_GrfmtReadTifTiledWithNotFullTiles test; test.safe_run(); } +class CV_GrfmtReadTifMultiPage : public cvtest::BaseTest +{ +private: + void compare(int flags) + { + const string folder = string(cvtest::TS::ptr()->get_data_path()) + "/readwrite/"; + const int page_count = 6; + + vector pages; + bool res = imreadmulti(folder + "multipage.tif", pages, flags); + ASSERT_TRUE(res == true); + ASSERT_TRUE(pages.size() == page_count); + + for (int i = 0; i < page_count; i++) + { + char buffer[256]; + sprintf(buffer, "%smultipage_p%d.tif", folder.c_str(), i + 1); + const string filepath(buffer); + const Mat page = imread(filepath, flags); + ASSERT_TRUE(mats_equal(page, pages[i])); + } + } + +public: + void run(int) + { + compare(IMREAD_UNCHANGED); + compare(IMREAD_GRAYSCALE); + compare(IMREAD_COLOR); + compare(IMREAD_ANYDEPTH); + compare(IMREAD_ANYCOLOR); + compare(IMREAD_LOAD_GDAL); + } +}; + +TEST(Imgcodecs_Tiff, decode_multipage) +{ + CV_GrfmtReadTifMultiPage test; test.safe_run(); +} + #endif #ifdef HAVE_WEBP