File indexing completed on 2024-06-16 04:16:59

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>