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 <stdint.h>
20 #include <zip.h>
21 #include <libxml/xmlmemory.h>
22 #include <libxml/parser.h>
23 #include <ofxNatron.h>
24 #include "GenericReader.h"
25 #include "GenericOCIO.h"
26 #include "ofxsMacros.h"
27 #include "ofxsImageEffect.h"
28 #include "ofxsMultiPlane.h"
29 #include "lodepng.h"
30
31 #define kPluginName "OpenRaster"
32 #define kPluginGrouping "Image/Readers"
33 #define kPluginIdentifier "fr.inria.openfx.OpenRaster"
34 #define kPluginVersionMajor 2
35 #define kPluginVersionMinor 1
36 #define kPluginEvaluation 50
37
38 #define kSupportsRGBA true
39 #define kSupportsRGB false
40 #define kSupportsXY false
41 #define kSupportsAlpha false
42 #define kSupportsTiles false
43 #define kIsMultiPlanar true
44
45 using namespace OFX::IO;
46
47 #ifdef OFX_IO_USING_OCIO
48 namespace OCIO = OCIO_NAMESPACE;
49 #endif
50
51 OFXS_NAMESPACE_ANONYMOUS_ENTER
52
53 static bool gHostIsNatron = false;
54
55 class OpenRasterPlugin : public GenericReaderPlugin
56 {
57 public:
58 OpenRasterPlugin(OfxImageEffectHandle handle, const std::vector<std::string>& extensions);
59 virtual ~OpenRasterPlugin();
60 virtual void restoreStateFromParams() OVERRIDE FINAL;
61 private:
isVideoStream(const std::string &)62 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)63 virtual void decode(const std::string& filename, OfxTime time, int view, bool isPlayback, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& bounds,
64 OFX::PixelComponentEnum pixelComponents, int pixelComponentCount, int rowBytes) OVERRIDE FINAL
65 {
66 std::string rawComps;
67 switch (pixelComponents) {
68 case OFX::ePixelComponentAlpha:
69 rawComps = kOfxImageComponentAlpha;
70 break;
71 case OFX::ePixelComponentRGB:
72 rawComps = kOfxImageComponentRGB;
73 break;
74 case OFX::ePixelComponentRGBA:
75 rawComps = kOfxImageComponentRGBA;
76 break;
77 default:
78 OFX::throwSuiteStatusException(kOfxStatFailed);
79 return;
80 }
81 decodePlane(filename, time, view, isPlayback, renderWindow, pixelData, bounds, pixelComponents, pixelComponentCount, rawComps, rowBytes);
82 }
83 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;
84 virtual OfxStatus getClipComponents(const OFX::ClipComponentsArguments& args, OFX::ClipComponentsSetter& clipComponents) OVERRIDE FINAL;
85 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;
86 virtual bool guessParamsFromFilename(const std::string& filename, std::string *colorspace, OFX::PreMultiplicationEnum *filePremult, OFX::PixelComponentEnum *components, int *componentCount) OVERRIDE FINAL;
87 virtual void changedFilename(const OFX::InstanceChangedArgs &args) OVERRIDE FINAL;
88 std::string extractXML(std::string filename);
89 void getImageSize(int *width, int *height, std::string filename);
90 bool hasMergedImage(std::string filename);
91 void getLayersInfo(xmlNode *node, std::vector<std::vector<std::string> > *layers);
92 std::vector<std::vector<std::string> > imageLayers;
93 };
94
OpenRasterPlugin(OfxImageEffectHandle handle,const std::vector<std::string> & extensions)95 OpenRasterPlugin::OpenRasterPlugin(OfxImageEffectHandle handle, const std::vector<std::string>& extensions)
96 : GenericReaderPlugin(handle, extensions, kSupportsRGBA, kSupportsRGB, kSupportsXY, kSupportsAlpha, kSupportsTiles,
97 #ifdef OFX_EXTENSIONS_NUKE
98 (OFX::getImageEffectHostDescription() && OFX::getImageEffectHostDescription()->isMultiPlanar) ? kIsMultiPlanar : false
99 #else
100 false
101 #endif
102 )
103 {
104 }
105
~OpenRasterPlugin()106 OpenRasterPlugin::~OpenRasterPlugin()
107 {
108 }
109
110 std::string
extractXML(std::string filename)111 OpenRasterPlugin::extractXML(std::string filename)
112 {
113 std::string output;
114 int err = 0;
115 zip *fileOpen = zip_open(filename.c_str(), 0, &err);
116 struct zip_stat xmlSt;
117 zip_stat_init(&xmlSt);
118 err=zip_stat(fileOpen,"stack.xml",0,&xmlSt);
119 if (err!=-1) {
120 char *xml = new char[xmlSt.size+1];
121 zip_file *xmlFile = zip_fopen(fileOpen,"stack.xml",0);
122 err=zip_fread(xmlFile,xml,xmlSt.size);
123 if (err!=-1) {
124 zip_fclose(xmlFile);
125 xml[xmlSt.size] = '\0';
126 output=xml;
127 }
128 delete[] xml;
129 }
130 zip_close(fileOpen);
131 return output;
132 }
133
134 void
getImageSize(int * width,int * height,std::string filename)135 OpenRasterPlugin::getImageSize(int *width, int *height, std::string filename)
136 {
137 std::string xml = extractXML(filename);
138 if (!xml.empty()) {
139 int imgW = 0;
140 int imgH = 0;
141 xmlDocPtr doc;
142 doc = xmlParseDoc((const xmlChar*)xml.c_str());
143 xmlNode *root_element = NULL;
144 xmlNode *cur_node = NULL;
145 root_element = xmlDocGetRootElement(doc);
146 for (cur_node = root_element; cur_node; cur_node = cur_node->next) {
147 if (cur_node->type == XML_ELEMENT_NODE) {
148 if ((!xmlStrcmp(cur_node->name,(const xmlChar*)"image"))) {
149 xmlChar *imgWchar;
150 xmlChar *imgHchar;
151 imgWchar = xmlGetProp(cur_node,(const xmlChar*)"w");
152 imgHchar = xmlGetProp(cur_node,(const xmlChar*)"h");
153 imgW = atoi((const char*)imgWchar);
154 imgH = atoi((const char*)imgHchar);
155 xmlFree(imgWchar);
156 xmlFree(imgHchar);
157 if (imgW>0 && imgH>0) {
158 (*width)=imgW;
159 (*height)=imgH;
160 }
161 }
162 }
163 }
164 xmlFreeDoc(doc);
165 }
166 }
167
168 void
getLayersInfo(xmlNode * node,std::vector<std::vector<std::string>> * layers)169 OpenRasterPlugin::getLayersInfo(xmlNode *node, std::vector<std::vector<std::string> > *layers)
170 {
171 xmlNode *cur_node = NULL;
172 for (cur_node = node; cur_node; cur_node = cur_node->next) {
173 if (cur_node->type == XML_ELEMENT_NODE) {
174 if ((!xmlStrcmp(cur_node->name, (const xmlChar *)"layer"))) {
175 std::vector<std::string> layerInfo;
176 xmlChar *xmlLayerName;
177 xmlChar *xmlOpacity;
178 xmlChar *xmlVisibility;
179 xmlChar *xmlComposite;
180 xmlChar *xmlPng;
181 xmlChar *xmlOffsetX;
182 xmlChar *xmlOffsetY;
183 xmlLayerName = xmlGetProp(cur_node, (const xmlChar *)"name");
184 xmlOpacity = xmlGetProp(cur_node, (const xmlChar *)"opacity"); // not used
185 xmlVisibility = xmlGetProp(cur_node, (const xmlChar *)"visibility"); // not used
186 xmlComposite = xmlGetProp(cur_node, (const xmlChar *)"composite-op"); // not used
187 xmlPng = xmlGetProp(cur_node, (const xmlChar *)"src");
188 xmlOffsetX = xmlGetProp(cur_node, (const xmlChar *)"x"); // a bit pointless since all layers are cropped in gfx app on save
189 xmlOffsetY = xmlGetProp(cur_node, (const xmlChar *)"y"); // a bit pointless since all layers are cropped in gfx app on save
190 std::string layerName,layerOpacity,layerVisibility,layerComposite,layerFile,layerOffsetX,layerOffsetY;
191
192 if (xmlLayerName!=NULL)
193 layerName=(reinterpret_cast<char*>(xmlLayerName));
194 if (xmlOpacity!=NULL)
195 layerOpacity=(reinterpret_cast<char*>(xmlOpacity));
196 if (xmlVisibility!=NULL)
197 layerVisibility=(reinterpret_cast<char*>(xmlVisibility));
198 if (xmlComposite!=NULL)
199 layerComposite=(reinterpret_cast<char*>(xmlComposite));
200 if (xmlPng!=NULL)
201 layerFile=(reinterpret_cast<char*>(xmlPng));
202 if (xmlOffsetX!=NULL)
203 layerOffsetX=(reinterpret_cast<char*>(xmlOffsetX));
204 if (xmlOffsetY!=NULL)
205 layerOffsetY=(reinterpret_cast<char*>(xmlOffsetY));
206
207 xmlFree(xmlLayerName);
208 xmlFree(xmlOpacity);
209 xmlFree(xmlVisibility);
210 xmlFree(xmlComposite);
211 xmlFree(xmlPng);
212 xmlFree(xmlOffsetX);
213 xmlFree(xmlOffsetY);
214
215 if (layerOpacity.empty())
216 layerOpacity = "1";
217 if (layerVisibility.empty())
218 layerVisibility = "1";
219 else {
220 if (layerVisibility=="hidden")
221 layerVisibility = "0";
222 else
223 layerVisibility = "1";
224 }
225 if (layerComposite.empty())
226 layerComposite = "svg:src-over";
227 if (layerOffsetX.empty())
228 layerOffsetX = "0";
229 if (layerOffsetY.empty())
230 layerOffsetY = "0";
231 if (!layerFile.empty() && !layerName.empty()) {
232 layerInfo.push_back(layerName);
233 layerInfo.push_back(layerFile);
234 layerInfo.push_back(layerOpacity);
235 layerInfo.push_back(layerVisibility);
236 layerInfo.push_back(layerComposite);
237 layerInfo.push_back(layerOffsetX);
238 layerInfo.push_back(layerOffsetY);
239 layers->push_back(layerInfo);
240 }
241 }
242 }
243 getLayersInfo(cur_node->children,layers);
244 }
245 }
246
247 bool
hasMergedImage(std::string filename)248 OpenRasterPlugin::hasMergedImage(std::string filename)
249 {
250 bool status = false;
251 if (!filename.empty()) {
252 int err = 0;
253 zip *layerOpen = zip_open(filename.c_str(),0,&err);
254 struct zip_stat layerSt;
255 zip_stat_init(&layerSt);
256 err=zip_stat(layerOpen,"mergedimage.png",0,&layerSt);
257 if (err!=-1) {
258 char *layerData = new char[layerSt.size];
259 zip_file *layerFile = zip_fopen(layerOpen,"mergedimage.png",0);
260 err=zip_fread(layerFile,layerData,layerSt.size);
261 if (err!=-1) {
262 zip_fclose(layerFile);
263 if ((layerData!=NULL)&&(layerSt.size>0)) {
264 status = true;
265 }
266 }
267 delete[] layerData;
268 }
269 zip_close(layerOpen);
270 }
271 return status;
272 }
273
274 OfxStatus
getClipComponents(const OFX::ClipComponentsArguments & args,OFX::ClipComponentsSetter & clipComponents)275 OpenRasterPlugin::getClipComponents(const OFX::ClipComponentsArguments& args, OFX::ClipComponentsSetter& clipComponents)
276 {
277 assert(isMultiPlanar());
278 clipComponents.setPassThroughClip(NULL, args.time, args.view);
279 if (imageLayers.size()>0 && gHostIsNatron) {
280 for (int i = 0; i < (int)imageLayers.size(); i++) {
281 std::string layerName;
282 {
283 std::ostringstream ss;
284 if (!imageLayers[i][0].empty()) {
285 ss << imageLayers[i][0];
286 } else {
287 ss << "Image Layer #" << i; // if layer name is empty
288 }
289 layerName = ss.str();
290 }
291 const char* components[4] = {"R","G","B", "A"};
292 OFX::MultiPlane::ImagePlaneDesc plane(layerName, layerName, "", components, 4);
293 clipComponents.addClipPlane(*_outputClip, OFX::MultiPlane::ImagePlaneDesc::mapPlaneToOFXPlaneString(plane));
294 }
295
296 // Also add the color plane
297 clipComponents.addClipPlane(*_outputClip, OFX::MultiPlane::ImagePlaneDesc::mapPlaneToOFXPlaneString(OFX::MultiPlane::ImagePlaneDesc::getRGBAComponents()));
298 }
299 return kOfxStatOK;
300 }
301
302 void
decodePlane(const std::string & filename,OfxTime,int,bool,const OfxRectI & renderWindow,float * pixelData,const OfxRectI &,OFX::PixelComponentEnum,int pixelComponentCount,const std::string & rawComponents,int)303 OpenRasterPlugin::decodePlane(const std::string& filename, OfxTime /*time*/, int /*view*/, bool /*isPlayback*/, const OfxRectI& renderWindow, float *pixelData, const OfxRectI& /*bounds*/,
304 OFX::PixelComponentEnum /*pixelComponents*/, int pixelComponentCount, const std::string& rawComponents, int /*rowBytes*/)
305 {
306 if (filename.empty()) {
307 setPersistentMessage(OFX::Message::eMessageError, "", "No filename");
308 OFX::throwSuiteStatusException(kOfxStatErrFormat);
309 }
310
311 if (imageLayers.size() == 0) {
312 setPersistentMessage(OFX::Message::eMessageError, "", "No layers");
313 OFX::throwSuiteStatusException(kOfxStatErrFormat);
314 }
315
316 int layer = 0;
317 if (gHostIsNatron) {
318
319 OFX::MultiPlane::ImagePlaneDesc plane, pairedPlane;
320 OFX::MultiPlane::ImagePlaneDesc::mapOFXComponentsTypeStringToPlanes(rawComponents, &plane, &pairedPlane);
321
322 if (!plane.isColorPlane()) {
323 for (int i = 0; i < (int)imageLayers.size(); i++) {
324 bool foundLayer = false;
325 std::ostringstream nonameLayer;
326 nonameLayer << "Image Layer #" << i; // if layer name is empty
327 if (imageLayers[i][0]==plane.getPlaneLabel())
328 foundLayer = true;
329 if (nonameLayer.str()==plane.getPlaneLabel() && !foundLayer)
330 foundLayer = true;
331 if (foundLayer) {
332 layer = i;
333 break;
334 }
335 }
336 }
337 else {
338 layer = 0;
339 }
340 }
341
342 int width = 0;
343 int height = 0;
344 unsigned char* buffer = NULL;
345 int renderWidth= renderWindow.x2 - renderWindow.x1;
346 int renderHeight= renderWindow.y2 - renderWindow.y1;
347
348 int err = 0;
349 zip *layerOpen = zip_open(filename.c_str(),0,&err);
350 struct zip_stat layerSt;
351 zip_stat_init(&layerSt);
352 err=zip_stat(layerOpen,imageLayers[layer][1].c_str(),0,&layerSt);
353 if (err!=-1) {
354 char *layerData = new char[layerSt.size];
355 zip_file *layerFile = zip_fopen(layerOpen,imageLayers[layer][1].c_str(),0);
356 err=zip_fread(layerFile,layerData,layerSt.size);
357 if (err!=-1) {
358 zip_fclose(layerFile);
359 if ((layerData!=NULL)&&(layerSt.size>0)) {
360 lodepng_decode32(&buffer,(unsigned int*)&width,(unsigned int*)&height,(unsigned char *)layerData,(size_t)layerSt.size);
361 }
362 }
363 delete[] layerData;
364 }
365 zip_close(layerOpen);
366
367 if (buffer==NULL || width!=renderWidth || height!=renderHeight) {
368 setPersistentMessage(OFX::Message::eMessageError, "", "Unable to read image");
369 OFX::throwSuiteStatusException(kOfxStatErrFormat);
370 }
371
372 unsigned char* pixels = new unsigned char[width * height * pixelComponentCount];
373 for (int i = 0; i < width; ++i) {
374 for (int j = 0; j < height; ++j) {
375 for (int k = 0; k < pixelComponentCount; ++k)
376 pixels[(i + j * width) * pixelComponentCount + k] = buffer[(i + (height - 1 - j) * width) * pixelComponentCount + k];
377 }
378 }
379
380 int offset = 0;
381 for (int y = 0; y < height; y++) {
382 for (int x = 0; x < width; x++) {
383 pixelData[offset + 0] = pixels[offset + 0] * (1.f / 255);
384 pixelData[offset + 1] = pixels[offset + 1] * (1.f / 255);
385 pixelData[offset + 2] = pixels[offset + 2] * (1.f / 255);
386 pixelData[offset + 3] = pixels[offset + 3] * (1.f / 255);
387 offset += pixelComponentCount;
388 }
389 }
390
391 buffer = NULL;
392 delete[] pixels;
393 }
394
getFrameBounds(const std::string & filename,OfxTime,int,OfxRectI * bounds,OfxRectI * format,double * par,std::string *,int * tile_width,int * tile_height)395 bool OpenRasterPlugin::getFrameBounds(const std::string& filename,
396 OfxTime /*time*/,
397 int /*view*/,
398 OfxRectI *bounds,
399 OfxRectI* format,
400 double *par,
401 std::string* /*error*/,int *tile_width, int *tile_height)
402 {
403 int width = 0;
404 int height = 0;
405 getImageSize(&width,&height,filename);
406 if (width>0 && height>0) {
407 bounds->x1 = 0;
408 bounds->x2 = width;
409 bounds->y1 = 0;
410 bounds->y2 = height;
411 *format = *bounds;
412 *par = 1.0;
413 }
414 *tile_width = *tile_height = 0;
415 return true;
416 }
417
guessParamsFromFilename(const std::string &,std::string * colorspace,OFX::PreMultiplicationEnum * filePremult,OFX::PixelComponentEnum * components,int * componentCount)418 bool OpenRasterPlugin::guessParamsFromFilename(const std::string& /*newFile*/,
419 std::string *colorspace,
420 OFX::PreMultiplicationEnum *filePremult,
421 OFX::PixelComponentEnum *components,
422 int *componentCount)
423 {
424 assert(colorspace && filePremult && components && componentCount);
425 # ifdef OFX_IO_USING_OCIO
426 *colorspace = "sRGB";
427 # endif
428 int startingTime = getStartingTime();
429 std::string filename;
430 OfxStatus st = getFilenameAtTime(startingTime, &filename);
431 if ( st != kOfxStatOK || filename.empty() ) {
432 return false;
433 }
434
435 imageLayers.clear();
436 std::string xml = extractXML(filename);
437 if (!xml.empty()) {
438 xmlDocPtr doc;
439 doc = xmlParseDoc((const xmlChar *)xml.c_str());
440 xmlNode *root_element = NULL;
441 root_element = xmlDocGetRootElement(doc);
442 getLayersInfo(root_element,&imageLayers);
443 xmlFreeDoc(doc);
444 if (hasMergedImage(filename)) {
445 std::vector<std::string> layerInfo;
446 layerInfo.push_back(kFnOfxImagePlaneColour);
447 layerInfo.push_back("mergedimage.png");
448 imageLayers.push_back(layerInfo);
449 }
450 std::reverse(imageLayers.begin(),imageLayers.end());
451 }
452 if (imageLayers.empty()) {
453 setPersistentMessage(OFX::Message::eMessageError, "", "Empty and/or corrupt image");
454 }
455
456 *components = OFX::ePixelComponentRGBA;
457 *filePremult = OFX::eImageUnPreMultiplied;
458
459 return true;
460 }
461
changedFilename(const OFX::InstanceChangedArgs & args)462 void OpenRasterPlugin::changedFilename(const OFX::InstanceChangedArgs &args)
463 {
464 GenericReaderPlugin::changedFilename(args);
465
466 int startingTime = getStartingTime();
467 std::string filename;
468 OfxStatus st = getFilenameAtTime(startingTime, &filename);
469 if ( st != kOfxStatOK || filename.empty() ) {
470 setPersistentMessage(OFX::Message::eMessageError, "", "No filename");
471 OFX::throwSuiteStatusException(kOfxStatErrFormat);
472 }
473 imageLayers.clear();
474 std::string xml = extractXML(filename);
475 if (!xml.empty()) {
476 xmlDocPtr doc;
477 doc = xmlParseDoc((const xmlChar *)xml.c_str());
478 xmlNode *root_element = NULL;
479 root_element = xmlDocGetRootElement(doc);
480 getLayersInfo(root_element,&imageLayers);
481 xmlFreeDoc(doc);
482 if (hasMergedImage(filename)) {
483 std::vector<std::string> layerInfo;
484 layerInfo.push_back(kFnOfxImagePlaneColour);
485 layerInfo.push_back("mergedimage.png");
486 imageLayers.push_back(layerInfo);
487 }
488 std::reverse(imageLayers.begin(),imageLayers.end());
489 }
490 }
491
restoreStateFromParams()492 void OpenRasterPlugin::restoreStateFromParams()
493 {
494 GenericReaderPlugin::restoreStateFromParams();
495
496 int startingTime = getStartingTime();
497 std::string filename;
498 OfxStatus st = getFilenameAtTime(startingTime, &filename);
499 if ( st == kOfxStatOK || !filename.empty() ) {
500 imageLayers.clear();
501 std::string xml = extractXML(filename);
502 if (!xml.empty()) {
503 xmlDocPtr doc;
504 doc = xmlParseDoc((const xmlChar *)xml.c_str());
505 xmlNode *root_element = NULL;
506 root_element = xmlDocGetRootElement(doc);
507 getLayersInfo(root_element,&imageLayers);
508 xmlFreeDoc(doc);
509 if (hasMergedImage(filename)) {
510 std::vector<std::string> layerInfo;
511 layerInfo.push_back(kFnOfxImagePlaneColour);
512 layerInfo.push_back("mergedimage.png");
513 imageLayers.push_back(layerInfo);
514 }
515 std::reverse(imageLayers.begin(),imageLayers.end());
516 }
517 }
518 }
519
520 using namespace OFX;
521
522 mDeclareReaderPluginFactory(OpenRasterPluginFactory, {}, false);
523
524 void
load()525 OpenRasterPluginFactory::load()
526 {
527 _extensions.clear();
528 _extensions.push_back("ora");
529 }
530
531 /** @brief The basic describe function, passed a plugin descriptor */
describe(OFX::ImageEffectDescriptor & desc)532 void OpenRasterPluginFactory::describe(OFX::ImageEffectDescriptor &desc)
533 {
534 GenericReaderDescribe(desc, _extensions, kPluginEvaluation, kSupportsTiles, kIsMultiPlanar);
535 desc.setLabel(kPluginName);
536 desc.setPluginDescription("Read OpenRaster image format.");
537 }
538
539 /** @brief The describe in context function, passed a plugin descriptor and a context */
describeInContext(OFX::ImageEffectDescriptor & desc,ContextEnum context)540 void OpenRasterPluginFactory::describeInContext(OFX::ImageEffectDescriptor &desc, ContextEnum context)
541 {
542 gHostIsNatron = (OFX::getImageEffectHostDescription()->isNatron);
543 PageParamDescriptor *page = GenericReaderDescribeInContextBegin(desc, context, isVideoStreamPlugin(), kSupportsRGBA, kSupportsRGB, kSupportsXY,kSupportsAlpha, kSupportsTiles, true);
544 GenericReaderDescribeInContextEnd(desc, context, page, "sRGB", "scene_linear");
545 }
546
547 /** @brief The create instance function, the plugin must return an object derived from the \ref OFX::ImageEffect class */
createInstance(OfxImageEffectHandle handle,ContextEnum)548 ImageEffect* OpenRasterPluginFactory::createInstance(OfxImageEffectHandle handle,
549 ContextEnum /*context*/)
550 {
551 OpenRasterPlugin* ret = new OpenRasterPlugin(handle, _extensions);
552 ret->restoreStateFromParams();
553 return ret;
554 }
555
556 static OpenRasterPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
557 mRegisterPluginFactoryInstance(p)
558
559 OFXS_NAMESPACE_ANONYMOUS_EXIT
560