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 ¶mName) 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 ¶mName)
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