common: render text buffers with opaque background

After a roundabout discussion[1] with wlroots devs, it's become apparent
that subpixel text rendering (a.k.a. "ClearType") does not work properly
when rendering over a transparent background, as labwc currently does.

Basically it comes down to the fact that the color of semi-transparent
pixels (which is adjusted redder or bluer to compensate for RGB subpixel
alignment) depends somewhat on background color. When rendering over
transparency, the text engine doesn't know the intended background color
and can't adjust the pixel colors correctly.

With Pango/Cairo, the end result can range from grayscale rendering (no
subpixel rendering at all) to wrong/oversaturated colors (for example,
bright pink pixels when rendering white text on blue background).

This change solves the issue by first filling the text buffer with an
opaque background color before rendering the text over it. Currently,
this is easy since the background is always a solid color. It may be a
little more complex (but doable) if we implement gradients in future.

Note that GTK 4 (and to some degree, recent versions of Microsoft
Windows) avoid this issue by disabling subpixel rendering altogether. I
would much prefer that labwc NOT do this -- it results in noticeably
blurrier text on non-retina LCD screens, which are still common.

[1] https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/3822
This commit is contained in:
John Lindgren 2024-03-16 22:20:40 -04:00 committed by Johan Malm
parent 5a20014f50
commit 4fa51b950c
9 changed files with 34 additions and 15 deletions

View file

@ -42,11 +42,12 @@ int font_width(struct font *font, const char *string);
* @text: text to be generated as texture * @text: text to be generated as texture
* @font: font description * @font: font description
* @color: foreground color in rgba format * @color: foreground color in rgba format
* @bg_color: background color in rgba format
* @arrow: arrow (utf8) character to show or NULL for none * @arrow: arrow (utf8) character to show or NULL for none
*/ */
void font_buffer_create(struct lab_data_buffer **buffer, int max_width, void font_buffer_create(struct lab_data_buffer **buffer, int max_width,
const char *text, struct font *font, float *color, const char *arrow, const char *text, struct font *font, const float *color,
double scale); const float *bg_color, const char *arrow, double scale);
/** /**
* font_finish - free some font related resources * font_finish - free some font related resources

View file

@ -42,7 +42,7 @@ void multi_rect_set_size(struct multi_rect *rect, int width, int height);
* Sets the cairo color. * Sets the cairo color.
* Splits a float[4] single color array into its own arguments * Splits a float[4] single color array into its own arguments
*/ */
void set_cairo_color(cairo_t *cairo, float *color); void set_cairo_color(cairo_t *cairo, const float *color);
/* Draws a border with a specified line width */ /* Draws a border with a specified line width */
void draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width); void draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width);

View file

@ -17,6 +17,7 @@ struct scaled_font_buffer {
char *text; char *text;
int max_width; int max_width;
float color[4]; float color[4];
float bg_color[4];
char *arrow; char *arrow;
struct font font; struct font font;
struct scaled_scene_buffer *scaled_buffer; struct scaled_scene_buffer *scaled_buffer;
@ -46,7 +47,8 @@ struct scaled_font_buffer *scaled_font_buffer_create(struct wlr_scene_tree *pare
* - font and color the same * - font and color the same
*/ */
void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text,
int max_width, struct font *font, float *color, const char *arrow); int max_width, struct font *font, const float *color,
const float *bg_color, const char *arrow);
/** /**
* Update the max width of an existing auto scaling font buffer * Update the max width of an existing auto scaling font buffer

View file

@ -77,8 +77,8 @@ font_width(struct font *font, const char *string)
void void
font_buffer_create(struct lab_data_buffer **buffer, int max_width, font_buffer_create(struct lab_data_buffer **buffer, int max_width,
const char *text, struct font *font, float *color, const char *arrow, const char *text, struct font *font, const float *color,
double scale) const float *bg_color, const char *arrow, double scale)
{ {
/* Allow a minimum of one pixel each for text and arrow */ /* Allow a minimum of one pixel each for text and arrow */
if (max_width < 2) { if (max_width < 2) {
@ -114,6 +114,15 @@ font_buffer_create(struct lab_data_buffer **buffer, int max_width,
cairo_t *cairo = (*buffer)->cairo; cairo_t *cairo = (*buffer)->cairo;
cairo_surface_t *surf = cairo_get_target(cairo); cairo_surface_t *surf = cairo_get_target(cairo);
/*
* Fill background color first - necessary for subpixel
* rendering, which does not work properly on transparency
*/
set_cairo_color(cairo, bg_color);
cairo_rectangle(cairo, 0, 0, (*buffer)->unscaled_width,
(*buffer)->unscaled_height);
cairo_fill(cairo);
set_cairo_color(cairo, color); set_cairo_color(cairo, color);
cairo_move_to(cairo, 0, 0); cairo_move_to(cairo, 0, 0);

View file

@ -83,7 +83,7 @@ draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width)
/* Sets the cairo color. Splits the single color channels */ /* Sets the cairo color. Splits the single color channels */
void void
set_cairo_color(cairo_t *cairo, float *c) set_cairo_color(cairo_t *cairo, const float *c)
{ {
cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]); cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]);
} }

