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