File indexing completed on 2024-06-23 04:27:17
0001 /* 0002 * SPDX-FileCopyrightText: 2021 the JPEG XL Project Authors 0003 * SPDX-License-Identifier: BSD-3-Clause 0004 * 0005 * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me> 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "JPEGXLImport.h" 0010 0011 #include <KisGlobalResourcesInterface.h> 0012 0013 #include <jxl/decode_cxx.h> 0014 #include <jxl/resizable_parallel_runner_cxx.h> 0015 #include <jxl/types.h> 0016 #include <kpluginfactory.h> 0017 0018 #include <QBuffer> 0019 #include <algorithm> 0020 #include <array> 0021 #include <cstring> 0022 #include <map> 0023 0024 #include <KisDocument.h> 0025 #include <KisImportExportErrorCode.h> 0026 #include <KoColorModelStandardIds.h> 0027 #include <KoColorProfile.h> 0028 #include <KoColorSpaceRegistry.h> 0029 #include <KoColorTransferFunctions.h> 0030 #include <KoConfig.h> 0031 #include <dialogs/kis_dlg_hlg_import.h> 0032 #include <filter/kis_filter.h> 0033 #include <filter/kis_filter_configuration.h> 0034 #include <filter/kis_filter_registry.h> 0035 #include <kis_assert.h> 0036 #include <kis_debug.h> 0037 #include <kis_group_layer.h> 0038 #include <kis_image_animation_interface.h> 0039 #include <kis_iterator_ng.h> 0040 #include <kis_meta_data_backend_registry.h> 0041 #include <kis_paint_layer.h> 0042 #include <kis_raster_keyframe_channel.h> 0043 0044 K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_jxl_import.json", registerPlugin<JPEGXLImport>();) 0045 0046 static constexpr std::array<char, 4> exifTag = {'e', 'x', 'i', 'f'}; 0047 static constexpr std::array<char, 4> xmpTag = {'x', 'm', 'l', ' '}; 0048 0049 class Q_DECL_HIDDEN JPEGXLImportData 0050 { 0051 public: 0052 JxlBasicInfo m_info{}; 0053 JxlExtraChannelInfo m_extra{}; 0054 JxlPixelFormat m_pixelFormat{}; 0055 JxlPixelFormat m_pixelFormat_target{}; 0056 JxlFrameHeader m_header{}; 0057 std::vector<quint8> m_rawData{}; 0058 KisPaintDeviceSP m_currentFrame{nullptr}; 0059 uint32_t cmykChannelID = 0; 0060 int m_nextFrameTime{0}; 0061 int m_durationFrameInTicks{0}; 0062 KoID m_colorID; 0063 KoID m_colorID_target; 0064 KoID m_depthID; 0065 KoID m_depthID_target; 0066 KoColorConversionTransformation::Intent m_intent; 0067 bool isCMYK = false; 0068 bool applyOOTF = true; 0069 float displayGamma = 1.2f; 0070 float displayNits = 1000.0; 0071 LinearizePolicy linearizePolicy = LinearizePolicy::KeepTheSame; 0072 const KoColorSpace *cs = nullptr; 0073 const KoColorSpace *cs_target = nullptr; 0074 const KoColorSpace *cs_intermediate = nullptr; 0075 std::vector<quint8> kPlane; 0076 QVector<qreal> lCoef; 0077 }; 0078 0079 template<LinearizePolicy policy> 0080 inline float linearizeValueAsNeeded(float value) 0081 { 0082 if (policy == LinearizePolicy::LinearFromPQ) { 0083 return removeSmpte2048Curve(value); 0084 } else if (policy == LinearizePolicy::LinearFromHLG) { 0085 return removeHLGCurve(value); 0086 } else if (policy == LinearizePolicy::LinearFromSMPTE428) { 0087 return removeSMPTE_ST_428Curve(value); 0088 } 0089 return value; 0090 } 0091 0092 template<LinearizePolicy policy, typename T, typename std::enable_if_t<std::numeric_limits<T>::is_integer, int> = 1> 0093 inline float value(const T *src, size_t ch) 0094 { 0095 float v = float(src[ch]) / float(std::numeric_limits<T>::max()); 0096 0097 return linearizeValueAsNeeded<policy>(v); 0098 } 0099 0100 template<LinearizePolicy policy, typename T, typename std::enable_if_t<!std::numeric_limits<T>::is_integer, int> = 1> 0101 inline float value(const T *src, size_t ch) 0102 { 0103 float v = float(src[ch]); 0104 0105 return linearizeValueAsNeeded<policy>(v); 0106 } 0107 0108 template<typename channelsType, bool swap, LinearizePolicy policy, bool applyOOTF> 0109 inline void imageOutCallback(JPEGXLImportData &d) 0110 { 0111 const uint32_t xPos = d.m_header.layer_info.crop_x0; 0112 const uint32_t yPos = d.m_header.layer_info.crop_y0; 0113 const uint32_t width = d.m_header.layer_info.xsize; 0114 const uint32_t height = d.m_header.layer_info.ysize; 0115 KisHLineIteratorSP it = d.m_currentFrame->createHLineIteratorNG(xPos, yPos, width); 0116 0117 const auto *src = reinterpret_cast<const channelsType *>(d.m_rawData.data()); 0118 const uint32_t channels = d.m_pixelFormat.num_channels; 0119 0120 if (policy != LinearizePolicy::KeepTheSame) { 0121 const KoColorSpace *cs = d.cs; 0122 const double *lCoef = d.lCoef.constData(); 0123 QVector<float> pixelValues(static_cast<int>(cs->channelCount())); 0124 float *tmp = pixelValues.data(); 0125 const quint32 alphaPos = cs->alphaPos(); 0126 0127 for (size_t j = 0; j < height; j++) { 0128 for (size_t i = 0; i < width; i++) { 0129 for (size_t i = 0; i < channels; i++) { 0130 tmp[i] = 1.0; 0131 } 0132 0133 for (size_t ch = 0; ch < channels; ch++) { 0134 if (ch == alphaPos) { 0135 tmp[ch] = value<LinearizePolicy::KeepTheSame, channelsType>(src, ch); 0136 } else { 0137 tmp[ch] = value<policy, channelsType>(src, ch); 0138 } 0139 } 0140 0141 if (swap) { 0142 std::swap(tmp[0], tmp[2]); 0143 } 0144 0145 if (policy == LinearizePolicy::LinearFromHLG && applyOOTF) { 0146 applyHLGOOTF(tmp, lCoef, d.displayGamma, d.displayNits); 0147 } 0148 0149 cs->fromNormalisedChannelsValue(it->rawData(), pixelValues); 0150 0151 src += d.m_pixelFormat.num_channels; 0152 0153 it->nextPixel(); 0154 } 0155 it->nextRow(); 0156 } 0157 } else { 0158 for (size_t j = 0; j < height; j++) { 0159 for (size_t i = 0; i < width; i++) { 0160 auto *dst = reinterpret_cast<channelsType *>(it->rawData()); 0161 0162 std::memcpy(dst, src, channels * sizeof(channelsType)); 0163 0164 if (swap) { 0165 std::swap(dst[0], dst[2]); 0166 } else if (d.isCMYK && d.m_info.uses_original_profile) { 0167 // Swap alpha and key channel for CMYK 0168 std::swap(dst[3], dst[4]); 0169 } 0170 0171 src += d.m_pixelFormat.num_channels; 0172 0173 it->nextPixel(); 0174 } 0175 it->nextRow(); 0176 } 0177 } 0178 } 0179 0180 template<typename channelsType, bool swap, LinearizePolicy policy> 0181 inline void generateCallbackWithPolicy(JPEGXLImportData &d) 0182 { 0183 if (d.applyOOTF) { 0184 imageOutCallback<channelsType, swap, policy, true>(d); 0185 } else { 0186 imageOutCallback<channelsType, swap, policy, false>(d); 0187 } 0188 } 0189 0190 template<typename channelsType, bool swap> 0191 inline void generateCallbackWithSwap(JPEGXLImportData &d) 0192 { 0193 switch (d.linearizePolicy) { 0194 case LinearizePolicy::LinearFromPQ: 0195 generateCallbackWithPolicy<channelsType, swap, LinearizePolicy::LinearFromPQ>(d); 0196 break; 0197 case LinearizePolicy::LinearFromHLG: 0198 generateCallbackWithPolicy<channelsType, swap, LinearizePolicy::LinearFromHLG>(d); 0199 break; 0200 case LinearizePolicy::LinearFromSMPTE428: 0201 generateCallbackWithPolicy<channelsType, swap, LinearizePolicy::LinearFromSMPTE428>(d); 0202 break; 0203 case LinearizePolicy::KeepTheSame: 0204 default: 0205 generateCallbackWithPolicy<channelsType, swap, LinearizePolicy::KeepTheSame>(d); 0206 break; 0207 }; 0208 } 0209 0210 template<typename channelsType> 0211 inline void generateCallbackWithType(JPEGXLImportData &d) 0212 { 0213 if (d.m_colorID == RGBAColorModelID 0214 && (d.m_depthID == Integer8BitsColorDepthID || d.m_depthID == Integer16BitsColorDepthID) 0215 && d.linearizePolicy == LinearizePolicy::KeepTheSame) { 0216 generateCallbackWithSwap<channelsType, true>(d); 0217 } else { 0218 generateCallbackWithSwap<channelsType, false>(d); 0219 } 0220 } 0221 0222 inline void generateCallback(JPEGXLImportData &d) 0223 { 0224 switch (d.m_pixelFormat.data_type) { 0225 case JXL_TYPE_FLOAT: 0226 return generateCallbackWithType<float>(d); 0227 case JXL_TYPE_UINT8: 0228 return generateCallbackWithType<uint8_t>(d); 0229 case JXL_TYPE_UINT16: 0230 return generateCallbackWithType<uint16_t>(d); 0231 #ifdef HAVE_OPENEXR 0232 case JXL_TYPE_FLOAT16: 0233 return generateCallbackWithType<half>(d); 0234 break; 0235 #endif 0236 default: 0237 KIS_ASSERT_X(false, "JPEGXL::generateCallback", "Unknown image format!"); 0238 } 0239 } 0240 0241 JPEGXLImport::JPEGXLImport(QObject *parent, const QVariantList &) 0242 : KisImportExportFilter(parent) 0243 { 0244 } 0245 0246 KisImportExportErrorCode 0247 JPEGXLImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) 0248 { 0249 if (!io->isReadable()) { 0250 errFile << "Cannot read image contents"; 0251 return ImportExportCodes::NoAccessToRead; 0252 } 0253 0254 JPEGXLImportData d{}; 0255 0256 // Multi-threaded parallel runner. 0257 auto runner = JxlResizableParallelRunnerMake(nullptr); 0258 auto dec = JxlDecoderMake(nullptr); 0259 0260 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(runner && dec, ImportExportCodes::InternalError); 0261 0262 // Set coalescing FALSE to enable layered JXL 0263 if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), JXL_FALSE)) { 0264 errFile << "JxlDecoderSetCoalescing failed"; 0265 return ImportExportCodes::InternalError; 0266 } 0267 bool decSetCoalescing = false; 0268 0269 if (JXL_DEC_SUCCESS 0270 != JxlDecoderSubscribeEvents(dec.get(), 0271 JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE | JXL_DEC_BOX 0272 | JXL_DEC_FRAME)) { 0273 errFile << "JxlDecoderSubscribeEvents failed"; 0274 return ImportExportCodes::InternalError; 0275 } 0276 0277 if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { 0278 errFile << "JxlDecoderSetParallelRunner failed"; 0279 return ImportExportCodes::InternalError; 0280 } 0281 0282 const auto data = io->readAll(); 0283 0284 const auto validation = 0285 JxlSignatureCheck(reinterpret_cast<const uint8_t *>(data.constData()), static_cast<size_t>(data.size())); 0286 0287 switch (validation) { 0288 case JXL_SIG_NOT_ENOUGH_BYTES: 0289 errFile << "Failed magic byte validation, not enough data"; 0290 return ImportExportCodes::FileFormatIncorrect; 0291 case JXL_SIG_INVALID: 0292 errFile << "Failed magic byte validation, incorrect format"; 0293 return ImportExportCodes::FileFormatIncorrect; 0294 default: 0295 break; 0296 } 0297 0298 if (JXL_DEC_SUCCESS 0299 != JxlDecoderSetInput(dec.get(), 0300 reinterpret_cast<const uint8_t *>(data.constData()), 0301 static_cast<size_t>(data.size()))) { 0302 errFile << "JxlDecoderSetInput failed"; 0303 return ImportExportCodes::InternalError; 0304 }; 0305 JxlDecoderCloseInput(dec.get()); 0306 if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) { 0307 errFile << "JxlDecoderSetDecompressBoxes failed"; 0308 return ImportExportCodes::InternalError; 0309 }; 0310 0311 KisImageSP image{nullptr}; 0312 KisLayerSP layer{nullptr}; 0313 std::multimap<QByteArray, QByteArray> metadataBoxes; 0314 std::vector<KisLayerSP> additionalLayers; 0315 bool bgLayerSet = false; 0316 bool needColorTransform = false; 0317 bool needIntermediateTransform = false; 0318 QByteArray boxType(5, 0x0); 0319 QByteArray box(16384, 0x0); 0320 auto boxSize = box.size(); 0321 0322 // List of blend mode that we can currently support 0323 static constexpr std::array<JxlBlendMode, 3> supportedBlendMode = {JXL_BLEND_REPLACE, JXL_BLEND_BLEND, JXL_BLEND_MULADD}; 0324 0325 // Internal function to rewind decoder and enable coalescing 0326 auto rewindDecoderWithCoalesce = [&]() { 0327 // Check to make sure coalescing only called once, 0328 // otherwise it can trigger an infinite loop if we forgot to check decSetCoalescing when decoding below 0329 if (decSetCoalescing) { 0330 return false; 0331 } 0332 JxlDecoderRewind(dec.get()); 0333 if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), JXL_TRUE)) { 0334 errFile << "JxlDecoderSetCoalescing failed"; 0335 return false; 0336 } 0337 if (JXL_DEC_SUCCESS 0338 != JxlDecoderSubscribeEvents(dec.get(), 0339 JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE | JXL_DEC_BOX 0340 | JXL_DEC_FRAME)) { 0341 errFile << "JxlDecoderSubscribeEvents failed"; 0342 return false; 0343 } 0344 if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { 0345 errFile << "JxlDecoderSetParallelRunner failed"; 0346 return false; 0347 } 0348 if (JXL_DEC_SUCCESS 0349 != JxlDecoderSetInput(dec.get(), 0350 reinterpret_cast<const uint8_t *>(data.constData()), 0351 static_cast<size_t>(data.size()))) { 0352 errFile << "JxlDecoderSetInput failed"; 0353 return false; 0354 }; 0355 JxlDecoderCloseInput(dec.get()); 0356 if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) { 0357 errFile << "JxlDecoderSetDecompressBoxes failed"; 0358 return false; 0359 }; 0360 decSetCoalescing = true; 0361 return true; 0362 }; 0363 0364 for (;;) { 0365 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 0366 0367 if (status == JXL_DEC_ERROR) { 0368 errFile << "Decoder error"; 0369 return ImportExportCodes::InternalError; 0370 } else if (status == JXL_DEC_NEED_MORE_INPUT) { 0371 errFile << "Error, already provided all input"; 0372 return ImportExportCodes::InternalError; 0373 } else if (status == JXL_DEC_BASIC_INFO) { 0374 if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &d.m_info)) { 0375 errFile << "JxlDecoderGetBasicInfo failed"; 0376 return ImportExportCodes::ErrorWhileReading; 0377 } 0378 // Coalesce frame on animation import 0379 if (d.m_info.have_animation && !decSetCoalescing) { 0380 if (!rewindDecoderWithCoalesce()) { 0381 return ImportExportCodes::InternalError; 0382 } else { 0383 continue; 0384 } 0385 } 0386 0387 dbgFile << "Extra Channel[s] info:"; 0388 for (uint32_t i = 0; i < d.m_info.num_extra_channels; i++) { 0389 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec.get(), i, &d.m_extra)) { 0390 errFile << "JxlDecoderGetExtraChannelInfo failed"; 0391 break; 0392 } 0393 // Channel name references taken from libjxl repo: 0394 // https://github.com/libjxl/libjxl/blob/v0.8.0/lib/extras/enc/pnm.cc#L262 0395 // With added "JXL-" prefix to indicate that it comes from JXL image. 0396 const QString channelTypeString = [&]() { 0397 switch (d.m_extra.type) { 0398 case JXL_CHANNEL_ALPHA: 0399 return QString("JXL-Alpha"); 0400 case JXL_CHANNEL_DEPTH: 0401 return QString("JXL-Depth"); 0402 case JXL_CHANNEL_SPOT_COLOR: 0403 return QString("JXL-SpotColor"); 0404 case JXL_CHANNEL_SELECTION_MASK: 0405 return QString("JXL-SelectionMask"); 0406 case JXL_CHANNEL_BLACK: 0407 return QString("JXL-Black"); 0408 case JXL_CHANNEL_CFA: 0409 return QString("JXL-CFA"); 0410 case JXL_CHANNEL_THERMAL: 0411 return QString("JXL-Thermal"); 0412 default: 0413 return QString("JXL-UNKNOWN"); 0414 } 0415 }(); 0416 0417 // List all extra channels 0418 dbgFile << "index:" << i << " | type:" << channelTypeString; 0419 if (d.m_extra.type == JXL_CHANNEL_BLACK) { 0420 d.isCMYK = true; 0421 d.cmykChannelID = i; 0422 } 0423 if (d.m_extra.type == JXL_CHANNEL_SPOT_COLOR && !decSetCoalescing) { 0424 warnFile << "Spot color channels unsupported! Rewinding decoder with coalescing enabled"; 0425 document->setWarningMessage(i18nc("JPEG-XL errors", 0426 "Detected JPEG-XL image with spot color channels, " 0427 "importing flattened image.")); 0428 if (!rewindDecoderWithCoalesce()) { 0429 return ImportExportCodes::InternalError; 0430 } else { 0431 continue; 0432 } 0433 } 0434 } 0435 0436 dbgFile << "Info"; 0437 dbgFile << "Size:" << d.m_info.xsize << "x" << d.m_info.ysize; 0438 dbgFile << "Depth:" << d.m_info.bits_per_sample << d.m_info.exponent_bits_per_sample; 0439 dbgFile << "Number of color channels:" << d.m_info.num_color_channels; 0440 dbgFile << "Number of extra channels:" << d.m_info.num_extra_channels; 0441 dbgFile << "Extra channels depth:" << d.m_info.alpha_bits << d.m_info.alpha_exponent_bits; 0442 dbgFile << "Has animation:" << d.m_info.have_animation << "loops:" << d.m_info.animation.num_loops 0443 << "tick:" << d.m_info.animation.tps_numerator << d.m_info.animation.tps_denominator; 0444 dbgFile << "Internal pixel format:" << (d.m_info.uses_original_profile ? "Original" : "XYB"); 0445 JxlResizableParallelRunnerSetThreads( 0446 runner.get(), 0447 JxlResizableParallelRunnerSuggestThreads(d.m_info.xsize, d.m_info.ysize)); 0448 0449 if (d.m_info.exponent_bits_per_sample != 0) { 0450 if (d.m_info.bits_per_sample <= 16) { 0451 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT16; 0452 d.m_depthID = Float16BitsColorDepthID; 0453 } else if (d.m_info.bits_per_sample <= 32) { 0454 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT; 0455 d.m_depthID = Float32BitsColorDepthID; 0456 } else { 0457 errFile << "Unsupported JPEG-XL input depth" << d.m_info.bits_per_sample 0458 << d.m_info.exponent_bits_per_sample; 0459 return ImportExportCodes::FormatFeaturesUnsupported; 0460 } 0461 } else if (d.m_info.bits_per_sample <= 8) { 0462 d.m_pixelFormat.data_type = JXL_TYPE_UINT8; 0463 d.m_depthID = Integer8BitsColorDepthID; 0464 } else if (d.m_info.bits_per_sample <= 16) { 0465 d.m_pixelFormat.data_type = JXL_TYPE_UINT16; 0466 d.m_depthID = Integer16BitsColorDepthID; 0467 } else { 0468 errFile << "Unsupported JPEG-XL input depth" << d.m_info.bits_per_sample 0469 << d.m_info.exponent_bits_per_sample; 0470 return ImportExportCodes::FormatFeaturesUnsupported; 0471 } 0472 0473 if (d.m_info.num_color_channels == 1) { 0474 // Grayscale 0475 d.m_pixelFormat.num_channels = 2; 0476 d.m_colorID = GrayAColorModelID; 0477 } else if (d.m_info.num_color_channels == 3 && !d.isCMYK) { 0478 // RGBA 0479 d.m_pixelFormat.num_channels = 4; 0480 d.m_colorID = RGBAColorModelID; 0481 } else if (d.m_info.num_color_channels == 3 && d.isCMYK) { 0482 // CMYKA 0483 d.m_pixelFormat.num_channels = 4; 0484 d.m_colorID = CMYKAColorModelID; 0485 } else { 0486 warnFile << "Forcing a RGBA conversion, unknown color space"; 0487 d.m_pixelFormat.num_channels = 4; 0488 d.m_colorID = RGBAColorModelID; 0489 } 0490 0491 if (!d.m_info.uses_original_profile) { 0492 d.m_pixelFormat_target.data_type = d.m_pixelFormat.data_type; 0493 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT; 0494 d.m_depthID_target = d.m_depthID; 0495 d.m_colorID_target = d.m_colorID; 0496 d.m_depthID = Float32BitsColorDepthID; 0497 0498 if (d.m_colorID != GrayAColorModelID) { 0499 d.m_colorID = RGBAColorModelID; 0500 } 0501 } 0502 } else if (status == JXL_DEC_COLOR_ENCODING) { 0503 // Determine color space information 0504 const KoColorProfile *profile = nullptr; 0505 const KoColorProfile *profileTarget = nullptr; 0506 const KoColorProfile *profileIntermediate = nullptr; 0507 0508 // Chrome way of decoding JXL implies first scanning 0509 // the CICP encoding for HDR, and only afterwards 0510 // falling back to ICC. 0511 JxlColorEncoding colorEncoding{}; 0512 if (JXL_DEC_SUCCESS 0513 == JxlDecoderGetColorAsEncodedProfile(dec.get(), 0514 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0) 0515 nullptr, 0516 #endif 0517 JXL_COLOR_PROFILE_TARGET_DATA, 0518 &colorEncoding)) { 0519 const TransferCharacteristics transferFunction = [&]() { 0520 switch (colorEncoding.transfer_function) { 0521 case JXL_TRANSFER_FUNCTION_PQ: { 0522 dbgFile << "linearizing from PQ"; 0523 d.linearizePolicy = LinearizePolicy::LinearFromPQ; 0524 return TRC_LINEAR; 0525 } 0526 case JXL_TRANSFER_FUNCTION_HLG: { 0527 dbgFile << "linearizing from HLG"; 0528 if (!document->fileBatchMode()) { 0529 KisDlgHLGImport dlg(d.applyOOTF, d.displayGamma, d.displayNits); 0530 dlg.exec(); 0531 d.applyOOTF = dlg.applyOOTF(); 0532 d.displayGamma = dlg.gamma(); 0533 d.displayNits = dlg.nominalPeakBrightness(); 0534 } 0535 d.linearizePolicy = LinearizePolicy::LinearFromHLG; 0536 return TRC_LINEAR; 0537 } 0538 case JXL_TRANSFER_FUNCTION_DCI: { 0539 dbgFile << "linearizing from SMPTE 428"; 0540 d.linearizePolicy = LinearizePolicy::LinearFromSMPTE428; 0541 return TRC_LINEAR; 0542 } 0543 case JXL_TRANSFER_FUNCTION_709: 0544 return TRC_ITU_R_BT_709_5; 0545 case JXL_TRANSFER_FUNCTION_SRGB: 0546 return TRC_IEC_61966_2_1; 0547 case JXL_TRANSFER_FUNCTION_GAMMA: { 0548 // Using roughly the same logic in KoColorProfile. 0549 const double estGamma = 1.0 / colorEncoding.gamma; 0550 const double error = 0.0001; 0551 // ICC v2 u8Fixed8Number calculation 0552 // Or can be prequantized as 1.80078125, courtesy of Elle Stone 0553 if ((std::fabs(estGamma - 1.8) < error) || (std::fabs(estGamma - (461.0 / 256.0)) < error)) { 0554 return TRC_GAMMA_1_8; 0555 } else if (std::fabs(estGamma - 2.2) < error) { 0556 return TRC_ITU_R_BT_470_6_SYSTEM_M; 0557 } else if (std::fabs(estGamma - (563.0 / 256.0)) < error) { 0558 return TRC_A98; 0559 } else if (std::fabs(estGamma - 2.4) < error) { 0560 return TRC_GAMMA_2_4; 0561 } else if (std::fabs(estGamma - 2.8) < error) { 0562 return TRC_ITU_R_BT_470_6_SYSTEM_B_G; 0563 } else { 0564 warnFile << "Found custom estimated gamma value for JXL color space" << estGamma; 0565 return TRC_UNSPECIFIED; 0566 } 0567 } 0568 case JXL_TRANSFER_FUNCTION_LINEAR: 0569 return TRC_LINEAR; 0570 case JXL_TRANSFER_FUNCTION_UNKNOWN: 0571 default: 0572 warnFile << "Found unknown OETF"; 0573 return TRC_UNSPECIFIED; 0574 } 0575 }(); 0576 0577 const ColorPrimaries colorPrimaries = [&]() { 0578 switch (colorEncoding.primaries) { 0579 case JXL_PRIMARIES_SRGB: 0580 return PRIMARIES_ITU_R_BT_709_5; 0581 case JXL_PRIMARIES_2100: 0582 return PRIMARIES_ITU_R_BT_2020_2_AND_2100_0; 0583 case JXL_PRIMARIES_P3: 0584 return PRIMARIES_SMPTE_RP_431_2; 0585 default: 0586 return PRIMARIES_UNSPECIFIED; 0587 } 0588 }(); 0589 0590 const QVector<double> colorants = [&]() -> QVector<double> { 0591 if (colorEncoding.primaries != JXL_PRIMARIES_CUSTOM) { 0592 return {}; 0593 } else { 0594 return {colorEncoding.white_point_xy[0], 0595 colorEncoding.white_point_xy[1], 0596 colorEncoding.primaries_red_xy[0], 0597 colorEncoding.primaries_red_xy[1], 0598 colorEncoding.primaries_green_xy[0], 0599 colorEncoding.primaries_green_xy[1], 0600 colorEncoding.primaries_blue_xy[0], 0601 colorEncoding.primaries_blue_xy[1]}; 0602 } 0603 }(); 0604 0605 if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_PERCEPTUAL) { 0606 d.m_intent = KoColorConversionTransformation::IntentPerceptual; 0607 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_RELATIVE) { 0608 d.m_intent = KoColorConversionTransformation::IntentRelativeColorimetric; 0609 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_ABSOLUTE) { 0610 d.m_intent = KoColorConversionTransformation::IntentAbsoluteColorimetric; 0611 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_SATURATION) { 0612 d.m_intent = KoColorConversionTransformation::IntentSaturation; 0613 } else { 0614 warnFile << "Cannot determine color rendering intent, set to Perceptual instead"; 0615 d.m_intent = KoColorConversionTransformation::IntentPerceptual; 0616 } 0617 0618 profile = KoColorSpaceRegistry::instance()->profileFor(colorants, colorPrimaries, transferFunction); 0619 0620 dbgFile << "CICP profile data:" << colorants << colorPrimaries << transferFunction; 0621 0622 if (profile) { 0623 dbgFile << "JXL CICP profile found" << profile->name(); 0624 0625 if (d.linearizePolicy != LinearizePolicy::KeepTheSame) { 0626 // Override output format! 0627 d.m_depthID = Float32BitsColorDepthID; 0628 d.m_pixelFormat.data_type = d.m_pixelFormat_target.data_type; 0629 0630 // HDR is a special case because we need to linearize in-house 0631 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile); 0632 } 0633 } 0634 } 0635 0636 if (!d.cs) { 0637 size_t iccSize = 0; 0638 QByteArray iccProfile; 0639 if (JXL_DEC_SUCCESS 0640 != JxlDecoderGetICCProfileSize(dec.get(), 0641 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) 0642 nullptr, 0643 #endif 0644 JXL_COLOR_PROFILE_TARGET_DATA, 0645 &iccSize)) { 0646 errFile << "ICC profile size retrieval failed"; 0647 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile.")); 0648 return ImportExportCodes::ErrorWhileReading; 0649 } 0650 iccProfile.resize(static_cast<int>(iccSize)); 0651 if (JXL_DEC_SUCCESS 0652 != JxlDecoderGetColorAsICCProfile(dec.get(), 0653 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) 0654 nullptr, 0655 #endif 0656 JXL_COLOR_PROFILE_TARGET_DATA, 0657 reinterpret_cast<uint8_t *>(iccProfile.data()), 0658 static_cast<size_t>(iccProfile.size()))) { 0659 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile.")); 0660 return ImportExportCodes::ErrorWhileReading; 0661 } 0662 0663 // Get original profile if XYB is used 0664 size_t iccTargetSize = 0; 0665 QByteArray iccTargetProfile; 0666 if (!d.m_info.uses_original_profile) { 0667 if (JXL_DEC_SUCCESS 0668 != JxlDecoderGetICCProfileSize(dec.get(), 0669 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) 0670 nullptr, 0671 #endif 0672 JXL_COLOR_PROFILE_TARGET_ORIGINAL, 0673 &iccTargetSize)) { 0674 errFile << "ICC profile size retrieval failed"; 0675 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile.")); 0676 return ImportExportCodes::ErrorWhileReading; 0677 } 0678 iccTargetProfile.resize(static_cast<int>(iccTargetSize)); 0679 if (JXL_DEC_SUCCESS 0680 != JxlDecoderGetColorAsICCProfile(dec.get(), 0681 #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0) 0682 nullptr, 0683 #endif 0684 JXL_COLOR_PROFILE_TARGET_ORIGINAL, 0685 reinterpret_cast<uint8_t *>(iccTargetProfile.data()), 0686 static_cast<size_t>(iccTargetProfile.size()))) { 0687 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile.")); 0688 return ImportExportCodes::ErrorWhileReading; 0689 } 0690 } 0691 0692 if (iccTargetSize && (iccProfile != iccTargetProfile)) { 0693 // If the icc target is not 0 and different than target data. 0694 // Meaning that the JXL is in XYB format and needing to convert back to 0695 // the original color profile. 0696 // 0697 // Here we need to provide an intermediate transform space in float to prevent 0698 // gamut clipping to sRGB if the target depth is integer. 0699 dbgFile << "XYB with color transform needed"; 0700 needColorTransform = true; 0701 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID.id(), 0702 d.m_depthID.id(), 0703 iccProfile); 0704 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile); 0705 profileIntermediate = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(), 0706 d.m_depthID.id(), 0707 iccTargetProfile); 0708 d.cs_intermediate = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(), 0709 d.m_depthID.id(), 0710 profileIntermediate); 0711 profileTarget = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(), 0712 d.m_depthID_target.id(), 0713 iccTargetProfile); 0714 d.cs_target = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(), 0715 d.m_depthID_target.id(), 0716 profileTarget); 0717 0718 // No need for intermediate transform on float since it won't get clipped. 0719 if (!(d.m_depthID_target == Float16BitsColorDepthID 0720 || d.m_depthID_target == Float32BitsColorDepthID)) { 0721 needIntermediateTransform = true; 0722 } 0723 } else if (!d.m_info.uses_original_profile) { 0724 // If XYB is used but the profiles are same, skip conversion. 0725 // Also set the color depth target to default. 0726 // 0727 // Try to fetch profile from CICP first... 0728 dbgFile << "XYB without color transform needed"; 0729 needColorTransform = false; 0730 d.m_depthID = d.m_depthID_target; 0731 d.m_colorID = d.m_colorID_target; 0732 d.m_pixelFormat.data_type = d.m_pixelFormat_target.data_type; 0733 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile); 0734 0735 // ...or use ICC instead if CICP fetch failed. 0736 if (!d.cs) { 0737 dbgFile << "JXL CICP data couldn't be handled, falling back to ICC profile retrieval"; 0738 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(), 0739 d.m_depthID_target.id(), 0740 iccProfile); 0741 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(), 0742 d.m_depthID_target.id(), 0743 profile); 0744 } 0745 } else { 0746 // Skip conversion on original profile. 0747 dbgFile << "Original without color transform needed"; 0748 needColorTransform = false; 0749 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID.id(), 0750 d.m_depthID.id(), 0751 iccProfile); 0752 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile); 0753 } 0754 } 0755 0756 if (d.cs_target) { 0757 dbgFile << "Source profile:" << d.cs->profile()->name(); 0758 dbgFile << "Source space:" << d.cs->name() << d.cs->colorModelId() << d.cs->colorDepthId(); 0759 dbgFile << "Target profile:" << d.cs_target->profile()->name(); 0760 dbgFile << "Color space:" << d.cs_target->name() << d.cs_target->colorModelId() 0761 << d.cs_target->colorDepthId(); 0762 } else { 0763 dbgFile << "Color space:" << d.cs->name() << d.cs->colorModelId() << d.cs->colorDepthId(); 0764 } 0765 dbgFile << "JXL depth" << d.m_pixelFormat.data_type; 0766 0767 d.lCoef = d.cs->lumaCoefficients(); 0768 0769 image = new KisImage(document->createUndoStore(), 0770 static_cast<int>(d.m_info.xsize), 0771 static_cast<int>(d.m_info.ysize), 0772 d.cs, 0773 "JPEG-XL image"); 0774 0775 layer = new KisPaintLayer(image, image->nextLayerName(), UCHAR_MAX); 0776 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 0777 d.m_currentFrame = new KisPaintDevice(image->colorSpace()); 0778 0779 // Use raw byte buffer instead of image callback 0780 size_t rawSize = 0; 0781 if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &d.m_pixelFormat, &rawSize)) { 0782 qWarning() << "JxlDecoderImageOutBufferSize failed"; 0783 return ImportExportCodes::InternalError; 0784 } 0785 d.m_rawData.resize(rawSize); 0786 if (JXL_DEC_SUCCESS 0787 != JxlDecoderSetImageOutBuffer(dec.get(), 0788 &d.m_pixelFormat, 0789 reinterpret_cast<uint8_t *>(d.m_rawData.data()), 0790 static_cast<size_t>(d.m_rawData.size()))) { 0791 qWarning() << "JxlDecoderSetImageOutBuffer failed"; 0792 return ImportExportCodes::InternalError; 0793 } 0794 0795 if (d.isCMYK) { 0796 // Prepare planar buffer for key channel 0797 size_t bufferSize = 0; 0798 if (JXL_DEC_SUCCESS 0799 != JxlDecoderExtraChannelBufferSize(dec.get(), &d.m_pixelFormat, &bufferSize, d.cmykChannelID)) { 0800 errFile << "JxlDecoderExtraChannelBufferSize failed"; 0801 return ImportExportCodes::ErrorWhileReading; 0802 break; 0803 } 0804 d.kPlane.resize(bufferSize); 0805 if (JXL_DEC_SUCCESS 0806 != JxlDecoderSetExtraChannelBuffer(dec.get(), 0807 &d.m_pixelFormat, 0808 d.kPlane.data(), 0809 bufferSize, 0810 d.cmykChannelID)) { 0811 errFile << "JxlDecoderSetExtraChannelBuffer failed"; 0812 return ImportExportCodes::ErrorWhileReading; 0813 break; 0814 } 0815 } 0816 } else if (status == JXL_DEC_FRAME) { 0817 if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &d.m_header)) { 0818 errFile << "JxlDecoderGetFrameHeader failed"; 0819 return ImportExportCodes::ErrorWhileReading; 0820 } 0821 0822 const JxlBlendMode blendMode = d.m_header.layer_info.blend_info.blendmode; 0823 const bool isBlendSupported = 0824 std::find(supportedBlendMode.begin(), supportedBlendMode.end(), blendMode) != supportedBlendMode.end(); 0825 0826 if (!isBlendSupported && !decSetCoalescing) { 0827 warnFile << "Blending mode unsupported! Rewinding decoder with coalescing enabled"; 0828 document->setWarningMessage(i18nc("JPEG-XL errors", 0829 "Detected multi layer JPEG-XL image with unsupported blending mode, " 0830 "importing flattened image.")); 0831 if (!rewindDecoderWithCoalesce()) { 0832 return ImportExportCodes::InternalError; 0833 } else { 0834 additionalLayers.clear(); 0835 bgLayerSet = false; 0836 continue; 0837 } 0838 } 0839 0840 if (!d.m_info.have_animation) { 0841 QString layerName; 0842 QByteArray layerNameRaw; 0843 if (d.m_header.name_length) { 0844 KIS_SAFE_ASSERT_RECOVER(d.m_header.name_length < std::numeric_limits<int>::max()) 0845 { 0846 document->setErrorMessage(i18nc("JPEG-XL", "Invalid JPEG-XL layer name length")); 0847 return ImportExportCodes::FormatFeaturesUnsupported; 0848 } 0849 layerNameRaw.resize(static_cast<int>(d.m_header.name_length + 1)); 0850 if (JXL_DEC_SUCCESS 0851 != JxlDecoderGetFrameName(dec.get(), 0852 layerNameRaw.data(), 0853 static_cast<size_t>(layerNameRaw.size()))) { 0854 errFile << "JxlDecoderGetFrameName failed"; 0855 break; 0856 } 0857 dbgFile << "\tlayer name:" << QString(layerNameRaw); 0858 layerName = QString(layerNameRaw); 0859 } else { 0860 layerName = QString("Layer"); 0861 } 0862 // Set the first layer name (if any) 0863 if (!bgLayerSet) { 0864 if (!layerNameRaw.isEmpty()) { 0865 layer->setName(layerName); 0866 } 0867 } else { 0868 additionalLayers.emplace_back(new KisPaintLayer(image, layerName, UCHAR_MAX)); 0869 if (blendMode == JXL_BLEND_MULADD) { 0870 additionalLayers.back()->setCompositeOpId(QString("add")); 0871 } 0872 } 0873 } 0874 } else if (status == JXL_DEC_FULL_IMAGE) { 0875 // Parse raw data using existing callback function 0876 generateCallback(d); 0877 const JxlLayerInfo layerInfo = d.m_header.layer_info; 0878 const QRect layerBounds = QRect(static_cast<int>(layerInfo.crop_x0), 0879 static_cast<int>(layerInfo.crop_y0), 0880 static_cast<int>(layerInfo.xsize), 0881 static_cast<int>(layerInfo.ysize)); 0882 if (d.m_info.have_animation) { 0883 dbgFile << "Importing frame @" << d.m_nextFrameTime 0884 << d.m_header.duration; 0885 0886 if (d.m_nextFrameTime == 0) { 0887 dbgFile << "Animation detected, ticks per second:" 0888 << d.m_info.animation.tps_numerator 0889 << d.m_info.animation.tps_denominator; 0890 // XXX: How many ticks per second (FPS)? 0891 // If > 240, block the derivation-- it's a stock JXL and 0892 // Krita only supports up to 240 FPS. 0893 // We'll try to derive the framerate from the first frame 0894 // instead. 0895 int framerate = 0896 std::lround(d.m_info.animation.tps_numerator 0897 / static_cast<double>( 0898 d.m_info.animation.tps_denominator)); 0899 if (framerate > 240) { 0900 warnFile << "JXL ticks per second value exceeds 240, " 0901 "approximating FPS from the duration of " 0902 "the first frame"; 0903 document->setWarningMessage( 0904 i18nc("JPEG-XL errors", 0905 "The animation declares a frame rate of more " 0906 "than 240 FPS.")); 0907 const int approximatedFramerate = std::lround( 0908 1000.0 / static_cast<double>(d.m_header.duration)); 0909 d.m_durationFrameInTicks = 0910 static_cast<int>(d.m_header.duration); 0911 framerate = std::max(approximatedFramerate, 1); 0912 } else { 0913 d.m_durationFrameInTicks = 1; 0914 } 0915 dbgFile << "Framerate:" << framerate; 0916 layer->enableAnimation(); 0917 image->animationInterface()->setDocumentRangeStartFrame(0); 0918 image->animationInterface()->setFramerate(framerate); 0919 } 0920 0921 const int currentFrameTime = std::lround( 0922 static_cast<double>(d.m_nextFrameTime) 0923 / static_cast<double>(d.m_durationFrameInTicks)); 0924 0925 auto *channel = layer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true); 0926 auto *frame = dynamic_cast<KisRasterKeyframeChannel *>(channel); 0927 image->animationInterface()->setDocumentRangeEndFrame( 0928 std::lround(static_cast<double>(d.m_nextFrameTime 0929 + d.m_header.duration) 0930 / static_cast<double>(d.m_durationFrameInTicks)) 0931 - 1); 0932 frame->importFrame(currentFrameTime, d.m_currentFrame, nullptr); 0933 d.m_nextFrameTime += static_cast<int>(d.m_header.duration); 0934 } else { 0935 if (d.isCMYK && d.m_info.uses_original_profile) { 0936 QVector<quint8 *> planes = d.m_currentFrame->readPlanarBytes(layerBounds.x(), 0937 layerBounds.y(), 0938 layerBounds.width(), 0939 layerBounds.height()); 0940 0941 // Planar buffer insertion for key channel 0942 planes[3] = reinterpret_cast<quint8 *>(d.kPlane.data()); 0943 d.m_currentFrame->writePlanarBytes(planes, 0944 layerBounds.x(), 0945 layerBounds.y(), 0946 layerBounds.width(), 0947 layerBounds.height()); 0948 0949 // JPEG-XL decode outputs an inverted CMYK colors 0950 // This one I took from kis_filter_test for inverting the colors.. 0951 const KisFilterSP f = KisFilterRegistry::instance()->value("invert"); 0952 KIS_ASSERT(f); 0953 const KisFilterConfigurationSP kfc = 0954 f->defaultConfiguration(KisGlobalResourcesInterface::instance()); 0955 KIS_ASSERT(kfc); 0956 f->process(d.m_currentFrame, layerBounds, kfc->cloneWithResourcesSnapshot()); 0957 } 0958 if (!bgLayerSet) { 0959 layer->paintDevice()->makeCloneFrom(d.m_currentFrame, layerBounds); 0960 bgLayerSet = true; 0961 } else { 0962 additionalLayers.back()->paintDevice()->makeCloneFrom(d.m_currentFrame, layerBounds); 0963 } 0964 } 0965 } else if (status == JXL_DEC_SUCCESS || status == JXL_DEC_BOX) { 0966 if (std::strlen(boxType.data()) != 0) { 0967 // Release buffer and get its final size. 0968 const auto availOut = JxlDecoderReleaseBoxBuffer(dec.get()); 0969 const int finalSize = box.size() - static_cast<int>(availOut); 0970 // Only resize and write boxes if it's not empty. 0971 // And only input metadata boxes while skipping other boxes. 0972 QByteArray type = boxType.toLower(); 0973 if ((std::equal(exifTag.begin(), exifTag.end(), type.constBegin()) 0974 || std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin())) 0975 && finalSize != 0) { 0976 metadataBoxes.emplace(type, QByteArray(box.data(), finalSize)); 0977 } 0978 // Preemptively zero the box type out to prevent dangling 0979 // boxes. 0980 boxType.fill('\0'); 0981 } 0982 if (status == JXL_DEC_SUCCESS) { 0983 // All decoding successfully finished. 0984 0985 // Insert layer metadata if available (delayed 0986 // in case the boxes came before the BASIC_INFO event) 0987 for (auto &metaBox : metadataBoxes) { 0988 const QByteArray &type = metaBox.first; 0989 QByteArray &value = metaBox.second; 0990 QBuffer buf(&value); 0991 if (std::equal(exifTag.begin(), exifTag.end(), type.constBegin())) { 0992 dbgFile << "Loading EXIF data. Size: " << value.size(); 0993 0994 const auto *backend = 0995 KisMetadataBackendRegistry::instance()->value( 0996 "exif"); 0997 0998 backend->loadFrom(layer->metaData(), &buf); 0999 } else if (std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin())) { 1000 dbgFile << "Loading XMP or IPTC data. Size: " << value.size(); 1001 1002 const auto *xmpBackend = 1003 KisMetadataBackendRegistry::instance()->value( 1004 "xmp"); 1005 1006 if (!xmpBackend->loadFrom(layer->metaData(), &buf)) { 1007 const KisMetaData::IOBackend *iptcBackend = 1008 KisMetadataBackendRegistry::instance()->value( 1009 "iptc"); 1010 iptcBackend->loadFrom(layer->metaData(), &buf); 1011 } 1012 } 1013 } 1014 1015 // It's not required to call JxlDecoderReleaseInput(dec.get()) here since 1016 // the decoder will be destroyed. 1017 image->addNode(layer, image->rootLayer().data()); 1018 // Slip additional layers into layer stack 1019 for (const KisLayerSP &addLayer : additionalLayers) { 1020 image->addNode(addLayer, image->rootLayer().data()); 1021 } 1022 if (needColorTransform) { 1023 if (needIntermediateTransform) { 1024 dbgFile << "Transforming to intermediate color space"; 1025 image->convertImageColorSpace(d.cs_intermediate, 1026 d.m_intent, 1027 KoColorConversionTransformation::internalConversionFlags()); 1028 image->waitForDone(); 1029 } 1030 dbgFile << "Transforming to target color space"; 1031 image->convertImageColorSpace(d.cs_target, 1032 d.m_intent, 1033 KoColorConversionTransformation::internalConversionFlags()); 1034 image->waitForDone(); 1035 } 1036 document->setCurrentImage(image); 1037 return ImportExportCodes::OK; 1038 } else { 1039 if (JxlDecoderGetBoxType(dec.get(), boxType.data(), JXL_TRUE) != JXL_DEC_SUCCESS) { 1040 errFile << "JxlDecoderGetBoxType failed"; 1041 return ImportExportCodes::ErrorWhileReading; 1042 } 1043 const QByteArray type = boxType.toLower(); 1044 if (std::equal(exifTag.begin(), exifTag.end(), type.constBegin()) 1045 || std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin())) { 1046 if (JxlDecoderSetBoxBuffer( 1047 dec.get(), 1048 reinterpret_cast<uint8_t *>(box.data()), 1049 static_cast<size_t>(box.size())) 1050 != JXL_DEC_SUCCESS) { 1051 errFile << "JxlDecoderSetBoxBuffer failed"; 1052 return ImportExportCodes::InternalError; 1053 } 1054 } else { 1055 dbgFile << "Skipping box" << boxType.data(); 1056 } 1057 } 1058 } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { 1059 // Update the box size if it was truncated in a previous buffering. 1060 boxSize = box.size(); 1061 box.resize(boxSize * 2); 1062 // Release buffer before setting it up again 1063 JxlDecoderReleaseBoxBuffer(dec.get()); 1064 if (JxlDecoderSetBoxBuffer( 1065 dec.get(), 1066 reinterpret_cast<uint8_t *>(box.data() + boxSize), 1067 static_cast<size_t>(box.size() - boxSize)) 1068 != JXL_DEC_SUCCESS) { 1069 errFile << "JxlDecoderGetBoxType failed"; 1070 return ImportExportCodes::ErrorWhileReading; 1071 } 1072 } else { 1073 errFile << "Unknown decoder status" << status; 1074 return ImportExportCodes::InternalError; 1075 } 1076 } 1077 1078 return ImportExportCodes::OK; 1079 } 1080 1081 #include <JPEGXLImport.moc>