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

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