1 /*
2 * Copyright 1995-2002 by Frederic Lepied, France. <Lepied@XFree86.org>
3 * Copyright 2002-2008 by Ping Cheng, Wacom. <pingc@wacom.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program 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 this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <math.h>
25 #include "xf86Wacom.h"
26 #include "wcmFilter.h"
27
28 /*****************************************************************************
29 * Static functions
30 ****************************************************************************/
31
32 static void filterCurveToLine(int* pCurve, int nMax, double x0, double y0,
33 double x1, double y1, double x2, double y2,
34 double x3, double y3);
35 static int filterOnLine(double x0, double y0, double x1, double y1,
36 double a, double b);
37 static void filterLine(int* pCurve, int nMax, int x0, int y0, int x1, int y1);
38
39
40 /*****************************************************************************
41 * wcmCheckPressureCurveValues -- check pressure curve values for sanity.
42 * Return TRUE if values are sane or FALSE otherwise.
43 ****************************************************************************/
wcmCheckPressureCurveValues(int x0,int y0,int x1,int y1)44 int wcmCheckPressureCurveValues(int x0, int y0, int x1, int y1)
45 {
46 return !((x0 < 0) || (x0 > 100) || (y0 < 0) || (y0 > 100) ||
47 (x1 < 0) || (x1 > 100) || (y1 < 0) || (y1 > 100));
48 }
49
50
51 /*****************************************************************************
52 * wcmSetPressureCurve -- apply user-defined curve to pressure values
53 ****************************************************************************/
wcmSetPressureCurve(WacomDevicePtr pDev,int x0,int y0,int x1,int y1)54 void wcmSetPressureCurve(WacomDevicePtr pDev, int x0, int y0,
55 int x1, int y1)
56 {
57 /* sanity check values */
58 if (!wcmCheckPressureCurveValues(x0, y0, x1, y1))
59 return;
60
61 /* A NULL pPressCurve indicates the (default) linear curve */
62 if (x0 == 0 && y0 == 0 && x1 == 100 && y1 == 100) {
63 free(pDev->pPressCurve);
64 pDev->pPressCurve = NULL;
65 }
66 else if (!pDev->pPressCurve) {
67 pDev->pPressCurve = calloc(FILTER_PRESSURE_RES+1, sizeof(*pDev->pPressCurve));
68
69 if (!pDev->pPressCurve) {
70 LogMessageVerbSigSafe(X_WARNING, 0,
71 "Unable to allocate memory for pressure curve; using default.\n");
72 x0 = 0;
73 y0 = 0;
74 x1 = 100;
75 y1 = 100;
76 }
77 }
78
79 if (pDev->pPressCurve)
80 filterCurveToLine(pDev->pPressCurve,
81 pDev->maxCurve,
82 0.0, 0.0, /* bottom left */
83 x0/100.0, y0/100.0, /* control point 1 */
84 x1/100.0, y1/100.0, /* control point 2 */
85 1.0, 1.0); /* top right */
86
87 pDev->nPressCtrl[0] = x0;
88 pDev->nPressCtrl[1] = y0;
89 pDev->nPressCtrl[2] = x1;
90 pDev->nPressCtrl[3] = y1;
91 }
92
93 /*
94 * wcmResetSampleCounter --
95 * Device specific filter routines are responcable for storing raw data
96 * as well as filtering. wcmResetSampleCounter is called to reset
97 * raw counters.
98 */
wcmResetSampleCounter(const WacomChannelPtr pChannel)99 void wcmResetSampleCounter(const WacomChannelPtr pChannel)
100 {
101 pChannel->nSamples = 0;
102 pChannel->rawFilter.npoints = 0;
103 }
104
105
filterNearestPoint(double x0,double y0,double x1,double y1,double a,double b,double * x,double * y)106 static void filterNearestPoint(double x0, double y0, double x1, double y1,
107 double a, double b, double* x, double* y)
108 {
109 double vx, vy, wx, wy, d1, d2, c;
110
111 wx = a - x0; wy = b - y0;
112 vx = x1 - x0; vy = y1 - y0;
113
114 d1 = vx * wx + vy * wy;
115 if (d1 <= 0)
116 {
117 *x = x0;
118 *y = y0;
119 }
120 else
121 {
122 d2 = vx * vx + vy * vy;
123 if (d1 >= d2)
124 {
125 *x = x1;
126 *y = y1;
127 }
128 else
129 {
130 c = d1 / d2;
131 *x = x0 + c * vx;
132 *y = y0 + c * vy;
133 }
134 }
135 }
136
filterOnLine(double x0,double y0,double x1,double y1,double a,double b)137 static int filterOnLine(double x0, double y0, double x1, double y1,
138 double a, double b)
139 {
140 double x, y, d;
141 filterNearestPoint(x0,y0,x1,y1,a,b,&x,&y);
142 d = (x-a)*(x-a) + (y-b)*(y-b);
143 return d < 0.00001; /* within 100th of a point (1E-2 squared) */
144 }
145
filterCurveToLine(int * pCurve,int nMax,double x0,double y0,double x1,double y1,double x2,double y2,double x3,double y3)146 static void filterCurveToLine(int* pCurve, int nMax, double x0, double y0,
147 double x1, double y1, double x2, double y2,
148 double x3, double y3)
149 {
150 double x01,y01,x32,y32,xm,ym;
151 double c1,d1,c2,d2,e,f;
152
153 /* check if control points are on line */
154 if (filterOnLine(x0,y0,x3,y3,x1,y1) && filterOnLine(x0,y0,x3,y3,x2,y2))
155 {
156 filterLine(pCurve,nMax,
157 (int)(x0*nMax),(int)(y0*nMax),
158 (int)(x3*nMax),(int)(y3*nMax));
159 return;
160 }
161
162 /* calculate midpoints */
163 x01 = (x0 + x1) / 2; y01 = (y0 + y1) / 2;
164 x32 = (x3 + x2) / 2; y32 = (y3 + y2) / 2;
165
166 /* calc split point */
167 xm = (x1 + x2) / 2; ym = (y1 + y2) / 2;
168
169 /* calc control points and midpoint */
170 c1 = (x01 + xm) / 2; d1 = (y01 + ym) / 2;
171 c2 = (x32 + xm) / 2; d2 = (y32 + ym) / 2;
172 e = (c1 + c2) / 2; f = (d1 + d2) / 2;
173
174 /* do each side */
175 filterCurveToLine(pCurve,nMax,x0,y0,x01,y01,c1,d1,e,f);
176 filterCurveToLine(pCurve,nMax,e,f,c2,d2,x32,y32,x3,y3);
177 }
178
filterLine(int * pCurve,int nMax,int x0,int y0,int x1,int y1)179 static void filterLine(int* pCurve, int nMax, int x0, int y0, int x1, int y1)
180 {
181 int dx, dy, ax, ay, sx, sy, x, y, d;
182
183 /* sanity check */
184 if ((x0 < 0) || (y0 < 0) || (x1 < 0) || (y1 < 0) ||
185 (x0 > nMax) || (y0 > nMax) || (x1 > nMax) || (y1 > nMax))
186 return;
187
188 dx = x1 - x0; ax = abs(dx) * 2; sx = (dx>0) ? 1 : -1;
189 dy = y1 - y0; ay = abs(dy) * 2; sy = (dy>0) ? 1 : -1;
190 x = x0; y = y0;
191
192 /* x dominant */
193 if (ax > ay)
194 {
195 d = ay - ax / 2;
196 while (1)
197 {
198 pCurve[x] = y;
199 if (x == x1) break;
200 if (d >= 0)
201 {
202 y += sy;
203 d -= ax;
204 }
205 x += sx;
206 d += ay;
207 }
208 }
209
210 /* y dominant */
211 else
212 {
213 d = ax - ay / 2;
214 while (1)
215 {
216 pCurve[x] = y;
217 if (y == y1) break;
218 if (d >= 0)
219 {
220 x += sx;
221 d -= ay;
222 }
223 y += sy;
224 d += ax;
225 }
226 }
227 }
storeRawSample(WacomCommonPtr common,WacomChannelPtr pChannel,WacomDeviceStatePtr ds)228 static void storeRawSample(WacomCommonPtr common, WacomChannelPtr pChannel,
229 WacomDeviceStatePtr ds)
230 {
231 WacomFilterState *fs;
232 int i;
233
234 fs = &pChannel->rawFilter;
235 if (!fs->npoints)
236 {
237 DBG(10, common, "initialize channel data.\n");
238 /* Store initial value over whole average window */
239 for (i=common->wcmRawSample - 1; i>=0; i--)
240 {
241 fs->x[i]= ds->x;
242 fs->y[i]= ds->y;
243 }
244 if (HANDLE_TILT(common) && (ds->device_type == STYLUS_ID ||
245 ds->device_type == ERASER_ID))
246 {
247 for (i=common->wcmRawSample - 1; i>=0; i--)
248 {
249 fs->tiltx[i] = ds->tiltx;
250 fs->tilty[i] = ds->tilty;
251 }
252 }
253 ++fs->npoints;
254 } else {
255 /* Shift window and insert latest sample */
256 for (i=common->wcmRawSample - 1; i>0; i--)
257 {
258 fs->x[i]= fs->x[i-1];
259 fs->y[i]= fs->y[i-1];
260 }
261 fs->x[0] = ds->x;
262 fs->y[0] = ds->y;
263 if (HANDLE_TILT(common) && (ds->device_type == STYLUS_ID ||
264 ds->device_type == ERASER_ID))
265 {
266 for (i=common->wcmRawSample - 1; i>0; i--)
267 {
268 fs->tiltx[i]= fs->tiltx[i-1];
269 fs->tilty[i]= fs->tilty[i-1];
270 }
271 fs->tiltx[0] = ds->tiltx;
272 fs->tilty[0] = ds->tilty;
273 }
274 if (fs->npoints < common->wcmRawSample)
275 ++fs->npoints;
276 }
277 }
278
wcmFilterAverage(int * samples,int n)279 static int wcmFilterAverage(int *samples, int n)
280 {
281 int x = 0;
282 int i;
283
284 for (i = 0; i < n; i++)
285 {
286 x += samples[i];
287 }
288 return x / n;
289 }
290
291 /*****************************************************************************
292 * wcmFilterCoord -- provide noise correction to all transducers
293 ****************************************************************************/
294
wcmFilterCoord(WacomCommonPtr common,WacomChannelPtr pChannel,WacomDeviceStatePtr ds)295 int wcmFilterCoord(WacomCommonPtr common, WacomChannelPtr pChannel,
296 WacomDeviceStatePtr ds)
297 {
298 WacomFilterState *state;
299
300 DBG(10, common, "common->wcmRawSample = %d \n", common->wcmRawSample);
301
302 storeRawSample(common, pChannel, ds);
303
304 state = &pChannel->rawFilter;
305
306 ds->x = wcmFilterAverage(state->x, common->wcmRawSample);
307 ds->y = wcmFilterAverage(state->y, common->wcmRawSample);
308 if (HANDLE_TILT(common) && (ds->device_type == STYLUS_ID ||
309 ds->device_type == ERASER_ID))
310 {
311 ds->tiltx = wcmFilterAverage(state->tiltx, common->wcmRawSample);
312 if (ds->tiltx > common->wcmTiltMaxX)
313 ds->tiltx = common->wcmTiltMaxX;
314 else if (ds->tiltx < common->wcmTiltMinX)
315 ds->tiltx = common->wcmTiltMinX;
316
317 ds->tilty = wcmFilterAverage(state->tilty, common->wcmRawSample);
318 if (ds->tilty > common->wcmTiltMaxY)
319 ds->tilty = common->wcmTiltMaxY;
320 else if (ds->tilty < common->wcmTiltMinY)
321 ds->tilty = common->wcmTiltMinY;
322 }
323
324 return 0; /* lookin' good */
325 }
326
327 /***
328 * Convert a point (X/Y) in a left-handed coordinate system to a normalized
329 * rotation angle.
330 *
331 * This function is currently called for the Intuos4 mouse (cursor) tool
332 * only (to convert tilt to rotation), but it may be used for other devices
333 * in the future.
334 *
335 * Method used: rotation angle is calculated through the atan of x/y
336 * then converted to degrees and normalized into the rotation
337 * range (MIN_ROTATION/MAX_ROTATION).
338
339 * IMPORTANT: calculation inverts direction, the formula to get the target
340 * rotation value in degrees is: 360 - offset - input-angle.
341 *
342 * Example table of return values for an offset of 0, assuming a left-handed
343 * coordinate system:
344 * input 0 degrees: MIN
345 * 90 degrees: MAX - RANGE/4
346 * 180 degrees: RANGE/2
347 * 270 degrees: MIN + RANGE/4
348 *
349 * @param x X coordinate in left-handed coordiante system.
350 * @param y Y coordiante in left-handed coordinate system.
351 * @param offset Custom rotation offset in degrees. Offset is
352 * applied in counterclockwise direction.
353 *
354 * @return The mapped rotation angle based on the device's tilt state.
355 */
wcmTilt2R(int x,int y,double offset)356 int wcmTilt2R(int x, int y, double offset)
357 {
358 double angle = 0.0;
359 int rotation;
360
361 if (x || y)
362 /* rotate in the inverse direction, changing CW to CCW
363 * rotation and vice versa */
364 angle = ((180.0 * atan2(-x, y)) / M_PI);
365
366 /* rotation is now in 0 - 360 deg value range, apply the offset. Use
367 * 360 to avoid getting into negative range, the normalization code
368 * below expects 0...360 */
369 angle = 360 + angle - offset;
370
371 /* normalize into the rotation range (0...MAX), then offset by MIN_ROTATION
372 we used 360 as base offset above, so %= MAX_ROTATION_RANGE brings us back.
373 Note: we can't use xf86ScaleAxis here because of rounding issues.
374 */
375 rotation = round(angle * (MAX_ROTATION_RANGE / 360.0));
376 rotation %= MAX_ROTATION_RANGE;
377
378 /* now scale back from 0...MAX to MIN..(MIN+MAX) */
379 rotation = xf86ScaleAxis(rotation,
380 MIN_ROTATION + MAX_ROTATION_RANGE,
381 MIN_ROTATION,
382 MAX_ROTATION_RANGE, 0);
383
384 return rotation;
385 }
386
387 /* vim: set noexpandtab tabstop=8 shiftwidth=8: */
388