[PATCH xf86-input-libinput 2/2] Implement stylus pressure curve support

Peter Hutterer peter.hutterer at who-t.net
Mon Oct 31 05:26:33 UTC 2016


Takes a 4-point cubic bezier curve as input and maps the pressure coordinates
to the values outlined by this curve. This is an extension of the current
implementation in the xf86-input-wacom driver which only allows the two center
control points to be modified.

Over the years a few users have noted that the wacom driver's pressure curve
makes it impossible to cap the pressure at a given value. Given our bezier
implementation here, it's effectively a freebie to add configurability of the
first and last control points. We do require all control points' x coordinates
to be in ascending order.

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
 include/libinput-properties.h |   7 ++
 man/libinput.man              |  31 +++++++
 src/Makefile.am               |   2 +-
 src/bezier.c                  |   7 ++
 src/bezier.h                  |   2 +
 src/xf86libinput.c            | 184 +++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 230 insertions(+), 3 deletions(-)

diff --git a/include/libinput-properties.h b/include/libinput-properties.h
index 8c6942d..f76500f 100644
--- a/include/libinput-properties.h
+++ b/include/libinput-properties.h
@@ -183,4 +183,11 @@
 /* Device rotation: FLOAT, 1 value, 32 bit, read-only */
 #define LIBINPUT_PROP_ROTATION_ANGLE_DEFAULT "libinput Rotation Angle Default"
 
+/* Tablet tool pressure curve: float, 8 values, 32 bit
+ * Value range is [0.0, 1.0], the values specify the x/y of the four
+ * control points for a cubic bezier curve.
+ * Default value: 0.0 0.0 0.0 0.0 1.0 1.0 1.0 1.0
+ */
+#define LIBINPUT_PROP_TABLET_TOOL_PRESSURECURVE "libinput Tablet Tool Pressurecurve"
+
 #endif /* _LIBINPUT_PROPERTIES_H_ */
diff --git a/man/libinput.man b/man/libinput.man
index 36775e6..c1655a7 100644
--- a/man/libinput.man
+++ b/man/libinput.man
@@ -155,6 +155,12 @@ default scroll option for this device is used.
 Sets the send events mode to disabled, enabled, or "disable when an external
 mouse is connected".
 .TP 7
+.BI "Option \*qTabletToolPressureCurve\*q \*q" "x0/y0 x1/y1 x2/y2 x3/y3" \*q
+Set the pressure curve for a tablet stylus to the bezier formed by the four
+points. The respective x/y coordinate must be in the [0.0, 1.0] range. For
+more information see section
+.B TABLET STYLUS PRESSURE CURVE.
+.TP 7
 .BI "Option \*qTapping\*q \*q" bool \*q
 Enables or disables tap-to-click behavior.
 .TP 7
@@ -252,6 +258,11 @@ on this device.
 "disabled-on-external-mouse". Indicates which send-event modes is currently
 enabled on this device.
 .TP 7
+.BI "libinput Tablet Tool Pressurecurve"
+4 32-bit float values [0.0 to 1.0]. See section
+.B TABLET TOOL PRESSURE CURVE
+for more information.
+.TP 7
 .BI "libinput Tapping Enabled"
 1 boolean value (8 bit, 0 or 1). 1 enables tapping
 .TP 7
@@ -313,6 +324,26 @@ and only the target button events are sent.
 .TP
 This feature is provided by this driver, not by libinput.
 
+.SH TABLET TOOL PRESSURECURVE
+The pressure curve affects how stylus pressure is reported. By default, the
+hardware pressure is reported as-is and (usually). By setting a pressure curve,
+the feel of the stylus can be adjusted to be more like e.g. a pencil or a
+brush.
+.PP
+The pressure curve is a cubic Bezier curve, drawn within a normalized range
+of 0.0 to 1.0 between the four points provided. This normalized range is
+applied to the tablet's pressure input so that the highest pressure maps to
+1.0. The points must have increasing x coordinates, if x0 is larger than 0.0
+all pressure values lower than x0 are equivalent to y0. If x3 is less than
+1.0, all pressure values higher than x3  are equivalent to y3.
+
+The input for a linear  curve  (default) is  "0.0/0.0 0.0/0.0 1.0/1.0 1.0/1.0";
+a slightly
+depressed curve (firmer) might be "0.0/0.0 0.05/0.0 1.0/0.95 1.0/1.0"; a slightly raised
+curve (softer) might  be "0.0/0.0 0.0/0.05 0.95/1.0 1.0/1.0".
+.TP
+This feature is provided by this driver, not by libinput.
+
 .SH AUTHORS
 Peter Hutterer
 .SH "SEE ALSO"
