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

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-06-14
0007  * Description : A JPEG IO file for DImg framework - load operations
0008  *
0009  * SPDX-FileCopyrightText: 2005      by Renchi Raju <renchi dot raju at gmail dot com>
0010  * SPDX-FileCopyrightText: 2005-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 #define XMD_H
0017 
0018 #include "dimgjpegloader.h"
0019 
0020 // C ANSI includes
0021 
0022 extern "C"
0023 {
0024 #include "iccjpeg.h"
0025 }
0026 
0027 // Qt includes
0028 
0029 #include <QFile>
0030 #include <QByteArray>
0031 
0032 // Local includes
0033 
0034 #include "digikam_debug.h"
0035 #include "digikam_config.h"
0036 #include "dimgloaderobserver.h"
0037 
0038 #ifdef Q_OS_WIN
0039 #   include "jpegwin.h"
0040 #endif
0041 
0042 namespace DigikamJPEGDImgPlugin
0043 {
0044 
0045 bool DImgJPEGLoader::load(const QString& filePath, DImgLoaderObserver* const observer)
0046 {
0047     readMetadata(filePath);
0048 
0049 #ifdef Q_OS_WIN
0050 
0051     FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"rb");
0052 
0053 #else
0054 
0055     FILE* const file = fopen(filePath.toUtf8().constData(), "rb");
0056 
0057 #endif
0058 
0059     if (!file)
0060     {
0061         loadingFailed();
0062         return false;
0063     }
0064 
0065     struct jpeg_decompress_struct cinfo;
0066     struct dimg_jpeg_error_mgr    jerr;
0067 
0068     // -------------------------------------------------------------------
0069     // JPEG error handling.
0070 
0071     cinfo.err                 = jpeg_std_error(&jerr);
0072     cinfo.err->error_exit     = dimg_jpeg_error_exit;
0073     cinfo.err->emit_message   = dimg_jpeg_emit_message;
0074     cinfo.err->output_message = dimg_jpeg_output_message;
0075 
0076     // setjmp-save cleanup
0077     class Q_DECL_HIDDEN CleanupData
0078     {
0079     public:
0080 
0081         CleanupData()
0082           : data(nullptr),
0083             dest(nullptr),
0084             file(nullptr),
0085             cmod(0)
0086         {
0087         }
0088 
0089         ~CleanupData()
0090         {
0091             delete [] data;
0092             delete [] dest;
0093 
0094             if (file)
0095             {
0096                 fclose(file);
0097             }
0098         }
0099 
0100         void setData(uchar* const d)
0101         {
0102             data = d;
0103         }
0104 
0105         void setDest(uchar* const d)
0106         {
0107             dest = d;
0108         }
0109 
0110         void setFile(FILE* const f)
0111         {
0112             file = f;
0113         }
0114 
0115         void setSize(const QSize& s)
0116         {
0117             size = s;
0118         }
0119 
0120         void setColorModel(int c)
0121         {
0122             cmod = c;
0123         }
0124 
0125         void deleteData()
0126         {
0127             delete [] data;
0128             data = nullptr;
0129         }
0130 
0131         void takeDest()
0132         {
0133             dest = nullptr;
0134         }
0135 
0136     public:
0137 
0138         uchar* data;
0139         uchar* dest;
0140         FILE*  file;
0141 
0142         QSize  size;
0143         int    cmod;
0144     };
0145 
0146     CleanupData* const cleanupData = new CleanupData;
0147     cleanupData->setFile(file);
0148 
0149     // If an error occurs during reading, libjpeg will jump here
0150 
0151 #ifdef __MINGW32__  // krazy:exclude=cpp
0152 
0153     if (__builtin_setjmp(jerr.setjmp_buffer))
0154 
0155 #else
0156 
0157     if (setjmp(jerr.setjmp_buffer))
0158 
0159 #endif
0160 
0161     {
0162         jpeg_destroy_decompress(&cinfo);
0163 
0164         if (!cleanupData->dest ||
0165             !cleanupData->size.isValid())
0166         {
0167             delete cleanupData;
0168             loadingFailed();
0169             return false;
0170         }
0171 
0172         // We check only Exif metadata for ICC profile to prevent endless loop
0173 
0174         if (m_loadFlags & LoadICCData)
0175         {
0176             checkExifWorkingColorSpace();
0177         }
0178 
0179         if (observer)
0180         {
0181             observer->progressInfo(1.0F);
0182         }
0183 
0184         imageWidth()  = cleanupData->size.width();
0185         imageHeight() = cleanupData->size.height();
0186         imageData()   = cleanupData->dest;
0187         imageSetAttribute(QLatin1String("format"),             QLatin1String("JPG"));
0188         imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod);
0189         imageSetAttribute(QLatin1String("originalBitDepth"),   8);
0190         imageSetAttribute(QLatin1String("originalSize"),       cleanupData->size);
0191 
0192         cleanupData->takeDest();
0193         delete cleanupData;
0194         return true;
0195     }
0196 
0197     // -------------------------------------------------------------------
0198     // Find out if we do the fast-track loading with reduced size. Jpeg specific.
0199     int scaledLoadingSize = 0;
0200     QVariant attribute    = imageGetAttribute(QLatin1String("scaledLoadingSize"));
0201 
0202     if (attribute.isValid())
0203     {
0204         scaledLoadingSize = attribute.toInt();
0205     }
0206 
0207     // -------------------------------------------------------------------
0208     // Set JPEG decompressor instance
0209 
0210     jpeg_create_decompress(&cinfo);
0211     bool startedDecompress = false;
0212 
0213     jpeg_stdio_src(&cinfo, file);
0214 
0215     // Recording ICC profile marker (from iccjpeg.c)
0216     if (m_loadFlags & LoadICCData)
0217     {
0218         setup_read_icc_profile(&cinfo);
0219     }
0220 
0221     // read image information
0222     jpeg_read_header(&cinfo, boolean(true));
0223 
0224     // read dimension (nominal values from header)
0225     int w = cinfo.image_width;
0226     int h = cinfo.image_height;
0227     QSize originalSize(w, h);
0228 
0229     // Libjpeg handles the following conversions:
0230     // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK
0231     // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below
0232     int colorModel = DImg::COLORMODELUNKNOWN;
0233 
0234     switch (cinfo.jpeg_color_space)
0235     {
0236         case JCS_UNKNOWN:
0237             // perhaps jpeg_read_header did some guessing, leave value unchanged
0238             colorModel            = DImg::COLORMODELUNKNOWN;
0239             break;
0240 
0241         case JCS_GRAYSCALE:
0242             cinfo.out_color_space = JCS_RGB;
0243             colorModel            = DImg::GRAYSCALE;
0244             break;
0245 
0246         case JCS_RGB:
0247             cinfo.out_color_space = JCS_RGB;
0248             colorModel            = DImg::RGB;
0249             break;
0250 
0251         case JCS_YCbCr:
0252             cinfo.out_color_space = JCS_RGB;
0253             colorModel            = DImg::YCBCR;
0254             break;
0255 
0256         case JCS_CMYK:
0257         case JCS_YCCK:
0258             cinfo.out_color_space = JCS_CMYK;
0259             colorModel            = DImg::CMYK;
0260             break;
0261 
0262         default:
0263             break;
0264     }
0265 
0266     cleanupData->setColorModel(colorModel);
0267 
0268     // -------------------------------------------------------------------
0269     // Load image data.
0270 
0271     uchar* dest = nullptr;
0272 
0273     if (m_loadFlags & LoadImageData)
0274     {
0275         // set decompression parameters
0276         cinfo.do_fancy_upsampling = boolean(true);
0277         cinfo.do_block_smoothing  = boolean(false);
0278 
0279         // handle scaled loading
0280         if (scaledLoadingSize)
0281         {
0282             int imgSize = qMax(cinfo.image_width, cinfo.image_height);
0283             int scale   = 1;
0284 
0285             // libjpeg supports 1/1, 1/2, 1/4, 1/8
0286 
0287             while (scaledLoadingSize * scale * 2 <= imgSize)
0288             {
0289                 scale *= 2;
0290             }
0291 
0292             if (scale > 8)
0293             {
0294                 scale = 8;
0295             }
0296 /*
0297             cinfo.scale_num   = 1;
0298             cinfo.scale_denom = scale;
0299 */
0300             cinfo.scale_denom *= scale;
0301         }
0302 
0303         // initialize decompression
0304         if (!startedDecompress)
0305         {
0306             jpeg_start_decompress(&cinfo);
0307             startedDecompress = true;
0308         }
0309 
0310         // some pseudo-progress
0311         if (observer)
0312         {
0313             observer->progressInfo(0.1F);
0314         }
0315 
0316         // re-read dimension (scaling included)
0317         w = cinfo.output_width;
0318         h = cinfo.output_height;
0319 
0320         // -------------------------------------------------------------------
0321         // Get scanlines
0322 
0323         uchar* ptr  = nullptr, *data = nullptr, *line[16];
0324         uchar* ptr2 = nullptr;
0325         int    x, y, l, i, scans;
0326         //        int count;
0327         //        int prevy;
0328 
0329         if (cinfo.rec_outbuf_height > 16)
0330         {
0331             jpeg_destroy_decompress(&cinfo);
0332             qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Height of JPEG scanline buffer out of range!";
0333             delete cleanupData;
0334             loadingFailed();
0335             return false;
0336         }
0337 
0338         // We only take RGB with 1 or 3 components, or CMYK with 4 components
0339         if (!(
0340                 (cinfo.out_color_space == JCS_RGB  && (cinfo.output_components == 3 || cinfo.output_components == 1)) ||
0341                 (cinfo.out_color_space == JCS_CMYK &&  cinfo.output_components == 4)
0342             ))
0343         {
0344             jpeg_destroy_decompress(&cinfo);
0345             qCWarning(DIGIKAM_DIMG_LOG_JPEG)
0346                     << "JPEG colorspace ("
0347                     << cinfo.out_color_space
0348                     << ") or Number of JPEG color components ("
0349                     << cinfo.output_components
0350                     << ") unsupported!";
0351             delete cleanupData;
0352             loadingFailed();
0353             return false;
0354         }
0355 
0356         data = new_failureTolerant(w * 16 * cinfo.output_components);
0357         cleanupData->setData(data);
0358 
0359         if (!data)
0360         {
0361             jpeg_destroy_decompress(&cinfo);
0362             qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!";
0363             delete cleanupData;
0364             loadingFailed();
0365             return false;
0366         }
0367 
0368         dest = new_failureTolerant(w, h, 4);
0369         cleanupData->setSize(QSize(w, h));
0370         cleanupData->setDest(dest);
0371 
0372         if (!dest)
0373         {
0374             jpeg_destroy_decompress(&cinfo);
0375             qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!";
0376             delete cleanupData;
0377             loadingFailed();
0378             return false;
0379         }
0380 
0381         ptr2  = dest;
0382         //        count = 0;
0383         //        prevy = 0;
0384 
0385         if (cinfo.output_components == 3)
0386         {
0387             for (i = 0; i < cinfo.rec_outbuf_height; ++i)
0388             {
0389                 line[i] = data + (i * w * 3);
0390             }
0391 
0392             int checkPoint = 0;
0393 
0394             for (l = 0; l < h; l += cinfo.rec_outbuf_height)
0395             {
0396                 // use 0-10% and 90-100% for pseudo-progress
0397                 if (observer && l >= checkPoint)
0398                 {
0399                     checkPoint += granularity(observer, h, 0.8F);
0400 
0401                     if (!observer->continueQuery())
0402                     {
0403                         jpeg_destroy_decompress(&cinfo);
0404                         delete cleanupData;
0405                         loadingFailed();
0406                         return false;
0407                     }
0408 
0409                     observer->progressInfo(0.1F + (0.8F * (((float)l) / ((float)h))));
0410                 }
0411 
0412                 jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height);
0413                 scans = cinfo.rec_outbuf_height;
0414 
0415                 if ((h - l) < scans)
0416                 {
0417                     scans = h - l;
0418                 }
0419 
0420                 ptr = data;
0421 
0422                 for (y = 0; y < scans; ++y)
0423                 {
0424                     for (x = 0; x < w; ++x)
0425                     {
0426                         ptr2[3] = 0xFF;
0427                         ptr2[2] = ptr[0];
0428                         ptr2[1] = ptr[1];
0429                         ptr2[0] = ptr[2];
0430 
0431                         ptr    += 3;
0432                         ptr2   += 4;
0433                     }
0434                 }
0435             }
0436         }
0437         else if (cinfo.output_components == 1)
0438         {
0439             for (i = 0; i < cinfo.rec_outbuf_height; ++i)
0440             {
0441                 line[i] = data + (i * w);
0442             }
0443 
0444             int checkPoint = 0;
0445 
0446             for (l = 0; l < h; l += cinfo.rec_outbuf_height)
0447             {
0448                 if (observer && l >= checkPoint)
0449                 {
0450                     checkPoint += granularity(observer, h, 0.8F);
0451 
0452                     if (!observer->continueQuery())
0453                     {
0454                         jpeg_destroy_decompress(&cinfo);
0455                         delete cleanupData;
0456                         loadingFailed();
0457                         return false;
0458                     }
0459 
0460                     observer->progressInfo(0.1F + (0.8F * (((float)l) / ((float)h))));
0461                 }
0462 
0463                 jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height);
0464                 scans = cinfo.rec_outbuf_height;
0465 
0466                 if ((h - l) < scans)
0467                 {
0468                     scans = h - l;
0469                 }
0470 
0471                 ptr = data;
0472 
0473                 for (y = 0; y < scans; ++y)
0474                 {
0475                     for (x = 0; x < w; ++x)
0476                     {
0477                         ptr2[3] = 0xFF;
0478                         ptr2[2] = ptr[0];
0479                         ptr2[1] = ptr[0];
0480                         ptr2[0] = ptr[0];
0481 
0482                         ptr    ++;
0483                         ptr2   += 4;
0484                     }
0485                 }
0486             }
0487         }
0488         else // CMYK
0489         {
0490             for (i = 0; i < cinfo.rec_outbuf_height; ++i)
0491             {
0492                 line[i] = data + (i * w * 4);
0493             }
0494 
0495             int checkPoint = 0;
0496 
0497             for (l = 0; l < h; l += cinfo.rec_outbuf_height)
0498             {
0499                 // use 0-10% and 90-100% for pseudo-progress
0500                 if (observer && l >= checkPoint)
0501                 {
0502                     checkPoint += granularity(observer, h, 0.8F);
0503 
0504                     if (!observer->continueQuery())
0505                     {
0506                         jpeg_destroy_decompress(&cinfo);
0507                         delete cleanupData;
0508                         loadingFailed();
0509                         return false;
0510                     }
0511 
0512                     observer->progressInfo(0.1F + (0.8F * (((float)l) / ((float)h))));
0513                 }
0514 
0515                 jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height);
0516                 scans = cinfo.rec_outbuf_height;
0517 
0518                 if ((h - l) < scans)
0519                 {
0520                     scans = h - l;
0521                 }
0522 
0523                 ptr   = data;
0524 
0525                 for (y = 0; y < scans; ++y)
0526                 {
0527                     for (x = 0; x < w; ++x)
0528                     {
0529                         // Inspired by Qt's JPEG loader
0530 
0531                         int k = ptr[3];
0532 
0533                         ptr2[3] = 0xFF;
0534                         ptr2[2] = k * ptr[0] / 255;
0535                         ptr2[1] = k * ptr[1] / 255;
0536                         ptr2[0] = k * ptr[2] / 255;
0537 
0538                         ptr    += 4;
0539                         ptr2   += 4;
0540                     }
0541                 }
0542             }
0543         }
0544 
0545         // clean up
0546         cleanupData->deleteData();
0547     }
0548 
0549     // -------------------------------------------------------------------
0550     // Read image ICC profile
0551 
0552     if (m_loadFlags & LoadICCData)
0553     {
0554         if (!startedDecompress)
0555         {
0556             jpeg_start_decompress(&cinfo);
0557             startedDecompress = true;
0558         }
0559 
0560         JOCTET* profile_data = nullptr;
0561         uint    profile_size = 0;
0562 
0563         read_icc_profile(&cinfo, &profile_data, &profile_size);
0564 
0565         if (profile_data != nullptr)
0566         {
0567             QByteArray profile_rawdata;
0568             profile_rawdata.resize(profile_size);
0569             memcpy(profile_rawdata.data(), profile_data, profile_size);
0570             imageSetIccProfile(IccProfile(profile_rawdata));
0571             free(profile_data);
0572         }
0573         else
0574         {
0575             // If ICC profile is null, check Exif metadata.
0576             checkExifWorkingColorSpace();
0577         }
0578     }
0579 
0580     // -------------------------------------------------------------------
0581 
0582     if (startedDecompress)
0583     {
0584         jpeg_finish_decompress(&cinfo);
0585     }
0586 
0587     jpeg_destroy_decompress(&cinfo);
0588 
0589     // -------------------------------------------------------------------
0590 
0591     cleanupData->takeDest();
0592     delete cleanupData;
0593 
0594     if (observer)
0595     {
0596         observer->progressInfo(1.0F);
0597     }
0598 
0599     imageWidth()  = w;
0600     imageHeight() = h;
0601     imageData()   = dest;
0602     imageSetAttribute(QLatin1String("format"),             QLatin1String("JPG"));
0603     imageSetAttribute(QLatin1String("originalColorModel"), colorModel);
0604     imageSetAttribute(QLatin1String("originalBitDepth"),   8);
0605     imageSetAttribute(QLatin1String("originalSize"),       originalSize);
0606 
0607     return true;
0608 }
0609 
0610 } // namespace DigikamJPEGDImgPlugin