319 if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
320 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
321 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &
photometric)) {
325 size = QSize(width, height);
327 uint16_t orientationTag;
328 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
329 transformation = exif2Qt(orientationTag);
332 uint16_t bitPerSample;
333 if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample))
335 uint16_t samplesPerPixel;
336 if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
338 uint16_t sampleFormat;
339 if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat))
340 sampleFormat = SAMPLEFORMAT_VOID;
345 if (
grayscale && bitPerSample == 1 && samplesPerPixel == 1)
346 format = QImage::Format_Mono;
347 else if (
photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1)
348 format = QImage::Format_Grayscale8;
350 format = QImage::Format_Grayscale16;
351 else if ((
grayscale ||
photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1)
352 format = QImage::Format_Indexed8;
353 else if (samplesPerPixel < 4) {
354 bool regular = (samplesPerPixel != 2) && (
photometric == PHOTOMETRIC_RGB ||
photometric == PHOTOMETRIC_MINISBLACK);
355 if (bitPerSample == 16 && regular)
356 format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
357 else if (bitPerSample == 32 && floatingPoint && regular)
358 format = QImage::Format_RGBX32FPx4;
360 format = QImage::Format_RGB32;
363 uint16_t *extrasamples;
368 bool premultiplied =
true;
369 bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
370 if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
371 premultiplied =
false;
373 if (bitPerSample == 16 &&
photometric == PHOTOMETRIC_RGB) {
375 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
376 premultiplied =
false;
378 format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied;
380 format = floatingPoint ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
382 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
383 premultiplied =
false;
385 format = QImage::Format_RGBA32FPx4_Premultiplied;
387 format = QImage::Format_RGBA32FPx4;
388 }
else if (samplesPerPixel == 4 && bitPerSample == 8 &&
photometric == PHOTOMETRIC_SEPARATED) {
390 const bool gotInkSetField = TIFFGetField(tiff, TIFFTAG_INKSET, &inkSet);
391 if (!gotInkSetField || inkSet == INKSET_CMYK) {
392 format = QImage::Format_CMYK8888;
399 format = QImage::Format_ARGB32_Premultiplied;
401 format = QImage::Format_ARGB32;
447 if (!QImageIOHandler::allocateImage(size, format, image))
451 if (TIFFIsTiled(tiff) && TIFFTileSize64(tiff) > uint64_t(image->sizeInBytes()))
454 const quint32 width = size.width();
455 const quint32 height = size.height();
458 if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
459 if (format == QImage::Format_Mono) {
460 QList<QRgb> colortable(2);
461 if (photometric == PHOTOMETRIC_MINISBLACK) {
462 colortable[0] = 0xff000000;
463 colortable[1] = 0xffffffff;
465 colortable[0] = 0xffffffff;
466 colortable[1] = 0xff000000;
468 image->setColorTable(colortable);
469 }
else if (format == QImage::Format_Indexed8) {
470 const uint16_t tableSize = 256;
471 QList<QRgb> qtColorTable(tableSize);
473 for (
int i = 0; i<tableSize; ++i) {
474 const int c = (photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
475 qtColorTable[i] = qRgb(c, c, c);
479 uint16_t *redTable = 0;
480 uint16_t *greenTable = 0;
481 uint16_t *blueTable = 0;
482 if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable))
484 if (!redTable || !greenTable || !blueTable)
487 for (
int i = 0; i<tableSize ;++i) {
489 const int red = redTable[i] >> 8;
490 const int green = greenTable[i] >> 8;
491 const int blue = blueTable[i] >> 8;
492 qtColorTable[i] = qRgb(red, green, blue);
495 image->setColorTable(qtColorTable);
499 bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
500 bool format16bit = (format == QImage::Format_Grayscale16);
501 bool formatCmyk32bit = (format == QImage::Format_CMYK8888);
502 bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
503 bool format64fp = (format == QImage::Format_RGBX16FPx4 || format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA16FPx4_Premultiplied);
504 bool format128fp = (format == QImage::Format_RGBX32FPx4 || format == QImage::Format_RGBA32FPx4 || format == QImage::Format_RGBA32FPx4_Premultiplied);
507 if (format8bit || format16bit || formatCmyk32bit || format64bit || format64fp || format128fp) {
508 int bytesPerPixel = image->depth() / 8;
509 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
510 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 6 : 2;
511 else if (format == QImage::Format_RGBX32FPx4)
512 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 12 : 4;
513 if (TIFFIsTiled(tiff)) {
514 quint32 tileWidth, tileLength;
515 if (!TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth)
516 || !TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength)
517 || !tileWidth || !tileLength || tileWidth % 16 || tileLength % 16)
521 quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
522 quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
523 tmsize_t byteTileSize = TIFFTileSize(tiff);
524 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth)
526 uchar *buf = (uchar *)_TIFFmalloc(byteTileSize);
529 for (quint32 y = 0; y < height; y += tileLength) {
530 for (quint32 x = 0; x < width; x += tileWidth) {
531 if (TIFFReadTile(tiff, buf, x, y, 0, 0) < 0) {
535 quint32 linesToCopy = qMin(tileLength, height - y);
536 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
537 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
538 for (quint32 i = 0; i < linesToCopy; i++) {
539 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
545 if (image->bytesPerLine() < TIFFScanlineSize(tiff))
547 for (uint32_t y=0; y<height; ++y) {
548 if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0)
552 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
553 if (photometric == PHOTOMETRIC_RGB)
557 }
else if (format == QImage::Format_RGBX32FPx4) {
558 if (photometric == PHOTOMETRIC_RGB)
564 const int stopOnError = 1;
565 if (TIFFReadRGBAImageOriented(tiff, width, height,
reinterpret_cast<uint32_t *>(image->bits()),
566 qt2Exif(transformation), stopOnError))
568 for (uint32_t y=0; y<height; ++y)
569 convert32BitOrder(image->scanLine(y), width);
579 if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
580 resUnit = RESUNIT_INCH;
582 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
583 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
586 case RESUNIT_CENTIMETER:
587 image->setDotsPerMeterX(qRound(resX * 100));
588 image->setDotsPerMeterY(qRound(resY * 100));
591 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
592 image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
603 if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &count, &profile)) {
604 QByteArray iccProfile(
reinterpret_cast<
const char *>(profile), count);
605 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
666 const auto tiff = d->openInternal(
"wB", device());
671 const auto writeScanline = [&](
const void *line,
int y) {
672 return TIFFWriteScanline(tiff.get(),
const_cast<
void*>(line), y);
675 const auto setField = [&] (uint32_t tag,
auto&&...args) {
676 return TIFFSetField(tiff.get(), tag,
std::forward<
decltype(args)>(args)...);
679 const int width = image.width();
680 const int height = image.height();
682 if (!setField(TIFFTAG_IMAGEWIDTH, width)
683 || !setField(TIFFTAG_IMAGELENGTH, height)
684 || !setField(TIFFTAG_COMPRESSION, toLibTiffCompression(d->compression))
685 || !setField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
691 bool resolutionSet =
false;
692 const int dotPerMeterX = image.dotsPerMeterX();
693 const int dotPerMeterY = image.dotsPerMeterY();
694 if ((dotPerMeterX % 100) == 0
695 && (dotPerMeterY % 100) == 0) {
696 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
697 && setField(TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
698 && setField(TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
700 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
701 && setField(TIFFTAG_XRESOLUTION,
static_cast<
float>(image.logicalDpiX()))
702 && setField(TIFFTAG_YRESOLUTION,
static_cast<
float>(image.logicalDpiY()));
708 if (!setField(TIFFTAG_ORIENTATION,
qt2Exif(d->transformation)))
712 const QByteArray iccProfile = image.colorSpace().iccProfile();
713 if (!iccProfile.isEmpty()) {
714 const auto size =
static_cast<uint32_t>(iccProfile.size());
715 if (!q20::cmp_equal(size, iccProfile.size())
716 || !setField(TIFFTAG_ICCPROFILE, size, iccProfile.data()))
723 const QImage::Format format = image.format();
724 if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
725 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
726 if (image.colorTable().at(0) == 0xffffffff)
727 photometric = PHOTOMETRIC_MINISWHITE;
728 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
729 || !setField(TIFFTAG_BITSPERSAMPLE, 1)
730 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
736 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
737 const int chunkHeight = qMax(height / chunks, 1);
741 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
744 int chunkEnd = y + chunk.height();
745 while (y < chunkEnd) {
746 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
751 }
else if (format == QImage::Format_Indexed8
752 || format == QImage::Format_Grayscale8
753 || format == QImage::Format_Grayscale16
754 || format == QImage::Format_Alpha8) {
755 QList<QRgb> colorTable = effectiveColorTable(image);
756 bool isGrayscale = checkGrayscale(colorTable);
758 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
759 if (colorTable.at(0) == 0xffffffff)
760 photometric = PHOTOMETRIC_MINISWHITE;
761 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
762 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth())
763 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
764 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
769 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
770 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
771 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
777 const int tableSize = colorTable.size();
778 Q_ASSERT(tableSize <= 256);
779 QVarLengthArray<uint16_t> redTable(tableSize);
780 QVarLengthArray<uint16_t> greenTable(tableSize);
781 QVarLengthArray<uint16_t> blueTable(tableSize);
784 for (
int i = 0; i<tableSize; ++i) {
785 const QRgb color = colorTable.at(i);
786 redTable[i] = qRed(color) * 257;
787 greenTable[i] = qGreen(color) * 257;
788 blueTable[i] = qBlue(color) * 257;
791 if (!setField(TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data()))
796 for (
int y = 0; y < height; ++y) {
797 if (writeScanline(image.scanLine(y), y) != 1)
800 }
else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
801 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
802 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
803 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
804 || !setField(TIFFTAG_SAMPLEFORMAT,
805 format == QImage::Format_RGBX64
807 : SAMPLEFORMAT_IEEEFP)
808 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
812 std::unique_ptr<quint16[]> rgb48line(
new quint16[width * 3]);
813 for (
int y = 0; y < height; ++y) {
814 const quint16 *srcLine =
reinterpret_cast<
const quint16 *>(image.constScanLine(y));
815 for (
int x = 0; x < width; ++x) {
816 rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
817 rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
818 rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
821 if (writeScanline(rgb48line.get(), y) != 1)
824 }
else if (format == QImage::Format_RGBA64
825 || format == QImage::Format_RGBA64_Premultiplied) {
826 const bool premultiplied = image.format() != QImage::Format_RGBA64;
827 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
828 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
829 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
830 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
831 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
832 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
833 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
837 for (
int y = 0; y < height; ++y) {
838 if (writeScanline(image.scanLine(y), y) != 1)
841 }
else if (format == QImage::Format_RGBX32FPx4) {
842 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
843 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
844 || !setField(TIFFTAG_BITSPERSAMPLE, 32)
845 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
846 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
850 std::unique_ptr<
float[]> line(
new float[width * 3]);
851 for (
int y = 0; y < height; ++y) {
852 const float *srcLine =
reinterpret_cast<
const float *>(image.constScanLine(y));
853 for (
int x = 0; x < width; ++x) {
854 line[x * 3 + 0] = srcLine[x * 4 + 0];
855 line[x * 3 + 1] = srcLine[x * 4 + 1];
856 line[x * 3 + 2] = srcLine[x * 4 + 2];
859 if (writeScanline(line.get(), y) != 1)
862 }
else if (format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA32FPx4
863 || format == QImage::Format_RGBA16FPx4_Premultiplied
864 || format == QImage::Format_RGBA32FPx4_Premultiplied) {
865 const bool premultiplied = image.format() != QImage::Format_RGBA16FPx4 && image.format() != QImage::Format_RGBA32FPx4;
866 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
867 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
868 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
869 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth() == 64 ? 16 : 32)
870 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
871 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
872 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
876 for (
int y = 0; y < height; ++y) {
877 if (writeScanline(image.scanLine(y), y) != 1)
880 }
else if (format == QImage::Format_CMYK8888) {
881 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED)
882 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
883 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
884 || !setField(TIFFTAG_INKSET, INKSET_CMYK)
885 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
890 for (
int y = 0; y < image.height(); ++y) {
891 if (writeScanline(image.scanLine(y), y) != 1)
894 }
else if (!image.hasAlphaChannel()) {
895 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
896 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
897 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
898 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
903 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
904 const int chunkHeight = qMax(height / chunks, 1);
908 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
911 int chunkEnd = y + chunk.height();
912 while (y < chunkEnd) {
913 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
919 const bool premultiplied = image.format() != QImage::Format_ARGB32
920 && image.format() != QImage::Format_RGBA8888;
921 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
922 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
923 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
924 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
925 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
926 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
931 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
932 const int chunkHeight = qMax(height / chunks, 1);
934 const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
935 : QImage::Format_RGBA8888;
938 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
941 int chunkEnd = y + chunk.height();
942 while (y < chunkEnd) {
943 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)