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