2012-01-31 06:01:22 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
2016-01-19 11:57:12 +00:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2012-01-31 06:01:22 +00:00
|
|
|
**
|
|
|
|
** This file is part of the plugins of the Qt Toolkit.
|
|
|
|
**
|
2016-01-19 11:57:12 +00:00
|
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
2012-09-21 06:41:54 +00:00
|
|
|
** Commercial License Usage
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2015-01-28 11:42:13 +00:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
2016-01-19 11:57:12 +00:00
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2012-09-21 06:41:54 +00:00
|
|
|
**
|
2012-01-31 06:01:22 +00:00
|
|
|
** GNU Lesser General Public License Usage
|
2012-09-21 06:41:54 +00:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
2016-01-19 11:57:12 +00:00
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
2012-01-31 06:01:22 +00:00
|
|
|
**
|
2016-01-19 11:57:12 +00:00
|
|
|
** GNU General Public License Usage
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
2012-01-31 06:01:22 +00:00
|
|
|
**
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include "qtiffhandler_p.h"
|
2021-05-31 08:56:52 +00:00
|
|
|
|
2019-07-24 16:27:46 +00:00
|
|
|
#include <qcolorspace.h>
|
2012-01-31 06:01:22 +00:00
|
|
|
#include <qdebug.h>
|
2021-05-31 08:56:52 +00:00
|
|
|
#include <qfloat16.h>
|
2012-01-31 06:01:22 +00:00
|
|
|
#include <qimage.h>
|
2021-05-31 08:56:52 +00:00
|
|
|
#include <qvariant.h>
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
extern "C" {
|
|
|
|
#include "tiffio.h"
|
|
|
|
}
|
|
|
|
|
2018-06-21 14:52:41 +00:00
|
|
|
#include <memory>
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
|
|
|
tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
|
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
QIODevice *device = static_cast<QIODevice *>(fd);
|
2012-01-31 06:01:22 +00:00
|
|
|
return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
|
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
return static_cast<QIODevice *>(fd)->write(static_cast<char *>(buf), size);
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
|
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
QIODevice *device = static_cast<QIODevice *>(fd);
|
2012-01-31 06:01:22 +00:00
|
|
|
switch (whence) {
|
|
|
|
case SEEK_SET:
|
|
|
|
device->seek(off);
|
|
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
|
|
device->seek(device->pos() + off);
|
|
|
|
break;
|
|
|
|
case SEEK_END:
|
|
|
|
device->seek(device->size() + off);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return device->pos();
|
|
|
|
}
|
|
|
|
|
|
|
|
int qtiffCloseProc(thandle_t /*fd*/)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
toff_t qtiffSizeProc(thandle_t fd)
|
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
return static_cast<QIODevice *>(fd)->size();
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
class QTiffHandlerPrivate
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
QTiffHandlerPrivate();
|
|
|
|
~QTiffHandlerPrivate();
|
|
|
|
|
|
|
|
static bool canRead(QIODevice *device);
|
|
|
|
bool openForRead(QIODevice *device);
|
|
|
|
bool readHeaders(QIODevice *device);
|
|
|
|
void close();
|
|
|
|
|
|
|
|
TIFF *tiff;
|
|
|
|
int compression;
|
2015-04-29 08:55:27 +00:00
|
|
|
QImageIOHandler::Transformations transformation;
|
2015-04-20 09:27:10 +00:00
|
|
|
QImage::Format format;
|
|
|
|
QSize size;
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t photometric;
|
2015-04-20 09:27:10 +00:00
|
|
|
bool grayscale;
|
2021-05-31 08:56:52 +00:00
|
|
|
bool floatingPoint;
|
2015-04-20 09:27:10 +00:00
|
|
|
bool headersRead;
|
2017-01-01 22:49:35 +00:00
|
|
|
int currentDirectory;
|
|
|
|
int directoryCount;
|
2015-04-20 09:27:10 +00:00
|
|
|
};
|
|
|
|
|
2015-04-29 08:55:27 +00:00
|
|
|
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
|
|
|
|
{
|
|
|
|
switch (exifOrientation) {
|
|
|
|
case 1: // normal
|
|
|
|
return QImageIOHandler::TransformationNone;
|
|
|
|
case 2: // mirror horizontal
|
|
|
|
return QImageIOHandler::TransformationMirror;
|
|
|
|
case 3: // rotate 180
|
|
|
|
return QImageIOHandler::TransformationRotate180;
|
|
|
|
case 4: // mirror vertical
|
|
|
|
return QImageIOHandler::TransformationFlip;
|
|
|
|
case 5: // mirror horizontal and rotate 270 CW
|
|
|
|
return QImageIOHandler::TransformationFlipAndRotate90;
|
|
|
|
case 6: // rotate 90 CW
|
|
|
|
return QImageIOHandler::TransformationRotate90;
|
|
|
|
case 7: // mirror horizontal and rotate 90 CW
|
|
|
|
return QImageIOHandler::TransformationMirrorAndRotate90;
|
|
|
|
case 8: // rotate 270 CW
|
|
|
|
return QImageIOHandler::TransformationRotate270;
|
|
|
|
}
|
|
|
|
qWarning("Invalid EXIF orientation");
|
|
|
|
return QImageIOHandler::TransformationNone;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qt2Exif(QImageIOHandler::Transformations transformation)
|
|
|
|
{
|
|
|
|
switch (transformation) {
|
|
|
|
case QImageIOHandler::TransformationNone:
|
|
|
|
return 1;
|
|
|
|
case QImageIOHandler::TransformationMirror:
|
|
|
|
return 2;
|
|
|
|
case QImageIOHandler::TransformationRotate180:
|
|
|
|
return 3;
|
|
|
|
case QImageIOHandler::TransformationFlip:
|
|
|
|
return 4;
|
|
|
|
case QImageIOHandler::TransformationFlipAndRotate90:
|
|
|
|
return 5;
|
|
|
|
case QImageIOHandler::TransformationRotate90:
|
|
|
|
return 6;
|
|
|
|
case QImageIOHandler::TransformationMirrorAndRotate90:
|
|
|
|
return 7;
|
|
|
|
case QImageIOHandler::TransformationRotate270:
|
|
|
|
return 8;
|
|
|
|
}
|
|
|
|
qWarning("Invalid Qt image transformation");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
QTiffHandlerPrivate::QTiffHandlerPrivate()
|
|
|
|
: tiff(0)
|
|
|
|
, compression(QTiffHandler::NoCompression)
|
2015-04-29 08:55:27 +00:00
|
|
|
, transformation(QImageIOHandler::TransformationNone)
|
2015-04-20 09:27:10 +00:00
|
|
|
, format(QImage::Format_Invalid)
|
|
|
|
, photometric(false)
|
|
|
|
, grayscale(false)
|
|
|
|
, headersRead(false)
|
2017-01-01 22:49:35 +00:00
|
|
|
, currentDirectory(0)
|
|
|
|
, directoryCount(0)
|
2012-01-31 06:01:22 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
QTiffHandlerPrivate::~QTiffHandlerPrivate()
|
2012-01-31 06:01:22 +00:00
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
close();
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
void QTiffHandlerPrivate::close()
|
|
|
|
{
|
|
|
|
if (tiff)
|
|
|
|
TIFFClose(tiff);
|
|
|
|
tiff = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandlerPrivate::canRead(QIODevice *device)
|
2012-01-31 06:01:22 +00:00
|
|
|
{
|
|
|
|
if (!device) {
|
|
|
|
qWarning("QTiffHandler::canRead() called with no device");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// current implementation uses TIFFClientOpen which needs to be
|
|
|
|
// able to seek, so sequential devices are not supported
|
2019-12-19 13:10:54 +00:00
|
|
|
char h[4];
|
|
|
|
if (device->peek(h, 4) != 4)
|
|
|
|
return false;
|
|
|
|
if ((h[0] == 0x49 && h[1] == 0x49) && (h[2] == 0x2a || h[2] == 0x2b) && h[3] == 0)
|
|
|
|
return true; // Little endian, classic or bigtiff
|
|
|
|
if ((h[0] == 0x4d && h[1] == 0x4d) && h[2] == 0 && (h[3] == 0x2a || h[3] == 0x2b))
|
|
|
|
return true; // Big endian, classic or bigtiff
|
|
|
|
return false;
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
bool QTiffHandlerPrivate::openForRead(QIODevice *device)
|
2012-01-31 06:01:22 +00:00
|
|
|
{
|
2015-04-20 09:27:10 +00:00
|
|
|
if (tiff)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!canRead(device))
|
2012-01-31 06:01:22 +00:00
|
|
|
return false;
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
tiff = TIFFClientOpen("foo",
|
|
|
|
"r",
|
|
|
|
device,
|
|
|
|
qtiffReadProc,
|
|
|
|
qtiffWriteProc,
|
|
|
|
qtiffSeekProc,
|
|
|
|
qtiffCloseProc,
|
|
|
|
qtiffSizeProc,
|
|
|
|
qtiffMapProc,
|
|
|
|
qtiffUnmapProc);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
|
|
|
if (!tiff) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-01 22:49:35 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandlerPrivate::readHeaders(QIODevice *device)
|
|
|
|
{
|
|
|
|
if (headersRead)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (!openForRead(device))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
TIFFSetDirectory(tiff, currentDirectory);
|
|
|
|
|
2021-05-04 12:09:43 +00:00
|
|
|
uint32_t width;
|
|
|
|
uint32_t height;
|
2012-01-31 06:01:22 +00:00
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
|
|
|
|
|| !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
|
|
|
|
|| !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
|
2015-04-20 09:27:10 +00:00
|
|
|
close();
|
2012-01-31 06:01:22 +00:00
|
|
|
return false;
|
|
|
|
}
|
2015-04-20 09:27:10 +00:00
|
|
|
size = QSize(width, height);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t orientationTag;
|
2015-04-29 08:55:27 +00:00
|
|
|
if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
|
|
|
|
transformation = exif2Qt(orientationTag);
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
// BitsPerSample defaults to 1 according to the TIFF spec.
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t bitPerSample;
|
2012-01-31 06:01:22 +00:00
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample))
|
|
|
|
bitPerSample = 1;
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel
|
2012-01-31 06:01:22 +00:00
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
|
|
|
|
samplesPerPixel = 1;
|
2021-05-31 08:56:52 +00:00
|
|
|
uint16_t sampleFormat;
|
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat))
|
|
|
|
sampleFormat = SAMPLEFORMAT_VOID;
|
|
|
|
floatingPoint = (sampleFormat == SAMPLEFORMAT_IEEEFP);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE;
|
|
|
|
|
|
|
|
if (grayscale && bitPerSample == 1 && samplesPerPixel == 1)
|
|
|
|
format = QImage::Format_Mono;
|
2015-04-21 11:07:11 +00:00
|
|
|
else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1)
|
|
|
|
format = QImage::Format_Grayscale8;
|
2021-05-31 08:56:52 +00:00
|
|
|
else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1 && !floatingPoint)
|
2018-12-11 09:48:15 +00:00
|
|
|
format = QImage::Format_Grayscale16;
|
2015-04-20 09:27:10 +00:00
|
|
|
else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1)
|
|
|
|
format = QImage::Format_Indexed8;
|
|
|
|
else if (samplesPerPixel < 4)
|
2020-02-24 11:00:21 +00:00
|
|
|
if (bitPerSample == 16 && photometric == PHOTOMETRIC_RGB)
|
2021-05-31 08:56:52 +00:00
|
|
|
format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
|
|
|
|
else if (bitPerSample == 32 && floatingPoint && photometric == PHOTOMETRIC_RGB)
|
|
|
|
format = QImage::Format_RGBX32FPx4;
|
2018-06-21 14:52:41 +00:00
|
|
|
else
|
|
|
|
format = QImage::Format_RGB32;
|
2016-02-10 09:46:00 +00:00
|
|
|
else {
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t count;
|
|
|
|
uint16_t *extrasamples;
|
2016-02-10 09:46:00 +00:00
|
|
|
// If there is any definition of the alpha-channel, libtiff will return premultiplied
|
|
|
|
// data to us. If there is none, libtiff will not touch it and we assume it to be
|
|
|
|
// non-premultiplied, matching behavior of tested image editors, and how older Qt
|
|
|
|
// versions used to save it.
|
2018-06-21 14:52:41 +00:00
|
|
|
bool premultiplied = true;
|
2016-02-10 09:46:00 +00:00
|
|
|
bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
|
|
|
|
if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
|
2018-06-21 14:52:41 +00:00
|
|
|
premultiplied = false;
|
|
|
|
|
2020-02-24 11:00:21 +00:00
|
|
|
if (bitPerSample == 16 && photometric == PHOTOMETRIC_RGB) {
|
2018-06-21 14:52:41 +00:00
|
|
|
// We read 64-bit raw, so unassoc remains unpremultiplied.
|
|
|
|
if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
|
|
|
|
premultiplied = false;
|
|
|
|
if (premultiplied)
|
2021-05-31 08:56:52 +00:00
|
|
|
format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied;
|
|
|
|
else
|
|
|
|
format = floatingPoint ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
|
|
|
|
} else if (bitPerSample == 32 && floatingPoint && photometric == PHOTOMETRIC_RGB) {
|
|
|
|
if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
|
|
|
|
premultiplied = false;
|
|
|
|
if (premultiplied)
|
|
|
|
format = QImage::Format_RGBA32FPx4_Premultiplied;
|
2018-06-21 14:52:41 +00:00
|
|
|
else
|
2021-05-31 08:56:52 +00:00
|
|
|
format = QImage::Format_RGBA32FPx4;
|
2018-06-21 14:52:41 +00:00
|
|
|
} else {
|
|
|
|
if (premultiplied)
|
|
|
|
format = QImage::Format_ARGB32_Premultiplied;
|
|
|
|
else
|
|
|
|
format = QImage::Format_ARGB32;
|
|
|
|
}
|
2016-02-10 09:46:00 +00:00
|
|
|
}
|
2015-04-20 09:27:10 +00:00
|
|
|
|
|
|
|
headersRead = true;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QTiffHandler::QTiffHandler()
|
|
|
|
: QImageIOHandler()
|
|
|
|
, d(new QTiffHandlerPrivate)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandler::canRead() const
|
|
|
|
{
|
|
|
|
if (d->tiff)
|
|
|
|
return true;
|
|
|
|
if (QTiffHandlerPrivate::canRead(device())) {
|
|
|
|
setFormat("tiff");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandler::canRead(QIODevice *device)
|
|
|
|
{
|
|
|
|
return QTiffHandlerPrivate::canRead(device);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandler::read(QImage *image)
|
|
|
|
{
|
|
|
|
// Open file and read headers if it hasn't already been done.
|
2017-01-01 22:49:35 +00:00
|
|
|
if (!d->readHeaders(device()))
|
2015-04-20 09:27:10 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
QImage::Format format = d->format;
|
2018-06-21 14:52:41 +00:00
|
|
|
|
2021-03-01 16:27:21 +00:00
|
|
|
if (!QImageIOHandler::allocateImage(d->size, format, image)) {
|
2017-03-16 13:13:57 +00:00
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-20 09:27:10 +00:00
|
|
|
TIFF *const tiff = d->tiff;
|
2017-05-31 18:58:35 +00:00
|
|
|
const quint32 width = d->size.width();
|
|
|
|
const quint32 height = d->size.height();
|
2015-04-20 09:27:10 +00:00
|
|
|
|
2018-06-21 14:52:41 +00:00
|
|
|
// Setup color tables
|
|
|
|
if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
|
2017-03-16 13:13:57 +00:00
|
|
|
if (format == QImage::Format_Mono) {
|
2020-06-08 06:55:35 +00:00
|
|
|
QList<QRgb> colortable(2);
|
2017-03-16 13:13:57 +00:00
|
|
|
if (d->photometric == PHOTOMETRIC_MINISBLACK) {
|
|
|
|
colortable[0] = 0xff000000;
|
|
|
|
colortable[1] = 0xffffffff;
|
|
|
|
} else {
|
|
|
|
colortable[0] = 0xffffffff;
|
|
|
|
colortable[1] = 0xff000000;
|
|
|
|
}
|
|
|
|
image->setColorTable(colortable);
|
|
|
|
} else if (format == QImage::Format_Indexed8) {
|
2021-05-04 12:09:43 +00:00
|
|
|
const uint16_t tableSize = 256;
|
2020-06-08 06:55:35 +00:00
|
|
|
QList<QRgb> qtColorTable(tableSize);
|
2017-03-16 13:13:57 +00:00
|
|
|
if (d->grayscale) {
|
|
|
|
for (int i = 0; i<tableSize; ++i) {
|
|
|
|
const int c = (d->photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
|
|
|
|
qtColorTable[i] = qRgb(c, c, c);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create the color table
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t *redTable = 0;
|
|
|
|
uint16_t *greenTable = 0;
|
|
|
|
uint16_t *blueTable = 0;
|
2017-03-16 13:13:57 +00:00
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) {
|
2015-04-20 09:27:10 +00:00
|
|
|
d->close();
|
2012-01-31 06:01:22 +00:00
|
|
|
return false;
|
|
|
|
}
|
2017-03-16 13:13:57 +00:00
|
|
|
if (!redTable || !greenTable || !blueTable) {
|
|
|
|
d->close();
|
|
|
|
return false;
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
2017-03-16 13:13:57 +00:00
|
|
|
for (int i = 0; i<tableSize ;++i) {
|
Tiff: Align 16 to 8 bit colormap conversion to libtiff
For paletted images, tiff stores a color map with 16 bit deep
entries. When reading such images, the tiff handler tried to be clever
in the 16 to 8 bit mapping, but this resulted in slightly different
result than what libtiff itself produces if asked to read and convert
such an image (TIFFReadRGBAImageOriented()). libtiff simply ignores
the lower 8 bits, so we should do the same.
Importantly, this makes no difference when 8 bit original data is
stored in the orthodox 16 bit way, where e.g. 0xAB is stored as 0xABAB
- like we do. However, the alternative storages 0xAB00 and 0xABFF
exist in the wild, even in sample images in Qt repos.
Also, if we later should want to support proper 16 bit data here, the
previous code was anyway wrong: just dividing with 257 is highly
unorthodox. The correct way would be to use proper rounding like
QRgba64::toArgb32().
Fixes: QTBUG-79522
Change-Id: I7bd90ad7b89a923bd431781f4927b13ad0544407
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2019-10-28 13:53:58 +00:00
|
|
|
// emulate libtiff behavior for 16->8 bit color map conversion: just ignore the lower 8 bits
|
|
|
|
const int red = redTable[i] >> 8;
|
|
|
|
const int green = greenTable[i] >> 8;
|
|
|
|
const int blue = blueTable[i] >> 8;
|
2017-03-16 13:13:57 +00:00
|
|
|
qtColorTable[i] = qRgb(red, green, blue);
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
2017-03-16 13:13:57 +00:00
|
|
|
}
|
|
|
|
image->setColorTable(qtColorTable);
|
|
|
|
// free redTable, greenTable and greenTable done by libtiff
|
|
|
|
}
|
2018-06-21 14:52:41 +00:00
|
|
|
}
|
|
|
|
bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
|
2018-12-11 09:48:15 +00:00
|
|
|
bool format16bit = (format == QImage::Format_Grayscale16);
|
2018-06-21 14:52:41 +00:00
|
|
|
bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
|
2021-05-31 08:56:52 +00:00
|
|
|
bool format64fp = (format == QImage::Format_RGBX16FPx4 || format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA16FPx4_Premultiplied);
|
|
|
|
bool format128fp = (format == QImage::Format_RGBX32FPx4 || format == QImage::Format_RGBA32FPx4 || format == QImage::Format_RGBA32FPx4_Premultiplied);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
2018-12-11 09:48:15 +00:00
|
|
|
// Formats we read directly, instead of over RGBA32:
|
2021-05-31 08:56:52 +00:00
|
|
|
if (format8bit || format16bit || format64bit || format64fp || format128fp) {
|
2018-06-21 14:52:41 +00:00
|
|
|
int bytesPerPixel = image->depth() / 8;
|
2021-05-31 08:56:52 +00:00
|
|
|
if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
|
2018-06-21 14:52:41 +00:00
|
|
|
bytesPerPixel = 6;
|
2021-05-31 08:56:52 +00:00
|
|
|
else if (format == QImage::Format_RGBX32FPx4)
|
|
|
|
bytesPerPixel = 12;
|
2017-03-16 13:13:57 +00:00
|
|
|
if (TIFFIsTiled(tiff)) {
|
|
|
|
quint32 tileWidth, tileLength;
|
|
|
|
TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
|
|
|
|
TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength);
|
2021-02-24 13:55:10 +00:00
|
|
|
if (!tileWidth || !tileLength || tileWidth % 16 || tileLength % 16) {
|
2017-03-16 13:13:57 +00:00
|
|
|
d->close();
|
|
|
|
return false;
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
2018-06-21 14:52:41 +00:00
|
|
|
quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
|
|
|
|
quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
|
2021-02-24 13:55:10 +00:00
|
|
|
tmsize_t byteTileSize = TIFFTileSize(tiff);
|
2021-03-01 16:27:21 +00:00
|
|
|
if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth) {
|
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
2021-02-24 13:55:10 +00:00
|
|
|
uchar *buf = (uchar *)_TIFFmalloc(byteTileSize);
|
2021-03-01 16:27:21 +00:00
|
|
|
if (!buf) {
|
2021-02-24 13:55:10 +00:00
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-16 13:13:57 +00:00
|
|
|
for (quint32 y = 0; y < height; y += tileLength) {
|
|
|
|
for (quint32 x = 0; x < width; x += tileWidth) {
|
|
|
|
if (TIFFReadTile(tiff, buf, x, y, 0, 0) < 0) {
|
|
|
|
_TIFFfree(buf);
|
2015-04-21 11:07:11 +00:00
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-16 13:13:57 +00:00
|
|
|
quint32 linesToCopy = qMin(tileLength, height - y);
|
2018-06-21 14:52:41 +00:00
|
|
|
quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
|
2017-03-16 13:13:57 +00:00
|
|
|
quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
|
|
|
|
for (quint32 i = 0; i < linesToCopy; i++) {
|
|
|
|
::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
|
|
|
|
}
|
2015-04-21 11:07:11 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-16 13:13:57 +00:00
|
|
|
_TIFFfree(buf);
|
2012-01-31 06:01:22 +00:00
|
|
|
} else {
|
2021-03-02 15:57:15 +00:00
|
|
|
if (image->bytesPerLine() < TIFFScanlineSize(tiff)) {
|
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
2021-05-04 12:09:43 +00:00
|
|
|
for (uint32_t y=0; y<height; ++y) {
|
2017-03-16 13:13:57 +00:00
|
|
|
if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
|
2015-04-20 09:27:10 +00:00
|
|
|
d->close();
|
2012-01-31 06:01:22 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-31 08:56:52 +00:00
|
|
|
if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
|
|
|
|
rgb48fixup(image, d->floatingPoint);
|
|
|
|
else if (format == QImage::Format_RGBX32FPx4)
|
|
|
|
rgb96fixup(image);
|
2017-03-16 13:13:57 +00:00
|
|
|
} else {
|
|
|
|
const int stopOnError = 1;
|
2021-05-04 12:09:43 +00:00
|
|
|
if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32_t *>(image->bits()), qt2Exif(d->transformation), stopOnError)) {
|
|
|
|
for (uint32_t y=0; y<height; ++y)
|
2017-03-16 13:13:57 +00:00
|
|
|
convert32BitOrder(image->scanLine(y), width);
|
|
|
|
} else {
|
|
|
|
d->close();
|
|
|
|
return false;
|
|
|
|
}
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
float resX = 0;
|
|
|
|
float resY = 0;
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t resUnit;
|
2013-02-04 14:03:05 +00:00
|
|
|
if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
|
|
|
|
resUnit = RESUNIT_INCH;
|
|
|
|
|
|
|
|
if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
|
2012-01-31 06:01:22 +00:00
|
|
|
&& TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
|
|
|
|
|
|
|
|
switch(resUnit) {
|
|
|
|
case RESUNIT_CENTIMETER:
|
|
|
|
image->setDotsPerMeterX(qRound(resX * 100));
|
|
|
|
image->setDotsPerMeterY(qRound(resY * 100));
|
|
|
|
break;
|
|
|
|
case RESUNIT_INCH:
|
|
|
|
image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
|
|
|
|
image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// do nothing as defaults have already
|
|
|
|
// been set within the QImage class
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-04 12:09:43 +00:00
|
|
|
uint32_t count;
|
2019-07-24 16:27:46 +00:00
|
|
|
void *profile;
|
|
|
|
if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &count, &profile)) {
|
|
|
|
QByteArray iccProfile(reinterpret_cast<const char *>(profile), count);
|
|
|
|
image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
|
|
|
|
}
|
|
|
|
// We do not handle colorimetric metadat not on ICC profile form, it seems to be a lot
|
|
|
|
// less common, and would need additional API in QColorSpace.
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-08 06:55:35 +00:00
|
|
|
static bool checkGrayscale(const QList<QRgb> &colorTable)
|
2012-01-31 06:01:22 +00:00
|
|
|
{
|
|
|
|
if (colorTable.size() != 256)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const bool increasing = (colorTable.at(0) == 0xff000000);
|
|
|
|
for (int i = 0; i < 256; ++i) {
|
|
|
|
if ((increasing && colorTable.at(i) != qRgb(i, i, i))
|
|
|
|
|| (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i)))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-06-08 06:55:35 +00:00
|
|
|
static QList<QRgb> effectiveColorTable(const QImage &image)
|
2015-04-21 11:07:11 +00:00
|
|
|
{
|
2020-06-08 06:55:35 +00:00
|
|
|
QList<QRgb> colors;
|
2015-04-21 11:07:11 +00:00
|
|
|
switch (image.format()) {
|
|
|
|
case QImage::Format_Indexed8:
|
|
|
|
colors = image.colorTable();
|
|
|
|
break;
|
|
|
|
case QImage::Format_Alpha8:
|
|
|
|
colors.resize(256);
|
|
|
|
for (int i = 0; i < 256; ++i)
|
|
|
|
colors[i] = qRgba(0, 0, 0, i);
|
|
|
|
break;
|
|
|
|
case QImage::Format_Grayscale8:
|
2018-12-11 09:48:15 +00:00
|
|
|
case QImage::Format_Grayscale16:
|
2015-04-21 11:07:11 +00:00
|
|
|
colors.resize(256);
|
|
|
|
for (int i = 0; i < 256; ++i)
|
|
|
|
colors[i] = qRgb(i, i, i);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
}
|
|
|
|
return colors;
|
|
|
|
}
|
|
|
|
|
2018-10-24 09:36:02 +00:00
|
|
|
static quint32 defaultStripSize(TIFF *tiff)
|
|
|
|
{
|
|
|
|
// Aim for 4MB strips
|
2019-03-11 13:07:02 +00:00
|
|
|
qint64 scanSize = qMax(qint64(1), qint64(TIFFScanlineSize(tiff)));
|
2018-10-24 09:36:02 +00:00
|
|
|
qint64 numRows = (4 * 1024 * 1024) / scanSize;
|
|
|
|
quint32 reqSize = static_cast<quint32>(qBound(qint64(1), numRows, qint64(UINT_MAX)));
|
|
|
|
return TIFFDefaultStripSize(tiff, reqSize);
|
|
|
|
}
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
bool QTiffHandler::write(const QImage &image)
|
|
|
|
{
|
|
|
|
if (!device()->isWritable())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
TIFF *const tiff = TIFFClientOpen("foo",
|
2014-01-31 14:36:05 +00:00
|
|
|
"wB",
|
2015-04-20 09:27:10 +00:00
|
|
|
device(),
|
2012-01-31 06:01:22 +00:00
|
|
|
qtiffReadProc,
|
|
|
|
qtiffWriteProc,
|
|
|
|
qtiffSeekProc,
|
|
|
|
qtiffCloseProc,
|
|
|
|
qtiffSizeProc,
|
|
|
|
qtiffMapProc,
|
|
|
|
qtiffUnmapProc);
|
|
|
|
if (!tiff)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const int width = image.width();
|
|
|
|
const int height = image.height();
|
2015-04-20 09:27:10 +00:00
|
|
|
const int compression = d->compression;
|
2012-01-31 06:01:22 +00:00
|
|
|
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set the resolution
|
|
|
|
bool resolutionSet = false;
|
|
|
|
const int dotPerMeterX = image.dotsPerMeterX();
|
|
|
|
const int dotPerMeterY = image.dotsPerMeterY();
|
|
|
|
if ((dotPerMeterX % 100) == 0
|
|
|
|
&& (dotPerMeterY % 100) == 0) {
|
|
|
|
resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
|
|
|
|
&& TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
|
|
|
|
&& TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
|
|
|
|
} else {
|
|
|
|
resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
|
|
|
|
&& TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
|
|
|
|
&& TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
|
|
|
|
}
|
|
|
|
if (!resolutionSet) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
2015-04-29 08:55:27 +00:00
|
|
|
// set the orienataion
|
|
|
|
bool orientationSet = false;
|
|
|
|
orientationSet = TIFFSetField(tiff, TIFFTAG_ORIENTATION, qt2Exif(d->transformation));
|
|
|
|
if (!orientationSet) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
2019-07-24 16:27:46 +00:00
|
|
|
// set color space
|
|
|
|
if (image.colorSpace().isValid()) {
|
|
|
|
QByteArray iccProfile = image.colorSpace().iccProfile();
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_ICCPROFILE, iccProfile.size(), reinterpret_cast<const void *>(iccProfile.constData()))) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2012-01-31 06:01:22 +00:00
|
|
|
// configure image depth
|
|
|
|
const QImage::Format format = image.format();
|
|
|
|
if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t photometric = PHOTOMETRIC_MINISBLACK;
|
2012-01-31 06:01:22 +00:00
|
|
|
if (image.colorTable().at(0) == 0xffffffff)
|
|
|
|
photometric = PHOTOMETRIC_MINISWHITE;
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
|
2016-06-08 10:30:08 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
2018-06-05 12:07:33 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1)
|
2018-10-24 09:36:02 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to do the conversion in chunks no greater than 16 MB
|
2019-09-20 09:20:00 +00:00
|
|
|
const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
|
|
|
|
const int chunkHeight = qMax(height / chunks, 1);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
|
|
|
int y = 0;
|
|
|
|
while (y < height) {
|
|
|
|
QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
|
|
|
|
|
|
|
|
int chunkStart = y;
|
|
|
|
int chunkEnd = y + chunk.height();
|
|
|
|
while (y < chunkEnd) {
|
2021-05-04 12:09:43 +00:00
|
|
|
if (TIFFWriteScanline(tiff, reinterpret_cast<uint32_t *>(chunk.scanLine(y - chunkStart)), y) != 1) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
2015-04-21 11:07:11 +00:00
|
|
|
} else if (format == QImage::Format_Indexed8
|
|
|
|
|| format == QImage::Format_Grayscale8
|
2018-12-11 09:48:15 +00:00
|
|
|
|| format == QImage::Format_Grayscale16
|
2015-04-21 11:07:11 +00:00
|
|
|
|| format == QImage::Format_Alpha8) {
|
2020-06-08 06:55:35 +00:00
|
|
|
QList<QRgb> colorTable = effectiveColorTable(image);
|
2012-01-31 06:01:22 +00:00
|
|
|
bool isGrayscale = checkGrayscale(colorTable);
|
|
|
|
if (isGrayscale) {
|
2021-05-04 12:09:43 +00:00
|
|
|
uint16_t photometric = PHOTOMETRIC_MINISBLACK;
|
2015-04-21 11:07:11 +00:00
|
|
|
if (colorTable.at(0) == 0xffffffff)
|
2012-01-31 06:01:22 +00:00
|
|
|
photometric = PHOTOMETRIC_MINISWHITE;
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
|
2016-06-08 10:30:08 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
2018-12-11 09:48:15 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth())
|
2021-05-31 08:56:52 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
|
2018-10-24 09:36:02 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
|
2016-06-08 10:30:08 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
2018-06-05 12:07:33 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
|
2018-10-24 09:36:02 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//// write the color table
|
|
|
|
// allocate the color tables
|
|
|
|
const int tableSize = colorTable.size();
|
|
|
|
Q_ASSERT(tableSize <= 256);
|
2021-05-04 12:09:43 +00:00
|
|
|
QVarLengthArray<uint16_t> redTable(tableSize);
|
|
|
|
QVarLengthArray<uint16_t> greenTable(tableSize);
|
|
|
|
QVarLengthArray<uint16_t> blueTable(tableSize);
|
2015-03-16 13:08:31 +00:00
|
|
|
|
|
|
|
// set the color table
|
2012-01-31 06:01:22 +00:00
|
|
|
for (int i = 0; i<tableSize; ++i) {
|
|
|
|
const QRgb color = colorTable.at(i);
|
|
|
|
redTable[i] = qRed(color) * 257;
|
|
|
|
greenTable[i] = qGreen(color) * 257;
|
|
|
|
blueTable[i] = qBlue(color) * 257;
|
|
|
|
}
|
|
|
|
|
2015-03-16 13:08:31 +00:00
|
|
|
const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data());
|
2012-01-31 06:01:22 +00:00
|
|
|
|
|
|
|
if (!setColorTableSuccess) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//// write the data
|
2019-09-20 09:20:00 +00:00
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
if (TIFFWriteScanline(tiff, const_cast<uchar *>(image.scanLine(y)), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
2021-05-31 08:56:52 +00:00
|
|
|
} else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
|
2018-06-21 14:52:41 +00:00
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
|
2021-05-31 08:56:52 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT,
|
|
|
|
format == QImage::Format_RGBX64
|
|
|
|
? SAMPLEFORMAT_UINT
|
|
|
|
: SAMPLEFORMAT_IEEEFP)
|
2018-06-21 14:52:41 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::unique_ptr<quint16[]> rgb48line(new quint16[width * 3]);
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
const quint16 *srcLine = reinterpret_cast<const quint16 *>(image.constScanLine(y));
|
|
|
|
for (int x = 0; x < width; ++x) {
|
|
|
|
rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
|
|
|
|
rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
|
|
|
|
rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TIFFWriteScanline(tiff, (void*)rgb48line.get(), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
|
|
|
} else if (format == QImage::Format_RGBA64
|
|
|
|
|| format == QImage::Format_RGBA64_Premultiplied) {
|
|
|
|
const bool premultiplied = image.format() != QImage::Format_RGBA64;
|
2021-05-04 12:09:43 +00:00
|
|
|
const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
|
2018-06-21 14:52:41 +00:00
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
|
2021-05-31 08:56:52 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
|
|
|
} else if (format == QImage::Format_RGBX32FPx4) {
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 32)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::unique_ptr<float[]> line(new float[width * 3]);
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
const float *srcLine = reinterpret_cast<const float *>(image.constScanLine(y));
|
|
|
|
for (int x = 0; x < width; ++x) {
|
|
|
|
line[x * 3 + 0] = srcLine[x * 4 + 0];
|
|
|
|
line[x * 3 + 1] = srcLine[x * 4 + 1];
|
|
|
|
line[x * 3 + 2] = srcLine[x * 4 + 2];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TIFFWriteScanline(tiff, (void*)line.get(), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
|
|
|
} else if (format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA32FPx4
|
|
|
|
|| format == QImage::Format_RGBA16FPx4_Premultiplied
|
|
|
|
|| format == QImage::Format_RGBA32FPx4_Premultiplied) {
|
|
|
|
const bool premultiplied = image.format() != QImage::Format_RGBA16FPx4 && image.format() != QImage::Format_RGBA32FPx4;
|
|
|
|
const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth() == 64 ? 16 : 32)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
|
2018-06-21 14:52:41 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (int y = 0; y < height; ++y) {
|
|
|
|
if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
2014-02-03 11:30:38 +00:00
|
|
|
} else if (!image.hasAlphaChannel()) {
|
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
|
2018-06-05 12:07:33 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
|
2018-10-24 09:36:02 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
|
2014-02-03 11:30:38 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// try to do the RGB888 conversion in chunks no greater than 16 MB
|
2019-09-20 09:20:00 +00:00
|
|
|
const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
|
2014-02-03 11:30:38 +00:00
|
|
|
const int chunkHeight = qMax(height / chunks, 1);
|
|
|
|
|
|
|
|
int y = 0;
|
|
|
|
while (y < height) {
|
|
|
|
const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
|
|
|
|
|
|
|
|
int chunkStart = y;
|
|
|
|
int chunkEnd = y + chunk.height();
|
|
|
|
while (y < chunkEnd) {
|
|
|
|
if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
|
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
2012-01-31 06:01:22 +00:00
|
|
|
} else {
|
2015-04-21 11:49:50 +00:00
|
|
|
const bool premultiplied = image.format() != QImage::Format_ARGB32
|
|
|
|
&& image.format() != QImage::Format_RGBA8888;
|
2021-05-04 12:09:43 +00:00
|
|
|
const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
|
2012-01-31 06:01:22 +00:00
|
|
|
if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
|
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
|
2015-04-21 11:49:50 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
|
2018-06-05 12:07:33 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
|
2018-10-24 09:36:02 +00:00
|
|
|
|| !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
2014-01-31 14:36:05 +00:00
|
|
|
// try to do the RGBA8888 conversion in chunks no greater than 16 MB
|
2019-09-20 09:20:00 +00:00
|
|
|
const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
|
2014-02-03 11:30:38 +00:00
|
|
|
const int chunkHeight = qMax(height / chunks, 1);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
2015-04-21 11:49:50 +00:00
|
|
|
const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
|
|
|
|
: QImage::Format_RGBA8888;
|
2012-01-31 06:01:22 +00:00
|
|
|
int y = 0;
|
|
|
|
while (y < height) {
|
2015-04-21 11:49:50 +00:00
|
|
|
const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
|
2012-01-31 06:01:22 +00:00
|
|
|
|
|
|
|
int chunkStart = y;
|
|
|
|
int chunkEnd = y + chunk.height();
|
|
|
|
while (y < chunkEnd) {
|
2014-02-03 11:30:38 +00:00
|
|
|
if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
|
2012-01-31 06:01:22 +00:00
|
|
|
TIFFClose(tiff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
++y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
TIFFClose(tiff);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant QTiffHandler::option(ImageOption option) const
|
|
|
|
{
|
|
|
|
if (option == Size && canRead()) {
|
2015-04-20 09:27:10 +00:00
|
|
|
if (d->readHeaders(device()))
|
|
|
|
return d->size;
|
2012-01-31 06:01:22 +00:00
|
|
|
} else if (option == CompressionRatio) {
|
2015-04-20 09:27:10 +00:00
|
|
|
return d->compression;
|
2012-01-31 06:01:22 +00:00
|
|
|
} else if (option == ImageFormat) {
|
2015-04-20 09:27:10 +00:00
|
|
|
if (d->readHeaders(device()))
|
|
|
|
return d->format;
|
2015-04-29 08:55:27 +00:00
|
|
|
} else if (option == ImageTransformation) {
|
|
|
|
if (d->readHeaders(device()))
|
|
|
|
return int(d->transformation);
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTiffHandler::setOption(ImageOption option, const QVariant &value)
|
|
|
|
{
|
2021-01-29 14:55:33 +00:00
|
|
|
if (option == CompressionRatio && value.metaType().id() == QMetaType::Int)
|
2018-09-13 10:42:22 +00:00
|
|
|
d->compression = qBound(0, value.toInt(), 1);
|
2015-04-29 08:55:27 +00:00
|
|
|
if (option == ImageTransformation) {
|
|
|
|
int transformation = value.toInt();
|
|
|
|
if (transformation > 0 && transformation < 8)
|
|
|
|
d->transformation = QImageIOHandler::Transformations(transformation);
|
|
|
|
}
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandler::supportsOption(ImageOption option) const
|
|
|
|
{
|
|
|
|
return option == CompressionRatio
|
|
|
|
|| option == Size
|
2015-04-29 08:55:27 +00:00
|
|
|
|| option == ImageFormat
|
2019-06-14 17:07:42 +00:00
|
|
|
|| option == ImageTransformation;
|
2012-01-31 06:01:22 +00:00
|
|
|
}
|
|
|
|
|
2017-01-01 22:49:35 +00:00
|
|
|
bool QTiffHandler::jumpToNextImage()
|
|
|
|
{
|
|
|
|
if (!ensureHaveDirectoryCount())
|
|
|
|
return false;
|
|
|
|
if (d->currentDirectory >= d->directoryCount - 1)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
d->headersRead = false;
|
|
|
|
++d->currentDirectory;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QTiffHandler::jumpToImage(int imageNumber)
|
|
|
|
{
|
|
|
|
if (!ensureHaveDirectoryCount())
|
|
|
|
return false;
|
|
|
|
if (imageNumber < 0 || imageNumber >= d->directoryCount)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (d->currentDirectory != imageNumber) {
|
|
|
|
d->headersRead = false;
|
|
|
|
d->currentDirectory = imageNumber;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int QTiffHandler::imageCount() const
|
|
|
|
{
|
|
|
|
if (!ensureHaveDirectoryCount())
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return d->directoryCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
int QTiffHandler::currentImageNumber() const
|
|
|
|
{
|
|
|
|
return d->currentDirectory;
|
|
|
|
}
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
void QTiffHandler::convert32BitOrder(void *buffer, int width)
|
|
|
|
{
|
2021-05-04 12:09:43 +00:00
|
|
|
uint32_t *target = reinterpret_cast<uint32_t *>(buffer);
|
|
|
|
for (int32_t x=0; x<width; ++x) {
|
|
|
|
uint32_t p = target[x];
|
2012-01-31 06:01:22 +00:00
|
|
|
// convert between ARGB and ABGR
|
|
|
|
target[x] = (p & 0xff000000)
|
|
|
|
| ((p & 0x00ff0000) >> 16)
|
|
|
|
| (p & 0x0000ff00)
|
|
|
|
| ((p & 0x000000ff) << 16);
|
|
|
|
}
|
|
|
|
}
|
2017-01-01 22:49:35 +00:00
|
|
|
|
2021-05-31 08:56:52 +00:00
|
|
|
void QTiffHandler::rgb48fixup(QImage *image, bool floatingPoint)
|
2018-06-21 14:52:41 +00:00
|
|
|
{
|
|
|
|
Q_ASSERT(image->depth() == 64);
|
|
|
|
const int h = image->height();
|
|
|
|
const int w = image->width();
|
|
|
|
uchar *scanline = image->bits();
|
|
|
|
const qsizetype bpl = image->bytesPerLine();
|
2021-05-31 08:56:52 +00:00
|
|
|
quint16 mask = 0xffff;
|
|
|
|
const qfloat16 fp_mask = 1.0f;
|
|
|
|
if (floatingPoint)
|
|
|
|
memcpy(&mask, &fp_mask, 2);
|
2018-06-21 14:52:41 +00:00
|
|
|
for (int y = 0; y < h; ++y) {
|
2021-05-04 12:09:43 +00:00
|
|
|
quint16 *dst = reinterpret_cast<uint16_t *>(scanline);
|
2018-06-21 14:52:41 +00:00
|
|
|
for (int x = w - 1; x >= 0; --x) {
|
2021-05-31 08:56:52 +00:00
|
|
|
dst[x * 4 + 3] = mask;
|
|
|
|
dst[x * 4 + 2] = dst[x * 3 + 2];
|
|
|
|
dst[x * 4 + 1] = dst[x * 3 + 1];
|
|
|
|
dst[x * 4 + 0] = dst[x * 3 + 0];
|
|
|
|
}
|
|
|
|
scanline += bpl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void QTiffHandler::rgb96fixup(QImage *image)
|
|
|
|
{
|
|
|
|
Q_ASSERT(image->depth() == 128);
|
|
|
|
const int h = image->height();
|
|
|
|
const int w = image->width();
|
|
|
|
uchar *scanline = image->bits();
|
|
|
|
const qsizetype bpl = image->bytesPerLine();
|
|
|
|
for (int y = 0; y < h; ++y) {
|
|
|
|
float *dst = reinterpret_cast<float *>(scanline);
|
|
|
|
for (int x = w - 1; x >= 0; --x) {
|
|
|
|
dst[x * 4 + 3] = 1.0f;
|
2018-06-21 14:52:41 +00:00
|
|
|
dst[x * 4 + 2] = dst[x * 3 + 2];
|
|
|
|
dst[x * 4 + 1] = dst[x * 3 + 1];
|
|
|
|
dst[x * 4 + 0] = dst[x * 3 + 0];
|
|
|
|
}
|
|
|
|
scanline += bpl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-01 22:49:35 +00:00
|
|
|
bool QTiffHandler::ensureHaveDirectoryCount() const
|
|
|
|
{
|
|
|
|
if (d->directoryCount > 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
TIFF *tiff = TIFFClientOpen("foo",
|
|
|
|
"r",
|
|
|
|
device(),
|
|
|
|
qtiffReadProc,
|
|
|
|
qtiffWriteProc,
|
|
|
|
qtiffSeekProc,
|
|
|
|
qtiffCloseProc,
|
|
|
|
qtiffSizeProc,
|
|
|
|
qtiffMapProc,
|
|
|
|
qtiffUnmapProc);
|
|
|
|
if (!tiff) {
|
|
|
|
device()->reset();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
++d->directoryCount;
|
|
|
|
} while (TIFFReadDirectory(tiff));
|
|
|
|
TIFFClose(tiff);
|
|
|
|
device()->reset();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-01-31 06:01:22 +00:00
|
|
|
QT_END_NAMESPACE
|