diff options
Diffstat (limited to 'src/plugins/imageformats/tiff/qtiffhandler.cpp')
-rw-r--r-- | src/plugins/imageformats/tiff/qtiffhandler.cpp | 482 |
1 files changed, 343 insertions, 139 deletions
diff --git a/src/plugins/imageformats/tiff/qtiffhandler.cpp b/src/plugins/imageformats/tiff/qtiffhandler.cpp index b6c50b8..280dba2 100644 --- a/src/plugins/imageformats/tiff/qtiffhandler.cpp +++ b/src/plugins/imageformats/tiff/qtiffhandler.cpp @@ -1,48 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** 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 -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** 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. -** -** 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. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qtiffhandler_p.h" -#include <qvariant.h> + #include <qcolorspace.h> #include <qdebug.h> +#include <qfloat16.h> #include <qimage.h> -#include <qglobal.h> +#include <qloggingcategory.h> +#include <qvariant.h> +#include <qvarlengtharray.h> +#include <qbuffer.h> +#include <qfiledevice.h> + extern "C" { #include "tiffio.h" } @@ -51,6 +21,8 @@ extern "C" { QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcTiff, "qt.imageformats.tiff") + tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) { QIODevice *device = static_cast<QIODevice *>(fd); @@ -90,13 +62,33 @@ toff_t qtiffSizeProc(thandle_t fd) return static_cast<QIODevice *>(fd)->size(); } -int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/) +int qtiffMapProc(thandle_t fd, void **base, toff_t *size) { + QIODevice *device = static_cast<QIODevice *>(fd); + + QFileDevice *file = qobject_cast<QFileDevice *>(device); + if (file) { + *base = file->map(0, file->size()); + if (*base != nullptr) { + *size = file->size(); + return 1; + } + } else { + QBuffer *buf = qobject_cast<QBuffer *>(device); + if (buf) { + *base = const_cast<char *>(buf->data().constData()); + *size = buf->size(); + return 1; + } + } return 0; } -void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/) +void qtiffUnmapProc(thandle_t fd, void *base, toff_t /*size*/) { + QFileDevice *file = qobject_cast<QFileDevice *>(static_cast<QIODevice *>(fd)); + if (file && base) + file->unmap(static_cast<uchar *>(base)); } @@ -110,14 +102,22 @@ public: bool openForRead(QIODevice *device); bool readHeaders(QIODevice *device); void close(); + TIFF *openInternal(const char *mode, QIODevice *device); +#if TIFFLIB_VERSION >= 20221213 + static int tiffErrorHandler(TIFF *tif, void *user_data, const char *, + const char *fmt, va_list ap); + static int tiffWarningHandler(TIFF *tif, void *user_data, const char *, + const char *fmt, va_list ap); +#endif TIFF *tiff; int compression; QImageIOHandler::Transformations transformation; QImage::Format format; QSize size; - uint16 photometric; + uint16_t photometric; bool grayscale; + bool floatingPoint; bool headersRead; int currentDirectory; int directoryCount; @@ -143,7 +143,7 @@ static QImageIOHandler::Transformations exif2Qt(int exifOrientation) case 8: // rotate 270 CW return QImageIOHandler::TransformationRotate270; } - qWarning("Invalid EXIF orientation"); + qCWarning(lcTiff, "Invalid EXIF orientation"); return QImageIOHandler::TransformationNone; } @@ -167,7 +167,7 @@ static int qt2Exif(QImageIOHandler::Transformations transformation) case QImageIOHandler::TransformationRotate270: return 8; } - qWarning("Invalid Qt image transformation"); + qCWarning(lcTiff, "Invalid Qt image transformation"); return 1; } @@ -196,10 +196,67 @@ void QTiffHandlerPrivate::close() tiff = 0; } +TIFF *QTiffHandlerPrivate::openInternal(const char *mode, QIODevice *device) +{ +// TIFFLIB_VERSION 20221213 -> 4.5.0 +#if TIFFLIB_VERSION >= 20221213 + TIFFOpenOptions *opts = TIFFOpenOptionsAlloc(); + TIFFOpenOptionsSetErrorHandlerExtR(opts, &tiffErrorHandler, this); + TIFFOpenOptionsSetWarningHandlerExtR(opts, &tiffWarningHandler, this); + auto handle = TIFFClientOpenExt("foo", + mode, + device, + qtiffReadProc, + qtiffWriteProc, + qtiffSeekProc, + qtiffCloseProc, + qtiffSizeProc, + qtiffMapProc, + qtiffUnmapProc, + opts); + TIFFOpenOptionsFree(opts); +#else + auto handle = TIFFClientOpen("foo", + mode, + device, + qtiffReadProc, + qtiffWriteProc, + qtiffSeekProc, + qtiffCloseProc, + qtiffSizeProc, + qtiffMapProc, + qtiffUnmapProc); +#endif + return handle; +} + + +#if TIFFLIB_VERSION >= 20221213 +int QTiffHandlerPrivate::tiffErrorHandler(TIFF *tif, void *user_data, const char *, + const char *fmt, va_list ap) +{ + const auto priv = static_cast<QTiffHandlerPrivate *>(user_data); + if (!priv || priv->tiff != tif) + return 0; + qCCritical(lcTiff) << QString::vasprintf(fmt, ap); + return 1; +} + +int QTiffHandlerPrivate::tiffWarningHandler(TIFF *tif, void *user_data, const char *, + const char *fmt, va_list ap) +{ + const auto priv = static_cast<QTiffHandlerPrivate *>(user_data); + if (!priv || priv->tiff != tif) + return 0; + qCWarning(lcTiff) << QString::vasprintf(fmt, ap); + return 1; +} +#endif + bool QTiffHandlerPrivate::canRead(QIODevice *device) { if (!device) { - qWarning("QTiffHandler::canRead() called with no device"); + qCWarning(lcTiff, "QTiffHandler::canRead() called with no device"); return false; } @@ -223,21 +280,8 @@ bool QTiffHandlerPrivate::openForRead(QIODevice *device) if (!canRead(device)) return false; - tiff = TIFFClientOpen("foo", - "r", - device, - qtiffReadProc, - qtiffWriteProc, - qtiffSeekProc, - qtiffCloseProc, - qtiffSizeProc, - qtiffMapProc, - qtiffUnmapProc); - - if (!tiff) { - return false; - } - return true; + tiff = openInternal("rh", device); + return tiff != nullptr; } bool QTiffHandlerPrivate::readHeaders(QIODevice *device) @@ -248,10 +292,13 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) if (!openForRead(device)) return false; - TIFFSetDirectory(tiff, currentDirectory); + if (!TIFFSetDirectory(tiff, currentDirectory)) { + close(); + return false; + } - uint32 width; - uint32 height; + uint32_t width; + uint32_t height; if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width) || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height) || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) { @@ -260,17 +307,21 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) } size = QSize(width, height); - uint16 orientationTag; + uint16_t orientationTag; if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag)) transformation = exif2Qt(orientationTag); // BitsPerSample defaults to 1 according to the TIFF spec. - uint16 bitPerSample; + uint16_t bitPerSample; if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample)) bitPerSample = 1; - uint16 samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel + uint16_t samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) samplesPerPixel = 1; + uint16_t sampleFormat; + if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) + sampleFormat = SAMPLEFORMAT_VOID; + floatingPoint = (sampleFormat == SAMPLEFORMAT_IEEEFP); grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE; @@ -278,18 +329,20 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) format = QImage::Format_Mono; else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1) format = QImage::Format_Grayscale8; - else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1) + else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1 && !floatingPoint) format = QImage::Format_Grayscale16; else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1) format = QImage::Format_Indexed8; else if (samplesPerPixel < 4) - if (bitPerSample == 16 && photometric == PHOTOMETRIC_RGB) - format = QImage::Format_RGBX64; + if (bitPerSample == 16 && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK)) + format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64; + else if (bitPerSample == 32 && floatingPoint && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK)) + format = QImage::Format_RGBX32FPx4; else format = QImage::Format_RGB32; else { - uint16 count; - uint16 *extrasamples; + uint16_t count; + uint16_t *extrasamples; // 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 @@ -304,9 +357,25 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA) premultiplied = false; if (premultiplied) - format = QImage::Format_RGBA64_Premultiplied; + format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied; else - format = QImage::Format_RGBA64; + 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; + else + format = QImage::Format_RGBA32FPx4; + } else if (samplesPerPixel == 4 && bitPerSample == 8 && photometric == PHOTOMETRIC_SEPARATED) { + uint16_t inkSet; + const bool gotInkSetField = TIFFGetField(tiff, TIFFTAG_INKSET, &inkSet); + if (!gotInkSetField || inkSet == INKSET_CMYK) { + format = QImage::Format_CMYK8888; + } else { + close(); + return false; + } } else { if (premultiplied) format = QImage::Format_ARGB32_Premultiplied; @@ -349,18 +418,14 @@ bool QTiffHandler::read(QImage *image) QImage::Format format = d->format; - if (image->size() == d->size && image->format() != format) - image->reinterpretAsFormat(format); - - if (image->size() != d->size || image->format() != format) - *image = QImage(d->size, format); - - if (image->isNull()) { + if (!QImageIOHandler::allocateImage(d->size, format, image)) { d->close(); return false; } TIFF *const tiff = d->tiff; + if (TIFFIsTiled(tiff) && TIFFTileSize64(tiff) > uint64_t(image->sizeInBytes())) // Corrupt image + return false; const quint32 width = d->size.width(); const quint32 height = d->size.height(); @@ -377,7 +442,7 @@ bool QTiffHandler::read(QImage *image) } image->setColorTable(colortable); } else if (format == QImage::Format_Indexed8) { - const uint16 tableSize = 256; + const uint16_t tableSize = 256; QList<QRgb> qtColorTable(tableSize); if (d->grayscale) { for (int i = 0; i<tableSize; ++i) { @@ -386,9 +451,9 @@ bool QTiffHandler::read(QImage *image) } } else { // create the color table - uint16 *redTable = 0; - uint16 *greenTable = 0; - uint16 *blueTable = 0; + uint16_t *redTable = 0; + uint16_t *greenTable = 0; + uint16_t *blueTable = 0; if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) { d->close(); return false; @@ -412,25 +477,38 @@ bool QTiffHandler::read(QImage *image) } bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8); bool format16bit = (format == QImage::Format_Grayscale16); + bool formatCmyk32bit = (format == QImage::Format_CMYK8888); bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied); + 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); // Formats we read directly, instead of over RGBA32: - if (format8bit || format16bit || format64bit) { + if (format8bit || format16bit || formatCmyk32bit || format64bit || format64fp || format128fp) { int bytesPerPixel = image->depth() / 8; - if (format == QImage::Format_RGBX64) - bytesPerPixel = 6; + if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) + bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 6 : 2; + else if (format == QImage::Format_RGBX32FPx4) + bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 12 : 4; if (TIFFIsTiled(tiff)) { quint32 tileWidth, tileLength; TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength); - uchar *buf = (uchar *)_TIFFmalloc(TIFFTileSize(tiff)); - if (!tileWidth || !tileLength || !buf) { - _TIFFfree(buf); + if (!tileWidth || !tileLength || tileWidth % 16 || tileLength % 16) { d->close(); return false; } quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel); quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel); + tmsize_t byteTileSize = TIFFTileSize(tiff); + if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth) { + d->close(); + return false; + } + uchar *buf = (uchar *)_TIFFmalloc(byteTileSize); + if (!buf) { + d->close(); + return false; + } 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) { @@ -448,19 +526,32 @@ bool QTiffHandler::read(QImage *image) } _TIFFfree(buf); } else { - for (uint32 y=0; y<height; ++y) { + if (image->bytesPerLine() < TIFFScanlineSize(tiff)) { + d->close(); + return false; + } + for (uint32_t y=0; y<height; ++y) { if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) { d->close(); return false; } } } - if (format == QImage::Format_RGBX64) - rgb48fixup(image); + if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) { + if (d->photometric == PHOTOMETRIC_RGB) + rgb48fixup(image, d->floatingPoint); + else + rgbFixup(image); + } else if (format == QImage::Format_RGBX32FPx4) { + if (d->photometric == PHOTOMETRIC_RGB) + rgb96fixup(image); + else + rgbFixup(image); + } } else { const int stopOnError = 1; - if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(image->bits()), qt2Exif(d->transformation), stopOnError)) { - for (uint32 y=0; y<height; ++y) + if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32_t *>(image->bits()), qt2Exif(d->transformation), stopOnError)) { + for (uint32_t y=0; y<height; ++y) convert32BitOrder(image->scanLine(y), width); } else { d->close(); @@ -471,7 +562,7 @@ bool QTiffHandler::read(QImage *image) float resX = 0; float resY = 0; - uint16 resUnit; + uint16_t resUnit; if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit)) resUnit = RESUNIT_INCH; @@ -494,7 +585,7 @@ bool QTiffHandler::read(QImage *image) } } - uint32 count; + uint32_t count; void *profile; if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &count, &profile)) { QByteArray iccProfile(reinterpret_cast<const char *>(profile), count); @@ -558,16 +649,7 @@ bool QTiffHandler::write(const QImage &image) if (!device()->isWritable()) return false; - TIFF *const tiff = TIFFClientOpen("foo", - "wB", - device(), - qtiffReadProc, - qtiffWriteProc, - qtiffSeekProc, - qtiffCloseProc, - qtiffSizeProc, - qtiffMapProc, - qtiffUnmapProc); + TIFF *const tiff = d->openInternal("wB", device()); if (!tiff) return false; @@ -608,8 +690,8 @@ bool QTiffHandler::write(const QImage &image) return false; } // set color space - if (image.colorSpace().isValid()) { - QByteArray iccProfile = image.colorSpace().iccProfile(); + const QByteArray iccProfile = image.colorSpace().iccProfile(); + if (!iccProfile.isEmpty()) { if (!TIFFSetField(tiff, TIFFTAG_ICCPROFILE, iccProfile.size(), reinterpret_cast<const void *>(iccProfile.constData()))) { TIFFClose(tiff); return false; @@ -618,7 +700,7 @@ bool QTiffHandler::write(const QImage &image) // configure image depth const QImage::Format format = image.format(); if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) { - uint16 photometric = PHOTOMETRIC_MINISBLACK; + uint16_t photometric = PHOTOMETRIC_MINISBLACK; if (image.colorTable().at(0) == 0xffffffff) photometric = PHOTOMETRIC_MINISWHITE; if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric) @@ -640,7 +722,7 @@ bool QTiffHandler::write(const QImage &image) int chunkStart = y; int chunkEnd = y + chunk.height(); while (y < chunkEnd) { - if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) { + if (TIFFWriteScanline(tiff, reinterpret_cast<uint32_t *>(chunk.scanLine(y - chunkStart)), y) != 1) { TIFFClose(tiff); return false; } @@ -655,12 +737,13 @@ bool QTiffHandler::write(const QImage &image) QList<QRgb> colorTable = effectiveColorTable(image); bool isGrayscale = checkGrayscale(colorTable); if (isGrayscale) { - uint16 photometric = PHOTOMETRIC_MINISBLACK; + uint16_t photometric = PHOTOMETRIC_MINISBLACK; if (colorTable.at(0) == 0xffffffff) photometric = PHOTOMETRIC_MINISWHITE; if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric) || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth()) + || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT) || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) { TIFFClose(tiff); return false; @@ -677,9 +760,9 @@ bool QTiffHandler::write(const QImage &image) // allocate the color tables const int tableSize = colorTable.size(); Q_ASSERT(tableSize <= 256); - QVarLengthArray<uint16> redTable(tableSize); - QVarLengthArray<uint16> greenTable(tableSize); - QVarLengthArray<uint16> blueTable(tableSize); + QVarLengthArray<uint16_t> redTable(tableSize); + QVarLengthArray<uint16_t> greenTable(tableSize); + QVarLengthArray<uint16_t> blueTable(tableSize); // set the color table for (int i = 0; i<tableSize; ++i) { @@ -705,11 +788,15 @@ bool QTiffHandler::write(const QImage &image) } } TIFFClose(tiff); - } else if (format == QImage::Format_RGBX64) { + } else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) { 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) + || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, + format == QImage::Format_RGBX64 + ? SAMPLEFORMAT_UINT + : SAMPLEFORMAT_IEEEFP) || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) { TIFFClose(tiff); return false; @@ -732,11 +819,59 @@ bool QTiffHandler::write(const QImage &image) } else if (format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied) { const bool premultiplied = image.format() != QImage::Format_RGBA64; - const uint16 extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA; + 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, 16) + || !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) || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples) || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) { TIFFClose(tiff); @@ -749,6 +884,25 @@ bool QTiffHandler::write(const QImage &image) } } TIFFClose(tiff); + } else if (format == QImage::Format_CMYK8888) { + if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED) + || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) + || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4) + || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8) + || !TIFFSetField(tiff, TIFFTAG_INKSET, INKSET_CMYK) + || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) { + TIFFClose(tiff); + return false; + } + + for (int y = 0; y < image.height(); ++y) { + if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) { + TIFFClose(tiff); + return false; + } + } + + TIFFClose(tiff); } else if (!image.hasAlphaChannel()) { if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) @@ -780,7 +934,7 @@ bool QTiffHandler::write(const QImage &image) } else { const bool premultiplied = image.format() != QImage::Format_ARGB32 && image.format() != QImage::Format_RGBA8888; - const uint16 extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA; + 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) @@ -835,7 +989,7 @@ QVariant QTiffHandler::option(ImageOption option) const void QTiffHandler::setOption(ImageOption option, const QVariant &value) { - if (option == CompressionRatio && value.type() == QVariant::Int) + if (option == CompressionRatio && value.metaType().id() == QMetaType::Int) d->compression = qBound(0, value.toInt(), 1); if (option == ImageTransformation) { int transformation = value.toInt(); @@ -893,9 +1047,9 @@ int QTiffHandler::currentImageNumber() const void QTiffHandler::convert32BitOrder(void *buffer, int width) { - uint32 *target = reinterpret_cast<uint32 *>(buffer); - for (int32 x=0; x<width; ++x) { - uint32 p = target[x]; + uint32_t *target = reinterpret_cast<uint32_t *>(buffer); + for (int32_t x=0; x<width; ++x) { + uint32_t p = target[x]; // convert between ARGB and ABGR target[x] = (p & 0xff000000) | ((p & 0x00ff0000) >> 16) @@ -904,17 +1058,21 @@ void QTiffHandler::convert32BitOrder(void *buffer, int width) } } -void QTiffHandler::rgb48fixup(QImage *image) +void QTiffHandler::rgb48fixup(QImage *image, bool floatingPoint) { Q_ASSERT(image->depth() == 64); const int h = image->height(); const int w = image->width(); uchar *scanline = image->bits(); const qsizetype bpl = image->bytesPerLine(); + quint16 mask = 0xffff; + const qfloat16 fp_mask = qfloat16(1.0f); + if (floatingPoint) + memcpy(&mask, &fp_mask, 2); for (int y = 0; y < h; ++y) { - quint16 *dst = reinterpret_cast<uint16 *>(scanline); + quint16 *dst = reinterpret_cast<uint16_t *>(scanline); for (int x = w - 1; x >= 0; --x) { - dst[x * 4 + 3] = 0xffff; + 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]; @@ -923,29 +1081,75 @@ void QTiffHandler::rgb48fixup(QImage *image) } } +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; + 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::rgbFixup(QImage *image) +{ + Q_ASSERT(d->floatingPoint); + if (image->depth() == 64) { + 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) { + qfloat16 *dst = reinterpret_cast<qfloat16 *>(scanline); + for (int x = w - 1; x >= 0; --x) { + dst[x * 4 + 3] = qfloat16(1.0f); + dst[x * 4 + 2] = dst[x]; + dst[x * 4 + 1] = dst[x]; + dst[x * 4 + 0] = dst[x]; + } + scanline += bpl; + } + } else { + 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; + dst[x * 4 + 2] = dst[x]; + dst[x * 4 + 1] = dst[x]; + dst[x * 4 + 0] = dst[x]; + } + scanline += bpl; + } + } +} + bool QTiffHandler::ensureHaveDirectoryCount() const { if (d->directoryCount > 0) return true; - TIFF *tiff = TIFFClientOpen("foo", - "r", - device(), - qtiffReadProc, - qtiffWriteProc, - qtiffSeekProc, - qtiffCloseProc, - qtiffSizeProc, - qtiffMapProc, - qtiffUnmapProc); + TIFF *tiff = d->openInternal("rh", device()); + if (!tiff) { device()->reset(); return false; } - do { - ++d->directoryCount; - } while (TIFFReadDirectory(tiff)); + while (TIFFReadDirectory(tiff)) + ++d->directoryCount; TIFFClose(tiff); device()->reset(); return true; |