Krita Source Code Documentation
Loading...
Searching...
No Matches
JPEGXLImport Class Reference

#include <JPEGXLImport.h>

+ Inheritance diagram for JPEGXLImport:

Public Member Functions

KisImportExportErrorCode convert (KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration=nullptr) override
 
 JPEGXLImport (QObject *parent, const QVariantList &)
 
bool supportsIO () const override
 Override and return false for the filters that use a library that cannot handle file handles, only file names.
 
 ~JPEGXLImport () override=default
 
- Public Member Functions inherited from KisImportExportFilter
virtual KisConfigWidgetcreateConfigurationWidget (QWidget *parent, const QByteArray &from="", const QByteArray &to="") const
 createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter
 
virtual KisPropertiesConfigurationSP defaultConfiguration (const QByteArray &from="", const QByteArray &to="") const
 defaultConfiguration defines the default settings for the given import export filter
 
virtual QMap< QString, KisExportCheckBase * > exportChecks ()
 generate and return the list of capabilities of this export filter. The list
 
virtual bool exportSupportsGuides () const
 exportSupportsGuides Because guides are in the document and not the image, checking for guides cannot be made an exportCheck.
 
KisPropertiesConfigurationSP lastSavedConfiguration (const QByteArray &from="", const QByteArray &to="") const
 lastSavedConfiguration return the last saved configuration for this filter
 
 Private ()
 
void setBatchMode (bool batchmode)
 
void setFilename (const QString &filename)
 
void setImportUserFeedBackInterface (KisImportUserFeedbackInterface *interface)
 
void setMimeType (const QString &mime)
 
void setRealFilename (const QString &filename)
 
void setUpdater (QPointer< KoUpdater > updater)
 
QPointer< KoUpdaterupdater ()
 
virtual QString verify (const QString &fileName) const
 Verify whether the given file is correct and readable.
 
 ~KisImportExportFilter () override
 
 ~Private ()
 

Additional Inherited Members

- Public Attributes inherited from KisImportExportFilter
bool batchmode
 
QMap< QString, KisExportCheckBase * > capabilities
 
QString filename
 
KisImportUserFeedbackInterfaceimportUserFeedBackInterface {nullptr}
 
QByteArray mime
 
QString realFilename
 
QPointer< KoUpdaterupdater
 
- Static Public Attributes inherited from KisImportExportFilter
static const QString CICPPrimariesTag = "CICPCompatiblePrimaries"
 
static const QString CICPTransferCharacteristicsTag = "CICPCompatibleTransferFunction"
 
static const QString ColorDepthIDTag = "ColorDepthID"
 
static const QString ColorModelIDTag = "ColorModelID"
 
static const QString HDRTag = "HDRSupported"
 
static const QString ImageContainsTransparencyTag = "ImageContainsTransparency"
 
static const QString sRGBTag = "sRGB"
 
- Protected Member Functions inherited from KisImportExportFilter
void addCapability (KisExportCheckBase *capability)
 
void addSupportedColorModels (QList< QPair< KoID, KoID > > supportedColorModels, const QString &name, KisExportCheckBase::Level level=KisExportCheckBase::PARTIALLY)
 
bool batchMode () const
 
QString filename () const
 
KisImportUserFeedbackInterfaceimportUserFeedBackInterface () const
 
virtual void initializeCapabilities ()
 
 KisImportExportFilter (QObject *parent=0)
 
QByteArray mimeType () const
 
QString realFilename () const
 
void setProgress (int value)
 
QString verifyZiPBasedFiles (const QString &fileName, const QStringList &filesToCheck) const
 

Detailed Description

Definition at line 12 of file JPEGXLImport.h.

Constructor & Destructor Documentation

◆ JPEGXLImport()

JPEGXLImport::JPEGXLImport ( QObject * parent,
const QVariantList &  )

Definition at line 241 of file JPEGXLImport.cpp.

242 : KisImportExportFilter(parent)
243{
244}
KisImportExportFilter(QObject *parent=0)

◆ ~JPEGXLImport()

JPEGXLImport::~JPEGXLImport ( )
overridedefault

Member Function Documentation

◆ convert()

KisImportExportErrorCode JPEGXLImport::convert ( KisDocument * document,
QIODevice * io,
KisPropertiesConfigurationSP configuration = nullptr )
overridevirtual

The filter chain calls this method to perform the actual conversion. The passed mimetypes should be a pair of those you specified in your .desktop file. You have to implement this method to make the filter work.

Returns
The error status, see the #ConversionStatus enum. KisImportExportFilter::OK means that everything is alright.

Implements KisImportExportFilter.

Definition at line 247 of file JPEGXLImport.cpp.

