From 5f2691a65b2c89de9f20eb29e03170cb13efcd85 Mon Sep 17 00:00:00 2001 From: Christian Helmuth Date: Mon, 29 Jan 2024 17:01:24 +0100 Subject: [PATCH] event_filter: transformation of motion coordinates The filter configurably transforms touch and absolute-motion event coordinates by a sequence of translation (move), scaling, rotation, and flipping primitives in sub-nodes. Issue #5105 --- repos/os/src/server/event_filter/README | 28 +++ .../server/event_filter/affine_transform.h | 202 ++++++++++++++++++ repos/os/src/server/event_filter/main.cc | 4 + repos/os/src/server/event_filter/source.h | 1 + .../server/event_filter/transform_source.h | 141 ++++++++++++ 5 files changed, 376 insertions(+) create mode 100644 repos/os/src/server/event_filter/affine_transform.h create mode 100644 repos/os/src/server/event_filter/transform_source.h diff --git a/repos/os/src/server/event_filter/README b/repos/os/src/server/event_filter/README index bc09fb3d0d..a2a90a1c0d 100644 --- a/repos/os/src/server/event_filter/README +++ b/repos/os/src/server/event_filter/README @@ -96,6 +96,34 @@ one of the following filters: rectangular area - using the attributes 'xpos', 'ypos', 'width', and 'height' - and the name of the tapped key as 'key' attribute. +:: + + Transforms touch and absolute-motion event coordinates by a sequence of + the following primitives expressed as sub-nodes. + + :: + + Moves coordinates by 'x' and 'y' attributes. + + :: + + Scales horizontal and vertical axis by 'x' and 'y' attributes. + + :: + + Rotates clockwise (around origin (0,0) by 90, 180, or 270 degrees in + attribute 'angle'. + + :: + + Rotates clockwise (like 'rotate') but also translates results for + desired target 'width' and 'height' attributes. + + :/: + + Flip coordinates horizontally/vertically and translate into + 'width'/'height'. + Character generator rules ------------------------- diff --git a/repos/os/src/server/event_filter/affine_transform.h b/repos/os/src/server/event_filter/affine_transform.h new file mode 100644 index 0000000000..290909c07c --- /dev/null +++ b/repos/os/src/server/event_filter/affine_transform.h @@ -0,0 +1,202 @@ +/* + * \brief Affine transform of 2-D coordinates + * \author Christian Helmuth + * \date 2024-01-28 + * + * Affine transformation of coordinate (vector) x to y is the combination of + * two operations. + * + * - linear map - matrix multiplication (A) + * - translation - vector addition (b) + * + * y = A*x + b + * + * Both operations can be combined by using an augmented matrix that includes + * the translation vector. A sequence of transformations can be combined into a + * single transformation matrix by multiplication of respective matrices. + */ + +/* + * Copyright (C) 2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _EVENT_FILTER__AFFINE_TRANSFORM_H_ +#define _EVENT_FILTER__AFFINE_TRANSFORM_H_ + + +namespace Transform { + struct Matrix; + struct Point; + + enum Angle { ANGLE_0, ANGLE_90, ANGLE_180, ANGLE_270 }; + + static Angle angle_from_degrees(unsigned degrees) + { + switch (degrees) { + case 90: return ANGLE_90; + case 180: return ANGLE_180; + case 270: return ANGLE_270; + default: return ANGLE_0; + } + } +} + +/* + * Augmented transformation matrix + * + * | v11 v12 v13 | + * | v21 v22 v23 | + * | 0 0 1 | + */ +struct Transform::Matrix +{ + float v11, v12, v13, + v21, v22, v23; + + /* + * Identity transform (initial matrix) + */ + static Matrix identity() + { + return { .v11 = 1, .v12 = 0, .v13 = 0, + .v21 = 0, .v22 = 1, .v23 = 0 }; + } + + Matrix mul(Matrix const &t) const + { + return { + .v11 = t.v11 * v11 + t.v12 * v21 + t.v13 * 0, + .v12 = t.v11 * v12 + t.v12 * v22 + t.v13 * 0, + .v13 = t.v11 * v13 + t.v12 * v23 + t.v13 * 1, + .v21 = t.v21 * v11 + t.v22 * v21 + t.v23 * 0, + .v22 = t.v21 * v12 + t.v22 * v22 + t.v23 * 0, + .v23 = t.v21 * v13 + t.v22 * v23 + t.v23 * 1 + }; + } + + /* + * Translation by (x,y) + */ + Matrix translate(float x, float y) const + { + return mul(Matrix { 1, 0, x, + 0, 1, y}); + } + + /* + * Scaling by (x,y) + */ + Matrix scale(float x, float y) const + { + return mul(Matrix { x, 0, 0, + 0, y, 0}); + } + + /* + * Rotation clock-wise by 90, 180, 270 degrees + */ + Matrix rotate(Angle angle) const + { + /* + * 90° 180° 270° + * cos 0 -1 0 + * sin 1 0 -1 + */ + switch (angle) { + case ANGLE_90: + return mul(Matrix { 0, -1, 0, + 1, 0, 0 }); + case ANGLE_180: + return mul(Matrix { -1, 0, 0, + 0, -1, 0 }); + case ANGLE_270: + return mul(Matrix { 0, 1, 0, + -1, 0, 0 }); + default: + return *this; + } + } + + /* + * Reflection on (vertical) y-axis + */ + Matrix reflect_vertical_axis() const + { + return mul(Matrix { -1, 0, 0, + 0, 1, 0}); + } + + /* + * Reflection on (horizontal) x-axis + */ + Matrix reflect_horizontal_axis() const + { + return mul(Matrix { 1, 0, 0, + 0, -1, 0}); + } + + /* + * Rotate and adjust origin + */ + Matrix reorient(Angle angle, float width, float height) const + { + switch (angle) { + case ANGLE_90: return rotate(angle).translate(width - 1, 0); + case ANGLE_180: return rotate(angle).translate(width - 1, height - 1); + case ANGLE_270: return rotate(angle).translate(0, height - 1); + default: return *this; + } + } + + /* + * Flip (in vertical direction) and adjust origin + */ + Matrix vflip(float height) const + { + return reflect_horizontal_axis().translate(0, height - 1); + } + + /* + * Flip (in horizontal direction) and adjust origin + */ + Matrix hflip(float width) const + { + return reflect_vertical_axis().translate(width - 1, 0); + } +}; + + +/* + * Point as augmented vector + * + * | x | + * | y | + * | 1 | + */ +struct Transform::Point +{ + float x, y; + + Point transform(Matrix const &t) const + { + return { + .x = t.v11 * x + t.v12 * y + t.v13, + .y = t.v21 * x + t.v22 * y + t.v23, + }; + } + + int int_x() const + { + return x >= 0 ? int(x + 0.5) : -int(-x + 0.5); + } + + int int_y() const + { + return y >= 0 ? int(y + 0.5) : -int(-y + 0.5); + } +}; + +#endif /* _EVENT_FILTER__AFFINE_TRANSFORM_H_ */ diff --git a/repos/os/src/server/event_filter/main.cc b/repos/os/src/server/event_filter/main.cc index c9fae9fa7c..b218b4237a 100644 --- a/repos/os/src/server/event_filter/main.cc +++ b/repos/os/src/server/event_filter/main.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace Event_filter { struct Main; } @@ -252,6 +253,9 @@ struct Event_filter::Main : Source::Factory, Source::Trigger if (node.type() == Log_source::name()) return *new (_heap) Log_source(owner, node, *this); + if (node.type() == Transform_source::name()) + return *new (_heap) Transform_source(owner, node, *this); + if (node.type() == Touch_click_source::name()) return *new (_heap) Touch_click_source(owner, node, *this); diff --git a/repos/os/src/server/event_filter/source.h b/repos/os/src/server/event_filter/source.h index 4ecda4c7b7..054d50bc5a 100644 --- a/repos/os/src/server/event_filter/source.h +++ b/repos/os/src/server/event_filter/source.h @@ -48,6 +48,7 @@ class Event_filter::Source || node.type() == "button-scroll" || node.type() == "accelerate" || node.type() == "log" + || node.type() == "transform" || node.type() == "touch-click" || node.type() == "touch-key"; diff --git a/repos/os/src/server/event_filter/transform_source.h b/repos/os/src/server/event_filter/transform_source.h new file mode 100644 index 0000000000..e1053e10b9 --- /dev/null +++ b/repos/os/src/server/event_filter/transform_source.h @@ -0,0 +1,141 @@ +/* + * \brief Input-event source that transforms motion events + * \author Christian Helmuth + * \date 2024-01-28 + * + * This filter configurably transforms touch and absolute-motion event + * coordinates by a sequence of translation, scaling, rotation, and flipping + * primitives. + */ + +/* + * Copyright (C) 2024 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +#ifndef _EVENT_FILTER__TRANSFORM_SOURCE_H_ +#define _EVENT_FILTER__TRANSFORM_SOURCE_H_ + +/* local includes */ +#include +#include + +namespace Event_filter { class Transform_source; } + + +class Event_filter::Transform_source : public Source, Source::Filter +{ + private: + + Owner _owner; + + Source &_source; + + Transform::Matrix _transform = Transform::Matrix::identity(); + + void _apply_config(Xml_node const config) + { + config.for_each_sub_node([&] (Xml_node node) { + if (node.has_type("translate")) { + _transform = _transform.translate( + float(node.attribute_value("x", 0.0)), + float(node.attribute_value("y", 0.0))); + return; + } + if (node.has_type("scale")) { + _transform = _transform.scale( + float(node.attribute_value("x", 1.0)), + float(node.attribute_value("y", 1.0))); + return; + } + if (node.has_type("rotate")) { + unsigned degrees = node.attribute_value("angle", 0); + Transform::Angle angle = + Transform::angle_from_degrees(degrees); + + if (angle == Transform::ANGLE_0) + warning("invalid transform rotate(", degrees, ")"); + else + _transform = _transform.rotate(angle); + return; + } + if (node.has_type("hflip")) { + float width = float(node.attribute_value("width", -1.0)); + + if (width <= 0) + warning("invalid transform hflip"); + else + _transform = _transform.hflip(width); + return; + } + if (node.has_type("vflip")) { + float height = float(node.attribute_value("height", -1.0)); + + if (height <= 0) + warning("invalid transform vflip"); + else + _transform = _transform.vflip(height); + return; + } + if (node.has_type("reorient")) { + float width = float(node.attribute_value("width", -1.0)); + float height = float(node.attribute_value("height", -1.0)); + unsigned degrees = node.attribute_value("angle", 0); + + Transform::Angle angle = + Transform::angle_from_degrees(degrees); + + if (width <= 0 || height <= 0 || angle == Transform::ANGLE_0) + warning("invalid transform reorient"); + else + _transform = _transform.reorient(angle, width, height); + return; + } + }); + } + + /** + * Filter interface + */ + void filter_event(Sink &destination, Input::Event const &event) override + { + if (!event.absolute_motion() && !event.touch()) { + destination.submit(event); + return; + } + + event.handle_touch([&] (Input::Touch_id id, float x, float y) { + Transform::Point p { x, y }; + p = p.transform(_transform); + destination.submit(Input::Touch { id, p.x, p.y }); + }); + + event.handle_absolute_motion([&] (int x, int y) { + Transform::Point p { float(x), float(y) }; + p = p.transform(_transform); + destination.submit(Input::Absolute_motion { p.int_x(), p.int_y() }); + }); + } + + public: + + static char const *name() { return "transform"; } + + Transform_source(Owner &owner, Xml_node config, Source::Factory &factory) + : + Source(owner), + _owner(factory), + _source(factory.create_source(_owner, input_sub_node(config))) + { + _apply_config(config); + } + + void generate(Sink &destination) override + { + Source::Filter::apply(destination, *this, _source); + } +}; + +#endif /* _EVENT_FILTER__TRANSFORM_SOURCE_H_ */