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