1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX Transform & DirBlur plugins.
21  */
22 
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <iostream>
26 #include <limits>
27 #include <algorithm>
28 
29 #include "ofxsTransform3x3.h"
30 #include "ofxsTransformInteract.h"
31 #include "ofxsFormatResolution.h"
32 #include "ofxsCoords.h"
33 #include "ofxsThreadSuite.h"
34 
35 using namespace OFX;
36 
37 OFXS_NAMESPACE_ANONYMOUS_ENTER
38 
39 #define kPluginName "ReformatOFX"
40 #define kPluginGrouping "Transform"
41 #define kPluginDescription "Convert the image to another format or size.\n" \
42     "An image transform is computed that goes from the input region of definition (RoD) to the selected format. The Resize Type parameter adjust the way the transform is computed.\n" \
43     "This plugin concatenates transforms.\n" \
44     "See also: http://opticalenquiry.com/nuke/index.php?title=Reformat"
45 
46 #define kPluginDescriptionNatron "Convert the image to another format or size.\n" \
47     "An image transform is computed that goes from the input format, regardless of the region of definition (RoD), to the selected format. The Resize Type parameter adjust the way the transform is computed.\n" \
48     "The output format is set by this effect.\n" \
49     "In order to set the output format without transforming the image content, use the NoOp effect.\n" \
50     "This plugin concatenates transforms.\n" \
51     "See also: http://opticalenquiry.com/nuke/index.php?title=Reformat"
52 
53 #define kPluginIdentifier "net.sf.openfx.Reformat"
54 // History:
55 // version 1.0: initial version
56 // version 1.1: fix https://github.com/MrKepzie/Natron/issues/1397
57 // version 1.2: add useRoD parameter for Natron
58 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
59 #define kPluginVersionMinor 1 // Increment this when you have fixed a bug or made it faster.
60 
61 #define kParamUseRoD "useRoD"
62 #define kParamUseRoDLabel "Use Source RoD"
63 #define kParamUseRoDHint "Use the region of definition of the source as the source format."
64 
65 #define kParamType "reformatType"
66 #define kParamTypeLabel "Type"
67 #define kParamTypeHint "To Format: Converts between formats, the image is resized to fit in the target format. " \
68     "To Box: Scales to fit into a box of a given width and height. " \
69     "Scale: Scales the image (rounding to integer pixel sizes)."
70 #define kParamTypeOptionToFormat "To Format", "Resize to predefined format.", "format"
71 #define kParamTypeOptionToBox "To Box", "Resize to given bounding box.", "box"
72 #define kParamTypeOptionScale "Scale", "Apply scale.", "scale"
73 
74 enum ReformatTypeEnum
75 {
76     eReformatTypeToFormat = 0,
77     eReformatTypeToBox,
78     eReformatTypeScale,
79 };
80 
81 #define kParamFormat kNatronParamFormatChoice
82 #define kParamFormatLabel "Format"
83 #define kParamFormatHint "The output format"
84 #define kParamFormatDefault eParamFormatPCVideo
85 
86 
87 #define kParamFormatBoxSize kNatronParamFormatSize
88 #define kParamFormatBoxSizeLabel "Size"
89 #define kParamFormatBoxSizeHint "The output dimensions of the image in pixels."
90 
91 #define kParamFormatBoxPAR kNatronParamFormatPar
92 #define kParamFormatBoxPARLabel "Pixel Aspect Ratio"
93 #define kParamFormatBoxPARHint "Output pixel aspect ratio."
94 
95 #define kParamBoxSize "boxSize"
96 #define kParamBoxSizeLabel "Size"
97 #define kParamBoxSizeHint "The output dimensions of the image in pixels."
98 
99 #define kParamBoxFixed "boxFixed"
100 #define kParamBoxFixedLabel "Force This Shape"
101 #define kParamBoxFixedHint "If checked, the output image is cropped to this size. Else, image is resized according to the resize type but the whole image is kept."
102 
103 #define kParamBoxPAR "boxPar"
104 #define kParamBoxPARLabel "Pixel Aspect Ratio"
105 #define kParamBoxPARHint "Output pixel aspect ratio."
106 
107 #define kParamScale "reformatScale"
108 #define kParamScaleLabel "Scale"
109 #define kParamScaleHint "The scale factor to apply to the image. The scale factor is rounded slightly, so that the output image is an integer number of pixels in the direction chosen under resize type."
110 
111 #define kParamScaleUniform "reformatScaleUniform"
112 #define kParamScaleUniformLabel "Uniform"
113 #define kParamScaleUniformHint "Use the X scale for both directions"
114 
115 #define kParamResize "resize"
116 #define kParamResizeLabel "Resize Type"
117 #define kParamResizeHint "Format: Converts between formats, the image is resized to fit in the target format. " \
118     "Size: Scales to fit into a box of a given width and height. " \
119     "Scale: Scales the image."
120 #define kParamResizeOptionNone "None", "Do not resize the original.", "none"
121 #define kParamResizeOptionWidth "Width", "Scale the original so that its width fits the output width, while preserving the aspect ratio.", "width"
122 #define kParamResizeOptionHeight "Height", "Scale the original so that its height fits the output height, while preserving the aspect ratio.", "height"
123 #define kParamResizeOptionFit "Fit", "Scale the original so that its smallest size fits the output width or height, while preserving the aspect ratio.", "fit"
124 #define kParamResizeOptionFill "Fill", "Scale the original so that its longest size fits the output width or height, while preserving the aspect ratio.", "fill"
125 #define kParamResizeOptionDistort "Distort", "Scale the original so that both sides fit the output dimensions. This does not preserve the aspect ratio.", "distort"
126 
127 enum ResizeEnum
128 {
129     eResizeNone = 0,
130     eResizeWidth,
131     eResizeHeight,
132     eResizeFit,
133     eResizeFill,
134     eResizeDistort,
135 };
136 
137 
138 #define kParamReformatCenter "reformatCentered"
139 #define kParamReformatCenterLabel "Center"
140 #define kParamReformatCenterHint "Translate the center of the image to the center of the output. Otherwise, the lower left corner is left untouched."
141 
142 #define kParamFlip "flip"
143 #define kParamFlipLabel "Flip"
144 #define kParamFlipHint "Mirror the image vertically."
145 
146 #define kParamFlop "flop"
147 #define kParamFlopLabel "Flop"
148 #define kParamFlopHint "Mirror the image horizontally."
149 
150 #define kParamTurn "turn"
151 #define kParamTurnLabel "Turn"
152 #define kParamTurnHint "Rotate the image by 90 degrees counter-clockwise."
153 
154 #define kParamPreserveBoundingBox "preserveBB"
155 #define kParamPreserveBoundingBoxLabel "Preserve BBox" // and Concat"
156 #define kParamPreserveBoundingBoxHint \
157     "If checked, preserve the whole image bounding box and concatenate transforms downstream.\n" \
158     "Normally, all pixels outside of the outside format are clipped off. If this is checked, the whole image RoD is kept.\n" \
159     "By default, transforms are only concatenated upstream, i.e. the image is rendered by this effect by concatenating upstream transforms (e.g. CornerPin, Transform...), and the original image is resampled only once. If checked, and there are concatenating transform effects downstream, the image is rendered by the last consecutive concatenating effect."
160 
161 
162 static bool gHostCanTransform;
163 static bool gHostIsNatron = false;
164 static bool gHostSupportsFormat = false;
165 
166 ////////////////////////////////////////////////////////////////////////////////
167 /** @brief The plugin that does our work */
168 class ReformatPlugin
169     : public Transform3x3Plugin
170 {
171 public:
172     /** @brief ctor */
ReformatPlugin(OfxImageEffectHandle handle)173     ReformatPlugin(OfxImageEffectHandle handle)
174         : Transform3x3Plugin(handle, false, eTransform3x3ParamsTypeNone)
175         , _type(NULL)
176         , _format(NULL)
177         , _formatBoxSize(NULL)
178         , _formatBoxPAR(NULL)
179         , _boxSize(NULL)
180         , _boxFixed(NULL)
181         , _boxPAR(NULL)
182         , _scale(NULL)
183         , _scaleUniform(NULL)
184         , _preserveBB(NULL)
185         , _resize(NULL)
186         , _center(NULL)
187         , _flip(NULL)
188         , _flop(NULL)
189         , _turn(NULL)
190     {
191         _filter = fetchChoiceParam(kParamFilterType);
192         _clamp = fetchBooleanParam(kParamFilterClamp);
193         _blackOutside = fetchBooleanParam(kParamFilterBlackOutside);
194 
195         // NON-GENERIC
196         _useRoD = fetchBooleanParam(kParamUseRoD);
197         _type = fetchChoiceParam(kParamType);
198         _format = fetchChoiceParam(kParamFormat);
199         _formatBoxSize = fetchInt2DParam(kParamFormatBoxSize);
200         _formatBoxPAR = fetchDoubleParam(kParamFormatBoxPAR);
201         _boxSize = fetchInt2DParam(kParamBoxSize);
202         _boxFixed = fetchBooleanParam(kParamBoxFixed);
203         _boxPAR = fetchDoubleParam(kParamBoxPAR);
204         _scale = fetchDouble2DParam(kParamScale);
205         _scaleUniform = fetchBooleanParam(kParamScaleUniform);
206         _preserveBB = fetchBooleanParam(kParamPreserveBoundingBox);
207         _resize = fetchChoiceParam(kParamResize);
208         _center = fetchBooleanParam(kParamReformatCenter);
209         _flip = fetchBooleanParam(kParamFlip);
210         _flop = fetchBooleanParam(kParamFlop);
211         _turn = fetchBooleanParam(kParamTurn);
212         assert(_useRoD && _type && _format && _boxSize && _boxFixed && _boxPAR && _scale && _scaleUniform && _preserveBB && _resize && _center && _flip && _flop && _turn);
213 
214 
215         if (!gHostIsNatron) {
216             // try to guess the output format from the project size
217             // do it only if not Natron otherwise this will override what the host has set in the format when loading
218             double projectPAR = getProjectPixelAspectRatio();
219             OfxPointD projectSize = getProjectSize();
220 
221             ///Try to find a format matching the project format in which case we switch to format mode otherwise
222             ///switch to size mode and set the size accordingly
223             bool foundFormat = false;
224             for (int i = 0; i < (int)eParamFormatCount; ++i) {
225                 int width = 0, height = 0;
226                 double par = -1.;
227                 getFormatResolution( (EParamFormat)i, &width, &height, &par );
228                 assert(par != -1);
229                 if ( (width * par == projectSize.x) && (height == projectSize.y) && (std::abs(par - projectPAR) < 0.01) ) {
230                     _type->setValue( (int)eReformatTypeToFormat );
231                     _format->setValue( (EParamFormat)i );
232                     _boxSize->setValue(width, height);
233                     _formatBoxSize->setValue(width, height);
234                     _boxPAR->setValue(par);
235                     _formatBoxPAR->setValue(par);
236                     foundFormat = true;
237                     break;
238                 }
239             }
240             if (!foundFormat) {
241                 _type->setValue( (int)eReformatTypeToBox );
242                 _boxSize->setValue(projectSize.x / projectPAR, projectSize.y);
243                 _formatBoxSize->setValue(projectSize.x / projectPAR, projectSize.y);
244                 _boxPAR->setValue(projectPAR);
245                 _formatBoxPAR->setValue(projectPAR);
246                 _boxFixed->setValue(true);
247             }
248         }
249 
250         // finally
251         syncPrivateData();
252     }
253 
254 
255 private:
256     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
257     virtual bool isIdentity(double time) OVERRIDE FINAL;
258     virtual bool getInverseTransformCanonical(double time, int view, double amount, bool invert, Matrix3x3* invtransform) const OVERRIDE FINAL;
259     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
260     virtual void getClipPreferences(ClipPreferencesSetter &clipPreferences) OVERRIDE FINAL;
261 
262     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)263     virtual void syncPrivateData(void) OVERRIDE FINAL
264     {
265         // On Natron, hide the uniform parameter if it is false and not animated,
266         // since uniform scaling is easy through Natron's GUI.
267         // The parameter is kept for backward compatibility.
268         // Fixes https://github.com/MrKepzie/Natron/issues/1204
269         if ( getImageEffectHostDescription()->isNatron &&
270             !_scaleUniform->getValue() &&
271             ( _scaleUniform->getNumKeys() == 0) ) {
272             _scaleUniform->setIsSecretAndDisabled(true);
273         }
274         refreshVisibility();
275         refreshDynamicProps();
276     }
277 
278 
279     void refreshVisibility();
280 
281     void refreshDynamicProps();
282 
283     bool getBoxValues(double time, int *w, int *h, double *par, bool *boxFixed) const;
284 
285     void getInputFormat(const double time,
286                         double* par,
287                         OfxRectD* rect) const;
288 
289     void getOutputFormat(const double time,
290                          double* par,
291                          OfxRectD* rect, // the rect to which the input format is mapped
292                          OfxRectI* format) const; // the full format (only useful if host supports format, really)
293 
294     // NON-GENERIC
295     BooleanParam* _useRoD;
296     ChoiceParam *_type;
297     ChoiceParam *_format;
298     Int2DParam *_formatBoxSize;
299     DoubleParam* _formatBoxPAR;
300     Int2DParam *_boxSize;
301     BooleanParam* _boxFixed;
302     DoubleParam* _boxPAR;
303     Double2DParam *_scale;
304     BooleanParam* _scaleUniform;
305     BooleanParam* _preserveBB;
306     ChoiceParam *_resize;
307     BooleanParam* _center;
308     BooleanParam* _flip;
309     BooleanParam* _flop;
310     BooleanParam* _turn;
311 };
312 
313 // override the rod call
314 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)315 ReformatPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
316                                       OfxRectD &rod)
317 {
318     if ( !_srcClip || !_srcClip->isConnected() ) {
319         return false;
320     }
321 
322     bool ret = Transform3x3Plugin::getRegionOfDefinition(args, rod);
323     if ( !ret ||
324          _preserveBB->getValue() ) {
325         return ret;
326     }
327 
328     const double time = args.time;
329     // intersect with format RoD
330     OfxRectD rect;
331     OfxRectI format;
332     double par;
333     getOutputFormat(time, &par, &rect, &format);
334     const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
335     OfxRectD formatrod;
336     Coords::toCanonical(format, rsOne, par, &formatrod);
337 
338     Coords::rectIntersection(rod, formatrod, &rod);
339 
340     return true;
341 } // ReformatPlugin::getRegionOfDefinition
342 
343 // overridden is identity
344 bool
isIdentity(const double time)345 ReformatPlugin::isIdentity(const double time)
346 {
347     if ( _center->getValueAtTime(time) || _flip->getValueAtTime(time) ||
348          _flop->getValueAtTime(time)   || _turn->getValueAtTime(time) ) {
349         return false;
350     }
351 
352     if ( (ResizeEnum)_resize->getValueAtTime(time) == eResizeNone ) {
353         return true;
354     }
355 
356     return false;
357 }
358 
359 // recturn the input format in pixel units (we use a RectD in case the input format is the RoD)
360 void
getInputFormat(const double time,double * par,OfxRectD * rect) const361 ReformatPlugin::getInputFormat(const double time,
362                                double* par,
363                                OfxRectD* rect) const
364 {
365     *par = _srcClip->getPixelAspectRatio();
366 #ifdef OFX_EXTENSIONS_NATRON
367     if (gHostSupportsFormat && !_useRoD->getValueAtTime(time)) {
368         OfxRectI format;
369         _srcClip->getFormat(format);
370         if ( !Coords::rectIsEmpty(format) ) {
371             // host returned a non-empty format
372             rect->x1 = format.x1;
373             rect->y1 = format.y1;
374             rect->x2 = format.x2;
375             rect->y2 = format.y2;
376             return;
377         }
378     }
379     // host does not support format
380 #endif
381     OfxRectD srcRod = _srcClip->getRegionOfDefinition(time);
382     const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
383     Coords::toPixelSub(srcRod, rsOne, *par, rect);
384 }
385 
386 void
getOutputFormat(const double time,double * par,OfxRectD * rect,OfxRectI * format) const387 ReformatPlugin::getOutputFormat(const double time,
388                                 double* par,
389                                 OfxRectD* rect,
390                                 OfxRectI* format) const
391 {
392     int type_i;
393 
394     _type->getValue(type_i);
395     OfxPointI boxSize;
396     double boxPAR;
397     bool boxFixed;
398     getBoxValues(time, &boxSize.x, &boxSize.y, &boxPAR, &boxFixed);
399 
400     ResizeEnum resize = (ResizeEnum)_resize->getValueAtTime(time);
401     bool center = _center->getValueAtTime(time);
402     //bool flip = _flip->getValueAtTime(time);
403     //bool flop = _flop->getValueAtTime(time);
404     bool turn = _turn->getValueAtTime(time);
405     // same as getRegionOfDefinition, but without rounding, and without conversion to pixels
406 
407 
408     if (format && boxFixed) { // the non-boxFixed case is treated at the end of the function
409         format->x1 = format->y1 = 0;
410         format->x2 = boxSize.x;
411         format->y2 = boxSize.y;
412     }
413 
414     if ( (boxSize.x == 0) && (boxSize.y == 0) ) {
415         //probably scale is 0
416         rect->x1 = rect->y1 = rect->x2 = rect->y2 = 0;
417         *par = 1.;
418 
419         return;
420     }
421     OfxRectD boxRod = { 0., 0., (double)boxSize.x * boxPAR, (double)boxSize.y};
422 
423     OfxRectD srcRod;
424     {
425         double par;
426         OfxRectD format;
427         const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
428         getInputFormat(time, &par, &format);
429         Coords::toCanonical(format, rsOne, par, &srcRod);
430     }
431     if ( Coords::rectIsEmpty(srcRod) ) {
432         // degenerate case
433         rect->x1 = rect->y1 = rect->x2 = rect->y2 = 0;
434         *par = 1.;
435 
436         return;
437     }
438     double srcw = srcRod.x2 - srcRod.x1;
439     double srch = srcRod.y2 - srcRod.y1;
440     // if turn, inverse both dimensions
441     if (turn) {
442         std::swap(srcw, srch);
443     }
444     // if fit or fill, determine if it should be fit to width or height
445     if (resize == eResizeFit) {
446         if (boxRod.x2 * srch > boxRod.y2 * srcw) {
447             resize = eResizeHeight;
448         } else {
449             resize = eResizeWidth;
450         }
451     } else if (resize == eResizeFill) {
452         if (boxRod.x2 * srch > boxRod.y2 * srcw) {
453             resize = eResizeWidth;
454         } else {
455             resize = eResizeHeight;
456         }
457     }
458 
459     OfxRectD dstRod;
460     dstRod.x1 = dstRod.y1 = dstRod.x2 = dstRod.y2 = 0.;
461     if (resize == eResizeNone) {
462         if (center && boxFixed) {
463             // translate the source
464             double xoff = ( (boxRod.x1 + boxRod.x2) - (srcRod.x1 + srcRod.x2) ) / 2;
465             double yoff = ( (boxRod.y1 + boxRod.y2) - (srcRod.y1 + srcRod.y2) ) / 2;
466             dstRod.x1 = srcRod.x1 + xoff;
467             dstRod.x2 = srcRod.x2 + xoff;
468             dstRod.y1 = srcRod.y1 + yoff;
469             dstRod.y2 = srcRod.y2 + yoff;
470         } else {
471             // identity, with RoD = dstRod for flip/flop/turn
472             srcRod = dstRod = boxRod;
473         }
474     } else if (resize == eResizeDistort) {
475         // easy case
476         dstRod.x2 = boxRod.x2;
477         dstRod.y2 = boxRod.y2;
478     } else if (resize == eResizeWidth) {
479         double scale = boxRod.x2 / srcw;
480         dstRod.x2 = boxRod.x2;
481         double dsth = srch * scale;
482         double offset = (center && boxFixed) ? (boxRod.y2 - dsth) / 2 : 0;
483         dstRod.y1 = offset;
484         dstRod.y2 = offset + dsth;
485     } else if (resize == eResizeHeight) {
486         double scale = boxRod.y2 / srch;
487         double dstw = srcw * scale;
488         double offset = (center && boxFixed) ? (boxRod.x2 - dstw) / 2 : 0;
489         dstRod.x1 = offset;
490         dstRod.x2 = offset + dstw;
491         dstRod.y2 = boxRod.y2;
492     }
493     assert( !Coords::rectIsEmpty(dstRod) );
494     *par = boxPAR;
495     const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
496     Coords::toPixelSub(dstRod, rsOne, *par, rect);
497     if (format && !boxFixed) {
498         Coords::toPixelNearest(dstRod, rsOne, *par, format);
499     }
500 } // ReformatPlugin::getOutputFormat
501 
502 bool
getInverseTransformCanonical(const double time,const int,const double,const bool invert,Matrix3x3 * invtransform) const503 ReformatPlugin::getInverseTransformCanonical(const double time,
504                                              const int /*view*/,
505                                              const double /*amount*/,
506                                              const bool invert,
507                                              Matrix3x3* invtransform) const
508 {
509     if ( !_srcClip || !_srcClip->isConnected() ) {
510         return false;
511     }
512 
513     OfxRectD srcRod, dstRod;
514     {
515         double par;
516         OfxRectD format;
517         const OfxPointD rsOne = {1., 1.}; // format is with respect to unit renderscale
518         getInputFormat(time, &par, &format);
519         Coords::toCanonical(format, rsOne, par, &srcRod);
520         getOutputFormat(time, &par, &format, NULL);
521         Coords::toCanonical(format, rsOne, par, &dstRod);
522     }
523     bool flip = _flip->getValueAtTime(time);
524     bool flop = _flop->getValueAtTime(time);
525     bool turn = _turn->getValueAtTime(time);
526 
527     // flip/flop.
528     // be careful, srcRod may be empty after this, because bounds are swapped,
529     // but this is used for transform computation
530     if (flip) {
531         std::swap(srcRod.y1, srcRod.y2);
532     }
533     if (flop) {
534         std::swap(srcRod.x1, srcRod.x2);
535     }
536     if (!invert) {
537         if ( (dstRod.x1 == dstRod.x2) ||
538              ( dstRod.y1 == dstRod.y2) ) {
539             return false;
540         }
541         // now, compute the transform from dstRod to srcRod
542         if (!turn) {
543             // simple case: no rotation
544             // x <- srcRod.x1 + (x - dstRod.x1) * (srcRod.x2 - srcRod.x1) / (dstRod.x2 - dstRod.x1)
545             // y <- srcRod.y1 + (y - dstRod.y1) * (srcRod.y2 - srcRod.y1) / (dstRod.y2 - dstRod.y1)
546             double ax = (srcRod.x2 - srcRod.x1) / (dstRod.x2 - dstRod.x1);
547             double ay = (srcRod.y2 - srcRod.y1) / (dstRod.y2 - dstRod.y1);
548             assert(ax == ax && ay == ay);
549             (*invtransform)(0,0) = ax; (*invtransform)(0,1) =  0; (*invtransform)(0,2) = srcRod.x1 - dstRod.x1 * ax;
550             (*invtransform)(1,0) =  0; (*invtransform)(1,1) = ay; (*invtransform)(1,2) = srcRod.y1 - dstRod.y1 * ay;
551             (*invtransform)(2,0) =  0; (*invtransform)(2,1) =  0; (*invtransform)(2,2) = 1.;
552         } else {
553             // rotation 90 degrees counterclockwise
554             // x <- srcRod.x1 + (y - dstRod.y1) * (srcRod.x2 - srcRod.x1) / (dstRod.y2 - dstRod.y1)
555             // y <- srcRod.y1 + (dstRod.x2 - x) * (srcRod.y2 - srcRod.y1) / (dstRod.x2 - dstRod.x1)
556             double ax = (srcRod.x2 - srcRod.x1) / (dstRod.y2 - dstRod.y1);
557             double ay = (srcRod.y2 - srcRod.y1) / (dstRod.x2 - dstRod.x1);
558             assert(ax == ax && ay == ay);
559             (*invtransform)(0,0) =  0; (*invtransform)(0,1) = ax; (*invtransform)(0,2) = srcRod.x1 - dstRod.y1 * ax;
560             (*invtransform)(1,0) = -ay; (*invtransform)(1,1) =  0; (*invtransform)(1,2) = srcRod.y1 + dstRod.x2 * ay;
561             (*invtransform)(2,0) =  0; (*invtransform)(2,1) =  0; (*invtransform)(2,2) = 1.;
562         }
563     } else { // invert
564         if ( (srcRod.x1 == srcRod.x2) ||
565              ( srcRod.y1 == srcRod.y2) ) {
566             return false;
567         }
568         // now, compute the transform from srcRod to dstRod
569         if (!turn) {
570             // simple case: no rotation
571             // x <- dstRod.x1 + (x - srcRod.x1) * (dstRod.x2 - dstRod.x1) / (srcRod.x2 - srcRod.x1)
572             // y <- dstRod.y1 + (y - srcRod.y1) * (dstRod.y2 - dstRod.y1) / (srcRod.y2 - srcRod.y1)
573             double ax = (dstRod.x2 - dstRod.x1) / (srcRod.x2 - srcRod.x1);
574             double ay = (dstRod.y2 - dstRod.y1) / (srcRod.y2 - srcRod.y1);
575             assert(ax == ax && ay == ay);
576             (*invtransform)(0,0) = ax; (*invtransform)(0,1) =  0; (*invtransform)(0,2) = dstRod.x1 - srcRod.x1 * ax;
577             (*invtransform)(1,0) =  0; (*invtransform)(1,1) = ay; (*invtransform)(1,2) = dstRod.y1 - srcRod.y1 * ay;
578             (*invtransform)(2,0) =  0; (*invtransform)(2,1) =  0; (*invtransform)(2,2) = 1.;
579         } else {
580             // rotation 90 degrees counterclockwise
581             // x <- dstRod.x1 + (srcRod.y2 - y) * (dstRod.x2 - dstRod.x1) / (srcRod.y2 - srcRod.y1)
582             // y <- dstRod.y1 + (x - srcRod.x1) * (dstRod.y2 - dstRod.y1) / (srcRod.x2 - srcRod.x1)
583             double ax = (dstRod.x2 - dstRod.x1) / (srcRod.y2 - srcRod.y1);
584             double ay = (dstRod.y2 - dstRod.y1) / (srcRod.x2 - srcRod.x1);
585             assert(ax == ax && ay == ay);
586             (*invtransform)(0,0) =  0; (*invtransform)(0,1) = -ax; (*invtransform)(0,2) = dstRod.x1 + srcRod.y2 * ax;
587             (*invtransform)(1,0) = ay; (*invtransform)(1,1) =  0; (*invtransform)(1,2) = dstRod.y1 - srcRod.x1 * ay;
588             (*invtransform)(2,0) =  0; (*invtransform)(2,1) =  0; (*invtransform)(2,2) = 1.;
589         }
590     }
591     assert((*invtransform)(0,0) == (*invtransform)(0,0) && (*invtransform)(0,1) == (*invtransform)(0,1) && (*invtransform)(0,2) == (*invtransform)(0,2) &&
592            (*invtransform)(1,0) == (*invtransform)(1,0) && (*invtransform)(1,1) == (*invtransform)(1,1) && (*invtransform)(1,2) == (*invtransform)(1,2) &&
593            (*invtransform)(2,0) == (*invtransform)(2,0) && (*invtransform)(2,1) == (*invtransform)(2,1) && (*invtransform)(2,2) == (*invtransform)(2,2));
594 
595     return true;
596 } // ReformatPlugin::getInverseTransformCanonical
597 
598 bool
getBoxValues(const double time,int * w,int * h,double * par,bool * boxFixed) const599 ReformatPlugin::getBoxValues(const double time, int *w, int *h, double *par, bool *boxFixed) const
600 {
601     ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
602     assert(w && h && par && boxFixed);
603 
604     switch (type) {
605     case eReformatTypeToFormat: {
606         if (gHostIsNatron) {
607             //size & par have been set by natron with the Format choice extension
608             _formatBoxSize->getValue(*w, *h);
609             *par = _formatBoxPAR->getValue();
610         } else {
611             EParamFormat format = (EParamFormat)_format->getValue();
612             assert(0 <= (int)format && (int)format < eParamFormatCount);
613             getFormatResolution(format, w, h, par);
614             assert(*par != -1);
615         }
616         *boxFixed = true;
617         break;
618     }
619     case eReformatTypeToBox: {
620         _boxSize->getValue(*w, *h);
621         *par = _boxPAR->getValue();
622         *boxFixed = _boxFixed->getValue();
623         // nothing to do, the user sets the box
624         return false;
625         break;
626     }
627     case eReformatTypeScale: {
628         OfxPointD scale = _scale->getValue();
629         if ( _scaleUniform->getValue() ) {
630             scale.y = scale.x;
631         }
632         OfxRectD srcRod;
633         if (_srcClip && _srcClip->isConnected()) {
634             *par = _srcClip->getPixelAspectRatio();
635             srcRod = _srcClip->getRegionOfDefinition(time);
636         } else {
637             OfxPointD siz = getProjectSize();
638             OfxPointD off = getProjectOffset();
639             srcRod.x1 = off.x;
640             srcRod.x2 = off.x + siz.x;
641             srcRod.y1 = off.y;
642             srcRod.y2 = off.y + siz.y;
643             *par = getProjectPixelAspectRatio();
644         }
645         // scale the RoD
646         srcRod.x1 *= scale.x;
647         srcRod.x2 *= scale.x;
648         srcRod.y1 *= scale.y;
649         srcRod.y2 *= scale.y;
650         // round to the nearest pixel size
651         OfxRectI srcRodPixel;
652         OfxPointD rs = {1., 1.};
653         Coords::toPixelNearest(srcRod, rs, *par, &srcRodPixel);
654         *w = srcRodPixel.x2 - srcRodPixel.x1;
655         *h = srcRodPixel.y2 - srcRodPixel.y1;
656         *boxFixed = true;
657     }
658     }
659     return true; // box was set
660 } // ReformatPlugin::setBoxValues
661 
662 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)663 ReformatPlugin::changedParam(const InstanceChangedArgs &args,
664                              const std::string &paramName)
665 {
666     // must clear persistent message, or render() is not called by Nuke
667     clearPersistentMessage();
668 
669     if (paramName == kParamType) {
670         refreshVisibility();
671     }
672     if ( (paramName == kParamType) || (paramName == kParamFormat) || (paramName == kParamScale) || (paramName == kParamScaleUniform) ) {
673         int w, h;
674         double par;
675         bool boxFixed;
676         if ( getBoxValues(args.time, &w, &h, &par, &boxFixed) ) {
677             _boxSize->setValue(w, h);
678             _boxPAR->setValue(par);
679             _boxFixed->setValue(boxFixed);
680         }
681         return;
682     }
683 
684     if (paramName == kParamPreserveBoundingBox) {
685         refreshDynamicProps();
686 
687         return;
688     }
689 
690     return Transform3x3Plugin::changedParam(args, paramName);
691 }
692 
693 void
refreshDynamicProps()694 ReformatPlugin::refreshDynamicProps()
695 {
696     setCanTransform( _preserveBB->getValue() );
697 }
698 
699 void
refreshVisibility()700 ReformatPlugin::refreshVisibility()
701 {
702     ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
703 
704     switch (type) {
705     case eReformatTypeToFormat:
706         _format->setIsSecretAndDisabled(false);
707         _boxSize->setIsSecretAndDisabled(true);
708         _boxPAR->setIsSecretAndDisabled(true);
709         _boxFixed->setIsSecretAndDisabled(true);
710         _scale->setIsSecretAndDisabled(true);
711         _scaleUniform->setIsSecretAndDisabled(true);
712         break;
713 
714     case eReformatTypeToBox:
715         _format->setIsSecretAndDisabled(true);
716         _boxSize->setIsSecretAndDisabled(false);
717         _boxPAR->setIsSecretAndDisabled(false);
718         _boxFixed->setIsSecretAndDisabled(false);
719         _scale->setIsSecretAndDisabled(true);
720         _scaleUniform->setIsSecretAndDisabled(true);
721         break;
722 
723     case eReformatTypeScale:
724         _format->setIsSecretAndDisabled(true);
725         _boxSize->setIsSecretAndDisabled(true);
726         _boxPAR->setIsSecretAndDisabled(true);
727         _boxFixed->setIsSecretAndDisabled(true);
728         _scale->setIsSecretAndDisabled(false);
729         _scaleUniform->setIsSecretAndDisabled(false);
730         break;
731     }
732     _formatBoxSize->setIsSecretAndDisabled(true);
733     _formatBoxPAR->setIsSecretAndDisabled(true);
734 }
735 
736 void
getClipPreferences(ClipPreferencesSetter & clipPreferences)737 ReformatPlugin::getClipPreferences(ClipPreferencesSetter &clipPreferences)
738 {
739     ReformatTypeEnum type = (ReformatTypeEnum)_type->getValue();
740     double par;
741     OfxRectD rect;
742     OfxRectI format;
743 
744     getOutputFormat(0., &par, &rect, &format);
745 
746     switch (type) {
747     case eReformatTypeToFormat:
748     case eReformatTypeToBox: {
749         //specific output PAR
750         clipPreferences.setPixelAspectRatio(*_dstClip, par);
751         break;
752     }
753 
754     case eReformatTypeScale:
755         // don't change the pixel aspect ratio
756         break;
757     }
758 #ifdef OFX_EXTENSIONS_NATRON
759     clipPreferences.setOutputFormat(format);
760 #endif
761 }
762 
763 mDeclarePluginFactory(ReformatPluginFactory, {ofxsThreadSuiteCheck();}, {});
764 void
describe(ImageEffectDescriptor & desc)765 ReformatPluginFactory::describe(ImageEffectDescriptor &desc)
766 {
767     // basic labels
768     desc.setLabel(kPluginName);
769     desc.setPluginGrouping(kPluginGrouping);
770     desc.setPluginDescription(getImageEffectHostDescription()->isNatron ? kPluginDescriptionNatron : kPluginDescription);
771     Transform3x3Describe(desc, false);
772     desc.setSupportsMultiResolution(true);
773     desc.setSupportsMultipleClipPARs(true);
774     gHostCanTransform = false;
775 
776 #ifdef OFX_EXTENSIONS_NUKE
777     if (getImageEffectHostDescription()->canTransform) {
778         gHostCanTransform = true;
779         // say the effect implements getTransform(), even though transform concatenation
780         // may be disabled (see ReformatPlugin::refreshDynamicProps())
781         desc.setCanTransform(true);
782     }
783 #endif
784 #ifdef OFX_EXTENSIONS_NATRON
785     if (getImageEffectHostDescription()->isNatron) {
786         gHostIsNatron = true;
787         gHostSupportsFormat = true;
788     }
789 #endif
790 }
791 
792 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)793 ReformatPluginFactory::describeInContext(ImageEffectDescriptor &desc,
794                                          ContextEnum context)
795 {
796     // make some pages and to things in
797     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, false);
798 
799     {
800         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamUseRoD);
801         param->setLabel(kParamUseRoDLabel);
802         param->setHint(kParamUseRoDHint);
803         // for now, only Natron supports OFX format extension
804         param->setEnabled(gHostSupportsFormat);
805         param->setDefault(!gHostSupportsFormat);
806         param->setAnimates(false);
807         if (page) {
808             page->addChild(*param);
809         }
810     }
811 
812     // type
813     {
814         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamType);
815         param->setLabel(kParamTypeLabel);
816         param->setHint(kParamTypeHint);
817         assert(param->getNOptions() == eReformatTypeToFormat);
818         param->appendOption(kParamTypeOptionToFormat);
819         assert(param->getNOptions() == eReformatTypeToBox);
820         param->appendOption(kParamTypeOptionToBox);
821         assert(param->getNOptions() == eReformatTypeScale);
822         param->appendOption(kParamTypeOptionScale);
823         param->setDefault(0);
824         param->setAnimates(false);
825         desc.addClipPreferencesSlaveParam(*param);
826         if (page) {
827             page->addChild(*param);
828         }
829     }
830 
831     // format
832     {
833         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamFormat);
834         param->setLabel(kParamFormatLabel);
835         param->setHint(kParamFormatHint);
836         assert(param->getNOptions() == eParamFormatPCVideo);
837         param->appendOption(kParamFormatPCVideoLabel, "", kParamFormatPCVideo);
838         assert(param->getNOptions() == eParamFormatNTSC);
839         param->appendOption(kParamFormatNTSCLabel, "", kParamFormatNTSC);
840         assert(param->getNOptions() == eParamFormatPAL);
841         param->appendOption(kParamFormatPALLabel, "", kParamFormatPAL);
842         assert(param->getNOptions() == eParamFormatNTSC169);
843         param->appendOption(kParamFormatNTSC169Label, "", kParamFormatNTSC169);
844         assert(param->getNOptions() == eParamFormatPAL169);
845         param->appendOption(kParamFormatPAL169Label, "", kParamFormatPAL169);
846         assert(param->getNOptions() == eParamFormatHD720);
847         param->appendOption(kParamFormatHD720Label, "", kParamFormatHD720);
848         assert(param->getNOptions() == eParamFormatHD);
849         param->appendOption(kParamFormatHDLabel, "", kParamFormatHD);
850         assert(param->getNOptions() == eParamFormatUHD4K);
851         param->appendOption(kParamFormatUHD4KLabel, "", kParamFormatUHD4K);
852         assert(param->getNOptions() == eParamFormat1kSuper35);
853         param->appendOption(kParamFormat1kSuper35Label, "", kParamFormat1kSuper35);
854         assert(param->getNOptions() == eParamFormat1kCinemascope);
855         param->appendOption(kParamFormat1kCinemascopeLabel, "", kParamFormat1kCinemascope);
856         assert(param->getNOptions() == eParamFormat2kSuper35);
857         param->appendOption(kParamFormat2kSuper35Label, "", kParamFormat2kSuper35);
858         assert(param->getNOptions() == eParamFormat2kCinemascope);
859         param->appendOption(kParamFormat2kCinemascopeLabel, "", kParamFormat2kCinemascope);
860         assert(param->getNOptions() == eParamFormat2kDCP);
861         param->appendOption(kParamFormat2kDCPLabel, "", kParamFormat2kDCP);
862         assert(param->getNOptions() == eParamFormat4kSuper35);
863         param->appendOption(kParamFormat4kSuper35Label, "", kParamFormat4kSuper35);
864         assert(param->getNOptions() == eParamFormat4kCinemascope);
865         param->appendOption(kParamFormat4kCinemascopeLabel, "", kParamFormat4kCinemascope);
866         assert(param->getNOptions() == eParamFormat4kDCP);
867         param->appendOption(kParamFormat4kDCPLabel, "", kParamFormat4kDCP);
868         assert(param->getNOptions() == eParamFormatSquare256);
869         param->appendOption(kParamFormatSquare256Label, "", kParamFormatSquare256);
870         assert(param->getNOptions() == eParamFormatSquare512);
871         param->appendOption(kParamFormatSquare512Label, "", kParamFormatSquare512);
872         assert(param->getNOptions() == eParamFormatSquare1k);
873         param->appendOption(kParamFormatSquare1kLabel, "", kParamFormatSquare1k);
874         assert(param->getNOptions() == eParamFormatSquare2k);
875         param->appendOption(kParamFormatSquare2kLabel, "", kParamFormatSquare2k);
876         param->setDefault(kParamFormatDefault);
877         param->setHint(kParamFormatHint);
878         param->setAnimates(false);
879         desc.addClipPreferencesSlaveParam(*param);
880         if (page) {
881             page->addChild(*param);
882         }
883     }
884 
885     {
886         Int2DParamDescriptor* param = desc.defineInt2DParam(kParamFormatBoxSize);
887         param->setLabel(kParamFormatBoxSizeLabel);
888         param->setHint(kParamFormatBoxSizeHint);
889         param->setDefault(200, 200);
890         param->setIsSecretAndDisabled(true); // secret Natron-specific param
891         param->setAnimates(false);
892         desc.addClipPreferencesSlaveParam(*param);
893         if (page) {
894             page->addChild(*param);
895         }
896     }
897 
898     {
899         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamFormatBoxPAR);
900         param->setLabel(kParamFormatBoxPARLabel);
901         param->setHint(kParamFormatBoxPARHint);
902         param->setRange(0., 10);
903         param->setDisplayRange(0.5, 2.);
904         param->setDefault(1.);
905         param->setIsSecretAndDisabled(true); // secret Natron-specific param
906         param->setAnimates(false);
907         desc.addClipPreferencesSlaveParam(*param);
908         if (page) {
909             page->addChild(*param);
910         }
911     }
912 
913 
914     {
915         Int2DParamDescriptor* param = desc.defineInt2DParam(kParamBoxSize);
916         param->setLabel(kParamBoxSizeLabel);
917         param->setHint(kParamBoxSizeHint);
918         param->setDefault(200, 200);
919         param->setLayoutHint(eLayoutHintNoNewLine, 1);
920         param->setAnimates(false);
921         desc.addClipPreferencesSlaveParam(*param);
922         if (page) {
923             page->addChild(*param);
924         }
925     }
926     {
927         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamBoxFixed);
928         param->setLabel(kParamBoxFixedLabel);
929         param->setHint(kParamBoxFixedHint);
930         param->setDefault(false);
931         param->setAnimates(false);
932         desc.addClipPreferencesSlaveParam(*param);
933         if (page) {
934             page->addChild(*param);
935         }
936     }
937 
938     {
939         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamBoxPAR);
940         param->setLabel(kParamBoxPARLabel);
941         param->setHint(kParamBoxPARHint);
942         param->setRange(0., 10);
943         param->setDisplayRange(0.5, 2.);
944         param->setDefault(1.);
945         param->setAnimates(false);
946         desc.addClipPreferencesSlaveParam(*param);
947         if (page) {
948             page->addChild(*param);
949         }
950     }
951 
952     // scale
953     {
954         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamScale);
955         param->setLabel(kParamScaleLabel);
956         param->setHint(kParamScaleHint);
957         param->setDoubleType(eDoubleTypeScale);
958         //param->setDimensionLabels("w","h");
959         param->setDefault(1, 1);
960         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX);
961         param->setDisplayRange(0.1, 0.1, 10, 10);
962         param->setIncrement(0.01);
963         param->setUseHostNativeOverlayHandle(false);
964         param->setLayoutHint(eLayoutHintNoNewLine, 1);
965         param->setAnimates(false);
966         desc.addClipPreferencesSlaveParam(*param);
967         if (page) {
968             page->addChild(*param);
969         }
970     }
971 
972     // scaleUniform
973     {
974         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamScaleUniform);
975         param->setLabel(kParamScaleUniformLabel);
976         param->setHint(kParamScaleUniformHint);
977         // uniform parameter is false by default on Natron
978         // https://github.com/MrKepzie/Natron/issues/1204
979         param->setDefault(!getImageEffectHostDescription()->isNatron);
980         param->setLayoutHint(eLayoutHintDivider);
981         param->setAnimates(false);
982         desc.addClipPreferencesSlaveParam(*param);
983         if (page) {
984             page->addChild(*param);
985         }
986     }
987 
988     // resize
989     {
990         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamResize);
991         param->setLabel(kParamResizeLabel);
992         param->setHint(kParamResizeHint);
993         assert(param->getNOptions() == eResizeNone);
994         param->appendOption(kParamResizeOptionNone);
995         assert(param->getNOptions() == eResizeWidth);
996         param->appendOption(kParamResizeOptionWidth);
997         assert(param->getNOptions() == eResizeHeight);
998         param->appendOption(kParamResizeOptionHeight);
999         assert(param->getNOptions() == eResizeFit);
1000         param->appendOption(kParamResizeOptionFit);
1001         assert(param->getNOptions() == eResizeFill);
1002         param->appendOption(kParamResizeOptionFill);
1003         assert(param->getNOptions() == eResizeDistort);
1004         param->appendOption(kParamResizeOptionDistort);
1005         param->setDefault( (int)eResizeWidth );
1006         param->setAnimates(false);
1007         desc.addClipPreferencesSlaveParam(*param);
1008         if (page) {
1009             page->addChild(*param);
1010         }
1011     }
1012 
1013     // center
1014     {
1015         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamReformatCenter);
1016         param->setLabel(kParamReformatCenterLabel);
1017         param->setHint(kParamReformatCenterHint);
1018         param->setDefault(true);
1019         param->setAnimates(false);
1020         desc.addClipPreferencesSlaveParam(*param);
1021         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1022         if (page) {
1023             page->addChild(*param);
1024         }
1025     }
1026 
1027     // flip
1028     {
1029         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamFlip);
1030         param->setLabel(kParamFlipLabel);
1031         param->setHint(kParamFlipHint);
1032         param->setDefault(false);
1033         param->setAnimates(false);
1034         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1035         if (page) {
1036             page->addChild(*param);
1037         }
1038     }
1039 
1040     // flop
1041     {
1042         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamFlop);
1043         param->setLabel(kParamFlopLabel);
1044         param->setHint(kParamFlopHint);
1045         param->setDefault(false);
1046         param->setAnimates(false);
1047         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1048         if (page) {
1049             page->addChild(*param);
1050         }
1051     }
1052 
1053     // turn
1054     {
1055         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTurn);
1056         param->setLabel(kParamTurnLabel);
1057         param->setHint(kParamTurnHint);
1058         param->setDefault(false);
1059         param->setAnimates(false);
1060         desc.addClipPreferencesSlaveParam(*param);
1061         param->getPropertySet().propSetInt(kOfxParamPropLayoutPadWidth, 1, false);
1062         if (page) {
1063             page->addChild(*param);
1064         }
1065     }
1066 
1067     // Preserve bounding box
1068     {
1069         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPreserveBoundingBox);
1070         param->setLabel(kParamPreserveBoundingBoxLabel);
1071         param->setHint(kParamPreserveBoundingBoxHint);
1072         param->setDefault(false);
1073         param->setAnimates(false);
1074         desc.addClipPreferencesSlaveParam(*param);
1075         if (page) {
1076             page->addChild(*param);
1077         }
1078     }
1079 
1080     // clamp, filter, black outside
1081     ofxsFilterDescribeParamsInterpolate2D(desc, page, /*blackOutsideDefault*/ false);
1082 } // ReformatPluginFactory::describeInContext
1083 
1084 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1085 ReformatPluginFactory::createInstance(OfxImageEffectHandle handle,
1086                                       ContextEnum /*context*/)
1087 {
1088     return new ReformatPlugin(handle);
1089 }
1090 
1091 static ReformatPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1092 mRegisterPluginFactoryInstance(p)
1093 
1094 OFXS_NAMESPACE_ANONYMOUS_EXIT
1095