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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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