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 - save 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::save(const QString& filePath, DImgLoaderObserver* const observer)
0046 {
0047 #ifdef Q_OS_WIN
0048 
0049     FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"wb");
0050 
0051 #else
0052 
0053     FILE* const file = fopen(filePath.toUtf8().constData(), "wb");
0054 
0055 #endif
0056 
0057     if (!file)
0058     {
0059         return false;
0060     }
0061 
0062     struct jpeg_compress_struct  cinfo;
0063 
0064     struct dimg_jpeg_error_mgr jerr;
0065 
0066     // -------------------------------------------------------------------
0067     // JPEG error handling.
0068 
0069     cinfo.err                 = jpeg_std_error(&jerr);
0070     cinfo.err->error_exit     = dimg_jpeg_error_exit;
0071     cinfo.err->emit_message   = dimg_jpeg_emit_message;
0072     cinfo.err->output_message = dimg_jpeg_output_message;
0073 
0074     // setjmp-save cleanup
0075     class Q_DECL_HIDDEN CleanupData
0076     {
0077     public:
0078 
0079         CleanupData()
0080             : line(nullptr),
0081               f(nullptr)
0082         {
0083         }
0084 
0085         ~CleanupData()
0086         {
0087             deleteLine();
0088 
0089             if (f)
0090             {
0091                 fclose(f);
0092             }
0093         }
0094 
0095         void setLine(uchar* const l)
0096         {
0097             line = l;
0098         }
0099 
0100         void setFile(FILE* const file)
0101         {
0102             f = file;
0103         }
0104 
0105         void deleteLine()
0106         {
0107             delete [] line;
0108             line = nullptr;
0109         }
0110 
0111     public:
0112 
0113         uchar* line;
0114         FILE*  f;
0115     };
0116 
0117     CleanupData* const cleanupData = new CleanupData;
0118     cleanupData->setFile(file);
0119 
0120     // If an error occurs during writing, libjpeg will jump here
0121 
0122 #ifdef __MINGW32__  // krazy:exclude=cpp
0123 
0124     if (__builtin_setjmp(jerr.setjmp_buffer))
0125 
0126 #else
0127 
0128     if (setjmp(jerr.setjmp_buffer))
0129 
0130 #endif
0131     {
0132         jpeg_destroy_compress(&cinfo);
0133         delete cleanupData;
0134         return false;
0135     }
0136 
0137     // -------------------------------------------------------------------
0138     // Set JPEG compressor instance
0139 
0140     jpeg_create_compress(&cinfo);
0141     jpeg_stdio_dest(&cinfo, file);
0142 
0143     uint&              w   = imageWidth();
0144     uint&              h   = imageHeight();
0145     unsigned char*& data   = imageData();
0146 
0147     // Size of image.
0148     cinfo.image_width      = w;
0149     cinfo.image_height     = h;
0150 
0151     // Color components of image in RGB.
0152     cinfo.input_components = 3;
0153     cinfo.in_color_space   = JCS_RGB;
0154 
0155     QVariant qualityAttr   = imageGetAttribute(QLatin1String("quality"));
0156     int quality            = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
0157 
0158     if (quality < 0)
0159     {
0160         quality = 90;
0161     }
0162 
0163     if (quality > 100)
0164     {
0165         quality = 100;
0166     }
0167 
0168     QVariant subSamplingAttr = imageGetAttribute(QLatin1String("subsampling"));
0169     int subsampling          = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1;  // Medium
0170 
0171     jpeg_set_defaults(&cinfo);
0172 
0173     // bug #149578: set horizontal and vertical chroma subsampling factor to encoder.
0174     // See this page for details: https://en.wikipedia.org/wiki/Chroma_subsampling
0175 
0176     switch (subsampling)
0177     {
0178         case 1:  // 2x1, 1x1, 1x1 (4:2:2)
0179         {
0180             qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:2";
0181             cinfo.comp_info[0].h_samp_factor = 2;
0182             cinfo.comp_info[0].v_samp_factor = 1;
0183             cinfo.comp_info[1].h_samp_factor = 1;
0184             cinfo.comp_info[1].v_samp_factor = 1;
0185             cinfo.comp_info[2].h_samp_factor = 1;
0186             cinfo.comp_info[2].v_samp_factor = 1;
0187             break;
0188         }
0189 
0190         case 2:  // 2x2, 1x1, 1x1 (4:2:0)
0191         {
0192             qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:0";
0193             cinfo.comp_info[0].h_samp_factor = 2;
0194             cinfo.comp_info[0].v_samp_factor = 2;
0195             cinfo.comp_info[1].h_samp_factor = 1;
0196             cinfo.comp_info[1].v_samp_factor = 1;
0197             cinfo.comp_info[2].h_samp_factor = 1;
0198             cinfo.comp_info[2].v_samp_factor = 1;
0199             break;
0200         }
0201 
0202         case 3:  // 4x1, 1x1, 1x1 (4:1:1)
0203         {
0204             qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:1:1";
0205             cinfo.comp_info[0].h_samp_factor = 4;
0206             cinfo.comp_info[0].v_samp_factor = 1;
0207             cinfo.comp_info[1].h_samp_factor = 1;
0208             cinfo.comp_info[1].v_samp_factor = 1;
0209             cinfo.comp_info[2].h_samp_factor = 1;
0210             cinfo.comp_info[2].v_samp_factor = 1;
0211             break;
0212         }
0213 
0214         default: // 1x1, 1x1, 1x1 (4:4:4)
0215         {
0216             qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:4:4";
0217             cinfo.comp_info[0].h_samp_factor = 1;
0218             cinfo.comp_info[0].v_samp_factor = 1;
0219             cinfo.comp_info[1].h_samp_factor = 1;
0220             cinfo.comp_info[1].v_samp_factor = 1;
0221             cinfo.comp_info[2].h_samp_factor = 1;
0222             cinfo.comp_info[2].v_samp_factor = 1;
0223             break;
0224         }
0225     }
0226 
0227     jpeg_set_quality(&cinfo, quality, boolean(true));
0228     jpeg_start_compress(&cinfo, boolean(true));
0229 
0230     qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG quality compression value: " << quality;
0231 
0232     if (observer)
0233     {
0234         observer->progressInfo(0.1F);
0235     }
0236 
0237     // -------------------------------------------------------------------
0238     // Write ICC profile.
0239 
0240     QByteArray profile_rawdata = m_image->getIccProfile().data();
0241 
0242     if (!profile_rawdata.isEmpty())
0243     {
0244         purgeExifWorkingColorSpace();
0245         write_icc_profile(&cinfo, (JOCTET*)profile_rawdata.data(), profile_rawdata.size());
0246     }
0247 
0248     if (observer)
0249     {
0250         observer->progressInfo(0.2F);
0251     }
0252 
0253     // -------------------------------------------------------------------
0254     // Write Image data.
0255 
0256     uchar* line       = new uchar[w * 3];
0257     uchar* dstPtr     = nullptr;
0258     uint   checkPoint = 0;
0259     cleanupData->setLine(line);
0260 
0261     if (!imageSixteenBit())     // 8 bits image.
0262     {
0263 
0264         uchar* srcPtr = data;
0265 
0266         for (uint j = 0; j < h; ++j)
0267         {
0268 
0269             if (observer && j == checkPoint)
0270             {
0271                 checkPoint += granularity(observer, h, 0.8F);
0272 
0273                 if (!observer->continueQuery())
0274                 {
0275                     jpeg_destroy_compress(&cinfo);
0276                     delete cleanupData;
0277                     return false;
0278                 }
0279 
0280                 // use 0-20% for pseudo-progress, now fill 20-100%
0281                 observer->progressInfo(0.2F + (0.8F * (((float)j) / ((float)h))));
0282             }
0283 
0284             dstPtr = line;
0285 
0286             for (uint i = 0; i < w; ++i)
0287             {
0288                 dstPtr[2] = srcPtr[0];  // Blue
0289                 dstPtr[1] = srcPtr[1];  // Green
0290                 dstPtr[0] = srcPtr[2];  // Red
0291 
0292                 srcPtr   += 4;
0293                 dstPtr   += 3;
0294             }
0295 
0296             jpeg_write_scanlines(&cinfo, &line, 1);
0297         }
0298     }
0299     else
0300     {
0301         unsigned short* srcPtr = reinterpret_cast<unsigned short*>(data);
0302 
0303         for (uint j = 0; j < h; ++j)
0304         {
0305 
0306             if (observer && j == checkPoint)
0307             {
0308                 checkPoint += granularity(observer, h, 0.8F);
0309 
0310                 if (!observer->continueQuery())
0311                 {
0312                     jpeg_destroy_compress(&cinfo);
0313                     delete cleanupData;
0314                     return false;
0315                 }
0316 
0317                 // use 0-20% for pseudo-progress, now fill 20-100%
0318                 observer->progressInfo(0.2F + (0.8F * (((float)j) / ((float)h))));
0319             }
0320 
0321             dstPtr = line;
0322 
0323             for (uint i = 0; i < w; ++i)
0324             {
0325                 dstPtr[2] = (srcPtr[0] * 255UL) / 65535UL;  // Blue
0326                 dstPtr[1] = (srcPtr[1] * 255UL) / 65535UL;  // Green
0327                 dstPtr[0] = (srcPtr[2] * 255UL) / 65535UL;  // Red
0328 
0329                 srcPtr   += 4;
0330                 dstPtr   += 3;
0331             }
0332 
0333             jpeg_write_scanlines(&cinfo, &line, 1);
0334         }
0335     }
0336 
0337     cleanupData->deleteLine();
0338 
0339     // -------------------------------------------------------------------
0340 
0341     jpeg_finish_compress(&cinfo);
0342     jpeg_destroy_compress(&cinfo);
0343     delete cleanupData;
0344 
0345     imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JPG"));
0346 
0347     saveMetadata(filePath);
0348 
0349     return true;
0350 }
0351 
0352 } // namespace DigikamJPEGDImgPlugin