1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtiffhandler_p.h"
41#include <qvariant.h>
42#include <qdebug.h>
43#include <qimage.h>
44#include <qglobal.h>
45extern "C" {
46#include "tiffio.h"
47}
48
49#include <memory>
50
51QT_BEGIN_NAMESPACE
52
53tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
54{
55 QIODevice *device = static_cast<QIODevice *>(fd);
56 return device->isReadable() ? device->read(static_cast<char *>(buf), size) : -1;
57}
58
59tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
60{
61 return static_cast<QIODevice *>(fd)->write(static_cast<char *>(buf), size);
62}
63
64toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
65{
66 QIODevice *device = static_cast<QIODevice *>(fd);
67 switch (whence) {
68 case SEEK_SET:
69 device->seek(off);
70 break;
71 case SEEK_CUR:
72 device->seek(device->pos() + off);
73 break;
74 case SEEK_END:
75 device->seek(device->size() + off);
76 break;
77 }
78
79 return device->pos();
80}
81
82int qtiffCloseProc(thandle_t /*fd*/)
83{
84 return 0;
85}
86
87toff_t qtiffSizeProc(thandle_t fd)
88{
89 return static_cast<QIODevice *>(fd)->size();
90}
91
92int qtiffMapProc(thandle_t /*fd*/, tdata_t* /*pbase*/, toff_t* /*psize*/)
93{
94 return 0;
95}
96
97void qtiffUnmapProc(thandle_t /*fd*/, tdata_t /*base*/, toff_t /*size*/)
98{
99}
100
101
102class QTiffHandlerPrivate
103{
104public:
105 QTiffHandlerPrivate();
106 ~QTiffHandlerPrivate();
107
108 static bool canRead(QIODevice *device);
109 bool openForRead(QIODevice *device);
110 bool readHeaders(QIODevice *device);
111 void close();
112
113 TIFF *tiff;
114 int compression;
115 QImageIOHandler::Transformations transformation;
116 QImage::Format format;
117 QSize size;
118 uint16 photometric;
119 bool grayscale;
120 bool headersRead;
121 int currentDirectory;
122 int directoryCount;
123};
124
125static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
126{
127 switch (exifOrientation) {
128 case 1: // normal
129 return QImageIOHandler::TransformationNone;
130 case 2: // mirror horizontal
131 return QImageIOHandler::TransformationMirror;
132 case 3: // rotate 180
133 return QImageIOHandler::TransformationRotate180;
134 case 4: // mirror vertical
135 return QImageIOHandler::TransformationFlip;
136 case 5: // mirror horizontal and rotate 270 CW
137 return QImageIOHandler::TransformationFlipAndRotate90;
138 case 6: // rotate 90 CW
139 return QImageIOHandler::TransformationRotate90;
140 case 7: // mirror horizontal and rotate 90 CW
141 return QImageIOHandler::TransformationMirrorAndRotate90;
142 case 8: // rotate 270 CW
143 return QImageIOHandler::TransformationRotate270;
144 }
145 qWarning("Invalid EXIF orientation");
146 return QImageIOHandler::TransformationNone;
147}
148
149static int qt2Exif(QImageIOHandler::Transformations transformation)
150{
151 switch (transformation) {
152 case QImageIOHandler::TransformationNone:
153 return 1;
154 case QImageIOHandler::TransformationMirror:
155 return 2;
156 case QImageIOHandler::TransformationRotate180:
157 return 3;
158 case QImageIOHandler::TransformationFlip:
159 return 4;
160 case QImageIOHandler::TransformationFlipAndRotate90:
161 return 5;
162 case QImageIOHandler::TransformationRotate90:
163 return 6;
164 case QImageIOHandler::TransformationMirrorAndRotate90:
165 return 7;
166 case QImageIOHandler::TransformationRotate270:
167 return 8;
168 }
169 qWarning("Invalid Qt image transformation");
170 return 1;
171}
172
173QTiffHandlerPrivate::QTiffHandlerPrivate()
174 : tiff(0)
175 , compression(QTiffHandler::NoCompression)
176 , transformation(QImageIOHandler::TransformationNone)
177 , format(QImage::Format_Invalid)
178 , photometric(false)
179 , grayscale(false)
180 , headersRead(false)
181 , currentDirectory(0)
182 , directoryCount(0)
183{
184}
185
186QTiffHandlerPrivate::~QTiffHandlerPrivate()
187{
188 close();
189}
190
191void QTiffHandlerPrivate::close()
192{
193 if (tiff)
194 TIFFClose(tiff);
195 tiff = 0;
196}
197
198bool QTiffHandlerPrivate::canRead(QIODevice *device)
199{
200 if (!device) {
201 qWarning("QTiffHandler::canRead() called with no device");
202 return false;
203 }
204
205 // current implementation uses TIFFClientOpen which needs to be
206 // able to seek, so sequential devices are not supported
207 QByteArray header = device->peek(4);
208 return header == QByteArray::fromRawData("\x49\x49\x2A\x00", 4)
209 || header == QByteArray::fromRawData("\x4D\x4D\x00\x2A", 4);
210}
211
212bool QTiffHandlerPrivate::openForRead(QIODevice *device)
213{
214 if (tiff)
215 return true;
216
217 if (!canRead(device))
218 return false;
219
220 tiff = TIFFClientOpen("foo",
221 "r",
222 device,
223 qtiffReadProc,
224 qtiffWriteProc,
225 qtiffSeekProc,
226 qtiffCloseProc,
227 qtiffSizeProc,
228 qtiffMapProc,
229 qtiffUnmapProc);
230
231 if (!tiff) {
232 return false;
233 }
234 return true;
235}
236
237bool QTiffHandlerPrivate::readHeaders(QIODevice *device)
238{
239 if (headersRead)
240 return true;
241
242 if (!openForRead(device))
243 return false;
244
245 TIFFSetDirectory(tiff, currentDirectory);
246
247 uint32 width;
248 uint32 height;
249 if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
250 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
251 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
252 close();
253 return false;
254 }
255 size = QSize(width, height);
256
257 uint16 orientationTag;
258 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
259 transformation = exif2Qt(orientationTag);
260
261 // BitsPerSample defaults to 1 according to the TIFF spec.
262 uint16 bitPerSample;
263 if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample))
264 bitPerSample = 1;
265 uint16 samplesPerPixel; // they may be e.g. grayscale with 2 samples per pixel
266 if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
267 samplesPerPixel = 1;
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)
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 > 8 && photometric == PHOTOMETRIC_RGB)
281 format = QImage::Format_RGBX64;
282 else
283 format = QImage::Format_RGB32;
284 else {
285 uint16 count;
286 uint16 *extrasamples;
287 // If there is any definition of the alpha-channel, libtiff will return premultiplied
288 // data to us. If there is none, libtiff will not touch it and we assume it to be
289 // non-premultiplied, matching behavior of tested image editors, and how older Qt
290 // versions used to save it.
291 bool premultiplied = true;
292 bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
293 if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
294 premultiplied = false;
295
296 if (bitPerSample > 8 && photometric == PHOTOMETRIC_RGB) {
297 // We read 64-bit raw, so unassoc remains unpremultiplied.
298 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
299 premultiplied = false;
300 if (premultiplied)
301 format = QImage::Format_RGBA64_Premultiplied;
302 else
303 format = QImage::Format_RGBA64;
304 } else {
305 if (premultiplied)
306 format = QImage::Format_ARGB32_Premultiplied;
307 else
308 format = QImage::Format_ARGB32;
309 }
310 }
311
312 headersRead = true;
313 return true;
314}
315
316QTiffHandler::QTiffHandler()
317 : QImageIOHandler()
318 , d(new QTiffHandlerPrivate)
319{
320}
321
322bool QTiffHandler::canRead() const
323{
324 if (d->tiff)
325 return true;
326 if (QTiffHandlerPrivate::canRead(device())) {
327 setFormat("tiff");
328 return true;
329 }
330 return false;
331}
332
333bool QTiffHandler::canRead(QIODevice *device)
334{
335 return QTiffHandlerPrivate::canRead(device);
336}
337
338bool QTiffHandler::read(QImage *image)
339{
340 // Open file and read headers if it hasn't already been done.
341 if (!d->readHeaders(device()))
342 return false;
343
344 QImage::Format format = d->format;
345
346 if (image->size() == d->size && image->format() != format)
347 image->reinterpretAsFormat(format);
348
349 if (image->size() != d->size || image->format() != format)
350 *image = QImage(d->size, format);
351
352 if (image->isNull()) {
353 d->close();
354 return false;
355 }
356
357 TIFF *const tiff = d->tiff;
358 const quint32 width = d->size.width();
359 const quint32 height = d->size.height();
360
361 // Setup color tables
362 if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
363 if (format == QImage::Format_Mono) {
364 QVector<QRgb> colortable(2);
365 if (d->photometric == PHOTOMETRIC_MINISBLACK) {
366 colortable[0] = 0xff000000;
367 colortable[1] = 0xffffffff;
368 } else {
369 colortable[0] = 0xffffffff;
370 colortable[1] = 0xff000000;
371 }
372 image->setColorTable(colortable);
373 } else if (format == QImage::Format_Indexed8) {
374 const uint16 tableSize = 256;
375 QVector<QRgb> qtColorTable(tableSize);
376 if (d->grayscale) {
377 for (int i = 0; i<tableSize; ++i) {
378 const int c = (d->photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
379 qtColorTable[i] = qRgb(c, c, c);
380 }
381 } else {
382 // create the color table
383 uint16 *redTable = 0;
384 uint16 *greenTable = 0;
385 uint16 *blueTable = 0;
386 if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable)) {
387 d->close();
388 return false;
389 }
390 if (!redTable || !greenTable || !blueTable) {
391 d->close();
392 return false;
393 }
394
395 for (int i = 0; i<tableSize ;++i) {
396 const int red = redTable[i] / 257;
397 const int green = greenTable[i] / 257;
398 const int blue = blueTable[i] / 257;
399 qtColorTable[i] = qRgb(red, green, blue);
400 }
401 }
402 image->setColorTable(qtColorTable);
403 // free redTable, greenTable and greenTable done by libtiff
404 }
405 }
406 bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
407 bool format16bit = (format == QImage::Format_Grayscale16);
408 bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
409
410 // Formats we read directly, instead of over RGBA32:
411 if (format8bit || format16bit || format64bit) {
412 int bytesPerPixel = image->depth() / 8;
413 if (format == QImage::Format_RGBX64)
414 bytesPerPixel = 6;
415 if (TIFFIsTiled(tiff)) {
416 quint32 tileWidth, tileLength;
417 TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
418 TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength);
419 uchar *buf = (uchar *)_TIFFmalloc(TIFFTileSize(tiff));
420 if (!tileWidth || !tileLength || !buf) {
421 _TIFFfree(buf);
422 d->close();
423 return false;
424 }
425 quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
426 quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
427 for (quint32 y = 0; y < height; y += tileLength) {
428 for (quint32 x = 0; x < width; x += tileWidth) {
429 if (TIFFReadTile(tiff, buf, x, y, 0, 0) < 0) {
430 _TIFFfree(buf);
431 d->close();
432 return false;
433 }
434 quint32 linesToCopy = qMin(tileLength, height - y);
435 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
436 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
437 for (quint32 i = 0; i < linesToCopy; i++) {
438 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
439 }
440 }
441 }
442 _TIFFfree(buf);
443 } else {
444 for (uint32 y=0; y<height; ++y) {
445 if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
446 d->close();
447 return false;
448 }
449 }
450 }
451 if (format == QImage::Format_RGBX64)
452 rgb48fixup(image);
453 } else {
454 const int stopOnError = 1;
455 if (TIFFReadRGBAImageOriented(tiff, width, height, reinterpret_cast<uint32 *>(image->bits()), qt2Exif(d->transformation), stopOnError)) {
456 for (uint32 y=0; y<height; ++y)
457 convert32BitOrder(image->scanLine(y), width);
458 } else {
459 d->close();
460 return false;
461 }
462 }
463
464
465 float resX = 0;
466 float resY = 0;
467 uint16 resUnit;
468 if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
469 resUnit = RESUNIT_INCH;
470
471 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
472 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
473
474 switch(resUnit) {
475 case RESUNIT_CENTIMETER:
476 image->setDotsPerMeterX(qRound(resX * 100));
477 image->setDotsPerMeterY(qRound(resY * 100));
478 break;
479 case RESUNIT_INCH:
480 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
481 image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
482 break;
483 default:
484 // do nothing as defaults have already
485 // been set within the QImage class
486 break;
487 }
488 }
489
490 return true;
491}
492
493static bool checkGrayscale(const QVector<QRgb> &colorTable)
494{
495 if (colorTable.size() != 256)
496 return false;
497
498 const bool increasing = (colorTable.at(0) == 0xff000000);
499 for (int i = 0; i < 256; ++i) {
500 if ((increasing && colorTable.at(i) != qRgb(i, i, i))
501 || (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i)))
502 return false;
503 }
504 return true;
505}
506
507static QVector<QRgb> effectiveColorTable(const QImage &image)
508{
509 QVector<QRgb> colors;
510 switch (image.format()) {
511 case QImage::Format_Indexed8:
512 colors = image.colorTable();
513 break;
514 case QImage::Format_Alpha8:
515 colors.resize(256);
516 for (int i = 0; i < 256; ++i)
517 colors[i] = qRgba(0, 0, 0, i);
518 break;
519 case QImage::Format_Grayscale8:
520 case QImage::Format_Grayscale16:
521 colors.resize(256);
522 for (int i = 0; i < 256; ++i)
523 colors[i] = qRgb(i, i, i);
524 break;
525 default:
526 Q_UNREACHABLE();
527 }
528 return colors;
529}
530
531static quint32 defaultStripSize(TIFF *tiff)
532{
533 // Aim for 4MB strips
534 qint64 scanSize = qMax(qint64(1), qint64(TIFFScanlineSize(tiff)));
535 qint64 numRows = (4 * 1024 * 1024) / scanSize;
536 quint32 reqSize = static_cast<quint32>(qBound(qint64(1), numRows, qint64(UINT_MAX)));
537 return TIFFDefaultStripSize(tiff, reqSize);
538}
539
540bool QTiffHandler::write(const QImage &image)
541{
542 if (!device()->isWritable())
543 return false;
544
545 TIFF *const tiff = TIFFClientOpen("foo",
546 "wB",
547 device(),
548 qtiffReadProc,
549 qtiffWriteProc,
550 qtiffSeekProc,
551 qtiffCloseProc,
552 qtiffSizeProc,
553 qtiffMapProc,
554 qtiffUnmapProc);
555 if (!tiff)
556 return false;
557
558 const int width = image.width();
559 const int height = image.height();
560 const int compression = d->compression;
561
562 if (!TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, width)
563 || !TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, height)
564 || !TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) {
565 TIFFClose(tiff);
566 return false;
567 }
568
569 // set the resolution
570 bool resolutionSet = false;
571 const int dotPerMeterX = image.dotsPerMeterX();
572 const int dotPerMeterY = image.dotsPerMeterY();
573 if ((dotPerMeterX % 100) == 0
574 && (dotPerMeterY % 100) == 0) {
575 resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
576 && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
577 && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
578 } else {
579 resolutionSet = TIFFSetField(tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
580 && TIFFSetField(tiff, TIFFTAG_XRESOLUTION, static_cast<float>(image.logicalDpiX()))
581 && TIFFSetField(tiff, TIFFTAG_YRESOLUTION, static_cast<float>(image.logicalDpiY()));
582 }
583 if (!resolutionSet) {
584 TIFFClose(tiff);
585 return false;
586 }
587 // set the orienataion
588 bool orientationSet = false;
589 orientationSet = TIFFSetField(tiff, TIFFTAG_ORIENTATION, qt2Exif(d->transformation));
590 if (!orientationSet) {
591 TIFFClose(tiff);
592 return false;
593 }
594
595 // configure image depth
596 const QImage::Format format = image.format();
597 if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
598 uint16 photometric = PHOTOMETRIC_MINISBLACK;
599 if (image.colorTable().at(0) == 0xffffffff)
600 photometric = PHOTOMETRIC_MINISWHITE;
601 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
602 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
603 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 1)
604 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
605 TIFFClose(tiff);
606 return false;
607 }
608
609 // try to do the conversion in chunks no greater than 16 MB
610 int chunks = (width * height / (1024 * 1024 * 16)) + 1;
611 int chunkHeight = qMax(height / chunks, 1);
612
613 int y = 0;
614 while (y < height) {
615 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
616
617 int chunkStart = y;
618 int chunkEnd = y + chunk.height();
619 while (y < chunkEnd) {
620 if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
621 TIFFClose(tiff);
622 return false;
623 }
624 ++y;
625 }
626 }
627 TIFFClose(tiff);
628 } else if (format == QImage::Format_Indexed8
629 || format == QImage::Format_Grayscale8
630 || format == QImage::Format_Grayscale16
631 || format == QImage::Format_Alpha8) {
632 QVector<QRgb> colorTable = effectiveColorTable(image);
633 bool isGrayscale = checkGrayscale(colorTable);
634 if (isGrayscale) {
635 uint16 photometric = PHOTOMETRIC_MINISBLACK;
636 if (colorTable.at(0) == 0xffffffff)
637 photometric = PHOTOMETRIC_MINISWHITE;
638 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, photometric)
639 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
640 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, image.depth())
641 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
642 TIFFClose(tiff);
643 return false;
644 }
645 } else {
646 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
647 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
648 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
649 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
650 TIFFClose(tiff);
651 return false;
652 }
653 //// write the color table
654 // allocate the color tables
655 const int tableSize = colorTable.size();
656 Q_ASSERT(tableSize <= 256);
657 QVarLengthArray<uint16> redTable(tableSize);
658 QVarLengthArray<uint16> greenTable(tableSize);
659 QVarLengthArray<uint16> blueTable(tableSize);
660
661 // set the color table
662 for (int i = 0; i<tableSize; ++i) {
663 const QRgb color = colorTable.at(i);
664 redTable[i] = qRed(color) * 257;
665 greenTable[i] = qGreen(color) * 257;
666 blueTable[i] = qBlue(color) * 257;
667 }
668
669 const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data());
670
671 if (!setColorTableSuccess) {
672 TIFFClose(tiff);
673 return false;
674 }
675 }
676
677 //// write the data
678 // try to do the conversion in chunks no greater than 16 MB
679 int chunks = (width * height/ (1024 * 1024 * 16)) + 1;
680 int chunkHeight = qMax(height / chunks, 1);
681
682 int y = 0;
683 while (y < height) {
684 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y));
685
686 int chunkStart = y;
687 int chunkEnd = y + chunk.height();
688 while (y < chunkEnd) {
689 if (TIFFWriteScanline(tiff, reinterpret_cast<uint32 *>(chunk.scanLine(y - chunkStart)), y) != 1) {
690 TIFFClose(tiff);
691 return false;
692 }
693 ++y;
694 }
695 }
696 TIFFClose(tiff);
697 } else if (format == QImage::Format_RGBX64) {
698 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
699 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
700 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
701 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
702 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
703 TIFFClose(tiff);
704 return false;
705 }
706 std::unique_ptr<quint16[]> rgb48line(new quint16[width * 3]);
707 for (int y = 0; y < height; ++y) {
708 const quint16 *srcLine = reinterpret_cast<const quint16 *>(image.constScanLine(y));
709 for (int x = 0; x < width; ++x) {
710 rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
711 rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
712 rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
713 }
714
715 if (TIFFWriteScanline(tiff, (void*)rgb48line.get(), y) != 1) {
716 TIFFClose(tiff);
717 return false;
718 }
719 }
720 TIFFClose(tiff);
721 } else if (format == QImage::Format_RGBA64
722 || format == QImage::Format_RGBA64_Premultiplied) {
723 const bool premultiplied = image.format() != QImage::Format_RGBA64;
724 const uint16 extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
725 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
726 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
727 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
728 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 16)
729 || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
730 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
731 TIFFClose(tiff);
732 return false;
733 }
734 for (int y = 0; y < height; ++y) {
735 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
736 TIFFClose(tiff);
737 return false;
738 }
739 }
740 TIFFClose(tiff);
741 } else if (!image.hasAlphaChannel()) {
742 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
743 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
744 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 3)
745 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
746 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
747 TIFFClose(tiff);
748 return false;
749 }
750 // try to do the RGB888 conversion in chunks no greater than 16 MB
751 const int chunks = (width * height * 3 / (1024 * 1024 * 16)) + 1;
752 const int chunkHeight = qMax(height / chunks, 1);
753
754 int y = 0;
755 while (y < height) {
756 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
757
758 int chunkStart = y;
759 int chunkEnd = y + chunk.height();
760 while (y < chunkEnd) {
761 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
762 TIFFClose(tiff);
763 return false;
764 }
765 ++y;
766 }
767 }
768 TIFFClose(tiff);
769 } else {
770 const bool premultiplied = image.format() != QImage::Format_ARGB32
771 && image.format() != QImage::Format_RGBA8888;
772 const uint16 extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
773 if (!TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
774 || !TIFFSetField(tiff, TIFFTAG_COMPRESSION, compression == NoCompression ? COMPRESSION_NONE : COMPRESSION_LZW)
775 || !TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4)
776 || !TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8)
777 || !TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
778 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff))) {
779 TIFFClose(tiff);
780 return false;
781 }
782 // try to do the RGBA8888 conversion in chunks no greater than 16 MB
783 const int chunks = (width * height * 4 / (1024 * 1024 * 16)) + 1;
784 const int chunkHeight = qMax(height / chunks, 1);
785
786 const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
787 : QImage::Format_RGBA8888;
788 int y = 0;
789 while (y < height) {
790 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
791
792 int chunkStart = y;
793 int chunkEnd = y + chunk.height();
794 while (y < chunkEnd) {
795 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
796 TIFFClose(tiff);
797 return false;
798 }
799 ++y;
800 }
801 }
802 TIFFClose(tiff);
803 }
804
805 return true;
806}
807
808#if QT_DEPRECATED_SINCE(5, 13)
809QByteArray QTiffHandler::name() const
810{
811 return "tiff";
812}
813#endif
814
815QVariant QTiffHandler::option(ImageOption option) const
816{
817 if (option == Size && canRead()) {
818 if (d->readHeaders(device()))
819 return d->size;
820 } else if (option == CompressionRatio) {
821 return d->compression;
822 } else if (option == ImageFormat) {
823 if (d->readHeaders(device()))
824 return d->format;
825 } else if (option == ImageTransformation) {
826 if (d->readHeaders(device()))
827 return int(d->transformation);
828 }
829 return QVariant();
830}
831
832void QTiffHandler::setOption(ImageOption option, const QVariant &value)
833{
834 if (option == CompressionRatio && value.type() == QVariant::Int)
835 d->compression = qBound(0, value.toInt(), 1);
836 if (option == ImageTransformation) {
837 int transformation = value.toInt();
838 if (transformation > 0 && transformation < 8)
839 d->transformation = QImageIOHandler::Transformations(transformation);
840 }
841}
842
843bool QTiffHandler::supportsOption(ImageOption option) const
844{
845 return option == CompressionRatio
846 || option == Size
847 || option == ImageFormat
848 || option == ImageTransformation
849 || option == TransformedByDefault;
850}
851
852bool QTiffHandler::jumpToNextImage()
853{
854 if (!ensureHaveDirectoryCount())
855 return false;
856 if (d->currentDirectory >= d->directoryCount - 1)
857 return false;
858
859 d->headersRead = false;
860 ++d->currentDirectory;
861 return true;
862}
863
864bool QTiffHandler::jumpToImage(int imageNumber)
865{
866 if (!ensureHaveDirectoryCount())
867 return false;
868 if (imageNumber < 0 || imageNumber >= d->directoryCount)
869 return false;
870
871 if (d->currentDirectory != imageNumber) {
872 d->headersRead = false;
873 d->currentDirectory = imageNumber;
874 }
875 return true;
876}
877
878int QTiffHandler::imageCount() const
879{
880 if (!ensureHaveDirectoryCount())
881 return 1;
882
883 return d->directoryCount;
884}
885
886int QTiffHandler::currentImageNumber() const
887{
888 return d->currentDirectory;
889}
890
891void QTiffHandler::convert32BitOrder(void *buffer, int width)
892{
893 uint32 *target = reinterpret_cast<uint32 *>(buffer);
894 for (int32 x=0; x<width; ++x) {
895 uint32 p = target[x];
896 // convert between ARGB and ABGR
897 target[x] = (p & 0xff000000)
898 | ((p & 0x00ff0000) >> 16)
899 | (p & 0x0000ff00)
900 | ((p & 0x000000ff) << 16);
901 }
902}
903
904void QTiffHandler::rgb48fixup(QImage *image)
905{
906 Q_ASSERT(image->depth() == 64);
907 const int h = image->height();
908 const int w = image->width();
909 uchar *scanline = image->bits();
910 const qsizetype bpl = image->bytesPerLine();
911 for (int y = 0; y < h; ++y) {
912 quint16 *dst = reinterpret_cast<uint16 *>(scanline);
913 for (int x = w - 1; x >= 0; --x) {
914 dst[x * 4 + 3] = 0xffff;
915 dst[x * 4 + 2] = dst[x * 3 + 2];
916 dst[x * 4 + 1] = dst[x * 3 + 1];
917 dst[x * 4 + 0] = dst[x * 3 + 0];
918 }
919 scanline += bpl;
920 }
921}
922
923bool QTiffHandler::ensureHaveDirectoryCount() const
924{
925 if (d->directoryCount > 0)
926 return true;
927
928 TIFF *tiff = TIFFClientOpen("foo",
929 "r",
930 device(),
931 qtiffReadProc,
932 qtiffWriteProc,
933 qtiffSeekProc,
934 qtiffCloseProc,
935 qtiffSizeProc,
936 qtiffMapProc,
937 qtiffUnmapProc);
938 if (!tiff) {
939 device()->reset();
940 return false;
941 }
942
943 do {
944 ++d->directoryCount;
945 } while (TIFFReadDirectory(tiff));
946 TIFFClose(tiff);
947 device()->reset();
948 return true;
949}
950
951QT_END_NAMESPACE
952