File indexing completed on 2024-12-22 04:15:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <QBuffer>
0009 
0010 #include <memory>
0011 
0012 #include <tiff.h>
0013 
0014 #include <KoColorModelStandardIds.h>
0015 #include <KoColorProfile.h>
0016 #include <KoColorSpace.h>
0017 #include <KoColorSpaceRegistry.h>
0018 #include <KoID.h>
0019 #include <kis_assert.h>
0020 #include <kis_meta_data_backend_registry.h>
0021 
0022 #include <KoConfig.h>
0023 #ifdef HAVE_OPENEXR
0024 #include <half.h>
0025 #endif
0026 
0027 #include "kis_tiff_converter.h"
0028 #include "kis_tiff_writer_visitor.h"
0029 
0030 KisTIFFWriterVisitor::KisTIFFWriterVisitor(TIFF*image, KisTIFFOptions* options)
0031     : KisTIFFBaseWriter(image, options)
0032 {
0033 }
0034 
0035 KisTIFFWriterVisitor::~KisTIFFWriterVisitor() = default;
0036 
0037 bool KisTIFFWriterVisitor::saveLayerProjection(KisLayer *layer)
0038 {
0039     dbgFile << "visiting on layer" << layer->name() << "";
0040     KisPaintDeviceSP pd = layer->projection();
0041 
0042     uint16_t color_type = 0;
0043     uint16_t sample_format = SAMPLEFORMAT_UINT;
0044     const KoColorSpace *destColorSpace = nullptr;
0045     // Check colorspace
0046     if (!writeColorSpaceInformation(image(), pd->colorSpace(), color_type, sample_format, destColorSpace)) { // unsupported colorspace
0047         if (!destColorSpace) {
0048             return false;
0049         }
0050         pd.attach(new KisPaintDevice(*pd));
0051         pd->convertTo(destColorSpace);
0052     }
0053 
0054     {
0055         // WORKAROUND: block any attempts to use YCbCr with alpha channels.
0056         // This should not happen because alpha is disabled by default
0057         // and the checkbox is blocked for YCbCr and CMYK.
0058         KIS_SAFE_ASSERT_RECOVER(color_type != PHOTOMETRIC_YCBCR
0059                                 || !m_options->alpha)
0060         {
0061             warnFile << "TIFF does not support exporting alpha channels with "
0062                         "YCbCr. Skipping...";
0063             m_options->alpha = false;
0064         }
0065     }
0066 
0067     // Save depth
0068     uint32_t depth = 8 * pd->pixelSize() / pd->channelCount();
0069     TIFFSetField(image(), TIFFTAG_BITSPERSAMPLE, depth);
0070 
0071     {
0072         // WORKAROUND: block any attempts to use JPEG with >= 8 bits
0073 
0074         if (m_options->compressionType == COMPRESSION_JPEG && depth != 8) {
0075             warnFile << "Attempt to export JPEG with multi-byte depth, "
0076                         "disabling compression";
0077             m_options->compressionType = COMPRESSION_NONE;
0078         }
0079     }
0080 
0081     // Save number of samples
0082     if (m_options->alpha) {
0083         TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount());
0084         const std::array<uint16_t, 1> sampleinfo = {EXTRASAMPLE_UNASSALPHA};
0085         TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 1, sampleinfo.data());
0086     } else {
0087         TIFFSetField(image(), TIFFTAG_SAMPLESPERPIXEL, pd->channelCount() - 1);
0088         TIFFSetField(image(), TIFFTAG_EXTRASAMPLES, 0);
0089     }
0090 
0091     // Save colorspace information
0092     TIFFSetField(image(), TIFFTAG_PHOTOMETRIC, color_type);
0093     TIFFSetField(image(), TIFFTAG_SAMPLEFORMAT, sample_format);
0094     TIFFSetField(image(), TIFFTAG_IMAGEWIDTH, layer->image()->width());
0095     TIFFSetField(image(), TIFFTAG_IMAGELENGTH, layer->image()->height());
0096 
0097     // Set the compression options
0098     TIFFSetField(image(), TIFFTAG_COMPRESSION, m_options->compressionType);
0099     if (m_options->compressionType == COMPRESSION_JPEG) {
0100         TIFFSetField(image(), TIFFTAG_JPEGQUALITY, m_options->jpegQuality);
0101     } else if (m_options->compressionType == COMPRESSION_ADOBE_DEFLATE) {
0102         TIFFSetField(image(), TIFFTAG_ZIPQUALITY, m_options->deflateCompress);
0103     } else if (m_options->compressionType == COMPRESSION_PIXARLOG) {
0104         TIFFSetField(image(),
0105                      TIFFTAG_PIXARLOGQUALITY,
0106                      m_options->pixarLogCompress);
0107     }
0108 
0109     // Set the predictor
0110     if (m_options->compressionType == COMPRESSION_LZW
0111         || m_options->compressionType == COMPRESSION_ADOBE_DEFLATE)
0112         TIFFSetField(image(), TIFFTAG_PREDICTOR, m_options->predictor);
0113 
0114     // Use contiguous configuration
0115     TIFFSetField(image(), TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
0116 
0117     // Do not set the rowsperstrip, as it's incompatible with JPEG
0118 
0119     // But do set YCbCr 4:4:4 if applicable
0120     if (color_type == PHOTOMETRIC_YCBCR) {
0121         TIFFSetField(image(), TIFFTAG_YCBCRSUBSAMPLING, 1, 1);
0122         TIFFSetField(image(), TIFFTAG_YCBCRPOSITIONING, YCBCRPOSITION_CENTERED);
0123         if (m_options->compressionType == COMPRESSION_JPEG) {
0124             TIFFSetField(image(), TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RAW);
0125         }
0126     }
0127 
0128     // Save profile
0129     if (m_options->saveProfile) {
0130         const KoColorProfile* profile = pd->colorSpace()->profile();
0131         if (profile && profile->type() == "icc" && !profile->rawData().isEmpty()) {
0132             QByteArray ba = profile->rawData();
0133             TIFFSetField(image(), TIFFTAG_ICCPROFILE, ba.size(), ba.constData());
0134         }
0135     }
0136 
0137     {
0138         // IPTC
0139         KisMetaData::IOBackend *io =
0140             KisMetadataBackendRegistry::instance()->value("iptc");
0141         QBuffer buf;
0142         io->saveTo(layer->metaData(), &buf, KisMetaData::IOBackend::NoHeader);
0143 
0144         if (buf.size()
0145             && !TIFFSetField(image(),
0146                              TIFFTAG_RICHTIFFIPTC,
0147                              static_cast<uint32_t>(buf.size()),
0148                              buf.data().data())) {
0149             dbgFile << "Failed to write the IPTC metadata to the TIFF field";
0150         }
0151     }
0152 
0153     {
0154         // XMP
0155         KisMetaData::IOBackend *io =
0156             KisMetadataBackendRegistry::instance()->value("xmp");
0157         QBuffer buf;
0158         io->saveTo(layer->metaData(), &buf, KisMetaData::IOBackend::NoHeader);
0159 
0160         if (buf.size()
0161             && !TIFFSetField(image(),
0162                              TIFFTAG_XMLPACKET,
0163                              static_cast<uint32_t>(buf.size()),
0164                              buf.data().data())) {
0165             dbgFile << "Failed to write the XMP metadata to the TIFF field";
0166         }
0167     }
0168 
0169     tsize_t stripsize = TIFFStripSize(image());
0170     std::unique_ptr<std::remove_pointer_t<tdata_t>, decltype(&_TIFFfree)> buff(
0171         _TIFFmalloc(stripsize),
0172         &_TIFFfree);
0173     KIS_ASSERT_RECOVER_RETURN_VALUE(
0174         buff && "Unable to allocate buffer for TIFF!",
0175         false);
0176     qint32 height = layer->image()->height();
0177     qint32 width = layer->image()->width();
0178     bool r = true;
0179     for (int y = 0; y < height; y++) {
0180         KisHLineConstIteratorSP it = pd->createHLineConstIteratorNG(0, y, width);
0181         switch (color_type) {
0182         case PHOTOMETRIC_MINISBLACK: {
0183             const std::array<quint8, 5> poses = {0, 1};
0184             r = copyDataToStrips(it,
0185                                  buff.get(),
0186                                  depth,
0187                                  sample_format,
0188                                  1,
0189                                  poses);
0190             }
0191             break;
0192         case PHOTOMETRIC_RGB: {
0193             const auto poses = [&]() -> std::array<quint8, 5> {
0194                 if (sample_format == SAMPLEFORMAT_IEEEFP) {
0195                     return {0, 1, 2, 3};
0196                 } else {
0197                     return {2, 1, 0, 3};
0198                 }
0199             }();
0200             r = copyDataToStrips(it,
0201                                  buff.get(),
0202                                  depth,
0203                                  sample_format,
0204                                  3,
0205                                  poses);
0206             }
0207             break;
0208         case PHOTOMETRIC_SEPARATED: {
0209             const std::array<quint8, 5> poses = {0, 1, 2, 3, 4};
0210             r = copyDataToStrips(it,
0211                                  buff.get(),
0212                                  depth,
0213                                  sample_format,
0214                                  4,
0215                                  poses);
0216             }
0217             break;
0218             case PHOTOMETRIC_ICCLAB:
0219             case PHOTOMETRIC_YCBCR: {
0220                 const std::array<quint8, 5> poses = {0, 1, 2, 3};
0221                 r = copyDataToStrips(it,
0222                                      buff.get(),
0223                                      depth,
0224                                      sample_format,
0225                                      3,
0226                                      poses);
0227             } break;
0228         }
0229         if (!r) return false;
0230         TIFFWriteScanline(image(),
0231                           buff.get(),
0232                           static_cast<uint32_t>(y),
0233                           (tsample_t)-1);
0234     }
0235     buff.reset();
0236 
0237     return TIFFWriteDirectory(image());
0238 }