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