1 /* ***** BEGIN LICENSE BLOCK *****
2 * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3 * Copyright (C) 2013-2018 INRIA
4 *
5 * openfx-misc is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * openfx-misc is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with openfx-misc. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17 * ***** END LICENSE BLOCK ***** */
18
19 /*
20 * Basic tracker with exhaustive search algorithm OFX plugin.
21 */
22
23 #include <cmath>
24 #include <cfloat> // DBL_MAX
25 #include <map>
26 #include <limits>
27 #include <algorithm>
28
29 #include "ofxsProcessing.H"
30 #include "ofxsTracking.h"
31 #include "ofxsCoords.h"
32 #include "ofxsThreadSuite.h"
33 #include "ofxsMultiThread.h"
34 #ifdef OFX_USE_MULTITHREAD_MUTEX
35 namespace {
36 typedef MultiThread::Mutex Mutex;
37 typedef MultiThread::AutoMutex AutoMutex;
38 }
39 #else
40 // some OFX hosts do not have mutex handling in the MT-Suite (e.g. Sony Catalyst Edit)
41 // prefer using the fast mutex by Marcus Geelnard http://tinythreadpp.bitsnbites.eu/
42 #include "fast_mutex.h"
43 namespace {
44 typedef tthread::fast_mutex Mutex;
45 typedef OFX::MultiThread::AutoMutexT<tthread::fast_mutex> AutoMutex;
46 }
47 #endif
48
49 using namespace OFX;
50
51 OFXS_NAMESPACE_ANONYMOUS_ENTER
52
53 #define kPluginName "TrackerPM"
54 #define kPluginGrouping "Transform"
55 #define kPluginDescription \
56 "Point tracker based on pattern matching using an exhaustive search within an image region.\n" \
57 "The Mask input is used to weight the pattern, so that only pixels from the Mask will be tracked. \n" \
58 "The tracker always takes the previous/next frame as reference when searching for a pattern in an image. This can " \
59 "overtime make a track drift from its original pattern.\n" \
60 "Canceling a tracking operation will not wipe all the data analysed so far. If you resume a previously canceled tracking, " \
61 "the tracker will continue tracking, picking up the previous/next frame as reference. "
62 #define kPluginIdentifier "net.sf.openfx.TrackerPM"
63 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
64 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
65
66 #define kSupportsTiles 1
67 #define kSupportsMultiResolution 1
68 #define kSupportsRenderScale 1
69 #define kSupportsMultipleClipPARs false
70 #define kSupportsMultipleClipDepths false
71 #define kRenderThreadSafety eRenderFullySafe
72
73 #define kParamScore "score"
74 #define kParamScoreLabel "Score"
75 #define kParamScoreHint "Correlation score computation method"
76 #define kParamScoreOptionSSD "SSD", "Sum of Squared Differences", "ssd"
77 #define kParamScoreOptionSAD "SAD", "Sum of Absolute Differences, more robust to occlusions", "sad"
78 #define kParamScoreOptionNCC "NCC", "Normalized Cross-Correlation", "ncc"
79 #define kParamScoreOptionZNCC "ZNCC", "Zero-mean Normalized Cross-Correlation, less sensitive to illumination changes", "zncc"
80
81
82 enum TrackerScoreEnum
83 {
84 eTrackerSSD = 0,
85 eTrackerSAD,
86 eTrackerNCC,
87 eTrackerZNCC
88 };
89
90 class TrackerPMProcessorBase;
91 ////////////////////////////////////////////////////////////////////////////////
92 /** @brief The plugin that does our work */
93 class TrackerPMPlugin
94 : public GenericTrackerPlugin
95 {
96 public:
97 /** @brief ctor */
TrackerPMPlugin(OfxImageEffectHandle handle)98 TrackerPMPlugin(OfxImageEffectHandle handle)
99 : GenericTrackerPlugin(handle)
100 , _score(NULL)
101 , _center(NULL)
102 , _offset(NULL)
103 , _referenceFrame(NULL)
104 , _enableReferenceFrame(NULL)
105 , _correlationScore(NULL)
106 , _innerBtmLeft(NULL)
107 , _innerTopRight(NULL)
108 , _outerBtmLeft(NULL)
109 , _outerTopRight(NULL)
110 {
111 _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
112 assert(!_maskClip || !_maskClip->isConnected() || _maskClip->getPixelComponents() == ePixelComponentAlpha);
113 _score = fetchChoiceParam(kParamScore);
114 assert(_score);
115
116 _center = fetchDouble2DParam(kParamTrackingCenterPoint);
117 _offset = fetchDouble2DParam(kParamTrackingOffset);
118 _referenceFrame = fetchIntParam(kParamTrackingReferenceFrame);
119 _enableReferenceFrame = fetchBooleanParam(kParamTrackingEnableReferenceFrame);
120 _correlationScore = fetchDoubleParam(kParamTrackingCorrelationScore);
121 _innerBtmLeft = fetchDouble2DParam(kParamTrackingPatternBoxBtmLeft);
122 _innerTopRight = fetchDouble2DParam(kParamTrackingPatternBoxTopRight);
123 _outerBtmLeft = fetchDouble2DParam(kParamTrackingSearchBoxBtmLeft);
124 _outerTopRight = fetchDouble2DParam(kParamTrackingSearchBoxTopRight);
125 assert(_center && _offset && _innerTopRight && _innerBtmLeft && _outerTopRight && _outerBtmLeft);
126 }
127
128 private:
129 /**
130 * @brief Override to track the entire range between [first,last].
131 * @param forward If true then it should track from first to last, otherwise it should track
132 * from last to first.
133 * @param currentTime The current time at which the track has been requested.
134 **/
135 virtual void trackRange(const TrackArguments& args);
136
137 template <int nComponents>
138 void trackInternal(OfxTime refTime, OfxTime otherTime, const TrackArguments& args);
139
140 template <class PIX, int nComponents, int maxValue>
141 void trackInternalForDepth(OfxTime refTime,
142 const OfxRectD& refBounds,
143 const OfxPointD& refCenter,
144 const OfxPointD& refCenterWithOffset,
145 const Image* refImg,
146 const Image* maskImg,
147 OfxTime otherTime,
148 const OfxRectD& trackSearchBounds,
149 const Image* otherImg);
150
151 /* set up and run a processor */
152 void setupAndProcess(TrackerPMProcessorBase &processor,
153 OfxTime refTime,
154 const OfxRectD& refBounds,
155 const OfxPointD& refCenter,
156 const OfxPointD& refCenterWithOffset,
157 const Image* refImg,
158 const Image* maskImg,
159 OfxTime otherTime,
160 const OfxRectD& trackSearchBounds,
161 const Image* otherImg);
162
163 Clip *_maskClip;
164 ChoiceParam* _score;
165 Double2DParam* _center;
166 Double2DParam* _offset;
167 IntParam* _referenceFrame;
168 BooleanParam* _enableReferenceFrame;
169 DoubleParam* _correlationScore;
170 Double2DParam* _innerBtmLeft;
171 Double2DParam* _innerTopRight;
172 Double2DParam* _outerBtmLeft;
173 Double2DParam* _outerTopRight;
174 };
175
176
177 class TrackerPMProcessorBase
178 : public ImageProcessor
179 {
180 protected:
181 const Image *_otherImg;
182 OfxRectI _refRectPixel;
183 OfxPointI _refCenterI;
184 std::pair<OfxPointD, double> _bestMatch; //< the results for the current processor
185 Mutex _bestMatchMutex; //< this is used so we can multi-thread the tracking and protect the shared results
186
187 public:
TrackerPMProcessorBase(ImageEffect & instance)188 TrackerPMProcessorBase(ImageEffect &instance)
189 : ImageProcessor(instance)
190 , _otherImg(NULL)
191 , _refRectPixel()
192 , _refCenterI()
193 {
194 _bestMatch.second = std::numeric_limits<double>::infinity();
195 }
196
~TrackerPMProcessorBase()197 virtual ~TrackerPMProcessorBase()
198 {
199 }
200
201 /** @brief set the processing parameters. return false if processing cannot be done. */
202 virtual bool setValues(const Image *ref, const Image *other, const Image *mask,
203 const OfxRectI& pattern, const OfxPointI& centeri) = 0;
204
205 /**
206 * @brief Retrieves the results of the track. Must be called once process() returns so it is thread safe.
207 **/
getBestMatch() const208 const OfxPointD& getBestMatch() const { return _bestMatch.first; }
209
getBestScore() const210 double getBestScore() const { return _bestMatch.second; }
211 };
212
213
214 // The "masked", "filter" and "clamp" template parameters allow filter-specific optimization
215 // by the compiler, using the same generic code for all filters.
216 template <class PIX, int nComponents, int maxValue, TrackerScoreEnum scoreType>
217 class TrackerPMProcessor
218 : public TrackerPMProcessorBase
219 {
220 protected:
221 auto_ptr<ImageMemory> _patternImg;
222 PIX *_patternData;
223 auto_ptr<ImageMemory> _weightImg;
224 float *_weightData;
225 double _weightTotal;
226
227 public:
TrackerPMProcessor(ImageEffect & instance)228 TrackerPMProcessor(ImageEffect &instance)
229 : TrackerPMProcessorBase(instance)
230 , _patternImg()
231 , _patternData(NULL)
232 , _weightImg()
233 , _weightData(NULL)
234 , _weightTotal(0.)
235 {
236 }
237
~TrackerPMProcessor()238 ~TrackerPMProcessor()
239 {
240 }
241
242 private:
243 /** @brief set the processing parameters. return false if processing cannot be done. */
setValues(const Image * ref,const Image * other,const Image * mask,const OfxRectI & pattern,const OfxPointI & centeri)244 virtual bool setValues(const Image *ref,
245 const Image *other,
246 const Image *mask,
247 const OfxRectI& pattern,
248 const OfxPointI& centeri)
249 {
250 size_t rowsize = pattern.x2 - pattern.x1;
251 size_t nPix = rowsize * (pattern.y2 - pattern.y1);
252
253
254 // This happens if the pattern is empty. Most probably this is because it is totally outside the image
255 // we better return quickly.
256 if (nPix == 0) {
257 return false;
258 }
259
260 _patternImg.reset( new ImageMemory(sizeof(PIX) * nComponents * nPix, &_effect) );
261 _weightImg.reset( new ImageMemory(sizeof(float) * nPix, &_effect) );
262 _otherImg = other;
263 _refRectPixel = pattern;
264 _refCenterI = centeri;
265
266 _patternData = (PIX*)_patternImg->lock();
267 _weightData = (float*)_weightImg->lock();
268
269 // sliding pointers
270 long patternIdx = 0; // sliding index
271 PIX *patternPtr = _patternData;
272 float *weightPtr = _weightData;
273 _weightTotal = 0.;
274
275 // extract ref and mask
276 for (int i = _refRectPixel.y1; i < _refRectPixel.y2; ++i) {
277 for (int j = _refRectPixel.x1; j < _refRectPixel.x2; ++j, ++weightPtr, patternPtr += nComponents, ++patternIdx) {
278 assert( patternIdx == ( (i - _refRectPixel.y1) * (_refRectPixel.x2 - _refRectPixel.x1) + (j - _refRectPixel.x1) ) );
279 PIX *refPix = (PIX*) ref->getPixelAddress(_refCenterI.x + j, _refCenterI.y + i);
280
281 if (!refPix) {
282 // no reference pixel, set weight to 0
283 *weightPtr = 0.f;
284 for (int c = 0; c < nComponents; ++c) {
285 patternPtr[c] = PIX();
286 }
287 } else {
288 if (!mask) {
289 // no mask, weight is uniform
290 *weightPtr = 1.f;
291 } else {
292 PIX *maskPix = (PIX*) mask->getPixelAddress(_refCenterI.x + j, _refCenterI.y + i);
293 // weight is zero if there's a mask but we're outside of it
294 *weightPtr = maskPix ? (*maskPix / (float)maxValue) : 0.f;
295 }
296 for (int c = 0; c < nComponents; ++c) {
297 patternPtr[c] = refPix[c];
298 }
299 }
300 _weightTotal += *weightPtr;
301 }
302 }
303
304 return (_weightTotal > 0);
305 } // setValues
306
multiThreadProcessImages(OfxRectI procWindow)307 void multiThreadProcessImages(OfxRectI procWindow)
308 {
309 switch (scoreType) {
310 case eTrackerSSD:
311
312 return multiThreadProcessImagesForScore<eTrackerSSD>(procWindow);
313 case eTrackerSAD:
314
315 return multiThreadProcessImagesForScore<eTrackerSAD>(procWindow);
316 case eTrackerNCC:
317
318 return multiThreadProcessImagesForScore<eTrackerNCC>(procWindow);
319 case eTrackerZNCC:
320
321 return multiThreadProcessImagesForScore<eTrackerZNCC>(procWindow);
322 }
323 }
324
325 template<enum TrackerScoreEnum scoreTypeE>
computeScore(int x,int y,const double refMean[3])326 double computeScore(int x,
327 int y,
328 const double refMean[3])
329 {
330 double score = 0;
331 double otherSsq = 0.;
332 double otherMean[3];
333 const int scoreComps = std::min(nComponents, 3);
334
335 if (scoreTypeE == eTrackerZNCC) {
336 for (int c = 0; c < 3; ++c) {
337 otherMean[c] = 0;
338 }
339 // sliding pointers
340 long patternIdx = 0; // sliding index
341 const PIX *patternPtr = _patternData;
342 float *weightPtr = _weightData;
343 for (int i = _refRectPixel.y1; i < _refRectPixel.y2; ++i) {
344 for (int j = _refRectPixel.x1; j < _refRectPixel.x2; ++j, ++weightPtr, patternPtr += nComponents, ++patternIdx) {
345 assert( patternIdx == ( (i - _refRectPixel.y1) * (_refRectPixel.x2 - _refRectPixel.x1) + (j - _refRectPixel.x1) ) );
346 // take nearest pixel in other image (more chance to get a track than with black)
347 int otherx = x + j;
348 int othery = y + i;
349 otherx = std::max( _otherImg->getBounds().x1, std::min(otherx, _otherImg->getBounds().x2 - 1) );
350 othery = std::max( _otherImg->getBounds().y1, std::min(othery, _otherImg->getBounds().y2 - 1) );
351 const PIX *otherPix = (const PIX *) _otherImg->getPixelAddress(otherx, othery);
352 for (int c = 0; c < scoreComps; ++c) {
353 otherMean[c] += *weightPtr * otherPix[c];
354 }
355 }
356 }
357 for (int c = 0; c < scoreComps; ++c) {
358 otherMean[c] /= _weightTotal;
359 }
360 }
361
362 // sliding pointers
363 long patternIdx = 0; // sliding index
364 const PIX *patternPtr = _patternData;
365 float *weightPtr = _weightData;
366
367 for (int i = _refRectPixel.y1; i < _refRectPixel.y2; ++i) {
368 for (int j = _refRectPixel.x1; j < _refRectPixel.x2; ++j, ++weightPtr, patternPtr += nComponents, ++patternIdx) {
369 assert( patternIdx == ( (i - _refRectPixel.y1) * (_refRectPixel.x2 - _refRectPixel.x1) + (j - _refRectPixel.x1) ) );
370 const PIX * const refPix = patternPtr;
371 const float weight = *weightPtr;
372
373 // take nearest pixel in other image (more chance to get a track than with black)
374 int otherx = x + j;
375 int othery = y + i;
376 otherx = std::max( _otherImg->getBounds().x1, std::min(otherx, _otherImg->getBounds().x2 - 1) );
377 othery = std::max( _otherImg->getBounds().y1, std::min(othery, _otherImg->getBounds().y2 - 1) );
378 const PIX *otherPix = (const PIX *) _otherImg->getPixelAddress(otherx, othery);
379
380 ///the search window & pattern window have been intersected to the reference image's bounds
381 assert(refPix && otherPix);
382 for (int c = 0; c < scoreComps; ++c) {
383 switch (scoreTypeE) {
384 case eTrackerSSD:
385 // reference is squared in SSD, so is the weight
386 score += weight * weight * aggregateSD(refPix[c], otherPix[c]);
387 break;
388 case eTrackerSAD:
389 score += weight * aggregateAD(refPix[c], otherPix[c]);
390 break;
391 case eTrackerNCC:
392 score += weight * aggregateCC(refPix[c], otherPix[c]);
393 otherSsq -= weight * aggregateCC(otherPix[c], otherPix[c]);
394 break;
395 case eTrackerZNCC:
396 score += weight * aggregateNCC(refPix[c], refMean[c], otherPix[c], otherMean[c]);
397 otherSsq -= weight * aggregateNCC(otherPix[c], otherMean[c], otherPix[c], otherMean[c]);
398 break;
399 }
400 }
401 }
402 }
403 if ( (scoreTypeE == eTrackerNCC) || (scoreTypeE == eTrackerZNCC) ) {
404 double sdev = std::sqrt( std::max(otherSsq, 0.) );
405 if (sdev != 0.) {
406 score /= sdev;
407 } else {
408 score = std::numeric_limits<double>::infinity();
409 }
410 }
411
412 return score;
413 } // computeScore
414
415 template<enum TrackerScoreEnum scoreTypeE>
multiThreadProcessImagesForScore(const OfxRectI & procWindow)416 void multiThreadProcessImagesForScore(const OfxRectI& procWindow)
417 {
418 assert(_patternImg.get() && _patternData && _weightImg.get() && _weightData && _otherImg && _weightTotal > 0.);
419 assert(scoreType == scoreTypeE);
420 double bestScore = std::numeric_limits<double>::infinity();
421 OfxPointI point;
422 point.x = -1;
423 point.y = -1;
424
425 ///For every pixel in the sub window of the search area we find the pixel
426 ///that minimize the sum of squared differences between the pattern in the ref image
427 ///and the pattern in the other image.
428
429 const int scoreComps = std::min(nComponents, 3);
430 double refMean[3];
431 if (scoreTypeE == eTrackerZNCC) {
432 for (int c = 0; c < 3; ++c) {
433 refMean[c] = 0;
434 }
435 }
436 if (scoreTypeE == eTrackerZNCC) {
437 // sliding pointers
438 long patternIdx = 0; // sliding index
439 const PIX *patternPtr = _patternData;
440 float *weightPtr = _weightData;
441 for (int i = _refRectPixel.y1; i < _refRectPixel.y2; ++i) {
442 for (int j = _refRectPixel.x1; j < _refRectPixel.x2; ++j, ++weightPtr, patternPtr += nComponents, ++patternIdx) {
443 assert( patternIdx == ( (i - _refRectPixel.y1) * (_refRectPixel.x2 - _refRectPixel.x1) + (j - _refRectPixel.x1) ) );
444 const PIX *refPix = patternPtr;
445 for (int c = 0; c < scoreComps; ++c) {
446 refMean[c] += *weightPtr * refPix[c];
447 }
448 }
449 }
450 for (int c = 0; c < scoreComps; ++c) {
451 refMean[c] /= _weightTotal;
452 }
453 }
454
455 ///we're not interested in the alpha channel for RGBA images
456 for (int y = procWindow.y1; y < procWindow.y2; ++y) {
457 if ( _effect.abort() ) {
458 break;
459 }
460
461 for (int x = procWindow.x1; x < procWindow.x2; ++x) {
462 double score = computeScore<scoreTypeE>(x, y, refMean);
463 if (score < bestScore) {
464 bestScore = score;
465 point.x = x;
466 point.y = y;
467 }
468 }
469 }
470
471 // do the subpixel refinement, only if the score is a possible winner
472 // TODO: only do this for the best match
473 double dx = 0.;
474 double dy = 0.;
475
476 _bestMatchMutex.lock();
477 if (_bestMatch.second < bestScore) {
478 _bestMatchMutex.unlock();
479 } else {
480 // don't block other threads
481 _bestMatchMutex.unlock();
482 // compute subpixel position.
483 double scorepc = computeScore<scoreTypeE>(point.x - 1, point.y, refMean);
484 double scorenc = computeScore<scoreTypeE>(point.x + 1, point.y, refMean);
485 if ( (bestScore < scorepc) && (bestScore <= scorenc) ) {
486 // don't simplify the denominator in the following expression,
487 // 2*bestScore - scorenc - scorepc may cause an underflow.
488 double factor = 1. / ( (bestScore - scorenc) + (bestScore - scorepc) );
489 if (factor != 0.) {
490 dx = 0.5 * (scorenc - scorepc) * factor;
491 assert(-0.5 < dx && dx <= 0.5);
492 }
493 }
494 double scorecp = computeScore<scoreTypeE>(point.x, point.y - 1, refMean);
495 double scorecn = computeScore<scoreTypeE>(point.x, point.y + 1, refMean);
496 if ( (bestScore < scorecp) && (bestScore <= scorecn) ) {
497 // don't simplify the denominator in the following expression,
498 // 2*bestScore - scorenc - scorepc may cause an underflow.
499 double factor = 1. / ( (bestScore - scorecn) + (bestScore - scorecp) );
500 if (factor != 0.) {
501 dy = 0.5 * (scorecn - scorecp) / ( (bestScore - scorecn) + (bestScore - scorecp) );
502 assert(-0.5 < dy && dy <= 0.5);
503 }
504 }
505 // check again...
506 {
507 AutoMutex lock(_bestMatchMutex);
508 if (_bestMatch.second > bestScore) {
509 _bestMatch.second = bestScore;
510 _bestMatch.first.x = point.x + dx;
511 _bestMatch.first.y = point.y + dy;
512 }
513 }
514 }
515 } // multiThreadProcessImagesForScore
516
aggregateSD(PIX refPix,PIX otherPix)517 double aggregateSD(PIX refPix,
518 PIX otherPix)
519 {
520 double d = (double)refPix - otherPix;
521
522 return d * d;
523 }
524
aggregateAD(PIX refPix,PIX otherPix)525 double aggregateAD(PIX refPix,
526 PIX otherPix)
527 {
528 return std::abs( (double)refPix - otherPix );
529 }
530
aggregateCC(PIX refPix,PIX otherPix)531 double aggregateCC(PIX refPix,
532 PIX otherPix)
533 {
534 return -(double)refPix * otherPix;
535 }
536
aggregateNCC(PIX refPix,double refMean,PIX otherPix,double otherMean)537 double aggregateNCC(PIX refPix,
538 double refMean,
539 PIX otherPix,
540 double otherMean)
541 {
542 return -(refPix - refMean) * (otherPix - otherMean);
543 }
544 };
545
546
547 void
trackRange(const TrackArguments & args)548 TrackerPMPlugin::trackRange(const TrackArguments& args)
549 {
550 if (!_srcClip || !_srcClip->isConnected()) {
551 return;
552 }
553 # ifdef kOfxImageEffectPropInAnalysis // removed from OFX 1.4
554 // Although the following property has been there since OFX 1.0,
555 // it's not in the HostSupport library.
556 getPropertySet().propSetInt(kOfxImageEffectPropInAnalysis, 1, false);
557 # endif
558 //double t1, t2;
559 // get the first and last times available on the effect's timeline
560 //timeLineGetBounds(t1, t2);
561
562 OfxTime t = args.first;
563 bool changeTime = ( args.reason == eChangeUserEdit && t == timeLineGetTime() );
564 std::string name;
565 _instanceName->getValueAtTime(t, name);
566 assert( (args.forward && args.last >= args.first) || (!args.forward && args.last <= args.first) );
567 bool showProgress = std::abs(args.last - args.first) > 1;
568 if (showProgress) {
569 progressStart(name);
570 }
571
572 bool enableRefFrame = _enableReferenceFrame->getValue();
573
574 while ( args.forward ? (t <= args.last) : (t >= args.last) ) {
575 OfxTime refFrame;
576 if (enableRefFrame) {
577 refFrame = (OfxTime)_referenceFrame->getValueAtTime(t);
578 } else {
579 refFrame = args.forward ? (t - 1) : (t + 1);
580 }
581
582
583 PixelComponentEnum srcComponents = _srcClip->getPixelComponents();
584 assert(srcComponents == ePixelComponentRGB || srcComponents == ePixelComponentRGBA ||
585 srcComponents == ePixelComponentAlpha);
586
587 if (srcComponents == ePixelComponentRGBA) {
588 trackInternal<4>(refFrame, t, args);
589 } else if (srcComponents == ePixelComponentRGB) {
590 trackInternal<3>(refFrame, t, args);
591 } else {
592 assert(srcComponents == ePixelComponentAlpha);
593 trackInternal<1>(refFrame, t, args);
594 }
595 if (args.forward) {
596 ++t;
597 } else {
598 --t;
599 }
600 if (changeTime) {
601 // set the timeline to a specific time
602 timeLineGotoTime(t);
603 }
604 if ( showProgress && !progressUpdate( (t - args.first) / (args.last - args.first) ) ) {
605 progressEnd();
606
607 return;
608 }
609 }
610 if (showProgress) {
611 progressEnd();
612 }
613 # ifdef kOfxImageEffectPropInAnalysis // removed from OFX 1.4
614 getPropertySet().propSetInt(kOfxImageEffectPropInAnalysis, 0, false);
615 # endif
616 } // TrackerPMPlugin::trackRange
617
618 ////////////////////////////////////////////////////////////////////////////////
619 /** @brief render for the filter */
620
621 ////////////////////////////////////////////////////////////////////////////////
622 // basic plugin render function, just a skelington to instantiate templates from
623
624
625 static void
getRefBounds(const OfxRectD & refRect,const OfxPointD & refCenter,OfxRectD * bounds)626 getRefBounds(const OfxRectD& refRect,
627 const OfxPointD &refCenter,
628 OfxRectD *bounds)
629 {
630 bounds->x1 = refCenter.x + refRect.x1;
631 bounds->x2 = refCenter.x + refRect.x2;
632 bounds->y1 = refCenter.y + refRect.y1;
633 bounds->y2 = refCenter.y + refRect.y2;
634
635 // make the window at least 2 pixels high/wide
636 // (this should never happen, of course)
637 if (bounds->x2 < bounds->x1 + 2) {
638 bounds->x1 = (bounds->x1 + bounds->x2) / 2 - 1;
639 bounds->x2 = bounds->x1 + 2;
640 }
641 if (bounds->y2 < bounds->y1 + 2) {
642 bounds->y1 = (bounds->y1 + bounds->y2) / 2 - 1;
643 bounds->y2 = bounds->y1 + 2;
644 }
645 }
646
647 static void
getTrackSearchBounds(const OfxRectD & refRect,const OfxPointD & refCenter,const OfxRectD & searchRect,OfxRectD * bounds)648 getTrackSearchBounds(const OfxRectD& refRect,
649 const OfxPointD &refCenter,
650 const OfxRectD& searchRect,
651 OfxRectD *bounds)
652 {
653 // subtract the pattern window so that we don't check for pixels out of the search window
654 bounds->x1 = refCenter.x + searchRect.x1 - refRect.x1;
655 bounds->y1 = refCenter.y + searchRect.y1 - refRect.y1;
656 bounds->x2 = refCenter.x + searchRect.x2 - refRect.x2;
657 bounds->y2 = refCenter.y + searchRect.y2 - refRect.y2;
658
659 // if the window is empty, make it at least 1 pixel high/wide
660 if (bounds->x2 <= bounds->x1) {
661 bounds->x1 = (bounds->x1 + bounds->x2) / 2;
662 bounds->x2 = bounds->x1 + 1;
663 }
664 if (bounds->y2 <= bounds->y1) {
665 bounds->y1 = (bounds->y1 + bounds->y2) / 2;
666 bounds->y2 = bounds->y1 + 1;
667 }
668 }
669
670 static void
getOtherBounds(const OfxPointD & refCenter,const OfxRectD & searchRect,OfxRectD * bounds)671 getOtherBounds(const OfxPointD &refCenter,
672 const OfxRectD& searchRect,
673 OfxRectD *bounds)
674 {
675 // subtract the pattern window so that we don't check for pixels out of the search window
676 bounds->x1 = refCenter.x + searchRect.x1;
677 bounds->y1 = refCenter.y + searchRect.y1;
678 bounds->x2 = refCenter.x + searchRect.x2;
679 bounds->y2 = refCenter.y + searchRect.y2;
680
681 // if the window is empty, make it at least 1 pixel high/wide
682 if (bounds->x2 <= bounds->x1) {
683 bounds->x1 = (bounds->x1 + bounds->x2) / 2;
684 bounds->x2 = bounds->x1 + 1;
685 }
686 if (bounds->y2 <= bounds->y1) {
687 bounds->y1 = (bounds->y1 + bounds->y2) / 2;
688 bounds->y2 = bounds->y1 + 1;
689 }
690 }
691
692 /* set up and run a processor */
693 void
setupAndProcess(TrackerPMProcessorBase & processor,OfxTime refTime,const OfxRectD & refBounds,const OfxPointD & refCenter,const OfxPointD & refCenterWithOffset,const Image * refImg,const Image * maskImg,OfxTime otherTime,const OfxRectD & trackSearchBounds,const Image * otherImg)694 TrackerPMPlugin::setupAndProcess(TrackerPMProcessorBase &processor,
695 OfxTime refTime,
696 const OfxRectD& refBounds,
697 const OfxPointD& refCenter,
698 const OfxPointD& refCenterWithOffset,
699 const Image* refImg,
700 const Image* maskImg,
701 OfxTime otherTime,
702 const OfxRectD& trackSearchBounds,
703 const Image* otherImg)
704 {
705 if (!_srcClip || !_srcClip->isConnected()) {
706 return;
707 }
708 const double par = _srcClip->getPixelAspectRatio();
709 const OfxPointD rsOne = {1., 1.};
710 OfxRectI trackSearchBoundsPixel;
711 Coords::toPixelEnclosing(trackSearchBounds, rsOne, par, &trackSearchBoundsPixel);
712
713 // compute the pattern window in pixel coords
714 OfxRectI refRectPixel;
715 Coords::toPixelEnclosing(refBounds, rsOne, par, &refRectPixel);
716
717 // round center to nearest pixel center
718 OfxPointI refCenterI;
719 OfxPointD refCenterPixelSub;
720 Coords::toPixel(refCenterWithOffset, rsOne, par, &refCenterI);
721 Coords::toPixelSub(refCenterWithOffset, rsOne, par, &refCenterPixelSub);
722
723 //Clip the refRectPixel to the bounds of the ref image
724 bool intersect = Coords::rectIntersection(refRectPixel, refImg->getBounds(), &refRectPixel);
725
726 if (!intersect) {
727 // can't track: erase any existing track
728 _center->deleteKeyAtTime(otherTime);
729 }
730 refRectPixel.x1 -= refCenterI.x;
731 refRectPixel.x2 -= refCenterI.x;
732 refRectPixel.y1 -= refCenterI.y;
733 refRectPixel.y2 -= refCenterI.y;
734
735 // set the render window
736 processor.setRenderWindow(trackSearchBoundsPixel);
737
738 bool canProcess = processor.setValues(refImg, otherImg, maskImg, refRectPixel, refCenterI);
739
740 if (!canProcess) {
741 // can't track: erase any existing track
742 _center->deleteKeyAtTime(otherTime);
743 _correlationScore->deleteKeyAtTime(otherTime);
744 } else {
745 // Call the base class process member, this will call the derived templated process code
746 processor.process();
747
748 //////////////////////////////////
749 // TODO: subpixel interpolation //
750 //////////////////////////////////
751
752 ///ok the score is now computed, update the center
753 if ( processor.getBestScore() == std::numeric_limits<double>::infinity() ) {
754 // can't track: erase any existing track
755 _center->deleteKeyAtTime(otherTime);
756 } else {
757 // Offset the newCenter by the offset a thaat time
758 OfxPointD otherOffset;
759 _offset->getValueAtTime(otherTime, otherOffset.x, otherOffset.y);
760
761 OfxPointD newCenterPixelSub;
762 OfxPointD newCenter;
763 const OfxPointD& bestMatch = processor.getBestMatch();
764
765 newCenterPixelSub.x = refCenterPixelSub.x + bestMatch.x - refCenterI.x;
766 newCenterPixelSub.y = refCenterPixelSub.y + bestMatch.y - refCenterI.y;
767 Coords::toCanonicalSub(newCenterPixelSub, rsOne, par, &newCenter);
768
769 //Commented-out for Natron compat: Natron does beginEditBlock in the main-thread, hence
770 //since the instanceChanged action is executed in multiple separated thread by Natron when tracking, there's no
771 //telling that the actual setting of the value will be done when the next frame is tracked
772 //beginEditBlock("trackerUpdate");
773 // create a keyframe at starting point
774 _center->setValueAtTime(refTime, refCenter.x, refCenter.y);
775 // create a keyframe at end point
776 _center->setValueAtTime(otherTime, newCenter.x - otherOffset.x, newCenter.y - otherOffset.y);
777 _correlationScore->setValueAtTime( otherTime, processor.getBestScore() );
778 // endEditBlock();
779 }
780 }
781 } // TrackerPMPlugin::setupAndProcess
782
783 template <class PIX, int nComponents, int maxValue>
784 void
trackInternalForDepth(OfxTime refTime,const OfxRectD & refBounds,const OfxPointD & refCenter,const OfxPointD & refCenterWithOffset,const Image * refImg,const Image * maskImg,OfxTime otherTime,const OfxRectD & trackSearchBounds,const Image * otherImg)785 TrackerPMPlugin::trackInternalForDepth(OfxTime refTime,
786 const OfxRectD& refBounds,
787 const OfxPointD& refCenter,
788 const OfxPointD& refCenterWithOffset,
789 const Image* refImg,
790 const Image* maskImg,
791 OfxTime otherTime,
792 const OfxRectD& trackSearchBounds,
793 const Image* otherImg)
794 {
795 TrackerScoreEnum typeE = (TrackerScoreEnum)_score->getValueAtTime(refTime);
796
797 switch (typeE) {
798 case eTrackerSSD: {
799 TrackerPMProcessor<PIX, nComponents, maxValue, eTrackerSSD> fred(*this);
800 setupAndProcess(fred, refTime, refBounds, refCenter, refCenterWithOffset, refImg, maskImg, otherTime, trackSearchBounds, otherImg);
801 break;
802 }
803 case eTrackerSAD: {
804 TrackerPMProcessor<PIX, nComponents, maxValue, eTrackerSAD> fred(*this);
805 setupAndProcess(fred, refTime, refBounds, refCenter, refCenterWithOffset, refImg, maskImg, otherTime, trackSearchBounds, otherImg);
806 break;
807 }
808 case eTrackerNCC: {
809 TrackerPMProcessor<PIX, nComponents, maxValue, eTrackerNCC> fred(*this);
810 setupAndProcess(fred, refTime, refBounds, refCenter, refCenterWithOffset, refImg, maskImg, otherTime, trackSearchBounds, otherImg);
811 break;
812 }
813 case eTrackerZNCC: {
814 TrackerPMProcessor<PIX, nComponents, maxValue, eTrackerZNCC> fred(*this);
815 setupAndProcess(fred, refTime, refBounds, refCenter, refCenterWithOffset, refImg, maskImg, otherTime, trackSearchBounds, otherImg);
816 break;
817 }
818 }
819 }
820
821 // the internal render function
822 template <int nComponents>
823 void
trackInternal(OfxTime refTime,OfxTime otherTime,const TrackArguments & args)824 TrackerPMPlugin::trackInternal(OfxTime refTime,
825 OfxTime otherTime,
826 const TrackArguments& args)
827 {
828 OfxRectD refRect;
829
830 _innerBtmLeft->getValueAtTime(refTime, refRect.x1, refRect.y1);
831 _innerTopRight->getValueAtTime(refTime, refRect.x2, refRect.y2);
832 OfxPointD refCenter;
833 _center->getValueAtTime(refTime, refCenter.x, refCenter.y);
834 OfxRectD searchRect;
835 _outerBtmLeft->getValueAtTime(refTime, searchRect.x1, searchRect.y1);
836 _outerTopRight->getValueAtTime(refTime, searchRect.x2, searchRect.y2);
837
838 OfxPointD offset;
839 _offset->getValueAtTime(refTime, offset.x, offset.y);
840
841 OfxPointD refCenterWithOffset;
842 refCenterWithOffset.x = refCenter.x + offset.x;
843 refCenterWithOffset.y = refCenter.y + offset.y;
844
845 // The search window should be centered around the last keyframe we set to the center
846 OfxPointD prevTimeCenterWithOffset;
847 _center->getValueAtTime(otherTime, prevTimeCenterWithOffset.x, prevTimeCenterWithOffset.y);
848
849 OfxPointD offsetPrevTime;
850 _offset->getValueAtTime(otherTime, offsetPrevTime.x, offsetPrevTime.y);
851
852 prevTimeCenterWithOffset.x += offsetPrevTime.x;
853 prevTimeCenterWithOffset.y += offsetPrevTime.y;
854
855
856 OfxRectD refBounds;
857 getRefBounds(refRect, refCenterWithOffset, &refBounds);
858
859 OfxRectD otherBounds;
860 getOtherBounds(prevTimeCenterWithOffset, searchRect, &otherBounds);
861
862 auto_ptr<const Image> srcRef( ( _srcClip && _srcClip->isConnected() ) ?
863 _srcClip->fetchImage(refTime, refBounds) : 0 );
864 auto_ptr<const Image> srcOther( ( _srcClip && _srcClip->isConnected() ) ?
865 _srcClip->fetchImage(otherTime, otherBounds) : 0 );
866 if ( !srcRef.get() || !srcOther.get() ) {
867 return;
868 }
869 if ( srcRef.get() ) {
870 if ( (srcRef->getRenderScale().x != args.renderScale.x) ||
871 ( srcRef->getRenderScale().y != args.renderScale.y) ) {
872 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
873 throwSuiteStatusException(kOfxStatFailed);
874 }
875 }
876 if ( srcOther.get() ) {
877 if ( (srcOther->getRenderScale().x != args.renderScale.x) ||
878 ( srcOther->getRenderScale().y != args.renderScale.y) ) {
879 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
880 throwSuiteStatusException(kOfxStatFailed);
881 }
882 }
883 // renderScale should never be something else than 1 when called from ActionInstanceChanged
884 if ( ( srcRef->getPixelDepth() != srcOther->getPixelDepth() ) ||
885 ( srcRef->getPixelComponents() != srcOther->getPixelComponents() ) ||
886 ( srcRef->getRenderScale().x != 1.) || ( srcRef->getRenderScale().y != 1) ||
887 ( srcOther->getRenderScale().x != 1.) || ( srcOther->getRenderScale().y != 1) ) {
888 throwSuiteStatusException(kOfxStatErrImageFormat);
889 }
890
891 BitDepthEnum srcBitDepth = srcRef->getPixelDepth();
892
893 // mask cannot be black and transparent, so an empty mask means mask is disabled.
894 // auto ptr for the mask.
895 auto_ptr<const Image> mask( ( _maskClip && _maskClip->isConnected() ) ?
896 _maskClip->fetchImage(refTime) : 0 );
897 if ( mask.get() ) {
898 if ( (mask->getRenderScale().x != args.renderScale.x) ||
899 ( mask->getRenderScale().y != args.renderScale.y) ) {
900 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
901 throwSuiteStatusException(kOfxStatFailed);
902 }
903 }
904
905 OfxRectD trackSearchBounds;
906 getTrackSearchBounds(refRect, prevTimeCenterWithOffset, searchRect, &trackSearchBounds);
907
908 switch (srcBitDepth) {
909 case eBitDepthUByte: {
910 trackInternalForDepth<unsigned char, nComponents, 255>( refTime, refBounds, refCenter, refCenterWithOffset, srcRef.get(), mask.get(), otherTime, trackSearchBounds, srcOther.get() );
911 break;
912 }
913 case eBitDepthUShort: {
914 trackInternalForDepth<unsigned short, nComponents, 65535>( refTime, refBounds, refCenter, refCenterWithOffset, srcRef.get(), mask.get(), otherTime, trackSearchBounds, srcOther.get() );
915 break;
916 }
917 case eBitDepthFloat: {
918 trackInternalForDepth<float, nComponents, 1>( refTime, refBounds, refCenter, refCenterWithOffset, srcRef.get(), mask.get(), otherTime, trackSearchBounds, srcOther.get() );
919 break;
920 }
921 default:
922 throwSuiteStatusException(kOfxStatErrUnsupported);
923 }
924 } // TrackerPMPlugin::trackInternal
925
926 mDeclarePluginFactory(TrackerPMPluginFactory, {ofxsThreadSuiteCheck();}, {});
927 void
describe(ImageEffectDescriptor & desc)928 TrackerPMPluginFactory::describe(ImageEffectDescriptor &desc)
929 {
930 // basic labels
931 desc.setLabel(kPluginName);
932 desc.setPluginGrouping(kPluginGrouping);
933 desc.setPluginDescription(kPluginDescription);
934
935 // description common to all trackers
936 genericTrackerDescribe(desc);
937
938 // add the additional supported contexts
939 desc.addSupportedContext(eContextPaint); // this tracker can be masked
940
941 // supported bit depths depend on the tracking algorithm.
942 desc.addSupportedBitDepth(eBitDepthUByte);
943 desc.addSupportedBitDepth(eBitDepthUShort);
944 desc.addSupportedBitDepth(eBitDepthFloat);
945
946 // single instance depends on the algorithm
947 desc.setSingleInstance(false);
948
949 // rendertwicealways must be set to true if the tracker cannot handle interlaced content (most don't)
950 desc.setRenderTwiceAlways(true);
951 desc.setRenderThreadSafety(kRenderThreadSafety);
952 desc.setOverlayInteractDescriptor(new TrackerRegionOverlayDescriptor);
953
954 #ifdef OFX_EXTENSIONS_NATRON
955 // This plug-in is deprecated since Natron has its new tracker implementation
956 if ( getImageEffectHostDescription()->isNatron &&
957 ( getImageEffectHostDescription()->versionMajor >= 2) &&
958 ( getImageEffectHostDescription()->versionMinor >= 1) ) {
959 desc.setIsDeprecated(true);
960 }
961 #endif
962 }
963
964 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)965 TrackerPMPluginFactory::describeInContext(ImageEffectDescriptor &desc,
966 ContextEnum context)
967 {
968 PageParamDescriptor* page = genericTrackerDescribeInContextBegin(desc, context);
969
970
971 // description common to all trackers
972 genericTrackerDescribePointParameters(desc, page);
973 // center
974 {
975 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingCenterPoint);
976 param->setLabel(kParamTrackingCenterPointLabel);
977 param->setHint(kParamTrackingCenterPointHint);
978 param->setInstanceSpecific(true);
979 param->setDoubleType(eDoubleTypeXYAbsolute);
980 param->setDefaultCoordinateSystem(eCoordinatesNormalised);
981 param->setDefault(0.5, 0.5);
982 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
983 param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
984 param->setIncrement(1.);
985 param->setEvaluateOnChange(false); // The tracker is identity always
986 # ifdef kOfxParamPropPluginMayWrite // removed from OFX 1.4
987 param->getPropertySet().propSetInt(kOfxParamPropPluginMayWrite, 1, false);
988 # endif
989 if (page) {
990 page->addChild(*param);
991 }
992 }
993
994 // offset
995 {
996 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingOffset);
997 param->setLabel(kParamTrackingOffsetLabel);
998 param->setHint(kParamTrackingOffsetHint);
999 param->setInstanceSpecific(true);
1000 param->setDoubleType(eDoubleTypeXYAbsolute);
1001 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
1002 param->setDefault(0, 0);
1003 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1004 param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
1005 param->setIncrement(1.);
1006 param->setEvaluateOnChange(false); // The tracker is identity always
1007 if (page) {
1008 page->addChild(*param);
1009 }
1010 }
1011
1012 // ref
1013 {
1014 IntParamDescriptor* param = desc.defineIntParam(kParamTrackingReferenceFrame);
1015 param->setLabel(kParamTrackingReferenceFrameLabel);
1016 param->setHint(kParamTrackingReferenceFrameHint);
1017 param->setEvaluateOnChange(false); // The tracker is identity always
1018 param->setLayoutHint(eLayoutHintNoNewLine);
1019 if (page) {
1020 page->addChild(*param);
1021 }
1022 }
1023
1024 // enable ref
1025 {
1026 BooleanParamDescriptor* param = desc.defineBooleanParam(kParamTrackingEnableReferenceFrame);
1027 param->setLabel(kParamTrackingEnableReferenceFrameLabel);
1028 param->setHint(kParamTrackingEnableReferenceFrameHint);
1029 param->setEvaluateOnChange(false); // The tracker is identity always
1030 param->setDefault(false);
1031 if (page) {
1032 page->addChild(*param);
1033 }
1034 }
1035
1036 // correlation score
1037 {
1038 DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTrackingCorrelationScore);
1039 param->setLabel(kParamTrackingCorrelationScoreLabel);
1040 param->setHint(kParamTrackingCorrelationScoreHint);
1041 param->setInstanceSpecific(true);
1042 param->setEvaluateOnChange(false); // The tracker is identity always
1043 if (page) {
1044 page->addChild(*param);
1045 }
1046 }
1047
1048
1049 // innerBtmLeft
1050 {
1051 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingPatternBoxBtmLeft);
1052 param->setLabel(kParamTrackingPatternBoxBtmLeftLabel);
1053 param->setHint(kParamTrackingPatternBoxBtmLeftHint);
1054 param->setDoubleType(eDoubleTypeXY);
1055 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
1056 param->setDefault(-15, -15);
1057 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1058 param->setDisplayRange(-50., -50., 50., 50.);
1059 param->setIncrement(1.);
1060 param->setEvaluateOnChange(false); // The tracker is identity always
1061 # ifdef kOfxParamPropPluginMayWrite // removed from OFX 1.4
1062 param->getPropertySet().propSetInt(kOfxParamPropPluginMayWrite, 1, false);
1063 # endif
1064 if (page) {
1065 page->addChild(*param);
1066 }
1067 }
1068
1069 // innerTopRight
1070 {
1071 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingPatternBoxTopRight);
1072 param->setLabel(kParamTrackingPatternBoxTopRightLabel);
1073 param->setHint(kParamTrackingPatternBoxTopRightHint);
1074 param->setDoubleType(eDoubleTypeXY);
1075 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
1076 param->setDefault(15, 15);
1077 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1078 param->setDisplayRange(-50., -50., 50., 50.);
1079 param->setIncrement(1.);
1080 param->setEvaluateOnChange(false); // The tracker is identity always
1081 # ifdef kOfxParamPropPluginMayWrite // removed from OFX 1.4
1082 param->getPropertySet().propSetInt(kOfxParamPropPluginMayWrite, 1, false);
1083 # endif
1084 if (page) {
1085 page->addChild(*param);
1086 }
1087 }
1088
1089 // outerBtmLeft
1090 {
1091 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingSearchBoxBtmLeft);
1092 param->setLabel(kParamTrackingSearchBoxBtmLeftLabel);
1093 param->setHint(kParamTrackingSearchBoxBtmLeftHint);
1094 param->setDoubleType(eDoubleTypeXY);
1095 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
1096 param->setDefault(-25, -25);
1097 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1098 param->setDisplayRange(-100., -100., 100., 100.);
1099 param->setIncrement(1.);
1100 param->setEvaluateOnChange(false); // The tracker is identity always
1101 # ifdef kOfxParamPropPluginMayWrite // removed from OFX 1.4
1102 param->getPropertySet().propSetInt(kOfxParamPropPluginMayWrite, 1, false);
1103 # endif
1104 if (page) {
1105 page->addChild(*param);
1106 }
1107 }
1108
1109 // outerTopRight
1110 {
1111 Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamTrackingSearchBoxTopRight);
1112 param->setLabel(kParamTrackingSearchBoxTopRightLabel);
1113 param->setHint(kParamTrackingSearchBoxBtmLeftHint);
1114 param->setDoubleType(eDoubleTypeXY);
1115 param->setDefaultCoordinateSystem(eCoordinatesCanonical); // Nuke defaults to Normalized for XY and XYAbsolute!
1116 param->setDefault(25, 25);
1117 param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
1118 param->setDisplayRange(-100., -100., 100., 100.);
1119 param->setIncrement(1.);
1120 param->setEvaluateOnChange(false); // The tracker is identity always
1121 # ifdef kOfxParamPropPluginMayWrite // removed from OFX 1.4
1122 param->getPropertySet().propSetInt(kOfxParamPropPluginMayWrite, 1, false);
1123 # endif
1124 if (page) {
1125 page->addChild(*param);
1126 }
1127 }
1128
1129
1130 // this tracker can be masked
1131 if ( (context == eContextGeneral) || (context == eContextPaint) || (context == eContextTracker) ) {
1132 ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
1133 maskClip->addSupportedComponent(ePixelComponentAlpha);
1134 maskClip->setTemporalClipAccess(false);
1135 if ( (context == eContextGeneral) || (context == eContextTracker) ) {
1136 maskClip->setOptional(true);
1137 }
1138 maskClip->setSupportsTiles(kSupportsTiles);
1139 maskClip->setIsMask(true);
1140 }
1141
1142 // score
1143 {
1144 ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamScore);
1145 param->setLabel(kParamScoreLabel);
1146 param->setHint(kParamScoreHint);
1147 assert(param->getNOptions() == eTrackerSSD);
1148 param->appendOption(kParamScoreOptionSSD);
1149 assert(param->getNOptions() == eTrackerSAD);
1150 param->appendOption(kParamScoreOptionSAD);
1151 assert(param->getNOptions() == eTrackerNCC);
1152 param->appendOption(kParamScoreOptionNCC);
1153 assert(param->getNOptions() == eTrackerZNCC);
1154 param->appendOption(kParamScoreOptionZNCC);
1155 param->setDefault( (int)eTrackerSAD );
1156 if (page) {
1157 page->addChild(*param);
1158 }
1159 }
1160 } // TrackerPMPluginFactory::describeInContext
1161
1162 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)1163 TrackerPMPluginFactory::createInstance(OfxImageEffectHandle handle,
1164 ContextEnum /*context*/)
1165 {
1166 return new TrackerPMPlugin(handle);
1167 }
1168
1169 static TrackerPMPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
1170 mRegisterPluginFactoryInstance(p)
1171
1172 OFXS_NAMESPACE_ANONYMOUS_EXIT
1173