1+ using System ;
2+ using System . Collections . Generic ;
3+ using TabletDriverPlugin . Attributes ;
4+ using TabletDriverPlugin . Tablet ;
5+
6+ namespace TabletDriverPlugin
7+ {
8+ [ PluginName ( "TabletDriver Noise Reduction" ) ]
9+ public class TabletDriverNoiseReduction : IFilter
10+ {
11+ private LinkedList < Point > _buffer = new LinkedList < Point > ( ) ;
12+ private float _distThreshold , _distMax ;
13+ private const int _iterations = 10 ;
14+ private int _samples = 10 ;
15+ private Point _lastPoint ;
16+
17+ public Point Filter ( Point point )
18+ {
19+ SetTarget ( point ) ;
20+
21+ if ( _buffer . Count <= 1 )
22+ {
23+ return SetOutput ( point ) ;
24+ }
25+
26+ // Calculate geometric median from the buffer positions
27+ GetGeometricMedianVector ( ref _lastPoint ) ;
28+
29+ // Distance between latest position and ring buffer
30+ var distance = point . DistanceFrom ( _lastPoint ) ;
31+
32+ // Distance larger than threshold -> modify the ring buffer
33+ if ( distance > DistThreshold )
34+ {
35+ // Ratio between current distance and maximum distance
36+ double distanceRatio ;
37+
38+ // Distance ratio should be between 0.0 and 1.0
39+ // 0.0 -> distance == distanceThreshold
40+ // 1.0 -> distance == distanceMaximum
41+ distanceRatio = ( distance - DistThreshold ) / ( _distMax - DistThreshold ) ;
42+
43+ if ( distanceRatio >= 1f )
44+ {
45+ // Distance larger than maximum -> fill buffer with the latest target position
46+ var bufCount = _buffer . Count ;
47+ _buffer . Clear ( ) ;
48+ for ( int i = 0 ; i < bufCount ; i ++ )
49+ _buffer . AddLast ( point ) ;
50+ return SetOutput ( point ) ;
51+ }
52+ else
53+ {
54+ // Move buffer positions and current position towards the latest target using linear interpolation
55+ // Amount of movement is the distance ratio between threshold and maximum
56+ var bufEnum = _buffer . GetEnumerator ( ) ;
57+
58+ // buffer.LerpAdd()
59+ while ( bufEnum . MoveNext ( ) )
60+ {
61+ bufEnum . Current . X += ( float ) ( ( point . X - bufEnum . Current . X ) * distanceRatio ) ;
62+ bufEnum . Current . Y += ( float ) ( ( point . Y - bufEnum . Current . Y ) * distanceRatio ) ;
63+ }
64+
65+ // outputPosition.LerpAdd()
66+ _lastPoint . X += ( float ) ( ( point . X - _lastPoint . X ) * distanceRatio ) ;
67+ _lastPoint . Y += ( float ) ( ( point . Y - _lastPoint . Y ) * distanceRatio ) ;
68+
69+ // New logging feature available only in TDPlugin 0.3.2
70+ // Log.Write("NoiseReduction", "Noise Reduced! " + _lastPoint, LogLevel.Debug);
71+ return _lastPoint ;
72+ }
73+ }
74+ return SetOutput ( point ) ;
75+ }
76+
77+ private void SetTarget ( Point point )
78+ {
79+ _buffer . AddLast ( point ) ;
80+ while ( _buffer . Count > Samples )
81+ _buffer . RemoveFirst ( ) ;
82+ }
83+
84+ private Point SetOutput ( Point point )
85+ {
86+ _lastPoint = point ;
87+ return point ;
88+ }
89+
90+ private Point GetGeometricMedianVector ( ref Point point )
91+ {
92+ var candidate = new Point ( ) ;
93+ var next = new Point ( ) ;
94+ var minimumDistance = 0.001 ;
95+
96+ double denominator , weight , distance ;
97+
98+ // Calculate the starting position
99+ if ( ! GetAverageVector ( ref candidate ) )
100+ return _lastPoint ;
101+
102+ // Iterate
103+ for ( int iteration = 0 ; iteration < _iterations ; iteration ++ )
104+ {
105+ denominator = 0 ;
106+
107+ // Loop through the buffer and calculate a denominator.
108+ foreach ( var bufferPoint in _buffer )
109+ {
110+ distance = candidate . DistanceFrom ( bufferPoint ) ;
111+
112+ if ( distance > minimumDistance )
113+ denominator += 1.0 / distance ;
114+ else
115+ denominator += 1.0 / minimumDistance ;
116+ }
117+
118+ // Reset the next vector
119+ next . X = 0 ;
120+ next . Y = 0 ;
121+
122+ // Loop through the buffer and calculate a weighted average
123+ foreach ( var bufferPoint in _buffer )
124+ {
125+ distance = candidate . DistanceFrom ( bufferPoint ) ;
126+
127+ if ( distance > minimumDistance )
128+ weight = 1.0 / distance ;
129+ else
130+ weight = 1.0 / minimumDistance ;
131+
132+ next . X += ( float ) ( bufferPoint . X * weight / denominator ) ;
133+ next . Y += ( float ) ( bufferPoint . Y * weight / denominator ) ;
134+ }
135+
136+ // Set the new candidate vector
137+ candidate . X = next . X ;
138+ candidate . Y = next . Y ;
139+ }
140+
141+ // Set output
142+ point . X = candidate . X ;
143+ point . Y = candidate . Y ;
144+ return point ;
145+ }
146+
147+ private bool GetAverageVector ( ref Point point )
148+ {
149+ if ( _buffer . Count == 0 )
150+ return false ;
151+
152+ point . X = 0 ;
153+ point . Y = 0 ;
154+
155+ foreach ( var bufferPoint in _buffer )
156+ {
157+ point . X += bufferPoint . X ;
158+ point . Y += bufferPoint . Y ;
159+ }
160+
161+ point . X /= _buffer . Count ;
162+ point . Y /= _buffer . Count ;
163+ return true ;
164+ }
165+
166+ [ Property ( "Buffer" ) ]
167+ public int Samples
168+ {
169+ set
170+ {
171+ _samples = Math . Clamp ( value , 0 , 20 ) ;
172+ }
173+ get => _samples ;
174+ }
175+
176+ [ UnitProperty ( "Distance Threshold" , "px" ) ]
177+ public float DistThreshold
178+ {
179+ set
180+ {
181+ _distThreshold = Math . Clamp ( value , 0 , 10 ) ;
182+ _distMax = value * 2 ;
183+ }
184+ get => _distThreshold ;
185+ }
186+
187+ public FilterStage FilterStage => FilterStage . PostTranspose ;
188+ }
189+ }
0 commit comments