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