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