Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtiffhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#include <qcolorspace.h>
8#include <qdebug.h>
9#include <qfloat16.h>
10#include <qimage.h>
11#include <qloggingcategory.h>
12#include <qvariant.h>
13#include <qvarlengtharray.h>
14#include <qbuffer.h>
15#include <qfiledevice.h>
16#include <qimagereader.h>
17
18extern "C" {
19#include "tiffio.h"
20}
21
22#include <memory>
23
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(static_cast<char *>(buf), size) : -1;
32}
33
34tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
35{
36 return static_cast<QIODevice *>(fd)->write(static_cast<char *>(buf), 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(off);
45 break;
46 case SEEK_CUR:
47 device->seek(device->pos() + off);
48 break;
49 case SEEK_END:
50 device->seek(device->size() + off);
51 break;
52 }
53
54 return device->pos();
55}
56
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 *>(device);
72 if (file) {
73 *base = file->map(0, file->size());
74 if (*base != nullptr) {
75 *size = file->size();
76 return 1;
77 }
78 } else {
79 QBuffer *buf = qobject_cast<QBuffer *>(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 *>(static_cast<QIODevice *>(fd));
92 if (file && base)
93 file->unmap(static_cast<uchar *>(base));
94}
95
96
98{
99public:
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
119 QSize size;
120 uint16_t photometric;
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
188
193
195{
196 if (tiff)
197 TIFFClose(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, &tiffErrorHandler, this);
207 TIFFOpenOptionsSetWarningHandlerExtR(opts, &tiffWarningHandler, 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(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(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(h, 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("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(tiff, TIFFTAG_IMAGEWIDTH, &width)
314 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
315 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
316 close();
317 return false;
318 }
319 size = QSize(width, height);
320
321 uint16_t orientationTag;
322 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
323 transformation = exif2Qt(orientationTag);
324
325 // BitsPerSample defaults to 1 according to the TIFF spec.
326 uint16_t bitPerSample;
327 if (!TIFFGetField(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(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
331 samplesPerPixel = 1;
332 uint16_t sampleFormat;
333 if (!TIFFGetField(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(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(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
408
410{
411 if (d->tiff)
412 return true;
414 setFormat("tiff");
415 return true;
416 }
417 return false;
418}
419
420bool QTiffHandler::canRead(QIODevice *device)
421{
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()))
429 return false;
430
431 QImage::Format format = d->format;
432
433 if (!QImageIOHandler::allocateImage(d->size, format, image)) {
434 d->close();
435 return false;
436 }
437
438 TIFF *const tiff = d->tiff;
439 if (TIFFIsTiled(tiff) && TIFFTileSize64(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(c, c, 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(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(red, green, 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(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
507 TIFFGetField(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(tiff);
515 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth) {
516 d->close();
517 return false;
518 }
519 uchar *buf = (uchar *)_TIFFmalloc(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(tiff, buf, x, y, 0, 0) < 0) {
527 _TIFFfree(buf);
528 d->close();
529 return false;
530 }
531 quint32 linesToCopy = qMin(tileLength, height - y);
532 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
533 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
534 for (quint32 i = 0; i < linesToCopy; i++) {
535 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
536 }
537 }
538 }
539 _TIFFfree(buf);
540 } else {
541 if (image->bytesPerLine() < TIFFScanlineSize(tiff)) {
542 d->close();
543 return false;
544 }
545 for (uint32_t y=0; y<height; ++y) {
546 if (TIFFReadScanline(tiff, image->scanLine(y), y, 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, 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(d->transformation), stopOnError)) {
566 for (uint32_t y=0; y<height; ++y)
567 convert32BitOrder(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(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
579 resUnit = RESUNIT_INCH;
580
581 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
582 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
583
584 switch(resUnit) {
585 case RESUNIT_CENTIMETER:
586 image->setDotsPerMeterX(qRound(resX * 100));
587 image->setDotsPerMeterY(qRound(resY * 100));
588 break;
589 case RESUNIT_INCH:
590 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
591 image->setDotsPerMeterY(qRound(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(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(0) == 0xff000000);
618 for (int i = 0; i < 256; ++i) {
619 if ((increasing && colorTable.at(i) != qRgb(i, i, i))
620 || (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 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(256);
635 for (int i = 0; i < 256; ++i)
636 colors[i] = qRgba(0, 0, 0, i);
637 break;
638 case QImage::Format_Grayscale8:
639 case QImage::Format_Grayscale16:
640 colors.resize(256);
641 for (int i = 0; i < 256; ++i)
642 colors[i] = qRgb(i, i, 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(qint64(1), qint64(TIFFScanlineSize(tiff)));
654 qint64 numRows = (4 * 1024 * 1024) / scanSize;
655 quint32 reqSize = static_cast<quint32>(qBound(qint64(1), numRows, qint64(UINT_MAX)));
656 return TIFFDefaultStripSize(tiff, reqSize);
657}
658
659bool QTiffHandler::write(const QImage &image)
660{
661 if (!device()->isWritable())
662 return false;
663
664 TIFF *const tiff = d->openInternal("wB", 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(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(tiff);
695 return false;
696 }
697 // set the orienataion
698 bool orientationSet = false;
699 orientationSet = TIFFSetField(tiff, TIFFTAG_ORIENTATION, qt2Exif(d->transformation));
700 if (!orientationSet) {
701 TIFFClose(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(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(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(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(height / chunks, 1);
729
730 int y = 0;
731 while (y < height) {
732 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
733
734 int chunkStart = y;
735 int chunkEnd = y + chunk.height();
736 while (y < chunkEnd) {
737 if (TIFFWriteScanline(tiff, reinterpret_cast<uint32_t *>(chunk.scanLine(y - chunkStart)), y) != 1) {
738 TIFFClose(tiff);
739 return false;
740 }
741 ++y;
742 }
743 }
744 TIFFClose(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(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(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(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(color) * 257;
783 greenTable[i] = qGreen(color) * 257;
784 blueTable[i] = qBlue(color) * 257;
785 }
786
787 const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data());
788
789 if (!setColorTableSuccess) {
790 TIFFClose(tiff);
791 return false;
792 }
793 }
794
795 //// write the data
796 for (int y = 0; y < height; ++y) {
797 if (TIFFWriteScanline(tiff, const_cast<uchar *>(image.scanLine(y)), y) != 1) {
798 TIFFClose(tiff);
799 return false;
800 }
801 }
802 TIFFClose(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(tiff, 0))) {
813 TIFFClose(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(tiff, (void*)rgb48line.get(), y) != 1) {
826 TIFFClose(tiff);
827 return false;
828 }
829 }
830 TIFFClose(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(tiff, 0))) {
842 TIFFClose(tiff);
843 return false;
844 }
845 for (int y = 0; y < height; ++y) {
846 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
847 TIFFClose(tiff);
848 return false;
849 }
850 }
851 TIFFClose(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(tiff, 0))) {
859 TIFFClose(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(tiff, (void*)line.get(), y) != 1) {
872 TIFFClose(tiff);
873 return false;
874 }
875 }
876 TIFFClose(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(tiff, 0))) {
889 TIFFClose(tiff);
890 return false;
891 }
892 for (int y = 0; y < height; ++y) {
893 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
894 TIFFClose(tiff);
895 return false;
896 }
897 }
898 TIFFClose(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(tiff);
907 return false;
908 }
909
910 for (int y = 0; y < image.height(); ++y) {
911 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
912 TIFFClose(tiff);
913 return false;
914 }
915 }
916
917 TIFFClose(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(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(height / chunks, 1);
930
931 int y = 0;
932 while (y < height) {
933 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
934
935 int chunkStart = y;
936 int chunkEnd = y + chunk.height();
937 while (y < chunkEnd) {
938 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
939 TIFFClose(tiff);
940 return false;
941 }
942 ++y;
943 }
944 }
945 TIFFClose(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(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(height / chunks, 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(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
968
969 int chunkStart = y;
970 int chunkEnd = y + chunk.height();
971 while (y < chunkEnd) {
972 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
973 TIFFClose(tiff);
974 return false;
975 }
976 ++y;
977 }
978 }
979 TIFFClose(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()))
989 return d->size;
990 } else if (option == CompressionRatio) {
991 return d->compression;
992 } else if (option == ImageFormat) {
993 if (d->readHeaders(device()))
994 return d->format;
995 } else if (option == ImageTransformation) {
996 if (d->readHeaders(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(0, value.toInt(), 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
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
1048{
1049 if (!ensureHaveDirectoryCount())
1050 return 1;
1051
1052 return d->directoryCount;
1053}
1054
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(&mask, &fp_mask, 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("rh", device());
1157
1158 if (!tiff) {
1159 device()->reset();
1160 return false;
1161 }
1162
1163 while (TIFFReadDirectory(tiff))
1164 ++d->directoryCount;
1165 TIFFClose(tiff);
1166 device()->reset();
1167 return true;
1168}
1169
1170QT_END_NAMESPACE
QIODevice * device() const
Returns the device currently assigned to QImageReader, or \nullptr if no device has been assigned.
\inmodule QtGui
Definition qimage.h:37
static bool canRead(QIODevice *device)
bool readHeaders(QIODevice *device)
QImageIOHandler::Transformations transformation
bool openForRead(QIODevice *device)
TIFF * openInternal(const char *mode, QIODevice *device)
bool jumpToNextImage() override
For image formats that support animation, this function jumps to the next image.
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
bool jumpToImage(int imageNumber) override
For image formats that support animation, this function jumps to the image whose sequence number is i...
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
int currentImageNumber() const override
For image formats that support animation, this function returns the sequence number of the current im...
#define qCCritical(category,...)
#define qCWarning(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)
toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
QT_BEGIN_NAMESPACE tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
void qtiffUnmapProc(thandle_t fd, void *base, toff_t)
toff_t qtiffSizeProc(thandle_t fd)
static bool checkGrayscale(const QList< QRgb > &colorTable)
static QList< QRgb > effectiveColorTable(const QImage &image)
static quint32 defaultStripSize(TIFF *tiff)
static int qt2Exif(QImageIOHandler::Transformations transformation)
int qtiffCloseProc(thandle_t)
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
int qtiffMapProc(thandle_t fd, void **base, toff_t *size)