320 if (!TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &width)
321 || !TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &height)
322 || !TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &
photometric)) {
326 size = QSize(width, height);
328 uint16_t orientationTag;
329 if (TIFFGetField(tiff, TIFFTAG_ORIENTATION, &orientationTag))
330 transformation = exif2Qt(orientationTag);
333 uint16_t bitPerSample;
334 if (!TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bitPerSample))
336 uint16_t samplesPerPixel;
337 if (!TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel))
339 uint16_t sampleFormat;
340 if (!TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &sampleFormat))
341 sampleFormat = SAMPLEFORMAT_VOID;
344 uint16_t planarConfig;
345 if (!TIFFGetField(tiff, TIFFTAG_PLANARCONFIG, &planarConfig))
346 planarConfig = PLANARCONFIG_CONTIG;
347 separatePlanes = planarConfig == PLANARCONFIG_SEPARATE && samplesPerPixel > 1;
351 if (
grayscale && bitPerSample == 1 && samplesPerPixel == 1)
352 format = QImage::Format_Mono;
353 else if (
photometric == PHOTOMETRIC_MINISBLACK && bitPerSample == 8 && samplesPerPixel == 1)
354 format = QImage::Format_Grayscale8;
356 format = QImage::Format_Grayscale16;
357 else if ((
grayscale ||
photometric == PHOTOMETRIC_PALETTE) && bitPerSample == 8 && samplesPerPixel == 1)
358 format = QImage::Format_Indexed8;
359 else if (samplesPerPixel < 4) {
360 bool regular = (samplesPerPixel != 2) && (
photometric == PHOTOMETRIC_RGB ||
photometric == PHOTOMETRIC_MINISBLACK);
361 if (bitPerSample == 16 && regular)
362 format = floatingPoint ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
363 else if (bitPerSample == 32 && floatingPoint && regular)
364 format = QImage::Format_RGBX32FPx4;
366 format = QImage::Format_RGB32;
369 uint16_t *extrasamples;
374 bool premultiplied =
true;
375 bool gotField = TIFFGetField(tiff, TIFFTAG_EXTRASAMPLES, &count, &extrasamples);
376 if (!gotField || !count || extrasamples[0] == EXTRASAMPLE_UNSPECIFIED)
377 premultiplied =
false;
379 if (bitPerSample == 16 &&
photometric == PHOTOMETRIC_RGB) {
381 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
382 premultiplied =
false;
384 format = floatingPoint ? QImage::Format_RGBA16FPx4_Premultiplied : QImage::Format_RGBA64_Premultiplied;
386 format = floatingPoint ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
388 if (gotField && count && extrasamples[0] == EXTRASAMPLE_UNASSALPHA)
389 premultiplied =
false;
391 format = QImage::Format_RGBA32FPx4_Premultiplied;
393 format = QImage::Format_RGBA32FPx4;
394 }
else if (samplesPerPixel == 4 && bitPerSample == 8 &&
photometric == PHOTOMETRIC_SEPARATED) {
396 const bool gotInkSetField = TIFFGetField(tiff, TIFFTAG_INKSET, &inkSet);
397 if (!gotInkSetField || inkSet == INKSET_CMYK) {
398 format = QImage::Format_CMYK8888;
405 format = QImage::Format_ARGB32_Premultiplied;
407 format = QImage::Format_ARGB32;
453 if (!QImageIOHandler::allocateImage(size, format, image))
457 if (TIFFIsTiled(tiff) && TIFFTileSize64(tiff) > uint64_t(image->sizeInBytes()))
460 const quint32 width = size.width();
461 const quint32 height = size.height();
464 if (format == QImage::Format_Mono || format == QImage::Format_Indexed8) {
465 if (format == QImage::Format_Mono) {
466 QList<QRgb> colortable(2);
467 if (photometric == PHOTOMETRIC_MINISBLACK) {
468 colortable[0] = 0xff000000;
469 colortable[1] = 0xffffffff;
471 colortable[0] = 0xffffffff;
472 colortable[1] = 0xff000000;
474 image->setColorTable(colortable);
475 }
else if (format == QImage::Format_Indexed8) {
476 const uint16_t tableSize = 256;
477 QList<QRgb> qtColorTable(tableSize);
479 for (
int i = 0; i<tableSize; ++i) {
480 const int c = (photometric == PHOTOMETRIC_MINISBLACK) ? i : (255 - i);
481 qtColorTable[i] = qRgb(c, c, c);
485 uint16_t *redTable = 0;
486 uint16_t *greenTable = 0;
487 uint16_t *blueTable = 0;
488 if (!TIFFGetField(tiff, TIFFTAG_COLORMAP, &redTable, &greenTable, &blueTable))
490 if (!redTable || !greenTable || !blueTable)
493 for (
int i = 0; i<tableSize ;++i) {
495 const int red = redTable[i] >> 8;
496 const int green = greenTable[i] >> 8;
497 const int blue = blueTable[i] >> 8;
498 qtColorTable[i] = qRgb(red, green, blue);
501 image->setColorTable(qtColorTable);
505 bool format8bit = (format == QImage::Format_Mono || format == QImage::Format_Indexed8 || format == QImage::Format_Grayscale8);
506 bool format16bit = (format == QImage::Format_Grayscale16);
507 bool formatCmyk32bit = (format == QImage::Format_CMYK8888);
508 bool format64bit = (format == QImage::Format_RGBX64 || format == QImage::Format_RGBA64 || format == QImage::Format_RGBA64_Premultiplied);
509 bool format64fp = (format == QImage::Format_RGBX16FPx4 || format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA16FPx4_Premultiplied);
510 bool format128fp = (format == QImage::Format_RGBX32FPx4 || format == QImage::Format_RGBA32FPx4 || format == QImage::Format_RGBA32FPx4_Premultiplied);
513 if (format8bit || format16bit || formatCmyk32bit || format64bit || format64fp || format128fp) {
518 int bytesPerPixel = image->depth() / 8;
519 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4)
520 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 6 : 2;
521 else if (format == QImage::Format_RGBX32FPx4)
522 bytesPerPixel = photometric == PHOTOMETRIC_RGB ? 12 : 4;
524 if (TIFFIsTiled(tiff)) {
525 quint32 tileWidth, tileLength;
526 if (!TIFFGetField(tiff, TIFFTAG_TILEWIDTH, &tileWidth)
527 || !TIFFGetField(tiff, TIFFTAG_TILELENGTH, &tileLength)
528 || !tileWidth || !tileLength || tileWidth % 16 || tileLength % 16)
532 quint32 byteWidth = (format == QImage::Format_Mono) ? (width + 7)/8 : (width * bytesPerPixel);
533 quint32 byteTileWidth = (format == QImage::Format_Mono) ? tileWidth/8 : (tileWidth * bytesPerPixel);
534 tmsize_t byteTileSize = TIFFTileSize(tiff);
535 if (byteTileSize > image->sizeInBytes() || byteTileSize / tileLength < byteTileWidth)
537 uchar *buf = (uchar *)_TIFFmalloc(byteTileSize);
540 for (quint32 y = 0; y < height; y += tileLength) {
541 for (quint32 x = 0; x < width; x += tileWidth) {
542 if (TIFFReadTile(tiff, buf, x, y, 0, 0) < 0) {
546 quint32 linesToCopy = qMin(tileLength, height - y);
547 quint32 byteOffset = (format == QImage::Format_Mono) ? x/8 : (x * bytesPerPixel);
548 quint32 widthToCopy = qMin(byteTileWidth, byteWidth - byteOffset);
549 for (quint32 i = 0; i < linesToCopy; i++) {
550 ::memcpy(image->scanLine(y + i) + byteOffset, buf + (i * byteTileWidth), widthToCopy);
556 if (image->bytesPerLine() < TIFFScanlineSize(tiff))
558 for (uint32_t y=0; y<height; ++y) {
559 if (TIFFReadScanline(tiff, image->scanLine(y), y, 0) < 0)
563 if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
564 if (photometric == PHOTOMETRIC_RGB)
568 }
else if (format == QImage::Format_RGBX32FPx4) {
569 if (photometric == PHOTOMETRIC_RGB)
575 const int stopOnError = 1;
576 if (TIFFReadRGBAImageOriented(tiff, width, height,
reinterpret_cast<uint32_t *>(image->bits()),
577 qt2Exif(transformation), stopOnError))
579 for (uint32_t y=0; y<height; ++y)
580 convert32BitOrder(image->scanLine(y), width);
590 if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
591 resUnit = RESUNIT_INCH;
593 if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
594 && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
597 case RESUNIT_CENTIMETER:
598 image->setDotsPerMeterX(qRound(resX * 100));
599 image->setDotsPerMeterY(qRound(resY * 100));
602 image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
603 image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
614 if (TIFFGetField(tiff, TIFFTAG_ICCPROFILE, &count, &profile)) {
615 QByteArray iccProfile(
reinterpret_cast<
const char *>(profile), count);
616 image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
677 const auto tiff = d->openInternal(
"wB", device());
682 const auto writeScanline = [&](
const void *line,
int y) {
683 return TIFFWriteScanline(tiff.get(),
const_cast<
void*>(line), y);
686 const auto setField = [&] (uint32_t tag,
auto&&...args) {
687 return TIFFSetField(tiff.get(), tag,
std::forward<
decltype(args)>(args)...);
690 const int width = image.width();
691 const int height = image.height();
693 if (!setField(TIFFTAG_IMAGEWIDTH, width)
694 || !setField(TIFFTAG_IMAGELENGTH, height)
695 || !setField(TIFFTAG_COMPRESSION, toLibTiffCompression(d->compression))
696 || !setField(TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG))
702 bool resolutionSet =
false;
703 const int dotPerMeterX = image.dotsPerMeterX();
704 const int dotPerMeterY = image.dotsPerMeterY();
705 if ((dotPerMeterX % 100) == 0
706 && (dotPerMeterY % 100) == 0) {
707 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER)
708 && setField(TIFFTAG_XRESOLUTION, dotPerMeterX/100.0)
709 && setField(TIFFTAG_YRESOLUTION, dotPerMeterY/100.0);
711 resolutionSet = setField(TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)
712 && setField(TIFFTAG_XRESOLUTION,
static_cast<
float>(image.logicalDpiX()))
713 && setField(TIFFTAG_YRESOLUTION,
static_cast<
float>(image.logicalDpiY()));
719 if (!setField(TIFFTAG_ORIENTATION,
qt2Exif(d->transformation)))
723 const QByteArray iccProfile = image.colorSpace().iccProfile();
724 if (!iccProfile.isEmpty()) {
725 const auto size =
static_cast<uint32_t>(iccProfile.size());
726 if (!q20::cmp_equal(size, iccProfile.size())
727 || !setField(TIFFTAG_ICCPROFILE, size, iccProfile.data()))
734 const QImage::Format format = image.format();
735 if (format == QImage::Format_Mono || format == QImage::Format_MonoLSB) {
736 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
737 if (image.colorTable().value(0) == 0xffffffff)
738 photometric = PHOTOMETRIC_MINISWHITE;
739 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
740 || !setField(TIFFTAG_BITSPERSAMPLE, 1)
741 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
747 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
748 const int chunkHeight = qMax(height / chunks, 1);
752 QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_Mono);
755 int chunkEnd = y + chunk.height();
756 while (y < chunkEnd) {
757 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
762 }
else if (format == QImage::Format_Indexed8
763 || format == QImage::Format_Grayscale8
764 || format == QImage::Format_Grayscale16
765 || format == QImage::Format_Alpha8) {
766 QList<QRgb> colorTable = effectiveColorTable(image);
767 bool isGrayscale = checkGrayscale(colorTable);
769 uint16_t photometric = PHOTOMETRIC_MINISBLACK;
770 if (colorTable.at(0) == 0xffffffff)
771 photometric = PHOTOMETRIC_MINISWHITE;
772 if (!setField(TIFFTAG_PHOTOMETRIC, photometric)
773 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth())
774 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
775 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
780 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_PALETTE)
781 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
782 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
787 constexpr qsizetype colorMapSize = 256;
788 const int tableSize =
int(qMin(colorTable.size(), colorMapSize));
789 QVarLengthArray<uint16_t> redTable(colorMapSize, 0);
790 QVarLengthArray<uint16_t> greenTable(colorMapSize, 0);
791 QVarLengthArray<uint16_t> blueTable(colorMapSize, 0);
794 for (
int i = 0; i<tableSize; ++i) {
795 const QRgb color = colorTable.at(i);
796 redTable[i] = qRed(color) * 257;
797 greenTable[i] = qGreen(color) * 257;
798 blueTable[i] = qBlue(color) * 257;
801 if (!setField(TIFFTAG_COLORMAP, redTable.data(), greenTable.data(), blueTable.data()))
806 for (
int y = 0; y < height; ++y) {
807 if (writeScanline(image.scanLine(y), y) != 1)
810 }
else if (format == QImage::Format_RGBX64 || format == QImage::Format_RGBX16FPx4) {
811 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
812 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
813 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
814 || !setField(TIFFTAG_SAMPLEFORMAT,
815 format == QImage::Format_RGBX64
817 : SAMPLEFORMAT_IEEEFP)
818 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
822 std::unique_ptr<quint16[]> rgb48line(
new quint16[width * 3]);
823 for (
int y = 0; y < height; ++y) {
824 const quint16 *srcLine =
reinterpret_cast<
const quint16 *>(image.constScanLine(y));
825 for (
int x = 0; x < width; ++x) {
826 rgb48line[x * 3 + 0] = srcLine[x * 4 + 0];
827 rgb48line[x * 3 + 1] = srcLine[x * 4 + 1];
828 rgb48line[x * 3 + 2] = srcLine[x * 4 + 2];
831 if (writeScanline(rgb48line.get(), y) != 1)
834 }
else if (format == QImage::Format_RGBA64
835 || format == QImage::Format_RGBA64_Premultiplied) {
836 const bool premultiplied = image.format() != QImage::Format_RGBA64;
837 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
838 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
839 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
840 || !setField(TIFFTAG_BITSPERSAMPLE, 16)
841 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT)
842 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
843 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
847 for (
int y = 0; y < height; ++y) {
848 if (writeScanline(image.scanLine(y), y) != 1)
851 }
else if (format == QImage::Format_RGBX32FPx4) {
852 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
853 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
854 || !setField(TIFFTAG_BITSPERSAMPLE, 32)
855 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
856 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
860 std::unique_ptr<
float[]> line(
new float[width * 3]);
861 for (
int y = 0; y < height; ++y) {
862 const float *srcLine =
reinterpret_cast<
const float *>(image.constScanLine(y));
863 for (
int x = 0; x < width; ++x) {
864 line[x * 3 + 0] = srcLine[x * 4 + 0];
865 line[x * 3 + 1] = srcLine[x * 4 + 1];
866 line[x * 3 + 2] = srcLine[x * 4 + 2];
869 if (writeScanline(line.get(), y) != 1)
872 }
else if (format == QImage::Format_RGBA16FPx4 || format == QImage::Format_RGBA32FPx4
873 || format == QImage::Format_RGBA16FPx4_Premultiplied
874 || format == QImage::Format_RGBA32FPx4_Premultiplied) {
875 const bool premultiplied = image.format() != QImage::Format_RGBA16FPx4 && image.format() != QImage::Format_RGBA32FPx4;
876 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
877 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
878 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
879 || !setField(TIFFTAG_BITSPERSAMPLE, image.depth() == 64 ? 16 : 32)
880 || !setField(TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)
881 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
882 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
886 for (
int y = 0; y < height; ++y) {
887 if (writeScanline(image.scanLine(y), y) != 1)
890 }
else if (format == QImage::Format_CMYK8888) {
891 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_SEPARATED)
892 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
893 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
894 || !setField(TIFFTAG_INKSET, INKSET_CMYK)
895 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
900 for (
int y = 0; y < image.height(); ++y) {
901 if (writeScanline(image.scanLine(y), y) != 1)
904 }
else if (!image.hasAlphaChannel()) {
905 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
906 || !setField(TIFFTAG_SAMPLESPERPIXEL, 3)
907 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
908 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
913 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
914 const int chunkHeight = qMax(height / chunks, 1);
918 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(QImage::Format_RGB888);
921 int chunkEnd = y + chunk.height();
922 while (y < chunkEnd) {
923 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)
929 const bool premultiplied = image.format() != QImage::Format_ARGB32
930 && image.format() != QImage::Format_RGBA8888;
931 const uint16_t extrasamples = premultiplied ? EXTRASAMPLE_ASSOCALPHA : EXTRASAMPLE_UNASSALPHA;
932 if (!setField(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB)
933 || !setField(TIFFTAG_SAMPLESPERPIXEL, 4)
934 || !setField(TIFFTAG_BITSPERSAMPLE, 8)
935 || !setField(TIFFTAG_EXTRASAMPLES, 1, &extrasamples)
936 || !setField(TIFFTAG_ROWSPERSTRIP, defaultStripSize(tiff)))
941 const int chunks =
int(image.sizeInBytes() / (1024 * 1024 * 16)) + 1;
942 const int chunkHeight = qMax(height / chunks, 1);
944 const QImage::Format format = premultiplied ? QImage::Format_RGBA8888_Premultiplied
945 : QImage::Format_RGBA8888;
948 const QImage chunk = image.copy(0, y, width, qMin(chunkHeight, height - y)).convertToFormat(format);
951 int chunkEnd = y + chunk.height();
952 while (y < chunkEnd) {
953 if (writeScanline(chunk.scanLine(y - chunkStart), y) != 1)