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 CornerPin plugin.
21  */
22 
23 /*
24    Although the indications from nuke/fnOfxExtensions.h were followed, and the
25    kFnOfxImageEffectActionGetTransform action was implemented in the Support
26    library, that action is never called by the Nuke host, so it cannot be tested.
27    The code is left here for reference or for further extension.
28 
29    There is also an open question about how the last plugin in a transform chain
30    may get the concatenated transform from upstream, the untransformed source image,
31    concatenate its own transform and apply the resulting transform in its render
32    action. Should the host be doing this instead?
33  */
34 
35 #include <cmath>
36 #include <cfloat> // DBL_MAX
37 #include <vector>
38 #ifdef DEBUG
39 #include <iostream>
40 #endif
41 
42 #ifdef __APPLE__
43 #include <OpenGL/gl.h>
44 #else
45 #ifdef _WIN32
46 #define WIN32_LEAN_AND_MEAN
47 #ifndef NOMINMAX
48 #define NOMINMAX
49 #endif
50 #include <windows.h>
51 #endif
52 
53 #include <GL/gl.h>
54 #endif
55 
56 #include "ofxsOGLTextRenderer.h"
57 #include "ofxsTransform3x3.h"
58 #include "ofxsThreadSuite.h"
59 
60 using namespace OFX;
61 
62 OFXS_NAMESPACE_ANONYMOUS_ENTER
63 
64 #define kPluginName "CornerPinOFX"
65 #define kPluginMaskedName "CornerPinMaskedOFX"
66 #define kPluginGrouping "Transform"
67 #define kPluginDescription \
68     "Allows an image to fit another in translation, rotation and scale.\n" \
69     "The resulting transform is a translation if 1 point is enabled, a " \
70     "similarity if 2 are enabled, an affine transform if 3 are enabled, " \
71     "and a homography if they are all enabled.\n" \
72     "\n" \
73     "An effect where an image transitions from a full-frame image to an image " \
74     "placed on a billboard or a screen, or a crash zoom effect, can be obtained " \
75     "by combining the Transform and CornerPin effects and using the Amount " \
76     "parameter on both effects.\n" \
77     "Apply a CornerPin followed by a Transform effect (the order is important) " \
78     "and visualize the output superimposed on " \
79     "the target image. While leaving the value of the Amount parameter at 1, " \
80     "tune the Transform parameters (including Scale and Skew) so that the " \
81     "transformed image is as close as possible to the desired target location.\n" \
82     "Then, adjust the 'to' points of the CornerPin effect (which " \
83     "should be affected by the Transform) so that the warped image perfectly matches the " \
84     "desired target location. Link the Amount parameter of the Transform and " \
85     "CornerPin effects.\n" \
86     "Finally, by animating the Amount parameter of both effects from 0 to 1, " \
87     "the image goes progressively, and with minimal deformations, from " \
88     "full-frame to the target location, creating the desired effect (motion " \
89     "blur can be added on the Transform node, too).\n" \
90     "Note that if only the CornerPin effect is used instead of combining " \
91     "CornerPin and Transform, the position of the CornerPin points is linearly " \
92     "interpolated between their 'from' position and their 'to' position, which " \
93     "may result in unrealistic image motion, where the image shrinks and " \
94     "expands, especially when the image rotates.\n" \
95     "\n" \
96     "This plugin concatenates transforms.\n" \
97     "See also: http://opticalenquiry.com/nuke/index.php?title=CornerPin"
98 
99 #define kPluginIdentifier "net.sf.openfx.CornerPinPlugin"
100 #define kPluginMaskedIdentifier "net.sf.openfx.CornerPinMaskedPlugin"
101 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
102 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
103 
104 #define POINT_SIZE 5
105 #define POINT_TOLERANCE 6
106 
107 #define kGroupTo "to"
108 #define kGroupToLabel "To"
109 static const char* const kParamTo[4] = {
110     "to1",
111     "to2",
112     "to3",
113     "to4"
114 };
115 static const char* const kParamEnable[4] = {
116     "enable1",
117     "enable2",
118     "enable3",
119     "enable4"
120 };
121 #define kParamEnableHint "Enables the point on the left."
122 
123 #define kGroupFrom "from"
124 #define kGroupFromLabel "From"
125 static const char* const kParamFrom[4] = {
126     "from1",
127     "from2",
128     "from3",
129     "from4"
130 };
131 
132 
133 #define kParamCopyFrom "copyFrom"
134 #define kParamCopyFromLabel "Copy \"From\""
135 #define kParamCopyFromHint "Copy the contents (including animation) of the \"from\" points to the \"to\" points."
136 
137 #define kParamCopyFromSingle "copyFromSingle"
138 #define kParamCopyFromSingleLabel "Copy \"From\" (Single)"
139 #define kParamCopyFromSingleHint "Copy the current values of the \"from\" points to the \"to\" points."
140 
141 #define kParamCopyTo "copyTo"
142 #define kParamCopyToLabel "Copy \"To\""
143 #define kParamCopyToHint "Copy the contents (including animation) of the \"to\" points to the \"from\" points."
144 
145 #define kParamCopyToSingle "copyToSingle"
146 #define kParamCopyToSingleLabel "Copy \"To\" (Single)"
147 #define kParamCopyToSingleHint "Copy the current values of the \"to\" points to the \"from\" points."
148 
149 #define kParamCopyInputRoD "setToInputRod"
150 #define kParamCopyInputRoDLabel "Set to input rod"
151 #define kParamCopyInputRoDHint "Copy the values from the source region of definition into the \"from\" points."
152 
153 #define kParamOverlayPoints "overlayPoints"
154 #define kParamOverlayPointsLabel "Overlay Points"
155 #define kParamOverlayPointsHint "Whether to display the \"from\" or the \"to\" points in the overlay"
156 #define kParamOverlayPointsOptionTo "To", "Display the \"to\" points.", "to"
157 #define kParamOverlayPointsOptionFrom "From", "Display the \"from\" points.", "from"
158 
159 #define kParamTransformAmount "transformAmount"
160 #define kParamTransformAmountLabel "Amount"
161 #define kParamTransformAmountHint "Amount of transform to apply (excluding the extra matrix, which is always applied). 0 means the transform is identity, 1 means to apply the full transform. Intermediate transforms are computed by linear interpolation between the 'from' and the 'to' points. See the plugin description on how to use the amount parameter for a crash zoom effect."
162 
163 #define kGroupExtraMatrix "transformMatrix"
164 #define kGroupExtraMatrixLabel "Extra Matrix"
165 #define kGroupExtraMatrixHint "This matrix gets concatenated to the transform defined by the other parameters."
166 #define kParamExtraMatrixRow1 "transform_row1"
167 #define kParamExtraMatrixRow2 "transform_row2"
168 #define kParamExtraMatrixRow3 "transform_row3"
169 
170 #define kParamTransformInteractive "interactive"
171 #define kParamTransformInteractiveLabel "Interactive Update"
172 #define kParamTransformInteractiveHint "If checked, update the parameter values during interaction with the image viewer, else update the values when pen is released."
173 
174 #define kParamSrcClipChanged "srcClipChanged"
175 
176 // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
177 #define kParamDefaultsNormalised "defaultsNormalised"
178 
179 #define POINT_INTERACT_LINE_SIZE_PIXELS 20
180 
181 static bool gHostSupportsDefaultCoordinateSystem = true; // for kParamDefaultsNormalised
182 
183 ////////////////////////////////////////////////////////////////////////////////
184 /** @brief The plugin that does our work */
185 class CornerPinPlugin
186     : public Transform3x3Plugin
187 {
188 public:
189     /** @brief ctor */
CornerPinPlugin(OfxImageEffectHandle handle,bool masked)190     CornerPinPlugin(OfxImageEffectHandle handle,
191                     bool masked)
192         : Transform3x3Plugin(handle, masked, Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur)
193         , _transformAmount(NULL)
194         , _extraMatrixRow1(NULL)
195         , _extraMatrixRow2(NULL)
196         , _extraMatrixRow3(NULL)
197         , _srcClipChanged(NULL)
198     {
199         // NON-GENERIC
200         for (int i = 0; i < 4; ++i) {
201             _to[i] = fetchDouble2DParam(kParamTo[i]);
202             _enable[i] = fetchBooleanParam(kParamEnable[i]);
203             _from[i] = fetchDouble2DParam(kParamFrom[i]);
204             assert(_to[i] && _enable[i] && _from[i]);
205         }
206         _transformAmount = fetchDoubleParam(kParamTransformAmount);
207         _extraMatrixRow1 = fetchDouble3DParam(kParamExtraMatrixRow1);
208         _extraMatrixRow2 = fetchDouble3DParam(kParamExtraMatrixRow2);
209         _extraMatrixRow3 = fetchDouble3DParam(kParamExtraMatrixRow3);
210         assert(_extraMatrixRow1 && _extraMatrixRow2 && _extraMatrixRow3);
211 
212         _srcClipChanged = fetchBooleanParam(kParamSrcClipChanged);
213         assert(_srcClipChanged);
214 
215         // honor kParamDefaultsNormalised
216         if ( paramExists(kParamDefaultsNormalised) ) {
217             // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
218             // handle these ourselves!
219             BooleanParam* param = fetchBooleanParam(kParamDefaultsNormalised);
220             assert(param);
221             bool normalised = param->getValue();
222             if (normalised) {
223                 OfxPointD size = getProjectExtent();
224                 OfxPointD origin = getProjectOffset();
225                 OfxPointD p;
226                 // we must denormalise all parameters for which setDefaultCoordinateSystem(eCoordinatesNormalised) couldn't be done
227                 beginEditBlock(kParamDefaultsNormalised);
228                 for (int i = 0; i < 4; ++i) {
229                     p = _to[i]->getValue();
230                     _to[i]->setValue(p.x * size.x + origin.x, p.y * size.y + origin.y);
231                     p = _from[i]->getValue();
232                     _from[i]->setValue(p.x * size.x + origin.x, p.y * size.y + origin.y);
233                 }
234                 param->setValue(false);
235                 endEditBlock();
236             }
237         }
238     }
239 
240 private:
241 
getExtraMatrix(OfxTime time) const242     Matrix3x3 getExtraMatrix(OfxTime time) const
243     {
244         Matrix3x3 ret;
245 
246         _extraMatrixRow1->getValueAtTime(time, ret(0,0), ret(0,1), ret(0,2));
247         _extraMatrixRow2->getValueAtTime(time, ret(1,0), ret(1,1), ret(1,2));
248         _extraMatrixRow3->getValueAtTime(time, ret(2,0), ret(2,1), ret(2,2));
249 
250         return ret;
251     }
252 
253     bool getHomography(OfxTime time, const OfxPointD & scale,
254                        bool inverseTransform,
255                        const Point3D & p1,
256                        const Point3D & p2,
257                        const Point3D & p3,
258                        const Point3D & p4,
259                        Matrix3x3 & m);
260     virtual bool isIdentity(double time) OVERRIDE FINAL;
261     virtual bool getInverseTransformCanonical(double time, int view, double amount, bool invert, Matrix3x3* invtransform) const OVERRIDE FINAL;
262     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
263 
264     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
265     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
266 
267 private:
268     // NON-GENERIC
269     Double2DParam* _to[4];
270     BooleanParam* _enable[4];
271     DoubleParam* _transformAmount;
272     Double3DParam* _extraMatrixRow1;
273     Double3DParam* _extraMatrixRow2;
274     Double3DParam* _extraMatrixRow3;
275     Double2DParam* _from[4];
276     BooleanParam* _srcClipChanged; // set to true the first time the user connects src
277 };
278 
279 
280 bool
getInverseTransformCanonical(OfxTime time,int,double amount,bool invert,Matrix3x3 * invtransform) const281 CornerPinPlugin::getInverseTransformCanonical(OfxTime time,
282                                               int /*view*/,
283                                               double amount,
284                                               bool invert,
285                                               Matrix3x3* invtransform) const
286 {
287     // in this new version, both from and to are enabled/disabled at the same time
288     bool enable[4];
289     Point3D p[2][4];
290     int f = invert ? 0 : 1;
291     int t = invert ? 1 : 0;
292     int k = 0;
293 
294     for (int i = 0; i < 4; ++i) {
295         _enable[i]->getValueAtTime(time, enable[i]);
296         if (enable[i]) {
297             _from[i]->getValueAtTime(time, p[f][k].x, p[f][k].y);
298             _to[i]->getValueAtTime(time, p[t][k].x, p[t][k].y);
299             ++k;
300         }
301         p[0][i].z = p[1][i].z = 1.;
302     }
303 
304     amount *= _transformAmount->getValueAtTime(time);
305 
306     if (amount != 1.) {
307         int k = 0;
308         for (int i = 0; i < 4; ++i) {
309             if (enable[i]) {
310                 p[t][k].x = p[f][k].x + amount * (p[t][k].x - p[f][k].x);
311                 p[t][k].y = p[f][k].y + amount * (p[t][k].y - p[f][k].y);
312                 ++k;
313             }
314         }
315     }
316 
317     // k contains the number of valid points
318     Matrix3x3 homo3x3;
319     bool success = false;
320 
321     assert(0 <= k && k <= 4);
322     if (k == 0) {
323         // no points, only apply extraMat;
324         *invtransform = getExtraMatrix(time);
325 
326         return true;
327     }
328 
329     switch (k) {
330     case 4:
331         success = homo3x3.setHomographyFromFourPoints(p[0][0], p[0][1], p[0][2], p[0][3], p[1][0], p[1][1], p[1][2], p[1][3]);
332         break;
333     case 3:
334         success = homo3x3.setAffineFromThreePoints(p[0][0], p[0][1], p[0][2], p[1][0], p[1][1], p[1][2]);
335         break;
336     case 2:
337         success = homo3x3.setSimilarityFromTwoPoints(p[0][0], p[0][1], p[1][0], p[1][1]);
338         break;
339     case 1:
340         success = homo3x3.setTranslationFromOnePoint(p[0][0], p[1][0]);
341         break;
342     }
343     if (!success) {
344         ///cannot compute the homography when 3 points are aligned
345         return false;
346     }
347 
348     // make sure that p[0][0].xy transforms to a positive z. (before composing with the extra matrix)
349     Point3D p0(p[0][0].x, p[0][0].y, 1.);
350     if ( (homo3x3 * p0).z < 0.) {
351         homo3x3 *= -1;
352     }
353 
354     Matrix3x3 extraMat = getExtraMatrix(time);
355     *invtransform = homo3x3 * extraMat;
356 
357     return true;
358 } // CornerPinPlugin::getInverseTransformCanonical
359 
360 // overridden is identity
361 bool
isIdentity(double time)362 CornerPinPlugin::isIdentity(double time)
363 {
364     Matrix3x3 extraMat = getExtraMatrix(time);
365 
366     if ( !extraMat.isIdentity() ) {
367         return false;
368     }
369 
370     // extraMat is identity.
371 
372     // check if amount is zero
373     if (_paramsType != eTransform3x3ParamsTypeDirBlur) {
374         double amount = _transformAmount->getValueAtTime(time);
375         if (amount == 0.) {
376             return true;
377         }
378     }
379 
380     // The transform is identity either if no point is enabled, or if
381     // all enabled from's are equal to their counterpart to
382     for (int i = 0; i < 4; ++i) {
383         bool enable;
384         _enable[i]->getValueAtTime(time, enable);
385         if (enable) {
386             OfxPointD p, q;
387             _from[i]->getValueAtTime(time, p.x, p.y);
388             _to[i]->getValueAtTime(time, q.x, q.y);
389             if ( (p.x != q.x) || (p.y != q.y) ) {
390                 return false;
391             }
392         }
393     }
394 
395     return true;
396 }
397 
398 static void
copyPoint(Double2DParam * from,Double2DParam * to)399 copyPoint(Double2DParam* from,
400           Double2DParam* to)
401 {
402     // because some hosts (e.g. Resolve) have a faulty paramCopy, we first copy
403     // all keys and values
404     OfxPointD p;
405     unsigned int keyCount = from->getNumKeys();
406 
407     to->deleteAllKeys();
408     for (unsigned int i = 0; i < keyCount; ++i) {
409         OfxTime time = from->getKeyTime(i);
410         from->getValueAtTime(time, p.x, p.y);
411         to->setValueAtTime(time, p.x, p.y);
412     }
413     if (keyCount == 0) {
414         from->getValue(p.x, p.y);
415         to->setValue(p.x, p.y);
416     }
417     // OfxParameterSuiteV1::paramCopy (does not work under Resolve, returns kOfxStatErrUnknown under Catalyst Edit)
418     try {
419         to->copyFrom(*from, 0, NULL);
420     } catch (const Exception::Suite& e) {
421 #ifdef DEBUG
422         std::cout << "OfxParameterSuiteV1 threw exception: " << e.what() << std::endl;
423 #endif
424     }
425 }
426 
427 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)428 CornerPinPlugin::changedParam(const InstanceChangedArgs &args,
429                               const std::string &paramName)
430 {
431     const double time = args.time;
432 
433     // If any parameter is set by the user, set srcClipChanged to true so that from/to is not reset when
434     // connecting the input.
435     //printf("srcClipChanged=%s\n", _srcClipChanged->getValue() ? "true" : "false");
436     if (paramName == kParamCopyInputRoD) {
437         if ( _srcClip && _srcClip->isConnected() ) {
438             beginEditBlock(paramName);
439             const OfxRectD & srcRoD = _srcClip->getRegionOfDefinition(time);
440             _from[0]->setValue(srcRoD.x1, srcRoD.y1);
441             _from[1]->setValue(srcRoD.x2, srcRoD.y1);
442             _from[2]->setValue(srcRoD.x2, srcRoD.y2);
443             _from[3]->setValue(srcRoD.x1, srcRoD.y2);
444             changedTransform(args);
445             if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
446                 _srcClipChanged->setValue(true);
447             }
448             endEditBlock();
449         }
450     } else if (paramName == kParamCopyFrom) {
451         beginEditBlock(paramName);
452         for (int i = 0; i < 4; ++i) {
453             copyPoint(_from[i], _to[i]);
454         }
455         changedTransform(args);
456         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
457             _srcClipChanged->setValue(true);
458         }
459         endEditBlock();
460     } else if (paramName == kParamCopyFromSingle) {
461         beginEditBlock(paramName);
462         for (int i = 0; i < 4; ++i) {
463             _to[i]->setValue( _from[i]->getValueAtTime(time) );
464         }
465         changedTransform(args);
466         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
467             _srcClipChanged->setValue(true);
468         }
469         endEditBlock();
470     } else if (paramName == kParamCopyTo) {
471         beginEditBlock(paramName);
472         for (int i = 0; i < 4; ++i) {
473             copyPoint(_to[i], _from[i]);
474         }
475         changedTransform(args);
476         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
477             _srcClipChanged->setValue(true);
478         }
479         endEditBlock();
480     } else if (paramName == kParamCopyToSingle) {
481         beginEditBlock(paramName);
482         for (int i = 0; i < 4; ++i) {
483             _from[i]->setValue( _to[i]->getValueAtTime(time) );
484         }
485         changedTransform(args);
486         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
487             _srcClipChanged->setValue(true);
488         }
489         endEditBlock();
490     } else if ( (paramName == kParamTo[0]) ||
491                 ( paramName == kParamTo[1]) ||
492                 ( paramName == kParamTo[2]) ||
493                 ( paramName == kParamTo[3]) ||
494                 ( paramName == kParamEnable[0]) ||
495                 ( paramName == kParamEnable[1]) ||
496                 ( paramName == kParamEnable[2]) ||
497                 ( paramName == kParamEnable[3]) ||
498                 ( paramName == kParamFrom[0]) ||
499                 ( paramName == kParamFrom[1]) ||
500                 ( paramName == kParamFrom[2]) ||
501                 ( paramName == kParamFrom[3]) ||
502                 ( paramName == kParamExtraMatrixRow1) ||
503                 ( paramName == kParamExtraMatrixRow2) ||
504                 ( paramName == kParamExtraMatrixRow3) ) {
505         beginEditBlock(paramName);
506         changedTransform(args);
507         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
508             _srcClipChanged->setValue(true);
509         }
510         endEditBlock();
511     } else {
512         Transform3x3Plugin::changedParam(args, paramName);
513     }
514 } // CornerPinPlugin::changedParam
515 
516 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)517 CornerPinPlugin::changedClip(const InstanceChangedArgs &args,
518                              const std::string &clipName)
519 {
520     const double time = args.time;
521 
522     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
523          _srcClip && _srcClip->isConnected() &&
524          !_srcClipChanged->getValue() &&
525          ( args.reason == eChangeUserEdit) ) {
526         const OfxRectD & srcRoD = _srcClip->getRegionOfDefinition(time);
527         beginEditBlock(clipName);
528         _from[0]->setValue(srcRoD.x1, srcRoD.y1);
529         _from[1]->setValue(srcRoD.x2, srcRoD.y1);
530         _from[2]->setValue(srcRoD.x2, srcRoD.y2);
531         _from[3]->setValue(srcRoD.x1, srcRoD.y2);
532         _to[0]->setValue(srcRoD.x1, srcRoD.y1);
533         _to[1]->setValue(srcRoD.x2, srcRoD.y1);
534         _to[2]->setValue(srcRoD.x2, srcRoD.y2);
535         _to[3]->setValue(srcRoD.x1, srcRoD.y2);
536         changedTransform(args);
537         if ( (args.reason == eChangeUserEdit) && !_srcClipChanged->getValue() ) {
538             _srcClipChanged->setValue(true);
539         }
540         endEditBlock();
541     }
542 }
543 
544 class CornerPinTransformInteract
545     : public OverlayInteract
546 {
547 public:
548 
CornerPinTransformInteract(OfxInteractHandle handle,ImageEffect * effect)549     CornerPinTransformInteract(OfxInteractHandle handle,
550                                ImageEffect* effect)
551         : OverlayInteract(handle)
552         , _plugin( dynamic_cast<CornerPinPlugin*>(effect) )
553         , _invert(NULL)
554         , _overlayPoints(NULL)
555         , _interactive(NULL)
556         , _dragging(-1)
557         , _hovering(-1)
558         , _lastMousePos()
559     {
560         assert(_plugin);
561         for (int i = 0; i < 4; ++i) {
562             _to[i] = _plugin->fetchDouble2DParam(kParamTo[i]);
563             _from[i] = _plugin->fetchDouble2DParam(kParamFrom[i]);
564             _enable[i] = _plugin->fetchBooleanParam(kParamEnable[i]);
565             assert(_to[i] && _from[i] && _enable[i]);
566             addParamToSlaveTo(_to[i]);
567             addParamToSlaveTo(_from[i]);
568             addParamToSlaveTo(_enable[i]);
569         }
570         _invert = _plugin->fetchBooleanParam(kParamTransform3x3Invert);
571         addParamToSlaveTo(_invert);
572         _overlayPoints = _plugin->fetchChoiceParam(kParamOverlayPoints);
573         addParamToSlaveTo(_overlayPoints);
574         _interactive = _plugin->fetchBooleanParam(kParamTransformInteractive);
575         assert(_invert && _overlayPoints && _interactive);
576 
577         for (int i = 0; i < 4; ++i) {
578             _toDrag[i].x = _toDrag[i].y = 0;
579             _fromDrag[i].x = _fromDrag[i].y = 0;
580             _enableDrag[i] = false;
581         }
582         _useFromDrag = false;
583         _interactiveDrag = false;
584     }
585 
586     // overridden functions from Interact to do things
587     virtual bool draw(const DrawArgs &args) OVERRIDE FINAL;
588     virtual bool penMotion(const PenArgs &args) OVERRIDE FINAL;
589     virtual bool penDown(const PenArgs &args) OVERRIDE FINAL;
590     virtual bool penUp(const PenArgs &args) OVERRIDE FINAL;
591     //virtual bool keyDown(const KeyArgs &args) OVERRIDE FINAL;
592     //virtual bool keyUp(const KeyArgs &args) OVERRIDE FINAL;
593     virtual void loseFocus(const FocusArgs &args) OVERRIDE FINAL;
594 
595 private:
596 
597     /**
598      * @brief Returns true if the points that should be used by the overlay are
599      * the "from" points, otherwise the overlay is assumed to use the "to" points.
600      **/
601     /*
602        bool isFromPoints(double time) const
603        {
604         int v;
605         _overlayPoints->getValueAtTime(time, v);
606         return v == 1;
607        }
608      */
609     CornerPinPlugin* _plugin;
610     Double2DParam* _to[4];
611     Double2DParam* _from[4];
612     BooleanParam* _enable[4];
613     BooleanParam* _invert;
614     ChoiceParam* _overlayPoints;
615     BooleanParam* _interactive;
616     int _dragging; // -1: idle, else dragging point number
617     int _hovering; // -1: idle, else hovering point number
618     OfxPointD _lastMousePos;
619     OfxPointD _toDrag[4];
620     OfxPointD _fromDrag[4];
621     bool _enableDrag[4];
622     bool _useFromDrag;
623     bool _interactiveDrag;
624 };
625 
626 static bool
isNearby(const OfxPointD & p,double x,double y,double tolerance,const OfxPointD & pscale)627 isNearby(const OfxPointD & p,
628          double x,
629          double y,
630          double tolerance,
631          const OfxPointD & pscale)
632 {
633     return std::fabs(p.x - x) <= tolerance * pscale.x &&  std::fabs(p.y - y) <= tolerance * pscale.y;
634 }
635 
636 bool
draw(const DrawArgs & args)637 CornerPinTransformInteract::draw(const DrawArgs &args)
638 {
639 #if 0 //def DEBUG
640     const OfxPointD &pscale = args.pixelScale;
641     // kOfxInteractPropPixelScale gives the size of a screen pixel in canonical coordinates.
642     // - it is correct under Nuke and Natron
643     // - it is always (1,1) under Resolve
644     std::cout << "pixelScale: " << pscale.x << ',' << pscale.y << std::endl;
645 #endif
646     const double time = args.time;
647     OfxRGBColourD color = {
648         0.8, 0.8, 0.8
649     };
650     getSuggestedColour(color);
651     GLdouble projection[16];
652     glGetDoublev( GL_PROJECTION_MATRIX, projection);
653     GLint viewport[4];
654     glGetIntegerv(GL_VIEWPORT, viewport);
655 #ifdef CORNERPIN_DEBUG_OPENGL
656     GLdouble modelview[16];
657     glGetDoublev( GL_MODELVIEW_MATRIX, modelview);
658     std::cout << "modelview:\n";
659     {
660         GLdouble *m = modelview;
661         std::cout << "[[" << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << "]\n [" << m[4] << ',' << m[5] << ',' << m[6] << ',' << m[7] << "]\n [" << m[8] << ',' << m[9] << ',' << m[10] << ',' << m[11] << "]\n [" << m[12] << ',' << m[13] << ',' << m[14] << ',' << m[15] << "]]\n";
662     }
663     std::cout << "projection:\n";
664     {
665         GLdouble *m = projection;
666         std::cout << "[[" << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << "]\n [" << m[4] << ',' << m[5] << ',' << m[6] << ',' << m[7] << "]\n [" << m[8] << ',' << m[9] << ',' << m[10] << ',' << m[11] << "]\n [" << m[12] << ',' << m[13] << ',' << m[14] << ',' << m[15] << "]]\n";
667     }
668     {
669         std::cout << "projection*modelview:\n";
670         Matrix4x4 p(projection);
671         Matrix4x4 m(modelview);
672         Matrix4x4 pm = p * m;
673         std::cout << "[[" << pm(0, 0) << ',' << pm(0, 1) << ',' << pm(0, 2) << ',' << pm(0, 3) << "]\n [" << pm(1, 0) << ',' << pm(1, 1) << ',' << pm(1, 2) << ',' << pm(1, 3) << "]\n [" << pm(2, 0) << ',' << pm(2, 1) << ',' << pm(2, 2) << ',' << pm(2, 3) << "]\n [" << pm(3, 0) << ',' << pm(3, 1) << ',' << pm(3, 2) << ',' << pm(3, 3) << "]]\n";
674     }
675     std::cout << "viewport:\n";
676     {
677         std::cout << "[" << viewport[0] << ',' << viewport[1] << ',' << viewport[2] << ',' << viewport[3] << "]\n";
678     }
679 #endif
680     OfxPointD shadow; // how much to translate GL_PROJECTION to get exactly one pixel on screen
681     shadow.x = 2. / (projection[0] * viewport[2]);
682     shadow.y = 2. / (projection[5] * viewport[3]);
683 
684     OfxPointD to[4];
685     OfxPointD from[4];
686     bool enable[4];
687     bool useFrom;
688 
689     if (_dragging == -1) {
690         for (int i = 0; i < 4; ++i) {
691             _to[i]->getValueAtTime(time, to[i].x, to[i].y);
692             _from[i]->getValueAtTime(time, from[i].x, from[i].y);
693             _enable[i]->getValueAtTime(time, enable[i]);
694         }
695         int v;
696         _overlayPoints->getValueAtTime(time, v);
697         useFrom = (v == 1);
698     } else {
699         for (int i = 0; i < 4; ++i) {
700             to[i] = _toDrag[i];
701             from[i] = _fromDrag[i];
702             enable[i] = _enableDrag[i];
703         }
704         useFrom = _useFromDrag;
705     }
706 
707     OfxPointD p[4];
708     OfxPointD q[4];
709     int enableBegin = 4;
710     int enableEnd = 0;
711     for (int i = 0; i < 4; ++i) {
712         if (enable[i]) {
713             if (useFrom) {
714                 p[i] = from[i];
715                 q[i] = to[i];
716             } else {
717                 q[i] = from[i];
718                 p[i] = to[i];
719             }
720             if (i < enableBegin) {
721                 enableBegin = i;
722             }
723             if (i + 1 > enableEnd) {
724                 enableEnd = i + 1;
725             }
726         }
727     }
728 
729     //glPushAttrib(GL_ALL_ATTRIB_BITS); // caller is responsible for protecting attribs
730 
731     //glDisable(GL_LINE_STIPPLE);
732     glEnable(GL_LINE_SMOOTH);
733     //glEnable(GL_POINT_SMOOTH);
734     glEnable(GL_BLEND);
735     glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
736     glLineWidth(1.5f);
737     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
738 
739     glPointSize(POINT_SIZE);
740     // Draw everything twice
741     // l = 0: shadow
742     // l = 1: drawing
743     for (int l = 0; l < 2; ++l) {
744         // shadow (uses GL_PROJECTION)
745         glMatrixMode(GL_PROJECTION);
746         int direction = (l == 0) ? 1 : -1;
747         // translate (1,-1) pixels
748         glTranslated(direction * shadow.x, -direction * shadow.y, 0);
749         glMatrixMode(GL_MODELVIEW); // Modelview should be used on Nuke
750 
751         glColor3f( (float)(color.r / 2) * l, (float)(color.g / 2) * l, (float)(color.b / 2) * l );
752         glBegin(GL_LINES);
753         for (int i = enableBegin; i < enableEnd; ++i) {
754             if (enable[i]) {
755                 glVertex2d(p[i].x, p[i].y);
756                 glVertex2d(q[i].x, q[i].y);
757             }
758         }
759         glEnd();
760         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
761         glBegin(GL_LINE_LOOP);
762         for (int i = enableBegin; i < enableEnd; ++i) {
763             if (enable[i]) {
764                 glVertex2d(p[i].x, p[i].y);
765             }
766         }
767         glEnd();
768         glBegin(GL_POINTS);
769         for (int i = enableBegin; i < enableEnd; ++i) {
770             if (enable[i]) {
771                 if ( (_hovering == i) || (_dragging == i) ) {
772                     glColor3f(0.f * l, 1.f * l, 0.f * l);
773                 } else {
774                     glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
775                 }
776                 glVertex2d(p[i].x, p[i].y);
777             }
778         }
779         glEnd();
780         glColor3f( (float)color.r * l, (float)color.g * l, (float)color.b * l );
781         for (int i = enableBegin; i < enableEnd; ++i) {
782             if (enable[i]) {
783                 TextRenderer::bitmapString(p[i].x, p[i].y, useFrom ? kParamFrom[i] : kParamTo[i]);
784             }
785         }
786     }
787 
788     //glPopAttrib();
789 
790     return true;
791 } // CornerPinTransformInteract::draw
792 
793 bool
penMotion(const PenArgs & args)794 CornerPinTransformInteract::penMotion(const PenArgs &args)
795 {
796     const OfxPointD &pscale = args.pixelScale;
797     const double time = args.time;
798     OfxPointD to[4];
799     OfxPointD from[4];
800     bool enable[4];
801     bool useFrom;
802 
803     if (_dragging == -1) { // mouse is released
804         for (int i = 0; i < 4; ++i) {
805             _to[i]->getValueAtTime(time, to[i].x, to[i].y);
806             _from[i]->getValueAtTime(time, from[i].x, from[i].y);
807             _enable[i]->getValueAtTime(time, enable[i]);
808         }
809         int v;
810         _overlayPoints->getValueAtTime(time, v);
811         useFrom = (v == 1);
812     } else {
813         for (int i = 0; i < 4; ++i) {
814             to[i] = _toDrag[i];
815             from[i] = _fromDrag[i];
816             enable[i] = _enableDrag[i];
817         }
818         useFrom = _useFromDrag;
819     }
820 
821     OfxPointD p[4];
822     //OfxPointD q[4];
823     int enableBegin = 4;
824     int enableEnd = 0;
825     for (int i = 0; i < 4; ++i) {
826         if (enable[i]) {
827             if (useFrom) {
828                 p[i] = from[i];
829                 //q[i] = to[i];
830             } else {
831                 //q[i] = from[i];
832                 p[i] = to[i];
833             }
834             if (i < enableBegin) {
835                 enableBegin = i;
836             }
837             if (i + 1 > enableEnd) {
838                 enableEnd = i + 1;
839             }
840         }
841     }
842 
843     bool didSomething = false;
844     bool valuesChanged = false;
845     OfxPointD delta;
846     delta.x = args.penPosition.x - _lastMousePos.x;
847     delta.y = args.penPosition.y - _lastMousePos.y;
848 
849     _hovering = -1;
850 
851     for (int i = enableBegin; i < enableEnd; ++i) {
852         if (enable[i]) {
853             if (_dragging == i) {
854                 if (useFrom) {
855                     from[i].x += delta.x;
856                     from[i].y += delta.y;
857                     _fromDrag[i] = from[i];
858                 } else {
859                     to[i].x += delta.x;
860                     to[i].y += delta.y;
861                     _toDrag[i] = to[i];
862                 }
863                 valuesChanged = true;
864             } else if ( isNearby(args.penPosition, p[i].x, p[i].y, POINT_TOLERANCE, pscale) ) {
865                 _hovering = i;
866                 didSomething = true;
867             }
868         }
869     }
870 
871     if ( (_dragging != -1) && _interactiveDrag && valuesChanged ) {
872         // no need to redraw overlay since it is slave to the paramaters
873         if (useFrom) {
874             _from[_dragging]->setValue(from[_dragging].x, from[_dragging].y);
875         } else {
876             _to[_dragging]->setValue(to[_dragging].x, to[_dragging].y);
877         }
878     } else if (didSomething || valuesChanged) {
879         _effect->redrawOverlays();
880     }
881 
882     _lastMousePos = args.penPosition;
883 
884     return didSomething || valuesChanged;
885 } // CornerPinTransformInteract::penMotion
886 
887 bool
penDown(const PenArgs & args)888 CornerPinTransformInteract::penDown(const PenArgs &args)
889 {
890     const OfxPointD &pscale = args.pixelScale;
891     const double time = args.time;
892     OfxPointD to[4];
893     OfxPointD from[4];
894     bool enable[4];
895     bool useFrom;
896 
897     if (_dragging == -1) {
898         for (int i = 0; i < 4; ++i) {
899             _to[i]->getValueAtTime(time, to[i].x, to[i].y);
900             _from[i]->getValueAtTime(time, from[i].x, from[i].y);
901             _enable[i]->getValueAtTime(time, enable[i]);
902         }
903         int v;
904         _overlayPoints->getValueAtTime(time, v);
905         useFrom = (v == 1);
906         if (_interactive) {
907             _interactive->getValueAtTime(time, _interactiveDrag);
908         }
909     } else {
910         for (int i = 0; i < 4; ++i) {
911             to[i] = _toDrag[i];
912             from[i] = _fromDrag[i];
913             enable[i] = _enableDrag[i];
914         }
915         useFrom = _useFromDrag;
916     }
917 
918     OfxPointD p[4];
919     //OfxPointD q[4];
920     int enableBegin = 4;
921     int enableEnd = 0;
922     for (int i = 0; i < 4; ++i) {
923         if (enable[i]) {
924             if (useFrom) {
925                 p[i] = from[i];
926                 //q[i] = to[i];
927             } else {
928                 //q[i] = from[i];
929                 p[i] = to[i];
930             }
931             if (i < enableBegin) {
932                 enableBegin = i;
933             }
934             if (i + 1 > enableEnd) {
935                 enableEnd = i + 1;
936             }
937         }
938     }
939 
940     bool didSomething = false;
941 
942     for (int i = enableBegin; i < enableEnd; ++i) {
943         if (enable[i]) {
944             if ( isNearby(args.penPosition, p[i].x, p[i].y, POINT_TOLERANCE, pscale) ) {
945                 _dragging = i;
946                 didSomething = true;
947             }
948             _toDrag[i] = to[i];
949             _fromDrag[i] = from[i];
950             _enableDrag[i] = enable[i];
951         }
952     }
953     _useFromDrag = useFrom;
954 
955     if (didSomething) {
956         _effect->redrawOverlays();
957     }
958 
959     _lastMousePos = args.penPosition;
960 
961     return didSomething;
962 } // CornerPinTransformInteract::penDown
963 
964 bool
penUp(const PenArgs &)965 CornerPinTransformInteract::penUp(const PenArgs & /*args*/)
966 {
967     bool didSomething = _dragging != -1;
968 
969     if ( !_interactiveDrag && (_dragging != -1) ) {
970         // no need to redraw overlay since it is slave to the paramaters
971         if (_useFromDrag) {
972             _from[_dragging]->setValue(_fromDrag[_dragging].x, _fromDrag[_dragging].y);
973         } else {
974             _to[_dragging]->setValue(_toDrag[_dragging].x, _toDrag[_dragging].y);
975         }
976     } else if (didSomething) {
977         _effect->redrawOverlays();
978     }
979 
980     _dragging = -1;
981 
982     return didSomething;
983 }
984 
985 /** @brief Called when the interact is loses input focus */
986 void
loseFocus(const FocusArgs &)987 CornerPinTransformInteract::loseFocus(const FocusArgs & /*args*/)
988 {
989     _dragging = -1;
990     _hovering = -1;
991     _interactiveDrag = false;
992 }
993 
994 class CornerPinOverlayDescriptor
995     : public DefaultEffectOverlayDescriptor<CornerPinOverlayDescriptor, CornerPinTransformInteract>
996 {
997 };
998 
999 static void
defineCornerPinToDouble2DParam(ImageEffectDescriptor & desc,PageParamDescriptor * page,GroupParamDescriptor * group,int i,double x,double y)1000 defineCornerPinToDouble2DParam(ImageEffectDescriptor &desc,
1001                                PageParamDescriptor *page,
1002                                GroupParamDescriptor* group,
1003                                int i,
1004                                double x,
1005                                double y)
1006 {
1007     // size
1008     {
1009         Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTo[i]);
1010         param->setLabel(kParamTo[i]);
1011         param->setAnimates(true);
1012         param->setIncrement(1.);
1013         param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX);
1014         param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
1015         param->setDoubleType(eDoubleTypeXYAbsolute);
1016         // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
1017         if ( param->supportsDefaultCoordinateSystem() ) {
1018             param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
1019         } else {
1020             gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
1021         }
1022         param->setDefault(x, y);
1023         param->setDimensionLabels("x", "y");
1024         param->setLayoutHint(eLayoutHintNoNewLine, 1);
1025         if (group) {
1026             param->setParent(*group);
1027         }
1028         if (page) {
1029             page->addChild(*param);
1030         }
1031     }
1032 
1033     // enable
1034     {
1035         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamEnable[i]);
1036         param->setLabel(kParamEnable[i]);
1037         param->setDefault(true);
1038         param->setAnimates(true);
1039         param->setHint(kParamEnableHint);
1040         if (group) {
1041             param->setParent(*group);
1042         }
1043         if (page) {
1044             page->addChild(*param);
1045         }
1046     }
1047 }
1048 
1049 static void
defineCornerPinFromsDouble2DParam(ImageEffectDescriptor & desc,PageParamDescriptor * page,GroupParamDescriptor * group,int i,double x,double y)1050 defineCornerPinFromsDouble2DParam(ImageEffectDescriptor &desc,
1051                                   PageParamDescriptor *page,
1052                                   GroupParamDescriptor* group,
1053                                   int i,
1054                                   double x,
1055                                   double y)
1056 {
1057     Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamFrom[i]);
1058 
1059     param->setLabel(kParamFrom[i]);
1060     param->setAnimates(true);
1061     param->setIncrement(1.);
1062     param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1063     param->setDisplayRange(-10000, -10000, 10000, 10000);
1064     param->setDoubleType(eDoubleTypeXYAbsolute);
1065     // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
1066     if ( param->supportsDefaultCoordinateSystem() ) {
1067         param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
1068     } else {
1069         gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
1070     }
1071     param->setDefault(x, y);
1072     param->setDimensionLabels("x", "y");
1073     if (group) {
1074         param->setParent(*group);
1075     }
1076     if (page) {
1077         page->addChild(*param);
1078     }
1079 }
1080 
1081 static void
defineExtraMatrixRow(ImageEffectDescriptor & desc,PageParamDescriptor * page,GroupParamDescriptor * group,const std::string & name,int rowIndex,double x,double y,double z)1082 defineExtraMatrixRow(ImageEffectDescriptor &desc,
1083                      PageParamDescriptor *page,
1084                      GroupParamDescriptor* group,
1085                      const std::string & name,
1086                      int rowIndex,
1087                      double x,
1088                      double y,
1089                      double z)
1090 {
1091     Double3DParamDescriptor* param = desc.defineDouble3DParam(name);
1092 
1093     if ( getImageEffectHostDescription()->isNatron && (getImageEffectHostDescription()->versionMajor >= 2) && (getImageEffectHostDescription()->versionMinor >= 1) ) {
1094         param->setLabel(kGroupExtraMatrixLabel);
1095     } else {
1096         param->setLabels("", "", "");
1097     }
1098 
1099     param->setMatrixRow(rowIndex);
1100     param->setAnimates(true);
1101     param->setDefault(x, y, z);
1102     param->setIncrement(0.01);
1103     if (group) {
1104         param->setParent(*group);
1105     }
1106     if (page) {
1107         page->addChild(*param);
1108     }
1109 }
1110 
1111 static void
CornerPinPluginDescribeInContext(ImageEffectDescriptor & desc,ContextEnum,PageParamDescriptor * page)1112 CornerPinPluginDescribeInContext(ImageEffectDescriptor &desc,
1113                                  ContextEnum /*context*/,
1114                                  PageParamDescriptor *page)
1115 {
1116     // NON-GENERIC PARAMETERS
1117     //
1118     // toPoints
1119     {
1120         GroupParamDescriptor* group = desc.defineGroupParam(kGroupTo);
1121         if (group) {
1122             group->setLabel(kGroupTo);
1123             group->setAsTab();
1124             if (page) {
1125                 page->addChild(*group);
1126             }
1127         }
1128 
1129         defineCornerPinToDouble2DParam(desc, page, group, 0, 0, 0);
1130         defineCornerPinToDouble2DParam(desc, page, group, 1, 1, 0);
1131         defineCornerPinToDouble2DParam(desc, page, group, 2, 1, 1);
1132         defineCornerPinToDouble2DParam(desc, page, group, 3, 0, 1);
1133 
1134         // copyFrom
1135         {
1136             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamCopyFrom);
1137             param->setLabel(kParamCopyFromLabel);
1138             param->setHint(kParamCopyFromHint);
1139             param->setLayoutHint(eLayoutHintNoNewLine, 1);
1140             if (group) {
1141                 param->setParent(*group);
1142             }
1143             if (page) {
1144                 page->addChild(*param);
1145             }
1146         }
1147         // copyFromSingle
1148         {
1149             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamCopyFromSingle);
1150             param->setLabel(kParamCopyFromSingleLabel);
1151             param->setHint(kParamCopyFromSingleHint);
1152             if (group) {
1153                 param->setParent(*group);
1154             }
1155             if (page) {
1156                 page->addChild(*param);
1157             }
1158         }
1159     }
1160 
1161     // fromPoints
1162     {
1163         GroupParamDescriptor* group = desc.defineGroupParam(kGroupFrom);
1164         if (group) {
1165             group->setLabel(kGroupFrom);
1166             group->setAsTab();
1167             if (page) {
1168                 page->addChild(*group);
1169             }
1170         }
1171 
1172         defineCornerPinFromsDouble2DParam(desc, page, group, 0, 0, 0);
1173         defineCornerPinFromsDouble2DParam(desc, page, group, 1, 1, 0);
1174         defineCornerPinFromsDouble2DParam(desc, page, group, 2, 1, 1);
1175         defineCornerPinFromsDouble2DParam(desc, page, group, 3, 0, 1);
1176 
1177         // setToInput
1178         {
1179             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamCopyInputRoD);
1180             param->setLabel(kParamCopyInputRoDLabel);
1181             param->setHint(kParamCopyInputRoDHint);
1182             param->setLayoutHint(eLayoutHintNoNewLine, 1);
1183             if (group) {
1184                 param->setParent(*group);
1185             }
1186             if (page) {
1187                 page->addChild(*param);
1188             }
1189         }
1190 
1191         // copyTo
1192         {
1193             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamCopyTo);
1194             param->setLabel(kParamCopyToLabel);
1195             param->setHint(kParamCopyToHint);
1196             param->setLayoutHint(eLayoutHintNoNewLine, 1);
1197             if (group) {
1198                 param->setParent(*group);
1199             }
1200             if (page) {
1201                 page->addChild(*param);
1202             }
1203         }
1204         // copyToSingle
1205         {
1206             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamCopyToSingle);
1207             param->setLabel(kParamCopyToSingleLabel);
1208             param->setHint(kParamCopyToSingleHint);
1209             if (group) {
1210                 param->setParent(*group);
1211             }
1212             if (page) {
1213                 page->addChild(*param);
1214             }
1215         }
1216     }
1217 
1218     // amount
1219     {
1220         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTransformAmount);
1221         param->setLabel(kParamTransformAmountLabel);
1222         param->setHint(kParamTransformAmountHint);
1223         param->setDoubleType(eDoubleTypeScale);
1224         param->setDefault(1.);
1225         param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1226         param->setDisplayRange(0., 1.);
1227         param->setIncrement(0.01);
1228         //if (group) {
1229         //    param->setParent(*group);
1230         //}
1231         if (page) {
1232             page->addChild(*param);
1233         }
1234     }
1235 
1236     // extraMatrix
1237     {
1238         GroupParamDescriptor* group = desc.defineGroupParam(kGroupExtraMatrix);
1239         if (group) {
1240             group->setLabel(kGroupExtraMatrixLabel);
1241             group->setHint(kGroupExtraMatrixHint);
1242             group->setOpen(false);
1243             if (page) {
1244                 page->addChild(*group);
1245             }
1246         }
1247 
1248         defineExtraMatrixRow(desc, page, group, kParamExtraMatrixRow1, 1, 1, 0, 0);
1249         defineExtraMatrixRow(desc, page, group, kParamExtraMatrixRow2, 2, 0, 1, 0);
1250         defineExtraMatrixRow(desc, page, group, kParamExtraMatrixRow3, 3, 0, 0, 1);
1251     }
1252 
1253     // overlayPoints
1254     {
1255         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOverlayPoints);
1256         param->setLabel(kParamOverlayPointsLabel);
1257         param->setHint(kParamOverlayPointsHint);
1258         param->appendOption(kParamOverlayPointsOptionTo);
1259         param->appendOption(kParamOverlayPointsOptionFrom);
1260         param->setDefault(0);
1261         param->setEvaluateOnChange(false);
1262         if (page) {
1263             page->addChild(*param);
1264         }
1265     }
1266 
1267     // interactive
1268     {
1269         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransformInteractive);
1270         param->setLabel(kParamTransformInteractiveLabel);
1271         param->setHint(kParamTransformInteractiveHint);
1272         param->setEvaluateOnChange(false);
1273         if (page) {
1274             page->addChild(*param);
1275         }
1276     }
1277 
1278     // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
1279     if (!gHostSupportsDefaultCoordinateSystem) {
1280         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamDefaultsNormalised);
1281         param->setDefault(true);
1282         param->setEvaluateOnChange(false);
1283         param->setIsSecretAndDisabled(true);
1284         param->setIsPersistent(true);
1285         param->setAnimates(false);
1286         if (page) {
1287             page->addChild(*param);
1288         }
1289     }
1290 } // CornerPinPluginDescribeInContext
1291 
1292 mDeclarePluginFactory(CornerPinPluginFactory, {ofxsThreadSuiteCheck();}, {});
1293 void
describe(ImageEffectDescriptor & desc)1294 CornerPinPluginFactory::describe(ImageEffectDescriptor &desc)
1295 {
1296     // basic labels
1297     desc.setLabel(kPluginName);
1298     desc.setPluginGrouping(kPluginGrouping);
1299     desc.setPluginDescription(kPluginDescription);
1300 
1301     Transform3x3Describe(desc, false);
1302 
1303     desc.setOverlayInteractDescriptor(new CornerPinOverlayDescriptor);
1304 }
1305 
1306 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1307 CornerPinPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1308                                           ContextEnum context)
1309 {
1310     // make some pages and to things in
1311     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, false);
1312 
1313     CornerPinPluginDescribeInContext(desc, context, page);
1314 
1315     Transform3x3DescribeInContextEnd(desc, context, page, false, Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur);
1316 
1317     // srcClipChanged
1318     {
1319         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSrcClipChanged);
1320         param->setDefault(false);
1321         param->setIsSecretAndDisabled(true);
1322         param->setAnimates(false);
1323         param->setEvaluateOnChange(false);
1324         page->addChild(*param);
1325     }
1326 }
1327 
1328 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1329 CornerPinPluginFactory::createInstance(OfxImageEffectHandle handle,
1330                                        ContextEnum /*context*/)
1331 {
1332     return new CornerPinPlugin(handle, false);
1333 }
1334 
1335 mDeclarePluginFactory(CornerPinMaskedPluginFactory, {ofxsThreadSuiteCheck();}, {});
1336 void
describe(ImageEffectDescriptor & desc)1337 CornerPinMaskedPluginFactory::describe(ImageEffectDescriptor &desc)
1338 {
1339     // basic labels
1340     desc.setLabel(kPluginMaskedName);
1341     desc.setPluginGrouping(kPluginGrouping);
1342     desc.setPluginDescription(kPluginDescription);
1343 
1344     Transform3x3Describe(desc, true);
1345 
1346     desc.setOverlayInteractDescriptor(new CornerPinOverlayDescriptor);
1347 }
1348 
1349 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)1350 CornerPinMaskedPluginFactory::describeInContext(ImageEffectDescriptor &desc,
1351                                                 ContextEnum context)
1352 {
1353     // make some pages and to things in
1354     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, true);
1355 
1356     CornerPinPluginDescribeInContext(desc, context, page);
1357 
1358     Transform3x3DescribeInContextEnd(desc, context, page, true, Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur);
1359 
1360     // srcClipChanged
1361     {
1362         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSrcClipChanged);
1363         param->setDefault(false);
1364         param->setIsSecretAndDisabled(true);
1365         param->setAnimates(false);
1366         param->setEvaluateOnChange(false);
1367         page->addChild(*param);
1368     }
1369 }
1370 
1371 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1372 CornerPinMaskedPluginFactory::createInstance(OfxImageEffectHandle handle,
1373                                              ContextEnum /*context*/)
1374 {
1375     return new CornerPinPlugin(handle, true);
1376 }
1377 
1378 static CornerPinPluginFactory p1(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1379 static CornerPinMaskedPluginFactory p2(kPluginMaskedIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1380 mRegisterPluginFactoryInstance(p1)
1381 mRegisterPluginFactoryInstance(p2)
1382 
1383 OFXS_NAMESPACE_ANONYMOUS_EXIT
1384