248{
249 if (!io->isReadable()) {
250 errFile << "Cannot read image contents";
252 }
253
255 const auto data = io->readAll();
256
257 const auto validation =
258 JxlSignatureCheck(reinterpret_cast<const uint8_t *>(data.constData()), static_cast<size_t>(data.size()));
259
260 switch (validation) {
261 case JXL_SIG_NOT_ENOUGH_BYTES:
262 errFile << "Failed magic byte validation, not enough data";
264 case JXL_SIG_INVALID:
265 errFile << "Failed magic byte validation, incorrect format";
267 default:
268 break;
269 }
270
271 // Multi-threaded parallel runner.
272 auto runner = JxlResizableParallelRunnerMake(nullptr);
273 auto dec = JxlDecoderMake(nullptr);
274
276
277 // List of blend mode that we can currently support
278 static constexpr std::array<JxlBlendMode, 3> supportedBlendMode = {JXL_BLEND_REPLACE, JXL_BLEND_BLEND, JXL_BLEND_MULADD};
279
280 // Metadata and frame header decoding
281 bool isAnimated = false;
282 bool isMultilayer = false;
283 bool isMultipage = false;
284 bool forceCoalesce = false;
285 {
286 if (JXL_DEC_SUCCESS
287 != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FRAME)) {
288 errFile << "JxlDecoderSubscribeEvents failed";
290 }
291
292 if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) {
293 errFile << "JxlDecoderSetParallelRunner failed";
295 }
296
297 if (JXL_DEC_SUCCESS
298 != JxlDecoderSetInput(dec.get(),
299 reinterpret_cast<const uint8_t *>(data.constData()),
300 static_cast<size_t>(data.size()))) {
301 errFile << "JxlDecoderSetInput failed";
303 };
304 JxlDecoderCloseInput(dec.get());
305
306 if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
307 errFile << "JxlDecoderSetDecompressBoxes failed";
309 };
310
311 if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), JXL_FALSE)) {
312 errFile << "JxlDecoderSetCoalescing failed";
314 };
315
316 for (;;) {
317 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
318
319 if (status == JXL_DEC_ERROR) {
320 errFile << "Decoder error";
322 } else if (status == JXL_DEC_NEED_MORE_INPUT) {
323 errFile << "Error, already provided all input";
325 } else if (status == JXL_DEC_BASIC_INFO) {
326 if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &d.m_info)) {
327 errFile << "JxlDecoderGetBasicInfo failed";
329 }
330 // Coalesce frame on animation import
331 if (d.m_info.have_animation) {
332 isMultilayer = false;
333 isAnimated = true;
334 isMultipage = true;
335 forceCoalesce = true;
336 }
337
338 dbgFile << "Extra Channel[s] info:";
339 for (uint32_t i = 0; i < d.m_info.num_extra_channels; i++) {
340 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec.get(), i, &d.m_extra)) {
341 errFile << "JxlDecoderGetExtraChannelInfo failed";
342 break;
343 }
344 // Channel name references taken from libjxl repo:
345 // https://github.com/libjxl/libjxl/blob/v0.8.0/lib/extras/enc/pnm.cc#L262
346 // With added "JXL-" prefix to indicate that it comes from JXL image.
347 const QString channelTypeString = [&]() {
348 switch (d.m_extra.type) {
349 case JXL_CHANNEL_ALPHA:
350 return QString("JXL-Alpha");
351 case JXL_CHANNEL_DEPTH:
352 return QString("JXL-Depth");
353 case JXL_CHANNEL_SPOT_COLOR:
354 return QString("JXL-SpotColor");
355 case JXL_CHANNEL_SELECTION_MASK:
356 return QString("JXL-SelectionMask");
357 case JXL_CHANNEL_BLACK:
358 return QString("JXL-Black");
359 case JXL_CHANNEL_CFA:
360 return QString("JXL-CFA");
361 case JXL_CHANNEL_THERMAL:
362 return QString("JXL-Thermal");
363 default:
364 return QString("JXL-UNKNOWN");
365 }
366 }();
367
368 // List all extra channels
369 dbgFile << "index:" << i << " | type:" << channelTypeString;
370 if (d.m_extra.type == JXL_CHANNEL_BLACK) {
371 d.isCMYK = true;
372 d.cmykChannelID = i;
373 }
374 if (d.m_extra.type == JXL_CHANNEL_SPOT_COLOR) {
375 warnFile << "Spot color channels unsupported! Rewinding decoder with coalescing enabled";
376 document->setWarningMessage(i18nc("JPEG-XL errors",
377 "Detected JPEG-XL image with spot color channels, "
378 "importing flattened image."));
379 forceCoalesce = true;
380 }
381 }
382
383 dbgFile << "Info";
384 dbgFile << "Size:" << d.m_info.xsize << "x" << d.m_info.ysize;
385 dbgFile << "Depth:" << d.m_info.bits_per_sample << d.m_info.exponent_bits_per_sample;
386 dbgFile << "Number of color channels:" << d.m_info.num_color_channels;
387 dbgFile << "Number of extra channels:" << d.m_info.num_extra_channels;
388 dbgFile << "Extra channels depth:" << d.m_info.alpha_bits << d.m_info.alpha_exponent_bits;
389 dbgFile << "Has animation:" << d.m_info.have_animation << "loops:" << d.m_info.animation.num_loops
390 << "tick:" << d.m_info.animation.tps_numerator << d.m_info.animation.tps_denominator;
391 dbgFile << "Internal pixel format:" << (d.m_info.uses_original_profile ? "Original" : "XYB");
392 JxlResizableParallelRunnerSetThreads(
393 runner.get(),
394 JxlResizableParallelRunnerSuggestThreads(d.m_info.xsize, d.m_info.ysize));
395
396 if (d.m_info.exponent_bits_per_sample != 0) {
397 if (d.m_info.bits_per_sample <= 16) {
398 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT16;
399 d.m_depthID = Float16BitsColorDepthID;
400 } else if (d.m_info.bits_per_sample <= 32) {
401 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT;
402 d.m_depthID = Float32BitsColorDepthID;
403 } else {
404 errFile << "Unsupported JPEG-XL input depth" << d.m_info.bits_per_sample
405 << d.m_info.exponent_bits_per_sample;
407 }
408 } else if (d.m_info.bits_per_sample <= 8) {
409 d.m_pixelFormat.data_type = JXL_TYPE_UINT8;
410 d.m_depthID = Integer8BitsColorDepthID;
411 } else if (d.m_info.bits_per_sample <= 16) {
412 d.m_pixelFormat.data_type = JXL_TYPE_UINT16;
413 d.m_depthID = Integer16BitsColorDepthID;
414 } else {
415 errFile << "Unsupported JPEG-XL input depth" << d.m_info.bits_per_sample
416 << d.m_info.exponent_bits_per_sample;
418 }
419
420 if (d.m_info.num_color_channels == 1) {
421 // Grayscale
422 d.m_pixelFormat.num_channels = 2;
423 d.m_colorID = GrayAColorModelID;
424 } else if (d.m_info.num_color_channels == 3 && !d.isCMYK) {
425 // RGBA
426 d.m_pixelFormat.num_channels = 4;
427 d.m_colorID = RGBAColorModelID;
428 } else if (d.m_info.num_color_channels == 3 && d.isCMYK) {
429 // CMYKA
430 d.m_pixelFormat.num_channels = 4;
431 d.m_colorID = CMYKAColorModelID;
432 } else {
433 warnFile << "Forcing a RGBA conversion, unknown color space";
434 d.m_pixelFormat.num_channels = 4;
435 d.m_colorID = RGBAColorModelID;
436 }
437
438 if (!d.m_info.uses_original_profile) {
439 d.m_pixelFormat_target.data_type = d.m_pixelFormat.data_type;
440 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT;
441 d.m_depthID_target = d.m_depthID;
442 d.m_colorID_target = d.m_colorID;
443 d.m_depthID = Float32BitsColorDepthID;
444
445 if (d.m_colorID != GrayAColorModelID) {
446 d.m_colorID = RGBAColorModelID;
447 }
448 }
449 } else if (status == JXL_DEC_FRAME) {
450 if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &d.m_header)) {
451 errFile << "JxlDecoderGetFrameHeader failed";
453 }
454
455 const JxlBlendMode blendMode = d.m_header.layer_info.blend_info.blendmode;
456 const bool isBlendSupported =
457 std::find(supportedBlendMode.begin(), supportedBlendMode.end(), blendMode) != supportedBlendMode.end();
458
459 if (!isBlendSupported) {
460 forceCoalesce = true;
461 }
462
463 if (d.m_header.duration == 0) {
464 isMultilayer = true;
465 } else if (d.m_header.duration == 0xFFFFFFFF) {
466 isMultipage = true;
467 isAnimated = false;
468 forceCoalesce = true;
469 } else {
470 isAnimated = true;
471 isMultipage = false;
472 isMultilayer = false;
473 forceCoalesce = true;
474 }
475 } else if (status == JXL_DEC_SUCCESS) {
476 break;
477 }
478 }
479 }
480 JxlDecoderReset(dec.get());
481
482 // Set coalescing FALSE to enable layered JXL
483 if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), forceCoalesce ? JXL_TRUE : JXL_FALSE)) {
484 errFile << "JxlDecoderSetCoalescing failed";
486 }
487
488 if (JXL_DEC_SUCCESS
489 != JxlDecoderSubscribeEvents(dec.get(),
490 JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE | JXL_DEC_BOX | JXL_DEC_FRAME)) {
491 errFile << "JxlDecoderSubscribeEvents failed";
493 }
494
495 if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) {
496 errFile << "JxlDecoderSetParallelRunner failed";
498 }
499
500 if (JXL_DEC_SUCCESS
501 != JxlDecoderSetInput(dec.get(),
502 reinterpret_cast<const uint8_t *>(data.constData()),
503 static_cast<size_t>(data.size()))) {
504 errFile << "JxlDecoderSetInput failed";
506 };
507 JxlDecoderCloseInput(dec.get());
508
509 if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
510 errFile << "JxlDecoderSetDecompressBoxes failed";
512 };
513
514 KisImageSP image{nullptr};
515 KisLayerSP layer{nullptr};
516 std::multimap<QByteArray, QByteArray> metadataBoxes;
517 std::vector<KisLayerSP> additionalLayers;
518 bool bgLayerSet = false;
519 bool needColorTransform = false;
520 bool needIntermediateTransform = false;
521 QByteArray boxType(5, 0x0);
522 QByteArray box(16384, 0x0);
523 auto boxSize = box.size();
524
525 // Basic info already parsed above, skip doing it again
526 for (;;) {
527 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
528
529 if (status == JXL_DEC_ERROR) {
530 errFile << "Decoder error";
532 } else if (status == JXL_DEC_NEED_MORE_INPUT) {
533 errFile << "Error, already provided all input";
535 } else if (status == JXL_DEC_COLOR_ENCODING) {
536 // Determine color space information
537 const KoColorProfile *profile = nullptr;
538 const KoColorProfile *profileTarget = nullptr;
539 const KoColorProfile *profileIntermediate = nullptr;
540
541 // Chrome way of decoding JXL implies first scanning
542 // the CICP encoding for HDR, and only afterwards
543 // falling back to ICC.
544 JxlColorEncoding colorEncoding{};
545 if (JXL_DEC_SUCCESS
546 == JxlDecoderGetColorAsEncodedProfile(dec.get(),
547#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
548 nullptr,
549#endif
550 JXL_COLOR_PROFILE_TARGET_DATA,
551 &colorEncoding)) {
552 const TransferCharacteristics transferFunction = [&]() {
553 switch (colorEncoding.transfer_function) {
554 case JXL_TRANSFER_FUNCTION_PQ: {
555 dbgFile << "linearizing from PQ";
556 d.linearizePolicy = LinearizePolicy::LinearFromPQ;
557 return TRC_LINEAR;
558 }
559 case JXL_TRANSFER_FUNCTION_HLG: {
560 dbgFile << "linearizing from HLG";
561 if (!document->fileBatchMode()) {
562 KisDlgHLGImport dlg(d.applyOOTF, d.displayGamma, d.displayNits);
563 dlg.exec();
564 d.applyOOTF = dlg.applyOOTF();
565 d.displayGamma = dlg.gamma();
566 d.displayNits = dlg.nominalPeakBrightness();
567 }
568 d.linearizePolicy = LinearizePolicy::LinearFromHLG;
569 return TRC_LINEAR;
570 }
571 case JXL_TRANSFER_FUNCTION_DCI: {
572 dbgFile << "linearizing from SMPTE 428";
573 d.linearizePolicy = LinearizePolicy::LinearFromSMPTE428;
574 return TRC_LINEAR;
575 }
576 case JXL_TRANSFER_FUNCTION_709:
577 return TRC_ITU_R_BT_709_5;
578 case JXL_TRANSFER_FUNCTION_SRGB:
579 return TRC_IEC_61966_2_1;
580 case JXL_TRANSFER_FUNCTION_GAMMA: {
581 // Using roughly the same logic in KoColorProfile.
582 const double estGamma = 1.0 / colorEncoding.gamma;
583 const double error = 0.0001;
584 // ICC v2 u8Fixed8Number calculation
585 // Or can be prequantized as 1.80078125, courtesy of Elle Stone
586 if ((std::fabs(estGamma - 1.8) < error) || (std::fabs(estGamma - (461.0 / 256.0)) < error)) {
587 return TRC_GAMMA_1_8;
588 } else if (std::fabs(estGamma - 2.2) < error) {
590 } else if (std::fabs(estGamma - (563.0 / 256.0)) < error) {
591 return TRC_A98;
592 } else if (std::fabs(estGamma - 2.4) < error) {
593 return TRC_GAMMA_2_4;
594 } else if (std::fabs(estGamma - 2.8) < error) {
596 } else {
597 warnFile << "Found custom estimated gamma value for JXL color space" << estGamma;
598 return TRC_UNSPECIFIED;
599 }
600 }
601 case JXL_TRANSFER_FUNCTION_LINEAR:
602 return TRC_LINEAR;
603 case JXL_TRANSFER_FUNCTION_UNKNOWN:
604 default:
605 warnFile << "Found unknown OETF";
606 return TRC_UNSPECIFIED;
607 }
608 }();
609
610 const ColorPrimaries colorPrimaries = [&]() {
611 switch (colorEncoding.primaries) {
612 case JXL_PRIMARIES_SRGB:
614 case JXL_PRIMARIES_2100:
616 case JXL_PRIMARIES_P3:
618 default:
620 }
621 }();
622
623 const QVector<double> colorants = [&]() -> QVector<double> {
624 if (colorEncoding.primaries != JXL_PRIMARIES_CUSTOM) {
625 return {};
626 } else {
627 return {colorEncoding.white_point_xy[0],
628 colorEncoding.white_point_xy[1],
629 colorEncoding.primaries_red_xy[0],
630 colorEncoding.primaries_red_xy[1],
631 colorEncoding.primaries_green_xy[0],
632 colorEncoding.primaries_green_xy[1],
633 colorEncoding.primaries_blue_xy[0],
634 colorEncoding.primaries_blue_xy[1]};
635 }
636 }();
637
638 if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_PERCEPTUAL) {
640 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_RELATIVE) {
642 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_ABSOLUTE) {
644 } else if (colorEncoding.rendering_intent == JXL_RENDERING_INTENT_SATURATION) {
646 } else {
647 warnFile << "Cannot determine color rendering intent, set to Perceptual instead";
649 }
650
651 profile = KoColorSpaceRegistry::instance()->profileFor(colorants, colorPrimaries, transferFunction);
652
653 dbgFile << "CICP profile data:" << colorants << colorPrimaries << transferFunction;
654
655 if (profile) {
656 dbgFile << "JXL CICP profile found" << profile->name();
657
658 if (d.linearizePolicy != LinearizePolicy::KeepTheSame) {
659 // Override output format!
660 d.m_depthID = Float32BitsColorDepthID;
661 d.m_pixelFormat.data_type = d.m_pixelFormat_target.data_type;
662
663 // HDR is a special case because we need to linearize in-house
664 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
665 }
666 }
667 }
668
669 if (!d.cs) {
670 size_t iccSize = 0;
671 QByteArray iccProfile;
672 if (JXL_DEC_SUCCESS
673 != JxlDecoderGetICCProfileSize(dec.get(),
674#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
675 nullptr,
676#endif
677 JXL_COLOR_PROFILE_TARGET_DATA,
678 &iccSize)) {
679 errFile << "ICC profile size retrieval failed";
680 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile."));
682 }
683 iccProfile.resize(static_cast<int>(iccSize));
684 if (JXL_DEC_SUCCESS
685 != JxlDecoderGetColorAsICCProfile(dec.get(),
686#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
687 nullptr,
688#endif
689 JXL_COLOR_PROFILE_TARGET_DATA,
690 reinterpret_cast<uint8_t *>(iccProfile.data()),
691 static_cast<size_t>(iccProfile.size()))) {
692 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile."));
694 }
695
696 // Get original profile if XYB is used
697 size_t iccTargetSize = 0;
698 QByteArray iccTargetProfile;
699 if (!d.m_info.uses_original_profile) {
700 if (JXL_DEC_SUCCESS
701 != JxlDecoderGetICCProfileSize(dec.get(),
702#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
703 nullptr,
704#endif
705 JXL_COLOR_PROFILE_TARGET_ORIGINAL,
706 &iccTargetSize)) {
707 errFile << "ICC profile size retrieval failed";
708 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile."));
710 }
711 iccTargetProfile.resize(static_cast<int>(iccTargetSize));
712 if (JXL_DEC_SUCCESS
713 != JxlDecoderGetColorAsICCProfile(dec.get(),
714#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,9,0)
715 nullptr,
716#endif
717 JXL_COLOR_PROFILE_TARGET_ORIGINAL,
718 reinterpret_cast<uint8_t *>(iccTargetProfile.data()),
719 static_cast<size_t>(iccTargetProfile.size()))) {
720 document->setErrorMessage(i18nc("JPEG-XL errors", "Unable to read the image profile."));
722 }
723 }
724
725 if (iccTargetSize && (iccProfile != iccTargetProfile)) {
726 // If the icc target is not 0 and different than target data.
727 // Meaning that the JXL is in XYB format and needing to convert back to
728 // the original color profile.
729 //
730 // Here we need to provide an intermediate transform space in float to prevent
731 // gamut clipping to sRGB if the target depth is integer.
732 dbgFile << "XYB with color transform needed";
733 needColorTransform = true;
734 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID.id(),
735 d.m_depthID.id(),
736 iccProfile);
737 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
738 profileIntermediate = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(),
739 d.m_depthID.id(),
740 iccTargetProfile);
741 d.cs_intermediate = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(),
742 d.m_depthID.id(),
743 profileIntermediate);
744 profileTarget = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(),
745 d.m_depthID_target.id(),
746 iccTargetProfile);
747 d.cs_target = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(),
748 d.m_depthID_target.id(),
749 profileTarget);
750
751 // No need for intermediate transform on float since it won't get clipped.
752 if (!(d.m_depthID_target == Float16BitsColorDepthID
753 || d.m_depthID_target == Float32BitsColorDepthID)) {
754 needIntermediateTransform = true;
755 }
756 } else if (!d.m_info.uses_original_profile) {
757 // If XYB is used but the profiles are same, skip conversion.
758 // Also set the color depth target to default.
759 //
760 // Try to fetch profile from CICP first...
761 dbgFile << "XYB without color transform needed";
762 needColorTransform = false;
763 d.m_depthID = d.m_depthID_target;
764 d.m_colorID = d.m_colorID_target;
765 d.m_pixelFormat.data_type = d.m_pixelFormat_target.data_type;
766 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
767
768 // ...or use ICC instead if CICP fetch failed.
769 if (!d.cs) {
770 dbgFile << "JXL CICP data couldn't be handled, falling back to ICC profile retrieval";
771 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID_target.id(),
772 d.m_depthID_target.id(),
773 iccProfile);
774 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID_target.id(),
775 d.m_depthID_target.id(),
776 profile);
777 }
778 } else {
779 // Skip conversion on original profile.
780 dbgFile << "Original without color transform needed";
781 needColorTransform = false;
782 profile = KoColorSpaceRegistry::instance()->createColorProfile(d.m_colorID.id(),
783 d.m_depthID.id(),
784 iccProfile);
785 d.cs = KoColorSpaceRegistry::instance()->colorSpace(d.m_colorID.id(), d.m_depthID.id(), profile);
786 }
787 }
788
789 if (d.cs_target) {
790 dbgFile << "Source profile:" << d.cs->profile()->name();
791 dbgFile << "Source space:" << d.cs->name() << d.cs->colorModelId() << d.cs->colorDepthId();
792 dbgFile << "Target profile:" << d.cs_target->profile()->name();
793 dbgFile << "Color space:" << d.cs_target->name() << d.cs_target->colorModelId()
794 << d.cs_target->colorDepthId();
795 } else {
796 dbgFile << "Color space:" << d.cs->name() << d.cs->colorModelId() << d.cs->colorDepthId();
797 }
798 dbgFile << "JXL depth" << d.m_pixelFormat.data_type;
799
800 d.lCoef = d.cs->lumaCoefficients();
801
802 image = new KisImage(document->createUndoStore(),
803 static_cast<int>(d.m_info.xsize),
804 static_cast<int>(d.m_info.ysize),
805 d.cs,
806 "JPEG-XL image");
807
808 layer = new KisPaintLayer(image, image->nextLayerName(), UCHAR_MAX);
809 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
810 d.m_currentFrame = new KisPaintDevice(image->colorSpace());
811
812 // Use raw byte buffer instead of image callback
813 size_t rawSize = 0;
814 if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &d.m_pixelFormat, &rawSize)) {
815 qWarning() << "JxlDecoderImageOutBufferSize failed";
817 }
818 d.m_rawData.resize(rawSize);
819 if (JXL_DEC_SUCCESS
820 != JxlDecoderSetImageOutBuffer(dec.get(),
821 &d.m_pixelFormat,
822 reinterpret_cast<uint8_t *>(d.m_rawData.data()),
823 static_cast<size_t>(d.m_rawData.size()))) {
824 qWarning() << "JxlDecoderSetImageOutBuffer failed";
826 }
827
828 if (d.isCMYK) {
829 // Prepare planar buffer for key channel
830 size_t bufferSize = 0;
831 if (JXL_DEC_SUCCESS
832 != JxlDecoderExtraChannelBufferSize(dec.get(), &d.m_pixelFormat, &bufferSize, d.cmykChannelID)) {
833 errFile << "JxlDecoderExtraChannelBufferSize failed";
835 break;
836 }
837 d.kPlane.resize(bufferSize);
838 if (JXL_DEC_SUCCESS
839 != JxlDecoderSetExtraChannelBuffer(dec.get(),
840 &d.m_pixelFormat,
841 d.kPlane.data(),
842 bufferSize,
843 d.cmykChannelID)) {
844 errFile << "JxlDecoderSetExtraChannelBuffer failed";
846 break;
847 }
848 }
849 } else if (status == JXL_DEC_FRAME) {
850 if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec.get(), &d.m_header)) {
851 errFile << "JxlDecoderGetFrameHeader failed";
853 }
854
855 const JxlBlendMode blendMode = d.m_header.layer_info.blend_info.blendmode;
856
857 if (isMultilayer || isMultipage) {
858 QString layerName;
859 QByteArray layerNameRaw;
860 if (d.m_header.name_length) {
861 KIS_SAFE_ASSERT_RECOVER(d.m_header.name_length < std::numeric_limits<int>::max())
862 {
863 document->setErrorMessage(i18nc("JPEG-XL", "Invalid JPEG-XL layer name length"));
865 }
866 layerNameRaw.resize(static_cast<int>(d.m_header.name_length + 1));
867 if (JXL_DEC_SUCCESS
868 != JxlDecoderGetFrameName(dec.get(),
869 layerNameRaw.data(),
870 static_cast<size_t>(layerNameRaw.size()))) {
871 errFile << "JxlDecoderGetFrameName failed";
872 break;
873 }
874 dbgFile << "\tlayer name:" << QString(layerNameRaw);
875 layerName = QString(layerNameRaw);
876 } else {
877 layerName = QString("Layer");
878 }
879 // Set the first layer name (if any)
880 if (!bgLayerSet) {
881 if (!layerNameRaw.isEmpty()) {
882 layer->setName(layerName);
883 }
884 } else {
885 additionalLayers.emplace_back(new KisPaintLayer(image, layerName, UCHAR_MAX));
886 if (blendMode == JXL_BLEND_MULADD) {
887 additionalLayers.back()->setCompositeOpId(QString("add"));
888 }
889 }
890 }
891 } else if (status == JXL_DEC_FULL_IMAGE) {
892 // Parse raw data using existing callback function
894 const JxlLayerInfo layerInfo = d.m_header.layer_info;
895 const QRect layerBounds = QRect(static_cast<int>(layerInfo.crop_x0),
896 static_cast<int>(layerInfo.crop_y0),
897 static_cast<int>(layerInfo.xsize),
898 static_cast<int>(layerInfo.ysize));
899 if (isAnimated) {
900 dbgFile << "Importing frame @" << d.m_nextFrameTime
901 << d.m_header.duration;
902
903 // XXX: If frame header duration is set to 0xFFFFFFFF it indicates that the
904 // current animation page has ended. Since we didn't currently support
905 // multipage image/animation, this will dump the whole animation instead.
906 // Otherwise, it may cause a lockup when loading a multipage image.
907 const uint32_t frameDurationNorm = d.m_header.duration == 0xFFFFFFFF ? 1 : d.m_header.duration;
908 if (d.m_nextFrameTime == 0) {
909 dbgFile << "Animation detected, ticks per second:"
910 << d.m_info.animation.tps_numerator
911 << d.m_info.animation.tps_denominator;
912 // XXX: How many ticks per second (FPS)?
913 // If > 240, block the derivation-- it's a stock JXL and
914 // Krita only supports up to 240 FPS.
915 // We'll try to derive the framerate from the first frame
916 // instead.
917 int framerate =
918 std::lround(d.m_info.animation.tps_numerator
919 / static_cast<double>(
920 d.m_info.animation.tps_denominator));
921 if (framerate > 240) {
922 warnFile << "JXL ticks per second value exceeds 240, "
923 "approximating FPS from the duration of "
924 "the first frame";
925 document->setWarningMessage(
926 i18nc("JPEG-XL errors",
927 "The animation declares a frame rate of more "
928 "than 240 FPS."));
929 const int approximatedFramerate = std::lround(
930 1000.0 / static_cast<double>(d.m_header.duration));
931 d.m_durationFrameInTicks =
932 static_cast<int>(frameDurationNorm);
933 framerate = std::max(approximatedFramerate, 1);
934 } else {
935 d.m_durationFrameInTicks = 1;
936 }
937 dbgFile << "Framerate:" << framerate;
938 layer->enableAnimation();
939 image->animationInterface()->setDocumentRangeStartFrame(0);
940 image->animationInterface()->setFramerate(framerate);
941 }
942
943 const int currentFrameTime = std::lround(
944 static_cast<double>(d.m_nextFrameTime)
945 / static_cast<double>(d.m_durationFrameInTicks));
946
947 auto *channel = layer->getKeyframeChannel(KisKeyframeChannel::Raster.id(), true);
948 auto *frame = dynamic_cast<KisRasterKeyframeChannel *>(channel);
949 image->animationInterface()->setDocumentRangeEndFrame(
950 std::lround(static_cast<double>(d.m_nextFrameTime
951 + frameDurationNorm)
952 / static_cast<double>(d.m_durationFrameInTicks))
953 - 1);
954 frame->importFrame(currentFrameTime, d.m_currentFrame, nullptr);
955 d.m_nextFrameTime += static_cast<int>(frameDurationNorm);
956 } else {
957 if (d.isCMYK && d.m_info.uses_original_profile) {
958 QVector<quint8 *> planes = d.m_currentFrame->readPlanarBytes(layerBounds.x(),
959 layerBounds.y(),
960 layerBounds.width(),
961 layerBounds.height());
962
963 // Planar buffer insertion for key channel
964 planes[3] = reinterpret_cast<quint8 *>(d.kPlane.data());
965 d.m_currentFrame->writePlanarBytes(planes,
966 layerBounds.x(),
967 layerBounds.y(),
968 layerBounds.width(),
969 layerBounds.height());
970
971 // JPEG-XL decode outputs an inverted CMYK colors
972 // This one I took from kis_filter_test for inverting the colors..
973 const KisFilterSP f = KisFilterRegistry::instance()->value("invert");
974 KIS_ASSERT(f);
975 const KisFilterConfigurationSP kfc =
976 f->defaultConfiguration(KisGlobalResourcesInterface::instance());
977 KIS_ASSERT(kfc);
978 f->process(d.m_currentFrame, layerBounds, kfc->cloneWithResourcesSnapshot());
979 }
980 if (!bgLayerSet) {
981 layer->paintDevice()->makeCloneFrom(d.m_currentFrame, layerBounds);
982 bgLayerSet = true;
983 } else {
984 additionalLayers.back()->paintDevice()->makeCloneFrom(d.m_currentFrame, layerBounds);
985 }
986 }
987 } else if (status == JXL_DEC_SUCCESS || status == JXL_DEC_BOX) {
988 if (std::strlen(boxType.data()) != 0) {
989 // Release buffer and get its final size.
990 const auto availOut = JxlDecoderReleaseBoxBuffer(dec.get());
991 const int finalSize = box.size() - static_cast<int>(availOut);
992 // Only resize and write boxes if it's not empty.
993 // And only input metadata boxes while skipping other boxes.
994 QByteArray type = boxType.toLower();
995 if ((std::equal(exifTag.begin(), exifTag.end(), type.constBegin())
996 || std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin()))
997 && finalSize != 0) {
998 metadataBoxes.emplace(type, QByteArray(box.data(), finalSize));
999 }
1000 // Preemptively zero the box type out to prevent dangling
1001 // boxes.
1002 boxType.fill('\0');
1003 }
1004 if (status == JXL_DEC_SUCCESS) {
1005 // All decoding successfully finished.
1006
1007 // Insert layer metadata if available (delayed
1008 // in case the boxes came before the BASIC_INFO event)
1009 for (auto &metaBox : metadataBoxes) {
1010 const QByteArray &type = metaBox.first;
1011 QByteArray &value = metaBox.second;
1012 QBuffer buf(&value);
1013 if (std::equal(exifTag.begin(), exifTag.end(), type.constBegin())) {
1014 dbgFile << "Loading EXIF data. Size: " << value.size();
1015
1016 const auto *backend =
1018 "exif");
1019
1020 backend->loadFrom(layer->metaData(), &buf);
1021 } else if (std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin())) {
1022 dbgFile << "Loading XMP or IPTC data. Size: " << value.size();
1023
1024 const auto *xmpBackend =
1026 "xmp");
1027
1028 if (!xmpBackend->loadFrom(layer->metaData(), &buf)) {
1029 const KisMetaData::IOBackend *iptcBackend =
1031 "iptc");
1032 iptcBackend->loadFrom(layer->metaData(), &buf);
1033 }
1034 }
1035 }
1036
1037 // It's not required to call JxlDecoderReleaseInput(dec.get()) here since
1038 // the decoder will be destroyed.
1039 image->addNode(layer, image->rootLayer().data());
1040 // Slip additional layers into layer stack
1041 for (const KisLayerSP &addLayer : additionalLayers) {
1042 image->addNode(addLayer, image->rootLayer().data());
1043 }
1044 if (needColorTransform) {
1045 if (needIntermediateTransform) {
1046 dbgFile << "Transforming to intermediate color space";
1047 image->convertImageColorSpace(d.cs_intermediate,
1048 d.m_intent,
1050 image->waitForDone();
1051 }
1052 dbgFile << "Transforming to target color space";
1053 image->convertImageColorSpace(d.cs_target,
1054 d.m_intent,
1056 image->waitForDone();
1057 }
1058 document->setCurrentImage(image);
1059 return ImportExportCodes::OK;
1060 } else {
1061 if (JxlDecoderGetBoxType(dec.get(), boxType.data(), JXL_TRUE) != JXL_DEC_SUCCESS) {
1062 errFile << "JxlDecoderGetBoxType failed";
1064 }
1065 const QByteArray type = boxType.toLower();
1066 if (std::equal(exifTag.begin(), exifTag.end(), type.constBegin())
1067 || std::equal(xmpTag.begin(), xmpTag.end(), type.constBegin())) {
1068 if (JxlDecoderSetBoxBuffer(
1069 dec.get(),
1070 reinterpret_cast<uint8_t *>(box.data()),
1071 static_cast<size_t>(box.size()))
1072 != JXL_DEC_SUCCESS) {
1073 errFile << "JxlDecoderSetBoxBuffer failed";
1075 }
1076 } else {
1077 dbgFile << "Skipping box" << boxType.data();
1078 }
1079 }
1080 } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
1081 // Update the box size if it was truncated in a previous buffering.
1082 boxSize = box.size();
1083 box.resize(boxSize * 2);
1084 // Release buffer before setting it up again
1085 JxlDecoderReleaseBoxBuffer(dec.get());
1086 if (JxlDecoderSetBoxBuffer(
1087 dec.get(),
1088 reinterpret_cast<uint8_t *>(box.data() + boxSize),
1089 static_cast<size_t>(box.size() - boxSize))
1090 != JXL_DEC_SUCCESS) {
1091 errFile << "JxlDecoderGetBoxType failed";
1093 }
1094 } else {
1095 errFile << "Unknown decoder status" << status;
1097 }
1098 }
1099
1100 return ImportExportCodes::OK;
1101}
static constexpr std::array< char, 4 > exifTag
float value(const T *src, size_t ch)
static constexpr std::array< char, 4 > xmpTag
void generateCallback(JPEGXLImportData &d)
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ PRIMARIES_UNSPECIFIED
@ PRIMARIES_SMPTE_RP_431_2
@ PRIMARIES_ITU_R_BT_709_5
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_709_5
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP instance()
static const KoID Raster
virtual bool loadFrom(Store *store, QIODevice *ioDevice) const =0
static KisMetadataBackendRegistry * instance()
The KisRasterKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisRas...
void importFrame(int time, KisPaintDeviceSP sourceDevice, KUndo2Command *parentCommand)
const T value(const QString &id) const
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define warnFile
Definition kis_debug.h:95
#define errFile
Definition kis_debug.h:115
#define dbgFile
Definition kis_debug.h:53
const KoColorProfile * profileFor(const QVector< double > &colorants, ColorPrimaries colorPrimaries, TransferCharacteristics transferFunction) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)

