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.
69{
71
72 dbgFile << QString(
"libjxl version: %1.%2.%3")
73 .arg(JPEGXL_MAJOR_VERSION)
74 .arg(JPEGXL_MINOR_VERSION)
75 .arg(JPEGXL_PATCH_VERSION);
76
79
80 const bool cfgFlattenLayer = cfg->getBool("flattenLayers", true);
81 const bool cfgHaveAnimation = cfg->getBool("haveAnimation", false);
82 const bool cfgMultiLayer = cfg->getBool("multiLayer", false);
83 const bool cfgMultiPage = cfg->getBool("multiPage", false);
84
85 auto enc = JxlEncoderMake(nullptr);
86 auto runner = JxlResizableParallelRunnerMake(nullptr);
87 if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(), JxlResizableParallelRunner, runner.get())) {
88 errFile <<
"JxlEncoderSetParallelRunner failed";
90 }
91
92 JxlResizableParallelRunnerSetThreads(runner.get(),
93 JxlResizableParallelRunnerSuggestThreads(
static_cast<uint64_t
>(
bounds.width()),
static_cast<uint64_t
>(
bounds.height())));
94
97 bool convertToRec2020 = false;
98
100 const QString conversionOption = (cfg->getString("floatingPointConversionOption", "Rec2100PQ"));
101 if (conversionOption == "Rec2100PQ") {
102 convertToRec2020 = true;
104 } else if (conversionOption == "Rec2100HLG") {
105 convertToRec2020 = true;
107 } else if (conversionOption == "ApplyPQ") {
109 } else if (conversionOption == "ApplyHLG") {
111 } else if (conversionOption == "ApplySMPTE428") {
113 }
114 }
115
120 {
121 errFile <<
"Unable to find a working profile for Rec. 2020";
123 }
129
132 }
133
134 const float hlgGamma = cfg->getFloat("HLGgamma", 1.2f);
135 const float hlgNominalPeak = cfg->getFloat("HLGnominalPeak", 1000.0f);
136 const bool removeHGLOOTF = cfg->getBool("removeHGLOOTF", true);
137
140 static constexpr std::array<TransferCharacteristics, 14> supportedTRC = {
TRC_LINEAR,
154 const bool isSupportedTRC = std::find(supportedTRC.begin(), supportedTRC.end(), gamma) != supportedTRC.end();
155
156#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
158 if (JXL_ENC_SUCCESS != JxlEncoderSetOutputProcessor(enc.get(), processor.getOutputProcessor())) {
159 errFile <<
"JxlEncoderSetOutputProcessor failed";
161 }
162#endif
163
164 const JxlPixelFormat pixelFormat = [&]() {
165 JxlPixelFormat pixelFormat{};
167 pixelFormat.data_type = JXL_TYPE_UINT8;
170 pixelFormat.data_type = JXL_TYPE_UINT16;
171#ifdef HAVE_OPENEXR
173 pixelFormat.data_type = JXL_TYPE_FLOAT16;
174#endif
176 pixelFormat.data_type = JXL_TYPE_FLOAT;
177 }
179 pixelFormat.num_channels = 4;
181 pixelFormat.num_channels = 2;
183 pixelFormat.num_channels = 3;
184 }
185 return pixelFormat;
186 }();
187
188 if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc.get())) {
189 errFile <<
"JxlEncoderUseBoxes failed";
191 }
192
193 const auto basicInfo = [&]() {
194 auto info{std::make_unique<JxlBasicInfo>()};
195 JxlEncoderInitBasicInfo(info.get());
196 info->xsize =
static_cast<uint32_t
>(
bounds.width());
197 info->ysize =
static_cast<uint32_t
>(
bounds.height());
198 {
199 if (pixelFormat.data_type == JXL_TYPE_UINT8) {
200 info->bits_per_sample = 8;
201 info->exponent_bits_per_sample = 0;
202 info->alpha_bits = 8;
203 info->alpha_exponent_bits = 0;
204 } else if (pixelFormat.data_type == JXL_TYPE_UINT16) {
205 info->bits_per_sample = 16;
206 info->exponent_bits_per_sample = 0;
207 info->alpha_bits = 16;
208 info->alpha_exponent_bits = 0;
209#ifdef HAVE_OPENEXR
210 } else if (pixelFormat.data_type == JXL_TYPE_FLOAT16) {
211 info->bits_per_sample = 16;
212 info->exponent_bits_per_sample = 5;
213 info->alpha_bits = 16;
214 info->alpha_exponent_bits = 5;
215#endif
216 } else if (pixelFormat.data_type == JXL_TYPE_FLOAT) {
217 info->bits_per_sample = 32;
218 info->exponent_bits_per_sample = 8;
219 info->alpha_bits = 32;
220 info->alpha_exponent_bits = 8;
221 }
222 }
224 info->num_color_channels = 3;
225 info->num_extra_channels = 1;
227 info->num_color_channels = 1;
228 info->num_extra_channels = 1;
230 info->num_color_channels = 3;
231 info->num_extra_channels = 2;
232 }
233
235 || !isSupportedTRC) {
236 info->uses_original_profile = JXL_TRUE;
237 dbgFile <<
"JXL use original profile";
238 } else {
239 info->uses_original_profile = JXL_FALSE;
240 dbgFile <<
"JXL use internal XYB profile";
241 }
243 info->have_animation = JXL_TRUE;
244 info->animation.have_timecodes = JXL_FALSE;
245 info->animation.num_loops = 0;
246
247 info->animation.tps_numerator =
249 info->animation.tps_denominator = 1;
250 } else if (cfgMultiPage) {
251 info->have_animation = JXL_TRUE;
252 info->animation.have_timecodes = JXL_FALSE;
253 info->animation.num_loops = 0;
254 info->animation.tps_numerator = 1;
255 info->animation.tps_denominator = 1;
256 }
257 return info;
258 }();
259
260 if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), basicInfo.get())) {
261 errFile <<
"JxlEncoderSetBasicInfo failed";
263 }
264
265
267 const auto blackInfo = [&]() {
268 auto black{std::make_unique<JxlExtraChannelInfo>()};
269 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_BLACK, black.get());
270 black->bits_per_sample = basicInfo->bits_per_sample;
271 black->exponent_bits_per_sample = basicInfo->exponent_bits_per_sample;
272 return black;
273 }();
274 const auto alphaInfo = [&]() {
275 auto alpha{std::make_unique<JxlExtraChannelInfo>()};
276 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, alpha.get());
277 alpha->bits_per_sample = basicInfo->bits_per_sample;
278 alpha->exponent_bits_per_sample = basicInfo->exponent_bits_per_sample;
279 return alpha;
280 }();
281
282 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(enc.get(), 0, blackInfo.get())) {
283 errFile <<
"JxlEncoderSetBasicInfo Key failed";
285 }
286 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(enc.get(), 1, alphaInfo.get())) {
287 errFile <<
"JxlEncoderSetBasicInfo Alpha failed";
289 }
290 }
291
292 {
293 JxlColorEncoding cicpDescription{};
294
295 switch (conversionPolicy) {
297 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
298 break;
300 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
301 break;
303 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
304 break;
306 default: {
307 switch (gamma) {
309 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
310 break;
314 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_709;
315 break;
317 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
318 cicpDescription.gamma = 1.0 / 2.2;
319 break;
321 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
322 cicpDescription.gamma = 1.0 / 2.8;
323 break;
325 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
326 break;
328 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
329 break;
331 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
332 break;
334 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
335 break;
337 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
338 cicpDescription.gamma = 1.0 / 1.8;
339 break;
341 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
342 cicpDescription.gamma = 1.0 / 2.4;
343 break;
345 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
346 cicpDescription.gamma = 1.0 / 1.8;
347 break;
349 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
350 cicpDescription.gamma = 256.0 / 563.0;
351 break;
361 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
362 } else {
363 dbgFile <<
"JXL CICP cannot describe the current transfer function" << gamma
364 << ", falling back to ICC";
365 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN;
366 }
367 break;
368 }
369 } break;
370 }
371
373
375 && !cfg->getBool("forceCicpLossless"))
378
379 dbgFile <<
"Saving with ICC profile";
380
381 if (JXL_ENC_SUCCESS
382 != JxlEncoderSetICCProfile(enc.get(), reinterpret_cast<const uint8_t *>(profile.constData()), static_cast<size_t>(profile.size()))) {
383 errFile <<
"JxlEncoderSetICCProfile failed";
385 }
386 } else {
387 dbgFile <<
"Saving with CICP profile";
388
390
391
392
393
394 warnFile <<
"Using workaround for libjxl grayscale whitepoint";
395 cicpDescription.white_point = JXL_WHITE_POINT_D65;
396 cicpDescription.color_space = JXL_COLOR_SPACE_GRAY;
397 } else {
398 switch (primaries) {
400 cicpDescription.primaries = JXL_PRIMARIES_SRGB;
401 break;
403 cicpDescription.primaries = JXL_PRIMARIES_2100;
404 break;
406 cicpDescription.primaries = JXL_PRIMARIES_P3;
407 break;
408 default:
409 warnFile <<
"Writing possibly non-roundtrip primaries!";
411 cicpDescription.primaries = JXL_PRIMARIES_CUSTOM;
412 cicpDescription.primaries_red_xy[0] = colorants[0].x;
413 cicpDescription.primaries_red_xy[1] = colorants[0].y;
414 cicpDescription.primaries_green_xy[0] = colorants[1].x;
415 cicpDescription.primaries_green_xy[1] = colorants[1].y;
416 cicpDescription.primaries_blue_xy[0] = colorants[2].x;
417 cicpDescription.primaries_blue_xy[1] = colorants[2].y;
418 break;
419 }
420
421
423 cicpDescription.white_point = JXL_WHITE_POINT_CUSTOM;
424 cicpDescription.white_point_xy[0] = whitePoint.
x;
425 cicpDescription.white_point_xy[1] = whitePoint.
y;
426 }
427
428 if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &cicpDescription)) {
429 errFile <<
"JxlEncoderSetColorEncoding failed";
431 }
432 }
433 }
434
435 if (cfg->getBool("storeMetaData", false)) {
436 auto metaDataStore = [&]() -> std::unique_ptr<KisMetaData::Store> {
440 return std::make_unique<KisMetaData::Store>(*exivInfoVisitor.
exifInfo());
441 } else if (cfg->getBool("storeAuthor", true)) {
442 return std::make_unique<KisMetaData::Store>();
443 } else {
444 return {};
445 }
446 }();
447
448 if (metaDataStore && !metaDataStore->isEmpty()) {
452 }
453
456 Q_ASSERT(dcSchema);
457
458 if (cfg->getBool("storeAuthor", true)) {
459 QString author = document->documentInfo()->authorInfo("creator");
460 if (!author.isEmpty()) {
461 if (!document->documentInfo()->authorContactInfo().isEmpty()) {
462 QString contact = document->documentInfo()->authorContactInfo().at(0);
463 if (!contact.isEmpty()) {
464 author = author + "(" + contact + ")";
465 }
466 }
467 if (metaDataStore->containsEntry("creator")) {
468 metaDataStore->removeEntry("creator");
469 }
471 }
472 }
473
474 if (metaDataStore && cfg->getBool("exif", true)) {
476
477 QBuffer ioDevice;
478
479
480 io->
saveTo(metaDataStore.get(), &ioDevice);
481
482 if (JXL_ENC_SUCCESS
483 != JxlEncoderAddBox(enc.get(),
484 "Exif",
485 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
486 static_cast<size_t>(ioDevice.size()),
487 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
488 errFile <<
"JxlEncoderAddBox for EXIF failed";
490 }
491 }
492
493 if (metaDataStore && cfg->getBool("xmp", true)) {
495
496 QBuffer ioDevice;
497
498
499 io->
saveTo(metaDataStore.get(), &ioDevice);
500
501 if (JXL_ENC_SUCCESS
502 != JxlEncoderAddBox(enc.get(),
503 "xml ",
504 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
505 static_cast<size_t>(ioDevice.size()),
506 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
507 errFile <<
"JxlEncoderAddBox for XMP failed";
509 }
510 }
511
512 if (metaDataStore && cfg->getBool("iptc", true)) {
514
515 QBuffer ioDevice;
516
517
518 io->
saveTo(metaDataStore.get(), &ioDevice);
519
520 if (JXL_ENC_SUCCESS
521 != JxlEncoderAddBox(enc.get(),
522 "xml ",
523 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
524 static_cast<size_t>(ioDevice.size()),
525 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
526 errFile <<
"JxlEncoderAddBox for IPTC failed";
528 }
529 }
530 }
531
532 auto *frameSettings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
533 {
534 const auto setFrameLossless = [&](
bool v) {
535 if (JxlEncoderSetFrameLossless(frameSettings,
v ? JXL_TRUE : JXL_FALSE) != JXL_ENC_SUCCESS) {
536 errFile <<
"JxlEncoderSetFrameLossless failed";
537 return false;
538 }
539 return true;
540 };
541
542 const auto setSetting = [&](JxlEncoderFrameSettingId id,
int v) {
543
544 if (
id == JXL_ENC_FRAME_SETTING_RESAMPLING &&
v == -1)
545 return true;
546 if (JxlEncoderFrameSettingsSetOption(frameSettings,
id,
v) != JXL_ENC_SUCCESS) {
547 errFile <<
"JxlEncoderFrameSettingsSetOption failed";
548 return false;
549 }
550 return true;
551 };
552
553
554 const auto setDistance = [&](
float v) {
555 const float distance = cfg->getBool(
"lossless") ? 0.0
556 :
v >= 30 ? 0.1 + (100 -
v) * 0.09
557 : 53.0 / 3000.0 *
v *
v - 23.0 / 20.0 *
v + 25.0;
559 if (JxlEncoderSetFrameDistance(frameSettings,
distance) != JXL_ENC_SUCCESS) {
560 errFile <<
"JxlEncoderSetFrameDistance failed";
561 return false;
562 }
563#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
564
565 if (!cfg->getBool("lossless")) {
566 if (cfg->getBool("losslessAlpha")) {
567 if (JxlEncoderSetExtraChannelDistance(frameSettings,
569 0)
570 != JXL_ENC_SUCCESS) {
571 errFile <<
"JxlEncoderSetExtraChannelDistance failed";
572 return false;
573 }
574 } else {
575 if (JxlEncoderSetExtraChannelDistance(frameSettings,
578 != JXL_ENC_SUCCESS) {
579 errFile <<
"JxlEncoderSetExtraChannelDistance failed";
580 return false;
581 }
582 }
583 }
584#endif
585 return true;
586 };
587
588
589
590
591
592
593
594 const int setResponsive = [&]() -> int {
595 if (pixelFormat.data_type == JXL_TYPE_FLOAT && !cfg->getBool("lossless")) {
596 warnFile <<
"Using workaround for lossy 32-bit float, disabling progressive option";
597 return 0;
598 }
599 return cfg->getInt("responsive", -1);
600 }();
601
602
603
604
605
606 const int setPatches = [&]() -> int {
607#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
608 if ((cfg->getInt("effort", 7) > 4) && cfgFlattenLayer) {
609 warnFile <<
"Using workaround for layer exports, disabling patches option on effort > 4";
610 return 0;
611 }
612#endif
613 return cfg->getInt("patches", -1);
614 }();
615
616 if (!setFrameLossless(cfg->getBool("lossless"))
617 || !setSetting(JXL_ENC_FRAME_SETTING_EFFORT, cfg->getInt("effort", 7))
618 || !setSetting(JXL_ENC_FRAME_SETTING_DECODING_SPEED, cfg->getInt("decodingSpeed", 0))
619 || !setSetting(JXL_ENC_FRAME_SETTING_RESAMPLING, cfg->getInt("resampling", -1))
620 || !setSetting(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, cfg->getInt("extraChannelResampling", -1))
621 || !setSetting(JXL_ENC_FRAME_SETTING_DOTS, cfg->getInt("dots", -1))
622 || !setSetting(JXL_ENC_FRAME_SETTING_PATCHES, setPatches)
623 || !setSetting(JXL_ENC_FRAME_SETTING_EPF, cfg->getInt("epf", -1))
624 || !setSetting(JXL_ENC_FRAME_SETTING_GABORISH, cfg->getInt("gaborish", -1))
625 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR, cfg->getInt("modular", -1))
626 || !setSetting(JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE, cfg->getInt("keepInvisible", -1))
627 || !setSetting(JXL_ENC_FRAME_SETTING_GROUP_ORDER, cfg->getInt("groupOrder", -1))
628 || !setSetting(JXL_ENC_FRAME_SETTING_RESPONSIVE, setResponsive)
629 || !setSetting(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, cfg->getInt("progressiveAC", -1))
630 || !setSetting(JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, cfg->getInt("qProgressiveAC", -1))
631 || !setSetting(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, cfg->getInt("progressiveDC", -1))
632 || !setSetting(JXL_ENC_FRAME_SETTING_PALETTE_COLORS, cfg->getInt("paletteColors", -1))
633 || !setSetting(JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, cfg->getInt("lossyPalette", -1))
634 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, cfg->getInt("modularGroupSize", -1))
635 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, cfg->getInt("modularPredictor", -1))
636 || !setSetting(JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, cfg->getInt("jpegReconCFL", -1))
637 || !setDistance(cfg->getInt("lossyQuality", 100))) {
639 }
640 }
641
642 {
643 const auto setSettingFloat = [&](JxlEncoderFrameSettingId id,
float v) {
644 if (JxlEncoderFrameSettingsSetFloatOption(frameSettings,
id,
v) != JXL_ENC_SUCCESS) {
645 errFile <<
"JxlEncoderFrameSettingsSetFloatOption failed";
646 return false;
647 }
648 return true;
649 };
650
651 if (!setSettingFloat(JXL_ENC_FRAME_SETTING_PHOTON_NOISE, cfg->getFloat("photonNoise", 0))
652 || !setSettingFloat(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT,
653 cfg->getFloat("channelColorsGlobalPercent", -1))
654 || !setSettingFloat(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT,
655 cfg->getFloat("channelColorsGroupPercent", -1))
656 || !setSettingFloat(JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT,
657 cfg->getFloat("modularMATreeLearningPercent", -1))) {
659 }
660 }
661
662 {
663 const bool isAnimated = [&]() {
667
670 }
671 return false;
672 }();
673
674 if (isAnimated) {
675
678
682
684 const auto times = [&]() {
686 QSet<int> s = frames->allKeyframeTimes();
688 std::sort(t.begin(), t.end());
689 return t;
690 }();
691
692 auto frameHeader = []() {
693 auto header = std::make_unique<JxlFrameHeader>();
694 JxlEncoderInitFrameHeader(header.get());
695 return header;
696 }();
697
698 int frameNum = 0;
699 for (const auto i : times) {
700 frameHeader->duration = [&]() {
701 const auto nextKeyframe = frames->nextKeyframeTime(i);
702 if (nextKeyframe == -1) {
703 return static_cast<uint32_t>(
705 - i + 1);
706 } else {
707 return static_cast<uint32_t>(frames->nextKeyframeTime(i) - i);
708 }
709 }();
710 frameHeader->is_last = 0;
711
712 if (JxlEncoderSetFrameHeader(frameSettings, frameHeader.get()) != JXL_ENC_SUCCESS) {
713 errFile <<
"JxlEncoderSetFrameHeader failed";
715 }
716
717 const QByteArray pixels = [&]() {
721 frameData->writeFrameToDevice(dev);
722
724
726
729 dev->readBytes(
reinterpret_cast<quint8 *
>(
p.data()),
bounds);
731 } else {
733
734
735
737 convertToRec2020,
739 conversionPolicy,
740 removeHGLOOTF,
743 it,
744 hlgGamma,
745 hlgNominalPeak,
746 cs);
747 }
748 }();
749
750 if (JxlEncoderAddImageFrame(frameSettings,
751 &pixelFormat,
752 pixels.data(),
753 static_cast<size_t>(pixels.size()))
754 != JXL_ENC_SUCCESS) {
755 errFile <<
"JxlEncoderAddImageFrame @" << i <<
"failed";
757 }
758
759#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
763 }
764 const int progress = (frameNum * 100) / times.size();
766 frameNum++;
767 if (frames->nextKeyframeTime(i) == -1) {
768 JxlEncoderCloseInput(enc.get());
769 }
770 if (JxlEncoderFlushInput(enc.get()) != JXL_ENC_SUCCESS) {
771 errFile <<
"JxlEncoderFlushInput failed";
773 }
774#endif
775 }
776 } else {
777 auto frameHeader = std::make_unique<JxlFrameHeader>();
778
779
780
781 quint32 lastValidLayer = 0;
782 if (cfgMultiLayer || cfgMultiPage) {
783 for (quint32 pos = 0; pos < image->
root()->childCount(); pos++) {
785 KisLayer *layer = qobject_cast<KisLayer *>(node.
data());
786 if (layer && (layer->inherits(
"KisGroupLayer") || layer->
childCount() > 0 || layer->
layerStyle())
790 }
792 lastValidLayer = pos;
793 }
794 }
796 }
797
798
799 for (quint32 pos = 0; pos < image->
root()->childCount(); pos++) {
801
802 if ((cfgMultiLayer || cfgMultiPage) && (!node || !node->
visible() || node->
isFakeNode())) {
803 dbgFile <<
"Skipping hidden layer" << node->
name();
804 continue;
805 }
807
808 if (cfgMultiLayer || cfgMultiPage) {
810
811 if (!node->inherits("KisPaintLayer")) {
813 node = convertedNode.get();
814 }
815
817 if (lcs && (lcs != cs)) {
819
820 }
821 } else {
822 dbgFile <<
"Saving flattened image";
823 }
824
825 const QRect layerBounds = [&]() {
826 if (node->
exactBounds().isEmpty() || cfgFlattenLayer) {
828 }
830 }();
831
833 if (cfgFlattenLayer) {
835 } else {
837 }
838
840
846 f->process(dev, layerBounds, kfc->cloneWithResourcesSnapshot());
847 }
848
849 const QByteArray pixels = [&]() {
852
856
860
861
863 true,
864 0,
865 layerBounds.width(),
866 layerBounds.height(),
867 it);
868 }
869
871 p.resize(layerBounds.width() * layerBounds.height() *
static_cast<int>(cs->
pixelSize()));
872 dev->
readBytes(
reinterpret_cast<quint8 *
>(
p.data()), layerBounds);
874 } else {
877
878
879
881 convertToRec2020,
883 conversionPolicy,
884 removeHGLOOTF,
885 layerBounds.width(),
886 layerBounds.height(),
887 it,
888 hlgGamma,
889 hlgNominalPeak,
890 cs);
891 }
892 }();
893
894 if (cfgMultiLayer || cfgMultiPage) {
895 JxlEncoderInitFrameHeader(frameHeader.get());
896
897
898 if (cfgMultiLayer) {
899 frameHeader->duration = 0;
900 } else if (cfgMultiPage) {
901 frameHeader->duration = 0xFFFFFFFF;
902 }
903
904
905
907 frameHeader->layer_info.have_crop = false;
908 } else {
909 frameHeader->layer_info.have_crop = true;
910 }
911 frameHeader->layer_info.crop_x0 = layerBounds.x();
912 frameHeader->layer_info.crop_y0 = layerBounds.y();
913 frameHeader->layer_info.xsize = layerBounds.width();
914 frameHeader->layer_info.ysize = layerBounds.height();
915
917 frameHeader->layer_info.blend_info.alpha = 1;
918 } else {
919 frameHeader->layer_info.blend_info.alpha = 0;
920 }
921
922
923
924 const QString frameName = node->
name();
925 if (!isFirstLayer && cfgMultiLayer) {
927 frameHeader->layer_info.blend_info.blendmode = JXL_BLEND_MULADD;
928 } else {
929 frameHeader->layer_info.blend_info.blendmode = JXL_BLEND_BLEND;
930 }
931 }
932
933 if (JxlEncoderSetFrameHeader(frameSettings, frameHeader.get()) != JXL_ENC_SUCCESS) {
934 errFile <<
"JxlEncoderSetFrameHeader failed";
936 }
937 if (JxlEncoderSetFrameName(frameSettings, frameName.toLocal8Bit()) != JXL_ENC_SUCCESS) {
938 errFile <<
"JxlEncoderSetFrameName failed";
940 }
941 }
942
943 if (JxlEncoderAddImageFrame(frameSettings,
944 &pixelFormat,
945 pixels.data(),
946 static_cast<size_t>(pixels.size()))
947 != JXL_ENC_SUCCESS) {
948 errFile <<
"JxlEncoderAddImageFrame failed";
950 }
951
952
956
958 false,
959 3,
960 layerBounds.width(),
961 layerBounds.height(),
962 it);
965 false,
966 4,
967 layerBounds.width(),
968 layerBounds.height(),
969 it);
970
971 if (JxlEncoderSetExtraChannelBuffer(frameSettings,
972 &pixelFormat,
973 chaK,
974 static_cast<size_t>(chaK.size()),
975 0)
976 != JXL_ENC_SUCCESS) {
977 errFile <<
"JxlEncoderSetExtraChannelBuffer Key failed";
979 }
980 if (JxlEncoderSetExtraChannelBuffer(frameSettings,
981 &pixelFormat,
982 chaA,
983 static_cast<size_t>(chaA.size()),
984 1)
985 != JXL_ENC_SUCCESS) {
986 errFile <<
"JxlEncoderSetExtraChannelBuffer Alpha failed";
988 }
989 }
990
991#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
995 }
998 if ((pos == lastValidLayer && (cfgMultiLayer || cfgMultiPage)) || cfgFlattenLayer) {
999 JxlEncoderCloseInput(enc.get());
1000 }
1001 if (JxlEncoderFlushInput(enc.get()) != JXL_ENC_SUCCESS) {
1002 errFile <<
"JxlEncoderFlushInput failed";
1004 }
1005 if (cfgFlattenLayer) {
1006 break;
1007 }
1008 }
1009 }
1010#else
1011
1012 if (cfgFlattenLayer) {
1013 break;
1014 }
1015 }
1016 }
1017 JxlEncoderCloseInput(enc.get());
1018
1019 QByteArray compressed(16384, 0x0);
1020 auto *nextOut = reinterpret_cast<uint8_t *>(compressed.data());
1021 auto availOut = static_cast<size_t>(compressed.size());
1022 auto result = JXL_ENC_NEED_MORE_OUTPUT;
1023 while (result == JXL_ENC_NEED_MORE_OUTPUT) {
1024 result = JxlEncoderProcessOutput(enc.get(), &nextOut, &availOut);
1025 if (result != JXL_ENC_ERROR) {
1026 io->write(compressed.data(), compressed.size() - static_cast<int>(availOut));
1027 }
1028 if (result == JXL_ENC_NEED_MORE_OUTPUT) {
1029 compressed.resize(compressed.size() * 2);
1030 nextOut = reinterpret_cast<uint8_t *>(compressed.data());
1031 availOut = static_cast<size_t>(compressed.size());
1032 }
1033 }
1034 if (JXL_ENC_SUCCESS != result) {
1035 errFile <<
"JxlEncoderProcessOutput failed";
1037 }
1038#endif
1039 }
1040
1042}
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_2020_2_10bit
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_LOGARITHMIC_100_sqrt10
@ TRC_ITU_R_BT_2020_2_12bit
qreal distance(const QPointF &p1, const QPointF &p2)
The KisExifInfoVisitor class looks for a layer with metadata.
KisMetaData::Store * exifInfo()
bool visit(KisNode *) override
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP instance()
virtual void resetRowPos()=0
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
bool hasAnimation() const
KisGroupLayerSP rootLayer() const
const KoColorSpace * colorSpace() const
KisImageAnimationInterface * animationInterface() const
void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
KisPaintDeviceSP projection() const
QRect bounds() const override
QPointer< KoUpdater > updater
void setProgress(int value)
KisRasterKeyframeChannel * keyframeChannel() const
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
The KisRasterKeyframe class is a concrete subclass of KisKeyframe that wraps a physical raster image ...
virtual quint32 pixelSize() const =0
virtual bool hasHighDynamicRange() const =0
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
const T value(const QString &id) const
#define KIS_ASSERT_RECOVER(cond)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
QByteArray writeLayer(const int width, const int height, KisHLineConstIteratorSP it, float hlgGamma, float hlgNominalPeak, const KoColorSpace *cs)
@ FormatColorSpaceUnsupported
void flattenImage(KisImageSP image, KisNodeSP activeNode, MergeFlags flags)
void flattenLayer(KisImageSP image, KisLayerSP layer, MergeFlags flags)
std::future< KisNodeSP > convertToPaintLayer(KisImageSP image, KisNodeSP src)
virtual KisPaintDeviceSP projection() const =0
const QString & compositeOpId() const
virtual QRect exactBounds() const
virtual const KoColorSpace * colorSpace() const =0
virtual KisPaintDeviceSP paintDevice() const =0
virtual bool isFakeNode() const
virtual bool visible(bool recursive=false) const
bool hasEditablePaintDevice() const
KisPSDLayerStyleSP layerStyle
KisNodeSP firstChild() const
quint32 childCount() const
KisNodeSP at(quint32 index) const
The KoColorProfileQuery struct.
virtual QByteArray rawData() const
virtual KoColorimetryUtils::xyY getWhitePointxyY() const =0
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual bool hasColorants() const =0
virtual bool isLinear() const =0
virtual QVector< KoColorimetryUtils::xyY > getColorantsxyY() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
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,...