Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qtiffhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
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
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(static_cast<char *>(buf), size) : -1;
27}
28
29tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
30{
31 return static_cast<QIODevice *>(fd)->write(static_cast<char *>(buf), 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(off);
40 break;
41 case SEEK_CUR:
42 device->seek(device->pos() + off);
43 break;
44 case SEEK_END:
45 device->seek(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 *>(device);
67 if (file) {
68 *base = file->map(0, file->size());
69 if (*base != nullptr) {
70 *size = file->size();
71 return 1;
72 }
73 } else {
74 QBuffer *buf = qobject_cast<QBuffer *>(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 *>(static_cast<QIODevice *>(fd));
87 if (file && base)
88 file->unmap(static_cast<uchar *>(base));
89}
90
91
93{
94public:
97
98 static bool canRead(QIODevice *device);
101 void close();
102
103 TIFF *tiff;
105 QImageIOHandler::Transformations transformation;
108 uint16_t photometric;
114};
115
116static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
117{
118 switch (exifOrientation) {
119 case 1: // normal
121 case 2: // mirror horizontal
123 case 3: // rotate 180
125 case 4: // mirror vertical
127 case 5: // mirror horizontal and rotate 270 CW
129 case 6: // rotate 90 CW
131 case 7: // mirror horizontal and rotate 90 CW
133 case 8: // rotate 270 CW
135 }
136 qWarning("Invalid EXIF orientation");
138}
139
140static int qt2Exif(QImageIOHandler::Transformations transformation)
141{
142 switch (transformation) {
144 return 1;
146 return 2;
148 return 3;
150 return 4;
152 return 5;
154 return 6;
156 return 7;
158 return 8;
159 }
160 qWarning("Invalid Qt image transformation");
161 return 1;
162}
163
165 : tiff(0)
166 , compression(QTiffHandler::NoCompression)
167 , transformation(QImageIOHandler::TransformationNone)
168 , format(QImage::Format_Invalid)
169 , photometric(false)
171 , headersRead(false)
172 , currentDirectory(0)
173 , directoryCount(0)
174{
175}
176
178{
179 close();
180}
181
183{
184 if (tiff)
185 TIFFClose(tiff);
186 tiff = 0;
187}
188
190{
191 if (!device) {
192 qWarning("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(h, 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
209{
210 if (tiff)
211 return true;
212
213 if (!canRead(device))
214 return false;
215
216 tiff = TIFFClientOpen("foo",
217 "r",
218 device,
226
227 if (!tiff) {
228 return false;
229 }
230 return true;
231}
232
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(tiff, TIFFTAG_IMAGEWIDTH, &width)
246 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
247 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric)) {
248 close();
249 return false;
250 }
252
253 uint16_t orientationTag;
254 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
255 transformation = exif2Qt(orientationTag);
256
257 // BitsPerSample defaults to 1 according to the TIFF spec.
258 uint16_t bitPerSample;
259 if (!TIFFGetField(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(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
263 samplesPerPixel = 1;
264 uint16_t sampleFormat;
265 if (!TIFFGetField(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)
273 else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1)
275 else if (photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 16 && samplesPerPixel == 1 && !floatingPoint)
277 else if ((grayscale || photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1)
279 else if (samplesPerPixel < 4)
280 if (bitPerSample == 16 && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK))
282 else if (bitPerSample == 32 && floatingPoint && (photometric == PHOTOMETRIC_RGB || photometric == PHOTOMETRIC_MINISBLACK))
284 else
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(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)
304 else
306 } else if (bitPerSample == 32 && floatingPoint && photometric == PHOTOMETRIC_RGB) {
307 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
308 premultiplied = false;
309 if (premultiplied)
311 else
313 } else {
314 if (premultiplied)
316 else
318 }
319 }
320
321 headersRead = true;
322 return true;
323}
324
328{
329}
330
332{
333 if (d->tiff)
334 return true;
336 setFormat("tiff");
337 return true;
338 }
339 return false;
340}
341
343{
345}
346
348{
349 // Open file and read headers if it hasn't already been done.
350 if (!d->readHeaders(device()))
351 return false;
352
354
356 d->close();
357 return false;
358 }
359
360 TIFF *const tiff = d->tiff;
361 if (TIFFIsTiled(tiff) && TIFFTileSize64(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
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(c, c, 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(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(red, green, blue);
406 }
407 }
408 image->setColorTable(qtColorTable);
409 // free redTable, greenTable and greenTable done by libtiff
410 }
411 }
413 bool format16bit = (format == QImage::Format_Grayscale16);
417
418 // Formats we read directly, instead of over RGBA32:
419 if (format8bit || format16bit || format64bit || format64fp || format128fp) {
420 int bytesPerPixel = image->depth() / 8;
422 bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 6 : 2;
424 bytesPerPixel = d->photometric == PHOTOMETRIC_RGB ? 12 : 4;
425 if (TIFFIsTiled(tiff)) {
426 quint32 tileWidth, tileLength;
427 TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth);
428 TIFFGetField(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(tiff);
436 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth) {
437 d->close();
438 return false;
439 }
440 uchar *buf = (uchar *)_TIFFmalloc(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(tiff, buf, x, y, 0, 0) < 0) {
448 _TIFFfree(buf);
449 d->close();
450 return false;
451 }
452 quint32 linesToCopy = qMin(tileLength, height - y);
453 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
454 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
455 for (quint32 i = 0; i < linesToCopy; i++) {
456 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
457 }
458 }
459 }
460 _TIFFfree(buf);
461 } else {
462 if (image->bytesPerLine() < TIFFScanlineSize(tiff)) {
463 d->close();
464 return false;
465 }
466 for (uint32_t y=0; y<height; ++y) {
467 if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0) {
468 d->close();
469 return false;
470 }
471 }
472 }
474 if (d->photometric == PHOTOMETRIC_RGB)
475 rgb48fixup(image, 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(d->transformation), stopOnError)) {
487 for (uint32_t y=0; y<height; ++y)
488 convert32BitOrder(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(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
500 resUnit = RESUNIT_INCH;
501
502 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
503 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
504
505 switch(resUnit) {
506 case RESUNIT_CENTIMETER:
507 image->setDotsPerMeterX(qRound(resX * 100));
508 image->setDotsPerMeterY(qRound(resY * 100));
509 break;
510 case RESUNIT_INCH:
511 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
512 image->setDotsPerMeterY(qRound(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(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(0) == 0xff000000);
539 for (int i = 0; i < 256; ++i) {
540 if ((increasing && colorTable.at(i) != qRgb(i, i, i))
541 || (!increasing && colorTable.at(i) != qRgb(255 - i, 255 - i, 255 - i)))
542 return false;
543 }
544 return true;
545}
546
548{
550 switch (image.format()) {
552 colors = image.colorTable();
553 break;
555 colors.resize(256);
556 for (int i = 0; i < 256; ++i)
557 colors[i] = qRgba(0, 0, 0, i);
558 break;
561 colors.resize(256);
562 for (int i = 0; i < 256; ++i)
563 colors[i] = qRgb(i, i, 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(qint64(1), qint64(TIFFScanlineSize(tiff)));
575 qint64 numRows = (4 * 1024 * 1024) / scanSize;
576 quint32 reqSize = static_cast<quint32>(qBound(qint64(1), numRows, qint64(UINT_MAX)));
577 return TIFFDefaultStripSize(tiff, reqSize);
578}
579
581{
582 if (!device()->isWritable())
583 return false;
584
585 TIFF *const tiff = TIFFClientOpen("foo",
586 "wB",
587 device(),
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(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(tiff);
625 return false;
626 }
627 // set the orienataion
628 bool orientationSet = false;
629 orientationSet = TIFFSetField(tiff, TIFFTAG_ORIENTATION, qt2Exif(d->transformation));
630 if (!orientationSet) {
631 TIFFClose(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(tiff);
639 return false;
640 }
641 }
642 // configure image depth
643 const QImage::Format format = image.format();
645 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
646 if (image.colorTable().at(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(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(height / chunks, 1);
659
660 int y = 0;
661 while (y < height) {
662 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
663
664 int chunkStart = y;
665 int chunkEnd = y + chunk.height();
666 while (y < chunkEnd) {
667 if (TIFFWriteScanline(tiff, reinterpret_cast<uint32_t *>(chunk.scanLine(y - chunkStart)), y) != 1) {
668 TIFFClose(tiff);
669 return false;
670 }
671 ++y;
672 }
673 }
674 TIFFClose(tiff);
675 } else if (format == QImage::Format_Indexed8
680 bool isGrayscale = checkGrayscale(colorTable);
681 if (isGrayscale) {
682 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
683 if (colorTable.at(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(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(tiff);
699 return false;
700 }
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(color) * 257;
713 greenTable[i] = qGreen(color) * 257;
714 blueTable[i] = qBlue(color) * 257;
715 }
716
717 const bool setColorTableSuccess = TIFFSetField(tiff, TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data());
718
719 if (!setColorTableSuccess) {
720 TIFFClose(tiff);
721 return false;
722 }
723 }
724
726 for (int y = 0; y < height; ++y) {
727 if (TIFFWriteScanline(tiff, const_cast<uchar *>(image.scanLine(y)), y) != 1) {
728 TIFFClose(tiff);
729 return false;
730 }
731 }
732 TIFFClose(tiff);
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,
740 ? SAMPLEFORMAT_UINT
741 : SAMPLEFORMAT_IEEEFP)
742 || !TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tiff, 0))) {
743 TIFFClose(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(tiff, (void*)rgb48line.get(), y) != 1) {
756 TIFFClose(tiff);
757 return false;
758 }
759 }
760 TIFFClose(tiff);
761 } else if (format == QImage::Format_RGBA64
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(tiff, 0))) {
772 TIFFClose(tiff);
773 return false;
774 }
775 for (int y = 0; y < height; ++y) {
776 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
777 TIFFClose(tiff);
778 return false;
779 }
780 }
781 TIFFClose(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(tiff, 0))) {
789 TIFFClose(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(tiff, (void*)line.get(), y) != 1) {
802 TIFFClose(tiff);
803 return false;
804 }
805 }
806 TIFFClose(tiff);
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(tiff, 0))) {
819 TIFFClose(tiff);
820 return false;
821 }
822 for (int y = 0; y < height; ++y) {
823 if (TIFFWriteScanline(tiff, (void*)image.scanLine(y), y) != 1) {
824 TIFFClose(tiff);
825 return false;
826 }
827 }
828 TIFFClose(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(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(height / chunks, 1);
841
842 int y = 0;
843 while (y < height) {
844 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
845
846 int chunkStart = y;
847 int chunkEnd = y + chunk.height();
848 while (y < chunkEnd) {
849 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
850 TIFFClose(tiff);
851 return false;
852 }
853 ++y;
854 }
855 }
856 TIFFClose(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(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(height / chunks, 1);
873
876 int y = 0;
877 while (y < height) {
878 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
879
880 int chunkStart = y;
881 int chunkEnd = y + chunk.height();
882 while (y < chunkEnd) {
883 if (TIFFWriteScanline(tiff, (void*)chunk.scanLine(y - chunkStart), y) != 1) {
884 TIFFClose(tiff);
885 return false;
886 }
887 ++y;
888 }
889 }
890 TIFFClose(tiff);
891 }
892
893 return true;
894}
895
897{
898 if (option == Size && canRead()) {
899 if (d->readHeaders(device()))
900 return d->size;
901 } else if (option == CompressionRatio) {
902 return d->compression;
903 } else if (option == ImageFormat) {
904 if (d->readHeaders(device()))
905 return d->format;
906 } else if (option == ImageTransformation) {
907 if (d->readHeaders(device()))
908 return int(d->transformation);
909 }
910 return QVariant();
911}
912
914{
915 if (option == CompressionRatio && value.metaType().id() == QMetaType::Int)
916 d->compression = qBound(0, value.toInt(), 1);
918 int transformation = value.toInt();
919 if (transformation > 0 && transformation < 8)
920 d->transformation = QImageIOHandler::Transformations(transformation);
921 }
922}
923
925{
926 return option == CompressionRatio
927 || option == Size
928 || option == ImageFormat
930}
931
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
959{
960 if (!ensureHaveDirectoryCount())
961 return 1;
962
963 return d->directoryCount;
964}
965
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(&mask, &fp_mask, 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{
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(),
1077 if (!tiff) {
1078 device()->reset();
1079 return false;
1080 }
1081
1082 do {
1083 ++d->directoryCount;
1084 } while (TIFFReadDirectory(tiff));
1085 TIFFClose(tiff);
1086 device()->reset();
1087 return true;
1088}
1089
IOBluetoothDevice * device
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:474
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:122
static QColorSpace fromIccProfile(const QByteArray &iccProfile)
Creates a QColorSpace from ICC profile iccProfile.
\inmodule QtCore
Definition qfiledevice.h:16
uchar * map(qint64 offset, qint64 size, MemoryMapFlags flags=NoOptions)
Maps size bytes of the file into memory starting at offset.
bool unmap(uchar *address)
Unmaps the memory address.
qint64 size() const override
\reimp
Definition qfile.cpp:1156
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool reset()
Seeks to the start of input for random-access devices.
The QImageIOHandler class defines the common image I/O interface for all image formats in Qt.
ImageOption
This enum describes the different options supported by QImageIOHandler.
static bool allocateImage(QSize size, QImage::Format format, QImage *image)
QIODevice * device() const
Returns the device currently assigned to the QImageIOHandler.
void setFormat(const QByteArray &format)
Sets the format of the QImageIOHandler to format.
\inmodule QtGui
Definition qimage.h:37
uchar * scanLine(int)
Returns a pointer to the pixel data at the scanline with index i.
Definition qimage.cpp:1615
int height() const
Returns the height of the image.
Format
The following image formats are available in Qt.
Definition qimage.h:41
@ Format_Grayscale16
Definition qimage.h:70
@ Format_Alpha8
Definition qimage.h:65
@ Format_RGBA8888
Definition qimage.h:59
@ Format_RGB888
Definition qimage.h:55
@ Format_RGBA16FPx4
Definition qimage.h:73
@ Format_RGBA32FPx4_Premultiplied
Definition qimage.h:77
@ Format_RGB32
Definition qimage.h:46
@ Format_RGBX32FPx4
Definition qimage.h:75
@ Format_RGBA64_Premultiplied
Definition qimage.h:69
@ Format_MonoLSB
Definition qimage.h:44
@ Format_RGBA8888_Premultiplied
Definition qimage.h:60
@ Format_RGBA64
Definition qimage.h:68
@ Format_RGBA32FPx4
Definition qimage.h:76
@ Format_Mono
Definition qimage.h:43
@ Format_RGBA16FPx4_Premultiplied
Definition qimage.h:74
@ Format_RGBX64
Definition qimage.h:67
@ Format_RGBX16FPx4
Definition qimage.h:72
@ Format_Indexed8
Definition qimage.h:45
@ Format_ARGB32_Premultiplied
Definition qimage.h:48
@ Format_ARGB32
Definition qimage.h:47
@ Format_Grayscale8
Definition qimage.h:66
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
qsizetype size() const
Returns the number of characters in this string.
Definition qstring.h:182
QImage::Format format
static bool canRead(QIODevice *device)
bool readHeaders(QIODevice *device)
QImageIOHandler::Transformations transformation
bool openForRead(QIODevice *device)
bool jumpToNextImage() override
For image formats that support animation, this function jumps to the next image.
QVariant option(ImageOption option) const override
Returns the value assigned to option as a QVariant.
bool read(QImage *image) override
Read an image from the device, and stores it in image.
bool supportsOption(ImageOption option) const override
Returns true if the QImageIOHandler supports the option option; otherwise returns false.
void setOption(ImageOption option, const QVariant &value) override
Sets the option option with the value value.
bool canRead() const override
Returns true if an image can be read from the device (i.e., the image format is supported,...
bool jumpToImage(int imageNumber) override
For image formats that support animation, this function jumps to the image whose sequence number is i...
int imageCount() const override
For image formats that support animation, this function returns the number of images in the animation...
int currentImageNumber() const override
For image formats that support animation, this function returns the sequence number of the current im...
bool write(const QImage &image) override
Writes the image image to the assigned device.
T * data() noexcept
\inmodule QtCore
Definition qvariant.h:64
\keyword 16-bit Floating Point Support\inmodule QtCore \inheaderfile QFloat16
Definition qfloat16.h:46
Combined button and popup list for selecting options.
Definition image.cpp:4
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
int qRound(qfloat16 d) noexcept
Definition qfloat16.h:281
static int qt2Exif(QImageIOHandler::Transformations transformation)
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLenum GLsizei count
GLenum GLuint buffer
GLint GLsizei width
GLenum GLenum dst
GLenum GLuint GLenum GLsizei const GLchar * buf
GLenum target
GLuint64 GLenum GLint fd
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
GLint GLsizei GLsizei GLenum format
GLint y
GLfloat GLfloat GLfloat GLfloat h
GLbyte GLbyte blue
Definition qopenglext.h:385
const GLubyte * c
GLfloat GLfloat p
[1]
GLuint GLenum option
GLbyte green
Definition qopenglext.h:385
static void grayscale(const QImage &image, QImage &dest, const QRect &rect=QRect())
static QT_BEGIN_NAMESPACE const QRgb colors[][14]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QT_BEGIN_NAMESPACE typedef unsigned int QRgb
Definition qrgb.h:13
constexpr QRgb qRgb(int r, int g, int b)
Definition qrgb.h:30
constexpr int qRed(QRgb rgb)
Definition qrgb.h:18
constexpr int qGreen(QRgb rgb)
Definition qrgb.h:21
constexpr QRgb qRgba(int r, int g, int b, int a)
Definition qrgb.h:33
constexpr int qBlue(QRgb rgb)
Definition qrgb.h:24
toff_t qtiffSeekProc(thandle_t fd, toff_t off, int whence)
QT_BEGIN_NAMESPACE tsize_t qtiffReadProc(thandle_t fd, tdata_t buf, tsize_t size)
tsize_t qtiffWriteProc(thandle_t fd, tdata_t buf, tsize_t size)
void qtiffUnmapProc(thandle_t fd, void *base, toff_t)
toff_t qtiffSizeProc(thandle_t fd)
static bool checkGrayscale(const QList< QRgb > &colorTable)
static QList< QRgb > effectiveColorTable(const QImage &image)
static quint32 defaultStripSize(TIFF *tiff)
static int qt2Exif(QImageIOHandler::Transformations transformation)
int qtiffCloseProc(thandle_t)
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
int qtiffMapProc(thandle_t fd, void **base, toff_t *size)
unsigned int quint32
Definition qtypes.h:45
unsigned char uchar
Definition qtypes.h:27
unsigned short quint16
Definition qtypes.h:43
ptrdiff_t qsizetype
Definition qtypes.h:70
long long qint64
Definition qtypes.h:55
QFile file
[0]
gzip write("uncompressed data")
QGraphicsSvgItem * red