Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtiffhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#include <qcolorspace.h>
8#include <qdebug.h>
9#include <qfloat16.h>
10#include <qimage.h>
11#include <qloggingcategory.h>
12#include <qvariant.h>
13#include <qvarlengtharray.h>
14#include <qbuffer.h>
15#include <qfiledevice.h>
16#include <qimagereader.h>
17
18extern "C" {
19#include "tiffio.h"
20}
21
22#include <memory>
23#include <QtCore/q20utility.h>
24
25QT_BEGIN_NAMESPACE
26
27Q_STATIC_LOGGING_CATEGORY(lcTiff, "qt.imageformats.tiff")
28
29namespace {
31 void operator()(TIFF *p) const noexcept
32 { TIFFClose(p); } // unique_ptr only calls us for p != nullptr
33};
34}
35
36using TiffUniquePtr = std::unique_ptr<TIFF, TiffCloseDeleter>;
37
38tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
39{
40 QIODevice *device = static_cast<QIODevice *>(fd);
41 return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
42}
43
44tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
45{
46 return static_cast<QIODevice *>(fd)->write(static_cast<char *>(buf), size);
47}
48
49toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
50{
51 QIODevice *device = static_cast<QIODevice *>(fd);
52 switch (whence) {
53 case SEEK_SET:
54 device->seek(off);
55 break;
56 case SEEK_CUR:
57 device->seek(device->pos() + off);
58 break;
59 case SEEK_END:
60 device->seek(device->size() + off);
61 break;
62 }
63
64 return device->pos();
65}
66
68{
69 return 0;
70}
71
72toff_t qtiffSizeProc(thandle_t fd)
73{
74 return static_cast<QIODevice *>(fd)->size();
75}
76
77int qtiffMapProc(thandle_t fd, void **base, toff_t *size)
78{
79 QIODevice *device = static_cast<QIODevice *>(fd);
80
81 QFileDevice *file = qobject_cast<QFileDevice *>(device);
82 if (file) {
83 *base = file->map(0, file->size());
84 if (*base != nullptr) {
85 *size = file->size();
86 return 1;
87 }
88 } else {
89 QBuffer *buf = qobject_cast<QBuffer *>(device);
90 if (buf) {
91 *base = const_cast<char *>(buf->data().constData());
92 *size = buf->size();
93 return 1;
94 }
95 }
96 return 0;
97}
98
99void qtiffUnmapProc(thandle_t fd, void *base, toff_t /*size*/)
100{
101 QFileDevice *file = qobject_cast<QFileDevice *>(static_cast<QIODevice *>(fd));
102 if (file && base)
103 file->unmap(static_cast<uchar *>(base));
104}
105
106
108{
109public:
112
113 static bool canRead(QIODevice *device);
114 bool openForRead(QIODevice *device);
115 bool readHeaders(QIODevice *device);
116 bool readNextImage(QImage *image); // implementation of one read()
117 void close();
118 TiffUniquePtr openInternal(const char *mode, QIODevice *device);
119#if TIFFLIB_VERSION >= 20221213
120 static int tiffErrorHandler(TIFF *tif, void *user_data, const char *,
121 const char *fmt, va_list ap);
122 static int tiffWarningHandler(TIFF *tif, void *user_data, const char *,
123 const char *fmt, va_list ap);
124#endif
125 static void convert32BitOrder(void *buffer, int width);
126 void rgb48fixup(QImage *image);
127 static void rgb96fixup(QImage *image);
128 void rgbFixup(QImage *image);
129
130 TIFF *tiff = nullptr;
134 QSize size;
135 uint16_t photometric = {}; // no good default, so just value-init
136 bool grayscale = false;
137 bool floatingPoint = false;
138 bool separatePlanes = false;
139 bool headersRead = false;
142};
143
144static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
145{
146 switch (exifOrientation) {
147 case 1: // normal
148 return QImageIOHandler::TransformationNone;
149 case 2: // mirror horizontal
150 return QImageIOHandler::TransformationMirror;
151 case 3: // rotate 180
152 return QImageIOHandler::TransformationRotate180;
153 case 4: // mirror vertical
154 return QImageIOHandler::TransformationFlip;
155 case 5: // mirror horizontal and rotate 270 CW
156 return QImageIOHandler::TransformationFlipAndRotate90;
157 case 6: // rotate 90 CW
158 return QImageIOHandler::TransformationRotate90;
159 case 7: // mirror horizontal and rotate 90 CW
160 return QImageIOHandler::TransformationMirrorAndRotate90;
161 case 8: // rotate 270 CW
162 return QImageIOHandler::TransformationRotate270;
163 }
164 qCWarning(lcTiff, "Invalid EXIF orientation");
165 return QImageIOHandler::TransformationNone;
166}
167
168static int qt2Exif(QImageIOHandler::Transformations transformation)
169{
170 switch (transformation) {
171 case QImageIOHandler::TransformationNone:
172 return 1;
173 case QImageIOHandler::TransformationMirror:
174 return 2;
175 case QImageIOHandler::TransformationRotate180:
176 return 3;
177 case QImageIOHandler::TransformationFlip:
178 return 4;
179 case QImageIOHandler::TransformationFlipAndRotate90:
180 return 5;
181 case QImageIOHandler::TransformationRotate90:
182 return 6;
183 case QImageIOHandler::TransformationMirrorAndRotate90:
184 return 7;
185 case QImageIOHandler::TransformationRotate270:
186 return 8;
187 }
188 qCWarning(lcTiff, "Invalid Qt image transformation");
189 return 1;
190}
191
193 = default;
194
199
201{
202 if (tiff)
203 TIFFClose(tiff);
204 tiff = 0;
205 headersRead = false;
206}
207
208TiffUniquePtr QTiffHandlerPrivate::openInternal(const char *mode, QIODevice *device)
209{
210// TIFFLIB_VERSION 20221213 -> 4.5.0
211#if TIFFLIB_VERSION >= 20221213
212 TIFFOpenOptions *opts = TIFFOpenOptionsAlloc();
213 TIFFOpenOptionsSetErrorHandlerExtR(opts, &tiffErrorHandler, this);
214 TIFFOpenOptionsSetWarningHandlerExtR(opts, &tiffWarningHandler, this);
215
216#if TIFFLIB_AT_LEAST(4, 7, 0)
217 quint64 maxAlloc = quint64(QImageReader::allocationLimit()) << 20;
218 if (maxAlloc) {
219 maxAlloc = qMin(maxAlloc, quint64(std::numeric_limits<tmsize_t>::max()));
220 TIFFOpenOptionsSetMaxCumulatedMemAlloc(opts, tmsize_t(maxAlloc));
221 }
222#endif
223
224 auto handle = TIFFClientOpenExt("foo",
225 mode,
226 device,
227 qtiffReadProc,
228 qtiffWriteProc,
229 qtiffSeekProc,
230 qtiffCloseProc,
231 qtiffSizeProc,
232 qtiffMapProc,
233 qtiffUnmapProc,
234 opts);
235 TIFFOpenOptionsFree(opts);
236#else
237 auto handle = TIFFClientOpen("foo",
238 mode,
239 device,
240 qtiffReadProc,
241 qtiffWriteProc,
242 qtiffSeekProc,
243 qtiffCloseProc,
244 qtiffSizeProc,
245 qtiffMapProc,
246 qtiffUnmapProc);
247#endif
248 return TiffUniquePtr{handle};
249}
250
251
252#if TIFFLIB_VERSION >= 20221213
253int QTiffHandlerPrivate::tiffErrorHandler(TIFF *tif, void *user_data, const char *,
254 const char *fmt, va_list ap)
255{
256 const auto priv = static_cast<QTiffHandlerPrivate *>(user_data);
257 if (!priv || priv->tiff != tif)
258 return 0;
259 qCCritical(lcTiff) << QString::vasprintf(fmt, ap);
260 return 1;
261}
262
263int QTiffHandlerPrivate::tiffWarningHandler(TIFF *tif, void *user_data, const char *,
264 const char *fmt, va_list ap)
265{
266 const auto priv = static_cast<QTiffHandlerPrivate *>(user_data);
267 if (!priv || priv->tiff != tif)
268 return 0;
269 qCWarning(lcTiff) << QString::vasprintf(fmt, ap);
270 return 1;
271}
272#endif
273
274bool QTiffHandlerPrivate::canRead(QIODevice *device)
275{
276 if (!device) {
277 qCWarning(lcTiff, "QTiffHandler::canRead() called with no device");
278 return false;
279 }
280
281 // current implementation uses TIFFClientOpen which needs to be
282 // able to seek, so sequential devices are not supported
283 char h[4];
284 if (device->peek(h, 4) != 4)
285 return false;
286 if ((h[0] == 0x49 && h[1] == 0x49) && (h[2] == 0x2a || h[2] == 0x2b) && h[3] == 0)
287 return true; // Little endian, classic or bigtiff
288 if ((h[0] == 0x4d && h[1] == 0x4d) && h[2] == 0 && (h[3] == 0x2a || h[3] == 0x2b))
289 return true; // Big endian, classic or bigtiff
290 return false;
291}
292
293bool QTiffHandlerPrivate::openForRead(QIODevice *device)
294{
295 if (tiff)
296 return true;
297
298 if (!canRead(device))
299 return false;
300
301 tiff = openInternal("rh", device).release();
302 return tiff != nullptr;
303}
304
305bool QTiffHandlerPrivate::readHeaders(QIODevice *device)
306{
307 if (headersRead)
308 return true;
309
310 if (!openForRead(device))
311 return false;
312
313 if (!TIFFSetDirectory(tiff, currentDirectory)) {
314 close();
315 return false;
316 }
317
318 uint32_t width;
319 uint32_t height;
320 if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
321 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
322 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
323 close();
324 return false;
325 }
326 size = QSize(width, height);
327
328 uint16_t orientationTag;
329 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
330 transformation = exif2Qt(orientationTag);
331
332 // BitsPerSample defaults to 1 according to the TIFF spec.
333 uint16_t bitPerSample;
334 if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample))
335 bitPerSample = 1;
336 uint16_t samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel
337 if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
338 samplesPerPixel = 1;
339 uint16_t sampleFormat;
340 if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat))
341 sampleFormat = SAMPLEFORMAT_VOID;
342 floatingPoint = (sampleFormat == SAMPLEFORMAT_IEEEFP);
343
344 uint16_t planarConfig;
345 if (!TIFFGetField(tiff, TIFFTAG_PLANARCONFIG, &planarConfig))
346 planarConfig = PLANARCONFIG_CONTIG;
347 separatePlanes = planarConfig == PLANARCONFIG_SEPARATE && samplesPerPixel > 1;
348
349 grayscale = photometric == PHOTOMETRIC_MINISBLACK || photometric == PHOTOMETRIC_MINISWHITE;
350
351 if (grayscale && bitPerSample == 1 && samplesPerPixel == 1)
352 format = QImage::Format_Mono;
353 else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1)
354 format = QImage::Format_Grayscale8;
355 else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1 && !floatingPoint)
356 format = QImage::Format_Grayscale16;
357 else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1)
358 format = QImage::Format_Indexed8;
359 else if (samplesPerPixel < 4) {
360 bool regular = (samplesPerPixel != 2) && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK);
361 if (bitPerSample == 16 && regular)
362 format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
363 else if (bitPerSample == 32 && floatingPoint && regular)
364 format = QImage::Format_RGBX32FPx4;
365 else
366 format = QImage::Format_RGB32;
367 } else {
368 uint16_t count;
369 uint16_t *extrasamples;
370 // If there is any definition of the alpha-channel, libtiff will return premultiplied
371 // data to us. If there is none, libtiff will not touch it and we assume it to be
372 // non-premultiplied, matching behavior of tested image editors, and how older Qt
373 // versions used to save it.
374 bool premultiplied = true;
375 bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
376 if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
377 premultiplied = false;
378
379 if (bitPerSample == 16 && photometric == PHOTOMETRIC_RGB) {
380 // We read 64-bit raw, so unassoc remains unpremultiplied.
381 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
382 premultiplied = false;
383 if (premultiplied)
384 format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied;
385 else
386 format = floatingPoint ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
387 } else if (bitPerSample == 32 && floatingPoint && photometric == PHOTOMETRIC_RGB) {
388 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
389 premultiplied = false;
390 if (premultiplied)
391 format = QImage::Format_RGBA32FPx4_Premultiplied;
392 else
393 format = QImage::Format_RGBA32FPx4;
394 } else if (samplesPerPixel == 4 && bitPerSample == 8 && photometric == PHOTOMETRIC_SEPARATED) {
395 uint16_t inkSet;
396 const bool gotInkSetField = TIFFGetField(tiff, TIFFTAG_INKSET, &inkSet);
397 if (!gotInkSetField || inkSet == INKSET_CMYK) {
398 format = QImage::Format_CMYK8888;
399 } else {
400 close();
401 return false;
402 }
403 } else {
404 if (premultiplied)
405 format = QImage::Format_ARGB32_Premultiplied;
406 else
407 format = QImage::Format_ARGB32;
408 }
409 }
410
411 headersRead = true;
412 return true;
413}
414
420
422{
423 if (d->tiff)
424 return true;
426 setFormat("tiff");
427 return true;
428 }
429 return false;
430}
431
432bool QTiffHandler::canRead(QIODevice *device)
433{
435}
436
437bool QTiffHandler::read(QImage *image)
438{
439 // Open file and read headers if it hasn't already been done.
440 if (!d->readHeaders(device()))
441 return false;
442
443 if (!d->readNextImage(image)) {
444 d->close();
445 return false;
446 }
447
448 return true;
449}
450
451bool QTiffHandlerPrivate::readNextImage(QImage *image)
452{
453 if (!QImageIOHandler::allocateImage(size, format, image))
454 return false;
455
456 // Check for corrupt images early, before libtiff sinks time into parsing:
457 if (TIFFIsTiled(tiff) && TIFFTileSize64(tiff) > uint64_t(image->sizeInBytes()))
458 return false;
459
460 const quint32 width = size.width();
461 const quint32 height = size.height();
462
463 // Setup color tables
464 if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
465 if (format == QImage::Format_Mono) {
466 QList<QRgb> colortable(2);
467 if (photometric == PHOTOMETRIC_MINISBLACK) {
468 colortable[0] = 0xff000000;
469 colortable[1] = 0xffffffff;
470 } else {
471 colortable[0] = 0xffffffff;
472 colortable[1] = 0xff000000;
473 }
474 image->setColorTable(colortable);
475 } else if (format == QImage::Format_Indexed8) {
476 const uint16_t tableSize = 256;
477 QList<QRgb> qtColorTable(tableSize);
478 if (grayscale) {
479 for (int i = 0; i<tableSize; ++i) {
480 const int c = (photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
481 qtColorTable[i] = qRgb(c, c, c);
482 }
483 } else {
484 // create the color table
485 uint16_t *redTable = 0;
486 uint16_t *greenTable = 0;
487 uint16_t *blueTable = 0;
488 if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable))
489 return false;
490 if (!redTable || !greenTable || !blueTable)
491 return false;
492
493 for (int i = 0; i<tableSize ;++i) {
494 // emulate libtiff behavior for 16->8 bit color map conversion: just ignore the lower 8 bits
495 const int red = redTable[i] >> 8;
496 const int green = greenTable[i] >> 8;
497 const int blue = blueTable[i] >> 8;
498 qtColorTable[i] = qRgb(red, green, blue);
499 }
500 }
501 image->setColorTable(qtColorTable);
502 // free redTable, greenTable and greenTable done by libtiff
503 }
504 }
505 bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
506 bool format16bit = (format == QImage::Format_Grayscale16);
507 bool formatCmyk32bit = (format == QImage::Format_CMYK8888);
508 bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
509 bool format64fp = (format == QImage::Format_RGBX16FPx4 || format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA16FPx4_Premultiplied);
510 bool format128fp = (format == QImage::Format_RGBX32FPx4 || format == QImage::Format_RGBA32FPx4 || format == QImage::Format_RGBA32FPx4_Premultiplied);
511
512 // Formats we read directly, instead of over RGBA32:
513 if (format8bit || format16bit || formatCmyk32bit || format64bit || format64fp || format128fp) {
514 if (separatePlanes)
515 return false; // the direct-read path assumes interleaved samples
516 image->fill(0); // scanlines or tiles may fill partially, so ensure initialized
517
518 int bytesPerPixel = image->depth() / 8;
519 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
520 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 6 : 2;
521 else if (format == QImage::Format_RGBX32FPx4)
522 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 12 : 4;
523
524 if (TIFFIsTiled(tiff)) {
525 quint32 tileWidth, tileLength;
526 if (!TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth)
527 || !TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength)
528 || !tileWidth || !tileLength || tileWidth % 16 || tileLength % 16)
529 {
530 return false;
531 }
532 quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
533 quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
534 tmsize_t byteTileSize = TIFFTileSize(tiff);
535 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth)
536 return false;
537 uchar *buf = (uchar *)_TIFFmalloc(byteTileSize);
538 if (!buf)
539 return false;
540 for (quint32 y = 0; y < height; y += tileLength) {
541 for (quint32 x = 0; x < width; x += tileWidth) {
542 if (TIFFReadTile(tiff, buf, x, y, 0, 0) < 0) {
543 _TIFFfree(buf);
544 return false;
545 }
546 quint32 linesToCopy = qMin(tileLength, height - y);
547 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
548 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
549 for (quint32 i = 0; i < linesToCopy; i++) {
550 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
551 }
552 }
553 }
554 _TIFFfree(buf);
555 } else {
556 if (image->bytesPerLine() < TIFFScanlineSize(tiff))
557 return false;
558 for (uint32_t y=0; y<height; ++y) {
559 if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0)
560 return false;
561 }
562 }
563 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
564 if (photometric == PHOTOMETRIC_RGB)
565 rgb48fixup(image);
566 else
567 rgbFixup(image);
568 } else if (format == QImage::Format_RGBX32FPx4) {
569 if (photometric == PHOTOMETRIC_RGB)
570 rgb96fixup(image);
571 else
572 rgbFixup(image);
573 }
574 } else {
575 const int stopOnError = 1;
576 if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32_t *>(image->bits()),
577 qt2Exif(transformation), stopOnError))
578 {
579 for (uint32_t y=0; y<height; ++y)
580 convert32BitOrder(image->scanLine(y), width);
581 } else {
582 return false;
583 }
584 }
585
586
587 float resX = 0;
588 float resY = 0;
589 uint16_t resUnit;
590 if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
591 resUnit = RESUNIT_INCH;
592
593 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
594 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
595
596 switch(resUnit) {
597 case RESUNIT_CENTIMETER:
598 image->setDotsPerMeterX(qRound(resX * 100));
599 image->setDotsPerMeterY(qRound(resY * 100));
600 break;
601 case RESUNIT_INCH:
602 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
603 image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
604 break;
605 default:
606 // do nothing as defaults have already
607 // been set within the QImage class
608 break;
609 }
610 }
611
612 uint32_t count;
613 void *profile;
614 if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &count, &profile)) {
615 QByteArray iccProfile(reinterpret_cast<const char *>(profile), count);
616 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
617 }
618 // We do not handle colorimetric metadat not on ICC profile form, it seems to be a lot
619 // less common, and would need additional API in QColorSpace.
620
621 return true;
622}
623
624static bool checkGrayscale(const QList<QRgb> &colorTable)
625{
626 if (colorTable.size() != 256)
627 return false;
628
629 const bool increasing = (colorTable.at(0) == 0xff000000);
630 for (int i = 0; i < 256; ++i) {
631 if ((increasing && colorTable.at(i) != qRgb(i, i, i))
632 || (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i)))
633 return false;
634 }
635 return true;
636}
637
638static QList<QRgb> effectiveColorTable(const QImage &image)
639{
640 QList<QRgb> colors;
641 switch (image.format()) {
642 case QImage::Format_Indexed8:
643 colors = image.colorTable();
644 break;
645 case QImage::Format_Alpha8:
646 colors.resize(256);
647 for (int i = 0; i < 256; ++i)
648 colors[i] = qRgba(0, 0, 0, i);
649 break;
650 case QImage::Format_Grayscale8:
651 case QImage::Format_Grayscale16:
652 colors.resize(256);
653 for (int i = 0; i < 256; ++i)
654 colors[i] = qRgb(i, i, i);
655 break;
656 default:
657 Q_UNREACHABLE();
658 }
659 return colors;
660}
661
662static quint32 defaultStripSize(const TiffUniquePtr &p)
663{
664 auto tiff = p.get();
665 // Aim for 4MB strips
666 qint64 scanSize = qMax(qint64(1), qint64(TIFFScanlineSize(tiff)));
667 qint64 numRows = (4 * 1024 * 1024) / scanSize;
668 quint32 reqSize = static_cast<quint32>(qBound(qint64(1), numRows, qint64(UINT_MAX)));
669 return TIFFDefaultStripSize(tiff, reqSize);
670}
671
672bool QTiffHandler::write(const QImage &image)
673{
674 if (!device()->isWritable())
675 return false;
676
677 const auto tiff = d->openInternal("wB", device());
678 if (!tiff)
679 return false;
680
681 // image.scanLine() returns const uchar*, but TIFFWriteScanline wants non-const void*, adapt:
682 const auto writeScanline = [&](const void *line, int y) {
683 return TIFFWriteScanline(tiff.get(), const_cast<void*>(line), y);
684 };
685 // this one is just for DRYing:
686 const auto setField = [&] (uint32_t tag, auto&&...args) {
687 return TIFFSetField(tiff.get(), tag, std::forward<decltype(args)>(args)...);
688 };
689
690 const int width = image.width();
691 const int height = image.height();
692
693 if (!setField(TIFFTAG_IMAGEWIDTH, width)
694 || !setField(TIFFTAG_IMAGELENGTH, height)
695 || !setField(TIFFTAG_COMPRESSION, toLibTiffCompression(d->compression))
696 || !setField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
697 {
698 return false;
699 }
700
701 // set the resolution
702 bool resolutionSet = false;
703 const int dotPerMeterX = image.dotsPerMeterX();
704 const int dotPerMeterY = image.dotsPerMeterY();
705 if ((dotPerMeterX % 100) == 0
706 && (dotPerMeterY % 100) == 0) {
707 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
708 && setField(TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
709 && setField(TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
710 } else {
711 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
712 && setField(TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
713 && setField(TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
714 }
715 if (!resolutionSet)
716 return false;
717
718 // set the orienataion
719 if (!setField(TIFFTAG_ORIENTATION, qt2Exif(d->transformation)))
720 return false;
721
722 // set color space
723 const QByteArray iccProfile = image.colorSpace().iccProfile();
724 if (!iccProfile.isEmpty()) {
725 const auto size = static_cast<uint32_t>(iccProfile.size());
726 if (!q20::cmp_equal(size, iccProfile.size()) // narrowed
727 || !setField(TIFFTAG_ICCPROFILE, size, iccProfile.data()))
728 {
729 return false;
730 }
731 }
732
733 // configure image depth
734 const QImage::Format format = image.format();
735 if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
736 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
737 if (image.colorTable().value(0) == 0xffffffff)
738 photometric = PHOTOMETRIC_MINISWHITE;
739 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
740 || !setField(TIFFTAG_BITSPERSAMPLE, 1)
741 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
742 {
743 return false;
744 }
745
746 // try to do the conversion in chunks no greater than 16 MB
747 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
748 const int chunkHeight = qMax(height / chunks, 1);
749
750 int y = 0;
751 while (y < height) {
752 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
753
754 int chunkStart = y;
755 int chunkEnd = y + chunk.height();
756 while (y < chunkEnd) {
757 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
758 return false;
759 ++y;
760 }
761 }
762 } else if (format == QImage::Format_Indexed8
763 || format == QImage::Format_Grayscale8
764 || format == QImage::Format_Grayscale16
765 || format == QImage::Format_Alpha8) {
766 QList<QRgb> colorTable = effectiveColorTable(image);
767 bool isGrayscale = checkGrayscale(colorTable);
768 if (isGrayscale) {
769 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
770 if (colorTable.at(0) == 0xffffffff)
771 photometric = PHOTOMETRIC_MINISWHITE;
772 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
773 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth())
774 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
775 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
776 {
777 return false;
778 }
779 } else {
780 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
781 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
782 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
783 {
784 return false;
785 }
786 // write the color table; libtiff reads 256 entries
787 constexpr qsizetype colorMapSize = 256;
788 const int tableSize = int(qMin(colorTable.size(), colorMapSize));
789 QVarLengthArray<uint16_t> redTable(colorMapSize, 0);
790 QVarLengthArray<uint16_t> greenTable(colorMapSize, 0);
791 QVarLengthArray<uint16_t> blueTable(colorMapSize, 0);
792
793 // set the color table
794 for (int i = 0; i<tableSize; ++i) {
795 const QRgb color = colorTable.at(i);
796 redTable[i] = qRed(color) * 257;
797 greenTable[i] = qGreen(color) * 257;
798 blueTable[i] = qBlue(color) * 257;
799 }
800
801 if (!setField(TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data()))
802 return false;
803 }
804
805 //// write the data
806 for (int y = 0; y < height; ++y) {
807 if (writeScanline(image.scanLine(y), y) != 1)
808 return false;
809 }
810 } else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
811 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
812 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
813 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
814 || !setField(TIFFTAG_SAMPLEFORMAT,
815 format == QImage::Format_RGBX64
816 ? SAMPLEFORMAT_UINT
817 : SAMPLEFORMAT_IEEEFP)
818 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
819 {
820 return false;
821 }
822 std::unique_ptr<quint16[]> rgb48line(new quint16[width * 3]);
823 for (int y = 0; y < height; ++y) {
824 const quint16 *srcLine = reinterpret_cast<const quint16 *>(image.constScanLine(y));
825 for (int x = 0; x < width; ++x) {
826 rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
827 rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
828 rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
829 }
830
831 if (writeScanline(rgb48line.get(), y) != 1)
832 return false;
833 }
834 } else if (format == QImage::Format_RGBA64
835 || format == QImage::Format_RGBA64_Premultiplied) {
836 const bool premultiplied = image.format() != QImage::Format_RGBA64;
837 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
838 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
839 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
840 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
841 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
842 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
843 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
844 {
845 return false;
846 }
847 for (int y = 0; y < height; ++y) {
848 if (writeScanline(image.scanLine(y), y) != 1)
849 return false;
850 }
851 } else if (format == QImage::Format_RGBX32FPx4) {
852 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
853 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
854 || !setField(TIFFTAG_BITSPERSAMPLE, 32)
855 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
856 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
857 {
858 return false;
859 }
860 std::unique_ptr<float[]> line(new float[width * 3]);
861 for (int y = 0; y < height; ++y) {
862 const float *srcLine = reinterpret_cast<const float *>(image.constScanLine(y));
863 for (int x = 0; x < width; ++x) {
864 line[x * 3 + 0] = srcLine[x * 4 + 0];
865 line[x * 3 + 1] = srcLine[x * 4 + 1];
866 line[x * 3 + 2] = srcLine[x * 4 + 2];
867 }
868
869 if (writeScanline(line.get(), y) != 1)
870 return false;
871 }
872 } else if (format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA32FPx4
873 || format == QImage::Format_RGBA16FPx4_Premultiplied
874 || format == QImage::Format_RGBA32FPx4_Premultiplied) {
875 const bool premultiplied = image.format() != QImage::Format_RGBA16FPx4 && image.format() != QImage::Format_RGBA32FPx4;
876 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
877 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
878 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
879 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth() == 64 ? 16 : 32)
880 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
881 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
882 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
883 {
884 return false;
885 }
886 for (int y = 0; y < height; ++y) {
887 if (writeScanline(image.scanLine(y), y) != 1)
888 return false;
889 }
890 } else if (format == QImage::Format_CMYK8888) {
891 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED)
892 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
893 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
894 || !setField(TIFFTAG_INKSET, INKSET_CMYK)
895 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
896 {
897 return false;
898 }
899
900 for (int y = 0; y < image.height(); ++y) {
901 if (writeScanline(image.scanLine(y), y) != 1)
902 return false;
903 }
904 } else if (!image.hasAlphaChannel()) {
905 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
906 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
907 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
908 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
909 {
910 return false;
911 }
912 // try to do the RGB888 conversion in chunks no greater than 16 MB
913 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
914 const int chunkHeight = qMax(height / chunks, 1);
915
916 int y = 0;
917 while (y < height) {
918 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
919
920 int chunkStart = y;
921 int chunkEnd = y + chunk.height();
922 while (y < chunkEnd) {
923 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
924 return false;
925 ++y;
926 }
927 }
928 } else {
929 const bool premultiplied = image.format() != QImage::Format_ARGB32
930 && image.format() != QImage::Format_RGBA8888;
931 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
932 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
933 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
934 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
935 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
936 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
937 {
938 return false;
939 }
940 // try to do the RGBA8888 conversion in chunks no greater than 16 MB
941 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
942 const int chunkHeight = qMax(height / chunks, 1);
943
944 const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
945 : QImage::Format_RGBA8888;
946 int y = 0;
947 while (y < height) {
948 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
949
950 int chunkStart = y;
951 int chunkEnd = y + chunk.height();
952 while (y < chunkEnd) {
953 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
954 return false;
955 ++y;
956 }
957 }
958 }
959
960 return true;
961}
962
963QVariant QTiffHandler::option(ImageOption option) const
964{
965 if (option == Size && canRead()) {
966 if (d->readHeaders(device()))
967 return d->size;
968 } else if (option == CompressionRatio) {
969 return int(d->compression);
970 } else if (option == ImageFormat) {
971 if (d->readHeaders(device()))
972 return d->format;
973 } else if (option == ImageTransformation) {
974 if (d->readHeaders(device()))
975 return int(d->transformation);
976 }
977 return QVariant();
978}
979
980void QTiffHandler::setOption(ImageOption option, const QVariant &value)
981{
982 if (option == CompressionRatio && value.metaType().id() == QMetaType::Int)
983 d->compression = static_cast<Compression>(qBound(0, value.toInt(), 5));
984 if (option == ImageTransformation) {
985 int transformation = value.toInt();
986 if (transformation > 0 && transformation < 8)
987 d->transformation = QImageIOHandler::Transformations(transformation);
988 }
989}
990
991bool QTiffHandler::supportsOption(ImageOption option) const
992{
993 return option == CompressionRatio
994 || option == Size
995 || option == ImageFormat
996 || option == ImageTransformation;
997}
998
1000{
1001 if (!ensureHaveDirectoryCount())
1002 return false;
1003 if (d->currentDirectory >= d->directoryCount - 1)
1004 return false;
1005
1006 d->headersRead = false;
1007 ++d->currentDirectory;
1008 return true;
1009}
1010
1011bool QTiffHandler::jumpToImage(int imageNumber)
1012{
1013 if (!ensureHaveDirectoryCount())
1014 return false;
1015 if (imageNumber < 0 || imageNumber >= d->directoryCount)
1016 return false;
1017
1018 if (d->currentDirectory != imageNumber) {
1019 d->headersRead = false;
1020 d->currentDirectory = imageNumber;
1021 }
1022 return true;
1023}
1024
1026{
1027 if (!ensureHaveDirectoryCount())
1028 return 1;
1029
1030 return d->directoryCount;
1031}
1032
1034{
1035 return d->currentDirectory;
1036}
1037
1038void QTiffHandlerPrivate::convert32BitOrder(void *buffer, int width)
1039{
1040 uint32_t *target = reinterpret_cast<uint32_t *>(buffer);
1041 for (int32_t x=0; x<width; ++x) {
1042 uint32_t p = target[x];
1043 // convert between ARGB and ABGR
1044 target[x] = (p & 0xff000000)
1045 | ((p & 0x00ff0000) >> 16)
1046 | (p & 0x0000ff00)
1047 | ((p & 0x000000ff) << 16);
1048 }
1049}
1050
1051void QTiffHandlerPrivate::rgb48fixup(QImage *image)
1052{
1053 Q_ASSERT(image->depth() == 64);
1054 const int h = image->height();
1055 const int w = image->width();
1056 uchar *scanline = image->bits();
1057 const qsizetype bpl = image->bytesPerLine();
1058 quint16 mask = 0xffff;
1059 const qfloat16 fp_mask = qfloat16(1.0f);
1060 if (floatingPoint)
1061 memcpy(&mask, &fp_mask, 2);
1062 for (int y = 0; y < h; ++y) {
1063 quint16 *dst = reinterpret_cast<uint16_t *>(scanline);
1064 for (int x = w - 1; x >= 0; --x) {
1065 dst[x * 4 + 3] = mask;
1066 dst[x * 4 + 2] = dst[x * 3 + 2];
1067 dst[x * 4 + 1] = dst[x * 3 + 1];
1068 dst[x * 4 + 0] = dst[x * 3 + 0];
1069 }
1070 scanline += bpl;
1071 }
1072}
1073
1074void QTiffHandlerPrivate::rgb96fixup(QImage *image)
1075{
1076 Q_ASSERT(image->depth() == 128);
1077 const int h = image->height();
1078 const int w = image->width();
1079 uchar *scanline = image->bits();
1080 const qsizetype bpl = image->bytesPerLine();
1081 for (int y = 0; y < h; ++y) {
1082 float *dst = reinterpret_cast<float *>(scanline);
1083 for (int x = w - 1; x >= 0; --x) {
1084 dst[x * 4 + 3] = 1.0f;
1085 dst[x * 4 + 2] = dst[x * 3 + 2];
1086 dst[x * 4 + 1] = dst[x * 3 + 1];
1087 dst[x * 4 + 0] = dst[x * 3 + 0];
1088 }
1089 scanline += bpl;
1090 }
1091}
1092
1093void QTiffHandlerPrivate::rgbFixup(QImage *image)
1094{
1095 if (image->depth() == 64) {
1096 const int h = image->height();
1097 const int w = image->width();
1098 uchar *scanline = image->bits();
1099 const qsizetype bpl = image->bytesPerLine();
1100 quint16 mask = 0xffff;
1101 const qfloat16 fp_mask = qfloat16(1.0f);
1102 if (floatingPoint)
1103 memcpy(&mask, &fp_mask, 2);
1104 for (int y = 0; y < h; ++y) {
1105 quint16 *dst = reinterpret_cast<quint16 *>(scanline);
1106 for (int x = w - 1; x >= 0; --x) {
1107 dst[x * 4 + 3] = mask;
1108 dst[x * 4 + 2] = dst[x];
1109 dst[x * 4 + 1] = dst[x];
1110 dst[x * 4 + 0] = dst[x];
1111 }
1112 scanline += bpl;
1113 }
1114 } else {
1115 Q_ASSERT(floatingPoint); // depth 128 is only 32-bit float (RGBX32FPx4)
1116 const int h = image->height();
1117 const int w = image->width();
1118 uchar *scanline = image->bits();
1119 const qsizetype bpl = image->bytesPerLine();
1120 for (int y = 0; y < h; ++y) {
1121 float *dst = reinterpret_cast<float *>(scanline);
1122 for (int x = w - 1; x >= 0; --x) {
1123 dst[x * 4 + 3] = 1.0f;
1124 dst[x * 4 + 2] = dst[x];
1125 dst[x * 4 + 1] = dst[x];
1126 dst[x * 4 + 0] = dst[x];
1127 }
1128 scanline += bpl;
1129 }
1130 }
1131}
1132
1133bool QTiffHandler::ensureHaveDirectoryCount() const
1134{
1135 if (d->directoryCount > 0)
1136 return true;
1137
1138 const auto tiff = d->openInternal("rh", device());
1139
1140 if (!tiff) {
1141 device()->reset();
1142 return false;
1143 }
1144
1145 while (TIFFReadDirectory(tiff.get()))
1146 ++d->directoryCount;
1147 device()->reset();
1148 return true;
1149}
1150
1151int QTiffHandler::toLibTiffCompression(Compression compression) const
1152{
1153 switch (compression) {
1154 case Compression::None:
1155 return COMPRESSION_NONE;
1156 case Compression::Lzw:
1157 return COMPRESSION_LZW;
1159 return COMPRESSION_CCITTRLE;
1161 return COMPRESSION_CCITTFAX3;
1163 return COMPRESSION_CCITTFAX4;
1164 case Compression::Jpeg:
1165 return COMPRESSION_JPEG;
1166 }
1167 qCWarning(lcTiff, "Invalid compression value (%d)", int(compression));
1168 return COMPRESSION_NONE;
1169}
1170
1171QT_END_NAMESPACE
QIODevice * device() const
Returns the device currently assigned to QImageReader, or \nullptr if no device has been assigned.
\inmodule QtGui
Definition qimage.h:38
static void rgb96fixup(QImage *image)
static void convert32BitOrder(void *buffer, int width)
static bool canRead(QIODevice *device)
TiffUniquePtr openInternal(const char *mode, QIODevice *device)
bool readHeaders(QIODevice *device)
QImageIOHandler::Transformations transformation
QTiffHandler::Compression compression
bool openForRead(QIODevice *device)
bool readNextImage(QImage *image)
void rgb48fixup(QImage *image)
void rgbFixup(QImage *image)
bool jumpToNextImage() override
For image formats that support animation, this function jumps to the next image.
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
bool jumpToImage(int imageNumber) override
For image formats that support animation, this function jumps to the image whose sequence number is i...
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
int currentImageNumber() const override
For image formats that support animation, this function returns the sequence number of the current im...
#define qCCritical(category,...)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
static quint32 defaultStripSize(const TiffUniquePtr &p)
toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
void qtiffUnmapProc(thandle_t fd, void *base, toff_t)
tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
toff_t qtiffSizeProc(thandle_t fd)
static bool checkGrayscale(const QList< QRgb > &colorTable)
static QList< QRgb > effectiveColorTable(const QImage &image)
static int qt2Exif(QImageIOHandler::Transformations transformation)
int qtiffCloseProc(thandle_t)
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
int qtiffMapProc(thandle_t fd, void **base, toff_t *size)
void operator()(TIFF *p) const noexcept