[PATCH libinput 07/11] evdev: switch to a normalized transformation matrix

Peter Hutterer peter.hutterer at who-t.net
Tue Aug 26 21:31:38 PDT 2014


The big change here is the requirement to have the translation component in a
device-normalized coordinate space. Without that, we cannot reliably rotate as
the coordinate space is effectively unknown and may differ between the axes.
This affects any rotation matrix or translation matrix, pure scale matrices
were working just fine since they're unit-less.

Requiring the matrix in device-normalized space makes it possible for libinput
to rotate or otherwise handle the matrix independent of the screen resolution.
The rotation matrix is documented in a bit more detail to make it easier for
users to figure it out.

This changes the definition of the WL_CALIBRATION property (which is currently
broken).

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 src/evdev.c    | 71 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 src/evdev.h    |  2 +-
 src/libinput.h | 26 +++++++++++++++++++++
 3 files changed, 85 insertions(+), 14 deletions(-)

diff --git a/src/evdev.c b/src/evdev.c
index a029887..4cd3cfa 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -150,20 +150,10 @@ evdev_device_led_update(struct evdev_device *device, enum libinput_led leds)
 static void
 transform_absolute(struct evdev_device *device, int32_t *x, int32_t *y)
 {
-	int32_t tx, ty;
-
 	if (!device->abs.apply_calibration)
 		return;
 
-	tx = *x * device->abs.calibration[0] +
-		*y * device->abs.calibration[1] +
-		device->abs.calibration[2];
-
-	ty = *x * device->abs.calibration[3] +
-		*y * device->abs.calibration[4] +
-		device->abs.calibration[5];
-	*x = tx;
-	*y = ty;
+	matrix_mult_vec(&device->abs.calibration, x, y);
 }
 
 static inline double
@@ -913,6 +903,8 @@ evdev_device_create(struct libinput_seat *seat,
 	device->pending_event = EVDEV_NONE;
 	device->devname = libevdev_get_name(device->evdev);
 
+	matrix_init_identity(&device->abs.calibration);
+
 	if (evdev_configure_device(device) == -1)
 		goto err;
 
@@ -986,8 +978,61 @@ void
 evdev_device_calibrate(struct evdev_device *device,
 		       const float calibration[6])
 {
-	device->abs.apply_calibration = 1;
-	memcpy(device->abs.calibration, calibration, sizeof device->abs.calibration);
+	struct matrix scale,
+		      translate,
+		      transform;
+	double sx, sy;
+
+	matrix_from_farray6(&transform, calibration);
+	device->abs.apply_calibration = !matrix_is_identity(&transform);
+
+	if (!device->abs.apply_calibration) {
+		matrix_init_identity(&device->abs.calibration);
+		return;
+	}
+
+	sx = device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum + 1;
+	sy = device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum + 1;
+
+	/* The transformation matrix is in the form:
+	 *  [ a b c ]
+	 *  [ d e f ]
+	 *  [ 0 0 1 ]
+	 * Where a, e are the scale components, a, b, d, e are the rotation
+	 * component (combined with scale) and c and f are the translation
+	 * component. The translation component in the input matrix must be
+	 * normalized to multiples of the device width and height,
+	 * respectively. e.g. c == 1 shifts one device-width to the right.
+	 *
+	 * We pre-calculate a single matrix to apply to event coordinates:
+	 *     M = Un-Normalize * Calibration * Normalize
+	 *
+	 * Normalize: scales the device coordinates to [0,1]
+	 * Calibration: user-supplied matrix
+	 * Un-Normalize: scales back up to device coordinates
+	 * Matrix maths requires the normalize/un-normalize in reverse
+	 * order.
+	 */
+
+	/* Un-Normalize */
+	matrix_init_translate(&translate,
+			      device->abs.absinfo_x->minimum,
+			      device->abs.absinfo_y->minimum);
+	matrix_init_scale(&scale, sx, sy);
+	matrix_mult(&scale, &translate, &scale);
+
+	/* Calibration */
+	matrix_mult(&transform, &scale, &transform);
+
+	/* Normalize */
+	matrix_init_translate(&translate,
+			      -device->abs.absinfo_x->minimum/sx,
+			      -device->abs.absinfo_y->minimum/sy);
+	matrix_init_scale(&scale, 1.0/sx, 1.0/sy);
+	matrix_mult(&scale, &translate, &scale);
+
+	/* store final matrix in device */
+	matrix_mult(&device->abs.calibration, &transform, &scale);
 }
 
 int
diff --git a/src/evdev.h b/src/evdev.h
index 6aa98f5..9196bd2 100644
--- a/src/evdev.h
+++ b/src/evdev.h
@@ -73,7 +73,7 @@ struct evdev_device {
 		int32_t seat_slot;
 
 		int apply_calibration;
-		float calibration[6];
+		struct matrix calibration;
 	} abs;
 
 	struct {
diff --git a/src/libinput.h b/src/libinput.h
index 04645a7..82970e2 100644
--- a/src/libinput.h
+++ b/src/libinput.h
@@ -1374,6 +1374,32 @@ libinput_device_get_keys(struct libinput_device *device,
  * [ d  e  f ] * [ y ]
  * [ 0  0  1 ]   [ 1 ]
  * @endcode
+ *
+ * The translation component (c, f) is expected to be normalized to the
+ * device coordinate range. For example, the matrix
+ * @code
+ * [ 1 0  1 ]
+ * [ 0 1 -1 ]
+ * [ 0 0  1 ]
+ * @endcode
+ * moves all coordinates by 1 device-width to the right and 1 device-height
+ * up.
+ *
+ * The rotation matrix for rotation around the origin is defined as
+ * @code
+ * [ cos(a) -sin(a) 0 ]
+ * [ sin(a)  cos(a) 0 ]
+ * [   0      0     1 ]
+ * @endcode
+ * Note that any rotation requires an additional translation component to
+ * translate the rotated coordinates back into the original device space.
+ * The rotation matrixes for 90, 180 and 270 degrees clockwise are:
+ * @code
+ * 90 deg cw:		180 deg cw:		270 deg cw:
+ * [ 0 -1 1]		[ -1  0 1]		[  0 1 0 ]
+ * [ 1  0 0]		[  0 -1 1]		[ -1 0 1 ]
+ * [ 0  0 1]		[  0  0 1]		[  0 0 1 ]
+ * @endcode
  */
 void
 libinput_device_calibrate(struct libinput_device *device,
-- 
1.9.3



More information about the wayland-devel mailing list