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