diff --git a/src/Makefile.am b/src/Makefile.am
index 8634b69..5dcb55e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -30,7 +30,7 @@ AM_CPPFLAGS =-I$(top_srcdir)/include $(LIBINPUT_CFLAGS)
 
 @DRIVER_NAME at _drv_la_LTLIBRARIES = @DRIVER_NAME at _drv.la
 @DRIVER_NAME at _drv_la_LDFLAGS = -module -avoid-version
- at DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS) libdraglock.la -lm
+ at DRIVER_NAME@_drv_la_LIBADD = $(LIBINPUT_LIBS) libdraglock.la libbezier.la -lm
 @DRIVER_NAME at _drv_ladir = @inputdir@
 
 @DRIVER_NAME at _drv_la_SOURCES = xf86libinput.c
diff --git a/src/bezier.c b/src/bezier.c
index e571a3c..95af16a 100644
--- a/src/bezier.c
+++ b/src/bezier.c
@@ -31,6 +31,13 @@
 
 #include "bezier.h"
 
+const struct bezier_control_point bezier_defaults[4] = {
+	{ 0.0, 0.0 },
+	{ 0.0, 0.0 },
+	{ 1.0, 1.0 },
+	{ 1.0, 1.0 },
+};
+
 struct point {
 	int x, y;
 };
diff --git a/src/bezier.h b/src/bezier.h
index c7f3b27..7406565 100644
--- a/src/bezier.h
+++ b/src/bezier.h
@@ -35,6 +35,8 @@ struct bezier_control_point {
 	double x, y;
 };
 
+extern const struct bezier_control_point bezier_defaults[4];
+
 /**
  * Given four control points in the range [(0.0/0.0), (1.0/1.0)]
  * construct a Bézier curve.
diff --git a/src/xf86libinput.c b/src/xf86libinput.c
index 07b4d4e..470b1ff 100644
--- a/src/xf86libinput.c
+++ b/src/xf86libinput.c
@@ -42,6 +42,7 @@
 
 #include <X11/Xatom.h>
 
+#include "bezier.h"
 #include "draglock.h"
 #include "libinput-properties.h"
 
@@ -162,6 +163,7 @@ struct xf86libinput {
 		BOOL horiz_scrolling_enabled;
 
 		float rotation_angle;
+		struct bezier_control_point pressurecurve[4];
 	} options;
 
 	struct draglock draglock;
@@ -172,6 +174,13 @@ struct xf86libinput {
 	struct libinput_tablet_tool *tablet_tool;
 
 	bool allow_mode_group_updates;
+
+	/* Pre-calculated pressure curve.
+	   In the 0...TABLET_AXIS_MAX range */
+	struct {
+		int *values;
+		size_t sz;
+	} pressurecurve;
 };
 
 enum event_handling {
@@ -382,6 +391,30 @@ xf86libinput_shared_is_enabled(struct xf86libinput_device *shared_device)
 	return shared_device->enabled_count > 0;
 }
 
+static inline bool
+xf86libinput_set_pressurecurve(struct xf86libinput *driver_data,
+			       const struct bezier_control_point controls[4])
+{
+	if (memcmp(controls, bezier_defaults, sizeof(bezier_defaults)) == 0) {
+		free(driver_data->pressurecurve.values);
+		driver_data->pressurecurve.values = NULL;
+		return true;
+	}
+
+	if (!driver_data->pressurecurve.values) {
+		int *vals = calloc(TABLET_PRESSURE_AXIS_MAX + 1, sizeof(int));
+		if (!vals)
+			return false;
+
+		driver_data->pressurecurve.values = vals;
+		driver_data->pressurecurve.sz = TABLET_PRESSURE_AXIS_MAX + 1;
+	}
+
+	return cubic_bezier(controls,
+			    driver_data->pressurecurve.values,
+			    driver_data->pressurecurve.sz);
+}
+
 static int
 LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
                  BOOL checkonly);
@@ -1626,8 +1659,9 @@ xf86libinput_handle_tablet_axis(InputInfoPtr pInfo,
 	tool = libinput_event_tablet_tool_get_tool(event);
 
 	if (libinput_tablet_tool_has_pressure(tool)) {
-		value = libinput_event_tablet_tool_get_pressure(event);
-		value *= TABLET_PRESSURE_AXIS_MAX;
+		value = TABLET_PRESSURE_AXIS_MAX * libinput_event_tablet_tool_get_pressure(event);
+		if (driver_data->pressurecurve.values)
+			value = driver_data->pressurecurve.values[(int)value];
 		valuator_mask_set_double(mask, 2, value);
 	}
 
@@ -2606,6 +2640,69 @@ xf86libinput_parse_rotation_angle_option(InputInfoPtr pInfo,
 }
 
 static void
+xf86libinput_parse_pressurecurve_option(InputInfoPtr pInfo,
+					struct xf86libinput *driver_data,
+					struct bezier_control_point pcurve[4])
+{
+	struct bezier_control_point controls[4] = {
+		{ 0.0, 0.0 },
+		{ 0.0, 0.0 },
+		{ 1.0, 1.0 },
+		{ 1.0, 1.0 },
+	};
+	float points[8];
+	char *str;
+	int rc = 0;
+	int test_bezier[64];
+	struct libinput_tablet_tool *tool = driver_data->tablet_tool;
+
+	if ((driver_data->capabilities & CAP_TABLET_TOOL) == 0)
+		return;
+
+	if (!tool || !libinput_tablet_tool_has_pressure(tool))
+		return;
+
+	str = xf86SetStrOption(pInfo->options,
+			       "TabletToolPressureCurve",
+			       NULL);
+	if (!str)
+		goto out;
+
+	rc = sscanf(str, "%f/%f %f/%f %f/%f %f/%f",
+		    &points[0], &points[1], &points[2], &points[3],
+		    &points[4], &points[5], &points[6], &points[7]);
+	if (rc != 8)
+		goto out;
+
+	for (int i = 0; i < 4; i++) {
+		if (points[i] < 0.0 || points[i] > 1.0)
+			goto out;
+	}
+
+	controls[0].x = points[0];
+	controls[0].y = points[1];
+	controls[1].x = points[2];
+	controls[1].y = points[3];
+	controls[2].x = points[4];
+	controls[2].y = points[5];
+	controls[3].x = points[6];
+	controls[3].y = points[7];
+
+	if (!cubic_bezier(controls, test_bezier, ARRAY_SIZE(test_bezier))) {
+		memcpy(controls, bezier_defaults, sizeof(controls));
+		goto out;
+	}
+
+	rc = 0;
+out:
+	if (rc != 0)
+		xf86IDrvMsg(pInfo, X_ERROR, "Invalid pressure curve: %s\n",  str);
+	free(str);
+	memcpy(pcurve, controls, sizeof(controls));
+	xf86libinput_set_pressurecurve(driver_data, controls);
+}
+
+static void
 xf86libinput_parse_options(InputInfoPtr pInfo,
 			   struct xf86libinput *driver_data,
 			   struct libinput_device *device)
@@ -2638,6 +2735,10 @@ xf86libinput_parse_options(InputInfoPtr pInfo,
 		xf86libinput_parse_draglock_option(pInfo, driver_data);
 		options->horiz_scrolling_enabled = xf86libinput_parse_horiz_scroll_option(pInfo);
 	}
+
+	xf86libinput_parse_pressurecurve_option(pInfo,
+						driver_data,
+						options->pressurecurve);
 }
 
 static const char*
