From 74909d9b9989b672502b398024c4326fe9364a5e Mon Sep 17 00:00:00 2001
From: Alon Levy <alevy@redhat.com>
Date: Fri, 18 Oct 2013 14:51:33 +0300
Subject: [PATCH] channel-cursor: mono cursors edge highlighting
Fix 998529, mono (invert) cursors not visible on a black background, by doing
simple edge detection on the cursor (this is done once when the cursor
is changed and then cached, cursors are 32x32 generally) and thus having
a cursor with contrast on both dark and light backgrounds.
When (if) GDK gets invert cursor support (wayland?) then we can just use
the cursor as is. Until then X doesn't provide any way I see of solving
this otherwise. The end result was tested with the I beam cursor that
the original bug was referring to (run putty on a windows 7 vm) and
looks ok to me.
Moving the core function to spice-util for testing.
---
gtk/channel-cursor.c | 64 ++------------------
gtk/spice-util-priv.h | 2 +
gtk/spice-util.c | 113 +++++++++++++++++++++++++++++++++++
3 files changed, 121 insertions(+), 58 deletions(-)
diff --git a/gtk/channel-cursor.c b/gtk/channel-cursor.c
index 7407521..0e48446 100644
--- a/gtk/channel-cursor.c
+++ b/gtk/channel-cursor.c
@@ -275,70 +275,18 @@ static void print_cursor(display_cursor *cursor, const guint8 *data)
static void mono_cursor(display_cursor *cursor, const guint8 *data)
{
+ int bpl = (cursor->hdr.width + 7) / 8;
const guint8 *xor, *and;
guint8 *dest;
- int bpl, x, y, bit;
+ dest = (uint8_t *)cursor->data;
- bpl = (cursor->hdr.width + 7) / 8;
- and = data;
- xor = and + bpl * cursor->hdr.height;
- dest = (uint8_t *)cursor->data;
#ifdef DEBUG_CURSOR
print_cursor(cursor, data);
#endif
- for (y = 0; y < cursor->hdr.height; y++) {
- bit = 0x80;
- for (x = 0; x < cursor->hdr.width; x++, dest += 4) {
- if (and[x/8] & bit) {
- if (xor[x/8] & bit) {
- /*
- * flip -> unsupported by x11, since XCreatePixmapCursor has
- * no invert functionality, only a mask, shape, background and
- * foreground colors. Use this checkerboard hack to get some
- * contrast for cursors in the guest that relied on invert for
- * the same contrast.
- */
- if ((x ^ y) & 1) {
- dest[0] = 0xff;
- dest[1] = 0xff;
- dest[2] = 0xff;
- dest[3] = 0xff;
- } else {
- dest[0] = 0x00;
- dest[1] = 0x00;
- dest[2] = 0x00;
- dest[3] = 0x00;
- }
- } else {
- /* unchanged -> transparent */
- dest[0] = 0x00;
- dest[1] = 0x00;
- dest[2] = 0x00;
- dest[3] = 0x00;
- }
- } else {
- if (xor[x/8] & bit) {
- /* set -> white */
- dest[0] = 0xff;
- dest[1] = 0xff;
- dest[2] = 0xff;
- dest[3] = 0xff;
- } else {
- /* clear -> black */
- dest[0] = 0x00;
- dest[1] = 0x00;
- dest[2] = 0x00;
- dest[3] = 0xff;
- }
- }
- bit >>= 1;
- if (bit == 0) {
- bit = 0x80;
- }
- }
- and += bpl;
- xor += bpl;
- }
+ and = data;
+ xor = and + bpl * cursor->hdr.height;
+ spice_mono_edge_highlight(cursor->hdr.width, cursor->hdr.height,
+ and, xor, dest);
}
static guint8 get_pix_mask(const guint8 *data, gint offset, gint pix_index)
diff --git a/gtk/spice-util-priv.h b/gtk/spice-util-priv.h
index ee5a42d..2fe78a2 100644
--- a/gtk/spice-util-priv.h
+++ b/gtk/spice-util-priv.h
@@ -29,6 +29,8 @@ gboolean spice_strv_contains(const GStrv strv, const gchar *str);
gchar* spice_uuid_to_string(const guint8 uuid[16]);
const gchar* spice_yes_no(gboolean value);
guint16 spice_make_scancode(guint scancode, gboolean release);
+void spice_mono_edge_highlight(unsigned width, unsigned hight,
+ const guint8 *and, const guint8 *xor, guint8 *dest);
#if GLIB_CHECK_VERSION(2,32,0)
#define STATIC_MUTEX GMutex
diff --git a/gtk/spice-util.c b/gtk/spice-util.c
index 774a145..61a6597 100644
--- a/gtk/spice-util.c
+++ b/gtk/spice-util.c
@@ -19,6 +19,8 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
+
+#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <glib-object.h>
@@ -245,3 +247,114 @@ guint16 spice_make_scancode(guint scancode, gboolean release)
g_return_val_if_reached(0);
}
+
+static bool buf_is_ones(unsigned size, const guint8 *data)
+{
+ int i;
+
+ for (i = 0 ; i < size; ++i) {
+ if (data[i] != 0xff) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool is_edge_helper(const guint8 *xor, int bpl, int x, int y)
+{
+ return (xor[bpl * y + (x / 8)] & (0x80 >> (x % 8))) > 0;
+}
+
+static bool is_edge(unsigned width, unsigned height, const guint8 *xor, int bpl, int x, int y)
+{
+ if (x == 0 || x == width -1 || y == 0 || y == height - 1) {
+ return 0;
+ }
+#define P(x, y) is_edge_helper(xor, bpl, x, y)
+ return !P(x, y) && (P(x - 1, y + 1) || P(x, y + 1) || P(x + 1, y + 1) ||
+ P(x - 1, y) || P(x + 1, y) ||
+ P(x - 1, y - 1) || P(x, y - 1) || P(x + 1, y - 1));
+#undef P
+}
+
+/* Mono cursors have two places, "and" and "xor". If a bit is 1 in both, it
+ * means invertion of the corresponding pixel in the display. Since X11 (and
+ * gdk) doesn't do invertion, instead we do edge detection and turn the
+ * sorrounding edge pixels black, and the invert-me pixels white. To
+ * illustrate:
+ *
+ * and xor dest RGB (1=0xffffff, 0=0x000000)
+ *
+ * dest alpha (1=0xff, 0=0x00)
+ *
+ * 11111 00000 00000 00000
+ * 11111 00000 00000 01110
+ * 11111 00100 => 00100 01110
+ * 11111 00100 00100 01110
+ * 11111 00000 00000 01110
+ * 11111 00000 00000 00000
+ *
+ * See tests/util.c for more tests
+ *
+ * Notes:
+ * Assumes width >= 8 (i.e. bytes per line is at least 1)
+ * Assumes edges are not on the boundary (first/last line/column) for simplicity
+ *
+ */
+G_GNUC_INTERNAL
+void spice_mono_edge_highlight(unsigned width, unsigned height,
+ const guint8 *and, const guint8 *xor, guint8 *dest)
+{
+ int bpl = (width + 7) / 8;
+ bool and_ones = buf_is_ones(height * bpl, and);
+ int x, y, bit;
+ const guint8 *xor_base = xor;
+
+ for (y = 0; y < height; y++) {
+ bit = 0x80;
+ for (x = 0; x < width; x++, dest += 4) {
+ if (is_edge(width, height, xor_base, bpl, x, y) && and_ones) {
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0xff;
+ goto next_bit;
+ }
+ if (and[x/8] & bit) {
+ if (xor[x/8] & bit) {
+ dest[0] = 0xff;
+ dest[1] = 0xff;
+ dest[2] = 0xff;
+ dest[3] = 0xff;
+ } else {
+ /* unchanged -> transparent */
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0x00;
+ }
+ } else {
+ if (xor[x/8] & bit) {
+ /* set -> white */
+ dest[0] = 0xff;
+ dest[1] = 0xff;
+ dest[2] = 0xff;
+ dest[3] = 0xff;
+ } else {
+ /* clear -> black */
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0xff;
+ }
+ }
+ next_bit:
+ bit >>= 1;
+ if (bit == 0) {
+ bit = 0x80;
+ }
+ }
+ and += bpl;
+ xor += bpl;
+ }
+}
--
1.8.3.1