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