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

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