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