mirror of
https://github.com/opencv/opencv.git
synced 2024-12-16 10:29:11 +08:00
934e6899f8
imgcodecs: Add rgb flag for imread and imdecode #25809 Try to `imread` images by RGB to save R-B swapping costs. ## How to use it? ``` img_rgb = cv2.imread("PATH", IMREAD_COLOR_RGB) # OpenCV decode the image by RGB format. ``` ## TODO - [x] Fix the broken code - [x] Add imread rgb test - [x] Speed test of rgb mode. ## Performance test | file name | IMREAD_COLOR | IMREAD_COLOR_RGB | | --------- | ------ | --------- | | jpg01 | 284 ms | 277 ms | | jpg02 | 376 ms | 366 ms | | png01 | 62 ms | 60 ms | | Png02 | 97 ms | 94 ms | Test with [image_test.zip](https://github.com/user-attachments/files/15982949/image_test.zip) ```.cpp string img_path = "/Users/mzh/work/data/image_test/png02.png"; int loop = 20; TickMeter t; double t0 = 10000; for (int i = 0; i < loop; i++) { t.reset(); t.start(); img_bgr = imread(img_path, IMREAD_COLOR); t.stop(); if (t.getTimeMilli() < t0) t0 = t.getTimeMilli(); } std::cout<<"bgr time = "<<t0<<std::endl; t0 = 10000; for (int i = 0; i < loop; i++) { t.reset(); t.start(); img_rgb = imread(img_path, IMREAD_COLOR_RGB); t.stop(); if (t.getTimeMilli() < t0) t0 = t.getTimeMilli(); } std::cout<<"rgb time = "<<t0<<std::endl; ``` ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [ ] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
1490 lines
57 KiB
C++
1490 lines
57 KiB
C++
/*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 <opencv2/core/utils/logger.hpp>
|
|
|
|
#include "grfmt_tiff.hpp"
|
|
#include <limits>
|
|
|
|
#include "tiff.h"
|
|
#include "tiffio.h"
|
|
|
|
namespace cv
|
|
{
|
|
|
|
// to extend cvtColor() to support CV_8S, CV_16S, CV_32S and CV_64F.
|
|
static void extend_cvtColor( InputArray _src, OutputArray _dst, int code );
|
|
|
|
#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_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_Check(channels, channels >= 1 && channels <= 4, "Unsupported number of channels");
|
|
return channels;
|
|
}
|
|
|
|
ImageDecoder TiffDecoder::newDecoder() const
|
|
{
|
|
cv_tiffSetErrorHandler();
|
|
return makePtr<TiffDecoder>();
|
|
}
|
|
|
|
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<TiffDecoderBufHelper*>(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<TiffDecoderBufHelper*>(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<TiffDecoderBufHelper*>(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<TiffDecoderBufHelper*>(handle);
|
|
const Mat& buf = helper->m_buf;
|
|
return buf.cols*buf.rows*buf.elemSize();
|
|
}
|
|
|
|
static int close( thandle_t handle )
|
|
{
|
|
TiffDecoderBufHelper *helper = reinterpret_cast<TiffDecoderBufHelper*>(handle);
|
|
delete helper;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
bool TiffDecoder::readHeader()
|
|
{
|
|
bool result = false;
|
|
TIFF* tif = static_cast<TIFF*>(m_tif.get());
|
|
if (!tif)
|
|
{
|
|
// TIFFOpen() mode flags are different to fopen(). A 'b' in mode "rb" has no effect when reading.
|
|
// http://www.simplesystems.org/libtiff/functions/TIFFOpen.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<thandle_t>(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_t wdth = 0, hght = 0;
|
|
uint16_t 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_t 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_t 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 4:
|
|
//support 4-bit palette.
|
|
if (photometric == PHOTOMETRIC_PALETTE)
|
|
{
|
|
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, 3);
|
|
result = true;
|
|
}
|
|
else
|
|
CV_Error(cv::Error::StsError, "bitsperpixel value is 4 should be palette.");
|
|
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 10:
|
|
case 12:
|
|
case 14:
|
|
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, 10, 12, 14, 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<TIFF*>(m_tif.get())) &&
|
|
readHeader();
|
|
}
|
|
|
|
static void fixOrientationPartial(Mat &img, uint16_t 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_t orientation, bool isOrientationFull)
|
|
{
|
|
if( isOrientationFull )
|
|
{
|
|
fixOrientationFull(img, orientation);
|
|
}
|
|
else
|
|
{
|
|
fixOrientationPartial(img, orientation);
|
|
}
|
|
}
|
|
|
|
static void _unpack10To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements)
|
|
{
|
|
//5*8b=4*10b : 5 src for 4 dst
|
|
constexpr const size_t packedBitsCount = 10;
|
|
constexpr const size_t packedBitsMask = ((1<<packedBitsCount)-1);
|
|
constexpr const size_t srcElementsPerPacket = 5;
|
|
constexpr const size_t dstElementsPerPacket = 4;
|
|
constexpr const size_t bitsPerPacket = dstElementsPerPacket*packedBitsCount;
|
|
const size_t fullPacketsCount = std::min({
|
|
expectedDstElements/dstElementsPerPacket,
|
|
(static_cast<size_t>(srcEnd-src)/srcElementsPerPacket),
|
|
(static_cast<size_t>(dstEnd-dst)/dstElementsPerPacket)
|
|
});
|
|
union {
|
|
uint64_t u64;
|
|
uint8_t u8[8];
|
|
} buf = {0};
|
|
for(size_t i = 0 ; i<fullPacketsCount ; ++i)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = *src++;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
dst[dstElementsPerPacket-1-j] = static_cast<ushort>(buf.u64 & packedBitsMask);
|
|
buf.u64 >>= packedBitsCount;
|
|
}
|
|
dst += dstElementsPerPacket;
|
|
}
|
|
size_t remainingDstElements = std::min(
|
|
expectedDstElements-fullPacketsCount*dstElementsPerPacket,
|
|
static_cast<size_t>(dstEnd-dst)
|
|
);
|
|
bool stop = !remainingDstElements;
|
|
while(!stop)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = (src<srcEnd) ? *src++ : 0;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
stop |= !(remainingDstElements--);
|
|
if (!stop)
|
|
*dst++ = static_cast<ushort>((buf.u64 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask);
|
|
}
|
|
}//end while(!stop)
|
|
}
|
|
//end _unpack10To16()
|
|
|
|
static void _unpack12To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements)
|
|
{
|
|
//3*8b=2*12b : 3 src for 2 dst
|
|
constexpr const size_t packedBitsCount = 12;
|
|
constexpr const size_t packedBitsMask = ((1<<packedBitsCount)-1);
|
|
constexpr const size_t srcElementsPerPacket = 3;
|
|
constexpr const size_t dstElementsPerPacket = 2;
|
|
constexpr const size_t bitsPerPacket = dstElementsPerPacket*packedBitsCount;
|
|
const size_t fullPacketsCount = std::min({
|
|
expectedDstElements/dstElementsPerPacket,
|
|
(static_cast<size_t>(srcEnd-src)/srcElementsPerPacket),
|
|
(static_cast<size_t>(dstEnd-dst)/dstElementsPerPacket)
|
|
});
|
|
union {
|
|
uint32_t u32;
|
|
uint8_t u8[4];
|
|
} buf = {0};
|
|
for(size_t i = 0 ; i<fullPacketsCount ; ++i)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = *src++;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
dst[dstElementsPerPacket-1-j] = static_cast<ushort>(buf.u32 & packedBitsMask);
|
|
buf.u32 >>= packedBitsCount;
|
|
}
|
|
dst += dstElementsPerPacket;
|
|
}
|
|
size_t remainingDstElements = std::min(
|
|
expectedDstElements-fullPacketsCount*dstElementsPerPacket,
|
|
static_cast<size_t>(dstEnd-dst)
|
|
);
|
|
bool stop = !remainingDstElements;
|
|
while(!stop)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = (src<srcEnd) ? *src++ : 0;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
stop |= !(remainingDstElements--);
|
|
if (!stop)
|
|
*dst++ = static_cast<ushort>((buf.u32 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask);
|
|
}
|
|
}//end while(!stop)
|
|
}
|
|
//end _unpack12To16()
|
|
|
|
static void _unpack14To16(const uchar* src, const uchar* srcEnd, ushort* dst, ushort* dstEnd, size_t expectedDstElements)
|
|
{
|
|
//7*8b=4*14b : 7 src for 4 dst
|
|
constexpr const size_t packedBitsCount = 14;
|
|
constexpr const size_t packedBitsMask = ((1<<packedBitsCount)-1);
|
|
constexpr const size_t srcElementsPerPacket = 7;
|
|
constexpr const size_t dstElementsPerPacket = 4;
|
|
constexpr const size_t bitsPerPacket = dstElementsPerPacket*packedBitsCount;
|
|
const size_t fullPacketsCount = std::min({
|
|
expectedDstElements/dstElementsPerPacket,
|
|
(static_cast<size_t>(srcEnd-src)/srcElementsPerPacket),
|
|
(static_cast<size_t>(dstEnd-dst)/dstElementsPerPacket)
|
|
});
|
|
union {
|
|
uint64_t u64;
|
|
uint8_t u8[8];
|
|
} buf = {0};
|
|
for(size_t i = 0 ; i<fullPacketsCount ; ++i)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = *src++;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
dst[dstElementsPerPacket-1-j] = static_cast<ushort>(buf.u64 & packedBitsMask);
|
|
buf.u64 >>= packedBitsCount;
|
|
}
|
|
dst += dstElementsPerPacket;
|
|
}
|
|
size_t remainingDstElements = std::min(
|
|
expectedDstElements-fullPacketsCount*dstElementsPerPacket,
|
|
static_cast<size_t>(dstEnd-dst)
|
|
);
|
|
bool stop = !remainingDstElements;
|
|
while(!stop)
|
|
{
|
|
for(size_t j = 0 ; j<srcElementsPerPacket ; ++j)
|
|
buf.u8[srcElementsPerPacket-1-j] = (src<srcEnd) ? *src++ : 0;
|
|
for(size_t j = 0 ; j<dstElementsPerPacket ; ++j)
|
|
{
|
|
stop |= !(remainingDstElements--);
|
|
if (!stop)
|
|
*dst++ = static_cast<ushort>((buf.u64 >> (bitsPerPacket-(j+1)*packedBitsCount)) & packedBitsMask);
|
|
}
|
|
}//end while(!stop)
|
|
}
|
|
//end _unpack14To16()
|
|
|
|
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_t photometric = (uint16_t)-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_t 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_t img_orientation = ORIENTATION_TOPLEFT;
|
|
CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ORIENTATION, &img_orientation));
|
|
constexpr 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());
|
|
bool doReadScanline = false;
|
|
|
|
uint32_t 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<uint32_t>::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, "");
|
|
|
|
if (dst_bpp == 8)
|
|
{
|
|
const int _ncn = 4; // Read RGBA
|
|
const int _bpp = 8; // Read 8bit
|
|
|
|
// if buffer_size(as 32bit RGBA) >= MAX_TILE_SIZE*95%,
|
|
// we will use TIFFReadScanline function.
|
|
|
|
if (
|
|
(uint64_t)tile_width0 * tile_height0 * _ncn * std::max(1, (int)(_bpp / bitsPerByte))
|
|
>=
|
|
( (uint64_t) MAX_TILE_SIZE * 95 / 100)
|
|
)
|
|
{
|
|
uint16_t planerConfig = (uint16_t)-1;
|
|
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig));
|
|
|
|
doReadScanline = (!is_tiled) // no tile
|
|
&&
|
|
( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) )
|
|
&&
|
|
( ( bpp == 8 ) || ( bpp == 16 ) )
|
|
&&
|
|
(tile_height0 == (uint32_t) m_height) // single strip
|
|
&&
|
|
(
|
|
(photometric == PHOTOMETRIC_MINISWHITE)
|
|
||
|
|
(photometric == PHOTOMETRIC_MINISBLACK)
|
|
||
|
|
(photometric == PHOTOMETRIC_RGB)
|
|
)
|
|
&&
|
|
(planerConfig != PLANARCONFIG_SEPARATE);
|
|
|
|
// Currently only EXTRASAMPLE_ASSOCALPHA is supported.
|
|
if ( doReadScanline && ( ncn == 4 ) )
|
|
{
|
|
uint16_t extra_samples_num;
|
|
uint16_t *extra_samples = NULL;
|
|
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples ));
|
|
doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA );
|
|
}
|
|
}
|
|
|
|
if ( !doReadScanline )
|
|
{
|
|
// we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA
|
|
bpp = 8;
|
|
ncn = 4;
|
|
|
|
char errmsg[1024];
|
|
if (!TIFFRGBAImageOK(tif, errmsg))
|
|
{
|
|
CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg);
|
|
close();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (dst_bpp == 16)
|
|
{
|
|
// if buffer_size >= MAX_TILE_SIZE*95%,
|
|
// we will use TIFFReadScanline function.
|
|
if (
|
|
(uint64_t)tile_width0 * tile_height0 * ncn * std::max(1, (int)(bpp / bitsPerByte))
|
|
>=
|
|
MAX_TILE_SIZE * 95 / 100
|
|
)
|
|
{
|
|
uint16_t planerConfig = (uint16_t)-1;
|
|
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &planerConfig));
|
|
|
|
doReadScanline = (!is_tiled) // no tile
|
|
&&
|
|
( ( ncn == 1 ) || ( ncn == 3 ) || ( ncn == 4 ) )
|
|
&&
|
|
( ( bpp == 8 ) || ( bpp == 16 ) )
|
|
&&
|
|
(tile_height0 == (uint32_t) m_height) // single strip
|
|
&&
|
|
(
|
|
(photometric == PHOTOMETRIC_MINISWHITE)
|
|
||
|
|
(photometric == PHOTOMETRIC_MINISBLACK)
|
|
||
|
|
(photometric == PHOTOMETRIC_RGB)
|
|
)
|
|
&&
|
|
(planerConfig != PLANARCONFIG_SEPARATE);
|
|
|
|
// Currently only EXTRASAMPLE_ASSOCALPHA is supported.
|
|
if ( doReadScanline && ( ncn == 4 ) )
|
|
{
|
|
uint16_t extra_samples_num;
|
|
uint16_t *extra_samples = NULL;
|
|
CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_samples_num, &extra_samples ));
|
|
doReadScanline = ( extra_samples_num == 1 ) && ( extra_samples[0] == EXTRASAMPLE_ASSOCALPHA );
|
|
}
|
|
}
|
|
}
|
|
else if (dst_bpp == 32 || dst_bpp == 64)
|
|
{
|
|
CV_Assert(ncn == img.channels());
|
|
CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP));
|
|
}
|
|
|
|
if ( doReadScanline )
|
|
{
|
|
// Read each scanlines.
|
|
tile_height0 = 1;
|
|
}
|
|
|
|
const size_t src_buffer_bytes_per_row = divUp(static_cast<size_t>(ncn * tile_width0 * bpp), static_cast<size_t>(bitsPerByte));
|
|
const size_t src_buffer_size = tile_height0 * src_buffer_bytes_per_row;
|
|
CV_CheckLT(src_buffer_size, MAX_TILE_SIZE, "buffer_size is too large: >= 1Gb");
|
|
const size_t src_buffer_unpacked_bytes_per_row = divUp(static_cast<size_t>(ncn * tile_width0 * dst_bpp), static_cast<size_t>(bitsPerByte));
|
|
const size_t src_buffer_unpacked_size = tile_height0 * src_buffer_unpacked_bytes_per_row;
|
|
const bool needsUnpacking = (bpp < dst_bpp);
|
|
AutoBuffer<uchar> _src_buffer(src_buffer_size);
|
|
uchar* src_buffer = _src_buffer.data();
|
|
AutoBuffer<uchar> _src_buffer_unpacked(needsUnpacking ? src_buffer_unpacked_size : 0);
|
|
uchar* src_buffer_unpacked = needsUnpacking ? _src_buffer_unpacked.data() : nullptr;
|
|
|
|
if ( doReadScanline )
|
|
{
|
|
CV_CheckGE(src_buffer_size,
|
|
static_cast<size_t>(TIFFScanlineSize(tif)),
|
|
"src_buffer_size is smaller than TIFFScanlineSize().");
|
|
}
|
|
|
|
int tileidx = 0;
|
|
|
|
#define MAKE_FLAG(a,b) ( (a << 8) | b )
|
|
const int convert_flag = MAKE_FLAG( ncn, wanted_channels );
|
|
const bool isNeedConvert16to8 = ( doReadScanline ) && ( bpp == 16 ) && ( dst_bpp == 8);
|
|
|
|
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 = src_buffer;
|
|
if (doReadScanline)
|
|
{
|
|
CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32_t*)src_buffer, y) >= 0);
|
|
|
|
if ( isNeedConvert16to8 )
|
|
{
|
|
// Convert buffer image from 16bit to 8bit.
|
|
int ix;
|
|
for ( ix = 0 ; ix < tile_width * ncn - 4; ix += 4 )
|
|
{
|
|
src_buffer[ ix ] = src_buffer[ ix * 2 + 1 ];
|
|
src_buffer[ ix + 1 ] = src_buffer[ ix * 2 + 3 ];
|
|
src_buffer[ ix + 2 ] = src_buffer[ ix * 2 + 5 ];
|
|
src_buffer[ ix + 3 ] = src_buffer[ ix * 2 + 7 ];
|
|
}
|
|
|
|
for ( ; ix < tile_width * ncn ; ix ++ )
|
|
{
|
|
src_buffer[ ix ] = src_buffer[ ix * 2 + 1];
|
|
}
|
|
}
|
|
}
|
|
else if (!is_tiled)
|
|
{
|
|
CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32_t*)src_buffer));
|
|
}
|
|
else
|
|
{
|
|
CV_TIFF_CHECK_CALL(TIFFReadRGBATile(tif, x, y, (uint32_t*)src_buffer));
|
|
// Tiles fill the buffer from the bottom up
|
|
bstart += (tile_height0 - tile_height) * tile_width0 * 4;
|
|
}
|
|
|
|
uchar* img_line_buffer = (uchar*) img.ptr(y, 0);
|
|
|
|
for (int i = 0; i < tile_height; i++)
|
|
{
|
|
if (doReadScanline)
|
|
{
|
|
switch ( convert_flag )
|
|
{
|
|
case MAKE_FLAG( 1, 1 ): // GRAY to GRAY
|
|
memcpy( (void*) img_line_buffer,
|
|
(void*) bstart,
|
|
tile_width * sizeof(uchar) );
|
|
break;
|
|
|
|
case MAKE_FLAG( 1, 3 ): // GRAY to BGR
|
|
icvCvt_Gray2BGR_8u_C1C3R( bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1) );
|
|
break;
|
|
|
|
case MAKE_FLAG( 3, 1): // RGB to GRAY
|
|
icvCvt_BGR2Gray_8u_C3C1R( bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1) );
|
|
break;
|
|
|
|
case MAKE_FLAG( 3, 3 ): // RGB to BGR
|
|
if (m_use_rgb)
|
|
memcpy( (void*) img_line_buffer,
|
|
(void*) bstart,
|
|
tile_width * sizeof(uchar) );
|
|
else
|
|
icvCvt_BGR2RGB_8u_C3R( bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1) );
|
|
break;
|
|
|
|
case MAKE_FLAG( 4, 1 ): // RGBA to GRAY
|
|
icvCvt_BGRA2Gray_8u_C4C1R( bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1) );
|
|
break;
|
|
|
|
case MAKE_FLAG( 4, 3 ): // RGBA to BGR
|
|
icvCvt_BGRA2BGR_8u_C4C3R( bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1), m_use_rgb ? 0 : 2);
|
|
break;
|
|
|
|
case MAKE_FLAG( 4, 4 ): // RGBA to BGRA
|
|
icvCvt_BGRA2RGBA_8u_C4R(bstart, 0,
|
|
img_line_buffer, 0,
|
|
Size(tile_width, 1) );
|
|
break;
|
|
|
|
default:
|
|
CV_LOG_ONCE_ERROR(NULL, "OpenCV TIFF(line " << __LINE__ << "): Unsupported convertion :"
|
|
<< " bpp = " << bpp << " ncn = " << (int)ncn
|
|
<< " wanted_channels =" << wanted_channels );
|
|
break;
|
|
}
|
|
#undef MAKE_FLAG
|
|
}
|
|
else 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), m_use_rgb ? 0 : 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 (doReadScanline)
|
|
{
|
|
CV_TIFF_CHECK_CALL((int)TIFFReadScanline(tif, (uint32_t*)src_buffer, y) >= 0);
|
|
}
|
|
else if (!is_tiled)
|
|
{
|
|
CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32_t*)src_buffer, src_buffer_size) >= 0);
|
|
}
|
|
else
|
|
{
|
|
CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, (uint32_t*)src_buffer, src_buffer_size) >= 0);
|
|
}
|
|
|
|
for (int i = 0; i < tile_height; i++)
|
|
{
|
|
ushort* buffer16 = (ushort*)(src_buffer+i*src_buffer_bytes_per_row);
|
|
if (needsUnpacking)
|
|
{
|
|
const uchar* src_packed = src_buffer+i*src_buffer_bytes_per_row;
|
|
uchar* dst_unpacked = src_buffer_unpacked+i*src_buffer_unpacked_bytes_per_row;
|
|
if (bpp == 10)
|
|
_unpack10To16(src_packed, src_packed+src_buffer_bytes_per_row,
|
|
(ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row),
|
|
ncn * tile_width0);
|
|
else if (bpp == 12)
|
|
_unpack12To16(src_packed, src_packed+src_buffer_bytes_per_row,
|
|
(ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row),
|
|
ncn * tile_width0);
|
|
else if (bpp == 14)
|
|
_unpack14To16(src_packed, src_packed+src_buffer_bytes_per_row,
|
|
(ushort*)dst_unpacked, (ushort*)(dst_unpacked+src_buffer_unpacked_bytes_per_row),
|
|
ncn * tile_width0);
|
|
buffer16 = (ushort*)dst_unpacked;
|
|
}
|
|
|
|
if (color)
|
|
{
|
|
if (ncn == 1)
|
|
{
|
|
CV_CheckEQ(wanted_channels, 3, "");
|
|
icvCvt_Gray2BGR_16u_C1C3R(buffer16, 0,
|
|
img.ptr<ushort>(img_y + i, x), 0,
|
|
Size(tile_width, 1));
|
|
}
|
|
else if (ncn == 3)
|
|
{
|
|
CV_CheckEQ(wanted_channels, 3, "");
|
|
if (m_use_rgb)
|
|
memcpy(buffer16, img.ptr<ushort>(img_y + i, x), tile_width * sizeof(ushort));
|
|
else
|
|
icvCvt_RGB2BGR_16u_C3R(buffer16, 0,
|
|
img.ptr<ushort>(img_y + i, x), 0,
|
|
Size(tile_width, 1));
|
|
}
|
|
else if (ncn == 4)
|
|
{
|
|
if (wanted_channels == 4)
|
|
{
|
|
icvCvt_BGRA2RGBA_16u_C4R(buffer16, 0,
|
|
img.ptr<ushort>(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, 0,
|
|
img.ptr<ushort>(img_y + i, x), 0,
|
|
Size(tile_width, 1), m_use_rgb ? 0 : 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CV_Error(Error::StsError, "Not supported");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CV_CheckEQ(wanted_channels, 1, "");
|
|
if( ncn == 1 )
|
|
{
|
|
memcpy(img.ptr<ushort>(img_y + i, x),
|
|
buffer16,
|
|
tile_width*sizeof(ushort));
|
|
}
|
|
else
|
|
{
|
|
icvCvt_BGRA2Gray_16u_CnC1R(buffer16, 0,
|
|
img.ptr<ushort>(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, src_buffer, src_buffer_size) >= 0);
|
|
}
|
|
else
|
|
{
|
|
CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, src_buffer, src_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), src_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 && !m_use_rgb)
|
|
extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR);
|
|
else if (!m_hdr && ncn == 4)
|
|
extend_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
|
|
}
|
|
if (bpp < dst_bpp)
|
|
img *= (1<<(dst_bpp-bpp));
|
|
|
|
// If TIFFReadRGBA* function is used -> fixOrientationPartial().
|
|
// Otherwise -> fixOrientationFull().
|
|
fixOrientation(img, img_orientation,
|
|
( ( dst_bpp != 8 ) && ( !doReadScanline ) ) );
|
|
}
|
|
|
|
if (m_hdr && depth >= CV_32F)
|
|
{
|
|
CV_Assert(photometric == PHOTOMETRIC_LOGLUV);
|
|
if (m_use_rgb)
|
|
cvtColor(img, img, COLOR_XYZ2RGB);
|
|
else
|
|
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
|
|
{
|
|
cv_tiffSetErrorHandler();
|
|
return makePtr<TiffEncoder>();
|
|
}
|
|
|
|
bool TiffEncoder::isFormatSupported( int depth ) const
|
|
{
|
|
return depth == CV_8U || depth == CV_8S || depth == CV_16U || depth == CV_16S || depth == CV_32S || 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<uchar> *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.simplesystems.org/libtiff/functions/TIFFOpen.html
|
|
return TIFFClientOpen( "", "w", reinterpret_cast<thandle_t>(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<TiffEncoderBufHelper*>(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<TiffEncoderBufHelper*>(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<TiffEncoderBufHelper*>(handle);
|
|
return helper->m_buf->size();
|
|
}
|
|
|
|
static int close( thandle_t /*handle*/ )
|
|
{
|
|
// Do nothing.
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
|
|
std::vector<uchar>* m_buf;
|
|
toff_t m_buf_pos;
|
|
};
|
|
|
|
static bool readParam(const std::vector<int>& 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<Mat>& img_vec, const std::vector<int>& params)
|
|
{
|
|
// do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode.
|
|
// http://www.simplesystems.org/libtiff/functions/TIFFOpen.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<void> 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, IMWRITE_TIFF_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_8S || depth == CV_16U || depth == CV_16S || depth == CV_32S || 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;
|
|
uint16_t sample_format = SAMPLEFORMAT_INT;
|
|
switch (depth)
|
|
{
|
|
case CV_8U:
|
|
sample_format = SAMPLEFORMAT_UINT;
|
|
/* FALLTHRU */
|
|
case CV_8S:
|
|
{
|
|
bitsPerChannel = 8;
|
|
break;
|
|
}
|
|
|
|
case CV_16U:
|
|
sample_format = SAMPLEFORMAT_UINT;
|
|
/* FALLTHRU */
|
|
case CV_16S:
|
|
{
|
|
bitsPerChannel = 16;
|
|
break;
|
|
}
|
|
|
|
case CV_32S:
|
|
{
|
|
bitsPerChannel = 32;
|
|
sample_format = SAMPLEFORMAT_INT;
|
|
break;
|
|
}
|
|
case CV_32F:
|
|
{
|
|
bitsPerChannel = 32;
|
|
page_compression = COMPRESSION_NONE;
|
|
sample_format = SAMPLEFORMAT_IEEEFP;
|
|
break;
|
|
}
|
|
case CV_64F:
|
|
{
|
|
bitsPerChannel = 64;
|
|
page_compression = COMPRESSION_NONE;
|
|
sample_format = SAMPLEFORMAT_IEEEFP;
|
|
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, IMWRITE_TIFF_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, sample_format));
|
|
|
|
if (page_compression == COMPRESSION_LZW || page_compression == COMPRESSION_ADOBE_DEFLATE || page_compression == COMPRESSION_DEFLATE)
|
|
{
|
|
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<uchar> _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:
|
|
{
|
|
extend_cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB);
|
|
break;
|
|
}
|
|
|
|
case 4:
|
|
{
|
|
extend_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<float>(i), strip_size * sizeof(float)) != (tsize_t)-1);
|
|
}
|
|
CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif));
|
|
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 type = img.type();
|
|
int depth = CV_MAT_DEPTH(type);
|
|
|
|
CV_CheckType(type, depth == CV_8U || depth == CV_8S || depth == CV_16U || depth == CV_16S || depth == CV_32S || depth == CV_32F || depth == CV_64F, "");
|
|
|
|
std::vector<Mat> img_vec;
|
|
img_vec.push_back(img);
|
|
return writeLibTiff(img_vec, params);
|
|
}
|
|
|
|
static void extend_cvtColor( InputArray _src, OutputArray _dst, int code )
|
|
{
|
|
CV_Assert( !_src.empty() );
|
|
CV_Assert( _src.dims() == 2 );
|
|
|
|
// This function extend_cvtColor reorders the src channels with only thg limited condition.
|
|
// Otherwise, it calls cvtColor.
|
|
|
|
const int stype = _src.type();
|
|
if(!
|
|
(
|
|
(
|
|
( stype == CV_8SC3 ) || ( stype == CV_8SC4 ) ||
|
|
( stype == CV_16SC3 ) || ( stype == CV_16SC4 ) ||
|
|
( stype == CV_32SC3 ) || ( stype == CV_32SC4 ) ||
|
|
( stype == CV_64FC3 ) || ( stype == CV_64FC4 )
|
|
)
|
|
&&
|
|
(
|
|
( code == COLOR_BGR2RGB ) || ( code == COLOR_BGRA2RGBA )
|
|
)
|
|
)
|
|
)
|
|
{
|
|
cvtColor( _src, _dst, code );
|
|
return;
|
|
}
|
|
|
|
Mat src = _src.getMat();
|
|
|
|
// cv::mixChannels requires the output arrays to be pre-allocated before calling the function.
|
|
_dst.create( _src.size(), stype );
|
|
Mat dst = _dst.getMat();
|
|
|
|
// BGR to RGB or BGRA to RGBA
|
|
// src[0] -> dst[2]
|
|
// src[1] -> dst[1]
|
|
// src[2] -> dst[0]
|
|
// src[3] -> dst[3] if src has alpha channel.
|
|
std::vector<int> fromTo;
|
|
fromTo.push_back(0); fromTo.push_back(2);
|
|
fromTo.push_back(1); fromTo.push_back(1);
|
|
fromTo.push_back(2); fromTo.push_back(0);
|
|
if ( code == COLOR_BGRA2RGBA )
|
|
{
|
|
fromTo.push_back(3); fromTo.push_back(3);
|
|
}
|
|
|
|
cv::mixChannels( src, dst, fromTo );
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#endif
|