@@ -3086,6 +3187,7 @@ static Atom prop_rotation_angle_default;
 /* driver properties */
 static Atom prop_draglock;
 static Atom prop_horiz_scroll;
+static Atom prop_pressurecurve;
 
 /* general properties */
 static Atom prop_float;
@@ -3815,6 +3917,52 @@ LibinputSetPropertyRotationAngle(DeviceIntPtr dev,
 	return Success;
 }
 
+static inline int
+LibinputSetPropertyPressureCurve(DeviceIntPtr dev,
+				 Atom atom,
+				 XIPropertyValuePtr val,
+				 BOOL checkonly)
+{
+	InputInfoPtr pInfo = dev->public.devicePrivate;
+	struct xf86libinput *driver_data = pInfo->private;
+	float *vals;
+	struct bezier_control_point controls[4];
+
+	if (val->format != 32 || val->size != 8 || val->type != prop_float)
+		return BadMatch;
+
+	vals = val->data;
+	controls[0].x = vals[0];
+	controls[0].y = vals[1];
+	controls[1].x = vals[2];
+	controls[1].y = vals[3];
+	controls[2].x = vals[4];
+	controls[2].y = vals[5];
+	controls[3].x = vals[6];
+	controls[3].y = vals[7];
+
+	if (checkonly) {
+		int test_bezier[64];
+
+		for (int i = 0; i < val->size; i++) {
+			if (vals[i] < 0.0 || vals[i] > 1.0)
+				return BadValue;
+		}
+
+		if (!xf86libinput_check_device (dev, atom))
+			return BadMatch;
+
+		if (!cubic_bezier(controls, test_bezier, ARRAY_SIZE(test_bezier)))
+			return BadValue;
+	} else {
+		xf86libinput_set_pressurecurve(driver_data, controls);
+		memcpy(driver_data->options.pressurecurve, controls,
+		       sizeof(controls));
+	}
+
+	return Success;
+}
+
 static int
 LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
                  BOOL checkonly)
@@ -3867,6 +4015,8 @@ LibinputSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
 	}
 	else if (atom == prop_rotation_angle)
 		rc = LibinputSetPropertyRotationAngle(dev, atom, val, checkonly);
+	else if (atom == prop_pressurecurve)
+		rc = LibinputSetPropertyPressureCurve(dev, atom, val, checkonly);
 	else if (atom == prop_device || atom == prop_product_id ||
 		 atom == prop_tap_default ||
 		 atom == prop_tap_drag_default ||
@@ -4691,6 +4841,35 @@ LibinputInitRotationAngleProperty(DeviceIntPtr dev,
 }
 
 static void
+LibinputInitPressureCurveProperty(DeviceIntPtr dev,
+				  struct xf86libinput *driver_data)
+{
+	const struct bezier_control_point *curve = driver_data->options.pressurecurve;
+	struct libinput_tablet_tool *tool = driver_data->tablet_tool;
+	float data[8];
+
+	if ((driver_data->capabilities & CAP_TABLET_TOOL) == 0)
+		return;
+
+	if (!tool || !libinput_tablet_tool_has_pressure(tool))
+		return;
+
+	data[0] = curve[0].x;
+	data[1] = curve[0].y;
+	data[2] = curve[1].x;
+	data[3] = curve[1].y;
+	data[4] = curve[2].x;
+	data[5] = curve[2].y;
+	data[6] = curve[3].x;
+	data[7] = curve[3].y;
+
+	prop_pressurecurve = LibinputMakeProperty(dev,
+						  LIBINPUT_PROP_TABLET_TOOL_PRESSURECURVE,
+						  prop_float, 32,
+						  8, data);
+}
+
+static void
 LibinputInitProperty(DeviceIntPtr dev)
 {
 	InputInfoPtr pInfo  = dev->public.devicePrivate;
@@ -4746,4 +4925,5 @@ LibinputInitProperty(DeviceIntPtr dev)
 
 	LibinputInitDragLockProperty(dev, driver_data);
 	LibinputInitHorizScrollProperty(dev, driver_data);
+	LibinputInitPressureCurveProperty(dev, driver_data);
 }
-- 
2.9.3



More information about the xorg-devel mailing list