File indexing completed on 2025-01-19 03:51:01

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 - save 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 
0023 // Local includes
0024 
0025 #include "dimg.h"
0026 #include "digikam_debug.h"
0027 #include "digikam_config.h"
0028 #include "dimgloaderobserver.h"
0029 #include "dmetadata.h"
0030 
0031 // Jasper includes
0032 
0033 #ifndef Q_CC_MSVC
0034 extern "C"
0035 {
0036 #endif
0037 
0038 #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG)
0039 #   pragma clang diagnostic push
0040 #   pragma clang diagnostic ignored "-Wshift-negative-value"
0041 #endif
0042 
0043 #include <jasper/jasper.h>
0044 #include <jasper/jas_version.h>
0045 
0046 #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG)
0047 #   pragma clang diagnostic pop
0048 #endif
0049 
0050 #ifndef Q_CC_MSVC
0051 }
0052 #endif
0053 
0054 namespace DigikamJPEG2000DImgPlugin
0055 {
0056 
0057 bool DImgJPEG2000Loader::save(const QString& filePath, DImgLoaderObserver* const observer)
0058 {
0059 
0060 #ifdef Q_OS_WIN
0061 
0062     FILE* const file = _wfopen((const wchar_t*)filePath.utf16(), L"wb");
0063 
0064 #else
0065 
0066     FILE* const file = fopen(filePath.toUtf8().constData(), "wb");
0067 
0068 #endif
0069 
0070     if (!file)
0071     {
0072         qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 file";
0073 
0074         return false;
0075     }
0076 
0077     // -------------------------------------------------------------------
0078     // Initialize JPEG 2000 API.
0079 
0080     long                 i                 = 0;
0081     long                 x                 = 0;
0082     long                 y                 = 0;
0083     unsigned long        number_components = 0;
0084     jas_image_t*         jp2_image         = nullptr;
0085     jas_stream_t*        jp2_stream        = nullptr;
0086     jas_matrix_t*        pixels[4]         = { nullptr };
0087     jas_image_cmptparm_t component_info[4];
0088 
0089     if (initJasper() != 0)
0090     {
0091         qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder";
0092         fclose(file);
0093 
0094         return false;
0095     }
0096 
0097     jp2_stream = jas_stream_freopen(filePath.toUtf8().constData(), "wb", file);
0098 
0099     if (jp2_stream == nullptr)
0100     {
0101         qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream";
0102         fclose(file);
0103 
0104         cleanupJasper();
0105 
0106         return false;
0107     }
0108 
0109     number_components = imageHasAlpha() ? 4 : 3;
0110 
0111     for (i = 0 ; i < (long)number_components ; ++i)
0112     {
0113         component_info[i].tlx    = 0;
0114         component_info[i].tly    = 0;
0115         component_info[i].hstep  = 1;
0116         component_info[i].vstep  = 1;
0117         component_info[i].width  = imageWidth();
0118         component_info[i].height = imageHeight();
0119         component_info[i].prec   = imageBitsDepth();
0120         component_info[i].sgnd   = false;
0121     }
0122 
0123     jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN);
0124 
0125     if (jp2_image == nullptr)
0126     {
0127         qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to create JPEG2000 image";
0128         jas_stream_close(jp2_stream);
0129 
0130         cleanupJasper();
0131 
0132         return false;
0133     }
0134 
0135     if (observer)
0136     {
0137         observer->progressInfo(0.1F);
0138     }
0139 
0140     // -------------------------------------------------------------------
0141     // Check color space.
0142 
0143     if (number_components >= 3)     // RGB & RGBA
0144     {
0145         // Alpha Channel
0146 
0147         if (number_components == 4)
0148         {
0149             jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY);
0150         }
0151 
0152         jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB);
0153         jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R));
0154         jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G));
0155         jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B));
0156     }
0157 
0158     // -------------------------------------------------------------------
0159     // Set ICC color profile.
0160 
0161     // FIXME : doesn't work yet!
0162 
0163     QByteArray profile_rawdata = m_image->getIccProfile().data();
0164 
0165     if (!profile_rawdata.isEmpty())
0166     {
0167         jas_iccprof_t* const icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size());
0168 
0169         if (icc_profile)
0170         {
0171             jas_cmprof_t* const cm_profile = jas_cmprof_createfromiccprof(icc_profile);
0172 
0173             if (cm_profile)
0174             {
0175                 jas_image_setcmprof(jp2_image, cm_profile);
0176                 //enable when it works: purgeExifWorkingColorSpace();
0177             }
0178         }
0179     }
0180 
0181     // workaround:
0182 
0183     storeColorProfileInMetadata();
0184 
0185     // -------------------------------------------------------------------
0186     // Convert to JPEG 2000 pixels.
0187 
0188     for (i = 0 ; i < (long)number_components ; ++i)
0189     {
0190         pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth());
0191 
0192         if (pixels[i] == nullptr)
0193         {
0194             for (x = 0 ; x < i ; ++x)
0195             {
0196                 jas_matrix_destroy(pixels[x]);
0197             }
0198 
0199             qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data : Memory Allocation Failed";
0200             jas_image_destroy(jp2_image);
0201 
0202             cleanupJasper();
0203 
0204             return false;
0205         }
0206     }
0207 
0208     unsigned char* data  = imageData();
0209     unsigned char* pixel = nullptr;
0210     unsigned short r     = 0;
0211     unsigned short g     = 0;
0212     unsigned short b     = 0;
0213     unsigned short a     = 0;
0214     uint checkpoint      = 0;
0215 
0216     for (y = 0 ; y < (long)imageHeight() ; ++y)
0217     {
0218         if (observer && y == (long)checkpoint)
0219         {
0220             checkpoint += granularity(observer, imageHeight(), 0.8F);
0221 
0222             if (!observer->continueQuery())
0223             {
0224                 jas_image_destroy(jp2_image);
0225 
0226                 for (i = 0 ; i < (long)number_components ; ++i)
0227                 {
0228                     jas_matrix_destroy(pixels[i]);
0229                 }
0230 
0231                 cleanupJasper();
0232 
0233                 return false;
0234             }
0235 
0236             observer->progressInfo(0.1F + (0.8F * (((float)y) / ((float)imageHeight()))));
0237         }
0238 
0239         for (x = 0 ; x < (long)imageWidth() ; ++x)
0240         {
0241             pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()];
0242 
0243             if (imageSixteenBit())          // 16 bits image.
0244             {
0245                 b = (unsigned short)(pixel[0] + 256 * pixel[1]);
0246                 g = (unsigned short)(pixel[2] + 256 * pixel[3]);
0247                 r = (unsigned short)(pixel[4] + 256 * pixel[5]);
0248 
0249                 if (imageHasAlpha())
0250                 {
0251                     a = (unsigned short)(pixel[6] + 256 * pixel[7]);
0252                 }
0253             }
0254             else                            // 8 bits image.
0255             {
0256                 b = (unsigned short)pixel[0];
0257                 g = (unsigned short)pixel[1];
0258                 r = (unsigned short)pixel[2];
0259 
0260                 if (imageHasAlpha())
0261                 {
0262                     a = (unsigned short)(pixel[3]);
0263                 }
0264             }
0265 
0266             jas_matrix_setv(pixels[0], x, r);
0267             jas_matrix_setv(pixels[1], x, g);
0268             jas_matrix_setv(pixels[2], x, b);
0269 
0270             if (number_components > 3)
0271             {
0272                 jas_matrix_setv(pixels[3], x, a);
0273             }
0274         }
0275 
0276         for (i = 0 ; i < (long)number_components ; ++i)
0277         {
0278             int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y,
0279                                           (unsigned int)imageWidth(), 1, pixels[i]);
0280 
0281             if (ret != 0)
0282             {
0283                 qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data";
0284 
0285                 jas_image_destroy(jp2_image);
0286 
0287                 for (i = 0 ; i < (long)number_components ; ++i)
0288                 {
0289                     jas_matrix_destroy(pixels[i]);
0290                 }
0291 
0292                 cleanupJasper();
0293 
0294                 return false;
0295             }
0296         }
0297     }
0298 
0299     QVariant qualityAttr = imageGetAttribute(QLatin1String("quality"));
0300     int quality          = qualityAttr.isValid() ? qualityAttr.toInt() : 90;
0301 
0302     if (quality < 0)
0303     {
0304         quality = 90;
0305     }
0306 
0307     if (quality > 100)
0308     {
0309         quality = 100;
0310     }
0311 
0312     // optstr:
0313     // - rate=#B => the resulting file size is about # bytes
0314     // - rate=0.0 .. 1.0 => the resulting file size is about the factor times
0315     //                      the uncompressed size
0316     // use sprintf for locale-aware string
0317 
0318     char rateBuffer[16];
0319     sprintf(rateBuffer, "rate=%.2g", (quality / 100.0));
0320 
0321     qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 quality: " << quality;
0322     qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 "          << rateBuffer;
0323 
0324     int fmt = jas_image_strtofmt(QByteArray("jp2").data());
0325     int ret = jas_image_encode(jp2_image, jp2_stream, fmt, rateBuffer);
0326 
0327     if (ret != 0)
0328     {
0329         qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to encode JPEG2000 image";
0330 
0331         jas_image_destroy(jp2_image);
0332         jas_stream_close(jp2_stream);
0333 
0334         for (i = 0 ; i < (long)number_components ; ++i)
0335         {
0336             jas_matrix_destroy(pixels[i]);
0337         }
0338 
0339         cleanupJasper();
0340 
0341         return false;
0342     }
0343 
0344     if (observer)
0345     {
0346         observer->progressInfo(1.0F);
0347     }
0348 
0349     jas_image_destroy(jp2_image);
0350     jas_stream_close(jp2_stream);
0351 
0352     for (i = 0 ; i < (long)number_components ; ++i)
0353     {
0354         jas_matrix_destroy(pixels[i]);
0355     }
0356 
0357     cleanupJasper();
0358 
0359     imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JP2"));
0360     saveMetadata(filePath);
0361 
0362     return true;
0363 }
0364 
0365 } // namespace DigikamJPEG2000DImgPlugin