HI Mario, This is great! finally a sensible solution to the non-linear calibration.
Sometime ago I wrote a patch that does the nonlinear calibration via a huge 2d calibration grid: https://github.com/rainwoodman/xf86-input-wacom/tree/gridcal The grid was an ugly huge XAtom. A parametrized approach, like your polynomials make much more sense -- just a few bytes to send around. > To fix it I added an opposite distortion with a polynomial function. Each > polynomial depend on 4 parameters. There is 4 polynomials, one per border. > - I didn't manage to make floating point parameters > - I added some functions to solve a linear system but I'm sure that > including a mathematical library should be better. Pulling in a giant mathematical library (e.g. BLAS) into a driver is probably not a good idea. Since solve_lu is called only when the property is set (and the precision doesn't matter much in this context), I think a simple function like what you have is sufficient. > - I'm afraid that my calculus with variables of type double slows the > execution. But I didn't manage to measure it. > I suspect there are already quite a few floating point operations in the linear driver, and usually user apps add even more floating point operations after the event is dispatched. Thus my guess is a few more floating point operations will not make a noticeable difference. > For example on my laptop I use the following parameters : >> xinput set-prop "Wacom ISDv4 EC Pen stylus" --type=int "Wacom Tablet >> Border Distortion Top Y" 100 0 55 68 >> xinput set-prop "Wacom ISDv4 EC Pen stylus" --type=int "Wacom Tablet >> Border Distortion Top X" 80 0 20 26 How did you find these numbers? Did you also write a calibration tool? Otherwise, it may be possible to modify the calibration tool I wrote for the grid to write out the best fit parameters for your polynomials. (at https://github.com/rainwoodman/xf86-input-wacom/blob/gridcal/tools/calibrate.py ) Thanks, Yu > > Yours faithfully, > Mario Geiger > > From efa7dbb3a0bd54e3b58fba9541d54669c1224d5e Mon Sep 17 00:00:00 2001 > From: Mario Geiger <geiger.ma...@gmail.com> > Date: Mon, 24 Aug 2015 23:29:35 +0200 > Subject: [PATCH] distortion > > --- > include/wacom-properties.h | 6 ++ > src/wcmCommon.c | 52 ++++++++-- > src/wcmXCommand.c | 232 > +++++++++++++++++++++++++++++++++++++++++++++ > src/xf86WacomDefs.h | 10 ++ > 4 files changed, 293 insertions(+), 7 deletions(-) > > diff --git a/include/wacom-properties.h b/include/wacom-properties.h > index b845083..e25a944 100644 > --- a/include/wacom-properties.h > +++ b/include/wacom-properties.h > @@ -27,6 +27,12 @@ > /* 32 bit, 4 values, top x, top y, bottom x, bottom y */ > #define WACOM_PROP_TABLET_AREA "Wacom Tablet Area" > > +/* 32 bit, 4 values, border width, border offset, point physical, point > logical */ > +#define WACOM_PROP_TABLET_DISTORTION_TOP_X "Wacom Tablet Border Distortion > Top X" > +#define WACOM_PROP_TABLET_DISTORTION_TOP_Y "Wacom Tablet Border Distortion > Top Y" > +#define WACOM_PROP_TABLET_DISTORTION_BOTTOM_X "Wacom Tablet Border > Distortion Bottom X" > +#define WACOM_PROP_TABLET_DISTORTION_BOTTOM_Y "Wacom Tablet Border > Distortion Bottom Y" > + > /* 8 bit, 1 value, [0 - 3] (NONE, CW, CCW, HALF) */ > #define WACOM_PROP_ROTATION "Wacom Rotation" > > diff --git a/src/wcmCommon.c b/src/wcmCommon.c > index 9408f42..00c5dc5 100644 > --- a/src/wcmCommon.c > +++ b/src/wcmCommon.c > @@ -438,6 +438,21 @@ static void sendCommonEvents(InputInfoPtr pInfo, const > WacomDeviceState* ds, > sendWheelStripEvents(pInfo, ds, first_val, num_vals, valuators); > } > > +static double distortionCorrectionInBorders(double coord, double border, > double* polynomial) > +{ > + if (coord < border) { > + double x = coord; > + int i; > + > + coord = 0.0; > + for (i = 0; i < 5; ++i) { > + coord *= x; > + coord += polynomial[i]; > + } > + } > + return coord; > +} > + > /* rotate x and y before post X inout events */ > void wcmRotateAndScaleCoordinates(InputInfoPtr pInfo, int* x, int* y) > { > @@ -446,19 +461,42 @@ void wcmRotateAndScaleCoordinates(InputInfoPtr pInfo, > int* x, int* y) > DeviceIntPtr dev = pInfo->dev; > AxisInfoPtr axis_x, axis_y; > int tmp_coord; > + double dcoord; > > /* scale into on topX/topY area */ > axis_x = &dev->valuator->axes[0]; > axis_y = &dev->valuator->axes[1]; > > /* Don't try to scale relative axes */ > - if (axis_x->max_value > axis_x->min_value) > - *x = xf86ScaleAxis(*x, axis_x->max_value, axis_x->min_value, > - priv->bottomX, priv->topX); > - > - if (axis_y->max_value > axis_y->min_value) > - *y = xf86ScaleAxis(*y, axis_y->max_value, axis_y->min_value, > - priv->bottomY, priv->topY); > + if (axis_x->max_value > axis_x->min_value) { > + dcoord = (*x - priv->topX) / (double)(priv->bottomX - priv->topX); > + dcoord = distortionCorrectionInBorders(dcoord, > priv->distortion_topX_border, priv->distortion_topX_poly); > + dcoord = 1.0 - dcoord; > + dcoord = distortionCorrectionInBorders(dcoord, > priv->distortion_bottomX_border, priv->distortion_bottomX_poly); > + dcoord = 1.0 - dcoord; > + > + //*x = dcoord * (priv->bottomX - priv->topX) + priv->topX; > + //*x = xf86ScaleAxis(*x, axis_x->max_value, axis_x->min_value, > + // priv->bottomX, priv->topX); > + > + *x = round(dcoord * (axis_x->max_value - axis_x->min_value) + > axis_x->min_value); > + if (*x < axis_x->min_value) *x = axis_x->min_value; > + if (*x > axis_x->max_value) *x = axis_x->max_value; > + } > + > + if (axis_y->max_value > axis_y->min_value) { > + dcoord = (*y - priv->topY) / (double)(priv->bottomY - priv->topY); > + dcoord = distortionCorrectionInBorders(dcoord, > priv->distortion_topY_border, priv->distortion_topY_poly); > + dcoord = 1.0 - dcoord; > + dcoord = distortionCorrectionInBorders(dcoord, > priv->distortion_bottomY_border, priv->distortion_bottomY_poly); > + dcoord = 1.0 - dcoord; > + //*y = dcoord * (priv->bottomY - priv->topY) + priv->topY; > + //*y = xf86ScaleAxis(*y, axis_y->max_value, axis_y->min_value, > + // priv->bottomY, priv->topY); > + *y = round(dcoord * (axis_y->max_value - axis_y->min_value) + > axis_y->min_value); > + if (*y < axis_y->min_value) *y = axis_y->min_value; > + if (*y > axis_y->max_value) *y = axis_y->max_value; > + } > > /* coordinates are now in the axis rage we advertise for the device */ > > diff --git a/src/wcmXCommand.c b/src/wcmXCommand.c > index 346ff61..2a620f0 100644 > --- a/src/wcmXCommand.c > +++ b/src/wcmXCommand.c > @@ -34,6 +34,8 @@ > #endif > > static void wcmBindToSerial(InputInfoPtr pInfo, unsigned int serial); > +static int distortionCorrectionComputePolynomial(double p, double a, double > h, double d, double* poly); > + > > > /***************************************************************************** > * wcmDevSwitchModeCall -- > @@ -82,6 +84,10 @@ int wcmDevSwitchMode(ClientPtr client, DeviceIntPtr dev, > int mode) > static Atom prop_devnode; > static Atom prop_rotation; > static Atom prop_tablet_area; > +static Atom prop_tablet_distortion_topX; > +static Atom prop_tablet_distortion_topY; > +static Atom prop_tablet_distortion_bottomX; > +static Atom prop_tablet_distortion_bottomY; > static Atom prop_pressurecurve; > static Atom prop_serials; > static Atom prop_serial_binding; > @@ -227,6 +233,18 @@ void InitWcmDeviceProperties(InputInfoPtr pInfo) > prop_tablet_area = InitWcmAtom(pInfo->dev, WACOM_PROP_TABLET_AREA, > XA_INTEGER, 32, 4, values); > } > > + if (!IsPad(priv)) { > + /* border_width border_offset point_physical point_logical */ > + values[0] = 0; > + values[1] = 0; > + values[2] = 0; > + values[3] = 0; > + prop_tablet_distortion_topX = InitWcmAtom(pInfo->dev, > WACOM_PROP_TABLET_DISTORTION_TOP_X, XA_INTEGER, 32, 4, values); > + prop_tablet_distortion_topY = InitWcmAtom(pInfo->dev, > WACOM_PROP_TABLET_DISTORTION_TOP_Y, XA_INTEGER, 32, 4, values); > + prop_tablet_distortion_bottomX = InitWcmAtom(pInfo->dev, > WACOM_PROP_TABLET_DISTORTION_BOTTOM_X, XA_INTEGER, 32, 4, values); > + prop_tablet_distortion_bottomY = InitWcmAtom(pInfo->dev, > WACOM_PROP_TABLET_DISTORTION_BOTTOM_Y, XA_INTEGER, 32, 4, values); > + } > + > values[0] = common->wcmRotate; > if (!IsPad(priv)) { > prop_rotation = InitWcmAtom(pInfo->dev, WACOM_PROP_ROTATION, XA_INTEGER, > 8, 1, values); > @@ -717,6 +735,66 @@ int wcmSetProperty(DeviceIntPtr dev, Atom property, > XIPropertyValuePtr prop, > priv->bottomX = values[2]; > priv->bottomY = values[3]; > } > + } else if (property == prop_tablet_distortion_topX) > + { > + INT32 *values = (INT32*)prop->data; > + > + if (prop->size != 4 || prop->format != 32) > + return BadValue; > + > + if (!checkonly) > + { > + /* border_width border_offset point_physical point_logical */ > + priv->distortion_topX_border = values[0]/1000.0; > + distortionCorrectionComputePolynomial( > + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0, > + priv->distortion_topX_poly); > + } > + } else if (property == prop_tablet_distortion_topY) > + { > + INT32 *values = (INT32*)prop->data; > + > + if (prop->size != 4 || prop->format != 32) > + return BadValue; > + > + if (!checkonly) > + { > + /* border_width border_offset point_physical point_logical */ > + priv->distortion_topY_border = values[0]/1000.0; > + distortionCorrectionComputePolynomial( > + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0, > + priv->distortion_topY_poly); > + } > + } else if (property == prop_tablet_distortion_bottomX) > + { > + INT32 *values = (INT32*)prop->data; > + > + if (prop->size != 4 || prop->format != 32) > + return BadValue; > + > + if (!checkonly) > + { > + /* border_width border_offset point_physical point_logical */ > + priv->distortion_bottomX_border = values[0]/1000.0; > + distortionCorrectionComputePolynomial( > + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0, > + priv->distortion_bottomX_poly); > + } > + } else if (property == prop_tablet_distortion_bottomY) > + { > + INT32 *values = (INT32*)prop->data; > + > + if (prop->size != 4 || prop->format != 32) > + return BadValue; > + > + if (!checkonly) > + { > + /* border_width border_offset point_physical point_logical */ > + priv->distortion_bottomY_border = values[0]/1000.0; > + distortionCorrectionComputePolynomial( > + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0, > + priv->distortion_bottomY_poly); > + } > } else if (property == prop_pressurecurve) > { > INT32 *pcurve; > @@ -1073,4 +1151,158 @@ wcmBindToSerial(InputInfoPtr pInfo, unsigned int > serial) > > } > > +/* P A = L U > + * P : permutation matrix > + * L : lower matrix > + * U : upper matrix > +*/ > +static void > +lu_decomposition(int n, const double* A, double* P, double* L, double* U) > +{ > + int k,i,r; > + double m; > + > + // P,L = identity > + for (i = 0; i < n*n; ++i) P[i] = L[i] = 0.0; > + for (i = 0; i < n*n; i += n+1) P[i] = L[i] = 1.0; > + > + // U = A > + for (i = 0; i < n*n; ++i) U[i] = A[i]; > + > + for (k = 0; k < n; ++k) { > + // find maximum in column k > + r = k; > + m = U[r*n+k]; > + for (i = k; i < n; ++i) if (U[i*n+k] > m) { > + r = i; > + m = U[i*n+k]; > + } > + > + if (m > 0.0) { > + // swap lines r,k > + if (r != k) { > + for (i = 0; i < n; ++i) { > + m = U[r*n+i]; > + U[r*n+i] = U[k*n+i]; > + U[k*n+i] = m; > + m = P[r*n+i]; > + P[r*n+i] = P[k*n+i]; > + P[k*n+i] = m; > + } > + for (i = 0; i < k; ++i) { > + m = L[r*n+i]; > + L[r*n+i] = L[k*n+i]; > + L[k*n+i] = m; > + } > + } > + > + // make 0's > + for (i = k+1; i < n; ++i) { > + L[i*n+k] = U[i*n+k] / U[k*n+k]; > + for (r = 0; r < n; ++r) U[i*n+r] -= L[i*n+k] * U[k*n+r]; > + } > + } > + } > +} > + > +/* LUx = b */ > +static int > +solve_lu(int n, const double* L, const double* U, const double* b, double* > x) > +{ > + int k, i; > + double s; > + double* y = (double*)malloc(sizeof(double)*n); > + > + // Ly = b > + for (k = 0; k < n; ++k) { > + s = 0.0; > + for (i = 0; i < k; ++i) s += L[k*n+i] * y[i]; > + y[k] = b[k] - s; > + } > + > + // Ux = y > + for (k = n-1; k >= 0; --k) { > + s = 0.0; > + for (i = n-1; i > k; --i) s += U[k*n+i] * x[i]; > + if (U[k*n+k] == 0.0) { > + free(y); > + return 1; > + } > + x[k] = (y[k] - s) / U[k*n+k]; > + } > + free(y); > + return 0; > +} > + > + > +/* Ax = b */ > +static int > +solve_ls(int n, const double* A, const double* b, double* x) > +{ > + int i,k; > + double *P,*L,*U,*Pb; > + P = (double*)malloc(sizeof(double)*n*n); > + L = (double*)malloc(sizeof(double)*n*n); > + U = (double*)malloc(sizeof(double)*n*n); > + Pb = (double*)malloc(sizeof(double)*n); > + lu_decomposition(n, A, P, L, U); > + for (i = 0; i < n; ++i) { > + Pb[i] = 0; > + for (k = 0; k < n; ++k) { > + Pb[i] += P[i*n+k] * b[k]; > + } > + } > + i = solve_lu(n, L, U, Pb, x); > + free(P); > + free(L); > + free(U); > + free(Pb); > + return i; > +} > + > +static int > +distortionCorrectionComputePolynomial(double p, double a, double h, double > d, double* poly) > +{ > + /* p = offset at the border > + * (a,h) = dirtortion point > + * d = width of the border zone > + * > + * poly = {c4, c3... c0} > + */ > + > + /* F(x) = c4 x^4 + c3 x^3 + c2 x^2 + c1 x + c0 > + * > + * Constraints => Matrix > + * F(0) = p => 0 0 0 0 1 : p > + * F(d) = d => d^4 d^3 d^2 d 1 : d > + * F(a) = h => a^4 a^3 a^2 a 1 : h > + * F'(d) = 1 => 4d^3 3d^2 2d 1 0 : 1 > + * F'(a) = 1 => 4a^3 3a^2 2a 1 0 : 1 > + */ > + int r; > + > + const double Matrix[25] = { > + 0, 0, 0, 0, 1, > + d*d*d*d, d*d*d, d*d, d, 1, > + a*a*a*a, a*a*a, a*a, a, 1, > + 4*d*d*d, 3*d*d, 2*d, 1, 0, > + 4*a*a*a, 3*a*a, 2*a, 1, 0 > + }; > + const double rhs[5] = { > + p, > + d, > + h, > + 1, > + 1 > + }; > + > + r = solve_ls(5, Matrix, rhs, poly); > + if (r != 0) { > + // set to F(x) = x > + poly[0] = poly[1] = poly[2] = poly[4] = 0.0; > + poly[3] = 1.0; > + } > + return r; > +} > + > /* vim: set noexpandtab tabstop=8 shiftwidth=8: */ > diff --git a/src/xf86WacomDefs.h b/src/xf86WacomDefs.h > index 1575960..45679ec 100644 > --- a/src/xf86WacomDefs.h > +++ b/src/xf86WacomDefs.h > @@ -262,6 +262,16 @@ struct _WacomDeviceRec > unsigned int cur_serial; /* current serial in prox */ > int cur_device_id; /* current device ID in prox */ > > + /* distortion */ > + double distortion_topX_border; > + double distortion_topY_border; > + double distortion_bottomX_border; > + double distortion_bottomY_border; > + double distortion_topX_poly[5]; > + double distortion_topY_poly[5]; > + double distortion_bottomX_poly[5]; > + double distortion_bottomY_poly[5]; > + > /* button mapping information > * > * 'button' variables are indexed by physical button number (0..nbuttons) > -- > 2.1.4 > > > ------------------------------------------------------------------------------ > > _______________________________________________ > Linuxwacom-devel mailing list > Linuxwacom-devel@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/linuxwacom-devel > ------------------------------------------------------------------------------ _______________________________________________ Linuxwacom-devel mailing list Linuxwacom-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/linuxwacom-devel