1 /*
2  * This file is part of openfx-arena <https://github.com/olear/openfx-arena>,
3  * Copyright (C) 2016 INRIA
4  *
5  * openfx-arena is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as published
7  * by the Free Software Foundation.
8  *
9  * openfx-arena is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with openfx-arena.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
16 */
17 
18 #include <iostream>
19 #include <string>
20 #include <sys/stat.h>
21 #include <Magick++.h>
22 #include "GenericReader.h"
23 #include "GenericOCIO.h"
24 #include "ofxsMacros.h"
25 #include "ofxsMultiThread.h"
26 #include "ofxsImageEffect.h"
27 #include "ofxsMultiPlane.h"
28 #include <lcms2.h>
29 #include <dirent.h>
30 #include <ofxNatron.h>
31 #include <cstring>
32 
33 #define kPluginName "ReadPSD"
34 #define kPluginGrouping "Image/Readers"
35 #define kPluginIdentifier "net.fxarena.openfx.ReadPSD"
36 #define kPluginVersionMajor 2
37 #define kPluginVersionMinor 7
38 #define kPluginEvaluation 92
39 
40 #define kSupportsRGBA true
41 #define kSupportsRGB false
42 #define kSupportsXY false
43 #define kSupportsAlpha false
44 #define kSupportsTiles false
45 #define kIsMultiPlanar true
46 
47 #define kParamICC "icc"
48 #define kParamICCLabel "Color management"
49 #define kParamICCHint "Enable/Disable ICC color management\n\nRequires installed ICC v2/v4 color profiles."
50 #define kParamICCDefault false
51 
52 #define kParamICCIn "iccIn"
53 #define kParamICCInLabel "Input color profile"
54 #define kParamICCInHint "ICC input profile\n\nIf profile colorspace differs from image colorspace then a colorspace convert will happen."
55 #define kParamICCInSelected "iccInSelected"
56 
57 #define kParamICCOut "iccOut"
58 #define kParamICCOutLabel "Output color profile"
59 #define kParamICCOutHint "ICC RGB output profile\n\nIf image is CMYK/GRAY a colorspace convert will happen."
60 #define kParamICCOutDefault "sRGB"
61 #define kParamICCOutSelected "iccOutSelected"
62 
63 #define kParamICCRGB "iccRGB"
64 #define kParamICCRGBLabel "Default RGB profile"
65 #define kParamICCRGBHint "Default RGB profile\n\nUsed when a RGB image is missing an embedded color profile."
66 #define kParamICCRGBDefault "sRGB"
67 #define kParamICCRGBSelected "iccRGBSelected"
68 
69 #define kParamICCCMYK "iccCMYK"
70 #define kParamICCCMYKLabel "Default CMYK profile"
71 #define kParamICCCMYKHint "Default CMYK profile\n\nUsed when a CMYK image is missing an embedded color profile."
72 #define kParamICCCMYKDefault "U.S. Web Coated"
73 #define kParamICCCMYKSelected "iccCMYKSelected"
74 
75 #define kParamICCGRAY "iccGRAY"
76 #define kParamICCGRAYLabel "Default GRAY profile"
77 #define kParamICCGRAYHint "Default GRAY profile\n\nUsed when a GRAY image is missing an embedded color profile."
78 #define kParamICCGRAYDefault "Gray linear"
79 #define kParamICCGRAYSelected "iccGRAYSelected"
80 
81 #define kParamICCRender "renderingIntent"
82 #define kParamICCRenderLabel "Rendering intent"
83 #define kParamICCRenderHint "Rendering intent specifies the style of reproduction to be used."
84 #define kParamICCRenderDefault 2 //Perceptual
85 
86 #define kParamICCBlack "blackPoint"
87 #define kParamICCBlackLabel "Black point"
88 #define kParamICCBlackHint "Enable/Disable black point compensation"
89 #define kParamICCBlackDefault false
90 
91 #define kParamImageLayer "layer"
92 #define kParamImageLayerLabel "Image layer"
93 #define kParamImageLayerHint "Select image layer\n\nThe recommended way to access layers is through a merge/shuffle node (multi-plane)."
94 #define kParamImageLayerDefault 0
95 
96 #define kParamOffsetLayer "offset"
97 #define kParamOffsetLayerLabel "Offset layers"
98 #define kParamOffsetLayerHint "Enable/Disable layer offset"
99 #define kParamOffsetLayerDefault true
100 
101 using namespace OFX::IO;
102 
103 #ifdef OFX_IO_USING_OCIO
104 namespace OCIO = OCIO_NAMESPACE;
105 #endif
106 
107 OFXS_NAMESPACE_ANONYMOUS_ENTER
108 
109 static bool gHostIsNatron   = false;
110 
_setupChoice(OFX::ChoiceParam * visible,OFX::StringParam * hidden)111 void _setupChoice(OFX::ChoiceParam *visible, OFX::StringParam *hidden) {
112     std::string cString, cCombo;
113     hidden->getValue(cString);
114     int cID;
115     int cCount = visible->getNOptions();
116     visible->getValue(cID);
117     visible->getOption(cID, cCombo);
118     if (!cString.empty()) {
119         if (cCombo != cString) {
120             for(int x = 0; x < cCount; x++) {
121                 std::string cFound;
122                 visible->getOption(x, cFound);
123                 if (!cFound.empty()) {
124                     if (cFound == cString) {
125                         visible->setValue(x);
126                         break;
127                     }
128                 }
129             }
130         }
131     }
132     else {
133         if (!cCombo.empty())
134             hidden->setValue(cCombo);
135     }
136 }
137 
_getProFiles(std::vector<std::string> & files,bool desc,std::string filter,int colorspace)138 void _getProFiles(std::vector<std::string> &files, bool desc, std::string filter, int colorspace) {
139     std::vector<std::string> paths;
140     paths.push_back("/usr/share/color/icc/");
141     paths.push_back("\\Windows\\system32\\spool\\drivers\\color\\");
142     paths.push_back("/Library/ColorSync/Profiles/");
143     // TODO also add homedirs
144 
145     // get subfolders
146     for (unsigned int i = 0; i < paths.size(); i++) {
147         DIR *dp;
148         struct dirent *dirp;
149         if ((dp=opendir(paths[i].c_str())) != NULL) {
150             while ((dirp=readdir(dp)) != NULL) {
151                 std::ostringstream path;
152                 std::string proFile = dirp->d_name;
153                 path << paths[i] << proFile;
154                 struct stat sb;
155                 if (stat(path.str().c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
156                     if (proFile !="." && proFile != "..")
157                         paths.push_back(path.str()+"/");
158                 }
159             }
160         }
161         if (dp)
162             closedir(dp);
163     }
164 
165     // get profiles from paths
166     for (unsigned int i = 0; i < paths.size(); i++) {
167         DIR *dp;
168         struct dirent *dirp;
169         if ((dp=opendir(paths[i].c_str())) != NULL) {
170             while ((dirp=readdir(dp)) != NULL) {
171                 std::string proFile = dirp->d_name;
172                 std::ostringstream profileDesc;
173                     cmsHPROFILE lcmsProfile;
174                     char buffer[500];
175                     std::ostringstream path;
176                     int iccColorspace = 0;
177                     path << paths[i] << proFile;
178                     lcmsProfile = cmsOpenProfileFromFile(path.str().c_str(), "r");
179                     if (lcmsProfile) {
180                         cmsGetProfileInfoASCII(lcmsProfile, cmsInfoDescription, "en", "US", buffer, 500);
181                         profileDesc << buffer;
182                         if(cmsGetColorSpace(lcmsProfile) == cmsSigRgbData)
183                             iccColorspace = 1;
184                         if(cmsGetColorSpace(lcmsProfile) == cmsSigCmykData)
185                             iccColorspace = 2;
186                         if(cmsGetColorSpace(lcmsProfile) == cmsSigGrayData)
187                             iccColorspace = 3;
188                     }
189                     cmsCloseProfile(lcmsProfile);
190                     if (!profileDesc.str().empty() && (colorspace==iccColorspace||colorspace>3)) {
191                         if (!filter.empty()) {
192                             if (profileDesc.str()==filter) {
193                                 files.push_back(path.str());
194                                 break;
195                             }
196                         }
197                         else {
198                             if (desc)
199                                 files.push_back(profileDesc.str());
200                             else
201                                 files.push_back(proFile);
202                         }
203                     }
204             }
205         }
206         if (dp)
207             closedir(dp);
208     }
209 }
210 
211 class ReadPSDPlugin : public GenericReaderPlugin
212 {
213 public:
214     ReadPSDPlugin(OfxImageEffectHandle handle, const std::vector<std::string>& extensions);
215     virtual ~ReadPSDPlugin();
216     virtual void restoreStateFromParams() OVERRIDE FINAL;
217 private:
isVideoStream(const std::string &)218     virtual bool isVideoStream(const std::string& /*filename*/) OVERRIDE FINAL { return false; }
decode(const std::string & filename,OfxTime time,int view,bool isPlayback,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,OFX::PixelComponentEnum pixelComponents,int pixelComponentCount,int rowBytes)219     virtual void decode(const std::string& filename, OfxTime time, int view, bool isPlayback, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds,
220                              OFX::PixelComponentEnum pixelComponents, int pixelComponentCount, int rowBytes) OVERRIDE FINAL
221     {
222         std::string rawComps;
223         switch (pixelComponents) {
224             case OFX::ePixelComponentAlpha:
225                 rawComps = kOfxImageComponentAlpha;
226                 break;
227             case OFX::ePixelComponentRGB:
228                 rawComps = kOfxImageComponentRGB;
229                 break;
230             case OFX::ePixelComponentRGBA:
231                 rawComps = kOfxImageComponentRGBA;
232                 break;
233             default:
234                 OFX::throwSuiteStatusException(kOfxStatFailed);
235                 return;
236         }
237         decodePlane(filename, time, view, isPlayback, renderWindow, pixelData, bounds, pixelComponents, pixelComponentCount, rawComps, rowBytes);
238     }
239     virtual OfxStatus getClipComponents(const OFX::ClipComponentsArguments& args, OFX::ClipComponentsSetter& clipComponents) OVERRIDE FINAL;
240     virtual void decodePlane(const std::string& filename, OfxTime time, int view, bool isPlayback, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds, OFX::PixelComponentEnum pixelComponents, int pixelComponentCount, const std::string& rawComponents, int rowBytes) OVERRIDE FINAL;
241     virtual bool getFrameBounds(const std::string& filename, OfxTime time, int view,OfxRectI *bounds, OfxRectI* format, double *par, std::string *error,int *tile_width, int *tile_height) OVERRIDE FINAL;
242     virtual void changedParam(const OFX::InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
243     virtual bool guessParamsFromFilename(const std::string& filename, std::string *colorspace, OFX::PreMultiplicationEnum *filePremult, OFX::PixelComponentEnum *components, int *componentCount) OVERRIDE FINAL;
244     virtual void changedFilename(const OFX::InstanceChangedArgs &args) OVERRIDE FINAL;
245     void genLayerMenu();
246     std::string _filename;
247     bool _hasLCMS;
248     std::vector<Magick::Image> _psd;
249     OFX::ChoiceParam *_iccIn;
250     OFX::StringParam *_iccInSelected;
251     OFX::ChoiceParam *_iccOut;
252     OFX::StringParam *_iccOutSelected;
253     OFX::BooleanParam *_doICC;
254     OFX::ChoiceParam *_iccRGB;
255     OFX::StringParam *_iccRGBSelected;
256     OFX::ChoiceParam *_iccCMYK;
257     OFX::StringParam *_iccCMYKSelected;
258     OFX::ChoiceParam *_iccGRAY;
259     OFX::StringParam *_iccGRAYSelected;
260     OFX::ChoiceParam *_iccRender;
261     OFX::BooleanParam *_iccBlack;
262     OFX::ChoiceParam *_imageLayer;
263     OFX::BooleanParam *_offsetLayer;
264 };
265 
ReadPSDPlugin(OfxImageEffectHandle handle,const std::vector<std::string> & extensions)266 ReadPSDPlugin::ReadPSDPlugin(OfxImageEffectHandle handle, const std::vector<std::string>& extensions)
267 : GenericReaderPlugin(handle, extensions, kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles,
268 #ifdef OFX_EXTENSIONS_NUKE
269 (OFX::getImageEffectHostDescription() && OFX::getImageEffectHostDescription()->isMultiPlanar) ? kIsMultiPlanar : false
270 #else
271 false
272 #endif
273 )
274 ,_hasLCMS(false)
275 {
276     Magick::InitializeMagick(NULL);
277 
278 #ifndef LEGACYIM
279     std::string delegates = MagickCore::GetMagickDelegates();
280     if (delegates.find("lcms") != std::string::npos)
281         _hasLCMS = true;
282 #endif
283 
284     _iccIn = fetchChoiceParam(kParamICCIn);
285     _iccOut = fetchChoiceParam(kParamICCOut);
286     _iccRGB = fetchChoiceParam(kParamICCRGB);
287     _iccCMYK = fetchChoiceParam(kParamICCCMYK);
288     _iccGRAY = fetchChoiceParam(kParamICCGRAY);
289     _doICC = fetchBooleanParam(kParamICC);
290     _iccRender = fetchChoiceParam(kParamICCRender);
291     _iccBlack = fetchBooleanParam(kParamICCBlack);
292     _imageLayer = fetchChoiceParam(kParamImageLayer);
293     _offsetLayer = fetchBooleanParam(kParamOffsetLayer);
294 
295     _iccInSelected = fetchStringParam(kParamICCInSelected);
296     _iccOutSelected = fetchStringParam(kParamICCOutSelected);
297     _iccRGBSelected = fetchStringParam(kParamICCRGBSelected);
298     _iccCMYKSelected = fetchStringParam(kParamICCCMYKSelected);
299     _iccGRAYSelected = fetchStringParam(kParamICCGRAYSelected);
300 
301     assert(_iccIn && _iccOut && _doICC && _iccRGB && _iccCMYK && _iccGRAY && _iccRender && _iccBlack && _imageLayer && _offsetLayer && _iccInSelected && _iccOutSelected && _iccRGBSelected && _iccCMYKSelected && _iccGRAYSelected);
302 
303     _setupChoice(_iccIn, _iccInSelected);
304     _setupChoice(_iccOut, _iccOutSelected);
305     _setupChoice(_iccRGB, _iccRGBSelected);
306     _setupChoice(_iccCMYK, _iccCMYKSelected);
307     _setupChoice(_iccGRAY, _iccGRAYSelected);
308 }
309 
~ReadPSDPlugin()310 ReadPSDPlugin::~ReadPSDPlugin()
311 {
312 }
313 
restoreStateFromParams()314 void ReadPSDPlugin::restoreStateFromParams()
315 {
316     GenericReaderPlugin::restoreStateFromParams();
317 
318     int startingTime = getStartingTime();
319     std::string filename;
320     OfxStatus st = getFilenameAtTime(startingTime, &filename);
321     if ( st == kOfxStatOK || !filename.empty() ) {
322         _psd.clear();
323         try {
324             Magick::readImages(&_psd, filename);
325         }
326         catch(Magick::Exception) {
327             setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
328             OFX::throwSuiteStatusException(kOfxStatErrFormat);
329         }
330         genLayerMenu();
331         int layer = 0;
332         _imageLayer->getValue(layer);
333         if (!_psd.empty() && _psd[layer].columns()>0 && _psd[layer].rows()>0) {
334             _filename = filename;
335         } else {
336             _psd.clear();
337             setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
338         }
339     }
340 }
341 
genLayerMenu()342 void ReadPSDPlugin::genLayerMenu()
343 {
344     if (gHostIsNatron) {
345         _imageLayer->resetOptions();
346         int startLayer = 0;
347         if (!_psd.empty() && _psd[0].format() == "Adobe Photoshop bitmap") {
348             _imageLayer->appendOption("Default");
349             startLayer++; // first layer in a PSD is a comp
350         }
351         for (int i = startLayer; i < (int)_psd.size(); i++) {
352             std::ostringstream layerName;
353             layerName << _psd[i].label();
354             if (layerName.str().empty())
355                 layerName << "Layer " << i; // add a label if empty
356             _imageLayer->appendOption(layerName.str());
357         }
358     }
359 }
360 
getClipComponents(const OFX::ClipComponentsArguments & args,OFX::ClipComponentsSetter & clipComponents)361 OfxStatus ReadPSDPlugin::getClipComponents(const OFX::ClipComponentsArguments& args, OFX::ClipComponentsSetter& clipComponents)
362 {
363     #ifdef DEBUG
364     std::cout << "getClipComponents ..." << std::endl;
365     #endif
366 
367     assert(isMultiPlanar());
368     clipComponents.setPassThroughClip(NULL, args.time, args.view);
369     if (_psd.size()>0 && gHostIsNatron) { // what about nuke?
370         int startLayer = 0;
371         if (!_psd.empty() && _psd[0].format() == "Adobe Photoshop bitmap")
372             startLayer++; // first layer in a PSD is a comp
373         for (int i = startLayer; i < (int)_psd.size(); i++) {
374 
375             std::string layerName;
376             {
377                 std::ostringstream ss;
378                 if (!_psd[i].label().empty()) {
379                     ss << _psd[i].label();
380                 } else {
381                     ss << "Image Layer #" << i;
382                 }
383                 layerName = ss.str();
384             }
385             const char* components[4] = {"R","G","B", "A"};
386             OFX::MultiPlane::ImagePlaneDesc plane(layerName, layerName, "", components, 4);
387             clipComponents.addClipPlane(*_outputClip, OFX::MultiPlane::ImagePlaneDesc::mapPlaneToOFXPlaneString(plane));
388 
389         }
390 
391         // Also add the color plane
392         clipComponents.addClipPlane(*_outputClip, OFX::MultiPlane::ImagePlaneDesc::mapPlaneToOFXPlaneString(OFX::MultiPlane::ImagePlaneDesc::getRGBAComponents()));
393     }
394     return kOfxStatOK;
395 }
396 
decodePlane(const std::string & filename,OfxTime time,int,bool,const OfxRectI & renderWindow,float * pixelData,const OfxRectI & bounds,OFX::PixelComponentEnum,int,const std::string & rawComponents,int)397 void ReadPSDPlugin::decodePlane(const std::string& filename, OfxTime time, int /*view*/, bool /*isPlayback*/, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds,
398                                  OFX::PixelComponentEnum /*pixelComponents*/, int /*pixelComponentCount*/, const std::string& rawComponents, int /*rowBytes*/)
399 {
400     #ifdef DEBUG
401     std::cout << "decodePlane ..." << std::endl;
402     #endif
403 
404     int offsetX = 0;
405     int offsetY = 0;
406     int layer = 0;
407     int width = bounds.x2;
408     int height = bounds.y2;
409     bool color = false;
410     int iccRender = 0;
411     bool iccBlack = false;
412     int imageLayer = 0;
413     bool offsetLayer = false;
414 
415     OFX::MultiPlane::ImagePlaneDesc plane, paiedPlane;
416     OFX::MultiPlane::ImagePlaneDesc::mapOFXComponentsTypeStringToPlanes(rawComponents, &plane, &paiedPlane);
417 
418     std::string iccProfileIn, iccProfileOut, iccProfileRGB, iccProfileCMYK, iccProfileGRAY;
419     _iccInSelected->getValueAtTime(time, iccProfileIn);
420     _iccOutSelected->getValueAtTime(time, iccProfileOut);
421     _iccRGBSelected->getValueAtTime(time, iccProfileRGB);
422     _iccCMYKSelected->getValueAtTime(time, iccProfileCMYK);
423     _iccGRAYSelected->getValueAtTime(time, iccProfileGRAY);
424     _doICC->getValueAtTime(time, color);
425     _iccRender->getValueAtTime(time, iccRender);
426     _iccBlack->getValueAtTime(time, iccBlack);
427     _imageLayer->getValueAtTime(time, imageLayer);
428     _offsetLayer->getValueAtTime(time, offsetLayer);
429 
430     // Get multiplane layer
431     if (!plane.isColorPlane()) {
432         for (size_t i = 0; i < _psd.size(); i++) {
433             bool foundLayer = false;
434             std::ostringstream psdLayer;
435             psdLayer << "Image Layer #" << i; // if layer name is empty
436             if (_psd[i].label()==plane.getPlaneLabel())
437                 foundLayer = true;
438             if (psdLayer.str()==plane.getPlaneLabel() && !foundLayer)
439                 foundLayer = true;
440             if (foundLayer) {
441                 if (offsetLayer) {
442                     offsetX = _psd[i].page().xOff();
443                     offsetY = _psd[i].page().yOff();
444                 }
445                 layer = i;
446                 break;
447             }
448         }
449     }
450     else { // no multiplane
451         if (imageLayer>0 || _psd[imageLayer].format()!="Adobe Photoshop bitmap") {
452             if (offsetLayer) {
453                 offsetX = _psd[imageLayer].page().xOff();
454                 offsetY = _psd[imageLayer].page().yOff();
455             }
456         }
457         layer = imageLayer;
458     }
459 
460     // Get image
461     Magick::Image image;
462     if (_filename!=filename) { // anim?
463         std::ostringstream newFile;
464         newFile << filename << "[" << layer << "]";
465         image.read(newFile.str().c_str());
466     }
467     else
468         image = _psd[layer];
469 
470     // color management
471     if (color && _hasLCMS) {
472         // cascade menu
473         if (gHostIsNatron) {
474             iccProfileIn.erase(0,2);
475             iccProfileOut.erase(0,2);
476             iccProfileRGB.erase(0,2);
477             iccProfileCMYK.erase(0,2);
478             iccProfileGRAY.erase(0,2);
479         }
480         // blackpoint
481 #ifndef LEGACYIM
482         if (iccBlack)
483             image.blackPointCompensation(true);
484 #endif
485         // render intent
486         switch (iccRender) {
487         case 1: // SaturationIntent
488             image.renderingIntent(Magick::SaturationIntent);
489             break;
490         case 2: // PerceptualIntent
491             image.renderingIntent(Magick::PerceptualIntent);
492             break;
493         case 3: // AbsoluteIntent
494             image.renderingIntent(Magick::AbsoluteIntent);
495             break;
496         case 4: // RelativeIntent
497             image.renderingIntent(Magick::RelativeIntent);
498             break;
499         default:
500             //
501             break;
502         }
503         // default profile
504         std::string defaultProfile;
505         if (image.iccColorProfile().length()==0) {
506             std::vector<std::string> profileDef;
507             switch(image.colorSpace()) {
508             case Magick::RGBColorspace:
509                 if (!iccProfileRGB.empty())
510                     _getProFiles(profileDef, false, iccProfileRGB,1);
511                 break;
512             case Magick::sRGBColorspace:
513                 if (!iccProfileRGB.empty())
514                     _getProFiles(profileDef, false, iccProfileRGB,1);
515                 break;
516 #ifndef LEGACYIM
517             case Magick::scRGBColorspace:
518                 if (!iccProfileRGB.empty())
519                     _getProFiles(profileDef, false, iccProfileRGB,1);
520                 break;
521 #endif
522             case Magick::CMYKColorspace:
523                 if (!iccProfileCMYK.empty())
524                     _getProFiles(profileDef, false, iccProfileCMYK,2);
525                 break;
526             case Magick::GRAYColorspace:
527                 if (!iccProfileGRAY.empty())
528                     _getProFiles(profileDef, false, iccProfileGRAY,3);
529                 break;
530             default:
531                 //
532                 break;
533             }
534             if (profileDef.size()==1)
535                 defaultProfile=profileDef[0];
536         }
537         // ICC input
538         if (!iccProfileIn.empty() && iccProfileIn.find("None") == std::string::npos) {
539             std::vector<std::string> profile;
540             _getProFiles(profile, false, iccProfileIn,4);
541             if (profile.size()==1) {
542                 if (!profile[0].empty()) {
543                     if (!defaultProfile.empty()) { // apply default profile if not exist
544                         Magick::Blob iccBlobDef;
545                         Magick::Image iccExtractDef(defaultProfile);
546                         iccExtractDef.write(&iccBlobDef);
547                         if (iccBlobDef.length()>0)
548                             image.profile("ICC",iccBlobDef);
549                     }
550                     Magick::Blob iccBlob;
551                     Magick::Image iccExtract(profile[0]);
552                     iccExtract.write(&iccBlob);
553                     try { // catch final convert errors, like wrong profile compared to colorspace etc
554                         image.profile("ICC",iccBlob);
555                     }
556                     catch(Magick::Exception &error) {
557                         setPersistentMessage(OFX::Message::eMessageError, "", error.what());
558                         OFX::throwSuiteStatusException(kOfxStatFailed);
559                     }
560                 }
561             }
562         }
563         // ICC output
564         if (!iccProfileOut.empty() && iccProfileOut.find("None") == std::string::npos) {
565             std::vector<std::string> profile;
566             _getProFiles(profile, false, iccProfileOut,1);
567             if (profile.size()==1) {
568                 if (!profile[0].empty()) {
569                     if (!defaultProfile.empty() && (iccProfileIn.find("None") != std::string::npos)) { // apply default profile if not exist
570                         Magick::Blob iccBlobDef;
571                         Magick::Image iccExtractDef(defaultProfile);
572                         iccExtractDef.write(&iccBlobDef);
573                         if (iccBlobDef.length()>0)
574                             image.profile("ICC",iccBlobDef);
575                     }
576                     Magick::Blob iccBlob;
577                     Magick::Image iccExtract(profile[0]);
578                     iccExtract.write(&iccBlob);
579                     if (iccBlob.length()>0) {
580                         try { // catch final convert errors, like wrong profile compared to colorspace etc
581                             image.profile("ICC",iccBlob);
582                         }
583                         catch(Magick::Exception &error) {
584                             setPersistentMessage(OFX::Message::eMessageError, "", error.what());
585                             OFX::throwSuiteStatusException(kOfxStatFailed);
586                         }
587                     }
588                 }
589             }
590         }
591     }
592     else if (color && !_hasLCMS) {
593         setPersistentMessage(OFX::Message::eMessageError, "", "LCMS support missing in ImageMagick, unable to use color management");
594     }
595 
596     // Return image
597     Magick::Image container(Magick::Geometry(width,height),Magick::Color("rgba(0,0,0,0)"));
598     container.composite(image,offsetX,offsetY,Magick::OverCompositeOp);
599     container.flip();
600     container.write(0,0,renderWindow.x2 - renderWindow.x1,renderWindow.y2 - renderWindow.y1,"RGBA",Magick::FloatPixel,pixelData);
601 }
602 
changedParam(const OFX::InstanceChangedArgs & args,const std::string & paramName)603 void ReadPSDPlugin::changedParam(const OFX::InstanceChangedArgs &args, const std::string &paramName)
604 {
605     if (paramName == kParamICCIn) {
606         std::string profile;
607         int proID;
608         _iccIn->getValueAtTime(args.time, proID);
609         _iccIn->getOption(proID,profile);
610         _iccInSelected->setValueAtTime(args.time, profile);
611     }
612     else if (paramName == kParamICCOut) {
613         std::string profile;
614         int proID;
615         _iccOut->getValueAtTime(args.time, proID);
616         _iccOut->getOption(proID,profile);
617         _iccOutSelected->setValueAtTime(args.time, profile);
618     }
619     else if (paramName == kParamICCRGB) {
620         std::string profile;
621         int proID;
622         _iccRGB->getValueAtTime(args.time, proID);
623         _iccRGB->getOption(proID,profile);
624         _iccRGBSelected->setValueAtTime(args.time, profile);
625     }
626     else if (paramName == kParamICCCMYK) {
627         std::string profile;
628         int proID;
629         _iccCMYK->getValueAtTime(args.time, proID);
630         _iccCMYK->getOption(proID,profile);
631         _iccCMYKSelected->setValueAtTime(args.time, profile);
632     }
633     else if (paramName == kParamICCGRAY) {
634         std::string profile;
635         int proID;
636         _iccGRAY->getValueAtTime(args.time, proID);
637         _iccGRAY->getOption(proID,profile);
638         _iccGRAYSelected->setValueAtTime(args.time, profile);
639     }
640     else {
641         GenericReaderPlugin::changedParam(args,paramName);
642     }
643 }
644 
getFrameBounds(const std::string &,OfxTime,int,OfxRectI * bounds,OfxRectI * format,double * par,std::string *,int * tile_width,int * tile_height)645 bool ReadPSDPlugin::getFrameBounds(const std::string& /*filename*/,
646                               OfxTime /*time*/,
647                                    int /*view*/,
648                               OfxRectI *bounds,
649                               OfxRectI* format,
650                               double *par,
651                               std::string */*error*/,int *tile_width, int *tile_height)
652 {
653     #ifdef DEBUG
654     std::cout << "getFrameBounds ..." << std::endl;
655     #endif
656 
657     int layer = 0;
658     int maxWidth = 0;
659     int maxHeight = 0;
660     _imageLayer->getValue(layer);
661     if (!_psd.empty() && _psd[layer].columns()>0 && _psd[layer].rows()>0) {
662         for (int i = 0; i < (int)_psd.size(); i++) {
663             if ((int)_psd[i].columns()>maxWidth)
664                 maxWidth = (int)_psd[i].columns();
665             if ((int)_psd[i].rows()>maxHeight)
666                 maxHeight = (int)_psd[i].rows();
667         }
668     }
669     if (maxWidth>0 && maxHeight>0) {
670         bounds->x1 = 0;
671         bounds->x2 = maxWidth;
672         bounds->y1 = 0;
673         bounds->y2 = maxHeight;
674         *format = *bounds;
675         *par = 1.0;
676     }
677     *tile_width = *tile_height = 0;
678     return true;
679 }
680 
guessParamsFromFilename(const std::string &,std::string * colorspace,OFX::PreMultiplicationEnum * filePremult,OFX::PixelComponentEnum * components,int * componentCount)681 bool ReadPSDPlugin::guessParamsFromFilename(const std::string& /*newFile*/,
682                                        std::string *colorspace,
683                                        OFX::PreMultiplicationEnum *filePremult,
684                                        OFX::PixelComponentEnum *components,
685                                        int *componentCount)
686 {
687     assert(colorspace && filePremult && components && componentCount);
688 # ifdef OFX_IO_USING_OCIO
689     *colorspace = "sRGB";
690 # endif
691     int startingTime = getStartingTime();
692     std::string filename;
693     OfxStatus st = getFilenameAtTime(startingTime, &filename);
694     if ( st != kOfxStatOK || filename.empty() ) {
695         return false;
696     }
697 
698     _psd.clear();
699     try {
700         Magick::readImages(&_psd, filename);
701     }
702     catch(Magick::Exception) {
703         setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
704         OFX::throwSuiteStatusException(kOfxStatErrFormat);
705     }
706     genLayerMenu();
707     int layer = 0;
708     _imageLayer->getValue(layer);
709     if (!_psd.empty() && _psd[layer].columns()>0 && _psd[layer].rows()>0) {
710         _filename = filename;
711     } else {
712         _psd.clear();
713         setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
714     }
715 
716     *components = OFX::ePixelComponentRGBA;
717     *filePremult = OFX::eImageUnPreMultiplied;
718 
719     return true;
720 }
721 
changedFilename(const OFX::InstanceChangedArgs & args)722 void ReadPSDPlugin::changedFilename(const OFX::InstanceChangedArgs &args)
723 {
724     GenericReaderPlugin::changedFilename(args);
725 
726     int startingTime = getStartingTime();
727     std::string filename;
728     OfxStatus st = getFilenameAtTime(startingTime, &filename);
729     if ( st != kOfxStatOK || filename.empty() ) {
730         setPersistentMessage(OFX::Message::eMessageError, "", "No filename");
731         OFX::throwSuiteStatusException(kOfxStatErrFormat);
732     }
733 
734     _psd.clear();
735     try {
736         Magick::readImages(&_psd, filename);
737     }
738     catch(Magick::Exception) {
739         setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
740         OFX::throwSuiteStatusException(kOfxStatErrFormat);
741     }
742     genLayerMenu();
743     int layer = 0;
744     _imageLayer->getValue(layer);
745     if (!_psd.empty() && _psd[layer].columns()>0 && _psd[layer].rows()>0) {
746         _filename = filename;
747     } else {
748         _psd.clear();
749         setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
750     }
751 }
752 
753 using namespace OFX;
754 
755 mDeclareReaderPluginFactory(ReadPSDPluginFactory, {}, false);
756 
757 void
load()758 ReadPSDPluginFactory::load()
759 {
760     _extensions.clear();
761     _extensions.push_back("psd");
762     _extensions.push_back("xcf");
763 }
764 
765 
766 /** @brief The basic describe function, passed a plugin descriptor */
describe(OFX::ImageEffectDescriptor & desc)767 void ReadPSDPluginFactory::describe(OFX::ImageEffectDescriptor &desc)
768 {
769     GenericReaderDescribe(desc, _extensions, kPluginEvaluation, kSupportsTiles, kIsMultiPlanar);
770     desc.setLabel(kPluginName);
771 
772     desc.setPluginDescription("Read Photoshop/GIMP/Cinepaint (RGB/CMYK/GRAY) image formats with ICC color management.");
773 }
774 
775 /** @brief The describe in context function, passed a plugin descriptor and a context */
describeInContext(OFX::ImageEffectDescriptor & desc,ContextEnum context)776 void ReadPSDPluginFactory::describeInContext(OFX::ImageEffectDescriptor &desc, ContextEnum context)
777 {
778     gHostIsNatron = (OFX::getImageEffectHostDescription()->isNatron);
779 
780     PageParamDescriptor *page = GenericReaderDescribeInContextBegin(desc, context, isVideoStreamPlugin(), kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles, false);
781     {
782         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamImageLayer);
783         param->setLabel(kParamImageLayerLabel);
784         param->setHint(kParamImageLayerHint);
785         param->appendOption("Default");
786         param->appendOption("Layer 1"); // for non-natron:
787         param->appendOption("Layer 2");
788         param->appendOption("Layer 3");
789         param->appendOption("Layer 4");
790         param->appendOption("Layer 5");
791         param->appendOption("Layer 6");
792         param->appendOption("Layer 7");
793         param->appendOption("Layer 8");
794         param->appendOption("Layer 9");
795         page->addChild(*param);
796     }
797     {
798         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamOffsetLayer);
799         param->setLabel(kParamOffsetLayerLabel);
800         param->setHint(kParamOffsetLayerHint);
801         param->setDefault(kParamOffsetLayerDefault);
802         page->addChild(*param);
803     }
804     {
805         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamICC);
806         param->setLabel(kParamICCLabel);
807         param->setHint(kParamICCHint);
808         param->setDefault(kParamICCDefault);
809         page->addChild(*param);
810     }
811     {
812         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCRGB);
813         if (gHostIsNatron)
814             param->setCascading(OFX::getImageEffectHostDescription()->supportsCascadingChoices);
815         param->setLabel(kParamICCRGBLabel);
816         param->setHint(kParamICCRGBHint);
817         param->appendOption("None");
818         std::vector<std::string> profilesRGB;
819         _getProFiles(profilesRGB, true, "",1); // get RGB profiles
820         int defaultOpt = 0;
821         for (unsigned int i = 0;i < profilesRGB.size();i++) {
822             std::string proItem;
823             std::string proName = profilesRGB[i];
824             if (gHostIsNatron) {
825                 proItem=proName[0];
826                 proItem.append("/"+proName);
827             }
828             else
829                 proItem=proName;
830             param->appendOption(proItem);
831             if (profilesRGB[i].find(kParamICCRGBDefault) != std::string::npos) // set default
832                 defaultOpt=i;
833         }
834         if (defaultOpt>0) {
835             defaultOpt++;
836             param->setDefault(defaultOpt);
837         }
838         page->addChild(*param);
839     }
840     {
841         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCCMYK);
842         if (gHostIsNatron)
843             param->setCascading(OFX::getImageEffectHostDescription()->supportsCascadingChoices);
844         param->setLabel(kParamICCCMYKLabel);
845         param->setHint(kParamICCCMYKHint);
846         param->appendOption("None");
847         std::vector<std::string> profilesCMYK;
848         _getProFiles(profilesCMYK, true, "",2); // get CMYK profiles
849         int defaultOpt = 0;
850         for (unsigned int i = 0;i < profilesCMYK.size();i++) {
851             std::string proItem;
852             std::string proName = profilesCMYK[i];
853             if (gHostIsNatron) {
854                 proItem=proName[0];
855                 proItem.append("/"+proName);
856             }
857             else
858                 proItem=proName;
859             param->appendOption(proItem);
860             if (profilesCMYK[i].find(kParamICCCMYKDefault) != std::string::npos) // set default
861                 defaultOpt=i;
862         }
863         if (defaultOpt>0) {
864             defaultOpt++;
865             param->setDefault(defaultOpt);
866         }
867         page->addChild(*param);
868     }
869     {
870         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCGRAY);
871         if (gHostIsNatron)
872             param->setCascading(OFX::getImageEffectHostDescription()->supportsCascadingChoices);
873         param->setLabel(kParamICCGRAYLabel);
874         param->setHint(kParamICCGRAYHint);
875         param->appendOption("None");
876         std::vector<std::string> profilesGRAY;
877         _getProFiles(profilesGRAY, true, "",3); // get GRAY profiles
878         int defaultOpt = 0;
879         for (unsigned int i = 0;i < profilesGRAY.size();i++) {
880             std::string proItem;
881             std::string proName = profilesGRAY[i];
882             if (gHostIsNatron) {
883                 proItem=proName[0];
884                 proItem.append("/"+proName);
885             }
886             else
887                 proItem=proName;
888             param->appendOption(proItem);
889             if (profilesGRAY[i].find(kParamICCGRAYDefault) != std::string::npos) // set default
890                 defaultOpt=i;
891         }
892         if (defaultOpt>0) {
893             defaultOpt++;
894             param->setDefault(defaultOpt);
895         }
896         page->addChild(*param);
897     }
898     {
899         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCRender);
900         param->setLabel(kParamICCRenderLabel);
901         param->setHint(kParamICCRenderHint);
902         param->appendOption("Undefined");
903         param->appendOption("Saturation");
904         param->appendOption("Perceptual");
905         param->appendOption("Absolute");
906         param->appendOption("Relative");
907         param->setDefault(kParamICCRenderDefault);
908         page->addChild(*param);
909     }
910     {
911         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamICCBlack);
912         param->setLabel(kParamICCBlackLabel);
913         param->setHint(kParamICCBlackHint);
914         param->setDefault(kParamICCBlackDefault);
915         page->addChild(*param);
916     }
917     {
918         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCIn);
919         if (gHostIsNatron)
920             param->setCascading(OFX::getImageEffectHostDescription()->supportsCascadingChoices);
921         param->setLabel(kParamICCInLabel);
922         param->setHint(kParamICCInHint);
923         param->appendOption("None");
924         std::vector<std::string> profilesIn;
925         _getProFiles(profilesIn, true, "",4); // get RGB/CMYK/GRAY profiles
926         for (unsigned int i = 0;i < profilesIn.size();i++) {
927             std::string proItem;
928             std::string proName = profilesIn[i];
929             if (gHostIsNatron) {
930                 proItem=proName[0];
931                 proItem.append("/"+proName);
932             }
933             else
934                 proItem=proName;
935             param->appendOption(proItem);
936         }
937         page->addChild(*param);
938     }
939     {
940         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamICCOut);
941         if (gHostIsNatron)
942             param->setCascading(OFX::getImageEffectHostDescription()->supportsCascadingChoices);
943         param->setLabel(kParamICCOutLabel);
944         param->setHint(kParamICCOutHint);
945         param->appendOption("None");
946         std::vector<std::string> profilesOut;
947         _getProFiles(profilesOut, true, "",1); // get RGB profiles
948         int defaultOpt = 0;
949         for (unsigned int i = 0;i < profilesOut.size();i++) {
950             std::string proItem;
951             std::string proName = profilesOut[i];
952             if (gHostIsNatron) {
953                 proItem=proName[0];
954                 proItem.append("/"+proName);
955             }
956             else
957                 proItem=proName;
958             param->appendOption(proItem);
959             if (profilesOut[i].find(kParamICCOutDefault) != std::string::npos) // set default
960                 defaultOpt=i;
961         }
962         if (defaultOpt>0) {
963             defaultOpt++;
964             param->setDefault(defaultOpt);
965         }
966         page->addChild(*param);
967     }
968     {
969         StringParamDescriptor* param = desc.defineStringParam(kParamICCInSelected);
970         param->setStringType(eStringTypeSingleLine);
971         param->setAnimates(true);
972 
973         #ifdef DEBUG
974         param->setIsSecret(false);
975         #else
976         param->setIsSecret(true);
977         #endif
978 
979         page->addChild(*param);
980     }
981     {
982         StringParamDescriptor* param = desc.defineStringParam(kParamICCOutSelected);
983         param->setStringType(eStringTypeSingleLine);
984         param->setAnimates(true);
985 
986         #ifdef DEBUG
987         param->setIsSecret(false);
988         #else
989         param->setIsSecret(true);
990         #endif
991 
992         page->addChild(*param);
993     }
994     {
995         StringParamDescriptor* param = desc.defineStringParam(kParamICCRGBSelected);
996         param->setStringType(eStringTypeSingleLine);
997         param->setAnimates(true);
998 
999         #ifdef DEBUG
1000         param->setIsSecret(false);
1001         #else
1002         param->setIsSecret(true);
1003         #endif
1004 
1005         page->addChild(*param);
1006     }
1007     {
1008         StringParamDescriptor* param = desc.defineStringParam(kParamICCCMYKSelected);
1009         param->setStringType(eStringTypeSingleLine);
1010         param->setAnimates(true);
1011 
1012         #ifdef DEBUG
1013         param->setIsSecret(false);
1014         #else
1015         param->setIsSecret(true);
1016         #endif
1017 
1018         page->addChild(*param);
1019     }
1020     {
1021         StringParamDescriptor* param = desc.defineStringParam(kParamICCGRAYSelected);
1022         param->setStringType(eStringTypeSingleLine);
1023         param->setAnimates(true);
1024 
1025         #ifdef DEBUG
1026         param->setIsSecret(false);
1027         #else
1028         param->setIsSecret(true);
1029         #endif
1030         param->setLayoutHint(OFX::eLayoutHintDivider);
1031         page->addChild(*param);
1032     }
1033     GenericReaderDescribeInContextEnd(desc, context, page, "reference", "scene_linear");
1034 }
1035 
1036 /** @brief The create instance function, the plugin must return an object derived from the \ref OFX::ImageEffect class */
createInstance(OfxImageEffectHandle handle,ContextEnum)1037 ImageEffect* ReadPSDPluginFactory::createInstance(OfxImageEffectHandle handle,
1038                                      ContextEnum /*context*/)
1039 {
1040     ReadPSDPlugin* ret =  new ReadPSDPlugin(handle, _extensions);
1041     ret->restoreStateFromParams();
1042     return ret;
1043 }
1044 
1045 static ReadPSDPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1046 mRegisterPluginFactoryInstance(p)
1047 
1048 OFXS_NAMESPACE_ANONYMOUS_EXIT
1049