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 242 of file JPEGXLImport.cpp.

243 : KisImportExportFilter(parent)
244{
245}
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 248 of file JPEGXLImport.cpp.

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