Krita Source Code Documentation
Loading...
Searching...
No Matches
JPEGXLExport.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2021 the JPEG XL Project Authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *
5 * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "JPEGXLExport.h"
10
12
13#include <jxl/version.h>
14#include <jxl/color_encoding.h>
15#include <jxl/encode_cxx.h>
16#include <jxl/resizable_parallel_runner_cxx.h>
17#include <kpluginfactory.h>
18
19#include <QBuffer>
20#include <algorithm>
21#include <array>
22#include <cstdint>
23#include <cstring>
24
25#include <KisDocument.h>
28#include <KoAlwaysInline.h>
30#include <KoColorProfile.h>
31#include <KoColorSpace.h>
33#include <KoConfig.h>
34#include <KoDocumentInfo.h>
35#include <KoProperties.h>
36#include <KoUpdater.h>
37#include <filter/kis_filter.h>
40#include <kis_assert.h>
41#include <kis_debug.h>
44#include <kis_iterator_ng.h>
45#include <kis_layer.h>
46#include <kis_layer_utils.h>
48#include <kis_meta_data_entry.h>
52#include <kis_meta_data_store.h>
53#include <kis_meta_data_value.h>
55#include <kis_time_span.h>
56
59
60K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_jxl_export.json", registerPlugin<JPEGXLExport>();)
61
62JPEGXLExport::JPEGXLExport(QObject *parent, const QVariantList &)
63 : KisImportExportFilter(parent)
64{
65}
66
68{
70
71 dbgFile << QString("libjxl version: %1.%2.%3")
72 .arg(JPEGXL_MAJOR_VERSION)
73 .arg(JPEGXL_MINOR_VERSION)
74 .arg(JPEGXL_PATCH_VERSION);
75
76 KisImageSP image = document->savingImage();
77 const QRect bounds = image->bounds();
78
79 const bool cfgFlattenLayer = cfg->getBool("flattenLayers", true);
80 const bool cfgHaveAnimation = cfg->getBool("haveAnimation", false);
81 const bool cfgMultiLayer = cfg->getBool("multiLayer", false);
82 const bool cfgMultiPage = cfg->getBool("multiPage", false);
83
84 auto enc = JxlEncoderMake(nullptr);
85 auto runner = JxlResizableParallelRunnerMake(nullptr);
86 if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(), JxlResizableParallelRunner, runner.get())) {
87 errFile << "JxlEncoderSetParallelRunner failed";
89 }
90
91 JxlResizableParallelRunnerSetThreads(runner.get(),
92 JxlResizableParallelRunnerSuggestThreads(static_cast<uint64_t>(bounds.width()), static_cast<uint64_t>(bounds.height())));
93
94 const KoColorSpace *cs = image->colorSpace();
96 bool convertToRec2020 = false;
97
99 const QString conversionOption = (cfg->getString("floatingPointConversionOption", "Rec2100PQ"));
100 if (conversionOption == "Rec2100PQ") {
101 convertToRec2020 = true;
102 conversionPolicy = ConversionPolicy::ApplyPQ;
103 } else if (conversionOption == "Rec2100HLG") {
104 convertToRec2020 = true;
105 conversionPolicy = ConversionPolicy::ApplyHLG;
106 } else if (conversionOption == "ApplyPQ") {
107 conversionPolicy = ConversionPolicy::ApplyPQ;
108 } else if (conversionOption == "ApplyHLG") {
109 conversionPolicy = ConversionPolicy::ApplyHLG;
110 } else if (conversionOption == "ApplySMPTE428") {
111 conversionPolicy = ConversionPolicy::ApplySMPTE428;
112 }
113 }
114
115 if (cs->hasHighDynamicRange() && convertToRec2020) {
116 const KoColorProfile *linear =
118 KIS_ASSERT_RECOVER(linear)
119 {
120 errFile << "Unable to find a working profile for Rec. 2020";
122 }
123 const KoColorSpace *linearRec2020 =
125 image->convertImageColorSpace(linearRec2020,
128
129 image->waitForDone();
130 cs = image->colorSpace();
131 }
132
133 const float hlgGamma = cfg->getFloat("HLGgamma", 1.2f);
134 const float hlgNominalPeak = cfg->getFloat("HLGnominalPeak", 1000.0f);
135 const bool removeHGLOOTF = cfg->getBool("removeHGLOOTF", true);
136
137 const bool hasPrimaries = cs->profile()->hasColorants();
139 static constexpr std::array<TransferCharacteristics, 14> supportedTRC = {TRC_LINEAR,
152 TRC_A98};
153 const bool isSupportedTRC = std::find(supportedTRC.begin(), supportedTRC.end(), gamma) != supportedTRC.end();
154
155#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
156 JXLExpTool::JxlOutputProcessor processor(io);
157 if (JXL_ENC_SUCCESS != JxlEncoderSetOutputProcessor(enc.get(), processor.getOutputProcessor())) {
158 errFile << "JxlEncoderSetOutputProcessor failed";
160 }
161#endif
162
163 const JxlPixelFormat pixelFormat = [&]() {
164 JxlPixelFormat pixelFormat{};
166 pixelFormat.data_type = JXL_TYPE_UINT8;
167 } else if (conversionPolicy != ConversionPolicy::KeepTheSame
169 pixelFormat.data_type = JXL_TYPE_UINT16;
170#ifdef HAVE_OPENEXR
171 } else if (cs->colorDepthId() == Float16BitsColorDepthID) {
172 pixelFormat.data_type = JXL_TYPE_FLOAT16;
173#endif
174 } else if (cs->colorDepthId() == Float32BitsColorDepthID) {
175 pixelFormat.data_type = JXL_TYPE_FLOAT;
176 }
177 if (cs->colorModelId() == RGBAColorModelID) {
178 pixelFormat.num_channels = 4;
179 } else if (cs->colorModelId() == GrayAColorModelID) {
180 pixelFormat.num_channels = 2;
181 } else if (cs->colorModelId() == CMYKAColorModelID) {
182 pixelFormat.num_channels = 3;
183 }
184 return pixelFormat;
185 }();
186
187 if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc.get())) {
188 errFile << "JxlEncoderUseBoxes failed";
190 }
191
192 const auto basicInfo = [&]() {
193 auto info{std::make_unique<JxlBasicInfo>()};
194 JxlEncoderInitBasicInfo(info.get());
195 info->xsize = static_cast<uint32_t>(bounds.width());
196 info->ysize = static_cast<uint32_t>(bounds.height());
197 {
198 if (pixelFormat.data_type == JXL_TYPE_UINT8) {
199 info->bits_per_sample = 8;
200 info->exponent_bits_per_sample = 0;
201 info->alpha_bits = 8;
202 info->alpha_exponent_bits = 0;
203 } else if (pixelFormat.data_type == JXL_TYPE_UINT16) {
204 info->bits_per_sample = 16;
205 info->exponent_bits_per_sample = 0;
206 info->alpha_bits = 16;
207 info->alpha_exponent_bits = 0;
208#ifdef HAVE_OPENEXR
209 } else if (pixelFormat.data_type == JXL_TYPE_FLOAT16) {
210 info->bits_per_sample = 16;
211 info->exponent_bits_per_sample = 5;
212 info->alpha_bits = 16;
213 info->alpha_exponent_bits = 5;
214#endif
215 } else if (pixelFormat.data_type == JXL_TYPE_FLOAT) {
216 info->bits_per_sample = 32;
217 info->exponent_bits_per_sample = 8;
218 info->alpha_bits = 32;
219 info->alpha_exponent_bits = 8;
220 }
221 }
222 if (cs->colorModelId() == RGBAColorModelID) {
223 info->num_color_channels = 3;
224 info->num_extra_channels = 1;
225 } else if (cs->colorModelId() == GrayAColorModelID) {
226 info->num_color_channels = 1;
227 info->num_extra_channels = 1;
228 } else if (cs->colorModelId() == CMYKAColorModelID) {
229 info->num_color_channels = 3;
230 info->num_extra_channels = 2;
231 }
232 // Use original profile on lossless, non-matrix profile or unsupported transfer curve.
233 if (cfg->getBool("lossless") || (!hasPrimaries && !(cs->colorModelId() == GrayAColorModelID))
234 || !isSupportedTRC) {
235 info->uses_original_profile = JXL_TRUE;
236 dbgFile << "JXL use original profile";
237 } else {
238 info->uses_original_profile = JXL_FALSE;
239 dbgFile << "JXL use internal XYB profile";
240 }
241 if (image->animationInterface()->hasAnimation() && cfgHaveAnimation) {
242 info->have_animation = JXL_TRUE;
243 info->animation.have_timecodes = JXL_FALSE;
244 info->animation.num_loops = 0;
245 // Unlike WebP, JXL does allow for setting proper frame rates.
246 info->animation.tps_numerator =
247 static_cast<uint32_t>(image->animationInterface()->framerate());
248 info->animation.tps_denominator = 1;
249 } else if (cfgMultiPage) {
250 info->have_animation = JXL_TRUE;
251 info->animation.have_timecodes = JXL_FALSE;
252 info->animation.num_loops = 0;
253 info->animation.tps_numerator = 1;
254 info->animation.tps_denominator = 1;
255 }
256 return info;
257 }();
258
259 if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), basicInfo.get())) {
260 errFile << "JxlEncoderSetBasicInfo failed";
262 }
263
264 // CMYKA extra channel info
265 if (cs->colorModelId() == CMYKAColorModelID) {
266 const auto blackInfo = [&]() {
267 auto black{std::make_unique<JxlExtraChannelInfo>()};
268 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_BLACK, black.get());
269 black->bits_per_sample = basicInfo->bits_per_sample;
270 black->exponent_bits_per_sample = basicInfo->exponent_bits_per_sample;
271 return black;
272 }();
273 const auto alphaInfo = [&]() {
274 auto alpha{std::make_unique<JxlExtraChannelInfo>()};
275 JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, alpha.get());
276 alpha->bits_per_sample = basicInfo->bits_per_sample;
277 alpha->exponent_bits_per_sample = basicInfo->exponent_bits_per_sample;
278 return alpha;
279 }();
280
281 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(enc.get(), 0, blackInfo.get())) {
282 errFile << "JxlEncoderSetBasicInfo Key failed";
284 }
285 if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(enc.get(), 1, alphaInfo.get())) {
286 errFile << "JxlEncoderSetBasicInfo Alpha failed";
288 }
289 }
290
291 {
292 JxlColorEncoding cicpDescription{};
293
294 switch (conversionPolicy) {
296 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
297 break;
299 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
300 break;
302 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
303 break;
305 default: {
306 switch (gamma) {
307 case TRC_LINEAR:
308 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
309 break;
313 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_709;
314 break;
316 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
317 cicpDescription.gamma = 1.0 / 2.2;
318 break;
320 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
321 cicpDescription.gamma = 1.0 / 2.8;
322 break;
324 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
325 break;
327 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
328 break;
330 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_DCI;
331 break;
333 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_HLG;
334 break;
335 case TRC_GAMMA_1_8:
336 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
337 cicpDescription.gamma = 1.0 / 1.8;
338 break;
339 case TRC_GAMMA_2_4:
340 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
341 cicpDescription.gamma = 1.0 / 2.4;
342 break;
343 case TRC_PROPHOTO:
344 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
345 cicpDescription.gamma = 1.0 / 1.8;
346 break;
347 case TRC_A98:
348 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
349 cicpDescription.gamma = 256.0 / 563.0;
350 break;
353 case TRC_SMPTE_240M:
357 case TRC_LAB_L:
358 case TRC_UNSPECIFIED:
359 if (cs->profile()->isLinear()) {
360 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
361 } else {
362 dbgFile << "JXL CICP cannot describe the current transfer function" << gamma
363 << ", falling back to ICC";
364 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_UNKNOWN;
365 }
366 break;
367 }
368 } break;
369 }
370
371 const ColorPrimaries primaries = cs->profile()->getColorPrimaries();
372
373 if ((cfg->getBool("lossless") && conversionPolicy == ConversionPolicy::KeepTheSame
374 && !cfg->getBool("forceCicpLossless"))
375 || (!hasPrimaries && !(cs->colorModelId() == GrayAColorModelID)) || !isSupportedTRC) {
376 const QByteArray profile = cs->profile()->rawData();
377
378 dbgFile << "Saving with ICC profile";
379
380 if (JXL_ENC_SUCCESS
381 != JxlEncoderSetICCProfile(enc.get(), reinterpret_cast<const uint8_t *>(profile.constData()), static_cast<size_t>(profile.size()))) {
382 errFile << "JxlEncoderSetICCProfile failed";
384 }
385 } else {
386 dbgFile << "Saving with CICP profile";
387
388 if (cs->colorModelId() == GrayAColorModelID) {
389 // XXX: JXL can't parse custom white point for grayscale (yet) and returned as linear on roundtrip so
390 // let's use default D65 as whitepoint instead...
391 //
392 // See: https://github.com/libjxl/libjxl/issues/1933
393 warnFile << "Using workaround for libjxl grayscale whitepoint";
394 cicpDescription.white_point = JXL_WHITE_POINT_D65;
395 cicpDescription.color_space = JXL_COLOR_SPACE_GRAY;
396 } else {
397 switch (primaries) {
399 cicpDescription.primaries = JXL_PRIMARIES_SRGB;
400 break;
402 cicpDescription.primaries = JXL_PRIMARIES_2100;
403 break;
405 cicpDescription.primaries = JXL_PRIMARIES_P3;
406 break;
407 default:
408 warnFile << "Writing possibly non-roundtrip primaries!";
409 const QVector<qreal> colorants = cs->profile()->getColorantsxyY();
410 cicpDescription.primaries = JXL_PRIMARIES_CUSTOM;
411 cicpDescription.primaries_red_xy[0] = colorants[0];
412 cicpDescription.primaries_red_xy[1] = colorants[1];
413 cicpDescription.primaries_green_xy[0] = colorants[3];
414 cicpDescription.primaries_green_xy[1] = colorants[4];
415 cicpDescription.primaries_blue_xy[0] = colorants[6];
416 cicpDescription.primaries_blue_xy[1] = colorants[7];
417 break;
418 }
419
420 // Unfortunately, Wolthera never wrote an enum for white points...
421 const QVector<qreal> whitePoint = image->colorSpace()->profile()->getWhitePointxyY();
422 cicpDescription.white_point = JXL_WHITE_POINT_CUSTOM;
423 cicpDescription.white_point_xy[0] = whitePoint[0];
424 cicpDescription.white_point_xy[1] = whitePoint[1];
425 }
426
427 if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &cicpDescription)) {
428 errFile << "JxlEncoderSetColorEncoding failed";
430 }
431 }
432 }
433
434 if (cfg->getBool("storeMetaData", false)) {
435 auto metaDataStore = [&]() -> std::unique_ptr<KisMetaData::Store> {
436 KisExifInfoVisitor exivInfoVisitor;
437 exivInfoVisitor.visit(image->rootLayer().data());
438 if (exivInfoVisitor.metaDataCount() == 1) {
439 return std::make_unique<KisMetaData::Store>(*exivInfoVisitor.exifInfo());
440 } else if (cfg->getBool("storeAuthor", true)) {
441 return std::make_unique<KisMetaData::Store>();
442 } else {
443 return {};
444 }
445 }();
446
447 if (metaDataStore && !metaDataStore->isEmpty()) {
449 model.setEnabledFilters(cfg->getString("filters").split(","));
450 metaDataStore->applyFilters(model.enabledFilters());
451 }
452
453 const KisMetaData::Schema *dcSchema =
455 Q_ASSERT(dcSchema);
456
457 if (cfg->getBool("storeAuthor", true)) {
458 QString author = document->documentInfo()->authorInfo("creator");
459 if (!author.isEmpty()) {
460 if (!document->documentInfo()->authorContactInfo().isEmpty()) {
461 QString contact = document->documentInfo()->authorContactInfo().at(0);
462 if (!contact.isEmpty()) {
463 author = author + "(" + contact + ")";
464 }
465 }
466 if (metaDataStore->containsEntry("creator")) {
467 metaDataStore->removeEntry("creator");
468 }
469 metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author))));
470 }
471 }
472
473 if (metaDataStore && cfg->getBool("exif", true)) {
475
476 QBuffer ioDevice;
477
478 // Inject the data as any other IOBackend
479 io->saveTo(metaDataStore.get(), &ioDevice);
480
481 if (JXL_ENC_SUCCESS
482 != JxlEncoderAddBox(enc.get(),
483 "Exif",
484 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
485 static_cast<size_t>(ioDevice.size()),
486 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
487 errFile << "JxlEncoderAddBox for EXIF failed";
489 }
490 }
491
492 if (metaDataStore && cfg->getBool("xmp", true)) {
494
495 QBuffer ioDevice;
496
497 // Inject the data as any other IOBackend
498 io->saveTo(metaDataStore.get(), &ioDevice);
499
500 if (JXL_ENC_SUCCESS
501 != JxlEncoderAddBox(enc.get(),
502 "xml ",
503 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
504 static_cast<size_t>(ioDevice.size()),
505 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
506 errFile << "JxlEncoderAddBox for XMP failed";
508 }
509 }
510
511 if (metaDataStore && cfg->getBool("iptc", true)) {
513
514 QBuffer ioDevice;
515
516 // Inject the data as any other IOBackend
517 io->saveTo(metaDataStore.get(), &ioDevice);
518
519 if (JXL_ENC_SUCCESS
520 != JxlEncoderAddBox(enc.get(),
521 "xml ",
522 reinterpret_cast<const uint8_t *>(ioDevice.data().constData()),
523 static_cast<size_t>(ioDevice.size()),
524 cfg->getBool("lossless") ? JXL_FALSE : JXL_TRUE)) {
525 errFile << "JxlEncoderAddBox for IPTC failed";
527 }
528 }
529 }
530
531 auto *frameSettings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
532 {
533 const auto setFrameLossless = [&](bool v) {
534 if (JxlEncoderSetFrameLossless(frameSettings, v ? JXL_TRUE : JXL_FALSE) != JXL_ENC_SUCCESS) {
535 errFile << "JxlEncoderSetFrameLossless failed";
536 return false;
537 }
538 return true;
539 };
540
541 const auto setSetting = [&](JxlEncoderFrameSettingId id, int v) {
542 // https://github.com/libjxl/libjxl/issues/1210
543 if (id == JXL_ENC_FRAME_SETTING_RESAMPLING && v == -1)
544 return true;
545 if (JxlEncoderFrameSettingsSetOption(frameSettings, id, v) != JXL_ENC_SUCCESS) {
546 errFile << "JxlEncoderFrameSettingsSetOption failed";
547 return false;
548 }
549 return true;
550 };
551
552 // Using cjxl quality mapping that translates from arbitrary quality value to JPEG-XL distance
553 const auto setDistance = [&](float v) {
554 const float distance = cfg->getBool("lossless") ? 0.0
555 : v >= 30 ? 0.1 + (100 - v) * 0.09
556 : 53.0 / 3000.0 * v * v - 23.0 / 20.0 * v + 25.0;
557 dbgFile << "libjxl distance equivalent: " << distance;
558 if (JxlEncoderSetFrameDistance(frameSettings, distance) != JXL_ENC_SUCCESS) {
559 errFile << "JxlEncoderSetFrameDistance failed";
560 return false;
561 }
562#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
563 // Lossless alpha (extra channel), only available on libjxl 0.9.0+
564 if (!cfg->getBool("lossless")) {
565 if (cfg->getBool("losslessAlpha")) {
566 if (JxlEncoderSetExtraChannelDistance(frameSettings,
567 (cs->colorModelId() == CMYKAColorModelID) ? 1 : 0,
568 0)
569 != JXL_ENC_SUCCESS) {
570 errFile << "JxlEncoderSetExtraChannelDistance failed";
571 return false;
572 }
573 } else {
574 if (JxlEncoderSetExtraChannelDistance(frameSettings,
575 (cs->colorModelId() == CMYKAColorModelID) ? 1 : 0,
576 distance)
577 != JXL_ENC_SUCCESS) {
578 errFile << "JxlEncoderSetExtraChannelDistance failed";
579 return false;
580 }
581 }
582 }
583#endif
584 return true;
585 };
586
587 // XXX: Workaround for a buggy lossy F32.
588 //
589 // See: https://github.com/libjxl/libjxl/issues/2064
590 //
591 // Update: It's not the modular mode that caused the bug, but the progressive/responsive setting
592 // that didn't work well with F32. So let's disable it on F32 instead.
593 const int setResponsive = [&]() -> int {
594 if (pixelFormat.data_type == JXL_TYPE_FLOAT && !cfg->getBool("lossless")) {
595 warnFile << "Using workaround for lossy 32-bit float, disabling progressive option";
596 return 0;
597 }
598 return cfg->getInt("responsive", -1);
599 }();
600
601 // XXX: Workaround for a buggy lossless patches. Set to disable instead.
602 // Patch only for libjxl under v0.9.0
603 //
604 // See: https://github.com/libjxl/libjxl/issues/2463
605 const int setPatches = [&]() -> int {
606#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
607 if ((cfg->getInt("effort", 7) > 4) && cfgFlattenLayer) {
608 warnFile << "Using workaround for layer exports, disabling patches option on effort > 4";
609 return 0;
610 }
611#endif
612 return cfg->getInt("patches", -1);
613 }();
614
615 if (!setFrameLossless(cfg->getBool("lossless"))
616 || !setSetting(JXL_ENC_FRAME_SETTING_EFFORT, cfg->getInt("effort", 7))
617 || !setSetting(JXL_ENC_FRAME_SETTING_DECODING_SPEED, cfg->getInt("decodingSpeed", 0))
618 || !setSetting(JXL_ENC_FRAME_SETTING_RESAMPLING, cfg->getInt("resampling", -1))
619 || !setSetting(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, cfg->getInt("extraChannelResampling", -1))
620 || !setSetting(JXL_ENC_FRAME_SETTING_DOTS, cfg->getInt("dots", -1))
621 || !setSetting(JXL_ENC_FRAME_SETTING_PATCHES, setPatches)
622 || !setSetting(JXL_ENC_FRAME_SETTING_EPF, cfg->getInt("epf", -1))
623 || !setSetting(JXL_ENC_FRAME_SETTING_GABORISH, cfg->getInt("gaborish", -1))
624 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR, cfg->getInt("modular", -1))
625 || !setSetting(JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE, cfg->getInt("keepInvisible", -1))
626 || !setSetting(JXL_ENC_FRAME_SETTING_GROUP_ORDER, cfg->getInt("groupOrder", -1))
627 || !setSetting(JXL_ENC_FRAME_SETTING_RESPONSIVE, setResponsive)
628 || !setSetting(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, cfg->getInt("progressiveAC", -1))
629 || !setSetting(JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, cfg->getInt("qProgressiveAC", -1))
630 || !setSetting(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, cfg->getInt("progressiveDC", -1))
631 || !setSetting(JXL_ENC_FRAME_SETTING_PALETTE_COLORS, cfg->getInt("paletteColors", -1))
632 || !setSetting(JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, cfg->getInt("lossyPalette", -1))
633 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, cfg->getInt("modularGroupSize", -1))
634 || !setSetting(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, cfg->getInt("modularPredictor", -1))
635 || !setSetting(JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, cfg->getInt("jpegReconCFL", -1))
636 || !setDistance(cfg->getInt("lossyQuality", 100))) {
638 }
639 }
640
641 {
642 const auto setSettingFloat = [&](JxlEncoderFrameSettingId id, float v) {
643 if (JxlEncoderFrameSettingsSetFloatOption(frameSettings, id, v) != JXL_ENC_SUCCESS) {
644 errFile << "JxlEncoderFrameSettingsSetFloatOption failed";
645 return false;
646 }
647 return true;
648 };
649
650 if (!setSettingFloat(JXL_ENC_FRAME_SETTING_PHOTON_NOISE, cfg->getFloat("photonNoise", 0))
651 || !setSettingFloat(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT,
652 cfg->getFloat("channelColorsGlobalPercent", -1))
653 || !setSettingFloat(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT,
654 cfg->getFloat("channelColorsGroupPercent", -1))
655 || !setSettingFloat(JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT,
656 cfg->getFloat("modularMATreeLearningPercent", -1))) {
658 }
659 }
660
661 {
662 const bool isAnimated = [&]() {
663 if (image->animationInterface()->hasAnimation() && cfgHaveAnimation) {
664 KisLayerUtils::flattenImage(image, nullptr);
665 image->waitForDone();
666
667 const KisNodeSP projection = image->rootLayer()->firstChild();
668 return projection->isAnimated() && projection->hasEditablePaintDevice();
669 }
670 return false;
671 }();
672
673 if (isAnimated) {
674 // Flatten the image, projections don't have keyframes.
675 KisLayerUtils::flattenImage(image, nullptr);
676 image->waitForDone();
677
678 const KisNodeSP projection = image->rootLayer()->firstChild();
679 KIS_ASSERT(projection->isAnimated());
680 KIS_ASSERT(projection->hasEditablePaintDevice());
681
682 const auto *frames = projection->paintDevice()->keyframeChannel();
683 const auto times = [&]() {
684 QList<int> t;
685 QSet<int> s = frames->allKeyframeTimes();
686 t = QList<int>(s.begin(), s.end());
687 std::sort(t.begin(), t.end());
688 return t;
689 }();
690
691 auto frameHeader = []() {
692 auto header = std::make_unique<JxlFrameHeader>();
693 JxlEncoderInitFrameHeader(header.get());
694 return header;
695 }();
696
697 int frameNum = 0;
698 for (const auto i : times) {
699 frameHeader->duration = [&]() {
700 const auto nextKeyframe = frames->nextKeyframeTime(i);
701 if (nextKeyframe == -1) {
702 return static_cast<uint32_t>(
704 - i + 1);
705 } else {
706 return static_cast<uint32_t>(frames->nextKeyframeTime(i) - i);
707 }
708 }();
709 frameHeader->is_last = 0;
710
711 if (JxlEncoderSetFrameHeader(frameSettings, frameHeader.get()) != JXL_ENC_SUCCESS) {
712 errFile << "JxlEncoderSetFrameHeader failed";
714 }
715
716 const QByteArray pixels = [&]() {
717 const auto frameData = frames->keyframeAt<KisRasterKeyframe>(i);
718 KisPaintDeviceSP dev =
720 frameData->writeFrameToDevice(dev);
721
722 const KoID colorModel = cs->colorModelId();
723
724 if (colorModel != RGBAColorModelID) {
725 // blast it wholesale
726 QByteArray p;
727 p.resize(bounds.width() * bounds.height() * static_cast<int>(cs->pixelSize()));
728 dev->readBytes(reinterpret_cast<quint8 *>(p.data()), bounds);
729 return p;
730 } else {
731 KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, 0, bounds.width());
732
733 // detect traits based on depth
734 // if u8 or u16, also trigger swap
735 return HDR::writeLayer(cs->colorDepthId(),
736 convertToRec2020,
737 cs->profile()->isLinear(),
738 conversionPolicy,
739 removeHGLOOTF,
740 bounds.width(),
741 bounds.height(),
742 it,
743 hlgGamma,
744 hlgNominalPeak,
745 cs);
746 }
747 }();
748
749 if (JxlEncoderAddImageFrame(frameSettings,
750 &pixelFormat,
751 pixels.data(),
752 static_cast<size_t>(pixels.size()))
753 != JXL_ENC_SUCCESS) {
754 errFile << "JxlEncoderAddImageFrame @" << i << "failed";
756 }
757
758#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
759 if (updater() && updater()->interrupted()) {
760 warnFile << "Save cancelled";
762 }
763 const int progress = (frameNum * 100) / times.size();
764 setProgress(progress);
765 frameNum++;
766 if (frames->nextKeyframeTime(i) == -1) {
767 JxlEncoderCloseInput(enc.get());
768 }
769 if (JxlEncoderFlushInput(enc.get()) != JXL_ENC_SUCCESS) {
770 errFile << "JxlEncoderFlushInput failed";
772 }
773#endif
774 }
775 } else {
776 auto frameHeader = std::make_unique<JxlFrameHeader>();
777
778 // (On layered export) Convert group layer to paint layer to preserve
779 // out-of-bound pixels so that it won't get clipped to canvas size
780 quint32 lastValidLayer = 0;
781 if (cfgMultiLayer || cfgMultiPage) {
782 for (quint32 pos = 0; pos < image->root()->childCount(); pos++) {
783 KisNodeSP node = image->root()->at(pos);
784 KisLayer *layer = qobject_cast<KisLayer *>(node.data());
785 if (layer && (layer->inherits("KisGroupLayer") || layer->childCount() > 0 || layer->layerStyle())
786 && layer->visible()) {
787 dbgFile << "Flattening layer" << node->name();
788 KisLayerUtils::flattenLayer(image, layer);
789 }
790 if (node && node->visible() && !node->isFakeNode()) {
791 lastValidLayer = pos;
792 }
793 }
794 image->waitForDone();
795 }
796
797 // Iterate through the layers (non-recursively)
798 for (quint32 pos = 0; pos < image->root()->childCount(); pos++) {
799 KisNodeSP node = image->root()->at(pos);
800 // Skip invalid and invisible layers
801 if ((cfgMultiLayer || cfgMultiPage) && (!node || !node->visible() || node->isFakeNode())) {
802 dbgFile << "Skipping hidden layer" << node->name();
803 continue;
804 }
805 const bool isFirstLayer = (node == image->root()->firstChild());
806
807 if (cfgMultiLayer || cfgMultiPage) {
808 dbgFile << "Visiting on layer" << node->name();
809
810 if (!node->inherits("KisPaintLayer")) {
811 std::future<KisNodeSP> convertedNode = KisLayerUtils::convertToPaintLayer(image, node);
812 node = convertedNode.get();
813 }
814
815 const KoColorSpace *lcs = node->colorSpace();
816 if (lcs && (lcs != cs)) {
817 node->paintDevice()->convertTo(cs);
818 // Kampidh: Do I also need to call waitForDone() here?
819 }
820 } else {
821 dbgFile << "Saving flattened image";
822 }
823
824 const QRect layerBounds = [&]() {
825 if (node->exactBounds().isEmpty() || cfgFlattenLayer) {
826 return image->bounds();
827 }
828 return node->exactBounds();
829 }();
830
832 if (cfgFlattenLayer) {
833 dev = image->projection();
834 } else {
835 dev = node->projection();
836 }
837
838 if (cs->colorModelId() == CMYKAColorModelID) {
839 // Inverting colors for CMYK
840 const KisFilterSP f = KisFilterRegistry::instance()->value("invert");
841 KIS_ASSERT(f);
842 const KisFilterConfigurationSP kfc =
843 f->defaultConfiguration(KisGlobalResourcesInterface::instance());
844 KIS_ASSERT(kfc);
845 f->process(dev, layerBounds, kfc->cloneWithResourcesSnapshot());
846 }
847
848 const QByteArray pixels = [&]() {
849 const KoID colorModel = cs->colorModelId();
850 const KoID colorDepth = cs->colorDepthId();
851
852 if (colorModel != RGBAColorModelID
853 || (colorDepth != Integer8BitsColorDepthID && colorDepth != Integer16BitsColorDepthID
854 && conversionPolicy == ConversionPolicy::KeepTheSame)) {
855 // CMYK
856 if (colorModel == CMYKAColorModelID) {
858 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
859
860 // interleaved CMY buffer
862 true,
863 0,
864 layerBounds.width(),
865 layerBounds.height(),
866 it);
867 }
868 // blast it wholesale
869 QByteArray p;
870 p.resize(layerBounds.width() * layerBounds.height() * static_cast<int>(cs->pixelSize()));
871 dev->readBytes(reinterpret_cast<quint8 *>(p.data()), layerBounds);
872 return p;
873 } else {
875 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
876
877 // detect traits based on depth
878 // if u8 or u16, also trigger swap
879 return HDR::writeLayer(cs->colorDepthId(),
880 convertToRec2020,
881 cs->profile()->isLinear(),
882 conversionPolicy,
883 removeHGLOOTF,
884 layerBounds.width(),
885 layerBounds.height(),
886 it,
887 hlgGamma,
888 hlgNominalPeak,
889 cs);
890 }
891 }();
892
893 if (cfgMultiLayer || cfgMultiPage) {
894 JxlEncoderInitFrameHeader(frameHeader.get());
895
896 // Set frame duration to 0 to indicate a multi-layered image
897 if (cfgMultiLayer) {
898 frameHeader->duration = 0;
899 } else if (cfgMultiPage) {
900 frameHeader->duration = 0xFFFFFFFF;
901 }
902
903 // Enable crop info if layer dimension is different than main
904 // This also enables out-of-bound pixels to be preserved
905 if (node->exactBounds() == image->bounds()) {
906 frameHeader->layer_info.have_crop = false;
907 } else {
908 frameHeader->layer_info.have_crop = true;
909 }
910 frameHeader->layer_info.crop_x0 = layerBounds.x();
911 frameHeader->layer_info.crop_y0 = layerBounds.y();
912 frameHeader->layer_info.xsize = layerBounds.width();
913 frameHeader->layer_info.ysize = layerBounds.height();
914
915 if (cs->colorModelId() == CMYKAColorModelID) {
916 frameHeader->layer_info.blend_info.alpha = 1;
917 } else {
918 frameHeader->layer_info.blend_info.alpha = 0;
919 }
920
921 // EXPERIMENTAL! Additive blending mode on JPEG-XL produces
922 // slightly different result than Krita.
923 const QString frameName = node->name();
924 if (!isFirstLayer && cfgMultiLayer) {
925 if (node->compositeOpId() == QString("add")) {
926 frameHeader->layer_info.blend_info.blendmode = JXL_BLEND_MULADD;
927 } else {
928 frameHeader->layer_info.blend_info.blendmode = JXL_BLEND_BLEND;
929 }
930 }
931
932 if (JxlEncoderSetFrameHeader(frameSettings, frameHeader.get()) != JXL_ENC_SUCCESS) {
933 errFile << "JxlEncoderSetFrameHeader failed";
935 }
936 if (JxlEncoderSetFrameName(frameSettings, frameName.toLocal8Bit()) != JXL_ENC_SUCCESS) {
937 errFile << "JxlEncoderSetFrameName failed";
939 }
940 }
941
942 if (JxlEncoderAddImageFrame(frameSettings,
943 &pixelFormat,
944 pixels.data(),
945 static_cast<size_t>(pixels.size()))
946 != JXL_ENC_SUCCESS) {
947 errFile << "JxlEncoderAddImageFrame failed";
949 }
950
951 // CMYKA separate planar buffer for Key and Alpha
952 if (cs->colorModelId() == CMYKAColorModelID) {
954 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
955
956 const QByteArray chaK = JXLExpTool::writeCMYKLayer(cs->colorDepthId(),
957 false,
958 3,
959 layerBounds.width(),
960 layerBounds.height(),
961 it);
962 it->resetRowPos();
963 const QByteArray chaA = JXLExpTool::writeCMYKLayer(cs->colorDepthId(),
964 false,
965 4,
966 layerBounds.width(),
967 layerBounds.height(),
968 it);
969
970 if (JxlEncoderSetExtraChannelBuffer(frameSettings,
971 &pixelFormat,
972 chaK,
973 static_cast<size_t>(chaK.size()),
974 0)
975 != JXL_ENC_SUCCESS) {
976 errFile << "JxlEncoderSetExtraChannelBuffer Key failed";
978 }
979 if (JxlEncoderSetExtraChannelBuffer(frameSettings,
980 &pixelFormat,
981 chaA,
982 static_cast<size_t>(chaA.size()),
983 1)
984 != JXL_ENC_SUCCESS) {
985 errFile << "JxlEncoderSetExtraChannelBuffer Alpha failed";
987 }
988 }
989
990#if JPEGXL_NUMERIC_VERSION >= JPEGXL_COMPUTE_NUMERIC_VERSION(0, 10, 1)
991 if (updater() && updater()->interrupted()) {
992 warnFile << "Save cancelled";
994 }
995 const int progress = (pos * 100) / image->root()->childCount();
996 setProgress(progress);
997 if ((pos == lastValidLayer && (cfgMultiLayer || cfgMultiPage)) || cfgFlattenLayer) {
998 JxlEncoderCloseInput(enc.get());
999 }
1000 if (JxlEncoderFlushInput(enc.get()) != JXL_ENC_SUCCESS) {
1001 errFile << "JxlEncoderFlushInput failed";
1003 }
1004 if (cfgFlattenLayer) {
1005 break;
1006 }
1007 }
1008 }
1009#else
1010 // Quit loop if flatten is active
1011 if (cfgFlattenLayer) {
1012 break;
1013 }
1014 }
1015 }
1016 JxlEncoderCloseInput(enc.get());
1017
1018 QByteArray compressed(16384, 0x0);
1019 auto *nextOut = reinterpret_cast<uint8_t *>(compressed.data());
1020 auto availOut = static_cast<size_t>(compressed.size());
1021 auto result = JXL_ENC_NEED_MORE_OUTPUT;
1022 while (result == JXL_ENC_NEED_MORE_OUTPUT) {
1023 result = JxlEncoderProcessOutput(enc.get(), &nextOut, &availOut);
1024 if (result != JXL_ENC_ERROR) {
1025 io->write(compressed.data(), compressed.size() - static_cast<int>(availOut));
1026 }
1027 if (result == JXL_ENC_NEED_MORE_OUTPUT) {
1028 compressed.resize(compressed.size() * 2);
1029 nextOut = reinterpret_cast<uint8_t *>(compressed.data());
1030 availOut = static_cast<size_t>(compressed.size());
1031 }
1032 }
1033 if (JXL_ENC_SUCCESS != result) {
1034 errFile << "JxlEncoderProcessOutput failed";
1036 }
1037#endif
1038 }
1039
1040 return ImportExportCodes::OK;
1041}
1042
1044{
1045 // This checks before saving for what the file format supports: anything that is supported needs to be mentioned
1046 // here
1047
1048 QList<QPair<KoID, KoID>> supportedColorModels;
1050 ->get("AnimationCheck")
1056 supportedColorModels << QPair<KoID, KoID>() << QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
1057 << QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
1058 << QPair<KoID, KoID>(CMYKAColorModelID, Integer8BitsColorDepthID)
1059 << QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
1060 << QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID)
1061 << QPair<KoID, KoID>(CMYKAColorModelID, Integer16BitsColorDepthID)
1062#ifdef HAVE_OPENEXR
1063 << QPair<KoID, KoID>(RGBAColorModelID, Float16BitsColorDepthID)
1064 << QPair<KoID, KoID>(GrayAColorModelID, Float16BitsColorDepthID)
1065 << QPair<KoID, KoID>(CMYKAColorModelID, Float16BitsColorDepthID)
1066#endif
1067 << QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID)
1068 << QPair<KoID, KoID>(GrayAColorModelID, Float32BitsColorDepthID)
1069 << QPair<KoID, KoID>(CMYKAColorModelID, Float32BitsColorDepthID);
1070 addSupportedColorModels(supportedColorModels, "JPEG-XL");
1071
1073 addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::PARTIALLY));
1074 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::PARTIALLY));
1075 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGeneratorLayer")->create(KisExportCheckBase::PARTIALLY));
1076 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::PARTIALLY));
1078 addCapability(KisExportCheckRegistry::instance()->get("FillLayerTypeCheck/pattern")->create(KisExportCheckBase::PARTIALLY));
1079 addCapability(KisExportCheckRegistry::instance()->get("FillLayerTypeCheck/gradient")->create(KisExportCheckBase::PARTIALLY));
1081}
1082
1084JPEGXLExport::createConfigurationWidget(QWidget *parent, const QByteArray & /*from*/, const QByteArray & /*to*/) const
1085{
1086 return new KisWdgOptionsJPEGXL(parent);
1087}
1088
1089KisPropertiesConfigurationSP JPEGXLExport::defaultConfiguration(const QByteArray &, const QByteArray &) const
1090{
1092
1093 // WARNING: libjxl only allows setting encoding properties,
1094 // so I hardcoded values from https://libjxl.readthedocs.io/en/latest/api_encoder.html
1095 // https://readthedocs.org/projects/libjxl/builds/16271112/
1096
1097 // Options for the following were not added because they rely
1098 // on the image's specific color space, or can introduce more
1099 // trouble than help:
1100 // JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X
1101 // JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y
1102 // JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE
1103 // JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS
1104 // These are directly incompatible with the export logic:
1105 // JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED
1106 // JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM
1107
1108 cfg->setProperty("haveAnimation", false);
1109 cfg->setProperty("flattenLayers", true);
1110 cfg->setProperty("multiLayer", false);
1111 cfg->setProperty("multiPage", false);
1112 cfg->setProperty("lossless", true);
1113 cfg->setProperty("effort", 7);
1114 cfg->setProperty("decodingSpeed", 0);
1115 cfg->setProperty("lossyQuality", 100);
1116 cfg->setProperty("forceModular", false);
1117 cfg->setProperty("modularSetVal", -1);
1118 cfg->setProperty("losslessAlpha", false);
1119
1120 cfg->setProperty("forceCicpLossless", false);
1121 cfg->setProperty("floatingPointConversionOption", "KeepSame");
1122 cfg->setProperty("HLGnominalPeak", 1000.0);
1123 cfg->setProperty("HLGgamma", 1.2);
1124 cfg->setProperty("removeHGLOOTF", true);
1125
1126 cfg->setProperty("resampling", -1);
1127 cfg->setProperty("extraChannelResampling", -1);
1128 cfg->setProperty("photonNoise", 0);
1129 cfg->setProperty("dots", -1);
1130 cfg->setProperty("patches", -1);
1131 cfg->setProperty("epf", -1);
1132 cfg->setProperty("gaborish", -1);
1133 cfg->setProperty("modular", -1);
1134 cfg->setProperty("keepInvisible", -1);
1135 cfg->setProperty("groupOrder", -1);
1136 cfg->setProperty("responsive", -1);
1137 cfg->setProperty("progressiveAC", -1);
1138 cfg->setProperty("qProgressiveAC", -1);
1139 cfg->setProperty("progressiveDC", -1);
1140 cfg->setProperty("channelColorsGlobalPercent", -1);
1141 cfg->setProperty("channelColorsGroupPercent", -1);
1142 cfg->setProperty("paletteColors", -1);
1143 cfg->setProperty("lossyPalette", -1);
1144 cfg->setProperty("modularGroupSize", -1);
1145 cfg->setProperty("modularPredictor", -1);
1146 cfg->setProperty("modularMATreeLearningPercent", -1);
1147 cfg->setProperty("jpegReconCFL", -1);
1148
1149 cfg->setProperty("storeAuthor", false);
1150 cfg->setProperty("exif", true);
1151 cfg->setProperty("xmp", true);
1152 cfg->setProperty("iptc", true);
1153 cfg->setProperty("storeMetaData", false);
1154 cfg->setProperty("filters", "");
1155 return cfg;
1156}
1157
1158#include <JPEGXLExport.moc>
const Params2D p
qreal v
VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
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_IEC_61966_2_4
@ TRC_ITU_R_BT_2020_2_10bit
@ TRC_LOGARITHMIC_100
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_ITU_R_BT_1361
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_ITU_R_BT_601_6
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_709_5
@ TRC_SMPTE_ST_428_1
@ TRC_LOGARITHMIC_100_sqrt10
@ TRC_ITU_R_BT_2020_2_12bit
qreal distance(const QPointF &p1, const QPointF &p2)
void initializeCapabilities() override
KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP cfg=nullptr) override
KisConfigWidget * createConfigurationWidget(QWidget *parent, const QByteArray &from="", const QByteArray &to="") const override
createConfigurationWidget creates a widget that can be used to define the settings for a given import...
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray &from="", const QByteArray &to="") const override
defaultConfiguration defines the default settings for the given import export filter
JPEGXLExport(QObject *parent, const QVariantList &)
The KisExifInfoVisitor class looks for a layer with metadata.
KisMetaData::Store * exifInfo()
bool visit(KisNode *) override
static KisExportCheckRegistry * instance()
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP instance()
virtual void resetRowPos()=0
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
void waitForDone()
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
The base class for import and export filters.
QPointer< KoUpdater > updater
void addSupportedColorModels(QList< QPair< KoID, KoID > > supportedColorModels, const QString &name, KisExportCheckBase::Level level=KisExportCheckBase::PARTIALLY)
void addCapability(KisExportCheckBase *capability)
virtual void setEnabledFilters(const QStringList &enabledFilters)
enable the filters in the given list; others will be disabled.
virtual bool saveTo(const Store *store, QIODevice *ioDevice, HeaderType headerType=NoHeader) const =0
static KisMetaData::SchemaRegistry * instance()
const Schema * schemaFromUri(const QString &uri) const
static const QString DublinCoreSchemaUri
static KisMetadataBackendRegistry * instance()
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 ...
int end() const
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
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
#define warnFile
Definition kis_debug.h:95
#define errFile
Definition kis_debug.h:115
#define dbgFile
Definition kis_debug.h:53
QByteArray writeLayer(const int width, const int height, KisHLineConstIteratorSP it, float hlgGamma, float hlgNominalPeak, const KoColorSpace *cs)
QByteArray writeCMYKLayer(const KoID &id, Args &&...args)
void flattenImage(KisImageSP image, KisNodeSP activeNode, MergeFlags flags)
void flattenLayer(KisImageSP image, KisLayerSP layer, MergeFlags flags)
std::future< KisNodeSP > convertToPaintLayer(KisImageSP image, KisNodeSP src)
JxlEncoderOutputProcessor getOutputProcessor()
virtual KisPaintDeviceSP projection() const =0
const QString & compositeOpId() const
virtual QRect exactBounds() const
virtual const KoColorSpace * colorSpace() const =0
bool isAnimated() const
virtual KisPaintDeviceSP paintDevice() const =0
QString name() const
virtual bool isFakeNode() const
virtual bool visible(bool recursive=false) const
bool hasEditablePaintDevice() const
KisPSDLayerStyleSP layerStyle
Definition kis_layer.cc:171
KisNodeSP firstChild() const
Definition kis_node.cpp:361
quint32 childCount() const
Definition kis_node.cpp:414
KisNodeSP at(quint32 index) const
Definition kis_node.cpp:421
virtual QVector< qreal > getColorantsxyY() const =0
virtual QByteArray rawData() const
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual bool hasColorants() const =0
virtual bool isLinear() const =0
virtual QVector< qreal > getWhitePointxyY() const =0
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
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()