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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-11-01
0007  * Description : a PNG image loader for DImg framework - save operations.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dimgpngloader.h"
0016 
0017 // C ANSI includes
0018 
0019 extern "C"
0020 {
0021 #ifndef Q_CC_MSVC
0022 #   include <unistd.h>
0023 #endif
0024 }
0025 
0026 // C++ includes
0027 
0028 #include <cstdlib>
0029 #include <cstdio>
0030 
0031 // Qt includes
0032 
0033 #include <QFile>
0034 #include <QByteArray>
0035 #include <QSysInfo>
0036 
0037 // Local includes
0038 
0039 #include "metaengine.h"
0040 #include "digikam_debug.h"
0041 #include "digikam_config.h"
0042 #include "digikam_version.h"
0043 #include "dimgloaderobserver.h"
0044 
0045 // libPNG includes
0046 
0047 extern "C"
0048 {
0049 #include <png.h>
0050 }
0051 
0052 using namespace Digikam;
0053 
0054 namespace DigikamPNGDImgPlugin
0055 {
0056 
0057 #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
0058 
0059 typedef png_bytep iCCP_data;
0060 
0061 #else
0062 
0063 typedef png_charp iCCP_data;
0064 
0065 #endif
0066 
0067 bool DImgPNGLoader::save(const QString& filePath, DImgLoaderObserver* const observer)
0068 {
0069     png_structp    png_ptr;
0070     png_infop      info_ptr;
0071     uint           x, y, j;
0072     png_bytep      row_ptr;
0073     png_color_8    sig_bit;
0074     FILE*          f           = nullptr;
0075     uchar*         ptr         = nullptr;
0076     uchar*         data        = nullptr;
0077     int            quality     = 75;
0078     int            compression = 3;
0079 
0080     // Tp prevent cppcheck warnings.
0081     (void)f;
0082     (void)ptr;
0083     (void)data;
0084     (void)quality;
0085     (void)compression;
0086 
0087     // -------------------------------------------------------------------
0088     // Open the file
0089 
0090 #ifdef Q_OS_WIN
0091 
0092     f = _wfopen((const wchar_t*)filePath.utf16(), L"wb");
0093 
0094 #else
0095 
0096     f = fopen(filePath.toUtf8().constData(), "wb");
0097 
0098 #endif
0099 
0100     if (!f)
0101     {
0102         qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open target image file.";
0103         return false;
0104     }
0105 
0106     // -------------------------------------------------------------------
0107     // Initialize the internal structures
0108 
0109     png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
0110 
0111     if (!png_ptr)
0112     {
0113         qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid target PNG image file structure.";
0114         fclose(f);
0115         return false;
0116     }
0117 
0118     info_ptr = png_create_info_struct(png_ptr);
0119 
0120     if (info_ptr == nullptr)
0121     {
0122         qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot create PNG image file structure.";
0123         png_destroy_write_struct(&png_ptr, (png_infopp) nullptr);
0124         fclose(f);
0125         return false;
0126     }
0127 
0128     // -------------------------------------------------------------------
0129     // PNG error handling. If an error occurs during writing, libpng
0130     // will jump here
0131 
0132     // setjmp-save cleanup
0133     class Q_DECL_HIDDEN CleanupData
0134     {
0135     public:
0136 
0137         CleanupData()
0138           : data(nullptr),
0139             f   (nullptr)
0140         {
0141         }
0142 
0143         ~CleanupData()
0144         {
0145             delete [] data;
0146 
0147             if (f)
0148             {
0149                 fclose(f);
0150             }
0151         }
0152 
0153         void setData(uchar* const d)
0154         {
0155             data = d;
0156         }
0157 
0158         void setFile(FILE* const file)
0159         {
0160             f = file;
0161         }
0162 
0163         uchar* data;
0164         FILE*  f;
0165     };
0166 
0167     CleanupData* const cleanupData = new CleanupData;
0168     cleanupData->setFile(f);
0169 
0170 #if PNG_LIBPNG_VER >= 10400
0171 
0172     if (setjmp(png_jmpbuf(png_ptr)))
0173 
0174 #else
0175 
0176     if (setjmp(png_ptr->jmpbuf))
0177 
0178 #endif
0179     {
0180         qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during writing file. Process aborted!";
0181         png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
0182         png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
0183         delete cleanupData;
0184         return false;
0185     }
0186 
0187 #ifdef PNG_BENIGN_ERRORS_SUPPORTED
0188 
0189   // Change some libpng errors to warnings (e.g. bug 386396).
0190 
0191   png_set_benign_errors(png_ptr, true);
0192 
0193   png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
0194 
0195 #endif
0196 
0197     png_init_io(png_ptr, f);
0198     png_set_bgr(png_ptr);
0199 
0200     //png_set_swap_alpha(png_ptr);
0201 
0202     if (imageHasAlpha())
0203     {
0204         png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(),
0205                      PNG_COLOR_TYPE_RGB_ALPHA,  PNG_INTERLACE_NONE,
0206                      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
0207 
0208         if (imageSixteenBit())
0209         {
0210             data = new uchar[imageWidth() * 8 * sizeof(uchar)];
0211         }
0212         else
0213         {
0214             data = new uchar[imageWidth() * 4 * sizeof(uchar)];
0215         }
0216     }
0217     else
0218     {
0219         png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(),
0220                      PNG_COLOR_TYPE_RGB,        PNG_INTERLACE_NONE,
0221                      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
0222 
0223         if (imageSixteenBit())
0224         {
0225             data = new uchar[imageWidth() * 6 * sizeof(uchar)];
0226         }
0227         else
0228         {
0229             data = new uchar[imageWidth() * 3 * sizeof(uchar)];
0230         }
0231     }
0232 
0233     cleanupData->setData(data);
0234 
0235     sig_bit.red   = imageBitsDepth();
0236     sig_bit.green = imageBitsDepth();
0237     sig_bit.blue  = imageBitsDepth();
0238     sig_bit.alpha = imageBitsDepth();
0239     png_set_sBIT(png_ptr, info_ptr, &sig_bit);
0240 
0241     // -------------------------------------------------------------------
0242     // Quality to convert to compression
0243 
0244     QVariant qualityAttr = imageGetAttribute(QLatin1String("quality"));
0245     quality              = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
0246 
0247     qCDebug(DIGIKAM_DIMG_LOG_PNG) << "DImg quality level: " << quality;
0248 
0249     if (quality < 1)
0250     {
0251         quality = 1;
0252     }
0253 
0254     if (quality > 99)
0255     {
0256         quality = 99;
0257     }
0258 
0259     quality     = quality / 10;
0260     compression = 9 - quality;
0261 
0262     if (compression < 0)
0263     {
0264         compression = 0;
0265     }
0266 
0267     if (compression > 9)
0268     {
0269         compression = 9;
0270     }
0271 
0272     qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG compression level: " << compression;
0273     png_set_compression_level(png_ptr, compression);
0274 
0275     // -------------------------------------------------------------------
0276     // Write ICC profile.
0277 
0278     QByteArray profile_rawdata = m_image->getIccProfile().data();
0279 
0280     if (!profile_rawdata.isEmpty())
0281     {
0282         purgeExifWorkingColorSpace();
0283         png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"),
0284                      PNG_COMPRESSION_TYPE_BASE,
0285                      (iCCP_data)profile_rawdata.data(),
0286                      profile_rawdata.size());
0287     }
0288 
0289     // -------------------------------------------------------------------
0290     // Write embedded Text
0291 
0292     typedef QMap<QString, QString> EmbeddedTextMap;
0293     EmbeddedTextMap map = imageEmbeddedText();
0294 
0295     for (EmbeddedTextMap::const_iterator it = map.constBegin() ; it != map.constEnd() ; ++it)
0296     {
0297         if (it.key() != QLatin1String("Software") && it.key() != QLatin1String("Comment"))
0298         {
0299             QByteArray key   = it.key().toLatin1();
0300             QByteArray value = it.value().toLatin1();
0301             png_text text;
0302             text.key         = key.data();
0303             text.text        = value.data();
0304 
0305             qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key="
0306                                           << text.key << " text=" << text.text;
0307 
0308             text.compression = PNG_TEXT_COMPRESSION_zTXt;
0309             png_set_text(png_ptr, info_ptr, &(text), 1);
0310         }
0311     }
0312 
0313     // Update 'Software' text tag.
0314 
0315     QString software  = QLatin1String("digiKam ");
0316     software.append(digiKamVersion());
0317     QString libpngver = QLatin1String(PNG_HEADER_VERSION_STRING);
0318     libpngver.replace(QLatin1Char('\n'), QLatin1Char(' '));
0319     software.append(QString::fromLatin1(" (%1)").arg(libpngver));
0320     QByteArray softwareAsAscii = software.toLatin1();
0321     png_text text;
0322     text.key  = (png_charp)("Software");
0323     text.text = softwareAsAscii.data();
0324 
0325     qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key="
0326                                   << text.key << " text=" << text.text;
0327 
0328     text.compression = PNG_TEXT_COMPRESSION_zTXt;
0329     png_set_text(png_ptr, info_ptr, &(text), 1);
0330 
0331     if (observer)
0332     {
0333         observer->progressInfo(0.2F);
0334     }
0335 
0336     // -------------------------------------------------------------------
0337     // Write image data
0338 
0339     png_write_info(png_ptr, info_ptr);
0340     png_set_shift(png_ptr, &sig_bit);
0341     png_set_packing(png_ptr);
0342     ptr = imageData();
0343 
0344     uint checkPoint = 0;
0345 
0346     for (y = 0 ; y < imageHeight() ; ++y)
0347     {
0348 
0349         if (observer && y == checkPoint)
0350         {
0351             checkPoint += granularity(observer, imageHeight(), 0.8F);
0352 
0353             if (!observer->continueQuery())
0354             {
0355                 png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
0356                 png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
0357                 delete cleanupData;
0358                 return false;
0359             }
0360 
0361             observer->progressInfo(0.2F + (0.8F * (((float)y) / ((float)imageHeight()))));
0362         }
0363 
0364         j = 0;
0365 
0366         if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
0367         {
0368             for (x = 0 ; x < imageWidth()*imageBytesDepth() ; x += imageBytesDepth())
0369             {
0370                 if (imageSixteenBit())
0371                 {
0372                     if (imageHasAlpha())
0373                     {
0374                         data[j++] = ptr[x + 1]; // Blue
0375                         data[j++] = ptr[  x  ];
0376                         data[j++] = ptr[x + 3]; // Green
0377                         data[j++] = ptr[x + 2];
0378                         data[j++] = ptr[x + 5]; // Red
0379                         data[j++] = ptr[x + 4];
0380                         data[j++] = ptr[x + 7]; // Alpha
0381                         data[j++] = ptr[x + 6];
0382                     }
0383                     else
0384                     {
0385                         data[j++] = ptr[x + 1]; // Blue
0386                         data[j++] = ptr[  x  ];
0387                         data[j++] = ptr[x + 3]; // Green
0388                         data[j++] = ptr[x + 2];
0389                         data[j++] = ptr[x + 5]; // Red
0390                         data[j++] = ptr[x + 4];
0391                     }
0392                 }
0393                 else
0394                 {
0395                     if (imageHasAlpha())
0396                     {
0397                         data[j++] = ptr[  x  ]; // Blue
0398                         data[j++] = ptr[x + 1]; // Green
0399                         data[j++] = ptr[x + 2]; // Red
0400                         data[j++] = ptr[x + 3]; // Alpha
0401                     }
0402                     else
0403                     {
0404                         data[j++] = ptr[  x  ]; // Blue
0405                         data[j++] = ptr[x + 1]; // Green
0406                         data[j++] = ptr[x + 2]; // Red
0407                     }
0408                 }
0409             }
0410         }
0411         else
0412         {
0413             int bytes = (imageSixteenBit() ? 2 : 1) * (imageHasAlpha() ? 4 : 3);
0414 
0415             for (x = 0 ; x < imageWidth()*imageBytesDepth() ; x += imageBytesDepth())
0416             {
0417                 memcpy(data + j, ptr + x, bytes);
0418                 j += bytes;
0419             }
0420         }
0421 
0422         row_ptr = (png_bytep) data;
0423 
0424         png_write_rows(png_ptr, &row_ptr, 1);
0425         ptr += (imageWidth() * imageBytesDepth());
0426     }
0427 
0428     // -------------------------------------------------------------------
0429 
0430     png_write_end(png_ptr, info_ptr);
0431     png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr);
0432     png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr);
0433 
0434     delete cleanupData;
0435 
0436     imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("PNG"));
0437 
0438     saveMetadata(filePath);
0439 
0440     return true;
0441 }
0442 
0443 } // namespace DigikamPNGDImgPlugin