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 : 2006-06-14 0007 * Description : A JPEG-2000 IO file for DImg framework- load operations 0008 * 0009 * SPDX-FileCopyrightText: 2006-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 #include "dimgjpeg2000loader.h" 0016 0017 // Qt includes 0018 0019 #include <QFile> 0020 #include <QByteArray> 0021 #include <QTextStream> 0022 #include <QScopedPointer> 0023 0024 // Local includes 0025 0026 #include "dimg.h" 0027 #include "digikam_debug.h" 0028 #include "digikam_config.h" 0029 #include "dimgloaderobserver.h" 0030 #include "dmetadata.h" 0031 0032 // Jasper includes 0033 0034 #ifndef Q_CC_MSVC 0035 extern "C" 0036 { 0037 #endif 0038 0039 #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) 0040 # pragma clang diagnostic push 0041 # pragma clang diagnostic ignored "-Wshift-negative-value" 0042 #endif 0043 0044 #include <jasper/jasper.h> 0045 #include <jasper/jas_version.h> 0046 0047 #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) 0048 # pragma clang diagnostic pop 0049 #endif 0050 0051 #ifndef Q_CC_MSVC 0052 } 0053 #endif 0054 0055 namespace DigikamJPEG2000DImgPlugin 0056 { 0057 0058 bool DImgJPEG2000Loader::load(const QString& filePath, DImgLoaderObserver* const observer) 0059 { 0060 readMetadata(filePath); 0061 0062 #ifdef Q_OS_WIN 0063 0064 FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"rb"); 0065 0066 #else 0067 0068 FILE* const file = fopen(filePath.toUtf8().constData(), "rb"); 0069 0070 #endif 0071 0072 if (!file) 0073 { 0074 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 file"; 0075 loadingFailed(); 0076 0077 return false; 0078 } 0079 0080 imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); 0081 0082 #if defined JAS_VERSION_MAJOR && JAS_VERSION_MAJOR >= 3 0083 0084 // NOTE: nothing to do. 0085 0086 #else 0087 0088 QScopedPointer<DMetadata> metadata(new DMetadata(filePath)); 0089 QSize size = metadata->getItemDimensions(); 0090 QString decoderOptions = QLatin1String("max_samples=100000000"); 0091 0092 if (size.isValid()) 0093 { 0094 imageWidth() = size.width(); 0095 imageHeight() = size.height(); 0096 decoderOptions = QString::fromLatin1("max_samples=%1").arg(size.width() * size.height() * 4); 0097 qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JP2 image size:" << size; 0098 } 0099 0100 if (!(m_loadFlags & LoadImageData) && !(m_loadFlags & LoadICCData)) 0101 { 0102 // libjasper will load the full image in memory already when calling jas_image_decode. 0103 // This is bad when scanning. See bugs 215458 and 195583. 0104 // Exiv2 is used to extract this info. 0105 0106 fclose(file); 0107 0108 return true; 0109 } 0110 0111 #endif 0112 0113 // ------------------------------------------------------------------- 0114 // Initialize JPEG 2000 API. 0115 0116 long i = 0; 0117 long x = 0; 0118 long y = 0; 0119 int components[4] = { 0 }; 0120 unsigned int maximum_component_depth = 0; 0121 unsigned int scale[4] = { 0 }; 0122 unsigned int x_step[4] = { 0 }; 0123 unsigned int y_step[4] = { 0 }; 0124 unsigned long number_components = 0; 0125 jas_image_t* jp2_image = nullptr; 0126 jas_stream_t* jp2_stream = nullptr; 0127 jas_matrix_t* pixels[4] = { nullptr }; 0128 0129 if (initJasper() != 0) 0130 { 0131 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; 0132 loadingFailed(); 0133 fclose(file); 0134 0135 return false; 0136 } 0137 0138 jp2_stream = jas_stream_freopen(filePath.toUtf8().constData(), "rb", file); 0139 0140 if (jp2_stream == nullptr) 0141 { 0142 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; 0143 fclose(file); 0144 0145 cleanupJasper(); 0146 loadingFailed(); 0147 0148 return false; 0149 } 0150 0151 int fmt = jas_image_strtofmt(QByteArray("jp2").data()); 0152 0153 #if defined JAS_VERSION_MAJOR && JAS_VERSION_MAJOR >= 3 0154 0155 jp2_image = jas_image_decode(jp2_stream, fmt, nullptr); 0156 0157 #else 0158 0159 // See bug 447240 and UPSTREAM https://github.com/jasper-software/jasper/issues/315#issuecomment-1007872809 0160 0161 qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "jas_image_decode decoder options string:" << decoderOptions; 0162 0163 jp2_image = jas_image_decode(jp2_stream, fmt, decoderOptions.toLatin1().data()); 0164 0165 #endif 0166 0167 if (jp2_image == nullptr) 0168 { 0169 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to decode JPEG2000 image"; 0170 jas_stream_close(jp2_stream); 0171 0172 cleanupJasper(); 0173 loadingFailed(); 0174 0175 return false; 0176 } 0177 0178 jas_stream_close(jp2_stream); 0179 0180 // some pseudo-progress 0181 0182 if (observer) 0183 { 0184 observer->progressInfo(0.1F); 0185 } 0186 0187 // ------------------------------------------------------------------- 0188 // Check color space. 0189 0190 int colorModel = DImg::COLORMODELUNKNOWN; 0191 0192 switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) 0193 { 0194 case JAS_CLRSPC_FAM_RGB: 0195 { 0196 components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); 0197 components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); 0198 components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); 0199 0200 if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) 0201 { 0202 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Missing Image Channel"; 0203 jas_image_destroy(jp2_image); 0204 0205 cleanupJasper(); 0206 loadingFailed(); 0207 0208 return false; 0209 } 0210 0211 number_components = 3; 0212 components[3] = jas_image_getcmptbytype(jp2_image, 3); 0213 0214 if (components[3] > 0) 0215 { 0216 m_hasAlpha = true; 0217 ++number_components; 0218 } 0219 0220 colorModel = DImg::RGB; 0221 break; 0222 } 0223 0224 case JAS_CLRSPC_FAM_GRAY: 0225 { 0226 components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); 0227 0228 // cppcheck-suppress knownConditionTrueFalse 0229 if (components[0] < 0) 0230 { 0231 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; 0232 jas_image_destroy(jp2_image); 0233 0234 cleanupJasper(); 0235 loadingFailed(); 0236 0237 return false; 0238 } 0239 0240 number_components = 1; 0241 colorModel = DImg::GRAYSCALE; 0242 break; 0243 } 0244 0245 case JAS_CLRSPC_FAM_YCBCR: 0246 { 0247 components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); 0248 components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); 0249 components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); 0250 0251 // cppcheck-suppress knownConditionTrueFalse 0252 if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) 0253 { 0254 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; 0255 jas_image_destroy(jp2_image); 0256 0257 cleanupJasper(); 0258 loadingFailed(); 0259 0260 return false; 0261 } 0262 0263 number_components = 3; 0264 components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); 0265 0266 if (components[3] > 0) 0267 { 0268 m_hasAlpha = true; 0269 ++number_components; 0270 } 0271 0272 // FIXME: image->colorspace = YCbCrColorspace; 0273 0274 colorModel = DImg::YCBCR; 0275 break; 0276 } 0277 0278 default: 0279 { 0280 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Colorspace Model Is Not Supported"; 0281 jas_image_destroy(jp2_image); 0282 0283 cleanupJasper(); 0284 loadingFailed(); 0285 0286 return false; 0287 } 0288 } 0289 0290 // ------------------------------------------------------------------- 0291 // Check image geometry. 0292 0293 imageWidth() = jas_image_width(jp2_image); 0294 imageHeight() = jas_image_height(jp2_image); 0295 0296 for (i = 0 ; i < (long)number_components ; ++i) 0297 { 0298 if ((((jas_image_cmptwidth(jp2_image, components[i])* 0299 jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || 0300 (((jas_image_cmptheight(jp2_image, components[i])* 0301 jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || 0302 (jas_image_cmpttlx(jp2_image, components[i]) != 0) || 0303 (jas_image_cmpttly(jp2_image, components[i]) != 0) || 0304 (jas_image_cmptsgnd(jp2_image, components[i]) != false)) 0305 { 0306 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported"; 0307 jas_image_destroy(jp2_image); 0308 0309 cleanupJasper(); 0310 loadingFailed(); 0311 0312 return false; 0313 } 0314 0315 x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); 0316 y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); 0317 } 0318 0319 // ------------------------------------------------------------------- 0320 // Get image format. 0321 0322 maximum_component_depth = 0; 0323 0324 for (i = 0 ; i < (long)number_components ; ++i) 0325 { 0326 maximum_component_depth = qMax((long)jas_image_cmptprec(jp2_image, components[i]), 0327 (long)maximum_component_depth); 0328 0329 pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth()) / x_step[i]); 0330 0331 if (!pixels[i]) 0332 { 0333 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; 0334 jas_image_destroy(jp2_image); 0335 0336 cleanupJasper(); 0337 loadingFailed(); 0338 0339 return false; 0340 } 0341 } 0342 0343 #if defined JAS_VERSION_MAJOR && JAS_VERSION_MAJOR >= 3 0344 0345 if (!(m_loadFlags & LoadImageData) && !(m_loadFlags & LoadICCData)) 0346 { 0347 imageSetAttribute(QLatin1String("originalColorModel"), colorModel); 0348 imageSetAttribute(QLatin1String("originalBitDepth"), maximum_component_depth); 0349 imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); 0350 0351 jas_image_destroy(jp2_image); 0352 cleanupJasper(); 0353 0354 return true; 0355 } 0356 0357 #endif 0358 0359 if (maximum_component_depth > 8) 0360 { 0361 m_sixteenBit = true; 0362 } 0363 0364 for (i = 0 ; i < (long)number_components ; ++i) 0365 { 0366 scale[i] = 1; 0367 int prec = jas_image_cmptprec(jp2_image, components[i]); 0368 0369 if (m_sixteenBit && prec < 16) 0370 { 0371 scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); 0372 } 0373 } 0374 0375 // ------------------------------------------------------------------- 0376 // Get image data. 0377 0378 QScopedArrayPointer<uchar> data; 0379 0380 if (m_loadFlags & LoadImageData) 0381 { 0382 if (m_sixteenBit) // 16 bits image. 0383 { 0384 data.reset(new_failureTolerant(imageWidth(), imageHeight(), 8)); 0385 } 0386 else 0387 { 0388 data.reset(new_failureTolerant(imageWidth(), imageHeight(), 4)); 0389 } 0390 0391 if (!data) 0392 { 0393 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; 0394 jas_image_destroy(jp2_image); 0395 0396 for (i = 0 ; i < (long)number_components ; ++i) 0397 { 0398 jas_matrix_destroy(pixels[i]); 0399 } 0400 0401 cleanupJasper(); 0402 loadingFailed(); 0403 0404 return false; 0405 } 0406 0407 uint checkPoint = 0; 0408 uchar* dst = data.data(); 0409 unsigned short* dst16 = reinterpret_cast<unsigned short*>(data.data()); 0410 0411 for (y = 0 ; y < (long)imageHeight() ; ++y) 0412 { 0413 for (i = 0 ; i < (long)number_components ; ++i) 0414 { 0415 int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, 0416 ((unsigned int) y) / y_step[i], 0417 ((unsigned int) imageWidth()) / x_step[i], 0418 1, pixels[i]); 0419 0420 if (ret != 0) 0421 { 0422 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data"; 0423 jas_image_destroy(jp2_image); 0424 0425 for (i = 0 ; i < (long)number_components ; ++i) 0426 { 0427 jas_matrix_destroy(pixels[i]); 0428 } 0429 0430 cleanupJasper(); 0431 loadingFailed(); 0432 0433 return false; 0434 } 0435 } 0436 0437 switch (number_components) 0438 { 0439 case 1: // Grayscale. 0440 { 0441 if (!m_sixteenBit) // 8 bits image. 0442 { 0443 for (x = 0 ; x < (long)imageWidth() ; ++x) 0444 { 0445 dst[0] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0446 dst[1] = dst[0]; 0447 dst[2] = dst[0]; 0448 dst[3] = 0xFF; 0449 0450 dst += 4; 0451 } 0452 } 0453 else // 16 bits image. 0454 { 0455 for (x = 0 ; x < (long)imageWidth() ; ++x) 0456 { 0457 dst16[0] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0458 dst16[1] = dst16[0]; 0459 dst16[2] = dst16[0]; 0460 dst16[3] = 0xFFFF; 0461 0462 dst16 += 4; 0463 } 0464 } 0465 0466 break; 0467 } 0468 0469 case 3: // RGB. 0470 { 0471 if (!m_sixteenBit) // 8 bits image. 0472 { 0473 for (x = 0 ; x < (long)imageWidth() ; ++x) 0474 { 0475 // Blue 0476 dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); 0477 // Green 0478 dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); 0479 // Red 0480 dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0481 // Alpha 0482 dst[3] = 0xFF; 0483 0484 dst += 4; 0485 } 0486 } 0487 else // 16 bits image. 0488 { 0489 for (x = 0 ; x < (long)imageWidth() ; ++x) 0490 { 0491 // Blue 0492 dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); 0493 // Green 0494 dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); 0495 // Red 0496 dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0497 // Alpha 0498 dst16[3] = 0xFFFF; 0499 0500 dst16 += 4; 0501 } 0502 } 0503 0504 break; 0505 } 0506 0507 case 4: // RGBA. 0508 { 0509 if (!m_sixteenBit) // 8 bits image. 0510 { 0511 for (x = 0 ; x < (long)imageWidth() ; ++x) 0512 { 0513 // Blue 0514 dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); 0515 // Green 0516 dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); 0517 // Red 0518 dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0519 // Alpha 0520 dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); 0521 0522 dst += 4; 0523 } 0524 } 0525 else // 16 bits image. 0526 { 0527 for (x = 0 ; x < (long)imageWidth() ; ++x) 0528 { 0529 // Blue 0530 dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); 0531 // Green 0532 dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); 0533 // Red 0534 dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); 0535 // Alpha 0536 dst16[3] = (unsigned short)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); 0537 0538 dst16 += 4; 0539 } 0540 } 0541 0542 break; 0543 } 0544 } 0545 0546 // Use 0-10% and 90-100% for pseudo-progress 0547 0548 if (observer && y >= (long)checkPoint) 0549 { 0550 checkPoint += granularity(observer, y, 0.8F); 0551 0552 if (!observer->continueQuery()) 0553 { 0554 jas_image_destroy(jp2_image); 0555 0556 for (i = 0 ; i < (long)number_components ; ++i) 0557 { 0558 jas_matrix_destroy(pixels[i]); 0559 } 0560 0561 cleanupJasper(); 0562 loadingFailed(); 0563 0564 return false; 0565 } 0566 0567 observer->progressInfo(0.1F + (0.8F * (((float)y) / ((float)imageHeight())))); 0568 } 0569 } 0570 } 0571 0572 // ------------------------------------------------------------------- 0573 // Get ICC color profile. 0574 0575 if (m_loadFlags & LoadICCData) 0576 { 0577 jas_iccprof_t* icc_profile = nullptr; 0578 jas_stream_t* icc_stream = nullptr; 0579 jas_cmprof_t* cm_profile = nullptr; 0580 0581 // To prevent cppcheck warnings. 0582 (void)icc_profile; 0583 (void)icc_stream; 0584 (void)cm_profile; 0585 0586 cm_profile = jas_image_cmprof(jp2_image); 0587 0588 if (cm_profile != nullptr) 0589 { 0590 icc_profile = jas_iccprof_createfromcmprof(cm_profile); 0591 } 0592 0593 if (icc_profile != nullptr) 0594 { 0595 icc_stream = jas_stream_memopen(nullptr, 0); 0596 0597 if (icc_stream != nullptr) 0598 { 0599 if (jas_iccprof_save(icc_profile, icc_stream) == 0) 0600 { 0601 if (jas_stream_flush(icc_stream) == 0) 0602 { 0603 jas_stream_memobj_t* const blob = (jas_stream_memobj_t*) icc_stream->obj_; 0604 QByteArray profile_rawdata; 0605 profile_rawdata.resize(blob->len_); 0606 memcpy(profile_rawdata.data(), blob->buf_, blob->len_); 0607 imageSetIccProfile(IccProfile(profile_rawdata)); 0608 jas_stream_close(icc_stream); 0609 } 0610 } 0611 } 0612 } 0613 else 0614 { 0615 // If ICC profile is null, check Exif metadata. 0616 0617 checkExifWorkingColorSpace(); 0618 } 0619 } 0620 0621 if (observer) 0622 { 0623 observer->progressInfo(1.0F); 0624 } 0625 0626 imageData() = data.take(); 0627 imageSetAttribute(QLatin1String("originalColorModel"), colorModel); 0628 imageSetAttribute(QLatin1String("originalBitDepth"), maximum_component_depth); 0629 imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); 0630 0631 jas_image_destroy(jp2_image); 0632 0633 for (i = 0 ; i < (long)number_components ; ++i) 0634 { 0635 jas_matrix_destroy(pixels[i]); 0636 } 0637 0638 cleanupJasper(); 0639 0640 return true; 0641 } 0642 0643 } // namespace DigikamJPEG2000DImgPlugin