/*M/////////////////////////////////////////////////////////////////////////////////////// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. // // By downloading, copying, installing or using the software you agree to this license. // If you do not agree to this license, do not download, install, // copy or use the software. // // // License Agreement // For Open Source Computer Vision Library // // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. // Copyright (C) 2009, Willow Garage Inc., all rights reserved. // Third party copyrights are property of their respective owners. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // * Redistribution's of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // * Redistribution's in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // * The name of the copyright holders may not be used to endorse or promote products // derived from this software without specific prior written permission. // // This software is provided by the copyright holders and contributors "as is" and // any express or implied warranties, including, but not limited to, the implied // warranties of merchantability and fitness for a particular purpose are disclaimed. // In no event shall the Intel Corporation or contributors be liable for any direct, // indirect, incidental, special, exemplary, or consequential damages // (including, but not limited to, procurement of substitute goods or services; // loss of use, data, or profits; or business interruption) however caused // and on any theory of liability, whether in contract, strict liability, // or tort (including negligence or otherwise) arising in any way out of // the use of this software, even if advised of the possibility of such damage. // //M*/ /****************************************************************************************\ A part of the file implements TIFF reader on base of libtiff library (see otherlibs/_graphics/readme.txt for copyright notice) \****************************************************************************************/ #include "precomp.hpp" #ifdef HAVE_TIFF #include #include "grfmt_tiff.hpp" #include // TODO FIXIT Conflict declarations for common types like int64/uint64 namespace tiff_dummy_namespace { #include "tiff.h" #include "tiffio.h" } using namespace tiff_dummy_namespace; namespace cv { #define CV_TIFF_CHECK_CALL(call) \ if (0 == (call)) { \ CV_LOG_WARNING(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed " #call); \ CV_Error(Error::StsError, "OpenCV TIFF: failed " #call); \ } #define CV_TIFF_CHECK_CALL_INFO(call) \ if (0 == (call)) { \ CV_LOG_INFO(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed optional call: " #call ", ignoring"); \ } #define CV_TIFF_CHECK_CALL_DEBUG(call) \ if (0 == (call)) { \ CV_LOG_DEBUG(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed optional call: " #call ", ignoring"); \ } static void cv_tiffCloseHandle(void* handle) { TIFFClose((TIFF*)handle); } static void cv_tiffErrorHandler(const char* module, const char* fmt, va_list ap) { if (cv::utils::logging::getLogLevel() < cv::utils::logging::LOG_LEVEL_DEBUG) return; // TODO cv::vformat() with va_list parameter fprintf(stderr, "OpenCV TIFF: "); if (module != NULL) fprintf(stderr, "%s: ", module); fprintf(stderr, "Warning, "); vfprintf(stderr, fmt, ap); fprintf(stderr, ".\n"); } static bool cv_tiffSetErrorHandler_() { TIFFSetErrorHandler(cv_tiffErrorHandler); TIFFSetWarningHandler(cv_tiffErrorHandler); return true; } static bool cv_tiffSetErrorHandler() { static bool v = cv_tiffSetErrorHandler_(); return v; } static const char fmtSignTiffII[] = "II\x2a\x00"; static const char fmtSignTiffMM[] = "MM\x00\x2a"; static const char fmtSignBigTiffII[] = "II\x2b\x00"; static const char fmtSignBigTiffMM[] = "MM\x00\x2b"; TiffDecoder::TiffDecoder() { m_hdr = false; m_buf_supported = true; m_buf_pos = 0; } void TiffDecoder::close() { m_tif.release(); } TiffDecoder::~TiffDecoder() { close(); } size_t TiffDecoder::signatureLength() const { return 4; } bool TiffDecoder::checkSignature( const String& signature ) const { return signature.size() >= 4 && (memcmp(signature.c_str(), fmtSignTiffII, 4) == 0 || memcmp(signature.c_str(), fmtSignTiffMM, 4) == 0 || memcmp(signature.c_str(), fmtSignBigTiffII, 4) == 0 || memcmp(signature.c_str(), fmtSignBigTiffMM, 4) == 0); } int TiffDecoder::normalizeChannelsNumber(int channels) const { CV_Assert(channels <= 4); return channels > 4 ? 4 : channels; } ImageDecoder TiffDecoder::newDecoder() const { cv_tiffSetErrorHandler(); return makePtr(); } class TiffDecoderBufHelper { Mat& m_buf; size_t& m_buf_pos; public: TiffDecoderBufHelper(Mat& buf, size_t& buf_pos) : m_buf(buf), m_buf_pos(buf_pos) {} static tmsize_t read( thandle_t handle, void* buffer, tmsize_t n ) { TiffDecoderBufHelper *helper = reinterpret_cast(handle); const Mat& buf = helper->m_buf; const tmsize_t size = buf.cols*buf.rows*buf.elemSize(); tmsize_t pos = helper->m_buf_pos; if ( n > (size - pos) ) { n = size - pos; } memcpy(buffer, buf.ptr() + pos, n); helper->m_buf_pos += n; return n; } static tmsize_t write( thandle_t /*handle*/, void* /*buffer*/, tmsize_t /*n*/ ) { // Not used for decoding. return 0; } static toff_t seek( thandle_t handle, toff_t offset, int whence ) { TiffDecoderBufHelper *helper = reinterpret_cast(handle); const Mat& buf = helper->m_buf; const toff_t size = buf.cols*buf.rows*buf.elemSize(); toff_t new_pos = helper->m_buf_pos; switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos += offset; break; case SEEK_END: new_pos = size + offset; break; } new_pos = std::min(new_pos, size); helper->m_buf_pos = (size_t)new_pos; return new_pos; } static int map( thandle_t handle, void** base, toff_t* size ) { TiffDecoderBufHelper *helper = reinterpret_cast(handle); Mat& buf = helper->m_buf; *base = buf.ptr(); *size = buf.cols*buf.rows*buf.elemSize(); return 0; } static toff_t size( thandle_t handle ) { TiffDecoderBufHelper *helper = reinterpret_cast(handle); const Mat& buf = helper->m_buf; return buf.cols*buf.rows*buf.elemSize(); } static int close( thandle_t handle ) { TiffDecoderBufHelper *helper = reinterpret_cast(handle); delete helper; return 0; } }; bool TiffDecoder::readHeader() { bool result = false; TIFF* tif = static_cast(m_tif.get()); if (!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 if ( !m_buf.empty() ) { m_buf_pos = 0; TiffDecoderBufHelper* buf_helper = new TiffDecoderBufHelper(this->m_buf, this->m_buf_pos); tif = TIFFClientOpen( "", "r", reinterpret_cast(buf_helper), &TiffDecoderBufHelper::read, &TiffDecoderBufHelper::write, &TiffDecoderBufHelper::seek, &TiffDecoderBufHelper::close, &TiffDecoderBufHelper::size, &TiffDecoderBufHelper::map, /*unmap=*/0 ); if (!tif) delete buf_helper; } else { tif = TIFFOpen(m_filename.c_str(), "r"); } if (tif) m_tif.reset(tif, cv_tiffCloseHandle); else m_tif.release(); } if (tif) { uint32 wdth = 0, hght = 0; uint16 photometric = 0; CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &wdth)); CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &hght)); CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)); { bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK; uint16 bpp = 8, ncn = isGrayScale ? 1 : 3; if (0 == TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp)) { // TIFF bi-level images don't require TIFFTAG_BITSPERSAMPLE tag bpp = 1; } CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn)); m_width = wdth; m_height = hght; if (ncn == 3 && photometric == PHOTOMETRIC_LOGLUV) { m_type = CV_32FC3; m_hdr = true; return true; } m_hdr = false; if( bpp > 8 && ((photometric > 2) || (ncn != 1 && ncn != 3 && ncn != 4))) bpp = 8; uint16 sample_format = SAMPLEFORMAT_UINT; TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sample_format); int wanted_channels = normalizeChannelsNumber(ncn); switch (bpp) { case 1: { CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, ""); int depth = sample_format == SAMPLEFORMAT_INT ? CV_8S : CV_8U; m_type = CV_MAKETYPE(depth, !isGrayScale ? wanted_channels : 1); result = true; break; } case 8: { //Palette color, the value of the component is used as an index into the red, //green and blue curves in the ColorMap field to retrieve an RGB triplet that defines the color. CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, ""); int depth = sample_format == SAMPLEFORMAT_INT ? CV_8S : CV_8U; if (photometric == PHOTOMETRIC_PALETTE) m_type = CV_MAKETYPE(depth, 3); else m_type = CV_MAKETYPE(depth, !isGrayScale ? wanted_channels : 1); result = true; break; } case 16: { CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_UINT || sample_format == SAMPLEFORMAT_INT, ""); int depth = sample_format == SAMPLEFORMAT_INT ? CV_16S : CV_16U; m_type = CV_MAKETYPE(depth, !isGrayScale ? wanted_channels : 1); result = true; break; } case 32: { CV_Check((int)sample_format, sample_format == SAMPLEFORMAT_IEEEFP || sample_format == SAMPLEFORMAT_INT, ""); int depth = sample_format == SAMPLEFORMAT_IEEEFP ? CV_32F : CV_32S; m_type = CV_MAKETYPE(depth, wanted_channels); result = true; break; } case 64: CV_CheckEQ((int)sample_format, SAMPLEFORMAT_IEEEFP, ""); m_type = CV_MAKETYPE(CV_64F, wanted_channels); result = true; break; default: CV_Error(cv::Error::StsError, "Invalid bitsperpixel value read from TIFF header! Must be 1, 8, 16, 32 or 64."); } } } if( !result ) close(); return result; } bool TiffDecoder::nextPage() { // Prepare the next page, if any. return !m_tif.empty() && TIFFReadDirectory(static_cast(m_tif.get())) && readHeader(); } static void fixOrientationPartial(Mat &img, uint16 orientation) { switch(orientation) { case ORIENTATION_RIGHTTOP: case ORIENTATION_LEFTBOT: flip(img, img, -1); /* fall through */ case ORIENTATION_LEFTTOP: case ORIENTATION_RIGHTBOT: transpose(img, img); break; } } static void fixOrientationFull(Mat &img, int orientation) { switch(orientation) { case ORIENTATION_TOPRIGHT: flip(img, img, 1); break; case ORIENTATION_BOTRIGHT: flip(img, img, -1); break; case ORIENTATION_BOTLEFT: flip(img, img, 0); break; case ORIENTATION_LEFTTOP: transpose(img, img); break; case ORIENTATION_RIGHTTOP: transpose(img, img); flip(img, img, 1); break; case ORIENTATION_RIGHTBOT: transpose(img, img); flip(img, img, -1); break; case ORIENTATION_LEFTBOT: transpose(img, img); flip(img, img, 0); break; } } /** * Fix orientation defined in tag 274. * For 8 bit some corrections are done by TIFFReadRGBAStrip/Tile already. * Not so for 16/32/64 bit. */ static void fixOrientation(Mat &img, uint16 orientation, int dst_bpp) { switch(dst_bpp) { case 8: fixOrientationPartial(img, orientation); break; case 16: case 32: case 64: fixOrientationFull(img, orientation); break; } } bool TiffDecoder::readData( Mat& img ) { int type = img.type(); int depth = CV_MAT_DEPTH(type); CV_Assert(!m_tif.empty()); TIFF* tif = (TIFF*)m_tif.get(); uint16 photometric = (uint16)-1; CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)); if (m_hdr && depth >= CV_32F) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT)); } bool color = img.channels() > 1; CV_CheckType(type, depth == CV_8U || depth == CV_8S || depth == CV_16U || depth == CV_16S || depth == CV_32S || depth == CV_32F || depth == CV_64F, ""); if (m_width && m_height) { int is_tiled = TIFFIsTiled(tif) != 0; bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK; uint16 bpp = 8, ncn = isGrayScale ? 1 : 3; if (0 == TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp)) { // TIFF bi-level images don't require TIFFTAG_BITSPERSAMPLE tag bpp = 1; } CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn)); uint16 img_orientation = ORIENTATION_TOPLEFT; CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ORIENTATION, &img_orientation)); const int bitsPerByte = 8; int dst_bpp = (int)(img.elemSize1() * bitsPerByte); bool vert_flip = dst_bpp == 8 && (img_orientation == ORIENTATION_BOTRIGHT || img_orientation == ORIENTATION_RIGHTBOT || img_orientation == ORIENTATION_BOTLEFT || img_orientation == ORIENTATION_LEFTBOT); int wanted_channels = normalizeChannelsNumber(img.channels()); if (dst_bpp == 8) { char errmsg[1024]; if (!TIFFRGBAImageOK(tif, errmsg)) { CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); close(); return false; } } uint32 tile_width0 = m_width, tile_height0 = 0; if (is_tiled) { CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width0)); CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height0)); } else { // optional CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &tile_height0)); } { if (tile_width0 == 0) tile_width0 = m_width; if (tile_height0 == 0 || (!is_tiled && tile_height0 == std::numeric_limits::max()) ) tile_height0 = m_height; const int TILE_MAX_WIDTH = (1 << 24); const int TILE_MAX_HEIGHT = (1 << 24); CV_Assert((int)tile_width0 > 0 && (int)tile_width0 <= TILE_MAX_WIDTH); CV_Assert((int)tile_height0 > 0 && (int)tile_height0 <= TILE_MAX_HEIGHT); const uint64_t MAX_TILE_SIZE = (CV_BIG_UINT(1) << 30); CV_CheckLE((int)ncn, 4, ""); CV_CheckLE((int)bpp, 64, ""); CV_Assert(((uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte)) < MAX_TILE_SIZE) && "TIFF tile size is too large: >= 1Gb"); if (dst_bpp == 8) { // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA bpp = 8; ncn = 4; } else if (dst_bpp == 32 || dst_bpp == 64) { CV_Assert(ncn == img.channels()); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)); } const size_t buffer_size = (bpp / bitsPerByte) * ncn * tile_height0 * tile_width0; AutoBuffer _buffer(buffer_size); uchar* buffer = _buffer.data(); ushort* buffer16 = (ushort*)buffer; int tileidx = 0; for (int y = 0; y < m_height; y += (int)tile_height0) { int tile_height = std::min((int)tile_height0, m_height - y); const int img_y = vert_flip ? m_height - y - tile_height : y; for(int x = 0; x < m_width; x += (int)tile_width0, tileidx++) { int tile_width = std::min((int)tile_width0, m_width - x); switch (dst_bpp) { case 8: { uchar* bstart = buffer; if (!is_tiled) { CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)buffer)); } else { CV_TIFF_CHECK_CALL(TIFFReadRGBATile(tif, x, y, (uint32*)buffer)); // Tiles fill the buffer from the bottom up bstart += (tile_height0 - tile_height) * tile_width0 * 4; } for (int i = 0; i < tile_height; i++) { if (color) { if (wanted_channels == 4) { icvCvt_BGRA2RGBA_8u_C4R(bstart + i*tile_width0*4, 0, img.ptr(img_y + tile_height - i - 1, x), 0, Size(tile_width, 1) ); } else { CV_CheckEQ(wanted_channels, 3, "TIFF-8bpp: BGR/BGRA images are supported only"); icvCvt_BGRA2BGR_8u_C4C3R(bstart + i*tile_width0*4, 0, img.ptr(img_y + tile_height - i - 1, x), 0, Size(tile_width, 1), 2); } } else { CV_CheckEQ(wanted_channels, 1, ""); icvCvt_BGRA2Gray_8u_C4C1R( bstart + i*tile_width0*4, 0, img.ptr(img_y + tile_height - i - 1, x), 0, Size(tile_width, 1), 2); } } break; } case 16: { if (!is_tiled) { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); } else { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); } for (int i = 0; i < tile_height; i++) { if (color) { if (ncn == 1) { CV_CheckEQ(wanted_channels, 3, ""); icvCvt_Gray2BGR_16u_C1C3R(buffer16 + i*tile_width0*ncn, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } else if (ncn == 3) { CV_CheckEQ(wanted_channels, 3, ""); icvCvt_RGB2BGR_16u_C3R(buffer16 + i*tile_width0*ncn, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } else if (ncn == 4) { if (wanted_channels == 4) { icvCvt_BGRA2RGBA_16u_C4R(buffer16 + i*tile_width0*ncn, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1)); } else { CV_CheckEQ(wanted_channels, 3, "TIFF-16bpp: BGR/BGRA images are supported only"); icvCvt_BGRA2BGR_16u_C4C3R(buffer16 + i*tile_width0*ncn, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1), 2); } } else { CV_Error(Error::StsError, "Not supported"); } } else { CV_CheckEQ(wanted_channels, 1, ""); if( ncn == 1 ) { memcpy(img.ptr(img_y + i, x), buffer16 + i*tile_width0*ncn, tile_width*sizeof(ushort)); } else { icvCvt_BGRA2Gray_16u_CnC1R(buffer16 + i*tile_width0*ncn, 0, img.ptr(img_y + i, x), 0, Size(tile_width, 1), ncn, 2); } } } break; } case 32: case 64: { if( !is_tiled ) { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, buffer, buffer_size) >= 0); } else { CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, buffer, buffer_size) >= 0); } Mat m_tile(Size(tile_width0, tile_height0), CV_MAKETYPE((dst_bpp == 32) ? (depth == CV_32S ? CV_32S : CV_32F) : CV_64F, ncn), buffer); Rect roi_tile(0, 0, tile_width, tile_height); Rect roi_img(x, img_y, tile_width, tile_height); if (!m_hdr && ncn == 3) cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR); else if (!m_hdr && ncn == 4) cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA); else m_tile(roi_tile).copyTo(img(roi_img)); break; } default: { CV_Assert(0 && "OpenCV TIFF: unsupported depth"); } } // switch (dst_bpp) } // for x } // for y } fixOrientation(img, img_orientation, dst_bpp); } if (m_hdr && depth >= CV_32F) { CV_Assert(photometric == PHOTOMETRIC_LOGLUV); cvtColor(img, img, COLOR_XYZ2BGR); } return true; } ////////////////////////////////////////////////////////////////////////////////////////// TiffEncoder::TiffEncoder() { m_description = "TIFF Files (*.tiff;*.tif)"; m_buf_supported = true; } TiffEncoder::~TiffEncoder() { } ImageEncoder TiffEncoder::newEncoder() const { return makePtr(); } bool TiffEncoder::isFormatSupported( int depth ) const { return depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F; } void TiffEncoder::writeTag( WLByteStream& strm, TiffTag tag, TiffFieldType fieldType, int count, int value ) { strm.putWord( tag ); strm.putWord( fieldType ); strm.putDWord( count ); strm.putDWord( value ); } class TiffEncoderBufHelper { public: TiffEncoderBufHelper(std::vector *buf) : m_buf(buf), m_buf_pos(0) {} TIFF* open () { // 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 return TIFFClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, &TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek, &TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size, /*map=*/0, /*unmap=*/0 ); } static tmsize_t read( thandle_t /*handle*/, void* /*buffer*/, tmsize_t /*n*/ ) { // Not used for encoding. return 0; } static tmsize_t write( thandle_t handle, void* buffer, tmsize_t n ) { TiffEncoderBufHelper *helper = reinterpret_cast(handle); size_t begin = (size_t)helper->m_buf_pos; size_t end = begin + n; if ( helper->m_buf->size() < end ) { helper->m_buf->resize(end); } memcpy(&(*helper->m_buf)[begin], buffer, n); helper->m_buf_pos = end; return n; } static toff_t seek( thandle_t handle, toff_t offset, int whence ) { TiffEncoderBufHelper *helper = reinterpret_cast(handle); const toff_t size = helper->m_buf->size(); toff_t new_pos = helper->m_buf_pos; switch (whence) { case SEEK_SET: new_pos = offset; break; case SEEK_CUR: new_pos += offset; break; case SEEK_END: new_pos = size + offset; break; } helper->m_buf_pos = new_pos; return new_pos; } static toff_t size( thandle_t handle ) { TiffEncoderBufHelper *helper = reinterpret_cast(handle); return helper->m_buf->size(); } static int close( thandle_t /*handle*/ ) { // Do nothing. return 0; } private: std::vector* m_buf; toff_t m_buf_pos; }; static bool readParam(const std::vector& params, int key, int& value) { for (size_t i = 0; i + 1 < params.size(); i += 2) { if (params[i] == key) { value = params[i + 1]; return true; } } return false; } bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vector& params) { // 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* tif = NULL; TiffEncoderBufHelper buf_helper(m_buf); if ( m_buf ) { tif = buf_helper.open(); } else { tif = TIFFOpen(m_filename.c_str(), "w"); } if (!tif) { return false; } cv::Ptr tif_cleanup(tif, cv_tiffCloseHandle); //Settings that matter to all images int compression = COMPRESSION_LZW; int predictor = PREDICTOR_HORIZONTAL; int resUnit = -1, dpiX = -1, dpiY = -1; readParam(params, IMWRITE_TIFF_COMPRESSION, compression); readParam(params, TIFFTAG_PREDICTOR, predictor); readParam(params, IMWRITE_TIFF_RESUNIT, resUnit); readParam(params, IMWRITE_TIFF_XDPI, dpiX); readParam(params, IMWRITE_TIFF_YDPI, dpiY); //Iterate through each image in the vector and write them out as Tiff directories for (size_t page = 0; page < img_vec.size(); page++) { const Mat& img = img_vec[page]; CV_Assert(!img.empty()); int channels = img.channels(); int width = img.cols, height = img.rows; int type = img.type(); int depth = CV_MAT_DEPTH(type); CV_CheckType(type, depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F, ""); CV_CheckType(type, channels >= 1 && channels <= 4, ""); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height)); if (img_vec.size() > 1) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PAGENUMBER, page, img_vec.size())); } int compression_param = -1; // OPENCV_FUTURE if (type == CV_32FC3 && (!readParam(params, IMWRITE_TIFF_COMPRESSION, compression_param) || compression_param == COMPRESSION_SGILOG)) { if (!write_32FC3_SGILOG(img, tif)) return false; continue; } int page_compression = compression; int bitsPerChannel = -1; switch (depth) { case CV_8U: { bitsPerChannel = 8; break; } case CV_16U: { bitsPerChannel = 16; break; } case CV_32F: { bitsPerChannel = 32; page_compression = COMPRESSION_NONE; break; } case CV_64F: { bitsPerChannel = 64; page_compression = COMPRESSION_NONE; break; } default: { return false; } } const int bitsPerByte = 8; size_t fileStep = (width * channels * bitsPerChannel) / bitsPerByte; CV_Assert(fileStep > 0); int rowsPerStrip = (int)((1 << 13) / fileStep); readParam(params, TIFFTAG_ROWSPERSTRIP, rowsPerStrip); rowsPerStrip = std::max(1, std::min(height, rowsPerStrip)); int colorspace = channels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitsPerChannel)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, page_compression)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, colorspace)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, channels)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rowsPerStrip)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, depth >= CV_32F ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT)); if (page_compression != COMPRESSION_NONE) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)); } if (resUnit >= RESUNIT_NONE && resUnit <= RESUNIT_CENTIMETER) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, resUnit)); } if (dpiX >= 0) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)dpiX)); } if (dpiY >= 0) { CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)dpiY)); } // row buffer, because TIFFWriteScanline modifies the original data! size_t scanlineSize = TIFFScanlineSize(tif); AutoBuffer _buffer(scanlineSize + 32); uchar* buffer = _buffer.data(); CV_DbgAssert(buffer); Mat m_buffer(Size(width, 1), CV_MAKETYPE(depth, channels), buffer, (size_t)scanlineSize); for (int y = 0; y < height; ++y) { switch (channels) { case 1: { memcpy(buffer, img.ptr(y), scanlineSize); break; } case 3: { cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB); break; } case 4: { cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA); break; } default: { CV_Assert(0); } } CV_TIFF_CHECK_CALL(TIFFWriteScanline(tif, buffer, y, 0) == 1); } CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif)); } return true; } bool TiffEncoder::write_32FC3_SGILOG(const Mat& _img, void* tif_) { TIFF* tif = (TIFF*)tif_; CV_Assert(tif); Mat img; cvtColor(_img, img, COLOR_BGR2XYZ); //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, img.cols)); //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, img.rows)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 32)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_SGILOG)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_LOGLUV)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT)); CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1)); const int strip_size = 3 * img.cols; for (int i = 0; i < img.rows; i++) { CV_TIFF_CHECK_CALL(TIFFWriteEncodedStrip(tif, i, (tdata_t)img.ptr(i), strip_size * sizeof(float)) != (tsize_t)-1); } CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif)); 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 type = img.type(); int depth = CV_MAT_DEPTH(type); CV_CheckType(type, depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F, ""); std::vector img_vec; img_vec.push_back(img); return writeLibTiff(img_vec, params); } } // namespace #endif