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.
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
272 auto runner = JxlResizableParallelRunnerMake(nullptr);
273 auto dec = JxlDecoderMake(nullptr);
274
276
277
278 static constexpr std::array<JxlBlendMode, 3> supportedBlendMode = {JXL_BLEND_REPLACE, JXL_BLEND_BLEND, JXL_BLEND_MULADD};
279
280
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) {
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
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
345
346
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
369 dbgFile <<
"index:" << i <<
" | type:" << channelTypeString;
370 if (
d.m_extra.type == JXL_CHANNEL_BLACK) {
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
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;
400 }
else if (
d.m_info.bits_per_sample <= 32) {
401 d.m_pixelFormat.data_type = JXL_TYPE_FLOAT;
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;
411 }
else if (
d.m_info.bits_per_sample <= 16) {
412 d.m_pixelFormat.data_type = JXL_TYPE_UINT16;
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
422 d.m_pixelFormat.num_channels = 2;
424 }
else if (
d.m_info.num_color_channels == 3 && !
d.isCMYK) {
425
426 d.m_pixelFormat.num_channels = 4;
428 }
else if (
d.m_info.num_color_channels == 3 &&
d.isCMYK) {
429
430 d.m_pixelFormat.num_channels = 4;
432 } else {
433 warnFile <<
"Forcing a RGBA conversion, unknown color space";
434 d.m_pixelFormat.num_channels = 4;
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;
444
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
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
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
526 for (;;) {
527 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
528
529 if (status == JXL_DEC_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
540
541
542
543
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)) {
553 switch (colorEncoding.transfer_function) {
554 case JXL_TRANSFER_FUNCTION_PQ: {
555 dbgFile <<
"linearizing from PQ";
558 }
559 case JXL_TRANSFER_FUNCTION_HLG: {
560 dbgFile <<
"linearizing from HLG";
561 if (!document->fileBatchMode()) {
563 dlg.exec();
564 d.applyOOTF = dlg.applyOOTF();
565 d.displayGamma = dlg.gamma();
566 d.displayNits = dlg.nominalPeakBrightness();
567 }
570 }
571 case JXL_TRANSFER_FUNCTION_DCI: {
572 dbgFile <<
"linearizing from SMPTE 428";
575 }
576 case JXL_TRANSFER_FUNCTION_709:
578 case JXL_TRANSFER_FUNCTION_SRGB:
580 case JXL_TRANSFER_FUNCTION_GAMMA: {
581
582 const double estGamma = 1.0 / colorEncoding.gamma;
583 const double error = 0.0001;
584
585
586 if ((std::fabs(estGamma - 1.8) < error) || (std::fabs(estGamma - (461.0 / 256.0)) < error)) {
588 } else if (std::fabs(estGamma - 2.2) < error) {
590 } else if (std::fabs(estGamma - (563.0 / 256.0)) < error) {
592 } else if (std::fabs(estGamma - 2.4) < error) {
594 } else if (std::fabs(estGamma - 2.8) < error) {
596 } else {
597 warnFile <<
"Found custom estimated gamma value for JXL color space" << estGamma;
599 }
600 }
601 case JXL_TRANSFER_FUNCTION_LINEAR:
603 case JXL_TRANSFER_FUNCTION_UNKNOWN:
604 default:
607 }
608 }();
609
611 switch (colorEncoding.primaries) {
612 case JXL_PRIMARIES_SRGB:
614 case JXL_PRIMARIES_2100:
616 case JXL_PRIMARIES_P3:
618 default:
620 }
621 }();
622
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
652
653 dbgFile <<
"CICP profile data:" << colorants << colorPrimaries << transferFunction;
654
655 if (profile) {
656 dbgFile <<
"JXL CICP profile found" << profile->
name();
657
659
661 d.m_pixelFormat.data_type =
d.m_pixelFormat_target.data_type;
662
663
665 }
666 }
667 }
668
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
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
727
728
729
730
731
732 dbgFile <<
"XYB with color transform needed";
733 needColorTransform = true;
736 iccProfile);
740 iccTargetProfile);
743 profileIntermediate);
745 d.m_depthID_target.id(),
746 iccTargetProfile);
748 d.m_depthID_target.id(),
749 profileTarget);
750
751
754 needIntermediateTransform = true;
755 }
756 }
else if (!
d.m_info.uses_original_profile) {
757
758
759
760
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;
767
768
770 dbgFile <<
"JXL CICP data couldn't be handled, falling back to ICC profile retrieval";
772 d.m_depthID_target.id(),
773 iccProfile);
775 d.m_depthID_target.id(),
776 profile);
777 }
778 } else {
779
780 dbgFile <<
"Original without color transform needed";
781 needColorTransform = false;
784 iccProfile);
786 }
787 }
788
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),
806 "JPEG-XL image");
807
808 layer =
new KisPaintLayer(image, image->nextLayerName(), UCHAR_MAX);
809 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
811
812
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(),
822 reinterpret_cast<uint8_t *
>(
d.m_rawData.data()),
823 static_cast<size_t>(
d.m_rawData.size()))) {
824 qWarning() << "JxlDecoderSetImageOutBuffer failed";
826 }
827
829
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(),
842 bufferSize,
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) {
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
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
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
904
905
906
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
913
914
915
916
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
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) {
959 layerBounds.y(),
960 layerBounds.width(),
961 layerBounds.height());
962
963
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
972
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
990 const auto availOut = JxlDecoderReleaseBoxBuffer(dec.get());
991 const int finalSize = box.size() - static_cast<int>(availOut);
992
993
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
1001
1002 boxType.fill('\0');
1003 }
1004 if (status == JXL_DEC_SUCCESS) {
1005
1006
1007
1008
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)) {
1031 "iptc");
1032 iptcBackend->
loadFrom(layer->metaData(), &buf);
1033 }
1034 }
1035 }
1036
1037
1038
1039 image->addNode(layer, image->rootLayer().data());
1040
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,
1050 image->waitForDone();
1051 }
1052 dbgFile <<
"Transforming to target color space";
1053 image->convertImageColorSpace(
d.cs_target,
1056 image->waitForDone();
1057 }
1058 document->setCurrentImage(image);
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
1082 boxSize = box.size();
1083 box.resize(boxSize * 2);
1084
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
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_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
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP 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)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
@ FormatFeaturesUnsupported
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)