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 <KoColorProfileQuery.h>
32#include <KoColorSpace.h>
34#include <KoConfig.h>
35#include <KoDocumentInfo.h>
36#include <KoProperties.h>
37#include <KoUpdater.h>
38#include <filter/kis_filter.h>
41#include <kis_assert.h>
42#include <kis_debug.h>
45#include <kis_iterator_ng.h>
46#include <kis_layer.h>
47#include <kis_layer_utils.h>
49#include <kis_meta_data_entry.h>
53#include <kis_meta_data_store.h>
54#include <kis_meta_data_value.h>
56#include <kis_time_span.h>
57
60
61K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_jxl_export.json", registerPlugin<JPEGXLExport>();)
62
63JPEGXLExport::JPEGXLExport(QObject *parent, const QVariantList &)
64 : KisImportExportFilter(parent)
65{
66}
67
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
77 KisImageSP image = document->savingImage();
78 const QRect bounds = image->bounds();
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
95 const KoColorSpace *cs = image->colorSpace();
97 bool convertToRec2020 = false;
98
100 const QString conversionOption = (cfg->getString("floatingPointConversionOption", "Rec2100PQ"));
101 if (conversionOption == "Rec2100PQ") {
102 convertToRec2020 = true;
103 conversionPolicy = ConversionPolicy::ApplyPQ;
104 } else if (conversionOption == "Rec2100HLG") {
105 convertToRec2020 = true;
106 conversionPolicy = ConversionPolicy::ApplyHLG;
107 } else if (conversionOption == "ApplyPQ") {
108 conversionPolicy = ConversionPolicy::ApplyPQ;
109 } else if (conversionOption == "ApplyHLG") {
110 conversionPolicy = ConversionPolicy::ApplyHLG;
111 } else if (conversionOption == "ApplySMPTE428") {
112 conversionPolicy = ConversionPolicy::ApplySMPTE428;
113 }
114 }
115
116 if (cs->hasHighDynamicRange() && convertToRec2020) {
117 const KoColorProfile *linear =
119 KIS_ASSERT_RECOVER(linear)
120 {
121 errFile << "Unable to find a working profile for Rec. 2020";
123 }
124 const KoColorSpace *linearRec2020 =
126 image->convertImageColorSpace(linearRec2020,
129
130 image->waitForDone();
131 cs = image->colorSpace();
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
138 const bool hasPrimaries = cs->profile()->hasColorants();
140 static constexpr std::array<TransferCharacteristics, 14> supportedTRC = {TRC_LINEAR,
153 TRC_A98};
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)
157 JXLExpTool::JxlOutputProcessor processor(io);
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;
168 } else if (conversionPolicy != ConversionPolicy::KeepTheSame
170 pixelFormat.data_type = JXL_TYPE_UINT16;
171#ifdef HAVE_OPENEXR
172 } else if (cs->colorDepthId() == Float16BitsColorDepthID) {
173 pixelFormat.data_type = JXL_TYPE_FLOAT16;
174#endif
175 } else if (cs->colorDepthId() == Float32BitsColorDepthID) {
176 pixelFormat.data_type = JXL_TYPE_FLOAT;
177 }
178 if (cs->colorModelId() == RGBAColorModelID) {
179 pixelFormat.num_channels = 4;
180 } else if (cs->colorModelId() == GrayAColorModelID) {
181 pixelFormat.num_channels = 2;
182 } else if (cs->colorModelId() == CMYKAColorModelID) {
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 }
223 if (cs->colorModelId() == RGBAColorModelID) {
224 info->num_color_channels = 3;
225 info->num_extra_channels = 1;
226 } else if (cs->colorModelId() == GrayAColorModelID) {
227 info->num_color_channels = 1;
228 info->num_extra_channels = 1;
229 } else if (cs->colorModelId() == CMYKAColorModelID) {
230 info->num_color_channels = 3;
231 info->num_extra_channels = 2;
232 }
233 // Use original profile on lossless, non-matrix profile or unsupported transfer curve.
234 if (cfg->getBool("lossless") || (!hasPrimaries && !(cs->colorModelId() == GrayAColorModelID))
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 }
242 if (image->animationInterface()->hasAnimation() && cfgHaveAnimation) {
243 info->have_animation = JXL_TRUE;
244 info->animation.have_timecodes = JXL_FALSE;
245 info->animation.num_loops = 0;
246 // Unlike WebP, JXL does allow for setting proper frame rates.
247 info->animation.tps_numerator =
248 static_cast<uint32_t>(image->animationInterface()->framerate());
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 // CMYKA extra channel info
266 if (cs->colorModelId() == CMYKAColorModelID) {
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) {
308 case TRC_LINEAR:
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;
336 case TRC_GAMMA_1_8:
337 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
338 cicpDescription.gamma = 1.0 / 1.8;
339 break;
340 case TRC_GAMMA_2_4:
341 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
342 cicpDescription.gamma = 1.0 / 2.4;
343 break;
344 case TRC_PROPHOTO:
345 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
346 cicpDescription.gamma = 1.0 / 1.8;
347 break;
348 case TRC_A98:
349 cicpDescription.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
350 cicpDescription.gamma = 256.0 / 563.0;
351 break;
354 case TRC_SMPTE_240M:
358 case TRC_LAB_L:
359 case TRC_UNSPECIFIED:
360 if (cs->profile()->isLinear()) {
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
372 const ColorPrimaries primaries = cs->profile()->getColorPrimaries();
373
374 if ((cfg->getBool("lossless") && conversionPolicy == ConversionPolicy::KeepTheSame
375 && !cfg->getBool("forceCicpLossless"))
376 || (!hasPrimaries && !(cs->colorModelId() == GrayAColorModelID)) || !isSupportedTRC) {
377 const QByteArray profile = cs->profile()->rawData();
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
389 if (cs->colorModelId() == GrayAColorModelID) {
390 // XXX: JXL can't parse custom white point for grayscale (yet) and returned as linear on roundtrip so
391 // let's use default D65 as whitepoint instead...
392 //
393 // See: https://github.com/libjxl/libjxl/issues/1933
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 // Unfortunately, Wolthera never wrote an enum for white points...
422 const KoColorimetryUtils::xyY whitePoint = image->colorSpace()->profile()->getWhitePointxyY();
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> {
437 KisExifInfoVisitor exivInfoVisitor;
438 exivInfoVisitor.visit(image->rootLayer().data());
439 if (exivInfoVisitor.metaDataCount() == 1) {
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()) {
450 model.setEnabledFilters(cfg->getString("filters").split(","));
451 metaDataStore->applyFilters(model.enabledFilters());
452 }
453
454 const KisMetaData::Schema *dcSchema =
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 }
470 metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author))));
471 }
472 }
473
474 if (metaDataStore && cfg->getBool("exif", true)) {
476
477 QBuffer ioDevice;
478
479 // Inject the data as any other IOBackend
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 // Inject the data as any other IOBackend
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 // Inject the data as any other IOBackend
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 // https://github.com/libjxl/libjxl/issues/1210
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 // Using cjxl quality mapping that translates from arbitrary quality value to JPEG-XL distance
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;
558 dbgFile << "libjxl distance equivalent: " << distance;
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 // Lossless alpha (extra channel), only available on libjxl 0.9.0+
565 if (!cfg->getBool("lossless")) {
566 if (cfg->getBool("losslessAlpha")) {
567 if (JxlEncoderSetExtraChannelDistance(frameSettings,
568 (cs->colorModelId() == CMYKAColorModelID) ? 1 : 0,
569 0)
570 != JXL_ENC_SUCCESS) {
571 errFile << "JxlEncoderSetExtraChannelDistance failed";
572 return false;
573 }
574 } else {
575 if (JxlEncoderSetExtraChannelDistance(frameSettings,
576 (cs->colorModelId() == CMYKAColorModelID) ? 1 : 0,
577 distance)
578 != JXL_ENC_SUCCESS) {
579 errFile << "JxlEncoderSetExtraChannelDistance failed";
580 return false;
581 }
582 }
583 }
584#endif
585 return true;
586 };
587
588 // XXX: Workaround for a buggy lossy F32.
589 //
590 // See: https://github.com/libjxl/libjxl/issues/2064
591 //
592 // Update: It's not the modular mode that caused the bug, but the progressive/responsive setting
593 // that didn't work well with F32. So let's disable it on F32 instead.
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 // XXX: Workaround for a buggy lossless patches. Set to disable instead.
603 // Patch only for libjxl under v0.9.0
604 //
605 // See: https://github.com/libjxl/libjxl/issues/2463
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 = [&]() {
664 if (image->animationInterface()->hasAnimation() && cfgHaveAnimation) {
665 KisLayerUtils::flattenImage(image, nullptr);
666 image->waitForDone();
667
668 const KisNodeSP projection = image->rootLayer()->firstChild();
669 return projection->isAnimated() && projection->hasEditablePaintDevice();
670 }
671 return false;
672 }();
673
674 if (isAnimated) {
675 // Flatten the image, projections don't have keyframes.
676 KisLayerUtils::flattenImage(image, nullptr);
677 image->waitForDone();
678
679 const KisNodeSP projection = image->rootLayer()->firstChild();
680 KIS_ASSERT(projection->isAnimated());
681 KIS_ASSERT(projection->hasEditablePaintDevice());
682
683 const auto *frames = projection->paintDevice()->keyframeChannel();
684 const auto times = [&]() {
685 QList<int> t;
686 QSet<int> s = frames->allKeyframeTimes();
687 t = QList<int>(s.begin(), s.end());
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 = [&]() {
718 const auto frameData = frames->keyframeAt<KisRasterKeyframe>(i);
719 KisPaintDeviceSP dev =
721 frameData->writeFrameToDevice(dev);
722
723 const KoID colorModel = cs->colorModelId();
724
725 if (colorModel != RGBAColorModelID) {
726 // blast it wholesale
727 QByteArray p;
728 p.resize(bounds.width() * bounds.height() * static_cast<int>(cs->pixelSize()));
729 dev->readBytes(reinterpret_cast<quint8 *>(p.data()), bounds);
730 return p;
731 } else {
732 KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, 0, bounds.width());
733
734 // detect traits based on depth
735 // if u8 or u16, also trigger swap
736 return HDR::writeLayer(cs->colorDepthId(),
737 convertToRec2020,
738 cs->profile()->isLinear(),
739 conversionPolicy,
740 removeHGLOOTF,
741 bounds.width(),
742 bounds.height(),
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)
760 if (updater() && updater()->interrupted()) {
761 warnFile << "Save cancelled";
763 }
764 const int progress = (frameNum * 100) / times.size();
765 setProgress(progress);
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 // (On layered export) Convert group layer to paint layer to preserve
780 // out-of-bound pixels so that it won't get clipped to canvas size
781 quint32 lastValidLayer = 0;
782 if (cfgMultiLayer || cfgMultiPage) {
783 for (quint32 pos = 0; pos < image->root()->childCount(); pos++) {
784 KisNodeSP node = image->root()->at(pos);
785 KisLayer *layer = qobject_cast<KisLayer *>(node.data());
786 if (layer && (layer->inherits("KisGroupLayer") || layer->childCount() > 0 || layer->layerStyle())
787 && layer->visible()) {
788 dbgFile << "Flattening layer" << node->name();
789 KisLayerUtils::flattenLayer(image, layer);
790 }
791 if (node && node->visible() && !node->isFakeNode()) {
792 lastValidLayer = pos;
793 }
794 }
795 image->waitForDone();
796 }
797
798 // Iterate through the layers (non-recursively)
799 for (quint32 pos = 0; pos < image->root()->childCount(); pos++) {
800 KisNodeSP node = image->root()->at(pos);
801 // Skip invalid and invisible layers
802 if ((cfgMultiLayer || cfgMultiPage) && (!node || !node->visible() || node->isFakeNode())) {
803 dbgFile << "Skipping hidden layer" << node->name();
804 continue;
805 }
806 const bool isFirstLayer = (node == image->root()->firstChild());
807
808 if (cfgMultiLayer || cfgMultiPage) {
809 dbgFile << "Visiting on layer" << node->name();
810
811 if (!node->inherits("KisPaintLayer")) {
812 std::future<KisNodeSP> convertedNode = KisLayerUtils::convertToPaintLayer(image, node);
813 node = convertedNode.get();
814 }
815
816 const KoColorSpace *lcs = node->colorSpace();
817 if (lcs && (lcs != cs)) {
818 node->paintDevice()->convertTo(cs);
819 // Kampidh: Do I also need to call waitForDone() here?
820 }
821 } else {
822 dbgFile << "Saving flattened image";
823 }
824
825 const QRect layerBounds = [&]() {
826 if (node->exactBounds().isEmpty() || cfgFlattenLayer) {
827 return image->bounds();
828 }
829 return node->exactBounds();
830 }();
831
833 if (cfgFlattenLayer) {
834 dev = image->projection();
835 } else {
836 dev = node->projection();
837 }
838
839 if (cs->colorModelId() == CMYKAColorModelID) {
840 // Inverting colors for CMYK
841 const KisFilterSP f = KisFilterRegistry::instance()->value("invert");
842 KIS_ASSERT(f);
843 const KisFilterConfigurationSP kfc =
844 f->defaultConfiguration(KisGlobalResourcesInterface::instance());
845 KIS_ASSERT(kfc);
846 f->process(dev, layerBounds, kfc->cloneWithResourcesSnapshot());
847 }
848
849 const QByteArray pixels = [&]() {
850 const KoID colorModel = cs->colorModelId();
851 const KoID colorDepth = cs->colorDepthId();
852
853 if (colorModel != RGBAColorModelID
854 || (colorDepth != Integer8BitsColorDepthID && colorDepth != Integer16BitsColorDepthID
855 && conversionPolicy == ConversionPolicy::KeepTheSame)) {
856 // CMYK
857 if (colorModel == CMYKAColorModelID) {
859 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
860
861 // interleaved CMY buffer
863 true,
864 0,
865 layerBounds.width(),
866 layerBounds.height(),
867 it);
868 }
869 // blast it wholesale
870 QByteArray p;
871 p.resize(layerBounds.width() * layerBounds.height() * static_cast<int>(cs->pixelSize()));
872 dev->readBytes(reinterpret_cast<quint8 *>(p.data()), layerBounds);
873 return p;
874 } else {
876 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
877
878 // detect traits based on depth
879 // if u8 or u16, also trigger swap
880 return HDR::writeLayer(cs->colorDepthId(),
881 convertToRec2020,
882 cs->profile()->isLinear(),
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 // Set frame duration to 0 to indicate a multi-layered image
898 if (cfgMultiLayer) {
899 frameHeader->duration = 0;
900 } else if (cfgMultiPage) {
901 frameHeader->duration = 0xFFFFFFFF;
902 }
903
904 // Enable crop info if layer dimension is different than main
905 // This also enables out-of-bound pixels to be preserved
906 if (node->exactBounds() == image->bounds()) {
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
916 if (cs->colorModelId() == CMYKAColorModelID) {
917 frameHeader->layer_info.blend_info.alpha = 1;
918 } else {
919 frameHeader->layer_info.blend_info.alpha = 0;
920 }
921
922 // EXPERIMENTAL! Additive blending mode on JPEG-XL produces
923 // slightly different result than Krita.
924 const QString frameName = node->name();
925 if (!isFirstLayer && cfgMultiLayer) {
926 if (node->compositeOpId() == QString("add")) {
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 // CMYKA separate planar buffer for Key and Alpha
953 if (cs->colorModelId() == CMYKAColorModelID) {
955 dev->createHLineConstIteratorNG(layerBounds.x(), layerBounds.y(), layerBounds.width());
956
957 const QByteArray chaK = JXLExpTool::writeCMYKLayer(cs->colorDepthId(),
958 false,
959 3,
960 layerBounds.width(),
961 layerBounds.height(),
962 it);
963 it->resetRowPos();
964 const QByteArray chaA = JXLExpTool::writeCMYKLayer(cs->colorDepthId(),
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)
992 if (updater() && updater()->interrupted()) {
993 warnFile << "Save cancelled";
995 }
996 const int progress = (pos * 100) / image->root()->childCount();
997 setProgress(progress);
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 // Quit loop if flatten is active
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
1041 return ImportExportCodes::OK;
1042}
1043
1045{
1046 // This checks before saving for what the file format supports: anything that is supported needs to be mentioned
1047 // here
1048
1049 QList<QPair<KoID, KoID>> supportedColorModels;
1051 ->get("AnimationCheck")
1057 supportedColorModels << QPair<KoID, KoID>() << QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
1058 << QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
1059 << QPair<KoID, KoID>(CMYKAColorModelID, Integer8BitsColorDepthID)
1060 << QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
1061 << QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID)
1062 << QPair<KoID, KoID>(CMYKAColorModelID, Integer16BitsColorDepthID)
1063#ifdef HAVE_OPENEXR
1064 << QPair<KoID, KoID>(RGBAColorModelID, Float16BitsColorDepthID)
1065 << QPair<KoID, KoID>(GrayAColorModelID, Float16BitsColorDepthID)
1066 << QPair<KoID, KoID>(CMYKAColorModelID, Float16BitsColorDepthID)
1067#endif
1068 << QPair<KoID, KoID>(RGBAColorModelID, Float32BitsColorDepthID)
1069 << QPair<KoID, KoID>(GrayAColorModelID, Float32BitsColorDepthID)
1070 << QPair<KoID, KoID>(CMYKAColorModelID, Float32BitsColorDepthID);
1071 addSupportedColorModels(supportedColorModels, "JPEG-XL");
1072
1074 addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::PARTIALLY));
1075 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::PARTIALLY));
1076 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGeneratorLayer")->create(KisExportCheckBase::PARTIALLY));
1077 addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::PARTIALLY));
1079 addCapability(KisExportCheckRegistry::instance()->get("FillLayerTypeCheck/pattern")->create(KisExportCheckBase::PARTIALLY));
1080 addCapability(KisExportCheckRegistry::instance()->get("FillLayerTypeCheck/gradient")->create(KisExportCheckBase::PARTIALLY));
1082}
1083
1085JPEGXLExport::createConfigurationWidget(QWidget *parent, const QByteArray & /*from*/, const QByteArray & /*to*/) const
1086{
1087 return new KisWdgOptionsJPEGXL(parent);
1088}
1089
1090KisPropertiesConfigurationSP JPEGXLExport::defaultConfiguration(const QByteArray &, const QByteArray &) const
1091{
1093
1094 // WARNING: libjxl only allows setting encoding properties,
1095 // so I hardcoded values from https://libjxl.readthedocs.io/en/latest/api_encoder.html
1096 // https://readthedocs.org/projects/libjxl/builds/16271112/
1097
1098 // Options for the following were not added because they rely
1099 // on the image's specific color space, or can introduce more
1100 // trouble than help:
1101 // JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X
1102 // JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y
1103 // JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE
1104 // JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS
1105 // These are directly incompatible with the export logic:
1106 // JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED
1107 // JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM
1108
1109 cfg->setProperty("haveAnimation", false);
1110 cfg->setProperty("flattenLayers", true);
1111 cfg->setProperty("multiLayer", false);
1112 cfg->setProperty("multiPage", false);
1113 cfg->setProperty("lossless", true);
1114 cfg->setProperty("effort", 7);
1115 cfg->setProperty("decodingSpeed", 0);
1116 cfg->setProperty("lossyQuality", 100);
1117 cfg->setProperty("forceModular", false);
1118 cfg->setProperty("modularSetVal", -1);
1119 cfg->setProperty("losslessAlpha", false);
1120
1121 cfg->setProperty("forceCicpLossless", false);
1122 cfg->setProperty("floatingPointConversionOption", "KeepSame");
1123 cfg->setProperty("HLGnominalPeak", 1000.0);
1124 cfg->setProperty("HLGgamma", 1.2);
1125 cfg->setProperty("removeHGLOOTF", true);
1126
1127 cfg->setProperty("resampling", -1);
1128 cfg->setProperty("extraChannelResampling", -1);
1129 cfg->setProperty("photonNoise", 0);
1130 cfg->setProperty("dots", -1);
1131 cfg->setProperty("patches", -1);
1132 cfg->setProperty("epf", -1);
1133 cfg->setProperty("gaborish", -1);
1134 cfg->setProperty("modular", -1);
1135 cfg->setProperty("keepInvisible", -1);
1136 cfg->setProperty("groupOrder", -1);
1137 cfg->setProperty("responsive", -1);
1138 cfg->setProperty("progressiveAC", -1);
1139 cfg->setProperty("qProgressiveAC", -1);
1140 cfg->setProperty("progressiveDC", -1);
1141 cfg->setProperty("channelColorsGlobalPercent", -1);
1142 cfg->setProperty("channelColorsGroupPercent", -1);
1143 cfg->setProperty("paletteColors", -1);
1144 cfg->setProperty("lossyPalette", -1);
1145 cfg->setProperty("modularGroupSize", -1);
1146 cfg->setProperty("modularPredictor", -1);
1147 cfg->setProperty("modularMATreeLearningPercent", -1);
1148 cfg->setProperty("jpegReconCFL", -1);
1149
1150 cfg->setProperty("storeAuthor", false);
1151 cfg->setProperty("exif", true);
1152 cfg->setProperty("xmp", true);
1153 cfg->setProperty("iptc", true);
1154 cfg->setProperty("storeMetaData", false);
1155 cfg->setProperty("filters", "");
1156 return cfg;
1157}
1158
1159#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
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,...