References KisDlgHLGImport::applyOOTF(), CMYKAColorModelID, KoColorSpaceRegistry::colorSpace(), KoColorSpaceRegistry::createColorProfile(), KisImportExportFilter::d, dbgFile, errFile, ImportExportCodes::ErrorWhileReading, exifTag, ImportExportCodes::FileFormatIncorrect, Float16BitsColorDepthID, Float32BitsColorDepthID, ImportExportCodes::FormatFeaturesUnsupported, KisDlgHLGImport::gamma(), generateCallback(), GrayAColorModelID, KisRasterKeyframeChannel::importFrame(), KisFilterRegistry::instance(), KisMetadataBackendRegistry::instance(), KoColorSpaceRegistry::instance(), KisGlobalResourcesInterface::instance(), Integer16BitsColorDepthID, Integer8BitsColorDepthID, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::IntentRelativeColorimetric, KoColorConversionTransformation::IntentSaturation, KoColorConversionTransformation::internalConversionFlags(), ImportExportCodes::InternalError, KeepTheSame, KIS_ASSERT, KIS_SAFE_ASSERT_RECOVER, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, LinearFromHLG, LinearFromPQ, LinearFromSMPTE428, KisMetaData::IOBackend::loadFrom(), KoColorProfile::name, ImportExportCodes::NoAccessToRead, KisDlgHLGImport::nominalPeakBrightness(), ImportExportCodes::OK, PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, PRIMARIES_ITU_R_BT_709_5, PRIMARIES_SMPTE_RP_431_2, PRIMARIES_UNSPECIFIED, KoColorSpaceRegistry::profileFor(), KisKeyframeChannel::Raster, RGBAColorModelID, TRC_A98, TRC_GAMMA_1_8, TRC_GAMMA_2_4, TRC_IEC_61966_2_1, TRC_ITU_R_BT_470_6_SYSTEM_B_G, TRC_ITU_R_BT_470_6_SYSTEM_M, TRC_ITU_R_BT_709_5, TRC_LINEAR, TRC_UNSPECIFIED, KoGenericRegistry< T >::value(), value(), warnFile, and xmpTag.

◆ supportsIO()

bool JPEGXLImport::supportsIO ( ) const
inlineoverridevirtual

Override and return false for the filters that use a library that cannot handle file handles, only file names.

Reimplemented from KisImportExportFilter.

Definition at line 18 of file JPEGXLImport.h.

18{ return true; }

The documentation for this class was generated from the following files: