1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX Transform & DirBlur plugins.
21  */
22 
23 #include <cmath>
24 #include <iostream>
25 
26 #include "ofxsTransform3x3.h"
27 #include "ofxsTransformInteract.h"
28 #include "ofxsCoords.h"
29 #include "ofxsThreadSuite.h"
30 
31 using namespace OFX;
32 
33 OFXS_NAMESPACE_ANONYMOUS_ENTER
34 
35 #define kPluginName "TransformOFX"
36 #define kPluginMaskedName "TransformMaskedOFX"
37 #define kPluginGrouping "Transform"
38 #define kPluginDescription "Translate / Rotate / Scale a 2D image.\n" \
39     "This plugin concatenates transforms.\n" \
40     "See also http://opticalenquiry.com/nuke/index.php?title=Transform"
41 
42 #define kPluginMaskedDescription "Translate / Rotate / Scale a 2D image, with optional masking.\n" \
43     "This plugin concatenates transforms upstream."
44 #define kPluginIdentifier "net.sf.openfx.TransformPlugin"
45 #define kPluginMaskedIdentifier "net.sf.openfx.TransformMaskedPlugin"
46 #define kPluginDirBlurName "DirBlurOFX"
47 #define kPluginDirBlurGrouping "Filter"
48 #define kPluginDirBlurDescription "Apply directional blur to an image.\n" \
49     "This plugin concatenates transforms upstream."
50 #define kPluginDirBlurIdentifier "net.sf.openfx.DirBlur"
51 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
52 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
53 
54 #define kParamSrcClipChanged "srcClipChanged"
55 
56 
57 ////////////////////////////////////////////////////////////////////////////////
58 /** @brief The plugin that does our work */
59 class TransformPlugin
60     : public Transform3x3Plugin
61 {
62 public:
63     /** @brief ctor */
TransformPlugin(OfxImageEffectHandle handle,bool masked,bool isDirBlur)64     TransformPlugin(OfxImageEffectHandle handle,
65                     bool masked,
66                     bool isDirBlur)
67         : Transform3x3Plugin(handle, masked, isDirBlur ? eTransform3x3ParamsTypeDirBlur : eTransform3x3ParamsTypeMotionBlur)
68         , _translate(NULL)
69         , _rotate(NULL)
70         , _scale(NULL)
71         , _scaleUniform(NULL)
72         , _skewX(NULL)
73         , _skewY(NULL)
74         , _skewOrder(NULL)
75         , _transformAmount(NULL)
76         , _center(NULL)
77         , _interactive(NULL)
78         , _srcClipChanged(NULL)
79     {
80         // NON-GENERIC
81         if (isDirBlur) {
82             _dirBlurAmount = fetchDoubleParam(kParamTransform3x3DirBlurAmount);
83             _dirBlurCentered = fetchBooleanParam(kParamTransform3x3DirBlurCentered);
84             _dirBlurFading = fetchDoubleParam(kParamTransform3x3DirBlurFading);
85         }
86 
87         _translate = fetchDouble2DParam(kParamTransformTranslateOld);
88         _rotate = fetchDoubleParam(kParamTransformRotateOld);
89         _scale = fetchDouble2DParam(kParamTransformScaleOld);
90         _scaleUniform = fetchBooleanParam(kParamTransformScaleUniformOld);
91         _skewX = fetchDoubleParam(kParamTransformSkewXOld);
92         _skewY = fetchDoubleParam(kParamTransformSkewYOld);
93         _skewOrder = fetchChoiceParam(kParamTransformSkewOrderOld);
94         if (!isDirBlur) {
95             _transformAmount = fetchDoubleParam(kParamTransformAmount);
96         }
97         _center = fetchDouble2DParam(kParamTransformCenterOld);
98         _centerChanged = fetchBooleanParam(kParamTransformCenterChanged);
99         _interactive = fetchBooleanParam(kParamTransformInteractiveOld);
100         assert(_translate && _rotate && _scale && _scaleUniform && _skewX && _skewY && _skewOrder && _center && _interactive);
101         _srcClipChanged = fetchBooleanParam(kParamSrcClipChanged);
102         assert(_srcClipChanged);
103         // On Natron, hide the uniform parameter if it is false and not animated,
104         // since uniform scaling is easy through Natron's GUI.
105         // The parameter is kept for backward compatibility.
106         // Fixes https://github.com/MrKepzie/Natron/issues/1204
107         if ( getImageEffectHostDescription()->isNatron &&
108              !_scaleUniform->getValue() &&
109              ( _scaleUniform->getNumKeys() == 0) ) {
110             _scaleUniform->setIsSecretAndDisabled(true);
111         }
112     }
113 
114 private:
115     virtual bool isIdentity(double time) OVERRIDE FINAL;
116     virtual bool getInverseTransformCanonical(double time, int view, double amount, bool invert, Matrix3x3* invtransform) const OVERRIDE FINAL;
117 
118     void resetCenter(double time);
119 
120     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
121 
122     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
123     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
124 
125     // NON-GENERIC
126     Double2DParam* _translate;
127     DoubleParam* _rotate;
128     Double2DParam* _scale;
129     BooleanParam* _scaleUniform;
130     DoubleParam* _skewX;
131     DoubleParam* _skewY;
132     ChoiceParam* _skewOrder;
133     DoubleParam* _transformAmount;
134     Double2DParam* _center;
135     BooleanParam* _centerChanged;
136     BooleanParam* _interactive;
137     BooleanParam* _srcClipChanged; // set to true the first time the user connects src
138 };
139 
140 // overridden is identity
141 bool
isIdentity(double time)142 TransformPlugin::isIdentity(double time)
143 {
144     // NON-GENERIC
145     if (_paramsType != eTransform3x3ParamsTypeDirBlur) {
146         double amount = _transformAmount->getValueAtTime(time);
147         if (amount == 0.) {
148             return true;
149         }
150     }
151 
152     OfxPointD scaleParam = { 1., 1. };
153 
154     if (_scale) {
155         _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
156     }
157     bool scaleUniform = false;
158     if (_scaleUniform) {
159         _scaleUniform->getValueAtTime(time, scaleUniform);
160     }
161     OfxPointD scale = { 1., 1. };
162     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
163     OfxPointD translate = { 0., 0. };
164     if (_translate) {
165         _translate->getValueAtTime(time, translate.x, translate.y);
166     }
167     double rotate = 0.;
168     if (_rotate) {
169         _rotate->getValueAtTime(time, rotate);
170     }
171     double skewX = 0.;
172     if (_skewX) {
173         _skewX->getValueAtTime(time, skewX);
174     }
175     double skewY = 0.;
176     if (_skewY) {
177         _skewY->getValueAtTime(time, skewY);
178     }
179 
180     if ( (scale.x == 1.) && (scale.y == 1.) && (translate.x == 0.) && (translate.y == 0.) && (rotate == 0.) && (skewX == 0.) && (skewY == 0.) ) {
181         return true;
182     }
183 
184     return false;
185 }
186 
187 bool
getInverseTransformCanonical(double time,int,double amount,bool invert,Matrix3x3 * invtransform) const188 TransformPlugin::getInverseTransformCanonical(double time,
189                                               int /*view*/,
190                                               double amount,
191                                               bool invert,
192                                               Matrix3x3* invtransform) const
193 {
194     // NON-GENERIC
195     OfxPointD center = { 0., 0. };
196 
197     if (_center) {
198         _center->getValueAtTime(time, center.x, center.y);
199     }
200     OfxPointD translate = { 0., 0. };
201     if (_translate) {
202         _translate->getValueAtTime(time, translate.x, translate.y);
203     }
204     OfxPointD scaleParam = { 1., 1. };
205     if (_scale) {
206         _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
207     }
208     bool scaleUniform = false;
209     if (_scaleUniform) {
210         scaleUniform = _scaleUniform->getValueAtTime(time);
211     }
212     double rotate = 0.;
213     if (_rotate) {
214         rotate = _rotate->getValueAtTime(time);
215     }
216     double skewX = 0.;
217     if (_skewX) {
218         skewX = _skewX->getValueAtTime(time);
219     }
220     double skewY = 0.;
221     if (_skewY) {
222         skewY = _skewY->getValueAtTime(time);
223     }
224     int skewOrder = 0;
225     if (_skewOrder) {
226         skewOrder = _skewOrder->getValueAtTime(time);
227     }
228     if (_transformAmount) {
229         amount *= _transformAmount->getValueAtTime(time);
230     }
231 
232     OfxPointD scale = { 1., 1. };
233     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
234 
235     if (amount != 1.) {
236         translate.x *= amount;
237         translate.y *= amount;
238         if (scale.x <= 0. || amount <= 0.) {
239             // linear interpolation
240             scale.x = 1. + (scale.x - 1.) * amount;
241         } else {
242             // geometric interpolation
243             scale.x = std::pow(scale.x, amount);
244         }
245         if (scale.y <= 0 || amount <= 0.) {
246             // linear interpolation
247             scale.y = 1. + (scale.y - 1.) * amount;
248         } else {
249             // geometric interpolation
250             scale.y = std::pow(scale.y, amount);
251         }
252         rotate *= amount;
253         skewX *= amount;
254         skewY *= amount;
255     }
256 
257     double rot = ofxsToRadians(rotate);
258     if (!invert) {
259         *invtransform = ofxsMatInverseTransformCanonical(translate.x, translate.y, scale.x, scale.y, skewX, skewY, (bool)skewOrder, rot, center.x, center.y);
260     } else {
261         *invtransform = ofxsMatTransformCanonical(translate.x, translate.y, scale.x, scale.y, skewX, skewY, (bool)skewOrder, rot, center.x, center.y);
262     }
263 
264     return true;
265 } // TransformPlugin::getInverseTransformCanonical
266 
267 void
resetCenter(double time)268 TransformPlugin::resetCenter(double time)
269 {
270     if (!_srcClip || !_srcClip->isConnected()) {
271         return;
272     }
273     OfxRectD rod = _srcClip->getRegionOfDefinition(time);
274     if ( (rod.x1 <= kOfxFlagInfiniteMin) || (kOfxFlagInfiniteMax <= rod.x2) ||
275          ( rod.y1 <= kOfxFlagInfiniteMin) || ( kOfxFlagInfiniteMax <= rod.y2) ) {
276         return;
277     }
278     if ( Coords::rectIsEmpty(rod) ) {
279         // default to project window
280         OfxPointD offset = getProjectOffset();
281         OfxPointD size = getProjectSize();
282         rod.x1 = offset.x;
283         rod.x2 = offset.x + size.x;
284         rod.y1 = offset.y;
285         rod.y2 = offset.y + size.y;
286     }
287     double currentRotation = 0.;
288     if (_rotate) {
289         _rotate->getValueAtTime(time, currentRotation);
290     }
291     double rot = ofxsToRadians(currentRotation);
292     double skewX = 0.;
293     double skewY = 0.;
294     int skewOrder = 0;
295     if (_skewX) {
296         _skewX->getValueAtTime(time, skewX);
297     }
298     if (_skewY) {
299         _skewY->getValueAtTime(time, skewY);
300     }
301     if (_skewOrder) {
302         _skewOrder->getValueAtTime(time, skewOrder);
303     }
304 
305     OfxPointD scaleParam = { 1., 1. };
306     if (_scale) {
307         _scale->getValueAtTime(time, scaleParam.x, scaleParam.y);
308     }
309     bool scaleUniform = true;
310     if (_scaleUniform) {
311         _scaleUniform->getValueAtTime(time, scaleUniform);
312     }
313 
314     OfxPointD scale = { 1., 1. };
315     ofxsTransformGetScale(scaleParam, scaleUniform, &scale);
316 
317     OfxPointD translate = {0., 0. };
318     if (_translate) {
319         _translate->getValueAtTime(time, translate.x, translate.y);
320     }
321     OfxPointD center = {0., 0. };
322     if (_center) {
323         _center->getValueAtTime(time, center.x, center.y);
324     }
325 
326     Matrix3x3 Rinv = ( ofxsMatRotation(-rot) *
327                        ofxsMatSkewXY(skewX, skewY, skewOrder) *
328                        ofxsMatScale(scale.x, scale.y) );
329     OfxPointD newCenter;
330     newCenter.x = (rod.x1 + rod.x2) / 2;
331     newCenter.y = (rod.y1 + rod.y2) / 2;
332     beginEditBlock("resetCenter");
333     if (_center) {
334         _center->setValue(newCenter.x, newCenter.y);
335     }
336     if (_translate) {
337         double dxrot = newCenter.x - center.x;
338         double dyrot = newCenter.y - center.y;
339         Point3D dRot;
340         dRot.x = dxrot;
341         dRot.y = dyrot;
342         dRot.z = 1;
343         dRot = Rinv * dRot;
344         if (dRot.z != 0) {
345             dRot.x /= dRot.z;
346             dRot.y /= dRot.z;
347         }
348         double dx = dRot.x;
349         double dy = dRot.y;
350         OfxPointD newTranslate;
351         newTranslate.x = translate.x + dx - dxrot;
352         newTranslate.y = translate.y + dy - dyrot;
353         _translate->setValue(newTranslate.x, newTranslate.y);
354     }
355     endEditBlock();
356 } // TransformPlugin::resetCenter
357 
358 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)359 TransformPlugin::changedParam(const InstanceChangedArgs &args,
360                               const std::string &paramName)
361 {
362     if (paramName == kParamTransformResetCenterOld) {
363         resetCenter(args.time);
364         _centerChanged->setValue(false);
365     } else if ( (paramName == kParamTransformTranslateOld) ||
366                 ( paramName == kParamTransformRotateOld) ||
367                 ( paramName == kParamTransformScaleOld) ||
368                 ( paramName == kParamTransformScaleUniformOld) ||
369                 ( paramName == kParamTransformSkewXOld) ||
370                 ( paramName == kParamTransformSkewYOld) ||
371                 ( paramName == kParamTransformSkewOrderOld) ||
372                 ( paramName == kParamTransformCenterOld) ) {
373         if ( (paramName == kParamTransformCenterOld) &&
374              ( (args.reason == eChangeUserEdit) || (args.reason == eChangePluginEdit) ) ) {
375             _centerChanged->setValue(true);
376         }
377         changedTransform(args);
378     } else if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
379         _srcClipChanged->setValue(true);
380     } else {
381         Transform3x3Plugin::changedParam(args, paramName);
382     }
383 }
384 
385 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)386 TransformPlugin::changedClip(const InstanceChangedArgs &args,
387                              const std::string &clipName)
388 {
389     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
390          _srcClip && _srcClip->isConnected() &&
391          !_centerChanged->getValueAtTime(args.time) &&
392          ( args.reason == eChangeUserEdit) ) {
393         resetCenter(args.time);
394     }
395 }
396 
397 mDeclarePluginFactory(TransformPluginFactory, {ofxsThreadSuiteCheck();}, {});
398 static
399 void
TransformPluginDescribeInContext(ImageEffectDescriptor & desc,ContextEnum,PageParamDescriptor * page)400 TransformPluginDescribeInContext(ImageEffectDescriptor &desc,
401                                  ContextEnum /*context*/,
402                                  PageParamDescriptor *page)
403 {
404     // NON-GENERIC PARAMETERS
405     //
406     ofxsTransformDescribeParams(desc, page, NULL, /*isOpen=*/ true, /*oldParams=*/ true, /*hasAmount=*/ true, /*noTranslate=*/ false);
407 }
408 
409 void
describe(ImageEffectDescriptor & desc)410 TransformPluginFactory::describe(ImageEffectDescriptor &desc)
411 {
412     // basic labels
413     desc.setLabel(kPluginName);
414     desc.setPluginGrouping(kPluginGrouping);
415     desc.setPluginDescription(kPluginDescription);
416 
417     Transform3x3Describe(desc, false);
418 
419     desc.setOverlayInteractDescriptor(new TransformOverlayDescriptorOldParams);
420 }
421 
422 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)423 TransformPluginFactory::describeInContext(ImageEffectDescriptor &desc,
424                                           ContextEnum context)
425 {
426     // make some pages and to things in
427     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, false);
428 
429     TransformPluginDescribeInContext(desc, context, page);
430 
431     Transform3x3DescribeInContextEnd(desc, context, page, false, Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur);
432 
433     {
434         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSrcClipChanged);
435         param->setDefault(false);
436         param->setIsSecretAndDisabled(true);
437         param->setAnimates(false);
438         param->setEvaluateOnChange(false);
439         if (page) {
440             page->addChild(*param);
441         }
442     }
443 }
444 
445 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)446 TransformPluginFactory::createInstance(OfxImageEffectHandle handle,
447                                        ContextEnum /*context*/)
448 {
449     return new TransformPlugin(handle, false, false);
450 }
451 
452 mDeclarePluginFactory(TransformMaskedPluginFactory, {ofxsThreadSuiteCheck();}, {});
453 void
describe(ImageEffectDescriptor & desc)454 TransformMaskedPluginFactory::describe(ImageEffectDescriptor &desc)
455 {
456     // basic labels
457     desc.setLabel(kPluginMaskedName);
458     desc.setPluginGrouping(kPluginGrouping);
459     desc.setPluginDescription(kPluginMaskedDescription);
460 
461     Transform3x3Describe(desc, true);
462 
463     desc.setOverlayInteractDescriptor(new TransformOverlayDescriptorOldParams);
464 }
465 
466 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)467 TransformMaskedPluginFactory::describeInContext(ImageEffectDescriptor &desc,
468                                                 ContextEnum context)
469 {
470     // make some pages and to things in
471     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, true);
472 
473     TransformPluginDescribeInContext(desc, context, page);
474 
475     Transform3x3DescribeInContextEnd(desc, context, page, true, Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur);
476 
477     {
478         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSrcClipChanged);
479         param->setDefault(false);
480         param->setIsSecretAndDisabled(true);
481         param->setAnimates(false);
482         param->setEvaluateOnChange(false);
483         if (page) {
484             page->addChild(*param);
485         }
486     }
487 }
488 
489 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)490 TransformMaskedPluginFactory::createInstance(OfxImageEffectHandle handle,
491                                              ContextEnum /*context*/)
492 {
493     return new TransformPlugin(handle, true, false);
494 }
495 
496 mDeclarePluginFactory(DirBlurPluginFactory, {ofxsThreadSuiteCheck();}, {});
497 void
describe(ImageEffectDescriptor & desc)498 DirBlurPluginFactory::describe(ImageEffectDescriptor &desc)
499 {
500     // basic labels
501     desc.setLabel(kPluginDirBlurName);
502     desc.setPluginGrouping(kPluginDirBlurGrouping);
503     desc.setPluginDescription(kPluginDirBlurDescription);
504 
505     Transform3x3Describe(desc, true);
506 
507     desc.setOverlayInteractDescriptor(new TransformOverlayDescriptorOldParams);
508 }
509 
510 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)511 DirBlurPluginFactory::describeInContext(ImageEffectDescriptor &desc,
512                                         ContextEnum context)
513 {
514     // make some pages and to things in
515     PageParamDescriptor *page = Transform3x3DescribeInContextBegin(desc, context, true);
516 
517     TransformPluginDescribeInContext(desc, context, page);
518 
519     Transform3x3DescribeInContextEnd(desc, context, page, true, Transform3x3Plugin::eTransform3x3ParamsTypeDirBlur);
520 
521     {
522         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSrcClipChanged);
523         param->setDefault(false);
524         param->setIsSecretAndDisabled(true);
525         param->setAnimates(false);
526         param->setEvaluateOnChange(false);
527         if (page) {
528             page->addChild(*param);
529         }
530     }
531 }
532 
533 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)534 DirBlurPluginFactory::createInstance(OfxImageEffectHandle handle,
535                                      ContextEnum /*context*/)
536 {
537     return new TransformPlugin(handle, true, true);
538 }
539 
540 static TransformPluginFactory p1(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
541 static TransformMaskedPluginFactory p2(kPluginMaskedIdentifier, kPluginVersionMajor, kPluginVersionMinor);
542 static DirBlurPluginFactory p3(kPluginDirBlurIdentifier, kPluginVersionMajor, kPluginVersionMinor);
543 mRegisterPluginFactoryInstance(p1)
544 mRegisterPluginFactoryInstance(p2)
545 mRegisterPluginFactoryInstance(p3)
546 
547 OFXS_NAMESPACE_ANONYMOUS_EXIT
548