View file

@ -19,7 +19,7 @@ _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale)
/* Buffer gets free'd automatically along the backing wlr_buffer */ /* Buffer gets free'd automatically along the backing wlr_buffer */
font_buffer_create(&buffer, self->max_width, self->text, font_buffer_create(&buffer, self->max_width, self->text,
&self->font, self->color, self->arrow, scale); &self->font, self->color, self->bg_color, self->arrow, scale);
self->width = buffer ? buffer->unscaled_width : 0; self->width = buffer ? buffer->unscaled_width : 0;
self->height = buffer ? buffer->unscaled_height : 0; self->height = buffer ? buffer->unscaled_height : 0;
@ -64,8 +64,8 @@ scaled_font_buffer_create(struct wlr_scene_tree *parent)
void void
scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text,
int max_width, struct font *font, float *color, int max_width, struct font *font, const float *color,
const char *arrow) const float *bg_color, const char *arrow)
{ {
assert(self); assert(self);
assert(text); assert(text);
@ -87,6 +87,7 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text,
self->font.slant = font->slant; self->font.slant = font->slant;
self->font.weight = font->weight; self->font.weight = font->weight;
memcpy(self->color, color, sizeof(self->color)); memcpy(self->color, color, sizeof(self->color));
memcpy(self->bg_color, bg_color, sizeof(self->bg_color));
self->arrow = arrow ? xstrdup(arrow) : NULL; self->arrow = arrow ? xstrdup(arrow) : NULL;
/* Invalidate cache and force a new render */ /* Invalidate cache and force a new render */

View file

@ -205,9 +205,11 @@ item_create(struct menu *menu, const char *text, bool show_arrow)
/* Font buffers */ /* Font buffers */
scaled_font_buffer_update(menuitem->normal.buffer, text, menuitem->native_width, scaled_font_buffer_update(menuitem->normal.buffer, text, menuitem->native_width,
&rc.font_menuitem, theme->menu_items_text_color, arrow); &rc.font_menuitem, theme->menu_items_text_color,
theme->menu_items_bg_color, arrow);
scaled_font_buffer_update(menuitem->selected.buffer, text, menuitem->native_width, scaled_font_buffer_update(menuitem->selected.buffer, text, menuitem->native_width,
&rc.font_menuitem, theme->menu_items_active_text_color, arrow); &rc.font_menuitem, theme->menu_items_active_text_color,
theme->menu_items_active_bg_color, arrow);
/* Center font nodes */ /* Center font nodes */
x = theme->menu_item_padding_x; x = theme->menu_item_padding_x;

View file

@ -199,7 +199,8 @@ resize_indicator_update(struct view *view)
(eff_height - indicator->height) / 2); (eff_height - indicator->height) / 2);
scaled_font_buffer_update(indicator->text, text, width, &rc.font_osd, scaled_font_buffer_update(indicator->text, text, width, &rc.font_osd,
rc.theme->osd_label_text_color, NULL /* const char *arrow */); rc.theme->osd_label_text_color, rc.theme->osd_bg_color,
NULL /* const char *arrow */);
} }
void void

View file

@ -334,7 +334,8 @@ ssd_update_title(struct ssd *ssd)
struct ssd_state_title *state = &ssd->state.title; struct ssd_state_title *state = &ssd->state.title;
bool title_unchanged = state->text && !strcmp(title, state->text); bool title_unchanged = state->text && !strcmp(title, state->text);
float *text_color; const float *text_color;
const float *bg_color;
struct font *font = NULL; struct font *font = NULL;
struct ssd_part *part; struct ssd_part *part;
struct ssd_sub_tree *subtree; struct ssd_sub_tree *subtree;
@ -346,10 +347,12 @@ ssd_update_title(struct ssd *ssd)
if (subtree == &ssd->titlebar.active) { if (subtree == &ssd->titlebar.active) {
dstate = &state->active; dstate = &state->active;
text_color = theme->window_active_label_text_color; text_color = theme->window_active_label_text_color;
bg_color = theme->window_active_title_bg_color;
font = &rc.font_activewindow; font = &rc.font_activewindow;
} else { } else {
dstate = &state->inactive; dstate = &state->inactive;
text_color = theme->window_inactive_label_text_color; text_color = theme->window_inactive_label_text_color;
bg_color = theme->window_inactive_title_bg_color;
font = &rc.font_inactivewindow; font = &rc.font_inactivewindow;
} }
@ -379,7 +382,7 @@ ssd_update_title(struct ssd *ssd)
if (part->buffer) { if (part->buffer) {
scaled_font_buffer_update(part->buffer, title, scaled_font_buffer_update(part->buffer, title,
title_bg_width, font, title_bg_width, font,
text_color, NULL); text_color, bg_color, NULL);
} }
/* And finally update the cache */ /* And finally update the cache */