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

source code of qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp