From 2842212e88afb200a0fcfda7d306c4e8eee26407 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Thu, 21 Jun 2018 16:52:41 +0200 Subject: Add RGBA64 format support to TIFF plugin TIFF is one of the primary formats for HDR images. Change-Id: I5310b5c9a625fd3e759e5120be6ba547c633c81c Reviewed-by: Eirik Aavitsland --- src/plugins/imageformats/tiff/qtiffhandler.cpp | 115 ++++++++++++++++++++++--- src/plugins/imageformats/tiff/qtiffhandler_p.h | 1 + tests/auto/tiff/tst_qtiff.cpp | 17 ++++ tests/shared/images/tiff.qrc | 1 + tests/shared/images/tiff/16bpc.tiff | Bin 0 -> 21414 bytes 5 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 tests/shared/images/tiff/16bpc.tiff diff --git a/src/plugins/imageformats/tiff/qtiffhandler.cpp b/src/plugins/imageformats/tiff/qtiffhandler.cpp index 84221d6..1232b50 100644 --- a/src/plugins/imageformats/tiff/qtiffhandler.cpp +++ b/src/plugins/imageformats/tiff/qtiffhandler.cpp @@ -46,6 +46,8 @@ extern "C" { #include "tiffio.h" } +#include + QT_BEGIN_NAMESPACE tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) @@ -273,7 +275,10 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1) format = QImage::Format_Indexed8; else if (samplesPerPixel < 4) - format = QImage::Format_RGB32; + if (bitPerSample > 8 && photometric == PHOTOMETRIC_RGB) + format = QImage::Format_RGBX64; + else + format = QImage::Format_RGB32; else { uint16 count; uint16 *extrasamples; @@ -281,11 +286,25 @@ bool QTiffHandlerPrivate::readHeaders(QIODevice *device) // 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. + bool premultiplied = true; bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples); if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED) - format = QImage::Format_ARGB32; - else - format = QImage::Format_ARGB32_Premultiplied; + premultiplied = false; + + if (bitPerSample > 8 && photometric == PHOTOMETRIC_RGB) { + // We read 64-bit raw, so unassoc remains unpremultiplied. + if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA) + premultiplied = false; + if (premultiplied) + format = QImage::Format_RGBA64_Premultiplied; + else + format = QImage::Format_RGBA64; + } else { + if (premultiplied) + format = QImage::Format_ARGB32_Premultiplied; + else + format = QImage::Format_ARGB32; + } } headersRead = true; @@ -321,10 +340,9 @@ bool QTiffHandler::read(QImage *image) return false; QImage::Format format = d->format; - if (format == QImage::Format_RGB32 && - (image->format() == QImage::Format_ARGB32 || - image->format() == QImage::Format_ARGB32_Premultiplied)) - format = image->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); @@ -338,7 +356,8 @@ bool QTiffHandler::read(QImage *image) const quint32 width = d->size.width(); const quint32 height = d->size.height(); - if (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8) { + // Setup color tables + if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) { if (format == QImage::Format_Mono) { QVector colortable(2); if (d->photometric == PHOTOMETRIC_MINISBLACK) { @@ -381,7 +400,14 @@ bool QTiffHandler::read(QImage *image) image->setColorTable(qtColorTable); // free redTable, greenTable and greenTable done by libtiff } + } + bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8); + bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied); + if (format8bit || format64bit) { + int bytesPerPixel = image->depth() / 8; + if (format == QImage::Format_RGBX64) + bytesPerPixel = 6; if (TIFFIsTiled(tiff)) { quint32 tileWidth, tileLength; TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth); @@ -392,8 +418,8 @@ bool QTiffHandler::read(QImage *image) d->close(); return false; } - quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : width; - quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : tileWidth; + quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel); + quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel); 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) { @@ -402,7 +428,7 @@ bool QTiffHandler::read(QImage *image) return false; } quint32 linesToCopy = qMin(tileLength, height - y); - quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : x; + quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel); quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset); for (quint32 i = 0; i < linesToCopy; i++) { ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy); @@ -418,6 +444,8 @@ bool QTiffHandler::read(QImage *image) } } } + if (format == QImage::Format_RGBX64) + rgb48fixup(image); } else { const int stopOnError = 1; if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast(image->bits()), qt2Exif(d->transformation), stopOnError)) { @@ -651,6 +679,50 @@ bool QTiffHandler::write(const QImage &image) } } TIFFClose(tiff); + } else if (format == QImage::Format_RGBX64) { + 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_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) { + TIFFClose(tiff); + return false; + } + std::unique_ptr rgb48line(new quint16[width * 3]); + for (int y = 0; y < height; ++y) { + const quint16 *srcLine = reinterpret_cast(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; + const uint16 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_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 (!image.hasAlphaChannel()) { if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB) || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW) @@ -812,6 +884,25 @@ void QTiffHandler::convert32BitOrder(void *buffer, int width) } } +void QTiffHandler::rgb48fixup(QImage *image) +{ + Q_ASSERT(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) { + quint16 *dst = reinterpret_cast(scanline); + for (int x = w - 1; x >= 0; --x) { + dst[x * 4 + 3] = 0xffff; + 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; + } +} + bool QTiffHandler::ensureHaveDirectoryCount() const { if (d->directoryCount > 0) diff --git a/src/plugins/imageformats/tiff/qtiffhandler_p.h b/src/plugins/imageformats/tiff/qtiffhandler_p.h index c7b074d..2090e38 100644 --- a/src/plugins/imageformats/tiff/qtiffhandler_p.h +++ b/src/plugins/imageformats/tiff/qtiffhandler_p.h @@ -74,6 +74,7 @@ public: }; private: void convert32BitOrder(void *buffer, int width); + void rgb48fixup(QImage *image); const QScopedPointer d; bool ensureHaveDirectoryCount() const; }; diff --git a/tests/auto/tiff/tst_qtiff.cpp b/tests/auto/tiff/tst_qtiff.cpp index 1a96ab3..9c815d5 100644 --- a/tests/auto/tiff/tst_qtiff.cpp +++ b/tests/auto/tiff/tst_qtiff.cpp @@ -84,6 +84,8 @@ private slots: void tiled_data(); void tiled(); + void readRgba64(); + private: QString prefix; }; @@ -165,6 +167,7 @@ void tst_qtiff::readImage_data() QTest::newRow("tiled_mono") << QString("tiled_mono.tiff") << QSize(64, 64); QTest::newRow("tiled_oddsize_grayscale") << QString("tiled_oddsize_grayscale.tiff") << QSize(59, 71); QTest::newRow("tiled_oddsize_mono") << QString("tiled_oddsize_mono.tiff") << QSize(59, 71); + QTest::newRow("16bpc") << QString("16bpc.tiff") << QSize(64, 46); } void tst_qtiff::readImage() @@ -384,6 +387,9 @@ void tst_qtiff::readWriteNonDestructive_data() QTest::newRow("tiff argb32pm") << QImage::Format_ARGB32_Premultiplied << QImage::Format_ARGB32_Premultiplied << QImageIOHandler::TransformationRotate90; QTest::newRow("tiff rgb32") << QImage::Format_RGB32 << QImage::Format_RGB32 << QImageIOHandler::TransformationRotate270; QTest::newRow("tiff grayscale") << QImage::Format_Grayscale8 << QImage::Format_Grayscale8 << QImageIOHandler::TransformationFlip; + QTest::newRow("tiff rgb64") << QImage::Format_RGBX64 << QImage::Format_RGBX64 << QImageIOHandler::TransformationNone; + QTest::newRow("tiff rgba64") << QImage::Format_RGBA64 << QImage::Format_RGBA64 << QImageIOHandler::TransformationRotate90; + QTest::newRow("tiff rgba64pm") << QImage::Format_RGBA64_Premultiplied << QImage::Format_RGBA64_Premultiplied << QImageIOHandler::TransformationNone; } void tst_qtiff::readWriteNonDestructive() @@ -592,5 +598,16 @@ void tst_qtiff::tiled() QCOMPARE(expectedImage, tiledImage); } +void tst_qtiff::readRgba64() +{ + QString path = prefix + QString("16bpc.tiff"); + QImageReader reader(path); + QVERIFY(reader.canRead()); + QCOMPARE(reader.imageFormat(), QImage::Format_RGBX64); + QImage image = reader.read(); + QVERIFY(!image.isNull()); + QCOMPARE(image.format(), QImage::Format_RGBX64); +} + QTEST_MAIN(tst_qtiff) #include "tst_qtiff.moc" diff --git a/tests/shared/images/tiff.qrc b/tests/shared/images/tiff.qrc index 19675ba..91bbf93 100644 --- a/tests/shared/images/tiff.qrc +++ b/tests/shared/images/tiff.qrc @@ -1,5 +1,6 @@ + tiff/16bpc.tiff tiff/corrupt-data.tif tiff/grayscale-ref.tif tiff/grayscale.tif diff --git a/tests/shared/images/tiff/16bpc.tiff b/tests/shared/images/tiff/16bpc.tiff new file mode 100644 index 0000000..b1ecf26 Binary files /dev/null and b/tests/shared/images/tiff/16bpc.tiff differ -- cgit v1.2.1