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