1 /*
2 * Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
3 * Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "exr_converter.h"
22
23 #include <half.h>
24
25 #include <ImfAttribute.h>
26 #include <ImfChannelList.h>
27 #include <ImfFrameBuffer.h>
28 #include <ImfHeader.h>
29 #include <ImfInputFile.h>
30 #include <ImfOutputFile.h>
31
32 #include <ImfStringAttribute.h>
33 #include "exr_extra_tags.h"
34
35 #include <QApplication>
36 #include <QMessageBox>
37 #include <QDomDocument>
38 #include <QThread>
39
40 #include <QFileInfo>
41
42 #include <KoColorSpaceRegistry.h>
43 #include <KoCompositeOpRegistry.h>
44 #include <KoColorSpaceTraits.h>
45 #include <KoColorModelStandardIds.h>
46 #include <KoColor.h>
47 #include <KoColorProfile.h>
48
49 #include <KisDocument.h>
50 #include <kis_group_layer.h>
51 #include <kis_image.h>
52 #include <kis_paint_device.h>
53 #include <kis_paint_layer.h>
54 #include <kis_transaction.h>
55 #include "kis_iterator_ng.h"
56 #include <kis_exr_layers_sorter.h>
57
58 #include <kis_meta_data_entry.h>
59 #include <kis_meta_data_schema.h>
60 #include <kis_meta_data_schema_registry.h>
61 #include <kis_meta_data_store.h>
62 #include <kis_meta_data_value.h>
63
64 #include "kis_kra_savexml_visitor.h"
65
66 #include <KisImportExportAdditionalChecks.h>
67
68 // Do not translate!
69 #define HDR_LAYER "HDR Layer"
70
71 template<typename _T_>
72 struct Rgba {
73 _T_ r;
74 _T_ g;
75 _T_ b;
76 _T_ a;
77 };
78
79 struct ExrGroupLayerInfo;
80
81 struct ExrLayerInfoBase {
ExrLayerInfoBaseExrLayerInfoBase82 ExrLayerInfoBase() : colorSpace(0), parent(0) {
83 }
84 const KoColorSpace* colorSpace;
85 QString name;
86 const ExrGroupLayerInfo* parent;
87 };
88
89 struct ExrGroupLayerInfo : public ExrLayerInfoBase {
ExrGroupLayerInfoExrGroupLayerInfo90 ExrGroupLayerInfo() : groupLayer(0) {}
91 KisGroupLayerSP groupLayer;
92 };
93
94 enum ImageType {
95 IT_UNKNOWN,
96 IT_FLOAT16,
97 IT_FLOAT32,
98 IT_UNSUPPORTED
99 };
100
101 struct ExrPaintLayerInfo : public ExrLayerInfoBase {
ExrPaintLayerInfoExrPaintLayerInfo102 ExrPaintLayerInfo()
103 : imageType(IT_UNKNOWN)
104 {
105 }
106
107 ImageType imageType;
108 QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name
109
110 struct Remap {
RemapExrPaintLayerInfo::Remap111 Remap(const QString& _original, const QString& _current) : original(_original), current(_current) {
112 }
113 QString original;
114 QString current;
115 };
116
117 QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita
118 void updateImageType(ImageType channelType);
119 };
120
updateImageType(ImageType channelType)121 void ExrPaintLayerInfo::updateImageType(ImageType channelType)
122 {
123 if (imageType == IT_UNKNOWN) {
124 imageType = channelType;
125 }
126 else if (imageType != channelType) {
127 imageType = IT_UNSUPPORTED;
128 }
129 }
130
131 struct ExrPaintLayerSaveInfo {
132 QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.")
133 KisPaintDeviceSP layerDevice;
134 KisPaintLayerSP layer;
135 QList<QString> channels;
136 Imf::PixelType pixelType;
137 };
138
139 struct EXRConverter::Private {
PrivateEXRConverter::Private140 Private()
141 : doc(0)
142 , alphaWasModified(false)
143 , showNotifications(false)
144 {}
145
146 KisImageSP image;
147 KisDocument *doc;
148
149 bool alphaWasModified;
150 bool showNotifications;
151
152 QString errorMessage;
153
154 template <class WrapperType>
155 void unmultiplyAlpha(typename WrapperType::pixel_type *pixel);
156
157 template<typename _T_>
158 void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
159
160 template<typename _T_>
161 void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
162
163
164 QDomDocument loadExtraLayersInfo(const Imf::Header &header);
165 bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames);
166 void makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo>& informationObjects);
167 void recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo>& informationObjects, const QString& name, KisGroupLayerSP parent);
168 void reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved);
169 QString fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo>& informationObjects);
170 };
171
EXRConverter(KisDocument * doc,bool showNotifications)172 EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications)
173 : d(new Private)
174 {
175 d->doc = doc;
176 d->showNotifications = showNotifications;
177
178 // Set thread count for IlmImf library
179 Imf::setGlobalThreadCount(QThread::idealThreadCount());
180 dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount();
181 }
182
~EXRConverter()183 EXRConverter::~EXRConverter()
184 {
185 }
186
imfTypeToKisType(Imf::PixelType type)187 ImageType imfTypeToKisType(Imf::PixelType type)
188 {
189 switch (type) {
190 case Imf::UINT:
191 case Imf::NUM_PIXELTYPES:
192 return IT_UNSUPPORTED;
193 case Imf::HALF:
194 return IT_FLOAT16;
195 case Imf::FLOAT:
196 return IT_FLOAT32;
197 default:
198 qFatal("Out of bound enum");
199 return IT_UNKNOWN;
200 }
201 }
202
kisTypeToColorSpace(QString colorModelID,ImageType imageType)203 const KoColorSpace *kisTypeToColorSpace(QString colorModelID, ImageType imageType)
204 {
205
206 QString colorDepthID = "UNKNOWN";
207 switch(imageType) {
208 case IT_FLOAT16:
209 colorDepthID = Float16BitsColorDepthID.id();
210 break;
211 case IT_FLOAT32:
212 colorDepthID = Float32BitsColorDepthID.id();
213 break;
214 default:
215 return 0;
216 };
217
218 const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorModelID, colorDepthID);
219 const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(colorSpaceId));
220
221 return KoColorSpaceRegistry::instance()->colorSpace(colorModelID, colorDepthID, profileName);
222
223 }
224
225 template <typename T>
alphaEpsilon()226 static inline T alphaEpsilon()
227 {
228 return static_cast<T>(HALF_EPSILON);
229 }
230
231 template <typename T>
alphaNoiseThreshold()232 static inline T alphaNoiseThreshold()
233 {
234 return static_cast<T>(0.01); // 1%
235 }
236
qFuzzyCompare(half p1,half p2)237 static inline bool qFuzzyCompare(half p1, half p2)
238 {
239 return std::abs(p1 - p2) < float(HALF_EPSILON);
240 }
241
qFuzzyIsNull(half h)242 static inline bool qFuzzyIsNull(half h)
243 {
244 return std::abs(h) < float(HALF_EPSILON);
245 }
246
247 template <typename T>
248 struct RgbPixelWrapper
249 {
250 typedef T channel_type;
251 typedef Rgba<T> pixel_type;
252
RgbPixelWrapperRgbPixelWrapper253 RgbPixelWrapper(Rgba<T> &_pixel) : pixel(_pixel) {}
254
alphaRgbPixelWrapper255 inline T alpha() const {
256 return pixel.a;
257 }
258
checkMultipliedColorsConsistentRgbPixelWrapper259 inline bool checkMultipliedColorsConsistent() const {
260 return !(std::abs(pixel.a) < alphaEpsilon<T>() &&
261 (!qFuzzyIsNull(pixel.r) ||
262 !qFuzzyIsNull(pixel.g) ||
263 !qFuzzyIsNull(pixel.b)));
264 }
265
checkUnmultipliedColorsConsistentRgbPixelWrapper266 inline bool checkUnmultipliedColorsConsistent(const Rgba<T> &mult) const {
267 const T alpha = std::abs(pixel.a);
268
269 return alpha >= alphaNoiseThreshold<T>() ||
270 (qFuzzyCompare(T(pixel.r * alpha), mult.r) &&
271 qFuzzyCompare(T(pixel.g * alpha), mult.g) &&
272 qFuzzyCompare(T(pixel.b * alpha), mult.b));
273 }
274
setUnmultipliedRgbPixelWrapper275 inline void setUnmultiplied(const Rgba<T> &mult, T newAlpha) {
276 const T absoluteAlpha = std::abs(newAlpha);
277
278 pixel.r = mult.r / absoluteAlpha;
279 pixel.g = mult.g / absoluteAlpha;
280 pixel.b = mult.b / absoluteAlpha;
281 pixel.a = newAlpha;
282 }
283
284 Rgba<T> &pixel;
285 };
286
287 template <typename T>
288 struct GrayPixelWrapper
289 {
290 typedef T channel_type;
291 typedef typename KoGrayTraits<T>::Pixel pixel_type;
292
GrayPixelWrapperGrayPixelWrapper293 GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {}
294
alphaGrayPixelWrapper295 inline T alpha() const {
296 return pixel.alpha;
297 }
298
checkMultipliedColorsConsistentGrayPixelWrapper299 inline bool checkMultipliedColorsConsistent() const {
300 return !(std::abs(pixel.alpha) < alphaEpsilon<T>() &&
301 !qFuzzyIsNull(pixel.gray));
302 }
303
checkUnmultipliedColorsConsistentGrayPixelWrapper304 inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const {
305 const T alpha = std::abs(pixel.alpha);
306
307 return alpha >= alphaNoiseThreshold<T>() ||
308 qFuzzyCompare(T(pixel.gray * alpha), mult.gray);
309 }
310
setUnmultipliedGrayPixelWrapper311 inline void setUnmultiplied(const pixel_type &mult, T newAlpha) {
312 const T absoluteAlpha = std::abs(newAlpha);
313
314 pixel.gray = mult.gray / absoluteAlpha;
315 pixel.alpha = newAlpha;
316 }
317
318 pixel_type &pixel;
319 };
320
321 template <class WrapperType>
unmultiplyAlpha(typename WrapperType::pixel_type * pixel)322 void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel)
323 {
324 typedef typename WrapperType::pixel_type pixel_type;
325 typedef typename WrapperType::channel_type channel_type;
326
327 WrapperType srcPixel(*pixel);
328
329 if (!srcPixel.checkMultipliedColorsConsistent()) {
330
331 channel_type newAlpha = srcPixel.alpha();
332
333 pixel_type __dstPixelData;
334 WrapperType dstPixel(__dstPixelData);
335
336 /**
337 * Division by a tiny alpha may result in an overflow of half
338 * value. That is why we use safe iterational approach.
339 */
340 while (1) {
341 dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha);
342
343 if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) {
344 break;
345 }
346
347 newAlpha += alphaEpsilon<channel_type>();
348 alphaWasModified = true;
349 }
350
351 *pixel = dstPixel.pixel;
352
353
354 } else if (srcPixel.alpha() > 0.0) {
355 srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha());
356 }
357 }
358
359 template <typename T, typename Pixel, int size, int alphaPos>
multiplyAlpha(Pixel * pixel)360 void multiplyAlpha(Pixel *pixel)
361 {
362 if (alphaPos >= 0) {
363 T alpha = pixel->data[alphaPos];
364
365 if (alpha > 0.0) {
366 for (int i = 0; i < size; ++i) {
367 if (i != alphaPos) {
368 pixel->data[i] *= alpha;
369 }
370 }
371
372 pixel->data[alphaPos] = alpha;
373 }
374 }
375 }
376
377 template<typename _T_>
decodeData4(Imf::InputFile & file,ExrPaintLayerInfo & info,KisPaintLayerSP layer,int width,int xstart,int ystart,int height,Imf::PixelType ptype)378 void 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
433 template<typename _T_>
decodeData1(Imf::InputFile & file,ExrPaintLayerInfo & info,KisPaintLayerSP layer,int width,int xstart,int ystart,int height,Imf::PixelType ptype)434 void 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
439 KIS_ASSERT_RECOVER_RETURN(
440 layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID);
441
442 QVector<pixel_type> pixels(width * height);
443
444 Q_ASSERT(info.channelMap.contains("G"));
445 dbgFile << "G -> " << info.channelMap["G"];
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(info.channelMap["G"].toLatin1().constData(),
454 Imf::Slice(ptype, (char *) &frameBufferData->gray,
455 sizeof(pixel_type) * 1,
456 sizeof(pixel_type) * width));
457
458 if (hasAlpha) {
459 frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
460 Imf::Slice(ptype, (char *) &frameBufferData->alpha,
461 sizeof(pixel_type) * 1,
462 sizeof(pixel_type) * width));
463 }
464
465 file.setFrameBuffer(frameBuffer);
466 file.readPixels(ystart, height + ystart - 1);
467
468 pixel_type *srcPtr = pixels.data();
469
470 QRect paintRegion(xstart, ystart, width, height);
471 KisSequentialIterator it(layer->paintDevice(), paintRegion);
472 while (it.nextPixel()) {
473 if (hasAlpha) {
474 unmultiplyAlpha<GrayPixelWrapper<_T_> >(srcPtr);
475 }
476
477 pixel_type* dstPtr = reinterpret_cast<pixel_type*>(it.rawData());
478
479 dstPtr->gray = srcPtr->gray;
480 dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0);
481
482 ++srcPtr;
483 } ;
484 }
485
recCheckGroup(const ExrGroupLayerInfo & group,QStringList list,int idx1,int idx2)486 bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2)
487 {
488 if (idx1 > idx2) return true;
489 if (group.name == list[idx2]) {
490 return recCheckGroup(*group.parent, list, idx1, idx2 - 1);
491 }
492 return false;
493 }
494
searchGroup(QList<ExrGroupLayerInfo> * groups,QStringList list,int idx1,int idx2)495 ExrGroupLayerInfo* searchGroup(QList<ExrGroupLayerInfo>* groups, QStringList list, int idx1, int idx2)
496 {
497 if (idx1 > idx2) {
498 return 0;
499 }
500 // Look for the group
501 for (int i = 0; i < groups->size(); ++i) {
502 if (recCheckGroup(groups->at(i), list, idx1, idx2)) {
503 return &(*groups)[i];
504 }
505 }
506 // Create the group
507 ExrGroupLayerInfo info;
508 info.name = list.at(idx2);
509 info.parent = searchGroup(groups, list, idx1, idx2 - 1);
510 groups->append(info);
511 return &groups->last();
512 }
513
loadExtraLayersInfo(const Imf::Header & header)514 QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header)
515 {
516 const Imf::StringAttribute *layersInfoAttribute =
517 header.findTypedAttribute<Imf::StringAttribute>(EXR_KRITA_LAYERS);
518
519 if (!layersInfoAttribute) return QDomDocument();
520
521 QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str());
522
523 QDomDocument doc;
524 doc.setContent(layersInfoString);
525
526 return doc;
527 }
528
checkExtraLayersInfoConsistent(const QDomDocument & doc,std::set<std::string> exrLayerNames)529 bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set<std::string> exrLayerNames)
530 {
531 std::set<std::string> extraInfoLayers;
532
533 QDomElement root = doc.documentElement();
534
535 KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; };
536
537 QDomElement el = root.firstChildElement();
538
539 while(!el.isNull()) {
540 KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; };
541 QString layerName = el.attribute(EXR_NAME).toUtf8();
542 if (layerName != QString(HDR_LAYER)) {
543 extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData());
544 }
545 el = el.nextSiblingElement();
546 }
547
548 bool result = (extraInfoLayers == exrLayerNames);
549
550 if (!result) {
551 dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!";
552 dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size());
553
554 std::set<std::string>::const_iterator it1 = extraInfoLayers.begin();
555 std::set<std::string>::const_iterator it2 = exrLayerNames.begin();
556
557 std::set<std::string>::const_iterator end1 = extraInfoLayers.end();
558
559 for (; it1 != end1; ++it1, ++it2) {
560 dbgKrita << it1->c_str() << it2->c_str();
561 }
562
563 }
564
565 return result;
566 }
567
decode(const QString & filename)568 KisImportExportErrorCode EXRConverter::decode(const QString &filename)
569 {
570 try {
571 Imf::InputFile file(filename.toUtf8());
572
573 Imath::Box2i dw = file.header().dataWindow();
574 Imath::Box2i displayWindow = file.header().displayWindow();
575
576 int width = dw.max.x - dw.min.x + 1;
577 int height = dw.max.y - dw.min.y + 1;
578 int dx = dw.min.x;
579 int dy = dw.min.y;
580
581 // Display the attributes of a file
582 for (Imf::Header::ConstIterator it = file.header().begin();
583 it != file.header().end(); ++it) {
584 dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName();
585 }
586
587 // fetch Krita's extra layer info, which might have been stored previously
588 QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header());
589
590 // Construct the list of LayerInfo
591
592 QList<ExrPaintLayerInfo> informationObjects;
593 QList<ExrGroupLayerInfo> groups;
594
595 ImageType imageType = IT_UNKNOWN;
596
597 const Imf::ChannelList &channels = file.header().channels();
598 std::set<std::string> layerNames;
599 channels.layers(layerNames);
600
601 if (!extraLayersInfo.isNull() &&
602 !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) {
603
604 // it is inconsistent anyway
605 extraLayersInfo = QDomDocument();
606 }
607
608 // Check if there are A, R, G, B channels
609
610 dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:";
611 ExrPaintLayerInfo info;
612 bool topLevelRGBFound = false;
613 info.name = HDR_LAYER;
614
615 QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B"
616 << ".A" << ".R" << ".G" << ".B"
617 << "A." << "R." << "G." << "B."
618 << "A." << "R." << "G." << "B."
619 << ".alpha" << ".red" << ".green" << ".blue";
620
621 for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
622 const Imf::Channel &channel = i.channel();
623 dbgFile << "Channel name = " << i.name() << " type = " << channel.type;
624
625 QString qname = i.name();
626 if (topLevelChannelNames.contains(qname)) {
627 topLevelRGBFound = true;
628 dbgFile << "Found top-level channel" << qname;
629 info.channelMap[qname] = qname;
630 info.updateImageType(imfTypeToKisType(channel.type));
631 }
632 // Channel names that don't contain a "." or that contain a
633 // "." only at the beginning or at the end are not considered
634 // to be part of any layer.
635 else if (!qname.contains('.')
636 || !qname.mid(1).contains('.')
637 || !qname.left(qname.size() - 1).contains('.')) {
638 warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel.";
639 }
640 }
641 if (topLevelRGBFound) {
642 dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType;
643 informationObjects.push_back(info);
644 imageType = info.imageType;
645 }
646
647 dbgFile << "Extra layers:" << layerNames.size();
648
649 for (std::set<std::string>::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) {
650
651 info = ExrPaintLayerInfo();
652
653 dbgFile << "layer name = " << i->c_str();
654 info.name = i->c_str();
655 Imf::ChannelList::ConstIterator layerBegin, layerEnd;
656 channels.channelsInLayer(*i, layerBegin, layerEnd);
657 for (Imf::ChannelList::ConstIterator j = layerBegin;
658 j != layerEnd; ++j) {
659 const Imf::Channel &channel = j.channel();
660
661 info.updateImageType(imfTypeToKisType(channel.type));
662
663 QString qname = j.name();
664 QStringList list = qname.split('.');
665 QString layersuffix = list.last();
666
667 dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type;
668
669 // Nuke writes the channels for sublayers as .red instead of .R, so convert those.
670 // See https://bugs.kde.org/show_bug.cgi?id=393771
671 if (topLevelChannelNames.contains("." + layersuffix)) {
672 layersuffix = layersuffix.at(0).toUpper();
673 }
674 dbgFile << "\t\tsuffix" << layersuffix;
675
676
677 if (list.size() > 1) {
678 info.name = list[list.size()-2];
679 info.parent = searchGroup(&groups, list, 0, list.size() - 3);
680 }
681
682 info.channelMap[layersuffix] = qname;
683 }
684
685 if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) {
686 informationObjects.push_back(info);
687 if (imageType < info.imageType) {
688 imageType = info.imageType;
689 }
690 }
691 }
692
693 dbgFile << "File has" << informationObjects.size() << "layer(s)";
694
695 // Set the colorspaces
696 for (int i = 0; i < informationObjects.size(); ++i) {
697 ExrPaintLayerInfo& info = informationObjects[i];
698 QString modelId;
699
700 if (info.channelMap.size() == 1) {
701 modelId = GrayAColorModelID.id();
702 QString key = info.channelMap.begin().key();
703 if (key != "G") {
704 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G"));
705 QString channel = info.channelMap.begin().value();
706 info.channelMap.clear();
707 info.channelMap["G"] = channel;
708 }
709 }
710 else if (info.channelMap.size() == 2) {
711 modelId = GrayAColorModelID.id();
712
713 QMap<QString,QString>::const_iterator it = info.channelMap.constBegin();
714 QMap<QString,QString>::const_iterator end = info.channelMap.constEnd();
715
716 QString failingChannelKey;
717
718 for (; it != end; ++it) {
719 if (it.key() != "G" && it.key() != "A") {
720 failingChannelKey = it.key();
721 break;
722 }
723 }
724
725 info.remappedChannels.push_back(
726 ExrPaintLayerInfo::Remap(failingChannelKey, "G"));
727
728 QString failingChannelValue = info.channelMap[failingChannelKey];
729 info.channelMap.remove(failingChannelKey);
730 info.channelMap["G"] = failingChannelValue;
731
732 }
733 else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) {
734
735 if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) {
736 modelId = RGBAColorModelID.id();
737 }
738 else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) {
739 modelId = XYZAColorModelID.id();
740 QMap<QString, QString> newChannelMap;
741 if (info.channelMap.contains("W")) {
742 newChannelMap["A"] = info.channelMap["W"];
743 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A"));
744 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X"));
745 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y"));
746 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z"));
747 } else if (info.channelMap.contains("A")) {
748 newChannelMap["A"] = info.channelMap["A"];
749 }
750 // The decode function expect R, G, B in the channel map
751 newChannelMap["B"] = info.channelMap["X"];
752 newChannelMap["G"] = info.channelMap["Y"];
753 newChannelMap["R"] = info.channelMap["Z"];
754 info.channelMap = newChannelMap;
755 }
756 else {
757 modelId = RGBAColorModelID.id();
758 QMap<QString, QString> newChannelMap;
759 QMap<QString, QString>::iterator it = info.channelMap.begin();
760 newChannelMap["R"] = it.value();
761 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R"));
762 ++it;
763 newChannelMap["G"] = it.value();
764 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G"));
765 ++it;
766 newChannelMap["B"] = it.value();
767 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B"));
768 if (info.channelMap.size() == 4) {
769 ++it;
770 newChannelMap["A"] = it.value();
771 info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A"));
772 }
773
774 info.channelMap = newChannelMap;
775 }
776 }
777 else {
778 dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do.";
779 }
780 if (!modelId.isEmpty()) {
781 info.colorSpace = kisTypeToColorSpace(modelId, info.imageType);
782 }
783 }
784
785 // Get colorspace
786 dbgFile << "Image type = " << imageType;
787 const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType);
788
789 if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported;
790 dbgFile << "Colorspace: " << colorSpace->name();
791
792 // Set the colorspace on all groups
793 for (int i = 0; i < groups.size(); ++i) {
794 ExrGroupLayerInfo& info = groups[i];
795 info.colorSpace = colorSpace;
796 }
797
798 // Create the image
799 // Make sure the created image is the same size as the displayWindow since
800 // the dataWindow can be cropped in some cases.
801 int displayWidth = displayWindow.max.x - displayWindow.min.x + 1;
802 int displayHeight = displayWindow.max.y - displayWindow.min.y + 1;
803 d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, "");
804
805 if (!d->image) {
806 return ImportExportCodes::Failure;
807 }
808
809 /**
810 * EXR semi-transparent images are expected to be rendered on
811 * black to ensure correctness of the light model
812 *
813 * NOTE: We cannot do that automatically, because the EXR may be imported
814 * into the image as a layer, in which case the default color will create
815 * major issues. See https://bugs.kde.org/show_bug.cgi?id=427720
816 */
817 //d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace));
818
819 // Create group layers
820 for (int i = 0; i < groups.size(); ++i) {
821 ExrGroupLayerInfo& info = groups[i];
822 Q_ASSERT(info.parent == 0 || info.parent->groupLayer);
823 KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
824 info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8);
825 d->image->addNode(info.groupLayer, groupLayerParent);
826 }
827
828 // Load the layers
829 for (int i = informationObjects.size() - 1; i >= 0; --i) {
830 ExrPaintLayerInfo& info = informationObjects[i];
831 if (info.colorSpace) {
832 dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id();
833 KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace);
834
835 if (!layer) {
836 return ImportExportCodes::Failure;
837 }
838
839 layer->setCompositeOpId(COMPOSITE_OVER);
840
841 switch (info.channelMap.size()) {
842 case 1:
843 case 2:
844 // Decode the data
845 switch (info.imageType) {
846 case IT_FLOAT16:
847 d->decodeData1<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
848 break;
849 case IT_FLOAT32:
850 d->decodeData1<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
851 break;
852 case IT_UNKNOWN:
853 case IT_UNSUPPORTED:
854 qFatal("Impossible error");
855 }
856 break;
857 case 3:
858 case 4:
859 // Decode the data
860 switch (info.imageType) {
861 case IT_FLOAT16:
862 d->decodeData4<half>(file, info, layer, width, dx, dy, height, Imf::HALF);
863 break;
864 case IT_FLOAT32:
865 d->decodeData4<float>(file, info, layer, width, dx, dy, height, Imf::FLOAT);
866 break;
867 case IT_UNKNOWN:
868 case IT_UNSUPPORTED:
869 qFatal("Impossible error");
870 }
871 break;
872 default:
873 qFatal("Invalid number of channels: %i", info.channelMap.size());
874 }
875 // Check if should set the channels
876 if (!info.remappedChannels.isEmpty()) {
877 QList<KisMetaData::Value> values;
878 Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) {
879 QMap<QString, KisMetaData::Value> map;
880 map["original"] = KisMetaData::Value(remap.original);
881 map["current"] = KisMetaData::Value(remap.current);
882 values.append(map);
883 }
884 layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values));
885 }
886 // Add the layer
887 KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
888 d->image->addNode(layer, groupLayerParent);
889 } else {
890 dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space";
891 }
892 }
893
894 // After reading the image, notify the user about changed alpha.
895 if (d->alphaWasModified) {
896 QString msg =
897 i18nc("@info",
898 "The image contains pixels with zero alpha channel and non-zero "
899 "color channels. Krita has modified those pixels to have "
900 "at least some alpha. The initial values will <i>not</i> "
901 "be reverted on saving the image back."
902 "<br/><br/>"
903 "This will hardly make any visual difference just keep it in mind.");
904 if (d->showNotifications) {
905 QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg);
906 } else {
907 warnKrita << "WARNING:" << msg;
908 }
909 }
910
911 if (!extraLayersInfo.isNull()) {
912 KisExrLayersSorter sorter(extraLayersInfo, d->image);
913 }
914
915 return ImportExportCodes::OK;
916
917 } catch (std::exception &e) {
918 dbgFile << "Error while reading from the exr file: " << e.what();
919
920 if (!KisImportExportAdditionalChecks::doesFileExist(filename)) {
921 return ImportExportCodes::FileNotExist;
922 } else if(!KisImportExportAdditionalChecks::isFileReadable(filename)) {
923 return ImportExportCodes::NoAccessToRead;
924 } else {
925 return ImportExportCodes::ErrorWhileReading;
926 }
927 }
928
929 return ImportExportCodes::OK;
930 }
931
buildImage(const QString & filename)932 KisImportExportErrorCode EXRConverter::buildImage(const QString &filename)
933 {
934 return decode(filename);
935
936 }
937
938
image()939 KisImageSP EXRConverter::image()
940 {
941 return d->image;
942 }
943
errorMessage() const944 QString EXRConverter::errorMessage() const
945 {
946 return d->errorMessage;
947 }
948
949 template<typename _T_, int size>
950 struct ExrPixel_ {
951 _T_ data[size];
952 };
953
954 class Encoder
955 {
956 public:
~Encoder()957 virtual ~Encoder() {}
958 virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0;
959 virtual void encodeData(int line) = 0;
960
961 };
962
963 template<typename _T_, int size, int alphaPos>
964 class EncoderImpl : public Encoder
965 {
966 public:
EncoderImpl(Imf::OutputFile * _file,const ExrPaintLayerSaveInfo * _info,int width)967 EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {}
~EncoderImpl()968 ~EncoderImpl() override {}
969 void prepareFrameBuffer(Imf::FrameBuffer*, int line) override;
970 void encodeData(int line) override;
971 private:
972 typedef ExrPixel_<_T_, size> ExrPixel;
973 Imf::OutputFile* file;
974 const ExrPaintLayerSaveInfo* info;
975 QVector<ExrPixel> pixels;
976 int m_width;
977 };
978
979 template<typename _T_, int size, int alphaPos>
prepareFrameBuffer(Imf::FrameBuffer * frameBuffer,int line)980 void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line)
981 {
982 int xstart = 0;
983 int ystart = 0;
984 ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width;
985 for (int k = 0; k < size; ++k) {
986 frameBuffer->insert(info->channels[k].toUtf8(),
987 Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k],
988 sizeof(ExrPixel) * 1,
989 sizeof(ExrPixel) * m_width));
990 }
991 }
992
993 template<typename _T_, int size, int alphaPos>
encodeData(int line)994 void EncoderImpl<_T_, size, alphaPos>::encodeData(int line)
995 {
996 ExrPixel *rgba = pixels.data();
997 KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width);
998 do {
999 const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData());
1000
1001 for (int i = 0; i < size; ++i) {
1002 rgba->data[i] = dst[i];
1003 }
1004
1005 if (alphaPos != -1) {
1006 multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba);
1007 }
1008
1009 ++rgba;
1010 } while (it->nextPixel());
1011 }
1012
encoder(Imf::OutputFile & file,const ExrPaintLayerSaveInfo & info,int width)1013 Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width)
1014 {
1015 dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount();
1016 switch (info.layerDevice->colorSpace()->channelCount()) {
1017 case 1: {
1018 if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1019 Q_ASSERT(info.pixelType == Imf::HALF);
1020 return new EncoderImpl < half, 1, -1 > (&file, &info, width);
1021 } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1022 Q_ASSERT(info.pixelType == Imf::FLOAT);
1023 return new EncoderImpl < float, 1, -1 > (&file, &info, width);
1024 }
1025 break;
1026 }
1027 case 2: {
1028 if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1029 Q_ASSERT(info.pixelType == Imf::HALF);
1030 return new EncoderImpl<half, 2, 1>(&file, &info, width);
1031 } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1032 Q_ASSERT(info.pixelType == Imf::FLOAT);
1033 return new EncoderImpl<float, 2, 1>(&file, &info, width);
1034 }
1035 break;
1036 }
1037 case 4: {
1038 if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1039 Q_ASSERT(info.pixelType == Imf::HALF);
1040 return new EncoderImpl<half, 4, 3>(&file, &info, width);
1041 } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1042 Q_ASSERT(info.pixelType == Imf::FLOAT);
1043 return new EncoderImpl<float, 4, 3>(&file, &info, width);
1044 }
1045 break;
1046 }
1047 default:
1048 qFatal("Impossible error");
1049 }
1050 return 0;
1051 }
1052
encodeData(Imf::OutputFile & file,const QList<ExrPaintLayerSaveInfo> & informationObjects,int width,int height)1053 void encodeData(Imf::OutputFile& file, const QList<ExrPaintLayerSaveInfo>& informationObjects, int width, int height)
1054 {
1055 QList<Encoder*> encoders;
1056 Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
1057 encoders.push_back(encoder(file, info, width));
1058 }
1059
1060 for (int y = 0; y < height; ++y) {
1061 Imf::FrameBuffer frameBuffer;
1062 Q_FOREACH (Encoder* encoder, encoders) {
1063 encoder->prepareFrameBuffer(&frameBuffer, y);
1064 }
1065 file.setFrameBuffer(frameBuffer);
1066 Q_FOREACH (Encoder* encoder, encoders) {
1067 encoder->encodeData(y);
1068 }
1069 file.writePixels(1);
1070 }
1071 qDeleteAll(encoders);
1072 }
1073
wrapLayerDevice(KisPaintDeviceSP device)1074 KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device)
1075 {
1076 const KoColorSpace *cs = device->colorSpace();
1077
1078 if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) {
1079 cs = KoColorSpaceRegistry::instance()->colorSpace(
1080 cs->colorModelId() == GrayAColorModelID ?
1081 GrayAColorModelID.id() : RGBAColorModelID.id(),
1082 Float16BitsColorDepthID.id());
1083 } else if (cs->colorModelId() != GrayAColorModelID &&
1084 cs->colorModelId() != RGBAColorModelID) {
1085 cs = KoColorSpaceRegistry::instance()->colorSpace(
1086 RGBAColorModelID.id(),
1087 cs->colorDepthId().id());
1088 }
1089
1090 if (*cs != *device->colorSpace()) {
1091 device = new KisPaintDevice(*device);
1092 device->convertTo(cs);
1093 }
1094
1095 return device;
1096 }
1097
buildFile(const QString & filename,KisPaintLayerSP layer)1098 KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer)
1099 {
1100 KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
1101
1102 KisImageSP image = layer->image();
1103 KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
1104
1105
1106 // Make the header
1107 qint32 height = image->height();
1108 qint32 width = image->width();
1109 Imf::Header header(width, height);
1110
1111 ExrPaintLayerSaveInfo info;
1112 info.layer = layer;
1113 info.layerDevice = wrapLayerDevice(layer->paintDevice());
1114 Imf::PixelType pixelType = Imf::NUM_PIXELTYPES;
1115 if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1116 pixelType = Imf::HALF;
1117 }
1118 else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1119 pixelType = Imf::FLOAT;
1120 }
1121 header.channels().insert("R", Imf::Channel(pixelType));
1122 header.channels().insert("G", Imf::Channel(pixelType));
1123 header.channels().insert("B", Imf::Channel(pixelType));
1124 header.channels().insert("A", Imf::Channel(pixelType));
1125
1126 info.channels.push_back("R");
1127 info.channels.push_back("G");
1128 info.channels.push_back("B");
1129 info.channels.push_back("A");
1130 info.pixelType = pixelType;
1131
1132 // Open file for writing
1133 try {
1134 Imf::OutputFile file(filename.toUtf8(), header);
1135
1136 QList<ExrPaintLayerSaveInfo> informationObjects;
1137 informationObjects.push_back(info);
1138 encodeData(file, informationObjects, width, height);
1139 return ImportExportCodes::OK;
1140
1141 } catch(std::exception &e) {
1142 dbgFile << "Exception while writing to exr file: " << e.what();
1143 if (!KisImportExportAdditionalChecks::isFileWritable(filename)) {
1144 return ImportExportCodes::NoAccessToWrite;
1145 }
1146 return ImportExportCodes::ErrorWhileWriting;
1147 }
1148
1149 }
1150
remap(const QMap<QString,QString> & current2original,const QString & current)1151 QString remap(const QMap<QString, QString>& current2original, const QString& current)
1152 {
1153 if (current2original.contains(current)) {
1154 return current2original[current];
1155 }
1156 return current;
1157 }
1158
makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo> & informationObjects)1159 void EXRConverter::Private::makeLayerNamesUnique(QList<ExrPaintLayerSaveInfo>& informationObjects)
1160 {
1161 typedef QMultiMap<QString, QList<ExrPaintLayerSaveInfo>::iterator> NamesMap;
1162 NamesMap namesMap;
1163
1164 {
1165 QList<ExrPaintLayerSaveInfo>::iterator it = informationObjects.begin();
1166 QList<ExrPaintLayerSaveInfo>::iterator end = informationObjects.end();
1167
1168 for (; it != end; ++it) {
1169 namesMap.insert(it->name, it);
1170 }
1171 }
1172
1173 Q_FOREACH (const QString &key, namesMap.keys()) {
1174 if (namesMap.count(key) > 1) {
1175 KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; }
1176 QString strippedName = key.left(key.size() - 1); // trim the ending dot
1177 int nameCounter = 0;
1178
1179 NamesMap::iterator it = namesMap.find(key);
1180 NamesMap::iterator end = namesMap.end();
1181
1182 for (; it != end; ++it) {
1183 QString newName =
1184 QString("%1_%2.")
1185 .arg(strippedName)
1186 .arg(nameCounter++);
1187
1188 it.value()->name = newName;
1189
1190 QList<QString>::iterator channelsIt = it.value()->channels.begin();
1191 QList<QString>::iterator channelsEnd = it.value()->channels.end();
1192
1193 for (; channelsIt != channelsEnd; ++channelsIt) {
1194 channelsIt->replace(key, newName);
1195 }
1196 }
1197 }
1198 }
1199
1200 }
1201
recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo> & informationObjects,const QString & name,KisGroupLayerSP parent)1202 void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList<ExrPaintLayerSaveInfo>& informationObjects, const QString& name, KisGroupLayerSP parent)
1203 {
1204 QSet<KisNodeSP> layersNotSaved;
1205
1206 for (uint i = 0; i < parent->childCount(); ++i) {
1207 KisNodeSP node = parent->at(i);
1208
1209 if (KisPaintLayerSP paintLayer = dynamic_cast<KisPaintLayer*>(node.data())) {
1210 QMap<QString, QString> current2original;
1211
1212 if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) {
1213
1214 const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap");
1215 QList< KisMetaData::Value> values = entry.value().asArray();
1216
1217 Q_FOREACH (const KisMetaData::Value& value, values) {
1218 QMap<QString, KisMetaData::Value> map = value.asStructure();
1219 if (map.contains("original") && map.contains("current")) {
1220 current2original[map["current"].toString()] = map["original"].toString();
1221 }
1222 }
1223
1224 }
1225
1226 ExrPaintLayerSaveInfo info;
1227 info.name = name + paintLayer->name() + '.';
1228 info.layer = paintLayer;
1229 info.layerDevice = wrapLayerDevice(paintLayer->paintDevice());
1230
1231 if (info.name == QString(HDR_LAYER) + ".") {
1232 info.channels.push_back("R");
1233 info.channels.push_back("G");
1234 info.channels.push_back("B");
1235 info.channels.push_back("A");
1236 }
1237 else {
1238
1239 if (info.layerDevice->colorSpace()->colorModelId() == RGBAColorModelID) {
1240 info.channels.push_back(info.name + remap(current2original, "R"));
1241 info.channels.push_back(info.name + remap(current2original, "G"));
1242 info.channels.push_back(info.name + remap(current2original, "B"));
1243 info.channels.push_back(info.name + remap(current2original, "A"));
1244 }
1245 else if (info.layerDevice->colorSpace()->colorModelId() == GrayAColorModelID) {
1246 info.channels.push_back(info.name + remap(current2original, "G"));
1247 info.channels.push_back(info.name + remap(current2original, "A"));
1248 }
1249 else if (info.layerDevice->colorSpace()->colorModelId() == GrayColorModelID) {
1250 info.channels.push_back(info.name + remap(current2original, "G"));
1251 }
1252 else if (info.layerDevice->colorSpace()->colorModelId() == XYZAColorModelID) {
1253 info.channels.push_back(info.name + remap(current2original, "X"));
1254 info.channels.push_back(info.name + remap(current2original, "Y"));
1255 info.channels.push_back(info.name + remap(current2original, "Z"));
1256 info.channels.push_back(info.name + remap(current2original, "A"));
1257 }
1258
1259 }
1260
1261 if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
1262 info.pixelType = Imf::HALF;
1263 }
1264 else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
1265 info.pixelType = Imf::FLOAT;
1266 }
1267 else {
1268 info.pixelType = Imf::NUM_PIXELTYPES;
1269 }
1270
1271 if (info.pixelType < Imf::NUM_PIXELTYPES) {
1272 dbgFile << "Going to save layer" << info.name;
1273 informationObjects.push_back(info);
1274 }
1275 else {
1276 warnFile << "Will not save layer" << info.name;
1277 layersNotSaved << node;
1278 }
1279
1280 }
1281 else if (KisGroupLayerSP groupLayer = dynamic_cast<KisGroupLayer*>(node.data())) {
1282 recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer);
1283 }
1284 else {
1285 /**
1286 * The EXR can store paint and group layers only. The rest will
1287 * go to /dev/null :(
1288 */
1289 layersNotSaved.insert(node);
1290 }
1291 }
1292
1293 if (!layersNotSaved.isEmpty()) {
1294 reportLayersNotSaved(layersNotSaved);
1295 }
1296 }
1297
reportLayersNotSaved(const QSet<KisNodeSP> & layersNotSaved)1298 void EXRConverter::Private::reportLayersNotSaved(const QSet<KisNodeSP> &layersNotSaved)
1299 {
1300 QString layersList;
1301 QTextStream textStream(&layersList);
1302 textStream.setCodec("UTF-8");
1303
1304 Q_FOREACH (KisNodeSP node, layersNotSaved) {
1305 textStream << "<li>" << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "</li>";
1306 }
1307
1308 QString msg =
1309 i18nc("@info",
1310 "<p>The following layers have a type that is not supported by EXR format:</p>"
1311 "<r><ul>%1</ul></p>"
1312 "<p><warning>these layers have <b>not</b> been saved to the final EXR file</warning></p>", layersList);
1313
1314 errorMessage = msg;
1315 }
1316
fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo> & informationObjects)1317 QString EXRConverter::Private::fetchExtraLayersInfo(QList<ExrPaintLayerSaveInfo>& informationObjects)
1318 {
1319 KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty());
1320
1321 if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") {
1322 return QString();
1323 }
1324
1325 QDomDocument doc("krita-extra-layers-info");
1326 doc.appendChild(doc.createElement("root"));
1327 QDomElement rootElement = doc.documentElement();
1328
1329 for (int i = 0; i < informationObjects.size(); i++) {
1330 ExrPaintLayerSaveInfo &info = informationObjects[i];
1331 quint32 unused;
1332 KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false);
1333 QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc);
1334 // cut the ending '.'
1335 QString strippedName = info.name.left(info.name.size() - 1);
1336
1337 el.setAttribute(EXR_NAME, strippedName);
1338
1339 rootElement.appendChild(el);
1340 }
1341
1342 return doc.toString();
1343 }
1344
buildFile(const QString & filename,KisGroupLayerSP layer,bool flatten)1345 KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten)
1346 {
1347 KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
1348
1349 KisImageSP image = layer->image();
1350 KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
1351
1352 qint32 height = image->height();
1353 qint32 width = image->width();
1354 Imf::Header header(width, height);
1355
1356 if (flatten) {
1357 KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
1358 KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd);
1359 return buildFile(filename, l);
1360 }
1361 else {
1362 QList<ExrPaintLayerSaveInfo> informationObjects;
1363 d->recBuildPaintLayerSaveInfo(informationObjects, "", layer);
1364
1365 if(informationObjects.isEmpty()) {
1366 return ImportExportCodes::FormatColorSpaceUnsupported;
1367 }
1368 d->makeLayerNamesUnique(informationObjects);
1369
1370 QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8();
1371 if (!extraLayersInfo.isNull()) {
1372 header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData()));
1373 }
1374 dbgFile << informationObjects.size() << " layers to save";
1375 Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
1376 if (info.pixelType < Imf::NUM_PIXELTYPES) {
1377 Q_FOREACH (const QString& channel, info.channels) {
1378 dbgFile << channel << " " << info.pixelType;
1379 header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType));
1380 }
1381 }
1382 }
1383
1384 // Open file for writing
1385 try {
1386 Imf::OutputFile file(filename.toUtf8(), header);
1387 encodeData(file, informationObjects, width, height);
1388 return ImportExportCodes::OK;
1389 } catch(std::exception &e) {
1390 dbgFile << "Exception while writing to exr file: " << e.what();
1391 if (!KisImportExportAdditionalChecks::isFileWritable(filename.toUtf8())) {
1392 return ImportExportCodes::NoAccessToWrite;
1393 }
1394 return ImportExportCodes::ErrorWhileWriting;
1395 }
1396
1397 }
1398 }
1399
cancel()1400 void EXRConverter::cancel()
1401 {
1402 warnKrita << "WARNING: Cancelling of an EXR loading is not supported!";
1403 }
1404
1405
1406