Krita Source Code Documentation
Loading...
Searching...
No Matches
JPEGXLExport Class Reference

#include <JPEGXLExport.h>

+ Inheritance diagram for JPEGXLExport:

Public Member Functions

KisImportExportErrorCode convert (KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP cfg=nullptr) override
 
KisConfigWidgetcreateConfigurationWidget (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/export filter
 
KisPropertiesConfigurationSP defaultConfiguration (const QByteArray &from="", const QByteArray &to="") const override
 defaultConfiguration defines the default settings for the given import export filter
 
void initializeCapabilities () override
 
 JPEGXLExport (QObject *parent, const QVariantList &)
 
 ~JPEGXLExport () override=default
 
- Public Member Functions inherited from KisImportExportFilter
virtual QMap< QString, KisExportCheckBase * > exportChecks ()
 generate and return the list of capabilities of this export filter. The list
 
virtual bool exportSupportsGuides () const
 exportSupportsGuides Because guides are in the document and not the image, checking for guides cannot be made an exportCheck.
 
KisPropertiesConfigurationSP lastSavedConfiguration (const QByteArray &from="", const QByteArray &to="") const
 lastSavedConfiguration return the last saved configuration for this filter
 
 Private ()
 
void setBatchMode (bool batchmode)
 
void setFilename (const QString &filename)
 
void setImportUserFeedBackInterface (KisImportUserFeedbackInterface *interface)
 
void setMimeType (const QString &mime)
 
void setRealFilename (const QString &filename)
 
void setUpdater (QPointer< KoUpdater > updater)
 
virtual bool supportsIO () const
 Override and return false for the filters that use a library that cannot handle file handles, only file names.
 
QPointer< KoUpdaterupdater ()
 
virtual QString verify (const QString &fileName) const
 Verify whether the given file is correct and readable.
 
 ~KisImportExportFilter () override
 
 ~Private ()
 

Additional Inherited Members

- Public Attributes inherited from KisImportExportFilter
bool batchmode
 
QMap< QString, KisExportCheckBase * > capabilities
 
QString filename
 
KisImportUserFeedbackInterfaceimportUserFeedBackInterface {nullptr}
 
QByteArray mime
 
QString realFilename
 
QPointer< KoUpdaterupdater
 
- Static Public Attributes inherited from KisImportExportFilter
static const QString CICPPrimariesTag = "CICPCompatiblePrimaries"
 
static const QString CICPTransferCharacteristicsTag = "CICPCompatibleTransferFunction"
 
static const QString ColorDepthIDTag = "ColorDepthID"
 
static const QString ColorModelIDTag = "ColorModelID"
 
static const QString HDRTag = "HDRSupported"
 
static const QString ImageContainsTransparencyTag = "ImageContainsTransparency"
 
static const QString sRGBTag = "sRGB"
 
- Protected Member Functions inherited from KisImportExportFilter
void addCapability (KisExportCheckBase *capability)
 
void addSupportedColorModels (QList< QPair< KoID, KoID > > supportedColorModels, const QString &name, KisExportCheckBase::Level level=KisExportCheckBase::PARTIALLY)
 
bool batchMode () const
 
QString filename () const
 
KisImportUserFeedbackInterfaceimportUserFeedBackInterface () const
 
 KisImportExportFilter (QObject *parent=0)
 
QByteArray mimeType () const
 
QString realFilename () const
 
void setProgress (int value)
 
QString verifyZiPBasedFiles (const QString &fileName, const QStringList &filesToCheck) const
 

Detailed Description

Definition at line 12 of file JPEGXLExport.h.

Constructor & Destructor Documentation

◆ JPEGXLExport()

JPEGXLExport::JPEGXLExport ( QObject * parent,
const QVariantList &  )

Definition at line 62 of file JPEGXLExport.cpp.

63 : KisImportExportFilter(parent)
64{
65}
KisImportExportFilter(QObject *parent=0)

◆ ~JPEGXLExport()

JPEGXLExport::~JPEGXLExport ( )
overridedefault

Member Function Documentation

◆ convert()

KisImportExportErrorCode JPEGXLExport::convert ( KisDocument * document,
QIODevice * io,
KisPropertiesConfigurationSP configuration = nullptr )
overridevirtual

The filter chain calls this method to perform the actual conversion. The passed mimetypes should be a pair of those you specified in your .desktop file. You have to implement this method to make the filter work.

Returns
The error status, see the #ConversionStatus enum. KisImportExportFilter::OK means that everything is alright.

Implements KisImportExportFilter.

Definition at line 67 of file JPEGXLExport.cpp.

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}
const Params2D p
qreal 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)
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
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
QPointer< KoUpdater > updater
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
#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)
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()

