File indexing completed on 2025-01-19 03:51:03

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-06-17
0007  * Description : A TIFF IO file for DImg framework - save operations
0008  *
0009  * SPDX-FileCopyrightText: 2005      by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 // C ANSI includes
0017 extern "C"
0018 {
0019 #include <tiffvers.h>
0020 }
0021 
0022 // C++ includes
0023 
0024 #include <cstdio>
0025 
0026 // Qt includes
0027 
0028 #include <QFile>
0029 #include <QByteArray>
0030 #include <QScopedPointer>
0031 
0032 // Local includes
0033 
0034 #include "digikam_debug.h"
0035 #include "digikam_config.h"
0036 #include "dimgloaderobserver.h"
0037 #include "dimgtiffloader.h"     //krazy:exclude=includes
0038 
0039 namespace DigikamTIFFDImgPlugin
0040 {
0041 
0042 bool DImgTIFFLoader::save(const QString& filePath, DImgLoaderObserver* const observer)
0043 {
0044     uint32 w    = imageWidth();
0045     uint32 h    = imageHeight();
0046     uchar* data = imageData();
0047 
0048     // -------------------------------------------------------------------
0049     // TIFF error handling. If an errors/warnings occurs during reading,
0050     // libtiff will call these methods
0051 
0052     TIFFSetWarningHandler(dimg_tiff_warning);
0053     TIFFSetErrorHandler(dimg_tiff_error);
0054 
0055     // -------------------------------------------------------------------
0056     // Open the file
0057 
0058 #ifdef Q_OS_WIN
0059 
0060     TIFF* const tif = TIFFOpenW((const wchar_t*)filePath.utf16(), "w");
0061 
0062 #else
0063 
0064     TIFF* const tif = TIFFOpen(filePath.toUtf8().constData(), "w");
0065 
0066 #endif
0067 
0068     if (!tif)
0069     {
0070         qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot open target image file.";
0071         return false;
0072     }
0073 
0074     // -------------------------------------------------------------------
0075     // Set image properties
0076 
0077     TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,     w);
0078     TIFFSetField(tif, TIFFTAG_IMAGELENGTH,    h);
0079     TIFFSetField(tif, TIFFTAG_PHOTOMETRIC,    PHOTOMETRIC_RGB);
0080     TIFFSetField(tif, TIFFTAG_PLANARCONFIG,   PLANARCONFIG_CONTIG);
0081     TIFFSetField(tif, TIFFTAG_ORIENTATION,    ORIENTATION_TOPLEFT);
0082     TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
0083 
0084     // Image must be compressed using deflate algorithm ?
0085 
0086     QVariant compressAttr = imageGetAttribute(QLatin1String("compress"));
0087     bool compress         = compressAttr.isValid() ? compressAttr.toBool() : false;
0088 
0089     qCDebug(DIGIKAM_DIMG_LOG_TIFF) << "TIFF Compression Enabled:" << compress;
0090 
0091     if (compress)
0092     {
0093         TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE);
0094         TIFFSetField(tif, TIFFTAG_ZIPQUALITY,  9);
0095         // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL.
0096         //        Use horizontal differencing for images which are
0097         //        likely to be continuous tone. The TIFF spec says that this
0098         //        usually leads to better compression.
0099         //        See this Url for more details:
0100         //        www.awaresystems.be/imaging/tiff/tifftags/predictor.html
0101         TIFFSetField(tif, TIFFTAG_PREDICTOR,   2);
0102     }
0103     else
0104     {
0105         TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
0106     }
0107 
0108     uint16 sampleinfo[1];
0109 
0110     if (imageHasAlpha())
0111     {
0112         sampleinfo[0] = EXTRASAMPLE_ASSOCALPHA;
0113         TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4);
0114         TIFFSetField(tif, TIFFTAG_EXTRASAMPLES,    1, sampleinfo);
0115     }
0116     else
0117     {
0118         TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
0119     }
0120 
0121     TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth());
0122     TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP,  TIFFDefaultStripSize(tif, 0));
0123 
0124     // -------------------------------------------------------------------
0125     // Write meta-data Tags contents.
0126 
0127     QScopedPointer<DMetadata> metaData(new DMetadata(m_image->getMetadata()));
0128 
0129     // Standard IPTC tag (available with libtiff 3.6.1)
0130 
0131     QByteArray ba = metaData->getIptc(true);
0132 
0133     if (!ba.isEmpty())
0134     {
0135 
0136 #if defined(TIFFTAG_PHOTOSHOP)
0137 
0138         TIFFSetField(tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar*)ba.data());
0139 
0140 #endif
0141 
0142     }
0143 
0144     // Standard XMP tag (available with libtiff 3.6.1)
0145 
0146     if (metaData->hasXmp())
0147     {
0148 
0149 #if defined(TIFFTAG_XMLPACKET)
0150 
0151         tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET,            *metaData, "Exif.Image.XMLPacket");
0152 
0153 #endif
0154 
0155     }
0156 
0157     // Standard Exif ASCII tags (available with libtiff 3.6.1)
0158 
0159     tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME,            *metaData, "Exif.Image.DocumentName");
0160     tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION,        *metaData, "Exif.Image.ImageDescription");
0161     tiffSetExifAsciiTag(tif, TIFFTAG_MAKE,                    *metaData, "Exif.Image.Make");
0162     tiffSetExifAsciiTag(tif, TIFFTAG_MODEL,                   *metaData, "Exif.Image.Model");
0163     tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME,                *metaData, "Exif.Image.DateTime");
0164     tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST,                  *metaData, "Exif.Image.Artist");
0165     tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT,               *metaData, "Exif.Image.Copyright");
0166 
0167     QString soft       = metaData->getExifTagString("Exif.Image.Software");
0168     QString libtiffver = QLatin1String(TIFFLIB_VERSION_STR);
0169     libtiffver.replace(QLatin1Char('\n'), QLatin1Char(' '));
0170     soft.append(QString::fromLatin1(" ( %1 )").arg(libtiffver));
0171     TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.toLatin1().constData());
0172 
0173     // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18)
0174 
0175     // -------------------------------------------------------------------
0176     // Write ICC profile.
0177 
0178     QByteArray profile_rawdata = m_image->getIccProfile().data();
0179 
0180     if (!profile_rawdata.isEmpty())
0181     {
0182 
0183 #if defined(TIFFTAG_ICCPROFILE)
0184 
0185         purgeExifWorkingColorSpace();
0186         TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar*)profile_rawdata.data());
0187 
0188 #endif
0189 
0190     }
0191 
0192     // -------------------------------------------------------------------
0193     // Write full image data in tiff directory IFD0
0194 
0195     if (observer)
0196     {
0197         observer->progressInfo(0.1F);
0198     }
0199 
0200     uchar*  pixel        = nullptr;
0201     uint16* pixel16      = nullptr;
0202     double  alpha_factor = 0;
0203     uint32  x            = 0;
0204     uint32  y            = 0;
0205     uint8   r8           = 0;
0206     uint8   g8           = 0;
0207     uint8   b8           = 0;
0208     uint8   a8           = 0;
0209     uint16  r16          = 0;
0210     uint16  g16          = 0;
0211     uint16  b16          = 0;
0212     uint16  a16          = 0;
0213     int     i            = 0;
0214 
0215     uint8* buf    = (uint8*)_TIFFmalloc(TIFFScanlineSize(tif));
0216     uint16* buf16 = nullptr;
0217 
0218     if (!buf)
0219     {
0220         qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot allocate memory buffer for main image.";
0221         TIFFClose(tif);
0222         return false;
0223     }
0224 
0225     uint checkpoint = 0;
0226 
0227     for (y = 0 ; y < h ; ++y)
0228     {
0229 
0230         if (observer && y == checkpoint)
0231         {
0232             checkpoint += granularity(observer, h, 0.8F);
0233 
0234             if (!observer->continueQuery())
0235             {
0236                 _TIFFfree(buf);
0237                 TIFFClose(tif);
0238                 return false;
0239             }
0240 
0241             observer->progressInfo(0.1F + (0.8F * (((float)y) / ((float)h))));
0242         }
0243 
0244         i = 0;
0245 
0246         for (x = 0 ; x < w ; ++x)
0247         {
0248             pixel = &data[((y * w) + x) * imageBytesDepth()];
0249 
0250             if (imageSixteenBit())          // 16 bits image.
0251             {
0252                 pixel16 = reinterpret_cast<ushort*>(pixel);
0253                 b16 = pixel16[0];
0254                 g16 = pixel16[1];
0255                 r16 = pixel16[2];
0256 
0257                 if (imageHasAlpha())
0258                 {
0259                     // TIFF makes you pre-multiply the RGB components by alpha
0260 
0261                     a16          = pixel16[3];
0262                     alpha_factor = ((double)a16 / 65535.0);
0263                     r16          = (uint16)(r16 * alpha_factor);
0264                     g16          = (uint16)(g16 * alpha_factor);
0265                     b16          = (uint16)(b16 * alpha_factor);
0266                 }
0267 
0268                 // This might be endian dependent
0269 
0270                 buf16    = reinterpret_cast<ushort*>(buf+i);
0271                 *buf16++ = r16;
0272                 *buf16++ = g16;
0273                 *buf16++ = b16;
0274                 i       += 6;
0275 
0276                 if (imageHasAlpha())
0277                 {
0278                     *buf16++ = a16;
0279                     i       += 2;
0280                 }
0281             }
0282             else                            // 8 bits image.
0283             {
0284                 b8 = (uint8)pixel[0];
0285                 g8 = (uint8)pixel[1];
0286                 r8 = (uint8)pixel[2];
0287 
0288                 if (imageHasAlpha())
0289                 {
0290                     // TIFF makes you pre-multiply the RGB components by alpha
0291 
0292                     a8           = (uint8)(pixel[3]);
0293                     alpha_factor = ((double)a8 / 255.0);
0294                     r8           = (uint8)(r8 * alpha_factor);
0295                     g8           = (uint8)(g8 * alpha_factor);
0296                     b8           = (uint8)(b8 * alpha_factor);
0297                 }
0298 
0299                 // This might be endian dependent
0300 
0301                 buf[i++] = r8;
0302                 buf[i++] = g8;
0303                 buf[i++] = b8;
0304 
0305                 if (imageHasAlpha())
0306                 {
0307                     buf[i++] = a8;
0308                 }
0309             }
0310         }
0311 
0312         if (!TIFFWriteScanline(tif, buf, y, 0))
0313         {
0314             qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot write main image to target file.";
0315             _TIFFfree(buf);
0316             TIFFClose(tif);
0317             return false;
0318         }
0319     }
0320 
0321     _TIFFfree(buf);
0322     TIFFWriteDirectory(tif);
0323 
0324     // -------------------------------------------------------------------
0325     // Write thumbnail in tiff directory IFD1
0326 
0327     QImage thumb = m_image->smoothScale(160, 120, Qt::KeepAspectRatio).copyQImage();
0328 
0329     TIFFSetField(tif, TIFFTAG_IMAGEWIDTH,      (uint32)thumb.width());
0330     TIFFSetField(tif, TIFFTAG_IMAGELENGTH,     (uint32)thumb.height());
0331     TIFFSetField(tif, TIFFTAG_PHOTOMETRIC,     PHOTOMETRIC_RGB);
0332     TIFFSetField(tif, TIFFTAG_PLANARCONFIG,    PLANARCONFIG_CONTIG);
0333     TIFFSetField(tif, TIFFTAG_ORIENTATION,     ORIENTATION_TOPLEFT);
0334     TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT,  RESUNIT_NONE);
0335     TIFFSetField(tif, TIFFTAG_COMPRESSION,     COMPRESSION_NONE);
0336     TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
0337     TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE,   8);
0338     TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP,    TIFFDefaultStripSize(tif, 0));
0339 
0340     uchar* pixelThumb = nullptr;
0341     uchar* dataThumb  = thumb.bits();
0342     uint8* bufThumb   = (uint8*) _TIFFmalloc(TIFFScanlineSize(tif));
0343 
0344     if (!bufThumb)
0345     {
0346         qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot allocate memory buffer for thumbnail.";
0347         TIFFClose(tif);
0348         return false;
0349     }
0350 
0351     for (y = 0 ; y < uint32(thumb.height()) ; ++y)
0352     {
0353         i = 0;
0354 
0355         for (x = 0 ; x < uint32(thumb.width()) ; ++x)
0356         {
0357             pixelThumb    = &dataThumb[((y * thumb.width()) + x) * 4];
0358 
0359             // This might be endian dependent
0360             bufThumb[i++] = (uint8)pixelThumb[2];
0361             bufThumb[i++] = (uint8)pixelThumb[1];
0362             bufThumb[i++] = (uint8)pixelThumb[0];
0363         }
0364 
0365         if (!TIFFWriteScanline(tif, bufThumb, y, 0))
0366         {
0367             qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot write thumbnail to target file.";
0368             _TIFFfree(bufThumb);
0369             TIFFClose(tif);
0370             return false;
0371         }
0372     }
0373 
0374     _TIFFfree(bufThumb);
0375     TIFFClose(tif);
0376 
0377     // -------------------------------------------------------------------
0378 
0379     if (observer)
0380     {
0381         observer->progressInfo(1.0F);
0382     }
0383 
0384     imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("TIFF"));
0385 
0386     // Save metadata
0387 
0388     QScopedPointer<DMetadata> metaDataToFile(new DMetadata(filePath));
0389     metaDataToFile->setData(m_image->getMetadata());
0390 
0391     // See bug #211758 for these special steps needed when writing a TIFF
0392 
0393     metaDataToFile->removeExifThumbnail();
0394     metaDataToFile->removeExifTag("Exif.Image.ProcessingSoftware");
0395     metaDataToFile->applyChanges(true);
0396 
0397     return true;
0398 }
0399 
0400 void DImgTIFFLoader::tiffSetExifAsciiTag(TIFF* const tif,
0401                                          ttag_t tiffTag,
0402                                          const DMetadata& metaData,
0403                                          const char* const exifTagName)
0404 {
0405     QByteArray tag = metaData.getExifTagData(exifTagName);
0406 
0407     if (!tag.isEmpty())
0408     {
0409         QByteArray str(tag.data(), tag.size());
0410         TIFFSetField(tif, tiffTag, (const char*)str.constData());
0411     }
0412 }
0413 
0414 void DImgTIFFLoader::tiffSetExifDataTag(TIFF* const tif,
0415                                         ttag_t tiffTag,
0416                                         const DMetadata& metaData,
0417                                         const char* const exifTagName)
0418 {
0419     QByteArray tag = metaData.getExifTagData(exifTagName);
0420 
0421     if (!tag.isEmpty())
0422     {
0423         TIFFSetField(tif, tiffTag, (uint32)tag.size(), (char*)tag.data());
0424     }
0425 }
0426 
0427 } // namespace DigikamTIFFDImgPlugin