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