mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-21 22:47:50 +00:00
event_filter: transformation of motion coordinates
The <transform> 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
This commit is contained in:
parent
7304a019e7
commit
5f2691a65b
@ -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.
|
||||
|
||||
:<transform>:
|
||||
|
||||
Transforms touch and absolute-motion event coordinates by a sequence of
|
||||
the following primitives expressed as sub-nodes.
|
||||
|
||||
:<translate>:
|
||||
|
||||
Moves coordinates by 'x' and 'y' attributes.
|
||||
|
||||
:<scale>:
|
||||
|
||||
Scales horizontal and vertical axis by 'x' and 'y' attributes.
|
||||
|
||||
:<rotate>:
|
||||
|
||||
Rotates clockwise (around origin (0,0) by 90, 180, or 270 degrees in
|
||||
attribute 'angle'.
|
||||
|
||||
:<reorient>:
|
||||
|
||||
Rotates clockwise (like 'rotate') but also translates results for
|
||||
desired target 'width' and 'height' attributes.
|
||||
|
||||
:<hflip>/<vflip>:
|
||||
|
||||
Flip coordinates horizontally/vertically and translate into
|
||||
'width'/'height'.
|
||||
|
||||
|
||||
Character generator rules
|
||||
-------------------------
|
||||
|
202
repos/os/src/server/event_filter/affine_transform.h
Normal file
202
repos/os/src/server/event_filter/affine_transform.h
Normal file
@ -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_ */
|
@ -27,6 +27,7 @@
|
||||
#include <log_source.h>
|
||||
#include <touch_click_source.h>
|
||||
#include <touch_key_source.h>
|
||||
#include <transform_source.h>
|
||||
#include <event_session.h>
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
141
repos/os/src/server/event_filter/transform_source.h
Normal file
141
repos/os/src/server/event_filter/transform_source.h
Normal file
@ -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 <source.h>
|
||||
#include <affine_transform.h>
|
||||
|
||||
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_ */
|
Loading…
Reference in New Issue
Block a user