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