Krita Source Code Documentation
Loading...
Searching...
No Matches
exr_converter.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005 Adrian Page <adrian@pagenet.plus.com>
3 * SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include "exr_converter.h"
9
10#include <half.h>
11
12#include <ImfAttribute.h>
13#include <ImfChannelList.h>
14#include <ImfFrameBuffer.h>
15#include <ImfHeader.h>
16#include <ImfInputFile.h>
17#include <ImfOutputFile.h>
18
19#include <ImfStringAttribute.h>
20#include "exr_extra_tags.h"
21
22#include <QApplication>
23#include <QMessageBox>
24#include <QDomDocument>
25#include <QThread>
26
27
30#include <KoColorSpaceTraits.h>
32#include <KoColor.h>
33#include <KoColorProfile.h>
34
35#include <KisDocument.h>
36#include <kis_group_layer.h>
37#include <kis_image.h>
38#include <kis_paint_device.h>
39#include <kis_paint_layer.h>
40#include <kis_transaction.h>
41#include "kis_iterator_ng.h"
43
44#include <kis_meta_data_entry.h>
47#include <kis_meta_data_store.h>
48#include <kis_meta_data_value.h>
49
51
53#include <KisPortingUtils.h>
54
55// Do not translate!
56#define HDR_LAYER "HDR Layer"
57
58template<typename _T_>
59struct Rgba {
60 _T_ r;
61 _T_ g;
62 _T_ b;
63 _T_ a;
64};
65
67
75
80
87
93
95 QMap< QString, QString> channelMap;
96
97 struct Remap {
98 Remap(const QString& _original, const QString& _current) : original(_original), current(_current) {
99 }
100 QString original;
101 QString current;
102 };
103
105 void updateImageType(ImageType channelType);
106};
107
109{
110 if (imageType == IT_UNKNOWN) {
111 imageType = channelType;
112 }
113 else if (imageType != channelType) {
115 }
116}
117
125
128 : doc(0)
129 , alphaWasModified(false)
130 , showNotifications(false)
131 {}
132
135
138
140
141 template <class WrapperType>
142 void unmultiplyAlpha(typename WrapperType::pixel_type *pixel);
143
144 template<typename _T_>
145 void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
146
147 template<typename _T_>
148 void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
149
150
151 QDomDocument loadExtraLayersInfo(const Imf::Header &header);
152 bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames);
153 void makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo>& informationObjects);
154 void recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo>& informationObjects, const QString& name, KisGroupLayerSP parent);
155 void reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved);
156 QString fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo>& informationObjects);
157};
158
159EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications)
160 : d(new Private)
161{
162 d->doc = doc;
163 d->showNotifications = showNotifications;
164
165 // Set thread count for IlmImf library
166 Imf::setGlobalThreadCount(QThread::idealThreadCount());
167 dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount();
168}
169
173
174ImageType imfTypeToKisType(Imf::PixelType type)
175{
176 switch (type) {
177 case Imf::UINT:
178 case Imf::NUM_PIXELTYPES:
179 return IT_UNSUPPORTED;
180 case Imf::HALF:
181 return IT_FLOAT16;
182 case Imf::FLOAT:
183 return IT_FLOAT32;
184 default:
185 qFatal("Out of bound enum");
186 return IT_UNKNOWN;
187 }
188}
189
190const KoColorSpace *kisTypeToColorSpace(QString colorModelID, ImageType imageType)
191{
192
193 QString colorDepthID = "UNKNOWN";
194 switch(imageType) {
195 case IT_FLOAT16:
196 colorDepthID = Float16BitsColorDepthID.id();
197 break;
198 case IT_FLOAT32:
199 colorDepthID = Float32BitsColorDepthID.id();
200 break;
201 default:
202 return 0;
203 };
204
205 const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID);
206 const QString defaultProfileForColorSpace = KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId);
207
212 const QString profileName =
213 colorModelID == RGBAColorModelID.id() ?
214 KisConfig(false).readEntry("ExrDefaultColorProfile", defaultProfileForColorSpace) :
215 defaultProfileForColorSpace;
216
217 return KoColorSpaceRegistry::instance()->colorSpace(colorModelID, colorDepthID, profileName);
218
219}
220
221template <typename T>
222static inline T alphaEpsilon()
223{
224 return static_cast<T>(HALF_EPSILON);
225}
226
227template <typename T>
228static inline T alphaNoiseThreshold()
229{
230 return static_cast<T>(0.01); // 1%
231}
232
233static inline bool qFuzzyCompare(half p1, half p2)
234{
235 return std::abs(p1 - p2) < float(HALF_EPSILON);
236}
237
238static inline bool qFuzzyIsNull(half h)
239{
240 return std::abs(h) < float(HALF_EPSILON);
241}
242
243template <typename T>
245{
246 typedef T channel_type;
248
249 RgbPixelWrapper(Rgba<T> &_pixel) : pixel(_pixel) {}
250
251 inline T alpha() const {
252 return pixel.a;
253 }
254
256 return !(std::abs(pixel.a) <= alphaEpsilon<T>() &&
257 (!qFuzzyIsNull(pixel.r) ||
258 !qFuzzyIsNull(pixel.g) ||
259 !qFuzzyIsNull(pixel.b)));
260 }
261
262 inline bool checkUnmultipliedColorsConsistent(const Rgba<T> &mult) const {
263 const T alpha = std::abs(pixel.a);
264
265 return alpha >= alphaNoiseThreshold<T>() ||
266 (qFuzzyCompare(T(pixel.r * alpha), mult.r) &&
267 qFuzzyCompare(T(pixel.g * alpha), mult.g) &&
268 qFuzzyCompare(T(pixel.b * alpha), mult.b));
269 }
270
271 inline void setUnmultiplied(const Rgba<T> &mult, T newAlpha) {
272 const T absoluteAlpha = std::abs(newAlpha);
273
274 pixel.r = mult.r / absoluteAlpha;
275 pixel.g = mult.g / absoluteAlpha;
276 pixel.b = mult.b / absoluteAlpha;
277 pixel.a = newAlpha;
278 }
279
281};
282
283template <typename T>
285{
286 typedef T channel_type;
288
289 GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {}
290
291 inline T alpha() const {
292 return pixel.alpha;
293 }
294
296 return !(std::abs(pixel.alpha) <= alphaEpsilon<T>() &&
298 }
299
300 inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const {
301 const T alpha = std::abs(pixel.alpha);
302
303 return alpha >= alphaNoiseThreshold<T>() ||
304 qFuzzyCompare(T(pixel.gray * alpha), mult.gray);
305 }
306
307 inline void setUnmultiplied(const pixel_type &mult, T newAlpha) {
308 const T absoluteAlpha = std::abs(newAlpha);
309
310 pixel.gray = mult.gray / absoluteAlpha;
311 pixel.alpha = newAlpha;
312 }
313
315};
316
317template <class WrapperType>
318void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel)
319{
320 typedef typename WrapperType::pixel_type pixel_type;
321 typedef typename WrapperType::channel_type channel_type;
322
323 WrapperType srcPixel(*pixel);
324
325 if (!srcPixel.checkMultipliedColorsConsistent()) {
326
327 channel_type newAlpha = srcPixel.alpha();
328
329 pixel_type __dstPixelData;
330 WrapperType dstPixel(__dstPixelData);
331
336 while (1) {
337 dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha);
338
339 if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) {
340 break;
341 }
342
343 newAlpha += alphaEpsilon<channel_type>();
344 alphaWasModified = true;
345 }
346
347 *pixel = dstPixel.pixel;
348
349
350 } else if (srcPixel.alpha() > 0.0) {
351 srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha());
352 }
353}
354
355template <typename T, typename Pixel, int size, int alphaPos>
356void multiplyAlpha(Pixel *pixel)
357{
358 if (alphaPos >= 0) {
359 T alpha = pixel->data[alphaPos];
360
361 if (alpha > alphaEpsilon<T>()) {
362 for (int i = 0; i < size; ++i) {
363 if (i != alphaPos) {
364 pixel->data[i] *= alpha;
365 }
366 }
367
368 pixel->data[alphaPos] = alpha;
369 } else {
370 for (int i = 0; i < size; ++i) {
371 pixel->data[i] = 0;
372 }
373 }
374 }
375}
376
377template<typename _T_>
378void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
379{
380 typedef Rgba<_T_> Rgba;
381
382 QVector<Rgba> pixels(width * height);
383
384 bool hasAlpha = info.channelMap.contains("A");
385
386 Imf::FrameBuffer frameBuffer;
387 Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width;
388 frameBuffer.insert(info.channelMap["R"].toLatin1().constData(),
389 Imf::Slice(ptype, (char *) &frameBufferData->r,
390 sizeof(Rgba) * 1,
391 sizeof(Rgba) * width));
392 frameBuffer.insert(info.channelMap["G"].toLatin1().constData(),
393 Imf::Slice(ptype, (char *) &frameBufferData->g,
394 sizeof(Rgba) * 1,
395 sizeof(Rgba) * width));
396 frameBuffer.insert(info.channelMap["B"].toLatin1().constData(),
397 Imf::Slice(ptype, (char *) &frameBufferData->b,
398 sizeof(Rgba) * 1,
399 sizeof(Rgba) * width));
400 if (hasAlpha) {
401 frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
402 Imf::Slice(ptype, (char *) &frameBufferData->a,
403 sizeof(Rgba) * 1,
404 sizeof(Rgba) * width));
405 }
406
407 file.setFrameBuffer(frameBuffer);
408 file.readPixels(ystart, height + ystart - 1);
409 Rgba *rgba = pixels.data();
410
411 QRect paintRegion(xstart, ystart, width, height);
412 KisSequentialIterator it(layer->paintDevice(), paintRegion);
413 while (it.nextPixel()) {
414 if (hasAlpha) {
415 unmultiplyAlpha<RgbPixelWrapper<_T_> >(rgba);
416 }
417
418 typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast<typename KoRgbTraits<_T_>::Pixel*>(it.rawData());
419
420 dst->red = rgba->r;
421 dst->green = rgba->g;
422 dst->blue = rgba->b;
423 if (hasAlpha) {
424 dst->alpha = rgba->a;
425 } else {
426 dst->alpha = 1.0;
427 }
428
429 ++rgba;
430 }
431}
432
433template<typename _T_>
434void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
435{
436 typedef typename GrayPixelWrapper<_T_>::channel_type channel_type;
437 typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type;
438
441
442 QVector<pixel_type> pixels(width * height);
443
444 Q_ASSERT(info.channelMap.contains("Y"));
445 dbgFile << "Gray -> " << info.channelMap["Y"];
446
447 bool hasAlpha = info.channelMap.contains("A");
448 dbgFile << "Has Alpha:" << hasAlpha;
449
450
451 Imf::FrameBuffer frameBuffer;
452 pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width;
453 frameBuffer.insert(
454 info.channelMap["Y"].toLatin1().constData(),
455 Imf::Slice(ptype, (char *)&frameBufferData->gray, sizeof(pixel_type) * 1, sizeof(pixel_type) * width));
456
457 if (hasAlpha) {
458 frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
459 Imf::Slice(ptype, (char *) &frameBufferData->alpha,
460 sizeof(pixel_type) * 1,
461 sizeof(pixel_type) * width));
462 }
463
464 file.setFrameBuffer(frameBuffer);
465 file.readPixels(ystart, height + ystart - 1);
466
467 pixel_type *srcPtr = pixels.data();
468
469 QRect paintRegion(xstart, ystart, width, height);
470 KisSequentialIterator it(layer->paintDevice(), paintRegion);
471 while (it.nextPixel()) {
472 if (hasAlpha) {
473 unmultiplyAlpha<GrayPixelWrapper<_T_> >(srcPtr);
474 }
475
476 pixel_type* dstPtr = reinterpret_cast<pixel_type*>(it.rawData());
477
478 dstPtr->gray = srcPtr->gray;
479 dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0);
480
481 ++srcPtr;
482 } ;
483}
484
485bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2)
486{
487 if (idx1 > idx2) return true;
488 if (group.name == list[idx2]) {
489 return recCheckGroup(*group.parent, list, idx1, idx2 - 1);
490 }
491 return false;
492}
493
495{
496 if (idx1 > idx2) {
497 return 0;
498 }
499 // Look for the group
500 for (int i = 0; i < groups->size(); ++i) {
501 if (recCheckGroup(groups->at(i), list, idx1, idx2)) {
502 return &(*groups)[i];
503 }
504 }
505 // Create the group
507 info.name = list.at(idx2);
508 info.parent = searchGroup(groups, list, idx1, idx2 - 1);
509 groups->append(info);
510 return &groups->last();
511}
512
513QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header)
514{
515 const Imf::StringAttribute *layersInfoAttribute =
516 header.findTypedAttribute<Imf::StringAttribute>(EXR_KRITA_LAYERS);
517
518 if (!layersInfoAttribute) return QDomDocument();
519
520 QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str());
521
522 QDomDocument doc;
523 doc.setContent(layersInfoString);
524
525 return doc;
526}
527
528bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames)
529{
530 std::set<std::string> extraInfoLayers;
531
532 QDomElement root = doc.documentElement();
533
534 KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; };
535
536 QDomElement el = root.firstChildElement();
537
538 while(!el.isNull()) {
539 KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; };
540 QString layerName = el.attribute(EXR_NAME).toUtf8();
541 if (layerName != QString(HDR_LAYER)) {
542 extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData());
543 }
544 el = el.nextSiblingElement();
545 }
546
547 bool result = (extraInfoLayers == exrLayerNames);
548
549 if (!result) {
550 dbgKrita << "WARNING: Krita EXR extra layers info is inconsistent!";
551 dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size());
552
553 std::set<std::string>::const_iterator it1 = extraInfoLayers.begin();
554 std::set<std::string>::const_iterator it2 = exrLayerNames.begin();
555
556 std::set<std::string>::const_iterator end1 = extraInfoLayers.end();
557
558 for (; it1 != end1; ++it1, ++it2) {
559 dbgKrita << it1->c_str() << it2->c_str();
560 }
561
562 }
563
564 return result;
565}
566
568{
569 try {
570 Imf::InputFile file(filename.toUtf8());
571
572 Imath::Box2i dw = file.header().dataWindow();
573 Imath::Box2i displayWindow = file.header().displayWindow();
574
575 int width = dw.max.x - dw.min.x + 1;
576 int height = dw.max.y - dw.min.y + 1;
577 int dx = dw.min.x;
578 int dy = dw.min.y;
579
580 // Display the attributes of a file
581 for (Imf::Header::ConstIterator it = file.header().begin();
582 it != file.header().end(); ++it) {
583 dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName();
584 }
585
586 // fetch Krita's extra layer info, which might have been stored previously
587 QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header());
588
589 // Construct the list of LayerInfo
590
591 QList<ExrPaintLayerInfo> informationObjects;
593
594 ImageType imageType = IT_UNKNOWN;
595
596 const Imf::ChannelList &channels = file.header().channels();
597 std::set<std::string> layerNames;
598 channels.layers(layerNames);
599
600 if (!extraLayersInfo.isNull() &&
601 !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) {
602
603 // it is inconsistent anyway
604 extraLayersInfo = QDomDocument();
605 }
606
607 // Check if there are A, R, G, B channels
608
609 dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:";
611 bool topLevelRGBFound = false;
612 info.name = HDR_LAYER;
613
614 QStringList topLevelChannelNames = QStringList() << "A"
615 << "R"
616 << "G"
617 << "B"
618 << ".A"
619 << ".R"
620 << ".G"
621 << ".B"
622 << "A."
623 << "R."
624 << "G."
625 << "B."
626 << "A."
627 << "R."
628 << "G."
629 << "B."
630 << ".alpha"
631 << ".red"
632 << ".green"
633 << ".blue"
634 << "X"
635 << "Y"
636 << "Z"
637 << ".X"
638 << ".Y"
639 << ".Z"
640 << "X."
641 << "Y."
642 << "Z.";
643
644 for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
645 const Imf::Channel &channel = i.channel();
646 dbgFile << "Channel name = " << i.name() << " type = " << channel.type;
647
648 QString qname = i.name();
649 if (topLevelChannelNames.contains(qname)) {
650 topLevelRGBFound = true;
651 dbgFile << "Found top-level channel" << qname;
652 info.channelMap[qname] = qname;
653 info.updateImageType(imfTypeToKisType(channel.type));
654 }
655 // Channel names that don't contain a "." or that contain a
656 // "." only at the beginning or at the end are not considered
657 // to be part of any layer.
658 else if (!qname.contains('.')
659 || !qname.mid(1).contains('.')
660 || !qname.left(qname.size() - 1).contains('.')) {
661 warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel.";
662 }
663 }
664 if (topLevelRGBFound) {
665 dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType;
666 informationObjects.push_back(info);
667 imageType = info.imageType;
668 }
669
670 dbgFile << "Extra layers:" << layerNames.size();
671
672 for (std::set<std::string>::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) {
673
674 info = ExrPaintLayerInfo();
675
676 dbgFile << "layer name = " << i->c_str();
677 info.name = i->c_str();
678 Imf::ChannelList::ConstIterator layerBegin, layerEnd;
679 channels.channelsInLayer(*i, layerBegin, layerEnd);
680 for (Imf::ChannelList::ConstIterator j = layerBegin;
681 j != layerEnd; ++j) {
682 const Imf::Channel &channel = j.channel();
683
684 info.updateImageType(imfTypeToKisType(channel.type));
685
686 QString qname = j.name();
687 QStringList list = qname.split('.');
688 QString layersuffix = list.last();
689
690 dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type;
691
692 // Nuke writes the channels for sublayers as .red instead of .R, so convert those.
693 // See https://bugs.kde.org/show_bug.cgi?id=393771
694 if (topLevelChannelNames.contains("." + layersuffix)) {
695 layersuffix = layersuffix.at(0).toUpper();
696 }
697 dbgFile << "\t\tsuffix" << layersuffix;
698
699
700 if (list.size() > 1) {
701 info.name = list[list.size()-2];
702 info.parent = searchGroup(&groups, list, 0, list.size() - 3);
703 }
704
705 info.channelMap[layersuffix] = qname;
706 }
707
708 if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) {
709 informationObjects.push_back(info);
710 if (imageType < info.imageType) {
711 imageType = info.imageType;
712 }
713 }
714 }
715
716 dbgFile << "File has" << informationObjects.size() << "layer(s)";
717
718 // Set the colorspaces
719 for (int i = 0; i < informationObjects.size(); ++i) {
720 ExrPaintLayerInfo& info = informationObjects[i];
721 QString modelId;
722
723 if (info.channelMap.size() == 1) {
724 modelId = GrayAColorModelID.id();
725 QString key = info.channelMap.begin().key();
726 if (key != "Y") {
727 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "Y"));
728 QString channel = info.channelMap.begin().value();
729 info.channelMap.clear();
730 info.channelMap["Y"] = channel;
731 }
732 }
733 else if (info.channelMap.size() == 2) {
734 modelId = GrayAColorModelID.id();
735
736 QMap<QString,QString>::const_iterator it = info.channelMap.constBegin();
737 QMap<QString,QString>::const_iterator end = info.channelMap.constEnd();
738
739 QString failingChannelKey;
740
741 for (; it != end; ++it) {
742 // BUG: 461975
743 if (it.key() != "A") {
744 failingChannelKey = it.key();
745 break;
746 }
747 }
748
749 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(failingChannelKey, "Y"));
750
751 QString failingChannelValue = info.channelMap[failingChannelKey];
752 info.channelMap.remove(failingChannelKey);
753 info.channelMap["Y"] = failingChannelValue;
754 }
755 else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) {
756
757 if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) {
758 modelId = RGBAColorModelID.id();
759 }
760 else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) {
761 modelId = XYZAColorModelID.id();
762 QMap<QString, QString> newChannelMap;
763 if (info.channelMap.contains("W")) {
764 newChannelMap["A"] = info.channelMap["W"];
765 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A"));
766 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X"));
767 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y"));
768 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z"));
769 } else if (info.channelMap.contains("A")) {
770 newChannelMap["A"] = info.channelMap["A"];
771 }
772 // The decode function expect R, G, B in the channel map
773 newChannelMap["R"] = info.channelMap["X"];
774 newChannelMap["G"] = info.channelMap["Y"];
775 newChannelMap["B"] = info.channelMap["Z"];
776 info.channelMap = newChannelMap;
777 }
778 else {
779 modelId = RGBAColorModelID.id();
780 QMap<QString, QString> newChannelMap;
781 QMap<QString, QString>::iterator it = info.channelMap.begin();
782 newChannelMap["R"] = it.value();
783 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R"));
784 ++it;
785 newChannelMap["G"] = it.value();
786 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G"));
787 ++it;
788 newChannelMap["B"] = it.value();
789 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B"));
790 if (info.channelMap.size() == 4) {
791 ++it;
792 newChannelMap["A"] = it.value();
793 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A"));
794 }
795
796 info.channelMap = newChannelMap;
797 }
798 }
799 else {
800 dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do.";
801 }
802 if (!modelId.isEmpty()) {
803 info.colorSpace = kisTypeToColorSpace(modelId, info.imageType);
804 }
805 }
806
807 // Get colorspace
808 dbgFile << "Image type = " << imageType;
809 const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType);
810
812 dbgFile << "Color space: " << colorSpace->name();
813
814 // Set the colorspace on all groups
815 for (int i = 0; i < groups.size(); ++i) {
816 ExrGroupLayerInfo& info = groups[i];
817 info.colorSpace = colorSpace;
818 }
819
820 // Create the image
821 // Make sure the created image is the same size as the displayWindow since
822 // the dataWindow can be cropped in some cases.
823 int displayWidth = displayWindow.max.x - displayWindow.min.x + 1;
824 int displayHeight = displayWindow.max.y - displayWindow.min.y + 1;
825 d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, "");
826
827 if (!d->image) {
829 }
830
839 //d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace));
840
841 // Create group layers
842 for (int i = 0; i < groups.size(); ++i) {
843 ExrGroupLayerInfo& info = groups[i];
844 Q_ASSERT(info.parent == 0 || info.parent->groupLayer);
845 KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
846 info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8);
847 d->image->addNode(info.groupLayer, groupLayerParent);
848 }
849
850 // Load the layers
851 for (int i = informationObjects.size() - 1; i >= 0; --i) {
852 ExrPaintLayerInfo& info = informationObjects[i];
853 if (info.colorSpace) {
854 dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id();
855 KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace);
856
857 if (!layer) {
859 }
860
862
863 switch (info.channelMap.size()) {
864 case 1:
865 case 2:
866 // Decode the data
867 switch (info.imageType) {
868 case IT_FLOAT16:
869 d->decodeData1<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
870 break;
871 case IT_FLOAT32:
872 d->decodeData1<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
873 break;
874 case IT_UNKNOWN:
875 case IT_UNSUPPORTED:
876 qFatal("Impossible error");
877 }
878 break;
879 case 3:
880 case 4:
881 // Decode the data
882 switch (info.imageType) {
883 case IT_FLOAT16:
884 d->decodeData4<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
885 break;
886 case IT_FLOAT32:
887 d->decodeData4<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
888 break;
889 case IT_UNKNOWN:
890 case IT_UNSUPPORTED:
891 qFatal("Impossible error");
892 }
893 break;
894 default:
895 qFatal("Invalid number of channels: %i", info.channelMap.size());
896 }
897 // Check if should set the channels
898 if (!info.remappedChannels.isEmpty()) {
900 Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) {
901 QMap<QString, KisMetaData::Value> map;
902 map["original"] = KisMetaData::Value(remap.original);
903 map["current"] = KisMetaData::Value(remap.current);
904 values.append(map);
905 }
906 layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values));
907 }
908 // Add the layer
909 KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
910 d->image->addNode(layer, groupLayerParent);
911 } else {
912 dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space";
913 }
914 }
915
916 // After reading the image, notify the user about changed alpha.
917 if (d->alphaWasModified) {
918 QString msg =
919 i18nc("@info",
920 "The image contains pixels with zero alpha channel and non-zero "
921 "color channels. Krita has modified those pixels to have "
922 "at least some alpha. The initial values will <i>not</i> "
923 "be reverted on saving the image back."
924 "<br/><br/>"
925 "This will hardly make any visual difference just keep it in mind.");
926 if (d->showNotifications) {
927 QMessageBox::information(qApp->activeWindow(), i18nc("@title:window", "EXR image has been modified"), msg);
928 } else {
929 warnKrita << "WARNING:" << msg;
930 }
931 }
932
933 if (!extraLayersInfo.isNull()) {
934 KisExrLayersSorter sorter(extraLayersInfo, d->image);
935 }
936
938
939 } catch (std::exception &e) {
940 dbgFile << "Error while reading from the exr file: " << e.what();
941
946 } else {
948 }
949 }
950
952}
953
955{
956 return decode(filename);
957
958}
959
960
962{
963 return d->image;
964}
965
967{
968 return d->errorMessage;
969}
970
971template<typename _T_, int size>
972struct ExrPixel_ {
973 _T_ data[size];
974};
975
977{
978public:
979 virtual ~Encoder() {}
980 virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0;
981 virtual void encodeData(int line) = 0;
982
983};
984
985template<typename _T_, int size, int alphaPos>
986class EncoderImpl : public Encoder
987{
988public:
989 EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {}
990 ~EncoderImpl() override {}
991 void prepareFrameBuffer(Imf::FrameBuffer*, int line) override;
992 void encodeData(int line) override;
993private:
995 Imf::OutputFile* file;
999};
1000
1001template<typename _T_, int size, int alphaPos>
1002void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line)
1003{
1004 int xstart = 0;
1005 int ystart = 0;
1006 ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width;
1007 for (int k = 0; k < size; ++k) {
1008 frameBuffer->insert(info->channels[k].toUtf8(),
1009 Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k],
1010 sizeof(ExrPixel) * 1,
1011 sizeof(ExrPixel) * m_width));
1012 }
1013}
1014
1015template<typename _T_, int size, int alphaPos>
1017{
1018 ExrPixel *rgba = pixels.data();
1019 KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width);
1020 do {
1021 const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData());
1022
1023 for (int i = 0; i < size; ++i) {
1024 rgba->data[i] = dst[i];
1025 }
1026
1027 if (alphaPos != -1) {
1028 multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba);
1029 }
1030
1031 ++rgba;
1032 } while (it->nextPixel());
1033}
1034
1035Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width)
1036{
1037 dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount();
1038 switch (info.layerDevice->colorSpace()->channelCount()) {
1039 case 1: {
1041 Q_ASSERT(info.pixelType == Imf::HALF);
1042 return new EncoderImpl < half, 1, -1 > (&file, &info, width);
1044 Q_ASSERT(info.pixelType == Imf::FLOAT);
1045 return new EncoderImpl < float, 1, -1 > (&file, &info, width);
1046 }
1047 break;
1048 }
1049 case 2: {
1051 Q_ASSERT(info.pixelType == Imf::HALF);
1052 return new EncoderImpl<half, 2, 1>(&file, &info, width);
1054 Q_ASSERT(info.pixelType == Imf::FLOAT);
1055 return new EncoderImpl<float, 2, 1>(&file, &info, width);
1056 }
1057 break;
1058 }
1059 case 4: {
1061 Q_ASSERT(info.pixelType == Imf::HALF);
1062 return new EncoderImpl<half, 4, 3>(&file, &info, width);
1064 Q_ASSERT(info.pixelType == Imf::FLOAT);
1065 return new EncoderImpl<float, 4, 3>(&file, &info, width);
1066 }
1067 break;
1068 }
1069 default:
1070 qFatal("Impossible error");
1071 }
1072 return 0;
1073}
1074
1075void encodeData(Imf::OutputFile& file, const QList<ExrPaintLayerSaveInfo>& informationObjects, int width, int height)
1076{
1077 QList<Encoder*> encoders;
1078 Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
1079 encoders.push_back(encoder(file, info, width));
1080 }
1081
1082 for (int y = 0; y < height; ++y) {
1083 Imf::FrameBuffer frameBuffer;
1084 Q_FOREACH (Encoder* encoder, encoders) {
1085 encoder->prepareFrameBuffer(&frameBuffer, y);
1086 }
1087 file.setFrameBuffer(frameBuffer);
1088 Q_FOREACH (Encoder* encoder, encoders) {
1089 encoder->encodeData(y);
1090 }
1091 file.writePixels(1);
1092 }
1093 qDeleteAll(encoders);
1094}
1095
1097{
1098 const KoColorSpace *cs = device->colorSpace();
1099
1105 const KoColorProfile *targetBestEffortProfile = nullptr;
1106 if (cs->colorModelId() == GrayAColorModelID ||
1107 cs->colorModelId() == RGBAColorModelID) {
1108 targetBestEffortProfile = cs->profile();
1109 }
1110
1115 targetBestEffortProfile);
1116 } else if (cs->colorModelId() != GrayAColorModelID &&
1117 cs->colorModelId() != RGBAColorModelID) {
1120 cs->colorDepthId().id());
1121 }
1122
1123 if (*cs != *device->colorSpace()) {
1124 device = new KisPaintDevice(*device);
1125 device->convertTo(cs);
1126 }
1127
1128 return device;
1129}
1130
1132{
1134
1135 KisImageSP image = layer->image();
1137
1138
1139 // Make the header
1140 qint32 height = image->height();
1141 qint32 width = image->width();
1142 Imf::Header header(width, height);
1143
1145 info.layer = layer;
1146 info.layerDevice = wrapLayerDevice(layer->paintDevice());
1147 Imf::PixelType pixelType = Imf::NUM_PIXELTYPES;
1149 pixelType = Imf::HALF;
1150 }
1152 pixelType = Imf::FLOAT;
1153 }
1154
1155 info.pixelType = pixelType;
1156
1158 header.channels().insert("R", Imf::Channel(pixelType));
1159 header.channels().insert("G", Imf::Channel(pixelType));
1160 header.channels().insert("B", Imf::Channel(pixelType));
1161 header.channels().insert("A", Imf::Channel(pixelType));
1162
1163 info.channels.push_back("R");
1164 info.channels.push_back("G");
1165 info.channels.push_back("B");
1166 info.channels.push_back("A");
1167 } else if (info.layerDevice->colorSpace()->colorModelId() == GrayAColorModelID) {
1168 header.channels().insert("Y", Imf::Channel(pixelType));
1169 header.channels().insert("A", Imf::Channel(pixelType));
1170
1171 info.channels.push_back("Y");
1172 info.channels.push_back("A");
1173 } else if (info.layerDevice->colorSpace()->colorModelId() == XYZAColorModelID) {
1174 header.channels().insert("X", Imf::Channel(pixelType));
1175 header.channels().insert("Y", Imf::Channel(pixelType));
1176 header.channels().insert("Z", Imf::Channel(pixelType));
1177 header.channels().insert("A", Imf::Channel(pixelType));
1178
1179 info.channels.push_back("X");
1180 info.channels.push_back("Y");
1181 info.channels.push_back("Z");
1182 info.channels.push_back("A");
1183 }
1184
1185 // Open file for writing
1186 try {
1187 Imf::OutputFile file(filename.toUtf8(), header);
1188
1189 QList<ExrPaintLayerSaveInfo> informationObjects;
1190 informationObjects.push_back(info);
1191 encodeData(file, informationObjects, width, height);
1192 return ImportExportCodes::OK;
1193
1194 } catch(std::exception &e) {
1195 dbgFile << "Exception while writing to exr file: " << e.what();
1198 }
1200 }
1201
1202}
1203
1204QString remap(const QMap<QString, QString>& current2original, const QString& current)
1205{
1206 if (current2original.contains(current)) {
1207 return current2original[current];
1208 }
1209 return current;
1210}
1211
1213{
1214 typedef QMultiMap<QString, QList<ExrPaintLayerSaveInfo>::iterator> NamesMap;
1215 NamesMap namesMap;
1216
1217 {
1218 QList<ExrPaintLayerSaveInfo>::iterator it = informationObjects.begin();
1219 QList<ExrPaintLayerSaveInfo>::iterator end = informationObjects.end();
1220
1221 for (; it != end; ++it) {
1222 namesMap.insert(it->name, it);
1223 }
1224 }
1225
1226 Q_FOREACH (const QString &key, namesMap.keys()) {
1227 if (namesMap.count(key) > 1) {
1228 KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; }
1229 QString strippedName = key.left(key.size() - 1); // trim the ending dot
1230 int nameCounter = 0;
1231
1232 NamesMap::iterator it = namesMap.find(key);
1233 NamesMap::iterator end = namesMap.end();
1234
1235 for (; it != end; ++it) {
1236 QString newName =
1237 QString("%1_%2.")
1238 .arg(strippedName)
1239 .arg(nameCounter++);
1240
1241 it.value()->name = newName;
1242
1243 QList<QString>::iterator channelsIt = it.value()->channels.begin();
1244 QList<QString>::iterator channelsEnd = it.value()->channels.end();
1245
1246 for (; channelsIt != channelsEnd; ++channelsIt) {
1247 channelsIt->replace(key, newName);
1248 }
1249 }
1250 }
1251 }
1252
1253}
1254
1256{
1257 QSet<KisNodeSP> layersNotSaved;
1258
1259 for (uint i = 0; i < parent->childCount(); ++i) {
1260 KisNodeSP node = parent->at(i);
1261
1262 if (KisPaintLayerSP paintLayer = dynamic_cast<KisPaintLayer*>(node.data())) {
1263 QMap<QString, QString> current2original;
1264
1265 if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) {
1266
1267 const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap");
1268 QList< KisMetaData::Value> values = entry.value().asArray();
1269
1270 Q_FOREACH (const KisMetaData::Value& value, values) {
1271 QMap<QString, KisMetaData::Value> map = value.asStructure();
1272 if (map.contains("original") && map.contains("current")) {
1273 current2original[map["current"].toString()] = map["original"].toString();
1274 }
1275 }
1276
1277 }
1278
1280 info.name = name + paintLayer->name() + '.';
1281 info.layer = paintLayer;
1282 info.layerDevice = wrapLayerDevice(paintLayer->paintDevice());
1283
1284 if (info.name == QString(HDR_LAYER) + ".") {
1285 info.channels.push_back("R");
1286 info.channels.push_back("G");
1287 info.channels.push_back("B");
1288 info.channels.push_back("A");
1289 }
1290 else {
1291
1293 info.channels.push_back(info.name + remap(current2original, "R"));
1294 info.channels.push_back(info.name + remap(current2original, "G"));
1295 info.channels.push_back(info.name + remap(current2original, "B"));
1296 info.channels.push_back(info.name + remap(current2original, "A"));
1297 }
1298 else if (info.layerDevice->colorSpace()->colorModelId() == GrayAColorModelID) {
1299 info.channels.push_back(info.name + remap(current2original, "Y"));
1300 info.channels.push_back(info.name + remap(current2original, "A"));
1301 } else if (info.layerDevice->colorSpace()->colorModelId() == XYZAColorModelID) {
1302 info.channels.push_back(info.name + remap(current2original, "X"));
1303 info.channels.push_back(info.name + remap(current2original, "Y"));
1304 info.channels.push_back(info.name + remap(current2original, "Z"));
1305 info.channels.push_back(info.name + remap(current2original, "A"));
1306 }
1307 }
1308
1310 info.pixelType = Imf::HALF;
1311 }
1313 info.pixelType = Imf::FLOAT;
1314 }
1315 else {
1316 info.pixelType = Imf::NUM_PIXELTYPES;
1317 }
1318
1319 if (info.pixelType < Imf::NUM_PIXELTYPES) {
1320 dbgFile << "Going to save layer" << info.name;
1321 informationObjects.push_back(info);
1322 }
1323 else {
1324 warnFile << "Will not save layer" << info.name;
1325 layersNotSaved << node;
1326 }
1327
1328 }
1329 else if (KisGroupLayerSP groupLayer = dynamic_cast<KisGroupLayer*>(node.data())) {
1330 recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer);
1331 }
1332 else {
1337 layersNotSaved.insert(node);
1338 }
1339 }
1340
1341 if (!layersNotSaved.isEmpty()) {
1342 reportLayersNotSaved(layersNotSaved);
1343 }
1344}
1345
1346void EXRConverter::Private::reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved)
1347{
1348 QString layersList;
1349 QTextStream textStream(&layersList);
1351
1352 Q_FOREACH (KisNodeSP node, layersNotSaved) {
1353 textStream << "<li>" << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "</li>";
1354 }
1355
1356 QString msg =
1357 i18nc("@info",
1358 "<p>The following layers have a type that is not supported by EXR format:</p>"
1359 "<r><ul>%1</ul></p>"
1360 "<p><warning>these layers have <b>not</b> been saved to the final EXR file</warning></p>", layersList);
1361
1362 errorMessage = msg;
1363}
1364
1366{
1367 KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty());
1368
1369 if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") {
1370 return QString();
1371 }
1372
1373 QDomDocument doc("krita-extra-layers-info");
1374 doc.appendChild(doc.createElement("root"));
1375 QDomElement rootElement = doc.documentElement();
1376
1377 for (int i = 0; i < informationObjects.size(); i++) {
1378 ExrPaintLayerSaveInfo &info = informationObjects[i];
1379 quint32 unused;
1380 KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false);
1381
1382 // EXR data is saved without the offset, saving code uses normal iterators
1383 // that don't know about the layer offset, hence passing `false` for saving
1384 // the offsets
1385 QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc, false);
1386
1387 // cut the ending '.'
1388 QString strippedName = info.name.left(info.name.size() - 1);
1389
1390 el.setAttribute(EXR_NAME, strippedName);
1391
1392 rootElement.appendChild(el);
1393 }
1394
1395 return doc.toString();
1396}
1397
1398KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten)
1399{
1401
1402 KisImageSP image = layer->image();
1404
1405 qint32 height = image->height();
1406 qint32 width = image->width();
1407 Imf::Header header(width, height);
1408
1409 if (flatten) {
1411 KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd);
1412 return buildFile(filename, l);
1413 }
1414 else {
1415 QList<ExrPaintLayerSaveInfo> informationObjects;
1416 d->recBuildPaintLayerSaveInfo(informationObjects, "", layer);
1417
1418 if(informationObjects.isEmpty()) {
1420 }
1421 d->makeLayerNamesUnique(informationObjects);
1422
1423 QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8();
1424 if (!extraLayersInfo.isNull()) {
1425 header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData()));
1426 }
1427 dbgFile << informationObjects.size() << " layers to save";
1428 Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
1429 if (info.pixelType < Imf::NUM_PIXELTYPES) {
1430 Q_FOREACH (const QString& channel, info.channels) {
1431 dbgFile << channel << " " << info.pixelType;
1432 header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType));
1433 }
1434 }
1435 }
1436
1437 // Open file for writing
1438 try {
1439 Imf::OutputFile file(filename.toUtf8(), header);
1440 encodeData(file, informationObjects, width, height);
1441 return ImportExportCodes::OK;
1442 } catch(std::exception &e) {
1443 dbgFile << "Exception while writing to exr file: " << e.what();
1444 if (!KisImportExportAdditionalChecks::isFileWritable(filename.toUtf8())) {
1446 }
1448 }
1449
1450 }
1451}
1452
1454{
1455 warnKrita << "WARNING: Cancelling of an EXR loading is not supported!";
1456}
1457
1458
float value(const T *src, size_t ch)
QPointF p2
QPointF p1
QList< QString > QStringList
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID XYZAColorModelID("XYZA", ki18n("XYZ/Alpha"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const quint8 OPACITY_OPAQUE_U8
const QString COMPOSITE_OVER
unsigned int uint
KisImportExportErrorCode buildFile(const QString &filename, KisPaintLayerSP layer)
~EXRConverter() override
QString errorMessage() const
KisImageSP image()
KisImportExportErrorCode buildImage(const QString &filename)
virtual void cancel()
const QScopedPointer< Private > d
KisImportExportErrorCode decode(const QString &filename)
EXRConverter(KisDocument *doc, bool showNotifications)
void encodeData(int line) override
void prepareFrameBuffer(Imf::FrameBuffer *, int line) override
ExrPixel_< _T_, size > ExrPixel
Imf::OutputFile * file
~EncoderImpl() override
QVector< ExrPixel > pixels
const ExrPaintLayerSaveInfo * info
EncoderImpl(Imf::OutputFile *_file, const ExrPaintLayerSaveInfo *_info, int width)
virtual void encodeData(int line)=0
virtual void prepareFrameBuffer(Imf::FrameBuffer *, int line)=0
virtual ~Encoder()
virtual const quint8 * oldRawData() const =0
virtual bool nextPixel()=0
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:789
KisPaintDeviceSP projection() const
qint32 width() const
qint32 height() const
const KisMetaData::Value & value() const
static KisMetaData::SchemaRegistry * instance()
const KisMetaData::Schema * create(const QString &uri, const QString &prefix)
bool addEntry(const Entry &entry)
QList< KisMetaData::Value > asArray() const
const KoColorSpace * colorSpace() const
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
QDomElement savePaintLayerAttributes(KisPaintLayer *layer, QDomDocument &doc, bool saveLayerOffset)
ALWAYS_INLINE quint8 * rawData()
virtual KoID colorModelId() const =0
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
QString id() const
Definition KoID.cpp:63
static bool qFuzzyCompare(half p1, half p2)
static T alphaNoiseThreshold()
static bool qFuzzyIsNull(half h)
bool recCheckGroup(const ExrGroupLayerInfo &group, QStringList list, int idx1, int idx2)
ImageType
@ IT_FLOAT32
@ IT_UNKNOWN
@ IT_FLOAT16
@ IT_UNSUPPORTED
QString remap(const QMap< QString, QString > &current2original, const QString &current)
#define HDR_LAYER
const KoColorSpace * kisTypeToColorSpace(QString colorModelID, ImageType imageType)
void multiplyAlpha(Pixel *pixel)
ExrGroupLayerInfo * searchGroup(QList< ExrGroupLayerInfo > *groups, QStringList list, int idx1, int idx2)
Encoder * encoder(Imf::OutputFile &file, const ExrPaintLayerSaveInfo &info, int width)
KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device)
ImageType imfTypeToKisType(Imf::PixelType type)
void encodeData(Imf::OutputFile &file, const QList< ExrPaintLayerSaveInfo > &informationObjects, int width, int height)
static T alphaEpsilon()
const char EXR_KRITA_LAYERS[]
const char EXR_NAME[]
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define dbgKrita
Definition kis_debug.h:45
#define warnFile
Definition kis_debug.h:95
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
#define dbgFile
Definition kis_debug.h:53
void setUtf8OnStream(QTextStream &stream)
uint32_t rgba
Definition pixels.h:44
void recBuildPaintLayerSaveInfo(QList< ExrPaintLayerSaveInfo > &informationObjects, const QString &name, KisGroupLayerSP parent)
QDomDocument loadExtraLayersInfo(const Imf::Header &header)
void makeLayerNamesUnique(QList< ExrPaintLayerSaveInfo > &informationObjects)
void unmultiplyAlpha(typename WrapperType::pixel_type *pixel)
QString fetchExtraLayersInfo(QList< ExrPaintLayerSaveInfo > &informationObjects)
void reportLayersNotSaved(const QSet< KisNodeSP > &layersNotSaved)
void decodeData4(Imf::InputFile &file, ExrPaintLayerInfo &info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set< std::string > exrLayerNames)
void decodeData1(Imf::InputFile &file, ExrPaintLayerInfo &info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
KisGroupLayerSP groupLayer
const KoColorSpace * colorSpace
const ExrGroupLayerInfo * parent
Remap(const QString &_original, const QString &_current)
QMap< QString, QString > channelMap
first is either R, G, B or A second is the EXR channel name
void updateImageType(ImageType channelType)
QList< Remap > remappedChannels
this is used to store in the metadata the mapping between exr channel name, and channels used in Krit...
QList< QString > channels
QString name
name of the layer with a "." at the end (ie "group1.group2.layer1.")
KisPaintDeviceSP layerDevice
Imf::PixelType pixelType
KisPaintLayerSP layer
_T_ data[size]
void setUnmultiplied(const pixel_type &mult, T newAlpha)
bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const
GrayPixelWrapper(pixel_type &_pixel)
pixel_type & pixel
KoGrayTraits< T >::Pixel pixel_type
bool checkMultipliedColorsConsistent() const
KisImageWSP image
QString name() const
void setCompositeOpId(const QString &compositeOpId)
KisMetaData::Store * metaData()
KisPaintDeviceSP paintDevice
QString colorSpaceId(const QString &colorModelId, const QString &colorDepthId) const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
QString defaultProfileForColorSpace(const QString &colorSpaceId) const
bool checkMultipliedColorsConsistent() const
void setUnmultiplied(const Rgba< T > &mult, T newAlpha)
RgbPixelWrapper(Rgba< T > &_pixel)
Rgba< T > & pixel
bool checkUnmultipliedColorsConsistent(const Rgba< T > &mult) const