From 13972efd82813bdb0089b0234fec9c3700638d0d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 26 Aug 2014 10:34:05 +1000 Subject: [PATCH] evdev: switch to a normalized transformation matrix 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 Reviewed-by: Hans de Goede --- 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 a0298872..4cd3cfaa 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 6aa98f54..9196bd20 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 04645a7e..82970e25 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, -- 2.34.1