References KisImage::animationInterface(), ApplyHLG, ApplyPQ, ApplySMPTE428, KisNode::at(), KisImage::bounds(), bounds, ImportExportCodes::Cancelled, KisNode::childCount(), CMYKAColorModelID, KoColorSpace::colorDepthId(), KoColorSpace::colorModelId(), KisImage::colorSpace(), KisBaseNode::colorSpace(), KoColorSpaceRegistry::colorSpace(), KisBaseNode::compositeOpId(), KisImage::convertImageColorSpace(), KisPaintDevice::convertTo(), KisLayerUtils::convertToPaintLayer(), KritaUtils::CopySnapshot, KisPaintDevice::createHLineConstIteratorNG(), KisSharedPtr< T >::data(), dbgFile, distance(), KisImageAnimationInterface::documentPlaybackRange(), KisMetaData::Schema::DublinCoreSchemaUri, KisMetaData::FilterRegistryModel::enabledFilters(), KisTimeSpan::end(), errFile, KisBaseNode::exactBounds(), KisExifInfoVisitor::exifInfo(), KisNode::firstChild(), KisLayerUtils::flattenImage(), KisLayerUtils::flattenLayer(), Float16BitsColorDepthID, Float32BitsColorDepthID, ImportExportCodes::FormatColorSpaceUnsupported, KisImageAnimationInterface::framerate(), KoColorProfile::getColorantsxyY(), KoColorProfile::getColorPrimaries(), JXLExpTool::JxlOutputProcessor::getOutputProcessor(), KoColorProfile::getTransferCharacteristics(), KoColorProfile::getWhitePointxyY(), GrayAColorModelID, KisImageAnimationInterface::hasAnimation(), KoColorProfile::hasColorants(), KisBaseNode::hasEditablePaintDevice(), KoColorSpace::hasHighDynamicRange(), KoID::id(), KisFilterRegistry::instance(), KisMetadataBackendRegistry::instance(), KisMetaData::SchemaRegistry::instance(), KoColorSpaceRegistry::instance(), KisGlobalResourcesInterface::instance(), Integer16BitsColorDepthID, Integer8BitsColorDepthID, KoColorConversionTransformation::internalConversionFlags(), ImportExportCodes::InternalError, KoColorConversionTransformation::internalRenderingIntent(), KisBaseNode::isAnimated(), KisBaseNode::isFakeNode(), KoColorProfile::isLinear(), KeepTheSame, KisPaintDevice::keyframeChannel(), KIS_ASSERT, KIS_ASSERT_RECOVER, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, KisLayer::layerStyle, KisExifInfoVisitor::metaDataCount(), KisBaseNode::name(), ImportExportCodes::NoAccessToWrite, p, KisBaseNode::paintDevice(), KoColorSpace::pixelSize(), PRIMARIES_ITU_R_BT_2020_2_AND_2100_0, PRIMARIES_ITU_R_BT_709_5, PRIMARIES_SMPTE_RP_431_2, KoColorSpace::profile(), KoColorSpaceRegistry::profileFor(), KisImage::projection(), KisBaseNode::projection(), KoColorProfile::rawData(), KisPaintDevice::readBytes(), KisHLineConstIteratorNG::resetRowPos(), RGBAColorModelID, KisNodeFacade::root, KisImage::rootLayer(), KisMetaData::IOBackend::saveTo(), KisMetaData::SchemaRegistry::schemaFromUri(), KisMetaData::FilterRegistryModel::setEnabledFilters(), KisImportExportFilter::setProgress(), TRC_A98, TRC_GAMMA_1_8, TRC_GAMMA_2_4, TRC_IEC_61966_2_1, TRC_IEC_61966_2_4, TRC_ITU_R_BT_1361, TRC_ITU_R_BT_2020_2_10bit, TRC_ITU_R_BT_2020_2_12bit, TRC_ITU_R_BT_2100_0_HLG, TRC_ITU_R_BT_2100_0_PQ, TRC_ITU_R_BT_470_6_SYSTEM_B_G, TRC_ITU_R_BT_470_6_SYSTEM_M, TRC_ITU_R_BT_601_6, TRC_ITU_R_BT_709_5, TRC_LAB_L, TRC_LINEAR, TRC_LOGARITHMIC_100, TRC_LOGARITHMIC_100_sqrt10, TRC_PROPHOTO, TRC_SMPTE_240M, TRC_SMPTE_ST_428_1, TRC_UNSPECIFIED, KisImportExportFilter::updater, v, KoGenericRegistry< T >::value(), KisBaseNode::visible(), KisExifInfoVisitor::visit(), KisImage::waitForDone(), warnFile, JXLExpTool::writeCMYKLayer(), and HDR::writeLayer().

