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 if (bitPerSample == 16 && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK))
348 format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
349 else if (bitPerSample == 32 && floatingPoint && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK))
350 format = QImage::Format_RGBX32FPx4;
351 else
352 format = QImage::Format_RGB32;
353 else {
354 uint16_t count;
355 uint16_t *extrasamples;
356 // If there is any definition of the alpha-channel, libtiff will return premultiplied
357 // data to us. If there is none, libtiff will not touch it and we assume it to be
358 // non-premultiplied, matching behavior of tested image editors, and how older Qt
359 // versions used to save it.
360 bool premultiplied = true;
361 bool gotField = TIFFGetField(tif: tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
362 if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
363 premultiplied = false;
364
365 if (bitPerSample == 16 && photometric == PHOTOMETRIC_RGB) {
366 // We read 64-bit raw, so unassoc remains unpremultiplied.
367 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
368 premultiplied = false;
369 if (premultiplied)
370 format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied;
371 else
372 format = floatingPoint ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
373 } else if (bitPerSample == 32 && floatingPoint && photometric == PHOTOMETRIC_RGB) {
374 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
375 premultiplied = false;
376 if (premultiplied)
377 format = QImage::Format_RGBA32FPx4_Premultiplied;
378 else
379 format = QImage::Format_RGBA32FPx4;
380 } else if (samplesPerPixel == 4 && bitPerSample == 8 && photometric == PHOTOMETRIC_SEPARATED) {
381 uint16_t inkSet;
382 const bool gotInkSetField = TIFFGetField(tif: tiff, TIFFTAG_INKSET, &inkSet);
383 if (!gotInkSetField || inkSet == INKSET_CMYK) {
384 format = QImage::Format_CMYK8888;
385 } else {
386 close();
387 return false;
388 }
389 } else {
390 if (premultiplied)
391 format = QImage::Format_ARGB32_Premultiplied;
392 else
393 format = QImage::Format_ARGB32;
394 }
395 }
396
397 headersRead = true;
398 return true;
399}
400
401QTiffHandler::QTiffHandler()
402 : QImageIOHandler()
403 , d(new QTiffHandlerPrivate)
404{
405}
406
407bool QTiffHandler::canRead() const
408{
409 if (d->tiff)
410 return true;
411 if (QTiffHandlerPrivate::canRead(device: device())) {
412 setFormat("tiff");
413 return true;
414 }
415 return false;
416}
417
418bool QTiffHandler::canRead(QIODevice *device)
419{
420 return QTiffHandlerPrivate::canRead(device);
421}
422
423bool QTiffHandler::read(QImage *image)
424{
425 // Open file and read headers if it hasn't already been done.
426 if (!d->readHeaders(device: device()))
427 return false;
428
429 QImage::Format format = d->format;
430
431 if (!QImageIOHandler::allocateImage(size: d->size, format, image)) {
432 d->close();
433 return false;
434 }
435
436 TIFF *const tiff = d->tiff;
437 if (TIFFIsTiled(tiff) && TIFFTileSize64(tif: tiff) > uint64_t(image->sizeInBytes())) // Corrupt image
438 return false;
439 const quint32 width = d->size.width();
440 const quint32 height = d->size.height();
441
442 // Setup color tables
443 if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
444 if (format == QImage::Format_Mono) {
445 QList<QRgb> colortable(2);
446 if (d->photometric == PHOTOMETRIC_MINISBLACK) {
447 colortable[0] = 0xff000000;
448 colortable[1] = 0xffffffff;
449 } else {
450 colortable[0] = 0xffffffff;
451 colortable[1] = 0xff000000;
452 }
453 image->setColorTable(colortable);
454 } else if (format == QImage::Format_Indexed8) {
455 const uint16_t tableSize = 256;
456 QList<QRgb> qtColorTable(tableSize);
457 if (d->grayscale) {
458 for (int i = 0; i<tableSize; ++i) {
459 const int c = (d->photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
460 qtColorTable[i] = qRgb(r: c, g: c, b: c);
461 }
462 } else {
463 // create the color table
464 uint16_t *redTable = 0;
465 uint16_t *greenTable = 0;
466 uint16_t *blueTable = 0;
467 if (!TIFFGetField(tif: tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) {
468 d->close();
469 return false;
470 }
471 if (!redTable || !greenTable || !blueTable) {
472 d->close();
473 return false;
474 }
475
476 for (int i = 0; i<tableSize ;++i) {
477 // emulate libtiff behavior for 16->8 bit color map conversion: just ignore the lower 8 bits
478 const int red = redTable[i] >> 8;
479 const int green = greenTable[i] >> 8;
480 const int blue = blueTable[i] >> 8;
481 qtColorTable[i] = qRgb(r: red, g: green, b: blue);
482 }
483 }
484 image->setColorTable(qtColorTable);
485 // free redTable, greenTable and greenTable done by libtiff
486 }
487 }
488 bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
489 bool format16bit = (format == QImage::Format_Grayscale16);
490 bool formatCmyk32bit = (format == QImage::Format_CMYK8888);
491 bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
492 bool format64fp = (format == QImage::Format_RGBX16FPx4 || format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA16FPx4_Premultiplied);
493 bool format128fp = (format == QImage::Format_RGBX32FPx4 || format == QImage::Format_RGBA32FPx4 || format == QImage::Format_RGBA32FPx4_Premultiplied);
494
495 // Formats we read directly, instead of over RGBA32:
496 if (format8bit || format16bit || formatCmyk32bit || format64bit || format64fp || format128fp) {
497 int bytesPerPixel = image->depth() / 8;
498 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
499 bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 6 : 2;
500 else if (format == QImage::Format_RGBX32FPx4)
501 bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 12 : 4;
502 if (TIFFIsTiled(tiff)) {
503 quint32 tileWidth, tileLength;
504 TIFFGetField(tif: tiff, TIFFTAG_TILEWIDTH, &tileWidth);
505 TIFFGetField(tif: tiff, TIFFTAG_TILELENGTH, &tileLength);
506 if (!tileWidth || !tileLength || tileWidth % 16 || tileLength % 16) {
507 d->close();
508 return false;
509 }
510 quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
511 quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
512 tmsize_t byteTileSize = TIFFTileSize(tif: tiff);
513 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth) {
514 d->close();
515 return false;
516 }
517 uchar *buf = (uchar *)_TIFFmalloc(s: byteTileSize);
518 if (!buf) {
519 d->close();
520 return false;
521 }
522 for (quint32 y = 0; y < height; y += tileLength) {
523 for (quint32 x = 0; x < width; x += tileWidth) {
524 if (TIFFReadTile(tif: tiff, buf, x, y, z: 0, s: 0) < 0) {
525 _TIFFfree(p: buf);
526 d->close();
527 return false;
528 }
529 quint32 linesToCopy = qMin(a: tileLength, b: height - y);
530 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
531 quint32 widthToCopy = qMin(a: byteTileWidth, b: byteWidth - byteOffset);
532 for (quint32 i = 0; i < linesToCopy; i++) {
533 ::memcpy(dest: image->scanLine(y + i) + byteOffset, src: buf + (i * byteTileWidth), n: widthToCopy);
534 }
535 }
536 }
537 _TIFFfree(p: buf);
538 } else {
539 if (image->bytesPerLine() < TIFFScanlineSize(tif: tiff)) {
540 d->close();
541 return false;
542 }
543 for (uint32_t y=0; y<height; ++y) {
544 if (TIFFReadScanline(tif: tiff, buf: image->scanLine(y), row: y, sample: 0) < 0) {
545 d->close();
546 return false;
547 }
548 }
549 }
550 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
551 if (d->photometric == PHOTOMETRIC_RGB)
552 rgb48fixup(image, floatingPoint: d->floatingPoint);
553 else
554 rgbFixup(image);
555 } else if (format == QImage::Format_RGBX32FPx4) {
556 if (d->photometric == PHOTOMETRIC_RGB)
557 rgb96fixup(image);
558 else
559 rgbFixup(image);
560 }
561 } else {
562 const int stopOnError = 1;
563 if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32_t *>(image->bits()), qt2Exif(transformation: d->transformation), stopOnError)) {
564 for (uint32_t y=0; y<height; ++y)
565 convert32BitOrder(buffer: image->scanLine(y), width);
566 } else {
567 d->close();
568 return false;
569 }
570 }
571
572
573 float resX = 0;
574 float resY = 0;
575 uint16_t resUnit;
576 if (!TIFFGetField(tif: tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
577 resUnit = RESUNIT_INCH;
578
579 if (TIFFGetField(tif: tiff, TIFFTAG_XRESOLUTION, &resX)
580 && TIFFGetField(tif: tiff, TIFFTAG_YRESOLUTION, &resY)) {
581
582 switch(resUnit) {
583 case RESUNIT_CENTIMETER:
584 image->setDotsPerMeterX(qRound(f: resX * 100));
585 image->setDotsPerMeterY(qRound(f: resY * 100));
586 break;
587 case RESUNIT_INCH:
588 image->setDotsPerMeterX(qRound(d: resX * (100 / 2.54)));
589 image->setDotsPerMeterY(qRound(d: resY * (100 / 2.54)));
590 break;
591 default:
592 // do nothing as defaults have already
593 // been set within the QImage class
594 break;
595 }
596 }
597
598 uint32_t count;
599 void *profile;
600 if (TIFFGetField(tif: tiff, TIFFTAG_ICCPROFILE, &count, &profile)) {
601 QByteArray iccProfile(reinterpret_cast<const char *>(profile), count);
602 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
603 }
604 // We do not handle colorimetric metadat not on ICC profile form, it seems to be a lot
605 // less common, and would need additional API in QColorSpace.
606
607 return true;
608}
609
610static bool checkGrayscale(const QList<QRgb> &colorTable)
611{
612 if (colorTable.size() != 256)
613 return false;
614
615 const bool increasing = (colorTable.at(i: 0) == 0xff000000);
616 for (int i = 0; i < 256; ++i) {
617 if ((increasing && colorTable.at(i) != qRgb(r: i, g: i, b: i))
618 || (!increasing && colorTable.at(i) != qRgb(r: 255 - i, g: 255 - i, b: 255 - i)))
619 return false;
620 }
621 return true;
622}
623
624static QList<QRgb> effectiveColorTable(const QImage &image)
625{
626 QList<QRgb> colors;
627 switch (image.format()) {
628 case QImage::Format_Indexed8:
629 colors = image.colorTable();
630 break;
631 case QImage::Format_Alpha8:
632 colors.resize(size: 256);
633 for (int i = 0; i < 256; ++i)
634 colors[i] = qRgba(r: 0, g: 0, b: 0, a: i);
635 break;
636 case QImage::Format_Grayscale8:
637 case QImage::Format_Grayscale16:
638 colors.resize(size: 256);
639 for (int i = 0; i < 256; ++i)
640 colors[i] = qRgb(r: i, g: i, b: i);
641 break;
642 default:
643 Q_UNREACHABLE();
644 }
645 return colors;
646}
647
648static quint32 defaultStripSize(TIFF *tiff)
649{
650 // Aim for 4MB strips
651 qint64 scanSize = qMax(a: qint64(1), b: qint64(TIFFScanlineSize(tif: tiff)));
652 qint64 numRows = (4 * 1024 * 1024) / scanSize;
653 quint32 reqSize = static_cast<quint32>(qBound(min: qint64(1), val: numRows, max: qint64(UINT_MAX)));
654 return TIFFDefaultStripSize(tif: tiff, request: reqSize);
655}
656
657bool QTiffHandler::write(const QImage &image)
658{
659 if (!device()->isWritable())
660 return false;
661
662 TIFF *const tiff = d->openInternal(mode: "wB", device: device());
663 if (!tiff)
664 return false;
665
666 const int width = image.width();
667 const int height = image.height();
668 const int compression = d->compression;
669
670 if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width)
671 || !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height)
672 || !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
673 TIFFClose(tif: tiff);
674 return false;
675 }
676
677 // set the resolution
678 bool resolutionSet = false;
679 const int dotPerMeterX = image.dotsPerMeterX();
680 const int dotPerMeterY = image.dotsPerMeterY();
681 if ((dotPerMeterX % 100) == 0
682 && (dotPerMeterY % 100) == 0) {
683 resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
684 && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
685 && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
686 } else {
687 resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
688 && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
689 && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
690 }
691 if (!resolutionSet) {
692 TIFFClose(tif: tiff);
693 return false;
694 }
695 // set the orienataion
696 bool orientationSet = false;
697 orientationSet = TIFFSetField(tiff, TIFFTAG_ORIENTATION, qt2Exif(transformation: d->transformation));
698 if (!orientationSet) {
699 TIFFClose(tif: tiff);
700 return false;
701 }
702 // set color space
703 const QByteArray iccProfile = image.colorSpace().iccProfile();
704 if (!iccProfile.isEmpty()) {
705 if (!TIFFSetField(tiff, TIFFTAG_ICCPROFILE, iccProfile.size(), reinterpret_cast<const void *>(iccProfile.constData()))) {
706 TIFFClose(tif: tiff);
707 return false;
708 }
709 }
710 // configure image depth
711 const QImage::Format format = image.format();
712 if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
713 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
714 if (image.colorTable().at(i: 0) == 0xffffffff)
715 photometric = PHOTOMETRIC_MINISWHITE;
716 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
717 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
718 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1)
719 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
720 TIFFClose(tif: tiff);
721 return false;
722 }
723
724 // try to do the conversion in chunks no greater than 16 MB
725 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
726 const int chunkHeight = qMax(a: height / chunks, b: 1);
727
728 int y = 0;
729 while (y < height) {
730 QImage chunk = image.copy(x: 0, y, w: width, h: qMin(a: chunkHeight, b: height - y)).convertToFormat(f: QImage::Format_Mono);
731
732 int chunkStart = y;
733 int chunkEnd = y + chunk.height();
734 while (y < chunkEnd) {
735 if (TIFFWriteScanline(tif: tiff, buf: reinterpret_cast<uint32_t *>(chunk.scanLine(y - chunkStart)), row: y) != 1) {
736 TIFFClose(tif: tiff);
737 return false;
738 }
739 ++y;
740 }
741 }
742 TIFFClose(tif: tiff);
743 } else if (format == QImage::Format_Indexed8
744 || format == QImage::Format_Grayscale8
745 || format == QImage::Format_Grayscale16
746 || format == QImage::Format_Alpha8) {
747 QList<QRgb> colorTable = effectiveColorTable(image);
748 bool isGrayscale = checkGrayscale(colorTable);
749 if (isGrayscale) {
750 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
751 if (colorTable.at(i: 0) == 0xffffffff)
752 photometric = PHOTOMETRIC_MINISWHITE;
753 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
754 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
755 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth())
756 || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
757 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
758 TIFFClose(tif: tiff);
759 return false;
760 }
761 } else {
762 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
763 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
764 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
765 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
766 TIFFClose(tif: tiff);
767 return false;
768 }
769 //// write the color table
770 // allocate the color tables
771 const int tableSize = colorTable.size();
772 Q_ASSERT(tableSize <= 256);
773 QVarLengthArray<uint16_t> redTable(tableSize);
774 QVarLengthArray<uint16_t> greenTable(tableSize);
775 QVarLengthArray<uint16_t> blueTable(tableSize);
776
777 // set the color table
778 for (int i = 0; i<tableSize; ++i) {
779 const QRgb color = colorTable.at(i);
780 redTable[i] = qRed(rgb: color) * 257;
781 greenTable[i] = qGreen(rgb: color) * 257;
782 blueTable[i] = qBlue(rgb: color) * 257;
783 }
784
785 const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data());
786
787 if (!setColorTableSuccess) {
788 TIFFClose(tif: tiff);
789 return false;
790 }
791 }
792
793 //// write the data
794 for (int y = 0; y < height; ++y) {
795 if (TIFFWriteScanline(tif: tiff, buf: const_cast<uchar *>(image.scanLine(y)), row: y) != 1) {
796 TIFFClose(tif: tiff);
797 return false;
798 }
799 }
800 TIFFClose(tif: tiff);
801 } else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
802 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
803 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
804 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
805 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
806 || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT,
807 format == QImage::Format_RGBX64
808 ? SAMPLEFORMAT_UINT
809 : SAMPLEFORMAT_IEEEFP)
810 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: tiff, request: 0))) {
811 TIFFClose(tif: tiff);
812 return false;
813 }
814 std::unique_ptr<quint16[]> rgb48line(new quint16[width * 3]);
815 for (int y = 0; y < height; ++y) {
816 const quint16 *srcLine = reinterpret_cast<const quint16 *>(image.constScanLine(y));
817 for (int x = 0; x < width; ++x) {
818 rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
819 rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
820 rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
821 }
822
823 if (TIFFWriteScanline(tif: tiff, buf: (void*)rgb48line.get(), row: y) != 1) {
824 TIFFClose(tif: tiff);
825 return false;
826 }
827 }
828 TIFFClose(tif: tiff);
829 } else if (format == QImage::Format_RGBA64
830 || format == QImage::Format_RGBA64_Premultiplied) {
831 const bool premultiplied = image.format() != QImage::Format_RGBA64;
832 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
833 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
834 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
835 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
836 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
837 || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
838 || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
839 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: tiff, request: 0))) {
840 TIFFClose(tif: tiff);
841 return false;
842 }
843 for (int y = 0; y < height; ++y) {
844 if (TIFFWriteScanline(tif: tiff, buf: (void*)image.scanLine(y), row: y) != 1) {
845 TIFFClose(tif: tiff);
846 return false;
847 }
848 }
849 TIFFClose(tif: tiff);
850 } else if (format == QImage::Format_RGBX32FPx4) {
851 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
852 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
853 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
854 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 32)
855 || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
856 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: tiff, request: 0))) {
857 TIFFClose(tif: tiff);
858 return false;
859 }
860 std::unique_ptr<float[]> line(new float[width * 3]);
861 for (int y = 0; y < height; ++y) {
862 const float *srcLine = reinterpret_cast<const float *>(image.constScanLine(y));
863 for (int x = 0; x < width; ++x) {
864 line[x * 3 + 0] = srcLine[x * 4 + 0];
865 line[x * 3 + 1] = srcLine[x * 4 + 1];
866 line[x * 3 + 2] = srcLine[x * 4 + 2];
867 }
868
869 if (TIFFWriteScanline(tif: tiff, buf: (void*)line.get(), row: y) != 1) {
870 TIFFClose(tif: tiff);
871 return false;
872 }
873 }
874 TIFFClose(tif: tiff);
875 } else if (format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA32FPx4
876 || format == QImage::Format_RGBA16FPx4_Premultiplied
877 || format == QImage::Format_RGBA32FPx4_Premultiplied) {
878 const bool premultiplied = image.format() != QImage::Format_RGBA16FPx4 && image.format() != QImage::Format_RGBA32FPx4;
879 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
880 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
881 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
882 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
883 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth() == 64 ? 16 : 32)
884 || !TIFFSetField(tiff, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
885 || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
886 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: tiff, request: 0))) {
887 TIFFClose(tif: tiff);
888 return false;
889 }
890 for (int y = 0; y < height; ++y) {
891 if (TIFFWriteScanline(tif: tiff, buf: (void*)image.scanLine(y), row: y) != 1) {
892 TIFFClose(tif: tiff);
893 return false;
894 }
895 }
896 TIFFClose(tif: tiff);
897 } else if (format == QImage::Format_CMYK8888) {
898 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED)
899 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
900 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
901 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
902 || !TIFFSetField(tiff, TIFFTAG_INKSET, INKSET_CMYK)
903 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
904 TIFFClose(tif: tiff);
905 return false;
906 }
907
908 for (int y = 0; y < image.height(); ++y) {
909 if (TIFFWriteScanline(tif: tiff, buf: (void*)image.scanLine(y), row: y) != 1) {
910 TIFFClose(tif: tiff);
911 return false;
912 }
913 }
914
915 TIFFClose(tif: tiff);
916 } else if (!image.hasAlphaChannel()) {
917 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
918 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
919 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
920 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
921 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
922 TIFFClose(tif: tiff);
923 return false;
924 }
925 // try to do the RGB888 conversion in chunks no greater than 16 MB
926 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
927 const int chunkHeight = qMax(a: height / chunks, b: 1);
928
929 int y = 0;
930 while (y < height) {
931 const QImage chunk = image.copy(x: 0, y, w: width, h: qMin(a: chunkHeight, b: height - y)).convertToFormat(f: QImage::Format_RGB888);
932
933 int chunkStart = y;
934 int chunkEnd = y + chunk.height();
935 while (y < chunkEnd) {
936 if (TIFFWriteScanline(tif: tiff, buf: (void*)chunk.scanLine(y - chunkStart), row: y) != 1) {
937 TIFFClose(tif: tiff);
938 return false;
939 }
940 ++y;
941 }
942 }
943 TIFFClose(tif: tiff);
944 } else {
945 const bool premultiplied = image.format() != QImage::Format_ARGB32
946 && image.format() != QImage::Format_RGBA8888;
947 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
948 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
949 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
950 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
951 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
952 || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
953 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
954 TIFFClose(tif: tiff);
955 return false;
956 }
957 // try to do the RGBA8888 conversion in chunks no greater than 16 MB
958 const int chunks = int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
959 const int chunkHeight = qMax(a: height / chunks, b: 1);
960
961 const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
962 : QImage::Format_RGBA8888;
963 int y = 0;
964 while (y < height) {
965 const QImage chunk = image.copy(x: 0, y, w: width, h: qMin(a: chunkHeight, b: height - y)).convertToFormat(f: format);
966
967 int chunkStart = y;
968 int chunkEnd = y + chunk.height();
969 while (y < chunkEnd) {
970 if (TIFFWriteScanline(tif: tiff, buf: (void*)chunk.scanLine(y - chunkStart), row: y) != 1) {
971 TIFFClose(tif: tiff);
972 return false;
973 }
974 ++y;
975 }
976 }
977 TIFFClose(tif: tiff);
978 }
979
980 return true;
981}
982
983QVariant QTiffHandler::option(ImageOption option) const
984{
985 if (option == Size && canRead()) {
986 if (d->readHeaders(device: device()))
987 return d->size;
988 } else if (option == CompressionRatio) {
989 return d->compression;
990 } else if (option == ImageFormat) {
991 if (d->readHeaders(device: device()))
992 return d->format;
993 } else if (option == ImageTransformation) {
994 if (d->readHeaders(device: device()))
995 return int(d->transformation);
996 }
997 return QVariant();
998}
999
1000void QTiffHandler::setOption(ImageOption option, const QVariant &value)
1001{
1002 if (option == CompressionRatio && value.metaType().id() == QMetaType::Int)
1003 d->compression = qBound(min: 0, val: value.toInt(), max: 1);
1004 if (option == ImageTransformation) {
1005 int transformation = value.toInt();
1006 if (transformation > 0 && transformation < 8)
1007 d->transformation = QImageIOHandler::Transformations(transformation);
1008 }
1009}
1010
1011bool QTiffHandler::supportsOption(ImageOption option) const
1012{
1013 return option == CompressionRatio
1014 || option == Size
1015 || option == ImageFormat
1016 || option == ImageTransformation;
1017}
1018
1019bool QTiffHandler::jumpToNextImage()
1020{
1021 if (!ensureHaveDirectoryCount())
1022 return false;
1023 if (d->currentDirectory >= d->directoryCount - 1)
1024 return false;
1025
1026 d->headersRead = false;
1027 ++d->currentDirectory;
1028 return true;
1029}
1030
1031bool QTiffHandler::jumpToImage(int imageNumber)
1032{
1033 if (!ensureHaveDirectoryCount())
1034 return false;
1035 if (imageNumber < 0 || imageNumber >= d->directoryCount)
1036 return false;
1037
1038 if (d->currentDirectory != imageNumber) {
1039 d->headersRead = false;
1040 d->currentDirectory = imageNumber;
1041 }
1042 return true;
1043}
1044
1045int QTiffHandler::imageCount() const
1046{
1047 if (!ensureHaveDirectoryCount())
1048 return 1;
1049
1050 return d->directoryCount;
1051}
1052
1053int QTiffHandler::currentImageNumber() const
1054{
1055 return d->currentDirectory;
1056}
1057
1058void QTiffHandler::convert32BitOrder(void *buffer, int width)
1059{
1060 uint32_t *target = reinterpret_cast<uint32_t *>(buffer);
1061 for (int32_t x=0; x<width; ++x) {
1062 uint32_t p = target[x];
1063 // convert between ARGB and ABGR
1064 target[x] = (p & 0xff000000)
1065 | ((p & 0x00ff0000) >> 16)
1066 | (p & 0x0000ff00)
1067 | ((p & 0x000000ff) << 16);
1068 }
1069}
1070
1071void QTiffHandler::rgb48fixup(QImage *image, bool floatingPoint)
1072{
1073 Q_ASSERT(image->depth() == 64);
1074 const int h = image->height();
1075 const int w = image->width();
1076 uchar *scanline = image->bits();
1077 const qsizetype bpl = image->bytesPerLine();
1078 quint16 mask = 0xffff;
1079 const qfloat16 fp_mask = qfloat16(1.0f);
1080 if (floatingPoint)
1081 memcpy(dest: &mask, src: &fp_mask, n: 2);
1082 for (int y = 0; y < h; ++y) {
1083 quint16 *dst = reinterpret_cast<uint16_t *>(scanline);
1084 for (int x = w - 1; x >= 0; --x) {
1085 dst[x * 4 + 3] = mask;
1086 dst[x * 4 + 2] = dst[x * 3 + 2];
1087 dst[x * 4 + 1] = dst[x * 3 + 1];
1088 dst[x * 4 + 0] = dst[x * 3 + 0];
1089 }
1090 scanline += bpl;
1091 }
1092}
1093
1094void QTiffHandler::rgb96fixup(QImage *image)
1095{
1096 Q_ASSERT(image->depth() == 128);
1097 const int h = image->height();
1098 const int w = image->width();
1099 uchar *scanline = image->bits();
1100 const qsizetype bpl = image->bytesPerLine();
1101 for (int y = 0; y < h; ++y) {
1102 float *dst = reinterpret_cast<float *>(scanline);
1103 for (int x = w - 1; x >= 0; --x) {
1104 dst[x * 4 + 3] = 1.0f;
1105 dst[x * 4 + 2] = dst[x * 3 + 2];
1106 dst[x * 4 + 1] = dst[x * 3 + 1];
1107 dst[x * 4 + 0] = dst[x * 3 + 0];
1108 }
1109 scanline += bpl;
1110 }
1111}
1112
1113void QTiffHandler::rgbFixup(QImage *image)
1114{
1115 Q_ASSERT(d->floatingPoint);
1116 if (image->depth() == 64) {
1117 const int h = image->height();
1118 const int w = image->width();
1119 uchar *scanline = image->bits();
1120 const qsizetype bpl = image->bytesPerLine();
1121 for (int y = 0; y < h; ++y) {
1122 qfloat16 *dst = reinterpret_cast<qfloat16 *>(scanline);
1123 for (int x = w - 1; x >= 0; --x) {
1124 dst[x * 4 + 3] = qfloat16(1.0f);
1125 dst[x * 4 + 2] = dst[x];
1126 dst[x * 4 + 1] = dst[x];
1127 dst[x * 4 + 0] = dst[x];
1128 }
1129 scanline += bpl;
1130 }
1131 } else {
1132 const int h = image->height();
1133 const int w = image->width();
1134 uchar *scanline = image->bits();
1135 const qsizetype bpl = image->bytesPerLine();
1136 for (int y = 0; y < h; ++y) {
1137 float *dst = reinterpret_cast<float *>(scanline);
1138 for (int x = w - 1; x >= 0; --x) {
1139 dst[x * 4 + 3] = 1.0f;
1140 dst[x * 4 + 2] = dst[x];
1141 dst[x * 4 + 1] = dst[x];
1142 dst[x * 4 + 0] = dst[x];
1143 }
1144 scanline += bpl;
1145 }
1146 }
1147}
1148
1149bool QTiffHandler::ensureHaveDirectoryCount() const
1150{
1151 if (d->directoryCount > 0)
1152 return true;
1153
1154 TIFF *tiff = d->openInternal(mode: "rh", device: device());
1155
1156 if (!tiff) {
1157 device()->reset();
1158 return false;
1159 }
1160
1161 while (TIFFReadDirectory(tif: tiff))
1162 ++d->directoryCount;
1163 TIFFClose(tif: tiff);
1164 device()->reset();
1165 return true;
1166}
1167
1168QT_END_NAMESPACE
1169

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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