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 - load 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 #define PNG_BYTES_TO_CHECK 4 0016 0017 #include "dimgpngloader.h" 0018 0019 // C ANSI includes 0020 0021 extern "C" 0022 { 0023 #ifndef Q_CC_MSVC 0024 # include <unistd.h> 0025 #endif 0026 } 0027 0028 // C++ includes 0029 0030 #include <cstdlib> 0031 #include <cstdio> 0032 0033 // Qt includes 0034 0035 #include <QFile> 0036 #include <QByteArray> 0037 #include <QSysInfo> 0038 0039 // Local includes 0040 0041 #include "metaengine.h" 0042 #include "digikam_debug.h" 0043 #include "digikam_config.h" 0044 #include "digikam_version.h" 0045 #include "dimgloaderobserver.h" 0046 0047 // libPNG includes 0048 0049 extern "C" 0050 { 0051 #include <png.h> 0052 } 0053 0054 #ifdef Q_OS_WIN 0055 0056 void _ReadProc(struct png_struct_def* png_ptr, png_bytep data, png_size_t size) 0057 { 0058 FILE* const file_handle = (FILE*)png_get_io_ptr(png_ptr); 0059 fread(data, size, 1, file_handle); 0060 } 0061 0062 #endif 0063 0064 using namespace Digikam; 0065 0066 namespace DigikamPNGDImgPlugin 0067 { 0068 0069 #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 0070 0071 typedef png_bytep iCCP_data; 0072 0073 #else 0074 0075 typedef png_charp iCCP_data; 0076 0077 #endif 0078 0079 bool DImgPNGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) 0080 { 0081 png_uint_32 w32, h32; 0082 int width, height; 0083 int bit_depth, color_type, interlace_type; 0084 FILE* f = nullptr; 0085 png_structp png_ptr = nullptr; 0086 png_infop info_ptr = nullptr; 0087 0088 // To prevent cppcheck warnings. 0089 (void)f; 0090 (void)png_ptr; 0091 (void)info_ptr; 0092 0093 readMetadata(filePath); 0094 0095 // ------------------------------------------------------------------- 0096 // Open the file 0097 0098 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Opening file" << filePath; 0099 0100 #ifdef Q_OS_WIN 0101 0102 f = _wfopen((const wchar_t*)filePath.utf16(), L"rb"); 0103 0104 #else 0105 0106 f = fopen(filePath.toUtf8().constData(), "rb"); 0107 0108 #endif 0109 0110 if (!f) 0111 { 0112 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open image file."; 0113 loadingFailed(); 0114 return false; 0115 } 0116 0117 unsigned char buf[PNG_BYTES_TO_CHECK]; 0118 0119 size_t membersRead = fread(buf, 1, PNG_BYTES_TO_CHECK, f); 0120 0121 #if PNG_LIBPNG_VER >= 10400 0122 0123 if ((membersRead != PNG_BYTES_TO_CHECK) || png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) 0124 0125 #else 0126 0127 if ((membersRead != PNG_BYTES_TO_CHECK) || !png_check_sig(buf, PNG_BYTES_TO_CHECK)) 0128 0129 #endif 0130 { 0131 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Not a PNG image file."; 0132 fclose(f); 0133 loadingFailed(); 0134 return false; 0135 } 0136 0137 rewind(f); 0138 0139 // ------------------------------------------------------------------- 0140 // Initialize the internal structures 0141 0142 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); 0143 0144 if (!png_ptr) 0145 { 0146 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid PNG image file structure."; 0147 fclose(f); 0148 loadingFailed(); 0149 return false; 0150 } 0151 0152 info_ptr = png_create_info_struct(png_ptr); 0153 0154 if (!info_ptr) 0155 { 0156 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot reading PNG image file structure."; 0157 png_destroy_read_struct(&png_ptr, nullptr, nullptr); 0158 fclose(f); 0159 loadingFailed(); 0160 return false; 0161 } 0162 0163 // ------------------------------------------------------------------- 0164 // PNG error handling. If an error occurs during reading, libpng 0165 // will jump here 0166 0167 // setjmp-save cleanup 0168 class Q_DECL_HIDDEN CleanupData 0169 { 0170 0171 public: 0172 0173 CleanupData() 0174 : data (nullptr), 0175 lines(nullptr), 0176 file (nullptr), 0177 cmod (0) 0178 { 0179 } 0180 0181 ~CleanupData() 0182 { 0183 delete [] data; 0184 freeLines(); 0185 0186 if (file) 0187 { 0188 fclose(file); 0189 } 0190 } 0191 0192 void setData(uchar* const d) 0193 { 0194 data = d; 0195 } 0196 0197 void setLines(uchar** const l) 0198 { 0199 lines = l; 0200 } 0201 0202 void setFile(FILE* const f) 0203 { 0204 file = f; 0205 } 0206 0207 void setSize(const QSize& s) 0208 { 0209 size = s; 0210 } 0211 0212 void setColorModel(int c) 0213 { 0214 cmod = c; 0215 } 0216 0217 void takeData() 0218 { 0219 data = nullptr; 0220 } 0221 0222 void freeLines() 0223 { 0224 if (lines) 0225 { 0226 free(lines); 0227 } 0228 0229 lines = nullptr; 0230 } 0231 0232 public: 0233 0234 uchar* data; 0235 uchar** lines; 0236 FILE* file; 0237 0238 QSize size; 0239 int cmod; 0240 }; 0241 0242 CleanupData* const cleanupData = new CleanupData; 0243 cleanupData->setFile(f); 0244 0245 #if PNG_LIBPNG_VER >= 10400 0246 0247 if (setjmp(png_jmpbuf(png_ptr))) 0248 0249 #else 0250 0251 if (setjmp(png_ptr->jmpbuf)) 0252 0253 #endif 0254 { 0255 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); 0256 0257 if (!cleanupData->data || 0258 !cleanupData->size.isValid()) 0259 { 0260 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during reading file. Process aborted!"; 0261 delete cleanupData; 0262 loadingFailed(); 0263 return false; 0264 } 0265 0266 // We check only Exif metadata for ICC profile to prevent endless loop 0267 0268 if (m_loadFlags & LoadICCData) 0269 { 0270 checkExifWorkingColorSpace(); 0271 } 0272 0273 if (observer) 0274 { 0275 observer->progressInfo(1.0F); 0276 } 0277 0278 imageWidth() = cleanupData->size.width(); 0279 imageHeight() = cleanupData->size.height(); 0280 imageData() = cleanupData->data; 0281 imageSetAttribute(QLatin1String("format"), QLatin1String("PNG")); 0282 imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod); 0283 imageSetAttribute(QLatin1String("originalBitDepth"), m_sixteenBit ? 16 : 8); 0284 imageSetAttribute(QLatin1String("originalSize"), cleanupData->size); 0285 0286 cleanupData->takeData(); 0287 delete cleanupData; 0288 return true; 0289 } 0290 0291 #ifdef PNG_BENIGN_ERRORS_SUPPORTED 0292 0293 // Change some libpng errors to warnings (e.g. bug 386396). 0294 0295 png_set_benign_errors(png_ptr, true); 0296 0297 png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); 0298 0299 #endif 0300 0301 #ifdef Q_OS_WIN 0302 0303 png_set_read_fn(png_ptr, f, _ReadProc); 0304 0305 #else 0306 0307 png_init_io(png_ptr, f); 0308 0309 #endif 0310 0311 // ------------------------------------------------------------------- 0312 // Read all PNG info up to image data 0313 0314 png_read_info(png_ptr, info_ptr); 0315 0316 png_get_IHDR(png_ptr, 0317 info_ptr, 0318 (png_uint_32*)(&w32), 0319 (png_uint_32*)(&h32), 0320 &bit_depth, 0321 &color_type, 0322 &interlace_type, 0323 nullptr, 0324 nullptr); 0325 0326 width = (int)w32; 0327 height = (int)h32; 0328 0329 int colorModel = DImg::COLORMODELUNKNOWN; 0330 m_sixteenBit = (bit_depth == 16); 0331 0332 switch (color_type) 0333 { 0334 case PNG_COLOR_TYPE_RGB: // RGB 0335 { 0336 m_hasAlpha = false; 0337 colorModel = DImg::RGB; 0338 break; 0339 } 0340 0341 case PNG_COLOR_TYPE_RGB_ALPHA: // RGBA 0342 { 0343 m_hasAlpha = true; 0344 colorModel = DImg::RGB; 0345 break; 0346 } 0347 0348 case PNG_COLOR_TYPE_GRAY: // Grayscale 0349 { 0350 m_hasAlpha = false; 0351 colorModel = DImg::GRAYSCALE; 0352 break; 0353 } 0354 0355 case PNG_COLOR_TYPE_GRAY_ALPHA: // Grayscale + Alpha 0356 { 0357 m_hasAlpha = true; 0358 colorModel = DImg::GRAYSCALE; 0359 break; 0360 } 0361 0362 case PNG_COLOR_TYPE_PALETTE: // Indexed 0363 { 0364 m_hasAlpha = false; 0365 colorModel = DImg::INDEXED; 0366 break; 0367 } 0368 } 0369 0370 cleanupData->setColorModel(colorModel); 0371 cleanupData->setSize(QSize(width, height)); 0372 0373 uchar* data = nullptr; 0374 0375 if (m_loadFlags & LoadImageData) 0376 { 0377 // TODO: Endianness: 0378 // You may notice that the code for little and big endian 0379 // below is now identical. This was found to work by PPC users. 0380 // If this proves right, all the conditional clauses can be removed. 0381 0382 if (bit_depth == 16) 0383 { 0384 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in 16 bits/color/pixel."; 0385 0386 switch (color_type) 0387 { 0388 case PNG_COLOR_TYPE_RGB : // RGB 0389 { 0390 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; 0391 0392 png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); 0393 break; 0394 } 0395 0396 case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA 0397 { 0398 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; 0399 break; 0400 } 0401 0402 case PNG_COLOR_TYPE_GRAY : // Grayscale 0403 { 0404 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; 0405 0406 png_set_gray_to_rgb(png_ptr); 0407 png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); 0408 0409 break; 0410 } 0411 0412 case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha 0413 { 0414 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; 0415 0416 png_set_gray_to_rgb(png_ptr); 0417 0418 break; 0419 } 0420 0421 case PNG_COLOR_TYPE_PALETTE : // Indexed 0422 { 0423 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; 0424 0425 png_set_palette_to_rgb(png_ptr); 0426 png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); 0427 0428 break; 0429 } 0430 0431 default: 0432 { 0433 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown."; 0434 0435 delete cleanupData; 0436 loadingFailed(); 0437 return false; 0438 } 0439 } 0440 } 0441 else 0442 { 0443 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in >=8 bits/color/pixel."; 0444 0445 png_set_packing(png_ptr); 0446 0447 switch (color_type) 0448 { 0449 case PNG_COLOR_TYPE_RGB : // RGB 0450 { 0451 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; 0452 0453 png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); 0454 0455 break; 0456 } 0457 0458 case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA 0459 { 0460 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; 0461 0462 break; 0463 } 0464 0465 case PNG_COLOR_TYPE_GRAY : // Grayscale 0466 { 0467 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; 0468 0469 #if PNG_LIBPNG_VER >= 10400 0470 0471 png_set_expand_gray_1_2_4_to_8(png_ptr); 0472 0473 #else 0474 0475 png_set_gray_1_2_4_to_8(png_ptr); 0476 0477 #endif 0478 0479 png_set_gray_to_rgb(png_ptr); 0480 png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); 0481 0482 break; 0483 } 0484 0485 case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha 0486 { 0487 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; 0488 0489 png_set_gray_to_rgb(png_ptr); 0490 break; 0491 } 0492 0493 case PNG_COLOR_TYPE_PALETTE : // Indexed 0494 { 0495 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; 0496 0497 png_set_packing(png_ptr); 0498 png_set_palette_to_rgb(png_ptr); 0499 png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); 0500 0501 break; 0502 } 0503 0504 default: 0505 { 0506 qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown." << color_type; 0507 0508 delete cleanupData; 0509 loadingFailed(); 0510 return false; 0511 } 0512 } 0513 } 0514 0515 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) 0516 { 0517 png_set_tRNS_to_alpha(png_ptr); 0518 } 0519 0520 double file_gamma; 0521 0522 if (png_get_gAMA(png_ptr, info_ptr, &file_gamma)) 0523 { 0524 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Apply PNG file gamma" << file_gamma; 0525 0526 png_set_gamma(png_ptr, 2.2, file_gamma); 0527 } 0528 0529 png_set_bgr(png_ptr); 0530 0531 //png_set_swap_alpha(png_ptr); 0532 0533 if (observer) 0534 { 0535 observer->progressInfo(0.1F); 0536 } 0537 0538 // ------------------------------------------------------------------- 0539 // Get image data. 0540 0541 // Call before png_read_update_info and png_start_read_image() 0542 // for non-interlaced images number_passes will be 1 0543 int number_passes = png_set_interlace_handling(png_ptr); 0544 0545 png_read_update_info(png_ptr, info_ptr); 0546 0547 if (m_sixteenBit) 0548 { 0549 data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel 0550 } 0551 else 0552 { 0553 data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel 0554 } 0555 0556 cleanupData->setData(data); 0557 0558 uchar** lines = nullptr; 0559 (void)lines; // to prevent cppcheck warnings. 0560 lines = (uchar**)malloc(height * sizeof(uchar*)); 0561 cleanupData->setLines(lines); 0562 0563 if (!data || !lines) 0564 { 0565 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Cannot allocate memory to load PNG image data."; 0566 png_read_end(png_ptr, info_ptr); 0567 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); 0568 delete cleanupData; 0569 loadingFailed(); 0570 return false; 0571 } 0572 0573 for (int i = 0 ; i < height ; ++i) 0574 { 0575 if (m_sixteenBit) 0576 { 0577 lines[i] = data + ((quint64)i * (quint64)width * 8); 0578 } 0579 else 0580 { 0581 lines[i] = data + ((quint64)i * (quint64)width * 4); 0582 } 0583 } 0584 0585 // The easy way to read the whole image 0586 // png_read_image(png_ptr, lines); 0587 // The other way to read images is row by row. Necessary for observer. 0588 // Now we need to deal with interlacing. 0589 0590 for (int pass = 0 ; pass < number_passes ; ++pass) 0591 { 0592 int checkPoint = 0; 0593 0594 for (int y = 0 ; y < height ; ++y) 0595 { 0596 if (observer && y == checkPoint) 0597 { 0598 checkPoint += granularity(observer, height, 0.7F); 0599 0600 if (!observer->continueQuery()) 0601 { 0602 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); 0603 delete cleanupData; 0604 loadingFailed(); 0605 return false; 0606 } 0607 0608 // use 10% - 80% for progress while reading rows 0609 observer->progressInfo(0.1F + (0.7F * (((float)y) / ((float)height)))); 0610 } 0611 0612 png_read_rows(png_ptr, lines + y, nullptr, 1); 0613 } 0614 } 0615 0616 cleanupData->freeLines(); 0617 0618 if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) 0619 { 0620 // Swap bytes in 16 bits/color/pixel for DImg 0621 0622 if (m_sixteenBit) 0623 { 0624 uchar ptr[8]; // One pixel to swap 0625 0626 for (uint p = 0 ; p < (uint)width * height * 8 ; p += 8) 0627 { 0628 memcpy(&ptr[0], &data[p], 8); // Current pixel 0629 0630 data[ p ] = ptr[1]; // Blue 0631 data[p + 1] = ptr[0]; 0632 data[p + 2] = ptr[3]; // Green 0633 data[p + 3] = ptr[2]; 0634 data[p + 4] = ptr[5]; // Red 0635 data[p + 5] = ptr[4]; 0636 data[p + 6] = ptr[7]; // Alpha 0637 data[p + 7] = ptr[6]; 0638 } 0639 } 0640 } 0641 } 0642 0643 if (observer) 0644 { 0645 observer->progressInfo(0.9F); 0646 } 0647 0648 // ------------------------------------------------------------------- 0649 // Read image ICC profile 0650 0651 if (m_loadFlags & LoadICCData) 0652 { 0653 png_charp profile_name; 0654 iCCP_data profile_data = nullptr; 0655 png_uint_32 profile_size; 0656 int compression_type; 0657 0658 png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size); 0659 0660 if (profile_data != nullptr) 0661 { 0662 QByteArray profile_rawdata; 0663 profile_rawdata.resize(profile_size); 0664 memcpy(profile_rawdata.data(), profile_data, profile_size); 0665 imageSetIccProfile(IccProfile(profile_rawdata)); 0666 } 0667 else 0668 { 0669 // If ICC profile is null, check Exif metadata. 0670 0671 checkExifWorkingColorSpace(); 0672 } 0673 } 0674 0675 // ------------------------------------------------------------------- 0676 // Get embedded text data. 0677 0678 png_text* text_ptr = nullptr; 0679 int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, nullptr); 0680 0681 /* 0682 Standard Embedded text includes in PNG : 0683 0684 Title Short (one line) title or caption for image 0685 Author Name of image's creator 0686 Description Description of image (possibly long) 0687 Copyright Copyright notice 0688 Creation Time Time of original image creation 0689 Software Software used to create the image 0690 Disclaimer Legal disclaimer 0691 Warning Warning of nature of content 0692 Source Device used to create the image 0693 Comment Miscellaneous comment; conversion from GIF comment 0694 0695 Extra Raw profiles tag are used by ImageMagick and defines at this Url: 0696 search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData 0697 */ 0698 0699 if (m_loadFlags & LoadICCData) 0700 { 0701 for (int i = 0 ; i < num_comments ; ++i) 0702 { 0703 // Check if we have a Raw profile embedded using ImageMagick technique. 0704 0705 if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 || 0706 memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 || 0707 memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0) 0708 { 0709 imageSetEmbbededText(QLatin1String(text_ptr[i].key), QLatin1String(text_ptr[i].text)); 0710 0711 qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Reading PNG Embedded text: key=" << text_ptr[i].key 0712 << "size=" << QLatin1String(text_ptr[i].text).size(); 0713 } 0714 } 0715 } 0716 0717 // ------------------------------------------------------------------- 0718 0719 if (m_loadFlags & LoadImageData) 0720 { 0721 png_read_end(png_ptr, info_ptr); 0722 } 0723 0724 png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); 0725 cleanupData->takeData(); 0726 delete cleanupData; 0727 0728 if (observer) 0729 { 0730 observer->progressInfo(1.0F); 0731 } 0732 0733 imageWidth() = width; 0734 imageHeight() = height; 0735 imageData() = data; 0736 imageSetAttribute(QLatin1String("format"), QLatin1String("PNG")); 0737 imageSetAttribute(QLatin1String("originalColorModel"), colorModel); 0738 imageSetAttribute(QLatin1String("originalBitDepth"), bit_depth); 0739 imageSetAttribute(QLatin1String("originalSize"), QSize(width, height)); 0740 0741 return true; 0742 } 0743 0744 } // namespace DigikamPNGDImgPlugin