swaybar: use text subpixel antialias only where it would look good

Closes #5605

Text Subpixel antialiasing is :

- FreeType makes glyph bitmaps containing coverage percentage for each
  subpixel, instead of pixel
- Then draw by performing the blend for each subpixel, instead of pixel (e.g.
  dual-source blending in opengl)

And there's only one Alpha channel, so this extra coverage data can't
leave Cairo to reach the compositor through there.
Therefore, it can't work as intended if output text alpha != bar background
alpha. Disable it for those cases, enable it elsewhere

As for color emojis, they are RGBA bitmaps. If drawn with text alpha=1.0
and background alpha=1.0 (should be completely opaque bar), then with
'CAIRO_OPERATOR_SOURCE' then texels with alpha < 1.0 result in a blend with
whatever's behind the bar, instead of the bar background
master
Fenveireth 4 years ago committed by Tudor Brindus
parent d358aab8d9
commit 837605d68d

@ -23,8 +23,31 @@ static const int WS_HORIZONTAL_PADDING = 5;
static const double WS_VERTICAL_PADDING = 1.5; static const double WS_VERTICAL_PADDING = 1.5;
static const double BORDER_WIDTH = 1; static const double BORDER_WIDTH = 1;
static uint32_t render_status_line_error(cairo_t *cairo, struct render_context {
struct swaybar_output *output, double *x) { cairo_t *cairo;
struct swaybar_output *output;
cairo_font_options_t *textaa_sharp;
cairo_font_options_t *textaa_safe;
uint32_t background_color;
};
static void choose_text_aa_mode(struct render_context *ctx, uint32_t fontcolor) {
uint32_t salpha = fontcolor & 0xFF;
uint32_t balpha = ctx->background_color & 0xFF;
// Subpixel antialiasing requires blend be done in cairo, not compositor
cairo_font_options_t *fo = salpha == balpha ?
ctx->textaa_sharp : ctx->textaa_safe;
cairo_set_font_options(ctx->cairo, fo);
// Color emojis, being semitransparent bitmaps, are leaky with 'SOURCE'
cairo_operator_t op = salpha == 0xFF ?
CAIRO_OPERATOR_OVER : CAIRO_OPERATOR_SOURCE;
cairo_set_operator(ctx->cairo, op);
}
static uint32_t render_status_line_error(struct render_context *ctx, double *x) {
struct swaybar_output *output = ctx->output;
const char *error = output->bar->status->text; const char *error = output->bar->status->text;
if (!error) { if (!error) {
return 0; return 0;
@ -32,6 +55,7 @@ static uint32_t render_status_line_error(cairo_t *cairo,
uint32_t height = output->height * output->scale; uint32_t height = output->height * output->scale;
cairo_t *cairo = ctx->cairo;
cairo_set_source_u32(cairo, 0xFF0000FF); cairo_set_source_u32(cairo, 0xFF0000FF);
int margin = 3 * output->scale; int margin = 3 * output->scale;
@ -53,21 +77,24 @@ static uint32_t render_status_line_error(cairo_t *cairo,
double text_y = height / 2.0 - text_height / 2.0; double text_y = height / 2.0 - text_height / 2.0;
cairo_move_to(cairo, *x, (int)floor(text_y)); cairo_move_to(cairo, *x, (int)floor(text_y));
choose_text_aa_mode(ctx, 0xFF0000FF);
pango_printf(cairo, font, output->scale, false, "%s", error); pango_printf(cairo, font, output->scale, false, "%s", error);
*x -= margin; *x -= margin;
return output->height; return output->height;
} }
static uint32_t render_status_line_text(cairo_t *cairo, static uint32_t render_status_line_text(struct render_context *ctx, double *x) {
struct swaybar_output *output, double *x) { struct swaybar_output *output = ctx->output;
const char *text = output->bar->status->text; const char *text = output->bar->status->text;
if (!text) { if (!text) {
return 0; return 0;
} }
cairo_t *cairo = ctx->cairo;
struct swaybar_config *config = output->bar->config; struct swaybar_config *config = output->bar->config;
cairo_set_source_u32(cairo, output->focused ? uint32_t fontcolor = output->focused ?
config->colors.focused_statusline : config->colors.statusline); config->colors.focused_statusline : config->colors.statusline;
cairo_set_source_u32(cairo, fontcolor);
int text_width, text_height; int text_width, text_height;
get_text_size(cairo, config->font, &text_width, &text_height, NULL, get_text_size(cairo, config->font, &text_width, &text_height, NULL,
@ -87,6 +114,7 @@ static uint32_t render_status_line_text(cairo_t *cairo,
uint32_t height = output->height * output->scale; uint32_t height = output->height * output->scale;
double text_y = height / 2.0 - text_height / 2.0; double text_y = height / 2.0 - text_height / 2.0;
cairo_move_to(cairo, *x, (int)floor(text_y)); cairo_move_to(cairo, *x, (int)floor(text_y));
choose_text_aa_mode(ctx, fontcolor);
pango_printf(cairo, config->font, output->scale, pango_printf(cairo, config->font, output->scale,
config->pango_markup, "%s", text); config->pango_markup, "%s", text);
*x -= margin; *x -= margin;
@ -96,6 +124,7 @@ static uint32_t render_status_line_text(cairo_t *cairo,
static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, static void render_sharp_rectangle(cairo_t *cairo, uint32_t color,
double x, double y, double width, double height) { double x, double y, double width, double height) {
cairo_save(cairo); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_u32(cairo, color); cairo_set_source_u32(cairo, color);
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
cairo_rectangle(cairo, x, y, width, height); cairo_rectangle(cairo, x, y, width, height);
@ -109,6 +138,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
render_sharp_rectangle(cairo, color, x, y, width, height); render_sharp_rectangle(cairo, color, x, y, width, height);
} else { } else {
cairo_save(cairo); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_u32(cairo, color); cairo_set_source_u32(cairo, color);
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
if (width == 1) { if (width == 1) {
@ -146,9 +176,8 @@ static void i3bar_block_unref_callback(void *data) {
i3bar_block_unref(data); i3bar_block_unref(data);
} }
static uint32_t render_status_block(cairo_t *cairo, static uint32_t render_status_block(struct render_context *ctx,
struct swaybar_output *output, struct i3bar_block *block, double *x, struct i3bar_block *block, double *x, bool edge, bool use_short_text) {
bool edge, bool use_short_text) {
if (!block->full_text || !*block->full_text) { if (!block->full_text || !*block->full_text) {
return 0; return 0;
} }
@ -158,8 +187,9 @@ static uint32_t render_status_block(cairo_t *cairo,
text = block->short_text; text = block->short_text;
} }
cairo_t *cairo = ctx->cairo;
struct swaybar_output *output = ctx->output;
struct swaybar_config *config = output->bar->config; struct swaybar_config *config = output->bar->config;
int text_width, text_height; int text_width, text_height;
get_text_size(cairo, config->font, &text_width, &text_height, NULL, get_text_size(cairo, config->font, &text_width, &text_height, NULL,
output->scale, block->markup, "%s", text); output->scale, block->markup, "%s", text);
@ -240,6 +270,7 @@ static uint32_t render_status_block(cairo_t *cairo,
if (bg_color) { if (bg_color) {
render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, render_sharp_rectangle(cairo, bg_color, x_pos, y_pos,
block_width, render_height); block_width, render_height);
ctx->background_color = bg_color;
} }
uint32_t border_color = block->urgent uint32_t border_color = block->urgent
@ -274,6 +305,7 @@ static uint32_t render_status_block(cairo_t *cairo,
color = block->color_set ? block->color : color; color = block->color_set ? block->color : color;
color = block->urgent ? config->colors.urgent_workspace.text : color; color = block->urgent ? config->colors.urgent_workspace.text : color;
cairo_set_source_u32(cairo, color); cairo_set_source_u32(cairo, color);
choose_text_aa_mode(ctx, color);
pango_printf(cairo, config->font, output->scale, pango_printf(cairo, config->font, output->scale,
block->markup, "%s", text); block->markup, "%s", text);
x_pos += width; x_pos += width;
@ -287,17 +319,20 @@ static uint32_t render_status_block(cairo_t *cairo,
if (!edge && block->separator) { if (!edge && block->separator) {
if (output->focused) { if (output->focused) {
cairo_set_source_u32(cairo, config->colors.focused_separator); color = config->colors.focused_separator;
} else { } else {
cairo_set_source_u32(cairo, config->colors.separator); color = config->colors.separator;
} }
cairo_set_source_u32(cairo, color);
if (config->sep_symbol) { if (config->sep_symbol) {
offset = x_pos + (sep_block_width - sep_width) / 2; offset = x_pos + (sep_block_width - sep_width) / 2;
double sep_y = height / 2.0 - sep_height / 2.0; double sep_y = height / 2.0 - sep_height / 2.0;
cairo_move_to(cairo, offset, (int)floor(sep_y)); cairo_move_to(cairo, offset, (int)floor(sep_y));
choose_text_aa_mode(ctx, color);
pango_printf(cairo, config->font, output->scale, false, pango_printf(cairo, config->font, output->scale, false,
"%s", config->sep_symbol); "%s", config->sep_symbol);
} else { } else {
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_line_width(cairo, 1); cairo_set_line_width(cairo, 1);
cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); cairo_move_to(cairo, x_pos + sep_block_width / 2, margin);
cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin);
@ -459,13 +494,14 @@ static uint32_t predict_binding_mode_indicator_length(cairo_t *cairo,
return width; return width;
} }
static uint32_t render_status_line_i3bar(cairo_t *cairo, static uint32_t render_status_line_i3bar(struct render_context *ctx, double *x) {
struct swaybar_output *output, double *x) { struct swaybar_output *output = ctx->output;
uint32_t max_height = 0; uint32_t max_height = 0;
bool edge = *x == output->width * output->scale; bool edge = *x == output->width * output->scale;
struct i3bar_block *block; struct i3bar_block *block;
bool use_short_text = false; bool use_short_text = false;
cairo_t *cairo = ctx->cairo;
double reserved_width = double reserved_width =
predict_workspace_buttons_length(cairo, output) + predict_workspace_buttons_length(cairo, output) +
predict_binding_mode_indicator_length(cairo, output) + predict_binding_mode_indicator_length(cairo, output) +
@ -479,7 +515,7 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
} }
wl_list_for_each(block, &output->bar->status->blocks, link) { wl_list_for_each(block, &output->bar->status->blocks, link) {
uint32_t h = render_status_block(cairo, output, block, x, edge, uint32_t h = render_status_block(ctx, block, x, edge,
use_short_text); use_short_text);
max_height = h > max_height ? h : max_height; max_height = h > max_height ? h : max_height;
edge = false; edge = false;
@ -487,29 +523,30 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
return max_height; return max_height;
} }
static uint32_t render_status_line(cairo_t *cairo, static uint32_t render_status_line(struct render_context *ctx, double *x) {
struct swaybar_output *output, double *x) { struct status_line *status = ctx->output->bar->status;
struct status_line *status = output->bar->status;
switch (status->protocol) { switch (status->protocol) {
case PROTOCOL_ERROR: case PROTOCOL_ERROR:
return render_status_line_error(cairo, output, x); return render_status_line_error(ctx, x);
case PROTOCOL_TEXT: case PROTOCOL_TEXT:
return render_status_line_text(cairo, output, x); return render_status_line_text(ctx, x);
case PROTOCOL_I3BAR: case PROTOCOL_I3BAR:
return render_status_line_i3bar(cairo, output, x); return render_status_line_i3bar(ctx, x);
case PROTOCOL_UNDEF: case PROTOCOL_UNDEF:
return 0; return 0;
} }
return 0; return 0;
} }
static uint32_t render_binding_mode_indicator(cairo_t *cairo, static uint32_t render_binding_mode_indicator(struct render_context *ctx,
struct swaybar_output *output, double x) { double x) {
struct swaybar_output *output = ctx->output;
const char *mode = output->bar->mode; const char *mode = output->bar->mode;
if (!mode) { if (!mode) {
return 0; return 0;
} }
cairo_t *cairo = ctx->cairo;
struct swaybar_config *config = output->bar->config; struct swaybar_config *config = output->bar->config;
int text_width, text_height; int text_width, text_height;
get_text_size(cairo, config->font, &text_width, &text_height, NULL, get_text_size(cairo, config->font, &text_width, &text_height, NULL,
@ -533,7 +570,9 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
} }
uint32_t height = output->height * output->scale; uint32_t height = output->height * output->scale;
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_u32(cairo, config->colors.binding_mode.background); cairo_set_source_u32(cairo, config->colors.binding_mode.background);
ctx->background_color = config->colors.binding_mode.background;
cairo_rectangle(cairo, x, 0, width, height); cairo_rectangle(cairo, x, 0, width, height);
cairo_fill(cairo); cairo_fill(cairo);
@ -550,6 +589,7 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
double text_y = height / 2.0 - text_height / 2.0; double text_y = height / 2.0 - text_height / 2.0;
cairo_set_source_u32(cairo, config->colors.binding_mode.text); cairo_set_source_u32(cairo, config->colors.binding_mode.text);
cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y));
choose_text_aa_mode(ctx, config->colors.binding_mode.text);
pango_printf(cairo, config->font, output->scale, pango_printf(cairo, config->font, output->scale,
output->bar->mode_pango_markup, "%s", mode); output->bar->mode_pango_markup, "%s", mode);
return output->height; return output->height;
@ -565,9 +605,9 @@ static enum hotspot_event_handling workspace_hotspot_callback(
return HOTSPOT_IGNORE; return HOTSPOT_IGNORE;
} }
static uint32_t render_workspace_button(cairo_t *cairo, static uint32_t render_workspace_button(struct render_context *ctx,
struct swaybar_output *output,
struct swaybar_workspace *ws, double *x) { struct swaybar_workspace *ws, double *x) {
struct swaybar_output *output = ctx->output;
struct swaybar_config *config = output->bar->config; struct swaybar_config *config = output->bar->config;
struct box_colors box_colors; struct box_colors box_colors;
if (ws->urgent) { if (ws->urgent) {
@ -582,6 +622,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
uint32_t height = output->height * output->scale; uint32_t height = output->height * output->scale;
cairo_t *cairo = ctx->cairo;
int text_width, text_height; int text_width, text_height;
get_text_size(cairo, config->font, &text_width, &text_height, NULL, get_text_size(cairo, config->font, &text_width, &text_height, NULL,
output->scale, config->pango_markup, "%s", ws->label); output->scale, config->pango_markup, "%s", ws->label);
@ -603,7 +644,9 @@ static uint32_t render_workspace_button(cairo_t *cairo,
width = config->workspace_min_width * output->scale; width = config->workspace_min_width * output->scale;
} }
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
cairo_set_source_u32(cairo, box_colors.background); cairo_set_source_u32(cairo, box_colors.background);
ctx->background_color = box_colors.background;
cairo_rectangle(cairo, *x, 0, width, height); cairo_rectangle(cairo, *x, 0, width, height);
cairo_fill(cairo); cairo_fill(cairo);
@ -620,6 +663,7 @@ static uint32_t render_workspace_button(cairo_t *cairo,
double text_y = height / 2.0 - text_height / 2.0; double text_y = height / 2.0 - text_height / 2.0;
cairo_set_source_u32(cairo, box_colors.text); cairo_set_source_u32(cairo, box_colors.text);
cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y));
choose_text_aa_mode(ctx, box_colors.text);
pango_printf(cairo, config->font, output->scale, config->pango_markup, pango_printf(cairo, config->font, output->scale, config->pango_markup,
"%s", ws->label); "%s", ws->label);
@ -637,15 +681,19 @@ static uint32_t render_workspace_button(cairo_t *cairo,
return output->height; return output->height;
} }
static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) { static uint32_t render_to_cairo(struct render_context *ctx) {
cairo_t *cairo = ctx->cairo;
struct swaybar_output *output = ctx->output;
struct swaybar *bar = output->bar; struct swaybar *bar = output->bar;
struct swaybar_config *config = bar->config; struct swaybar_config *config = bar->config;
cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
if (output->focused) { if (output->focused) {
cairo_set_source_u32(cairo, config->colors.focused_background); ctx->background_color = config->colors.focused_background;
} else { } else {
cairo_set_source_u32(cairo, config->colors.background); ctx->background_color = config->colors.background;
} }
cairo_set_source_u32(cairo, ctx->background_color);
cairo_paint(cairo); cairo_paint(cairo);
int th; int th;
@ -666,19 +714,19 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
} }
#endif #endif
if (bar->status) { if (bar->status) {
uint32_t h = render_status_line(cairo, output, &x); uint32_t h = render_status_line(ctx, &x);
max_height = h > max_height ? h : max_height; max_height = h > max_height ? h : max_height;
} }
x = 0; x = 0;
if (config->workspace_buttons) { if (config->workspace_buttons) {
struct swaybar_workspace *ws; struct swaybar_workspace *ws;
wl_list_for_each(ws, &output->workspaces, link) { wl_list_for_each(ws, &output->workspaces, link) {
uint32_t h = render_workspace_button(cairo, output, ws, &x); uint32_t h = render_workspace_button(ctx, ws, &x);
max_height = h > max_height ? h : max_height; max_height = h > max_height ? h : max_height;
} }
} }
if (config->binding_mode_indicator) { if (config->binding_mode_indicator) {
uint32_t h = render_binding_mode_indicator(cairo, output, x); uint32_t h = render_binding_mode_indicator(ctx, x);
max_height = h > max_height ? h : max_height; max_height = h > max_height ? h : max_height;
} }
@ -708,26 +756,35 @@ void render_frame(struct swaybar_output *output) {
free_hotspots(&output->hotspots); free_hotspots(&output->hotspots);
struct render_context ctx = { 0 };
ctx.output = output;
cairo_surface_t *recorder = cairo_recording_surface_create( cairo_surface_t *recorder = cairo_recording_surface_create(
CAIRO_CONTENT_COLOR_ALPHA, NULL); CAIRO_CONTENT_COLOR_ALPHA, NULL);
cairo_t *cairo = cairo_create(recorder); cairo_t *cairo = cairo_create(recorder);
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
ctx.cairo = cairo;
cairo_font_options_t *fo = cairo_font_options_create(); cairo_font_options_t *fo = cairo_font_options_create();
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) {
cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY); cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
ctx.textaa_safe = fo;
if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) {
ctx.textaa_sharp = ctx.textaa_safe;
} else { } else {
fo = cairo_font_options_create();
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
cairo_font_options_set_subpixel_order(fo, cairo_font_options_set_subpixel_order(fo,
to_cairo_subpixel_order(output->subpixel)); to_cairo_subpixel_order(output->subpixel));
ctx.textaa_sharp = fo;
} }
cairo_set_font_options(cairo, fo);
cairo_font_options_destroy(fo);
cairo_save(cairo); cairo_save(cairo);
cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
cairo_paint(cairo); cairo_paint(cairo);
cairo_restore(cairo); cairo_restore(cairo);
uint32_t height = render_to_cairo(cairo, output); uint32_t height = render_to_cairo(&ctx);
int config_height = output->bar->config->height; int config_height = output->bar->config->height;
if (config_height > 0) { if (config_height > 0) {
height = config_height; height = config_height;
@ -779,6 +836,11 @@ void render_frame(struct swaybar_output *output) {
wl_surface_commit(output->surface); wl_surface_commit(output->surface);
} }
if (ctx.textaa_sharp != ctx.textaa_safe) {
cairo_font_options_destroy(ctx.textaa_sharp);
}
cairo_font_options_destroy(ctx.textaa_safe);
cairo_surface_destroy(recorder); cairo_surface_destroy(recorder);
cairo_destroy(cairo); cairo_destroy(cairo);
} }

Loading…
Cancel
Save