1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * This file is part of openfx-supportext <https://github.com/devernay/openfx-supportext>,
4  * Copyright (C) 2013-2018 INRIA
5  *
6  * openfx-supportext is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * openfx-supportext is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with openfx-supportext.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
18  * ***** END LICENSE BLOCK ***** */
19 
20 /*
21  * OFX Transform3x3 plugin: a base plugin for 2D homographic transform,
22  * represented by a 3x3 matrix.
23  */
24 
25 /*
26    Although the indications from nuke/fnOfxExtensions.h were followed, and the
27    kFnOfxImageEffectActionGetTransform action was implemented in the Support
28    library, that action is never called by the Nuke host.
29 
30    The extension was implemented as specified in Natron and in the Support library.
31 
32    @see gHostDescription.canTransform, ImageEffectDescriptor::setCanTransform(),
33    and ImageEffect::getTransform().
34 
35    There is also an open question about how the last plugin in a transform chain
36    may get the concatenated transform from upstream, the untransformed source image,
37    concatenate its own transform and apply the resulting transform in its render
38    action.
39 
40    Our solution is to have kFnOfxImageEffectCanTransform set on source clips for which
41    a transform can be attached to fetched images.
42    @see ClipDescriptor::setCanTransform().
43 
44    In this case, images fetched from the host may have a kFnOfxPropMatrix2D attached,
45    which must be combined with the transformation applied by the effect (which
46    may be any deformation function, not only a homography).
47    @see ImageBase::getTransform() and ImageBase::getTransformIsIdentity
48  */
49 // Uncomment the following to enable the experimental host transform code.
50 #define ENABLE_HOST_TRANSFORM
51 
52 #include <cfloat> // DBL_MAX
53 #include <memory>
54 #include <algorithm>
55 
56 #include "ofxsTransform3x3.h"
57 #include "ofxsTransform3x3Processor.h"
58 #include "ofxsCoords.h"
59 #include "ofxsShutter.h"
60 
61 
62 #ifndef ENABLE_HOST_TRANSFORM
63 #undef OFX_EXTENSIONS_NUKE // host transform is the only nuke extension used
64 #endif
65 
66 #ifdef OFX_EXTENSIONS_NUKE
67 #include "nuke/fnOfxExtensions.h"
68 #endif
69 
70 #define kSupportsTiles 1
71 #define kSupportsMultiResolution 1
72 #define kSupportsRenderScale 1
73 #define kRenderThreadSafety eRenderFullySafe
74 
75 using namespace OFX;
76 
77 using std::string;
78 
79 // It would be nice to be able to cache the set of transforms (with motion blur) used to compute the
80 // current frame between two renders.
81 // Unfortunately, we cannot rely on the host sending changedParam() when the animation changes
82 // (Nuke doesn't call the action when a linked animation is changed),
83 // nor on dst->getUniqueIdentifier (which is "ffffffffffffffff" on Nuke)
84 
85 #define kTransform3x3MotionBlurCount 1000 // number of transforms used in the motion
86 
87 namespace OFX {
Transform3x3Plugin(OfxImageEffectHandle handle,bool masked,Transform3x3ParamsTypeEnum paramsType)88 Transform3x3Plugin::Transform3x3Plugin(OfxImageEffectHandle handle,
89                                        bool masked,
90                                        Transform3x3ParamsTypeEnum paramsType)
91     : ImageEffect(handle)
92     , _dstClip(NULL)
93     , _srcClip(NULL)
94     , _maskClip(NULL)
95     , _paramsType(paramsType)
96     , _invert(NULL)
97     , _filter(NULL)
98     , _clamp(NULL)
99     , _blackOutside(NULL)
100     , _motionblur(NULL)
101     , _dirBlurAmount(NULL)
102     , _dirBlurCentered(NULL)
103     , _dirBlurFading(NULL)
104     , _directionalBlur(NULL)
105     , _shutter(NULL)
106     , _shutteroffset(NULL)
107     , _shuttercustomoffset(NULL)
108     , _masked(masked)
109     , _mix(NULL)
110     , _maskApply(NULL)
111     , _maskInvert(NULL)
112 {
113     _dstClip = fetchClip(kOfxImageEffectOutputClipName);
114     assert(1 <= _dstClip->getPixelComponentCount() && _dstClip->getPixelComponentCount() <= 4);
115     _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
116     assert( !_srcClip || !_srcClip->isConnected() || (1 <= _srcClip->getPixelComponentCount() && _srcClip->getPixelComponentCount() <= 4) );
117     // name of mask clip depends on the context
118     if (masked) {
119         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
120         assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
121     }
122 
123     if ( paramExists(kParamTransform3x3Invert) ) {
124         // Transform3x3-GENERIC
125         _invert = fetchBooleanParam(kParamTransform3x3Invert);
126         // GENERIC
127         _filter = fetchChoiceParam(kParamFilterType);
128         _clamp = fetchBooleanParam(kParamFilterClamp);
129         _blackOutside = fetchBooleanParam(kParamFilterBlackOutside);
130         assert(_invert && _filter && _clamp && _blackOutside);
131         if ( paramExists(kParamTransform3x3MotionBlur) ) {
132             _motionblur = fetchDoubleParam(kParamTransform3x3MotionBlur); // GodRays may not have have _motionblur
133             assert(_motionblur);
134         }
135         if (paramsType == eTransform3x3ParamsTypeMotionBlur) {
136             _directionalBlur = fetchBooleanParam(kParamTransform3x3DirectionalBlur);
137             _shutter = fetchDoubleParam(kParamShutter);
138             _shutteroffset = fetchChoiceParam(kParamShutterOffset);
139             _shuttercustomoffset = fetchDoubleParam(kParamShutterCustomOffset);
140             assert(_directionalBlur && _shutter && _shutteroffset && _shuttercustomoffset);
141         }
142         if (masked) {
143             _mix = fetchDoubleParam(kParamMix);
144             _maskInvert = fetchBooleanParam(kParamMaskInvert);
145             assert(_mix && _maskInvert);
146         }
147 
148         if (paramsType == eTransform3x3ParamsTypeMotionBlur) {
149             bool directionalBlur;
150             _directionalBlur->getValue(directionalBlur);
151             _shutter->setEnabled(!directionalBlur);
152             _shutteroffset->setEnabled(!directionalBlur);
153             _shuttercustomoffset->setEnabled(!directionalBlur);
154         }
155     }
156 }
157 
~Transform3x3Plugin()158 Transform3x3Plugin::~Transform3x3Plugin()
159 {
160 }
161 
162 ////////////////////////////////////////////////////////////////////////////////
163 /** @brief render for the filter */
164 
165 ////////////////////////////////////////////////////////////////////////////////
166 // basic plugin render function, just a skelington to instantiate templates from
167 
168 /* set up and run a processor */
169 void
setupAndProcess(Transform3x3ProcessorBase & processor,const RenderArguments & args)170 Transform3x3Plugin::setupAndProcess(Transform3x3ProcessorBase &processor,
171                                     const RenderArguments &args)
172 {
173     assert(!_invert || _motionblur); // this method should be overridden in GodRays
174     const double time = args.time;
175     auto_ptr<Image> dst( _dstClip->fetchImage(time) );
176 
177     if ( !dst.get() ) {
178         throwSuiteStatusException(kOfxStatFailed);
179 
180         return;
181     }
182     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
183     PixelComponentEnum dstComponents  = dst->getPixelComponents();
184     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
185          ( dstComponents != _dstClip->getPixelComponents() ) ) {
186         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
187         throwSuiteStatusException(kOfxStatFailed);
188 
189         return;
190     }
191     if ( (dst->getRenderScale().x != args.renderScale.x) ||
192          ( dst->getRenderScale().y != args.renderScale.y) ||
193          ( ( (dst->getField() != eFieldNone) /* for DaVinci Resolve */ && (dst->getField() != args.fieldToRender) ) ) ) {
194         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
195         throwSuiteStatusException(kOfxStatFailed);
196 
197         return;
198     }
199     auto_ptr<const Image> src( ( _srcClip && _srcClip->isConnected() ) ?
200                                     _srcClip->fetchImage(args.time) : 0 );
201     size_t invtransformsizealloc = 0;
202     size_t invtransformsize = 0;
203     std::vector<Matrix3x3> invtransform;
204     std::vector<double> invtransformalpha;
205     double motionblur = 0.;
206     bool directionalBlur = (_paramsType != eTransform3x3ParamsTypeNone);
207     double amountFrom = 0.;
208     double amountTo = 1.;
209     if (_dirBlurAmount) {
210         _dirBlurAmount->getValueAtTime(time, amountTo);
211     }
212     if (_dirBlurCentered) {
213         bool centered;
214         _dirBlurCentered->getValueAtTime(time, centered);
215         if (centered) {
216             amountFrom = -amountTo;
217         }
218     }
219     bool blackOutside = false;
220     double mix = 1.;
221 
222     if ( !src.get() ) {
223         // no source image, use a dummy transform
224         invtransformsizealloc = 1;
225         invtransform.resize(invtransformsizealloc);
226         invtransformsize = 1;
227         invtransform[0](0,0) = 0.;
228         invtransform[0](0,1) = 0.;
229         invtransform[0](0,2) = 0.;
230         invtransform[0](1,0) = 0.;
231         invtransform[0](1,1) = 0.;
232         invtransform[0](1,2) = 0.;
233         invtransform[0](2,0) = 0.;
234         invtransform[0](2,1) = 0.;
235         invtransform[0](2,2) = 1.;
236     } else {
237         BitDepthEnum dstBitDepth       = dst->getPixelDepth();
238         PixelComponentEnum dstComponents  = dst->getPixelComponents();
239         BitDepthEnum srcBitDepth      = src->getPixelDepth();
240         PixelComponentEnum srcComponents = src->getPixelComponents();
241         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
242             throwSuiteStatusException(kOfxStatFailed);
243 
244             return;
245         }
246 
247         bool invert = false;
248         if (_invert) {
249             _invert->getValueAtTime(time, invert);
250         }
251 
252         if (_blackOutside) {
253             _blackOutside->getValueAtTime(time, blackOutside);
254         }
255         if (_masked && _mix) {
256             _mix->getValueAtTime(time, mix);
257         }
258         if (_motionblur) {
259             _motionblur->getValueAtTime(time, motionblur);
260         }
261         if (_directionalBlur) {
262             _directionalBlur->getValueAtTime(time, directionalBlur);
263         }
264         double shutter = 0.;
265         if (!directionalBlur) {
266             if (_shutter) {
267                 _shutter->getValueAtTime(time, shutter);
268             }
269         }
270         const bool fielded = args.fieldToRender == eFieldLower || args.fieldToRender == eFieldUpper;
271         const double srcpixelAspectRatio = src->getPixelAspectRatio();
272         const double dstpixelAspectRatio = _dstClip->getPixelAspectRatio();
273         int view = 0;
274 #if defined(OFX_EXTENSIONS_VEGAS) || defined(OFX_EXTENSIONS_NUKE)
275         view = args.renderView;
276 #endif
277         if ( (shutter != 0.) && (motionblur != 0.) ) {
278             invtransformsizealloc = kTransform3x3MotionBlurCount;
279             invtransform.resize(invtransformsizealloc);
280             assert(_shutteroffset);
281             ShutterOffsetEnum shutteroffset = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
282             double shuttercustomoffset;
283             assert(_shuttercustomoffset);
284             _shuttercustomoffset->getValueAtTime(time, shuttercustomoffset);
285 
286             invtransformsize = getInverseTransforms(time, view, args.renderScale, fielded, srcpixelAspectRatio, dstpixelAspectRatio, invert, shutter, shutteroffset, shuttercustomoffset, &invtransform.front(), invtransformsizealloc);
287         } else if (directionalBlur) {
288             invtransformsizealloc = kTransform3x3MotionBlurCount;
289             invtransform.resize(invtransformsizealloc);
290             invtransformalpha.resize(invtransformsizealloc);
291             invtransformsize = getInverseTransformsBlur(time, view, args.renderScale, fielded, srcpixelAspectRatio, dstpixelAspectRatio, invert, amountFrom, amountTo, &invtransform.front(), &invtransformalpha.front(), invtransformsizealloc);
292             // normalize alpha, and apply gamma
293             double fading = 0.;
294             if (_dirBlurFading) {
295                 _dirBlurFading->getValueAtTime(time, fading);
296             }
297             if (fading <= 0.) {
298                 std::fill(invtransformalpha.begin(), invtransformalpha.end(), 1.);
299             } else {
300                 for (size_t i = 0; i < invtransformalpha.size(); ++i) {
301                     invtransformalpha[i] = std::pow(1. - std::abs(invtransformalpha[i]) / amountTo, fading);
302                 }
303             }
304         } else {
305             invtransformsizealloc = 1;
306             invtransform.resize(invtransformsizealloc);
307             invtransformsize = 1;
308             bool success = getInverseTransformCanonical(time, view, 1., invert, &invtransform[0]); // virtual function
309             if (!success) {
310                 invtransform[0](0,0) = 0.;
311                 invtransform[0](0,1) = 0.;
312                 invtransform[0](0,2) = 0.;
313                 invtransform[0](1,0) = 0.;
314                 invtransform[0](1,1) = 0.;
315                 invtransform[0](1,2) = 0.;
316                 invtransform[0](2,0) = 0.;
317                 invtransform[0](2,1) = 0.;
318                 invtransform[0](2,2) = 1.;
319             } else {
320                 Matrix3x3 canonicalToPixel = ofxsMatCanonicalToPixel(srcpixelAspectRatio, args.renderScale.x,
321                                                                      args.renderScale.y, fielded);
322                 Matrix3x3 pixelToCanonical = ofxsMatPixelToCanonical(dstpixelAspectRatio,  args.renderScale.x,
323                                                                      args.renderScale.y, fielded);
324                 invtransform[0] = canonicalToPixel * invtransform[0] * pixelToCanonical;
325             }
326         }
327         if (invtransformsize == 1) {
328             motionblur  = 0.;
329         }
330 #ifdef OFX_EXTENSIONS_NUKE
331         // compose with the input transform
332         if ( !src->getTransformIsIdentity() ) {
333             double srcTransform[9]; // transform to apply to the source image, in pixel coordinates, from source to destination
334             src->getTransform(srcTransform);
335             Matrix3x3 srcTransformMat;
336             srcTransformMat(0,0) = srcTransform[0];
337             srcTransformMat(0,1) = srcTransform[1];
338             srcTransformMat(0,2) = srcTransform[2];
339             srcTransformMat(1,0) = srcTransform[3];
340             srcTransformMat(1,1) = srcTransform[4];
341             srcTransformMat(1,2) = srcTransform[5];
342             srcTransformMat(2,0) = srcTransform[6];
343             srcTransformMat(2,1) = srcTransform[7];
344             srcTransformMat(2,2) = srcTransform[8];
345             // invert it
346             Matrix3x3 srcTransformInverse;
347             if ( srcTransformMat.inverse(&srcTransformInverse) ) {
348                 for (size_t i = 0; i < invtransformsize; ++i) {
349                     invtransform[i] = srcTransformInverse * invtransform[i];
350                 }
351             }
352         }
353 #endif
354     }
355 
356     // auto ptr for the mask.
357     bool doMasking = ( _masked && ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
358     auto_ptr<const Image> mask(doMasking ? _maskClip->fetchImage(args.time) : 0);
359     if (doMasking) {
360         bool maskInvert = false;
361         if (_maskInvert) {
362             _maskInvert->getValueAtTime(time, maskInvert);
363         }
364         // say we are masking
365         processor.doMasking(true);
366 
367         // Set it in the processor
368         processor.setMaskImg(mask.get(), maskInvert);
369     }
370 
371     // set the images
372     processor.setDstImg( dst.get() );
373     processor.setSrcImg( src.get() );
374 
375     // set the render window
376     processor.setRenderWindow(args.renderWindow);
377     assert(invtransform.size() && invtransformsize);
378     processor.setValues(&invtransform.front(),
379                         invtransformalpha.empty() ? 0 : &invtransformalpha.front(),
380                         invtransformsize,
381                         blackOutside,
382                         motionblur,
383                         mix);
384 
385     // Call the base class process member, this will call the derived templated process code
386     processor.process();
387 } // setupAndProcess
388 
389 // Compute the bounding box of the transform of four points representing a quadrilateral.
390 // - If a point has a negative Z, it is not taken into account (thus, if all points have a negative Z, the region is empty)
391 // - If two consecutive points have a chage of Z-sign, find a point close to the point with the negative z with a Z equal to 0.
392 // This gives a direction which must be fully included in the rod, and thus a full quadrant must be included, ie an infinite
393 // value for one of the x bounds and an infinite value for one of the y bounds.
394 static void
ofxsTransformRegionFromPoints(const Point3D p[4],OfxRectD & rod)395 ofxsTransformRegionFromPoints(const Point3D p[4],
396                               OfxRectD &rod)
397 {
398     // extract the x/y bounds
399     double x1, y1, x2, y2;
400     bool empty = true;
401 
402     int i = 0, j = 1;
403     for (; i < 4; ++i, j = (j + 1) % 4) {
404         if (p[i].z > 0) {
405             double x = p[i].x / p[i].z;
406             double y = p[i].y / p[i].z;
407 
408             if (empty) {
409                 empty = false;
410                 x1 = x2 = x;
411                 y1 = y2 = y;
412             } else {
413                 if (x < x1) {
414                     x1 = x;
415                 } else if (x > x2) {
416                     x2 = x;
417                 }
418                 if (y < y1) {
419                     y1 = y;
420                 } else if (y > y2) {
421                     y2 = y;
422                 }
423             }
424         } else if (p[j].z > 0) {
425             // add next point if set is empty
426             if (empty) {
427                 empty = false;
428                 x1 = x2 = p[j].x / p[j].z;
429                 y1 = y2 = p[j].y / p[j].z;
430             }
431         }
432         if ( (p[i].z > 0 && p[j].z <= 0) ||
433              (p[i].z <= 0 && p[j].z > 0) ) {
434             // find position of the z=0 point on the line
435             double a = -p[i].z / (p[j].z - p[i].z);
436             // compute infinite direction
437             double dx = p[i].x + a * (p[j].x - p[i].x);
438             double dy = p[i].y + a * (p[j].y - p[i].y);
439             // add next point if set is empty
440             if (empty) {
441                 empty = false;
442                 x1 = x2 = p[j].x / p[j].z;
443                 y1 = y2 = p[j].y / p[j].z;
444             }
445             // add the full quadrant
446             if (dx < 0) {
447                 x1 = kOfxFlagInfiniteMin;
448             } else if (dx > 0) {
449                 x2 = kOfxFlagInfiniteMax;
450             }
451             if (dy < 0) {
452                 y1 = kOfxFlagInfiniteMin;
453             } else if (dy > 0) {
454                 y2 = kOfxFlagInfiniteMax;
455             }
456         }
457     }
458 
459     if (empty) {
460         rod.x1 = rod.x2 = rod.y1 = rod.y2 = 0;
461     } else {
462         rod.x1 = x1;
463         rod.x2 = x2;
464         rod.y1 = y1;
465         rod.y2 = y2;
466     }
467     assert(rod.x1 <= rod.x2 && rod.y1 <= rod.y2);
468 } // ofxsTransformRegionFromPoints
469 
470 // compute the bounding box of the transform of a rectangle
471 static void
ofxsTransformRegionFromRoD(const OfxRectD & srcRoD,const Matrix3x3 & transform,Point3D p[4],OfxRectD & rod)472 ofxsTransformRegionFromRoD(const OfxRectD &srcRoD,
473                            const Matrix3x3 &transform,
474                            Point3D p[4],
475                            OfxRectD &rod)
476 {
477     /// now transform the 4 corners of the source clip to the output image
478     p[0] = transform * Point3D(srcRoD.x1, srcRoD.y1, 1);
479     p[1] = transform * Point3D(srcRoD.x1, srcRoD.y2, 1);
480     p[2] = transform * Point3D(srcRoD.x2, srcRoD.y2, 1);
481     p[3] = transform * Point3D(srcRoD.x2, srcRoD.y1, 1);
482 
483     ofxsTransformRegionFromPoints(p, rod);
484 }
485 
486 void
transformRegion(const OfxRectD & rectFrom,double time,int view,bool invert,double motionblur,bool directionalBlur,double amountFrom,double amountTo,double shutter,ShutterOffsetEnum shutteroffset,double shuttercustomoffset,bool isIdentity,OfxRectD * rectTo)487 Transform3x3Plugin::transformRegion(const OfxRectD &rectFrom,
488                                     double time,
489                                     int view,
490                                     bool invert,
491                                     double motionblur,
492                                     bool directionalBlur,
493                                     double amountFrom,
494                                     double amountTo,
495                                     double shutter,
496                                     ShutterOffsetEnum shutteroffset,
497                                     double shuttercustomoffset,
498                                     bool isIdentity,
499                                     OfxRectD *rectTo)
500 {
501     // Algorithm:
502     // - Compute positions of the four corners at start and end of shutter, and every multiple of 0.25 within this range.
503     // - Update the bounding box from these positions.
504     // - At the end, expand the bounding box by the maximum L-infinity distance between consecutive positions of each corner.
505 
506     OfxRangeD range;
507     bool hasmotionblur = ( (shutter != 0. || directionalBlur) && motionblur != 0. );
508 
509     if (hasmotionblur && !directionalBlur) {
510         shutterRange(time, shutter, shutteroffset, shuttercustomoffset, &range);
511     } else {
512         ///if is identity return the input rod instead of transforming
513         if (isIdentity) {
514             *rectTo = rectFrom;
515 
516             return;
517         }
518         range.min = range.max = time;
519     }
520 
521     // initialize with a super-empty RoD (note that max and min are reversed)
522     rectTo->x1 = kOfxFlagInfiniteMax;
523     rectTo->x2 = kOfxFlagInfiniteMin;
524     rectTo->y1 = kOfxFlagInfiniteMax;
525     rectTo->y2 = kOfxFlagInfiniteMin;
526     double t = range.min;
527     bool first = true;
528     bool last = !hasmotionblur; // ony one iteration if there is no motion blur
529     bool finished = false;
530     double expand = 0.;
531     double amount = 1.;
532     int dirBlurIter = 0;
533     Point3D p_prev[4];
534     while (!finished) {
535         // compute transformed positions
536         OfxRectD thisRoD;
537         Matrix3x3 transform;
538         bool success = getInverseTransformCanonical(t, view, amountFrom + amount * (amountTo - amountFrom), invert, &transform); // RoD is computed using the *DIRECT* transform, which is why we use !invert
539         if (!success) {
540             // return infinite region
541             rectTo->x1 = kOfxFlagInfiniteMin;
542             rectTo->x2 = kOfxFlagInfiniteMax;
543             rectTo->y1 = kOfxFlagInfiniteMin;
544             rectTo->y2 = kOfxFlagInfiniteMax;
545 
546             return;
547         }
548         Point3D p[4];
549         ofxsTransformRegionFromRoD(rectFrom, transform, p, thisRoD);
550 
551         // update min/max
552         Coords::rectBoundingBox(*rectTo, thisRoD, rectTo);
553 
554         // if first iteration, continue
555         if (first) {
556             first = false;
557         } else {
558             // compute the L-infinity distance between consecutive tested points
559             expand = (std::max)( expand, std::fabs(p_prev[0].x - p[0].x) );
560             expand = (std::max)( expand, std::fabs(p_prev[0].y - p[0].y) );
561             expand = (std::max)( expand, std::fabs(p_prev[1].x - p[1].x) );
562             expand = (std::max)( expand, std::fabs(p_prev[1].y - p[1].y) );
563             expand = (std::max)( expand, std::fabs(p_prev[2].x - p[2].x) );
564             expand = (std::max)( expand, std::fabs(p_prev[2].y - p[2].y) );
565             expand = (std::max)( expand, std::fabs(p_prev[3].x - p[3].x) );
566             expand = (std::max)( expand, std::fabs(p_prev[3].y - p[3].y) );
567         }
568 
569         if (last) {
570             finished = true;
571         } else {
572             // prepare for next iteration
573             p_prev[0] = p[0];
574             p_prev[1] = p[1];
575             p_prev[2] = p[2];
576             p_prev[3] = p[3];
577             if (directionalBlur) {
578                 const int dirBlurIterMax = 8;
579                 ++dirBlurIter;
580                 amount = 1. - dirBlurIter / (double)dirBlurIterMax;
581                 last = dirBlurIter == dirBlurIterMax;
582             } else {
583                 t = std::floor(t * 4 + 1) / 4; // next quarter-frame
584                 if (t >= range.max) {
585                     // last iteration should be done with range.max
586                     t = range.max;
587                     last = true;
588                 }
589             }
590         }
591     }
592     // expand to take into account errors due to motion blur
593     if (rectTo->x1 > kOfxFlagInfiniteMin) {
594         rectTo->x1 -= expand;
595     }
596     if (rectTo->x2 < kOfxFlagInfiniteMax) {
597         rectTo->x2 += expand;
598     }
599     if (rectTo->y1 > kOfxFlagInfiniteMin) {
600         rectTo->y1 -= expand;
601     }
602     if (rectTo->y2 < kOfxFlagInfiniteMax) {
603         rectTo->y2 += expand;
604     }
605 } // transformRegion
606 
607 // override the rod call
608 // Transform3x3-GENERIC
609 // the RoD should at least contain the region of definition of the source clip,
610 // which will be filled with black or by continuity.
611 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD & rod)612 Transform3x3Plugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
613                                           OfxRectD &rod)
614 {
615     if (!_srcClip || !_srcClip->isConnected()) {
616         return false;
617     }
618     const double time = args.time;
619     const OfxRectD& srcRoD = _srcClip->getRegionOfDefinition(time);
620 
621     if ( Coords::rectIsInfinite(srcRoD) ) {
622         // return an infinite RoD
623         rod.x1 = kOfxFlagInfiniteMin;
624         rod.x2 = kOfxFlagInfiniteMax;
625         rod.y1 = kOfxFlagInfiniteMin;
626         rod.y2 = kOfxFlagInfiniteMax;
627 
628         return true;
629     }
630 
631     if ( Coords::rectIsEmpty(srcRoD) ) {
632         // return an empty RoD
633         rod.x1 = 0.;
634         rod.x2 = 0.;
635         rod.y1 = 0.;
636         rod.y2 = 0.;
637 
638         return true;
639     }
640 
641     double mix = 1.;
642     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
643     if (doMasking && _mix) {
644         _mix->getValueAtTime(time, mix);
645         if (mix == 0.) {
646             // identity transform
647             rod = srcRoD;
648 
649             return true;
650         }
651     }
652 
653     bool invert = false;
654     if (_invert) {
655         _invert->getValueAtTime(time, invert);
656     }
657     invert = !invert; // only for getRegionOfDefinition
658     double motionblur = 1.; // default for GodRays
659     if (_motionblur) {
660         _motionblur->getValueAtTime(time, motionblur);
661     }
662     bool directionalBlur = (_paramsType != eTransform3x3ParamsTypeNone);
663     double amountFrom = 0.;
664     double amountTo = 1.;
665     if (_dirBlurAmount) {
666         _dirBlurAmount->getValueAtTime(time, amountTo);
667     }
668     if (_dirBlurCentered) {
669         bool centered;
670         _dirBlurCentered->getValueAtTime(time, centered);
671         if (centered) {
672             amountFrom = -amountTo;
673         }
674     }
675     double shutter = 0.;
676     ShutterOffsetEnum shutteroffset = eShutterOffsetCentered;
677     double shuttercustomoffset = 0.;
678     if (_directionalBlur) {
679         directionalBlur = _directionalBlur->getValueAtTime(time);
680         shutter = _shutter->getValueAtTime(time);
681         shutteroffset = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
682         shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
683     }
684 
685     bool identity = isIdentity(args.time);
686 
687     // set rod from srcRoD
688 #ifdef OFX_EXTENSIONS_NUKE
689     const int view = args.view;
690 #else
691     const int view = 0;
692 #endif
693 
694     transformRegion(srcRoD, time, view, invert, motionblur, directionalBlur, amountFrom, amountTo, shutter, shutteroffset, shuttercustomoffset, identity, &rod);
695 
696     // If identity do not expand for black outside, otherwise we would never be able to have identity.
697     // We want the RoD to be the same as the src RoD when we are identity.
698     if (!identity) {
699         bool blackOutside = false;
700         if (_blackOutside) {
701             _blackOutside->getValueAtTime(time, blackOutside);
702         }
703 
704         ofxsFilterExpandRoD(this, _dstClip->getPixelAspectRatio(), args.renderScale, blackOutside, &rod);
705     }
706 
707     if ( doMasking && ( (mix != 1.) || _maskClip->isConnected() ) ) {
708         // for masking or mixing, we also need the source image.
709         // compute the union of both RODs
710         Coords::rectBoundingBox(rod, srcRoD, &rod);
711     }
712 
713     // say we set it
714     return true;
715 } // getRegionOfDefinition
716 
717 // override the roi call
718 // Transform3x3-GENERIC
719 // Required if the plugin requires a region from the inputs which is different from the rendered region of the output.
720 // (this is the case for transforms)
721 // It may be difficult to implement for complicated transforms:
722 // consequently, these transforms cannot support tiles.
723 void
getRegionsOfInterest(const RegionsOfInterestArguments & args,RegionOfInterestSetter & rois)724 Transform3x3Plugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
725                                          RegionOfInterestSetter &rois)
726 {
727     if (!_srcClip || !_srcClip->isConnected()) {
728         return;
729     }
730     const double time = args.time;
731     const OfxRectD roi = args.regionOfInterest;
732     OfxRectD srcRoI;
733     double mix = 1.;
734     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
735     if (doMasking) {
736         _mix->getValueAtTime(time, mix);
737         if (mix == 0.) {
738             // identity transform
739             srcRoI = roi;
740             rois.setRegionOfInterest(*_srcClip, srcRoI);
741 
742             return;
743         }
744     }
745 
746     bool invert = false;
747     if (_invert) {
748         _invert->getValueAtTime(time, invert);
749     }
750     //invert = !invert; // only for getRegionOfDefinition
751     double motionblur = 1; // default for GodRays
752     if (_motionblur) {
753         _motionblur->getValueAtTime(time, motionblur);
754     }
755     bool directionalBlur = (_paramsType != eTransform3x3ParamsTypeNone);
756     double amountFrom = 0.;
757     double amountTo = 1.;
758     if (_dirBlurAmount) {
759         _dirBlurAmount->getValueAtTime(time, amountTo);
760     }
761     if (_dirBlurCentered) {
762         bool centered;
763         _dirBlurCentered->getValueAtTime(time, centered);
764         if (centered) {
765             amountFrom = -amountTo;
766         }
767     }
768     double shutter = 0.;
769     ShutterOffsetEnum shutteroffset = eShutterOffsetCentered;
770     double shuttercustomoffset = 0.;
771     if (_directionalBlur) {
772         directionalBlur = _directionalBlur->getValueAtTime(time);
773         shutter = _shutter->getValueAtTime(time);
774         shutteroffset = (ShutterOffsetEnum)_shutteroffset->getValueAtTime(time);
775         shuttercustomoffset = _shuttercustomoffset->getValueAtTime(time);
776     }
777 #ifdef OFX_EXTENSIONS_NUKE
778     const int view = args.view;
779 #else
780     const int view = 0;
781 #endif
782 
783     // set srcRoI from roi
784     transformRegion(roi, time, view, invert, motionblur, directionalBlur, amountFrom, amountTo, shutter, shutteroffset, shuttercustomoffset, isIdentity(time), &srcRoI);
785 
786     FilterEnum filter = eFilterCubic;
787     if (_filter) {
788         filter = (FilterEnum)_filter->getValueAtTime(time);
789     }
790 
791     assert(srcRoI.x1 <= srcRoI.x2 && srcRoI.y1 <= srcRoI.y2);
792 
793     ofxsFilterExpandRoI(roi, _srcClip->getPixelAspectRatio(), args.renderScale, filter, doMasking, mix, &srcRoI);
794 
795     if ( Coords::rectIsInfinite(srcRoI) ) {
796         // RoI cannot be infinite.
797         // This is not a mathematically correct solution, but better than nothing: set to the project size
798         OfxPointD size = getProjectSize();
799         OfxPointD offset = getProjectOffset();
800 
801         if (srcRoI.x1 <= kOfxFlagInfiniteMin) {
802             srcRoI.x1 = offset.x;
803         }
804         if (srcRoI.x2 >= kOfxFlagInfiniteMax) {
805             srcRoI.x2 = offset.x + size.x;
806         }
807         if (srcRoI.y1 <= kOfxFlagInfiniteMin) {
808             srcRoI.y1 = offset.y;
809         }
810         if (srcRoI.y2 >= kOfxFlagInfiniteMax) {
811             srcRoI.y2 = offset.y + size.y;
812         }
813     }
814 
815     if ( _masked && (mix != 1.) ) {
816         // compute the bounding box with the default ROI
817         Coords::rectBoundingBox(srcRoI, args.regionOfInterest, &srcRoI);
818     }
819 
820     // no need to set it on mask (the default ROI is OK)
821     rois.setRegionOfInterest(*_srcClip, srcRoI);
822 } // getRegionsOfInterest
823 
824 template <class PIX, int nComponents, int maxValue, bool masked>
825 void
renderInternalForBitDepth(const RenderArguments & args)826 Transform3x3Plugin::renderInternalForBitDepth(const RenderArguments &args)
827 {
828     const double time = args.time;
829     FilterEnum filter = args.renderQualityDraft ? eFilterImpulse : eFilterCubic;
830 
831     if (!args.renderQualityDraft && _filter) {
832         filter = (FilterEnum)_filter->getValueAtTime(time);
833     }
834     bool clamp = false;
835     if (_clamp) {
836         clamp = _clamp->getValueAtTime(time);
837     }
838 
839     // as you may see below, some filters don't need explicit clamping, since they are
840     // "clamped" by construction.
841     switch (filter) {
842     case eFilterImpulse: {
843         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterImpulse, false> fred(*this);
844         setupAndProcess(fred, args);
845         break;
846     }
847     case eFilterBox: {
848         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterBox, false> fred(*this);
849         setupAndProcess(fred, args);
850         break;
851     }
852     case eFilterBilinear: {
853         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterBilinear, false> fred(*this);
854         setupAndProcess(fred, args);
855         break;
856     }
857     case eFilterCubic: {
858         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterCubic, false> fred(*this);
859         setupAndProcess(fred, args);
860         break;
861     }
862     case eFilterKeys:
863         if (clamp) {
864             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterKeys, true> fred(*this);
865             setupAndProcess(fred, args);
866         } else {
867             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterKeys, false> fred(*this);
868             setupAndProcess(fred, args);
869         }
870         break;
871     case eFilterSimon:
872         if (clamp) {
873             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterSimon, true> fred(*this);
874             setupAndProcess(fred, args);
875         } else {
876             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterSimon, false> fred(*this);
877             setupAndProcess(fred, args);
878         }
879         break;
880     case eFilterRifman:
881         if (clamp) {
882             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterRifman, true> fred(*this);
883             setupAndProcess(fred, args);
884         } else {
885             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterRifman, false> fred(*this);
886             setupAndProcess(fred, args);
887         }
888         break;
889     case eFilterMitchell:
890         if (clamp) {
891             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterMitchell, true> fred(*this);
892             setupAndProcess(fred, args);
893         } else {
894             Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterMitchell, false> fred(*this);
895             setupAndProcess(fred, args);
896         }
897         break;
898     case eFilterParzen: {
899         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterParzen, false> fred(*this);
900         setupAndProcess(fred, args);
901         break;
902     }
903     case eFilterNotch: {
904         Transform3x3Processor<PIX, nComponents, maxValue, masked, eFilterNotch, false> fred(*this);
905         setupAndProcess(fred, args);
906         break;
907     }
908     } // switch
909 } // renderInternalForBitDepth
910 
911 // the internal render function
912 template <int nComponents, bool masked>
913 void
renderInternal(const RenderArguments & args,BitDepthEnum dstBitDepth)914 Transform3x3Plugin::renderInternal(const RenderArguments &args,
915                                    BitDepthEnum dstBitDepth)
916 {
917     switch (dstBitDepth) {
918     case eBitDepthUByte:
919         renderInternalForBitDepth<unsigned char, nComponents, 255, masked>(args);
920         break;
921     case eBitDepthUShort:
922         renderInternalForBitDepth<unsigned short, nComponents, 65535, masked>(args);
923         break;
924     case eBitDepthFloat:
925         renderInternalForBitDepth<float, nComponents, 1, masked>(args);
926         break;
927     default:
928         throwSuiteStatusException(kOfxStatErrUnsupported);
929     }
930 }
931 
932 // the overridden render function
933 void
render(const RenderArguments & args)934 Transform3x3Plugin::render(const RenderArguments &args)
935 {
936     // instantiate the render code based on the pixel depth of the dst clip
937     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
938     int dstComponentCount  = _dstClip->getPixelComponentCount();
939 
940     assert(1 <= dstComponentCount && dstComponentCount <= 4);
941     switch (dstComponentCount) {
942     case 4:
943         if (_masked) {
944             renderInternal<4, true>(args, dstBitDepth);
945         } else {
946             renderInternal<4, false>(args, dstBitDepth);
947         }
948         break;
949     case 3:
950         if (_masked) {
951             renderInternal<3, true>(args, dstBitDepth);
952         } else {
953             renderInternal<3, false>(args, dstBitDepth);
954         }
955         break;
956     case 2:
957         if (_masked) {
958             renderInternal<2, true>(args, dstBitDepth);
959         } else {
960             renderInternal<2, false>(args, dstBitDepth);
961         }
962         break;
963     case 1:
964         if (_masked) {
965             renderInternal<1, true>(args, dstBitDepth);
966         } else {
967             renderInternal<1, false>(args, dstBitDepth);
968         }
969         break;
970     default:
971         break;
972     }
973 }
974 
975 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)976 Transform3x3Plugin::isIdentity(const IsIdentityArguments &args,
977                                Clip * &identityClip,
978                                double & /*identityTime*/
979 #ifdef OFX_EXTENSIONS_NUKE
980                                , int& /*view*/, std::string& /*plane*/
981 #endif
982                                )
983 {
984     const double time = args.time;
985 
986     if (_dirBlurAmount) {
987         double amount = 1.;
988         _dirBlurAmount->getValueAtTime(time, amount);
989         if (amount == 0.) {
990             identityClip = _srcClip;
991 
992             return true;
993         }
994     }
995 
996     // if there is motion blur, we suppose the transform is not identity
997     double motionblur = _invert ? 1. : 0.; // default is 1 for GodRays, 0 for Mirror
998     if (_motionblur) {
999         _motionblur->getValueAtTime(time, motionblur);
1000     }
1001     double shutter = 0.;
1002     if (_shutter) {
1003         _shutter->getValueAtTime(time, shutter);
1004     }
1005     bool hasmotionblur = (shutter != 0. && motionblur != 0.);
1006     if (hasmotionblur) {
1007         return false;
1008     }
1009 
1010     if (_clamp) {
1011         // if image has values above 1., they will be clamped.
1012         bool clamp;
1013         _clamp->getValueAtTime(time, clamp);
1014         if (clamp) {
1015             return false;
1016         }
1017     }
1018 
1019     if ( isIdentity(time) ) { // let's call the Transform-specific one first
1020         identityClip = _srcClip;
1021 
1022         return true;
1023     }
1024 
1025     // GENERIC
1026     if (_masked) {
1027         double mix = 1.;
1028         if (_mix) {
1029             _mix->getValueAtTime(time, mix);
1030         }
1031         if (mix == 0.) {
1032             identityClip = _srcClip;
1033 
1034             return true;
1035         }
1036 
1037         bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(args.time) ) && _maskClip && _maskClip->isConnected() );
1038         if (doMasking) {
1039             bool maskInvert;
1040             _maskInvert->getValueAtTime(args.time, maskInvert);
1041             if (!maskInvert) {
1042                 OfxRectI maskRoD;
1043                 if (getImageEffectHostDescription()->supportsMultiResolution) {
1044                     // In Sony Catalyst Edit, clipGetRegionOfDefinition returns the RoD in pixels instead of canonical coordinates.
1045                     // In hosts that do not support multiResolution (e.g. Sony Catalyst Edit), all inputs have the same RoD anyway.
1046                     Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(args.time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
1047                     // effect is identity if the renderWindow doesn't intersect the mask RoD
1048                     if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
1049                         identityClip = _srcClip;
1050 
1051                         return true;
1052                     }
1053                 }
1054             }
1055         }
1056     }
1057 
1058     return false;
1059 } // Transform3x3Plugin::isIdentity
1060 
1061 #ifdef OFX_EXTENSIONS_NUKE
1062 // overridden getTransform
1063 bool
getTransform(const TransformArguments & args,Clip * & transformClip,double transformMatrix[9])1064 Transform3x3Plugin::getTransform(const TransformArguments &args,
1065                                  Clip * &transformClip,
1066                                  double transformMatrix[9])
1067 {
1068     //std::cout << "getTransform called!" << std::endl;
1069 
1070     // Even if the plugin advertizes it cannot transform, getTransform() may be called, e.g. to
1071     // get a transform for the overlays. We thus always return a transform.
1072     //assert(!_masked); // this should never get called for masked plugins, since they don't advertise that they can transform
1073     //if (_masked) {
1074     //    return false;
1075     //}
1076 
1077     const double time = args.time;
1078 
1079     if (!args.renderQualityDraft) {
1080         // first, check if effect has blur, see Transform3x3Plugin::setupAndProcess()
1081         double motionblur = 0.;
1082         bool directionalBlur = (_paramsType != eTransform3x3ParamsTypeNone);
1083 
1084         if (_motionblur) {
1085             _motionblur->getValueAtTime(time, motionblur);
1086         }
1087         if (_directionalBlur) {
1088             _directionalBlur->getValueAtTime(time, directionalBlur);
1089         }
1090         double shutter = 0.;
1091         if (!directionalBlur) {
1092             if (_shutter) {
1093                 _shutter->getValueAtTime(time, shutter);
1094             }
1095         }
1096         if ( ( (shutter != 0.) && (motionblur != 0.) ) || directionalBlur ) {
1097             // effect has blur
1098             return false;
1099         }
1100     }
1101 
1102     bool invert = false;
1103 
1104     // Transform3x3-GENERIC
1105     if (_invert) {
1106         _invert->getValueAtTime(time, invert);
1107     }
1108 
1109     Matrix3x3 invtransform;
1110     bool success = getInverseTransformCanonical(time, args.renderView, 1., invert, &invtransform);
1111     if (!success) {
1112         return false;
1113     }
1114 
1115 
1116     // invert it
1117     Matrix3x3 transformCanonical;
1118     if ( !invtransform.inverse(&transformCanonical) ) {
1119         return false; // no transform available, render as usual
1120     }
1121     double srcpixelaspectratio = ( _srcClip && _srcClip->isConnected() ) ? _srcClip->getPixelAspectRatio() : 1.;
1122     double dstpixelaspectratio = _dstClip ? _dstClip->getPixelAspectRatio() : 1.;
1123     bool fielded = args.fieldToRender == eFieldLower || args.fieldToRender == eFieldUpper;
1124     Matrix3x3 transformPixel = ( ofxsMatCanonicalToPixel(dstpixelaspectratio, args.renderScale.x, args.renderScale.y, fielded) *
1125                                  transformCanonical *
1126                                  ofxsMatPixelToCanonical(srcpixelaspectratio, args.renderScale.x, args.renderScale.y, fielded) );
1127     transformClip = _srcClip;
1128     transformMatrix[0] = transformPixel(0,0);
1129     transformMatrix[1] = transformPixel(0,1);
1130     transformMatrix[2] = transformPixel(0,2);
1131     transformMatrix[3] = transformPixel(1,0);
1132     transformMatrix[4] = transformPixel(1,1);
1133     transformMatrix[5] = transformPixel(1,2);
1134     transformMatrix[6] = transformPixel(2,0);
1135     transformMatrix[7] = transformPixel(2,1);
1136     transformMatrix[8] = transformPixel(2,2);
1137 
1138     return true;
1139 } // Transform3x3Plugin::getTransform
1140 
1141 #endif // ifdef OFX_EXTENSIONS_NUKE
1142 
1143 size_t
getInverseTransforms(double time,int view,OfxPointD renderscale,bool fielded,double srcpixelAspectRatio,double dstpixelAspectRatio,bool invert,double shutter,ShutterOffsetEnum shutteroffset,double shuttercustomoffset,Matrix3x3 * invtransform,size_t invtransformsizealloc) const1144 Transform3x3Plugin::getInverseTransforms(double time,
1145                                          int view,
1146                                          OfxPointD renderscale,
1147                                          bool fielded,
1148                                          double srcpixelAspectRatio,
1149                                          double dstpixelAspectRatio,
1150                                          bool invert,
1151                                          double shutter,
1152                                          ShutterOffsetEnum shutteroffset,
1153                                          double shuttercustomoffset,
1154                                          Matrix3x3* invtransform,
1155                                          size_t invtransformsizealloc) const
1156 {
1157     OfxRangeD range;
1158 
1159     shutterRange(time, shutter, shutteroffset, shuttercustomoffset, &range);
1160     double t_start = range.min;
1161     double t_end = range.max; // shutter time
1162     bool allequal = true;
1163     size_t invtransformsize = invtransformsizealloc;
1164     Matrix3x3 canonicalToPixel = ofxsMatCanonicalToPixel(srcpixelAspectRatio, renderscale.x, renderscale.y, fielded);
1165     Matrix3x3 pixelToCanonical = ofxsMatPixelToCanonical(dstpixelAspectRatio, renderscale.x, renderscale.y, fielded);
1166     Matrix3x3 invtransformCanonical;
1167 
1168     for (size_t i = 0; i < invtransformsize; ++i) {
1169         double t = (i == 0) ? t_start : ( t_start + i * (t_end - t_start) / (double)(invtransformsizealloc - 1) );
1170         bool success = getInverseTransformCanonical(t, view, 1., invert, &invtransformCanonical); // virtual function
1171         if (success) {
1172             invtransform[i] = canonicalToPixel * invtransformCanonical * pixelToCanonical;
1173         } else {
1174             invtransform[i](0,0) = 0.;
1175             invtransform[i](0,1) = 0.;
1176             invtransform[i](0,2) = 0.;
1177             invtransform[i](1,0) = 0.;
1178             invtransform[i](1,1) = 0.;
1179             invtransform[i](1,2) = 0.;
1180             invtransform[i](2,0) = 0.;
1181             invtransform[i](2,1) = 0.;
1182             invtransform[i](2,2) = 1.;
1183         }
1184         allequal = allequal && (invtransform[i](0,0) == invtransform[0](0,0) &&
1185                                 invtransform[i](0,1) == invtransform[0](0,1) &&
1186                                 invtransform[i](0,2) == invtransform[0](0,2) &&
1187                                 invtransform[i](1,0) == invtransform[0](1,0) &&
1188                                 invtransform[i](1,1) == invtransform[0](1,1) &&
1189                                 invtransform[i](1,2) == invtransform[0](1,2) &&
1190                                 invtransform[i](2,0) == invtransform[0](2,0) &&
1191                                 invtransform[i](2,1) == invtransform[0](2,1) &&
1192                                 invtransform[i](2,2) == invtransform[0](2,2));
1193     }
1194     if (allequal) { // there is only one transform, no need to do motion blur!
1195         invtransformsize = 1;
1196     }
1197 
1198     return invtransformsize;
1199 }
1200 
1201 size_t
getInverseTransformsBlur(double time,int view,OfxPointD renderscale,bool fielded,double srcpixelAspectRatio,double dstpixelAspectRatio,bool invert,double amountFrom,double amountTo,Matrix3x3 * invtransform,double * amount,size_t invtransformsizealloc) const1202 Transform3x3Plugin::getInverseTransformsBlur(double time,
1203                                              int view,
1204                                              OfxPointD renderscale,
1205                                              bool fielded,
1206                                              double srcpixelAspectRatio,
1207                                              double dstpixelAspectRatio,
1208                                              bool invert,
1209                                              double amountFrom,
1210                                              double amountTo,
1211                                              Matrix3x3* invtransform,
1212                                              double *amount,
1213                                              size_t invtransformsizealloc) const
1214 {
1215     bool allequal = true;
1216     Matrix3x3 canonicalToPixel = ofxsMatCanonicalToPixel(srcpixelAspectRatio, renderscale.x, renderscale.y, fielded);
1217     Matrix3x3 pixelToCanonical = ofxsMatPixelToCanonical(dstpixelAspectRatio, renderscale.x, renderscale.y, fielded);
1218     Matrix3x3 invtransformCanonical;
1219     size_t invtransformsize = 0;
1220 
1221     for (size_t i = 0; i < invtransformsizealloc; ++i) {
1222         //double a = 1. - i / (double)(invtransformsizealloc - 1); // Theoretically better
1223         double a = 1. - (i + 1) / (double)(invtransformsizealloc); // To be compatible with Nuke (Nuke bug?)
1224         double amt = amountFrom + (amountTo - amountFrom) * a;
1225         bool success = getInverseTransformCanonical(time, view, amt, invert, &invtransformCanonical); // virtual function
1226         if (success) {
1227             if (amount) {
1228                 amount[invtransformsize] = amt;
1229             }
1230             invtransform[invtransformsize] = canonicalToPixel * invtransformCanonical * pixelToCanonical;
1231             ++invtransformsize;
1232             allequal = allequal && (invtransform[i](0,0) == invtransform[0](0,0) &&
1233                                     invtransform[i](0,1) == invtransform[0](0,1) &&
1234                                     invtransform[i](0,2) == invtransform[0](0,2) &&
1235                                     invtransform[i](1,0) == invtransform[0](1,0) &&
1236                                     invtransform[i](1,1) == invtransform[0](1,1) &&
1237                                     invtransform[i](1,2) == invtransform[0](1,2) &&
1238                                     invtransform[i](2,0) == invtransform[0](2,0) &&
1239                                     invtransform[i](2,1) == invtransform[0](2,1) &&
1240                                     invtransform[i](2,2) == invtransform[0](2,2));
1241         }
1242     }
1243     if ( (invtransformsize != 0) && allequal ) { // there is only one transform, no need to do motion blur!
1244         invtransformsize = 1;
1245     }
1246 
1247     return invtransformsize;
1248 }
1249 
1250 // override changedParam
1251 void
changedParam(const InstanceChangedArgs & args,const string & paramName)1252 Transform3x3Plugin::changedParam(const InstanceChangedArgs &args,
1253                                  const string &paramName)
1254 {
1255     // must clear persistent message, or render() is not called by Nuke after an error
1256     clearPersistentMessage();
1257     if ( (paramName == kParamTransform3x3Invert) ||
1258          ( paramName == kParamShutter) ||
1259          ( paramName == kParamShutterOffset) ||
1260          ( paramName == kParamShutterCustomOffset) ) {
1261         // Motion Blur is the only parameter that doesn't matter
1262         assert(paramName != kParamTransform3x3MotionBlur);
1263 
1264         changedTransform(args);
1265     }
1266     if (paramName == kParamTransform3x3DirectionalBlur) {
1267         bool directionalBlur;
1268         _directionalBlur->getValueAtTime(args.time, directionalBlur);
1269         _shutter->setEnabled(!directionalBlur);
1270         _shutteroffset->setEnabled(!directionalBlur);
1271         _shuttercustomoffset->setEnabled(!directionalBlur);
1272     }
1273 }
1274 
1275 // this method must be called by the derived class when the transform was changed
1276 void
changedTransform(const InstanceChangedArgs & args)1277 Transform3x3Plugin::changedTransform(const InstanceChangedArgs &args)
1278 {
1279     (void)args;
1280 }
1281 
1282 void
Transform3x3Describe(ImageEffectDescriptor & desc,bool masked)1283 Transform3x3Describe(ImageEffectDescriptor &desc,
1284                      bool masked)
1285 {
1286     desc.addSupportedContext(eContextFilter);
1287     desc.addSupportedContext(eContextGeneral);
1288     if (masked) {
1289         desc.addSupportedContext(eContextPaint);
1290     }
1291     desc.addSupportedBitDepth(eBitDepthUByte);
1292     desc.addSupportedBitDepth(eBitDepthUShort);
1293     desc.addSupportedBitDepth(eBitDepthFloat);
1294 
1295     desc.setSingleInstance(false);
1296     desc.setHostFrameThreading(false);
1297     desc.setTemporalClipAccess(false);
1298     // each field has to be transformed separately, or you will get combing effect
1299     // this should be true for all geometric transforms
1300     desc.setRenderTwiceAlways(true);
1301     desc.setSupportsMultipleClipPARs(false);
1302     desc.setRenderThreadSafety(kRenderThreadSafety);
1303     desc.setSupportsRenderQuality(true);
1304 
1305     // Transform3x3-GENERIC
1306 
1307     // in order to support tiles, the transform plugin must implement the getRegionOfInterest function
1308     desc.setSupportsTiles(kSupportsTiles);
1309 
1310     // in order to support multiresolution, render() must take into account the pixelaspectratio and the renderscale
1311     // and scale the transform appropriately.
1312     // All other functions are usually in canonical coordinates.
1313     desc.setSupportsMultiResolution(kSupportsMultiResolution);
1314 
1315 #ifdef OFX_EXTENSIONS_NUKE
1316     if (!masked) {
1317         // Enable transform by the host.
1318         // It is only possible for transforms which can be represented as a 3x3 matrix.
1319         desc.setCanTransform(true);
1320         if (getImageEffectHostDescription()->canTransform) {
1321             //std::cout << "kFnOfxImageEffectCanTransform (describe) =" << desc.getPropertySet().propGetInt(kFnOfxImageEffectCanTransform) << std::endl;
1322         }
1323     }
1324     // ask the host to render all planes
1325     desc.setPassThroughForNotProcessedPlanes(ePassThroughLevelRenderAllRequestedPlanes);
1326 #endif
1327 #ifdef OFX_EXTENSIONS_NATRON
1328     desc.setChannelSelector(ePixelComponentNone);
1329 #endif
1330 }
1331 
1332 PageParamDescriptor *
Transform3x3DescribeInContextBegin(ImageEffectDescriptor & desc,ContextEnum context,bool masked)1333 Transform3x3DescribeInContextBegin(ImageEffectDescriptor &desc,
1334                                    ContextEnum context,
1335                                    bool masked)
1336 {
1337     // GENERIC
1338 
1339     // Source clip only in the filter context
1340     // create the mandated source clip
1341     // always declare the source clip first, because some hosts may consider
1342     // it as the default input clip (e.g. Nuke)
1343     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
1344 
1345     srcClip->addSupportedComponent(ePixelComponentRGBA);
1346     srcClip->addSupportedComponent(ePixelComponentRGB);
1347 #ifdef OFX_EXTENSIONS_NATRON
1348     srcClip->addSupportedComponent(ePixelComponentXY);
1349 #endif
1350     srcClip->addSupportedComponent(ePixelComponentAlpha);
1351     srcClip->setTemporalClipAccess(false);
1352     srcClip->setSupportsTiles(kSupportsTiles);
1353     srcClip->setIsMask(false);
1354 #ifdef OFX_EXTENSIONS_NUKE
1355     srcClip->setCanTransform(true); // source images can have a transform attached
1356 #endif
1357 
1358     if (masked) {
1359         // GENERIC (MASKED)
1360         //
1361         // if general or paint context, define the mask clip
1362         // if paint context, it is a mandated input called 'brush'
1363         ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
1364         maskClip->addSupportedComponent(ePixelComponentAlpha);
1365         maskClip->setTemporalClipAccess(false);
1366         if (context == eContextGeneral) {
1367             maskClip->setOptional(true);
1368         }
1369         maskClip->setSupportsTiles(kSupportsTiles);
1370         maskClip->setIsMask(true); // we are a mask input
1371     }
1372 
1373     // create the mandated output clip
1374     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
1375     dstClip->addSupportedComponent(ePixelComponentRGBA);
1376     dstClip->addSupportedComponent(ePixelComponentRGB);
1377 #ifdef OFX_EXTENSIONS_NATRON
1378     dstClip->addSupportedComponent(ePixelComponentXY);
1379 #endif
1380     dstClip->addSupportedComponent(ePixelComponentAlpha);
1381     dstClip->setSupportsTiles(kSupportsTiles);
1382 
1383 
1384     // make some pages and to things in
1385     PageParamDescriptor *page = desc.definePageParam("Controls");
1386 
1387     return page;
1388 } // Transform3x3DescribeInContextBegin
1389 
1390 void
Transform3x3DescribeInContextEnd(ImageEffectDescriptor & desc,ContextEnum context,PageParamDescriptor * page,bool masked,Transform3x3Plugin::Transform3x3ParamsTypeEnum paramsType)1391 Transform3x3DescribeInContextEnd(ImageEffectDescriptor &desc,
1392                                  ContextEnum context,
1393                                  PageParamDescriptor* page,
1394                                  bool masked,
1395                                  Transform3x3Plugin::Transform3x3ParamsTypeEnum paramsType)
1396 {
1397     // invert
1398     {
1399         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransform3x3Invert);
1400         param->setLabel(kParamTransform3x3InvertLabel);
1401         param->setHint(kParamTransform3x3InvertHint);
1402         param->setDefault(false);
1403         param->setAnimates(true);
1404         if (page) {
1405             page->addChild(*param);
1406         }
1407     }
1408     // GENERIC PARAMETERS
1409     //
1410 
1411     ofxsFilterDescribeParamsInterpolate2D(desc, page, paramsType == Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur);
1412 
1413     // motionBlur
1414     {
1415         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTransform3x3MotionBlur);
1416         param->setLabel(kParamTransform3x3MotionBlurLabel);
1417         param->setHint(kParamTransform3x3MotionBlurHint);
1418         param->setDefault(paramsType == Transform3x3Plugin::eTransform3x3ParamsTypeDirBlur ? 1. : 0.);
1419         param->setIncrement(0.01);
1420         param->setRange(0., 100.);
1421         param->setDisplayRange(0., 4.);
1422         if (page) {
1423             page->addChild(*param);
1424         }
1425     }
1426 
1427     if (paramsType == Transform3x3Plugin::eTransform3x3ParamsTypeDirBlur) {
1428         {
1429             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamTransform3x3DirBlurAmount);
1430             param->setLabel(kParamTransform3x3DirBlurAmountLabel);
1431             param->setHint(kParamTransform3x3DirBlurAmountHint);
1432             param->setRange(-DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1433             param->setDisplayRange(-1, 2.);
1434             param->setDefault(1);
1435             param->setAnimates(true); // can animate
1436             if (page) {
1437                 page->addChild(*param);
1438             }
1439         }
1440 
1441         {
1442             BooleanParamDescriptor *param = desc.defineBooleanParam(kParamTransform3x3DirBlurCentered);
1443             param->setLabel(kParamTransform3x3DirBlurCenteredLabel);
1444             param->setHint(kParamTransform3x3DirBlurCenteredHint);
1445             param->setAnimates(true); // can animate
1446             if (page) {
1447                 page->addChild(*param);
1448             }
1449         }
1450 
1451         {
1452             DoubleParamDescriptor *param = desc.defineDoubleParam(kParamTransform3x3DirBlurFading);
1453             param->setLabel(kParamTransform3x3DirBlurFadingLabel);
1454             param->setHint(kParamTransform3x3DirBlurFadingHint);
1455             param->setRange(0., 4.);
1456             param->setDisplayRange(0., 4.);
1457             param->setDefault(0.);
1458             param->setAnimates(true); // can animate
1459             if (page) {
1460                 page->addChild(*param);
1461             }
1462         }
1463     } else if (paramsType == Transform3x3Plugin::eTransform3x3ParamsTypeMotionBlur) {
1464         // directionalBlur
1465         {
1466             BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTransform3x3DirectionalBlur);
1467             param->setLabel(kParamTransform3x3DirectionalBlurLabel);
1468             param->setHint(kParamTransform3x3DirectionalBlurHint);
1469             param->setDefault(false);
1470             param->setAnimates(true);
1471             if (page) {
1472                 page->addChild(*param);
1473             }
1474         }
1475 
1476         shutterDescribeInContext(desc, context, page);
1477     }
1478 
1479     if (masked) {
1480         // GENERIC (MASKED)
1481         //
1482         ofxsMaskMixDescribeParams(desc, page);
1483 #ifdef OFX_EXTENSIONS_NUKE
1484     } else if (getImageEffectHostDescription()->canTransform) {
1485         // Transform3x3-GENERIC (NON-MASKED)
1486         //
1487         //std::cout << "kFnOfxImageEffectCanTransform in describeincontext(" << context << ")=" << desc.getPropertySet().propGetInt(kFnOfxImageEffectCanTransform) << std::endl;
1488 #endif
1489     }
1490 } // Transform3x3DescribeInContextEnd
1491 } // namespace OFX
1492