Skip to content

imwrite multipage TIFF files #10367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modules/imgcodecs/src/grfmt_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
return true;
}

bool BaseImageEncoder::writemulti(const std::vector<Mat>&, const std::vector<int>& )
{
return false;
}

ImageEncoder BaseImageEncoder::newEncoder() const
{
return ImageEncoder();
Expand Down
1 change: 1 addition & 0 deletions modules/imgcodecs/src/grfmt_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class BaseImageEncoder
virtual bool setDestination( const String& filename );
virtual bool setDestination( std::vector<uchar>& buf );
virtual bool write( const Mat& img, const std::vector<int>& params ) = 0;
virtual bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);

virtual String getDescription() const;
virtual ImageEncoder newEncoder() const;
Expand Down
207 changes: 112 additions & 95 deletions modules/imgcodecs/src/grfmt_tiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -724,44 +723,8 @@ static void readParam(const std::vector<int>& params, int key, int& value)
}
}

bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector<int>& params)
bool TiffEncoder::writeLibTiff( const std::vector<Mat>& img_vec, const std::vector<int>& 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;
Expand All @@ -780,86 +743,133 @@ bool TiffEncoder::writeLibTiff( const Mat& img, const std::vector<int>& 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<uchar> _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<ushort>(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<ushort>(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<uchar> _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<ushort>(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<ushort>(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);
Expand Down Expand Up @@ -946,6 +956,11 @@ bool TiffEncoder::write_32FC1(const Mat& _img)
return true;
}

bool TiffEncoder::writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params)
{
return writeLibTiff(img_vec, params);
}

bool TiffEncoder::write( const Mat& img, const std::vector<int>& params)
{
int depth = img.depth();
Expand All @@ -961,7 +976,9 @@ bool TiffEncoder::write( const Mat& img, const std::vector<int>& params)

CV_Assert(depth == CV_8U || depth == CV_16U);

return writeLibTiff(img, params);
std::vector<Mat> img_vec;
img_vec.push_back(img);
return writeLibTiff(img_vec, params);
}

} // namespace
Expand Down
5 changes: 4 additions & 1 deletion modules/imgcodecs/src/grfmt_tiff.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,17 @@ class TiffEncoder : public BaseImageEncoder
bool isFormatSupported( int depth ) const;

bool write( const Mat& img, const std::vector<int>& params );

bool writemulti(const std::vector<Mat>& img_vec, const std::vector<int>& params);

ImageEncoder newEncoder() const;

protected:
void writeTag( WLByteStream& strm, TiffTag tag,
TiffFieldType fieldType,
int count, int value );

bool writeLibTiff( const Mat& img, const std::vector<int>& params );
bool writeLibTiff( const std::vector<Mat>& img_vec, const std::vector<int>& params );
bool write_32FC3( const Mat& img );
bool write_32FC1( const Mat& img );

Expand Down
53 changes: 35 additions & 18 deletions modules/imgcodecs/src/loadsave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -667,33 +667,45 @@ bool imreadmulti(const String& filename, std::vector<Mat>& 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<Mat>& img_vec,
const std::vector<int>& params, bool flipv )
{
Mat temp;
const Mat* pimage = &image;

CV_Assert( image.channels() == 1 || image.channels() == 3 || image.channels() == 4 );
bool isMultiImg = img_vec.size() > 1;
std::vector<Mat> 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;
Expand All @@ -703,9 +715,14 @@ bool imwrite( const String& filename, InputArray _img,
const std::vector<int>& params )
{
CV_TRACE_FUNCTION();

Mat img = _img.getMat();
return imwrite_(filename, img, params, false);
std::vector<Mat> 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*
Expand Down
Loading