diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index d3223dba2a20..b7032c172375 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -127,6 +127,11 @@ bool BaseImageEncoder::setDestination( std::vector& buf ) return true; } +bool BaseImageEncoder::writemulti(const std::vector&, const std::vector& ) +{ + return false; +} + ImageEncoder BaseImageEncoder::newEncoder() const { return ImageEncoder(); diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 88e3ca7c1c19..7d75636cf59e 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -101,6 +101,7 @@ class BaseImageEncoder virtual bool setDestination( const String& filename ); virtual bool setDestination( std::vector& buf ); virtual bool write( const Mat& img, const std::vector& params ) = 0; + virtual bool writemulti(const std::vector& img_vec, const std::vector& params); virtual String getDescription() const; virtual ImageEncoder newEncoder() const; diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 9706b9494faa..f3382408b0fe 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -539,7 +539,6 @@ bool TiffDecoder::readData( Mat& img ) bool TiffDecoder::readData_32FC3(Mat& img) { - int rows_per_strip = 0, photometric = 0; if(!m_tif) { @@ -724,44 +723,8 @@ static void readParam(const std::vector& params, int key, int& value) } } -bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector& params) +bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vector& params) { - int channels = img.channels(); - int width = img.cols, height = img.rows; - int depth = img.depth(); - - int bitsPerChannel = -1; - switch (depth) - { - case CV_8U: - { - bitsPerChannel = 8; - break; - } - case CV_16U: - { - bitsPerChannel = 16; - break; - } - default: - { - return false; - } - } - - const int bitsPerByte = 8; - size_t fileStep = (width * channels * bitsPerChannel) / bitsPerByte; - - int rowsPerStrip = (int)((1 << 13)/fileStep); - readParam(params, TIFFTAG_ROWSPERSTRIP, rowsPerStrip); - - if( rowsPerStrip < 1 ) - rowsPerStrip = 1; - - if( rowsPerStrip > height ) - rowsPerStrip = height; - - // do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode. // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html TIFF* pTiffHandle; @@ -780,86 +743,133 @@ bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector& params) return false; } + //Settings that matter to all images // defaults for now, maybe base them on params in the future - int compression = COMPRESSION_LZW; - int predictor = PREDICTOR_HORIZONTAL; + int compression = COMPRESSION_LZW; + int predictor = PREDICTOR_HORIZONTAL; readParam(params, TIFFTAG_COMPRESSION, compression); readParam(params, TIFFTAG_PREDICTOR, predictor); - int colorspace = channels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; - - if ( !TIFFSetField(pTiffHandle, TIFFTAG_IMAGEWIDTH, width) - || !TIFFSetField(pTiffHandle, TIFFTAG_IMAGELENGTH, height) - || !TIFFSetField(pTiffHandle, TIFFTAG_BITSPERSAMPLE, bitsPerChannel) - || !TIFFSetField(pTiffHandle, TIFFTAG_COMPRESSION, compression) - || !TIFFSetField(pTiffHandle, TIFFTAG_PHOTOMETRIC, colorspace) - || !TIFFSetField(pTiffHandle, TIFFTAG_SAMPLESPERPIXEL, channels) - || !TIFFSetField(pTiffHandle, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) - || !TIFFSetField(pTiffHandle, TIFFTAG_ROWSPERSTRIP, rowsPerStrip) - ) - { - TIFFClose(pTiffHandle); - return false; - } - - if (compression != COMPRESSION_NONE && !TIFFSetField(pTiffHandle, TIFFTAG_PREDICTOR, predictor) ) + //Iterate through each image in the vector and write them out as Tiff directories + for (size_t page = 0; page < img_vec.size(); page++) { - TIFFClose(pTiffHandle); - return false; - } - - // row buffer, because TIFFWriteScanline modifies the original data! - size_t scanlineSize = TIFFScanlineSize(pTiffHandle); - AutoBuffer _buffer(scanlineSize+32); - uchar* buffer = _buffer; - if (!buffer) - { - TIFFClose(pTiffHandle); - return false; - } + const Mat& img = img_vec[page]; + int channels = img.channels(); + int width = img.cols, height = img.rows; + int depth = img.depth(); - for (int y = 0; y < height; ++y) - { - switch(channels) + int bitsPerChannel = -1; + switch (depth) { - case 1: - { - memcpy(buffer, img.ptr(y), scanlineSize); - break; - } - - case 3: + case CV_8U: { - if (depth == CV_8U) - icvCvt_BGR2RGB_8u_C3R( img.ptr(y), 0, buffer, 0, cvSize(width,1) ); - else - icvCvt_BGR2RGB_16u_C3R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width,1) ); + bitsPerChannel = 8; break; } - - case 4: + case CV_16U: { - if (depth == CV_8U) - icvCvt_BGRA2RGBA_8u_C4R( img.ptr(y), 0, buffer, 0, cvSize(width,1) ); - else - icvCvt_BGRA2RGBA_16u_C4R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width,1) ); + bitsPerChannel = 16; break; } - default: { - TIFFClose(pTiffHandle); return false; } } - int writeResult = TIFFWriteScanline(pTiffHandle, buffer, y, 0); - if (writeResult != 1) + const int bitsPerByte = 8; + size_t fileStep = (width * channels * bitsPerChannel) / bitsPerByte; + + int rowsPerStrip = (int)((1 << 13) / fileStep); + readParam(params, TIFFTAG_ROWSPERSTRIP, rowsPerStrip); + + if (rowsPerStrip < 1) + rowsPerStrip = 1; + + if (rowsPerStrip > height) + rowsPerStrip = height; + + int colorspace = channels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; + + if (!TIFFSetField(pTiffHandle, TIFFTAG_IMAGEWIDTH, width) + || !TIFFSetField(pTiffHandle, TIFFTAG_IMAGELENGTH, height) + || !TIFFSetField(pTiffHandle, TIFFTAG_BITSPERSAMPLE, bitsPerChannel) + || !TIFFSetField(pTiffHandle, TIFFTAG_COMPRESSION, compression) + || !TIFFSetField(pTiffHandle, TIFFTAG_PHOTOMETRIC, colorspace) + || !TIFFSetField(pTiffHandle, TIFFTAG_SAMPLESPERPIXEL, channels) + || !TIFFSetField(pTiffHandle, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) + || !TIFFSetField(pTiffHandle, TIFFTAG_ROWSPERSTRIP, rowsPerStrip) + || (img_vec.size() > 1 && ( + !TIFFSetField(pTiffHandle, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE) + || !TIFFSetField(pTiffHandle, TIFFTAG_PAGENUMBER, page, img_vec.size() ))) + ) + { + TIFFClose(pTiffHandle); + return false; + } + + if (compression != COMPRESSION_NONE && !TIFFSetField(pTiffHandle, TIFFTAG_PREDICTOR, predictor)) + { + TIFFClose(pTiffHandle); + return false; + } + + // row buffer, because TIFFWriteScanline modifies the original data! + size_t scanlineSize = TIFFScanlineSize(pTiffHandle); + AutoBuffer _buffer(scanlineSize + 32); + uchar* buffer = _buffer; + if (!buffer) { TIFFClose(pTiffHandle); return false; } + + for (int y = 0; y < height; ++y) + { + switch (channels) + { + case 1: + { + memcpy(buffer, img.ptr(y), scanlineSize); + break; + } + + case 3: + { + if (depth == CV_8U) + icvCvt_BGR2RGB_8u_C3R( img.ptr(y), 0, buffer, 0, cvSize(width, 1)); + else + icvCvt_BGR2RGB_16u_C3R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width, 1)); + break; + } + + case 4: + { + if (depth == CV_8U) + icvCvt_BGRA2RGBA_8u_C4R( img.ptr(y), 0, buffer, 0, cvSize(width, 1)); + else + icvCvt_BGRA2RGBA_16u_C4R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width, 1)); + break; + } + + default: + { + TIFFClose(pTiffHandle); + return false; + } + } + + int writeResult = TIFFWriteScanline(pTiffHandle, buffer, y, 0); + if (writeResult != 1) + { + TIFFClose(pTiffHandle); + return false; + } + } + + TIFFWriteDirectory(pTiffHandle); + } TIFFClose(pTiffHandle); @@ -946,6 +956,11 @@ bool TiffEncoder::write_32FC1(const Mat& _img) return true; } +bool TiffEncoder::writemulti(const std::vector& img_vec, const std::vector& params) +{ + return writeLibTiff(img_vec, params); +} + bool TiffEncoder::write( const Mat& img, const std::vector& params) { int depth = img.depth(); @@ -961,7 +976,9 @@ bool TiffEncoder::write( const Mat& img, const std::vector& params) CV_Assert(depth == CV_8U || depth == CV_16U); - return writeLibTiff(img, params); + std::vector img_vec; + img_vec.push_back(img); + return writeLibTiff(img_vec, params); } } // namespace diff --git a/modules/imgcodecs/src/grfmt_tiff.hpp b/modules/imgcodecs/src/grfmt_tiff.hpp index 2485e2d89261..eb0756f4d60b 100644 --- a/modules/imgcodecs/src/grfmt_tiff.hpp +++ b/modules/imgcodecs/src/grfmt_tiff.hpp @@ -128,6 +128,9 @@ class TiffEncoder : public BaseImageEncoder bool isFormatSupported( int depth ) const; bool write( const Mat& img, const std::vector& params ); + + bool writemulti(const std::vector& img_vec, const std::vector& params); + ImageEncoder newEncoder() const; protected: @@ -135,7 +138,7 @@ class TiffEncoder : public BaseImageEncoder TiffFieldType fieldType, int count, int value ); - bool writeLibTiff( const Mat& img, const std::vector& params ); + bool writeLibTiff( const std::vector& img_vec, const std::vector& params ); bool write_32FC3( const Mat& img ); bool write_32FC1( const Mat& img ); diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index b6b3c7e5ecba..814d30774859 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -667,33 +667,45 @@ bool imreadmulti(const String& filename, std::vector& mats, int flags) return imreadmulti_(filename, flags, mats); } -static bool imwrite_( const String& filename, const Mat& image, +static bool imwrite_( const String& filename, const std::vector& img_vec, const std::vector& params, bool flipv ) { - Mat temp; - const Mat* pimage = ℑ - - CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 ); + bool isMultiImg = img_vec.size() > 1; + std::vector write_vec; ImageEncoder encoder = findEncoder( filename ); if( !encoder ) CV_Error( CV_StsError, "could not find a writer for the specified extension" ); - if( !encoder->isFormatSupported(image.depth()) ) - { - CV_Assert( encoder->isFormatSupported(CV_8U) ); - image.convertTo( temp, CV_8U ); - pimage = &temp; - } - if( flipv ) + for (size_t page = 0; page < img_vec.size(); page++) { - flip(*pimage, temp, 0); - pimage = &temp; + Mat image = img_vec[page]; + CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 ); + + Mat temp; + if( !encoder->isFormatSupported(image.depth()) ) + { + CV_Assert( encoder->isFormatSupported(CV_8U) ); + image.convertTo( temp, CV_8U ); + image = temp; + } + + if( flipv ) + { + flip(image, temp, 0); + image = temp; + } + + write_vec.push_back(image); } encoder->setDestination( filename ); CV_Assert(params.size() <= CV_IO_MAX_IMAGE_PARAMS*2); - bool code = encoder->write( *pimage, params ); + bool code; + if (!isMultiImg) + code = encoder->write( write_vec[0], params ); + else + code = encoder->writemulti( write_vec, params ); //to be implemented // CV_Assert( code ); return code; @@ -703,9 +715,14 @@ bool imwrite( const String& filename, InputArray _img, const std::vector& params ) { CV_TRACE_FUNCTION(); - - Mat img = _img.getMat(); - return imwrite_(filename, img, params, false); + std::vector img_vec; + //Did we get a Mat or a vector of Mats? + if (_img.isMat()) + img_vec.push_back(_img.getMat()); + else if (_img.isMatVector()) + _img.getMatVector(img_vec); + + return imwrite_(filename, img_vec, params, false); } static void* diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 5c5cb3bb5aec..1f5ab2401a06 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -206,6 +206,40 @@ INSTANTIATE_TEST_CASE_P(AllModes, Imgcodecs_Tiff_Modes, testing::ValuesIn(all_mo //================================================================================================== +TEST(Imgcodecs_Tiff_Modes, write_multipage) +{ + 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; + for (size_t i = 0; i < page_count; i++) + { + const Mat page = imread(root + page_files[i]); + pages.push_back(page); + } + + string tmp_filename = cv::tempfile(".tiff"); + bool res = imwrite(tmp_filename, pages); + ASSERT_TRUE(res); + + vector read_pages; + imreadmulti(tmp_filename, read_pages); + for (size_t i = 0; i < page_count; i++) + { + EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), read_pages[i], pages[i]); + } +} + +//================================================================================================== + TEST(Imgcodecs_Tiff, imdecode_no_exception_temporary_file_removed) { const string root = cvtest::TS::ptr()->get_data_path();