◆ createConfigurationWidget()

KisConfigWidget * JPEGXLExport::createConfigurationWidget ( QWidget * parent,
const QByteArray & from = "",
const QByteArray & to = "" ) const
overridevirtual

createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter

Parameters
parentthe owner of the widget; the caller is responsible for deleting
fromThe mimetype of the source file/document
toThe mimetype of the destination file/document
Returns
the widget

Reimplemented from KisImportExportFilter.

Definition at line 1084 of file JPEGXLExport.cpp.

1085{
1086 return new KisWdgOptionsJPEGXL(parent);
1087}

◆ defaultConfiguration()

KisPropertiesConfigurationSP JPEGXLExport::defaultConfiguration ( const QByteArray & from = "",
const QByteArray & to = "" ) const
overridevirtual

defaultConfiguration defines the default settings for the given import export filter

Parameters
fromThe mimetype of the source file/document
toThe mimetype of the destination file/document
Returns
a serializable KisPropertiesConfiguration object

Reimplemented from KisImportExportFilter.

Definition at line 1089 of file JPEGXLExport.cpp.

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}

◆ initializeCapabilities()

void JPEGXLExport::initializeCapabilities ( )
overridevirtual

Reimplemented from KisImportExportFilter.

Definition at line 1043 of file JPEGXLExport.cpp.

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
1081}
VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
static KisExportCheckRegistry * instance()
void addSupportedColorModels(QList< QPair< KoID, KoID > > supportedColorModels, const QString &name, KisExportCheckBase::Level level=KisExportCheckBase::PARTIALLY)
void addCapability(KisExportCheckBase *capability)
QPainterPath create(const char32_t codepoint, double height)
Creates a tofu missing glyph indicator representing the provided Unicode codepoint.

References KisImportExportFilter::addCapability(), KisImportExportFilter::addSupportedColorModels(), CMYKAColorModelID, Float16BitsColorDepthID, Float32BitsColorDepthID, get(), GrayAColorModelID, KisExportCheckRegistry::instance(), Integer16BitsColorDepthID, Integer8BitsColorDepthID, KisExportCheckBase::PARTIALLY, RGBAColorModelID, and KisExportCheckBase::SUPPORTED.


The documentation for this class was generated from the following files: