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