render: gamma-correct blending

This implements gamma-correct blending, which mainly affects font
rendering.

The implementation requires compile-time availability of the new
color-management protocol (available in wayland-protocols >= 1.41),
and run-time support for the same in the compositor (specifically, the
EXT_LINEAR TF function and sRGB primaries).

How it works: all colors are decoded from sRGB to linear (using a
lookup table, generated in the exact same way pixman generates it's
internal conversion tables) before being used by pixman. The resulting
image buffer is thus in decoded/linear format. We use the
color-management protocol to inform the compositor of this, by tagging
the wayland surfaces with the 'ext_linear' image attribute.

Sixes: all colors are sRGB internally, and decoded to linear before
being used in any sixels. Thus, the image buffers will contain linear
colors. This is important, since otherwise there would be a
decode/encode penalty every time a sixel is blended to the grid.

Emojis: we require fcft >= 3.2, which adds support for sRGB decoding
color glyphs. Meaning, the emoji pixman surfaces can be blended
directly to the grid, just like sixels.

Gamma-correct blending is enabled by default *when the compositor
supports it*. There's a new option to explicitly enable/disable it:
gamma-correct-blending=no|yes. If set to 'yes', and the compositor
does not implement the required color-management features, warning
logs are emitted.

There's a loss of precision when storing linear pixels in 8-bit
channels. For this reason, this patch also adds supports for 10-bit
surfaces. For now, this is disabled by default since such surfaces
only have 2 bits for alpha. It can be enabled with
tweak.surface-bit-depth=10-bit.

Perhaps, in the future, we can enable it by default if:

* gamma-correct blending is enabled
* the user has not enabled a transparent background
This commit is contained in:
Daniel Eklöf 2025-02-21 11:01:29 +01:00
parent 6d39f66eb7
commit ccf625b991
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
20 changed files with 746 additions and 101 deletions

View file

@ -72,6 +72,11 @@
* `search-bindings.delete-to-start` and
`search-bindings.delete-to-end` key bindings, defaulting to
`Control+u` and `Control+k` respectively ([#1972][1972]).
* Gamma-correct font rendering. Requires compositor support
(`wp_color_management_v1`, and specifically, the `ext_linear`
transfer function). Enabled by default when compositor support is
available. Can be explicitly enabled or disabled with
`gamma-correct-blending=no|yes`.
[1386]: https://codeberg.org/dnkl/foot/issues/1386
[1872]: https://codeberg.org/dnkl/foot/issues/1872
@ -87,6 +92,7 @@
* Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex
based.
* Rename Tokyo Night Day theme to Tokyo Night Light and update colors.
* fcft >= 3.2.0 is now required.
[1925]: https://codeberg.org/dnkl/foot/issues/1925

View file

@ -67,13 +67,14 @@ version_and_features(void)
{
static char buf[256];
snprintf(buf, sizeof(buf),
"version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions",
"version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_xdg_toplevel_icon() ? '+' : '-',
feature_xdg_system_bell() ? '+' : '-',
feature_wp_color_management() ? '+' : '-',
feature_assertions() ? '+' : '-');
return buf;
}

View file

@ -1107,6 +1107,28 @@ parse_section_main(struct context *ctx)
return true;
}
else if (streq(key, "gamma-correct-blending")) {
bool gamma_correct;
if (!value_to_bool(ctx, &gamma_correct))
return false;
#if defined(HAVE_WP_COLOR_MANAGEMENT)
conf->gamma_correct =
gamma_correct
? GAMMA_CORRECT_ENABLED
: GAMMA_CORRECT_DISABLED;
return true;
#else
if (gamma_correct) {
LOG_CONTEXTUAL_WARN(
"ignoring; foot was built without color-management support");
}
conf->gamma_correct = GAMMA_CORRECT_DISABLED;
return true;
#endif
}
else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false;
@ -2767,6 +2789,16 @@ parse_section_tweak(struct context *ctx)
else if (streq(key, "bold-text-in-bright-amount"))
return value_to_float(ctx, &conf->bold_in_bright.amount);
else if (streq(key, "surface-bit-depth")) {
_Static_assert(sizeof(conf->tweak.surface_bit_depth) == sizeof(int),
"enum is not 32-bit");
return value_to_enum(
ctx,
(const char *[]){"8-bit", "10-bit", NULL},
(int *)&conf->tweak.surface_bit_depth);
}
else {
LOG_CONTEXTUAL_ERR("not a valid option: %s", key);
return false;
@ -3300,6 +3332,11 @@ config_load(struct config *conf, const char *conf_path,
.underline_thickness = {.pt = 0., .px = -1},
.strikeout_thickness = {.pt = 0., .px = -1},
.dpi_aware = false,
#if defined(HAVE_WP_COLOR_MANAGEMENT)
.gamma_correct = GAMMA_CORRECT_AUTO,
#else
.gamma_correct = GAMMA_CORRECT_DISABLED,
#endif
.security = {
.osc52 = OSC52_ENABLED,
},
@ -3408,6 +3445,7 @@ config_load(struct config *conf, const char *conf_path,
.box_drawing_solid_shades = true,
.font_monospace_warn = true,
.sixel = true,
.surface_bit_depth = 8,
},
.touch = {

View file

@ -164,6 +164,9 @@ struct config {
enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode;
bool dpi_aware;
enum {GAMMA_CORRECT_DISABLED,
GAMMA_CORRECT_ENABLED,
GAMMA_CORRECT_AUTO} gamma_correct;
struct config_font_list fonts[4];
struct font_size_adjustment font_size_adjustment;
@ -397,6 +400,7 @@ struct config {
bool box_drawing_solid_shades;
bool font_monospace_warn;
bool sixel;
enum { SHM_8_BIT, SHM_10_BIT } surface_bit_depth;
} tweak;
struct {

View file

@ -198,6 +198,35 @@ empty string to be set, but it must be quoted: *KEY=""*)
Default: _unset_
*gamma-correct-blending*
Boolean. When enabled, foot will do gamma-correct blending in
linear color space. This is how font glyphs are supposed to be
rendered, but since nearly no applications or toolkits are doing
it on Linux, the result may not look like you are used to.
Compared to the default (disabled), bright glyphs on a dark
background will appear thicker, and dark glyphs on a light
background will appear thinner.
Also be aware that many fonts have been developed on systems that
do not do gamma-correct blending, and may therefore look thicker
than intended when rendered with gamma-correct blending, since the
font designer set the font weight based on incorrect rendering.
FreeType can limit the effect of the latter, with a technique
called stem darkening. It is only available for CFF fonts
(OpenType, .otf) and disabled by default (in FreeType). You can
enable it by setting the environment variable
*FREETYPE_PROPERTIES="cff:no-stem-darkening=0"* before starting
foot.
You may also want to enable 10-bit image buffers when
gamma-correct blending is enabled. Though probably only if you do
not use a transparent background (with 10-bit buffers, you only
get 2 bits alpha). See *tweak.surface-bit-depth*.
Default: enabled when compositor support is available
*box-drawings-uses-font-glyphs*
Boolean. When disabled, foot generates box/line drawing characters
itself. The are several advantages to doing this instead of using
@ -1917,6 +1946,23 @@ any of these options.
*bold-text-in-bright* is set to *yes* (the *palette-based* variant
is not affected by this option). Default: _1.3_.
*surface-bit-depth*
Selects which RGB bit depth to use for image buffers. One of
*8-bit*, or *10-bit*.
The default, *8-bit*, uses 8 bits for all channels, alpha
included. When *gamma-correct-blending* is disabled, this is the
best option.
When *gamma-correct-blending* is enabled, you may want to enable
10-bit surfaces, as that improves the color resolution. Be aware
however, that in this mode, the alpha channel is only 2 bits
instead of 8 bits. Thus, if you are using a transparent
background, you may want to use the default, *8-bit*, even if you
have gamma-correct blending enabled.
Default: _8-bit_
# SEE ALSO
*foot*(1), *footclient*(1)

View file

@ -55,3 +55,12 @@ static inline bool feature_xdg_system_bell(void)
return false;
#endif
}
static inline bool feature_wp_color_management(void)
{
#if defined(HAVE_WP_COLOR_MANAGEMENT)
return true;
#else
return false;
#endif
}

3
main.c
View file

@ -51,13 +51,14 @@ version_and_features(void)
{
static char buf[256];
snprintf(buf, sizeof(buf),
"version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions",
"version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions",
FOOT_VERSION,
feature_pgo() ? '+' : '-',
feature_ime() ? '+' : '-',
feature_graphemes() ? '+' : '-',
feature_xdg_toplevel_icon() ? '+' : '-',
feature_xdg_system_bell() ? '+' : '-',
feature_wp_color_management() ? '+' : '-',
feature_assertions() ? '+' : '-');
return buf;
}

View file

@ -146,7 +146,7 @@ if utf8proc.found()
endif
tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist')
fcft = dependency('fcft', version: ['>=3.0.1', '<4.0.0'], fallback: 'fcft')
fcft = dependency('fcft', version: ['>=3.2.0', '<4.0.0'], fallback: 'fcft')
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
@ -187,6 +187,13 @@ else
xdg_system_bell = false
endif
if wayland_protocols.version().version_compare('>=1.41')
add_project_arguments('-DHAVE_WP_COLOR_MANAGEMENT', language: 'c')
wl_proto_xml += [wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml']
wp_color_management = true
else
wp_color_management = false
endif
foreach prot : wl_proto_xml
wl_proto_headers += custom_target(
@ -228,6 +235,13 @@ emoji_variation_sequences = custom_target(
command: [python, generate_emoji_variation_sequences, '@INPUT@', '@OUTPUT@']
)
generate_srgb_funcs = files('scripts/srgb.py')
srgb_funcs = custom_target(
'generate_srgb_funcs',
output: ['srgb.c', 'srgb.h'],
command: [python, generate_srgb_funcs, '@OUTPUT0@', '@OUTPUT1@']
)
common = static_library(
'common',
'log.c', 'log.h',
@ -260,7 +274,7 @@ vtlib = static_library(
'osc.c', 'osc.h',
'sixel.c', 'sixel.h',
'vt.c', 'vt.h',
builtin_terminfo, emoji_variation_sequences,
builtin_terminfo, emoji_variation_sequences, srgb_funcs,
wl_proto_src + wl_proto_headers,
version,
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
@ -424,6 +438,7 @@ summary(
'Grapheme clustering': utf8proc.found(),
'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon,
'Wayland: xdg-system-bell-v1': xdg_system_bell,
'Wayland: wp-color-management-v1': wp_color_management,
'utmp backend': utmp_backend,
'utmp helper default path': utmp_default_helper_path,
'Build terminfo': tic.found(),

View file

@ -128,6 +128,12 @@ render_worker_thread(void *_ctx)
return 0;
}
bool
render_do_linear_blending(const struct terminal *term)
{
return false;
}
struct extraction_context *
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
{
@ -197,7 +203,9 @@ void shm_unref(struct buffer *buf) {}
void shm_chain_free(struct buffer_chain *chain) {}
struct buffer_chain *
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
shm_chain_new(
struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_it_if_capable)
{
return NULL;
}

View file

@ -86,6 +86,7 @@ is_sway(void)
void
quirk_sway_subsurface_unmap(struct terminal *term)
{
return;
if (!is_sway())
return;

171
render.c
View file

@ -44,6 +44,7 @@
#include "selection.h"
#include "shm.h"
#include "sixel.h"
#include "srgb.h"
#include "url-mode.h"
#include "util.h"
#include "xmalloc.h"
@ -232,22 +233,45 @@ attrs_to_font(const struct terminal *term, const struct attributes *attrs)
return term->fonts[idx];
}
static inline pixman_color_t
color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha)
static pixman_color_t
color_hex_to_pixman_srgb(uint32_t color, uint16_t alpha)
{
return (pixman_color_t){
.red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) * alpha / 0xffff,
.green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) * alpha / 0xffff,
.blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) * alpha / 0xffff,
.alpha = alpha,
.alpha = alpha, /* Consider alpha linear already? */
.red = srgb_decode_8_to_16((color >> 16) & 0xff),
.green = srgb_decode_8_to_16((color >> 8) & 0xff),
.blue = srgb_decode_8_to_16((color >> 0) & 0xff),
};
}
static inline pixman_color_t
color_hex_to_pixman(uint32_t color)
color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha, bool srgb)
{
pixman_color_t ret;
if (srgb)
ret = color_hex_to_pixman_srgb(color, alpha);
else {
ret = (pixman_color_t){
.red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)),
.green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)),
.blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)),
.alpha = alpha,
};
}
ret.red = (uint32_t)ret.red * alpha / 0xffff;
ret.green = (uint32_t)ret.green * alpha / 0xffff;
ret.blue = (uint32_t)ret.blue * alpha / 0xffff;
return ret;
}
static inline pixman_color_t
color_hex_to_pixman(uint32_t color, bool srgb)
{
/* Count on the compiler optimizing this */
return color_hex_to_pixman_with_alpha(color, 0xffff);
return color_hex_to_pixman_with_alpha(color, 0xffff, srgb);
}
static inline uint32_t
@ -568,23 +592,24 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix,
static void
cursor_colors_for_cell(const struct terminal *term, const struct cell *cell,
const pixman_color_t *fg, const pixman_color_t *bg,
pixman_color_t *cursor_color, pixman_color_t *text_color)
const pixman_color_t *fg, const pixman_color_t *bg,
pixman_color_t *cursor_color, pixman_color_t *text_color,
bool gamma_correct)
{
if (term->colors.cursor_bg >> 31)
*cursor_color = color_hex_to_pixman(term->colors.cursor_bg);
*cursor_color = color_hex_to_pixman(term->colors.cursor_bg, gamma_correct);
else
*cursor_color = *fg;
if (term->colors.cursor_fg >> 31)
*text_color = color_hex_to_pixman(term->colors.cursor_fg);
*text_color = color_hex_to_pixman(term->colors.cursor_fg, gamma_correct);
else {
*text_color = *bg;
if (unlikely(text_color->alpha != 0xffff)) {
/* The *only* color that can have transparency is the
* default background color */
*text_color = color_hex_to_pixman(term->colors.bg);
*text_color = color_hex_to_pixman(term->colors.bg, gamma_correct);
}
}
@ -592,8 +617,8 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell,
text_color->green == cursor_color->green &&
text_color->blue == cursor_color->blue)
{
*text_color = color_hex_to_pixman(term->colors.bg);
*cursor_color = color_hex_to_pixman(term->colors.fg);
*text_color = color_hex_to_pixman(term->colors.bg, gamma_correct);
*cursor_color = color_hex_to_pixman(term->colors.fg, gamma_correct);
}
}
@ -604,7 +629,8 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
{
pixman_color_t cursor_color;
pixman_color_t text_color;
cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color);
cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color,
render_do_linear_blending(term));
if (unlikely(!term->kbd_focus)) {
switch (term->conf->cursor.unfocused_style) {
@ -656,8 +682,9 @@ draw_cursor(const struct terminal *term, const struct cell *cell,
}
static int
render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage,
struct row *row, int row_no, int col, bool has_cursor)
render_cell(struct terminal *term, pixman_image_t *pix,
pixman_region32_t *damage, struct row *row, int row_no, int col,
bool has_cursor)
{
struct cell *cell = &row->cells[col];
if (cell->attrs.clean)
@ -776,8 +803,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag
if (cell->attrs.blink && term->blink.state == BLINK_OFF)
_fg = color_decrease_luminance(_fg);
pixman_color_t fg = color_hex_to_pixman(_fg);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha);
const bool gamma_correct = render_do_linear_blending(term);
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
struct fcft_font *font = attrs_to_font(term, &cell->attrs);
const struct composed *composed = NULL;
@ -987,7 +1015,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag
if (i > 0 && glyph->x >= 0 && cell_cols == 1)
g_x -= term->cell_width;
if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) {
if (unlikely(glyph->is_color_glyph)) {
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) {
pixman_image_composite32(
@ -1071,12 +1099,12 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag
switch (range->underline.color_src) {
case COLOR_BASE256:
underline_color = color_hex_to_pixman(
term->colors.table[range->underline.color]);
term->colors.table[range->underline.color], gamma_correct);
break;
case COLOR_RGB:
underline_color =
color_hex_to_pixman(range->underline.color);
color_hex_to_pixman(range->underline.color, gamma_correct);
break;
case COLOR_DEFAULT:
@ -1105,8 +1133,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag
pixman_color_t url_color = color_hex_to_pixman(
term->conf->colors.use_custom.url
? term->conf->colors.url
: term->colors.table[3]
);
: term->colors.table[3],
gamma_correct);
draw_underline(term, pix, font, &url_color, x, y, cell_cols);
}
@ -1119,8 +1147,9 @@ draw_cursor:
}
static void
render_row(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage,
struct row *row, int row_no, int cursor_col)
render_row(struct terminal *term, pixman_image_t *pix,
pixman_region32_t *damage, struct row *row,
int row_no, int cursor_col)
{
for (int col = term->cols - 1; col >= 0; col--)
render_cell(term, pix, damage, row, row_no, col, cursor_col == col);
@ -1130,7 +1159,7 @@ static void
render_urgency(struct terminal *term, struct buffer *buf)
{
uint32_t red = term->colors.table[1];
pixman_color_t bg = color_hex_to_pixman(red);
pixman_color_t bg = color_hex_to_pixman(red, render_do_linear_blending(term));
int width = min(min(term->margins.left, term->margins.right),
min(term->margins.top, term->margins.bottom));
@ -1161,6 +1190,7 @@ render_margin(struct terminal *term, struct buffer *buf,
const int bmargin = term->height - term->margins.bottom;
const int line_count = end_line - start_line;
const bool gamma_correct = render_do_linear_blending(term);
const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg;
uint16_t alpha = term->colors.alpha;
@ -1169,7 +1199,7 @@ render_margin(struct terminal *term, struct buffer *buf,
alpha = 0xffff;
}
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &bg, 4,
@ -1596,8 +1626,7 @@ render_sixel(struct terminal *term, pixman_image_t *pix,
static void
render_sixel_images(struct terminal *term, pixman_image_t *pix,
pixman_region32_t *damage,
const struct coord *cursor)
pixman_region32_t *damage, const struct coord *cursor)
{
if (likely(tll_length(term->grid->sixel_images)) == 0)
return;
@ -1649,6 +1678,8 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
if (unlikely(term->is_searching))
return;
const bool gamma_correct = render_do_linear_blending(term);
/* Adjust cursor position to viewport */
struct coord cursor;
cursor = term->grid->cursor.point;
@ -1753,12 +1784,12 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
if (!seat->ime.preedit.cursor.hidden) {
const struct cell *start_cell = &seat->ime.preedit.cells[0];
pixman_color_t fg = color_hex_to_pixman(term->colors.fg);
pixman_color_t bg = color_hex_to_pixman(term->colors.bg);
pixman_color_t fg = color_hex_to_pixman(term->colors.fg, gamma_correct);
pixman_color_t bg = color_hex_to_pixman(term->colors.bg, gamma_correct);
pixman_color_t cursor_color, text_color;
cursor_colors_for_cell(
term, start_cell, &fg, &bg, &cursor_color, &text_color);
term, start_cell, &fg, &bg, &cursor_color, &text_color, gamma_correct);
int x = term->margins.left + (col_idx + start) * term->cell_width;
int y = term->margins.top + row_idx * term->cell_height;
@ -1789,12 +1820,14 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat,
row->cells[col_idx + i] = real_cells[i];
free(real_cells);
const int damage_x = term->margins.left + col_idx * term->cell_width;
const int damage_y = term->margins.top + row_idx * term->cell_height;
const int damage_w = cells_used * term->cell_width;
const int damage_h = term->cell_height;
wl_surface_damage_buffer(
term->window->surface.surf,
term->margins.left,
term->margins.top + row_idx * term->cell_height,
term->width - term->margins.left - term->margins.right,
1 * term->cell_height);
damage_x, damage_y, damage_w, damage_h);
}
#endif
@ -1916,7 +1949,7 @@ render_overlay(struct terminal *term)
case OVERLAY_FLASH:
color = color_hex_to_pixman_with_alpha(
term->conf->colors.flash,
term->conf->colors.flash_alpha);
term->conf->colors.flash_alpha, render_do_linear_blending(term));
break;
case OVERLAY_NONE:
@ -2118,6 +2151,7 @@ render_worker_thread(void *_ctx)
sem_wait(start);
struct buffer *buf = term->render.workers.buf;
bool frame_done = false;
/* Translate offset-relative cursor row to view-relative */
@ -2138,8 +2172,6 @@ render_worker_thread(void *_ctx)
switch (row_no) {
default: {
xassert(buf != NULL);
struct row *row = grid_row_in_view(term->grid, row_no);
int cursor_col = cursor.row == row_no ? cursor.col : -1;
@ -2259,13 +2291,14 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf,
pixman_image_set_clip_region32(buf->pix[0], &clip);
pixman_region32_fini(&clip);
const bool gamma_correct = render_do_linear_blending(term);
uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha);
pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &bg, 1,
&(pixman_rectangle16_t){0, 0, buf->width, buf->height});
pixman_color_t fg = color_hex_to_pixman(_fg);
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);
const int x_ofs = term->font_x_ofs;
const size_t len = c32len(text);
@ -2312,7 +2345,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf,
for (size_t i = 0; i < glyph_count; i++) {
const struct fcft_glyph *glyph = glyphs[i];
if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) {
if (unlikely(glyph->is_color_glyph)) {
pixman_image_composite32(
PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0,
x + x_ofs + glyph->x, y - glyph->y,
@ -2399,8 +2432,11 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx,
if (info->width == 0 || info->height == 0)
return;
const bool gamma_correct = render_do_linear_blending(term);
{
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
/* Fully transparent - no need to do a color space transform */
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0, gamma_correct);
render_csd_part(term, surf->surf, buf, info->width, info->height, &color);
}
@ -2461,7 +2497,8 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx,
_color = color_dim(term, _color);
uint16_t alpha = _color >> 24 | (_color >> 24 << 8);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
pixman_color_t color =
color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &color, 1,
@ -2472,8 +2509,9 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx,
}
static pixman_color_t
get_csd_button_fg_color(const struct config *conf)
get_csd_button_fg_color(const struct terminal *term)
{
const struct config *conf = term->conf;
uint32_t _color = conf->colors.bg;
uint16_t alpha = 0xffff;
@ -2482,13 +2520,14 @@ get_csd_button_fg_color(const struct config *conf)
alpha = _color >> 24 | (_color >> 24 << 8);
}
return color_hex_to_pixman_with_alpha(_color, alpha);
return color_hex_to_pixman_with_alpha(
_color, alpha, render_do_linear_blending(term));
}
static void
render_csd_button_minimize(struct terminal *term, struct buffer *buf)
{
pixman_color_t color = get_csd_button_fg_color(term->conf);
pixman_color_t color = get_csd_button_fg_color(term);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
@ -2516,7 +2555,7 @@ static void
render_csd_button_maximize_maximized(
struct terminal *term, struct buffer *buf)
{
pixman_color_t color = get_csd_button_fg_color(term->conf);
pixman_color_t color = get_csd_button_fg_color(term);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
@ -2548,7 +2587,7 @@ static void
render_csd_button_maximize_window(
struct terminal *term, struct buffer *buf)
{
pixman_color_t color = get_csd_button_fg_color(term->conf);
pixman_color_t color = get_csd_button_fg_color(term);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
@ -2588,7 +2627,7 @@ render_csd_button_maximize(struct terminal *term, struct buffer *buf)
static void
render_csd_button_close(struct terminal *term, struct buffer *buf)
{
pixman_color_t color = get_csd_button_fg_color(term->conf);
pixman_color_t color = get_csd_button_fg_color(term);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
@ -2759,14 +2798,14 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx,
if (!term->visual_focus)
_color = color_dim(term, _color);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
const bool gamma_correct = render_do_linear_blending(term);
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct);
render_csd_part(term, surf->surf, buf, info->width, info->height, &color);
switch (surf_idx) {
case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break;
case CSD_SURF_MAXIMIZE: render_csd_button_maximize(term, buf); break;
case CSD_SURF_CLOSE: render_csd_button_close(term, buf); break;
break;
default:
BUG("unhandled surface type: %u", (unsigned)surf_idx);
@ -3618,6 +3657,7 @@ render_search_box(struct terminal *term)
: term->conf->colors.use_custom.search_box_no_match;
/* Background - yellow on empty/match, red on mismatch (default) */
const bool gamma_correct = render_do_linear_blending(term);
const pixman_color_t color = color_hex_to_pixman(
is_match
? (custom_colors
@ -3625,13 +3665,14 @@ render_search_box(struct terminal *term)
: term->colors.table[3])
: (custom_colors
? term->conf->colors.search_box.no_match.bg
: term->colors.table[1]));
: term->colors.table[1]),
gamma_correct);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &color,
1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height});
pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0);
pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0, gamma_correct);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix[0], &transparent,
1, &(pixman_rectangle16_t){0, 0, width - visible_width, height});
@ -3641,12 +3682,14 @@ render_search_box(struct terminal *term)
const int x_ofs = term->font_x_ofs;
int x = x_left;
int y = margin;
pixman_color_t fg = color_hex_to_pixman(
custom_colors
? (is_match
? term->conf->colors.search_box.match.fg
: term->conf->colors.search_box.no_match.fg)
: term->colors.table[0]);
: term->colors.table[0],
gamma_correct);
/* Move offset we start rendering at, to ensure the cursor is visible */
for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) {
@ -3802,8 +3845,7 @@ render_search_box(struct terminal *term)
continue;
}
if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) {
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
if (unlikely(glyph->is_color_glyph)) {
pixman_image_composite32(
PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0,
x + x_ofs + glyph->x, y + term->font_baseline - glyph->y,
@ -5186,3 +5228,14 @@ render_xcursor_set(struct seat *seat, struct terminal *term,
seat->pointer.xcursor_pending = true;
return true;
}
bool
render_do_linear_blending(const struct terminal *term)
{
#if defined(HAVE_WP_COLOR_MANAGEMENT)
return term->conf->gamma_correct != GAMMA_CORRECT_DISABLED &&
term->wl->color_management.img_description != NULL;
#else
return false;
#endif
}

View file

@ -47,3 +47,5 @@ struct csd_data {
};
struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx);
bool render_do_linear_blending(const struct terminal *term);

90
scripts/srgb.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
import argparse
import math
import sys
def srgb_to_linear(f: float) -> float:
assert(f >= 0 and f <= 1.0)
if f <= 0.04045:
return f / 12.92
return math.pow((f + 0.055) / 1.055, 2.4)
def linear_to_srgb(f: float) -> float:
if f < 0.0031308:
return f * 12.92
return 1.055 * math.pow(f, 1 / 2.4) - 0.055
def main():
parser = argparse.ArgumentParser()
parser.add_argument('c_output', type=argparse.FileType('w'))
parser.add_argument('h_output', type=argparse.FileType('w'))
opts = parser.parse_args()
linear_table: list[int] = []
srgb_table: list[int] = []
for i in range(256):
linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5))
for i in range(4096):
srgb_table.append(int(linear_to_srgb(float(i) / 4095) * 255 + 0.5))
for i in range(256):
while True:
linear = linear_table[i]
srgb = srgb_table[linear >> 4]
if i == srgb:
break
linear_table[i] += 1
opts.h_output.write("#pragma once\n")
opts.h_output.write("#include <stdint.h>\n")
opts.h_output.write("\n")
opts.h_output.write('/* 8-bit input, 16-bit output */\n')
opts.h_output.write("extern const uint16_t srgb_decode_8_to_16_table[256];")
opts.h_output.write('\n')
opts.h_output.write('static inline uint16_t\n')
opts.h_output.write('srgb_decode_8_to_16(uint8_t v)\n')
opts.h_output.write('{\n')
opts.h_output.write(' return srgb_decode_8_to_16_table[v];\n')
opts.h_output.write('}\n')
opts.h_output.write('\n')
opts.h_output.write('/* 8-bit input, 8-bit output */\n')
opts.h_output.write("extern const uint8_t srgb_decode_8_to_8_table[256];\n")
opts.h_output.write('\n')
opts.h_output.write('static inline uint8_t\n')
opts.h_output.write('srgb_decode_8_to_8(uint8_t v)\n')
opts.h_output.write('{\n')
opts.h_output.write(' return srgb_decode_8_to_8_table[v];\n')
opts.h_output.write('}\n')
opts.c_output.write('#include "srgb.h"\n')
opts.c_output.write('\n')
opts.c_output.write("const uint16_t srgb_decode_8_to_16_table[256] = {\n")
for i in range(256):
opts.c_output.write(f' {linear_table[i]},\n')
opts.c_output.write('};\n')
opts.c_output.write("const uint8_t srgb_decode_8_to_8_table[256] = {\n")
for i in range(256):
opts.c_output.write(f' {linear_table[i] >> 8},\n')
opts.c_output.write('};\n')
if __name__ == '__main__':
sys.exit(main())

80
shm.c
View file

@ -92,6 +92,12 @@ struct buffer_chain {
struct wl_shm *shm;
size_t pix_instances;
bool scrollable;
pixman_format_code_t pixman_fmt_without_alpha;
enum wl_shm_format shm_format_without_alpha;
pixman_format_code_t pixman_fmt_with_alpha;
enum wl_shm_format shm_format_with_alpha;
};
static tll(struct buffer_private *) deferred;
@ -115,6 +121,7 @@ buffer_destroy_dont_close(struct buffer *buf)
if (buf->pix[i] != NULL)
pixman_image_unref(buf->pix[i]);
}
if (buf->wl_buf != NULL)
wl_buffer_destroy(buf->wl_buf);
@ -262,7 +269,9 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
wl_buf = wl_shm_pool_create_buffer(
pool->wl_pool, new_offset,
buf->public.width, buf->public.height, buf->public.stride,
buf->with_alpha ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888);
buf->with_alpha
? buf->chain->shm_format_with_alpha
: buf->chain->shm_format_without_alpha);
if (wl_buf == NULL) {
LOG_ERR("failed to create SHM buffer");
@ -272,9 +281,12 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
/* One pixman image for each worker thread (do we really need multiple?) */
for (size_t i = 0; i < buf->public.pix_instances; i++) {
pix[i] = pixman_image_create_bits_no_clear(
buf->with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
buf->with_alpha
? buf->chain->pixman_fmt_with_alpha
: buf->chain->pixman_fmt_without_alpha,
buf->public.width, buf->public.height,
(uint32_t *)mmapped, buf->public.stride);
if (pix[i] == NULL) {
LOG_ERR("failed to create pixman image");
goto err;
@ -959,14 +971,74 @@ shm_unref(struct buffer *_buf)
}
struct buffer_chain *
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_if_capable)
{
pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8;
enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888;
pixman_format_code_t pixman_fmt_with_alpha = PIXMAN_a8r8g8b8;
enum wl_shm_format shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB8888;
static bool have_logged = false;
if (ten_bit_if_capable) {
if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) {
pixman_fmt_without_alpha = PIXMAN_x2r10g10b10;
shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010;
pixman_fmt_with_alpha = PIXMAN_a2r10g10b10;
shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB2101010;
if (!have_logged) {
have_logged = true;
LOG_INFO("using 10-bit RGB surfaces");
}
}
else if (wayl->shm_have_abgr2101010 && wayl->shm_have_xbgr2101010) {
pixman_fmt_without_alpha = PIXMAN_x2b10g10r10;
shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR2101010;
pixman_fmt_with_alpha = PIXMAN_a2b10g10r10;
shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR2101010;
if (!have_logged) {
have_logged = true;
LOG_INFO("using 10-bit BGR surfaces");
}
}
else {
if (!have_logged) {
have_logged = true;
LOG_WARN(
"10-bit surfaces requested, but compositor does not "
"implement ARGB2101010+XRGB2101010, or "
"ABGR2101010+XBGR2101010. Falling back to 8-bit surfaces");
}
}
} else {
if (!have_logged) {
have_logged = true;
LOG_INFO("using 8-bit RGB surfaces");
}
}
struct buffer_chain *chain = xmalloc(sizeof(*chain));
*chain = (struct buffer_chain){
.bufs = tll_init(),
.shm = shm,
.shm = wayl->shm,
.pix_instances = pix_instances,
.scrollable = scrollable,
.pixman_fmt_without_alpha = pixman_fmt_without_alpha,
.shm_format_without_alpha = shm_fmt_without_alpha,
.pixman_fmt_with_alpha = pixman_fmt_with_alpha,
.shm_format_with_alpha = shm_fmt_with_alpha,
};
return chain;
}

5
shm.h
View file

@ -9,6 +9,8 @@
#include <tllist.h>
#include "wayland.h"
struct damage;
struct buffer {
@ -43,7 +45,8 @@ void shm_set_max_pool_size(off_t max_pool_size);
struct buffer_chain;
struct buffer_chain *shm_chain_new(
struct wl_shm *shm, bool scrollable, size_t pix_instances);
struct wayland *wayl, bool scrollable, size_t pix_instances,
bool ten_bit_it_if_capable);
void shm_chain_free(struct buffer_chain *chain);
/*

97
sixel.c
View file

@ -10,6 +10,7 @@
#include "grid.h"
#include "hsl.h"
#include "render.h"
#include "srgb.h"
#include "util.h"
#include "xmalloc.h"
#include "xsnprintf.h"
@ -19,6 +20,40 @@ static size_t count;
static void sixel_put_generic(struct terminal *term, uint8_t c);
static void sixel_put_ar_11(struct terminal *term, uint8_t c);
static uint32_t
color_decode_srgb(const struct terminal *term, uint16_t r, uint16_t g, uint16_t b)
{
if (term->sixel.linear_blending) {
if (term->sixel.use_10bit) {
r = srgb_decode_8_to_16(r) >> 6;
g = srgb_decode_8_to_16(g) >> 6;
b = srgb_decode_8_to_16(b) >> 6;
} else {
r = srgb_decode_8_to_8(r);
g = srgb_decode_8_to_8(g);
b = srgb_decode_8_to_8(b);
}
} else {
if (term->sixel.use_10bit) {
r <<= 2;
g <<= 2;
b <<= 2;
}
}
uint32_t color;
if (term->sixel.use_10bit) {
if (PIXMAN_FORMAT_TYPE(term->sixel.pixman_fmt) == PIXMAN_TYPE_ARGB)
color = 0x3u << 30 | r << 20 | g << 10 | b;
else
color = 0x3u << 30 | b << 20 | g << 10 | r;
} else
color = 0xffu << 24 | r << 16 | g << 8 | b;
return color;
}
void
sixel_fini(struct terminal *term)
{
@ -75,6 +110,23 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
term->sixel.image.height = 0;
term->sixel.image.alloc_height = 0;
term->sixel.image.bottom_pixel = 0;
term->sixel.linear_blending = render_do_linear_blending(term);
term->sixel.pixman_fmt = PIXMAN_a8r8g8b8;
if (term->conf->tweak.surface_bit_depth == SHM_10_BIT) {
if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) {
term->sixel.use_10bit = true;
term->sixel.pixman_fmt = PIXMAN_a2r10g10b10;
}
else if (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010) {
term->sixel.use_10bit = true;
term->sixel.pixman_fmt = PIXMAN_a2b10g10r10;
}
}
const size_t active_palette_entries = min(
ALEN(term->conf->colors.sixel), term->sixel.palette_size);
if (term->sixel.use_private_palette) {
xassert(term->sixel.private_palette == NULL);
@ -83,11 +135,18 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
memcpy(
term->sixel.private_palette, term->conf->colors.sixel,
min(sizeof(term->conf->colors.sixel),
term->sixel.palette_size * sizeof(term->sixel.private_palette[0])));
active_palette_entries * sizeof(term->sixel.private_palette[0]));
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
}
}
term->sixel.palette = term->sixel.private_palette;
} else {
if (term->sixel.shared_palette == NULL) {
term->sixel.shared_palette = xcalloc(
@ -95,8 +154,16 @@ sixel_init(struct terminal *term, int p1, int p2, int p3)
memcpy(
term->sixel.shared_palette, term->conf->colors.sixel,
min(sizeof(term->conf->colors.sixel),
term->sixel.palette_size * sizeof(term->sixel.shared_palette[0])));
active_palette_entries * sizeof(term->sixel.shared_palette[0]));
if (term->sixel.linear_blending || term->sixel.use_10bit) {
for (size_t i = 0; i < active_palette_entries; i++) {
uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff;
uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff;
uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff;
term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b);
}
}
} else {
/* Shared palette - do *not* reset palette for new sixels */
}
@ -488,7 +555,7 @@ blend_new_image_over_old(const struct terminal *term,
int stride = new_width * sizeof(uint32_t);
uint32_t *new_data = xmalloc(stride * new_height);
pixman_image_t *pix2 = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, new_width, new_height, new_data, stride);
term->sixel.pixman_fmt, new_width, new_height, new_data, stride);
#if defined(_DEBUG)
/* Fill new image with an easy-to-recognize color (green) */
@ -651,8 +718,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
}
pixman_image_t *new_pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8,
new_width, new_height, new_data, new_width * sizeof(uint32_t));
term->sixel.pixman_fmt, new_width, new_height, new_data, new_width * sizeof(uint32_t));
struct sixel new_six = {
.pix = NULL,
@ -948,7 +1014,7 @@ sixel_sync_cache(const struct terminal *term, struct sixel *six)
uint8_t *scaled_data = xmalloc(scaled_height * scaled_stride);
pixman_image_t *scaled_pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, scaled_width, scaled_height,
term->sixel.pixman_fmt, scaled_width, scaled_height,
(uint32_t *)scaled_data, scaled_stride);
pixman_image_composite32(
@ -1232,7 +1298,7 @@ sixel_unhook(struct terminal *term)
image.pos.row, image.pos.row + image.rows);
image.original.pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, image.original.width, image.original.height,
term->sixel.pixman_fmt, image.original.width, image.original.height,
img_data, stride);
pixel_row_idx += height;
@ -2006,15 +2072,14 @@ decgci(struct terminal *term, uint8_t c)
}
case 2: { /* RGB */
uint8_t r = 255 * min(c1, 100) / 100;
uint8_t g = 255 * min(c2, 100) / 100;
uint8_t b = 255 * min(c3, 100) / 100;
uint16_t r = 255 * min(c1, 100) / 100;
uint16_t g = 255 * min(c2, 100) / 100;
uint16_t b = 255 * min(c3, 100) / 100;
LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu",
LOG_DBG("setting palette #%d = RGB %hu/%hu/%hu",
term->sixel.color_idx, r, g, b);
term->sixel.palette[term->sixel.color_idx] =
0xffu << 24 | r << 16 | g << 8 | b;
term->sixel.palette[term->sixel.color_idx] = color_decode_srgb(term, r, g, b);
break;
}
}

View file

@ -991,6 +991,7 @@ struct font_load_data {
const char **names;
const char *attrs;
const struct fcft_font_options *options;
struct fcft_font **font;
};
@ -998,7 +999,8 @@ static int
font_loader_thread(void *_data)
{
struct font_load_data *data = _data;
*data->font = fcft_from_name(data->count, data->names, data->attrs);
*data->font = fcft_from_name2(
data->count, data->names, data->attrs, data->options);
return *data->font != NULL;
}
@ -1065,14 +1067,32 @@ reload_fonts(struct terminal *term, bool resize_grid)
[1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""),
[2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""),
[3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""),
};
};
struct fcft_font_options *options = fcft_font_options_create();
options->color_glyphs.format = PIXMAN_a8r8g8b8;
options->color_glyphs.srgb_decode = render_do_linear_blending(term);
if (conf->tweak.surface_bit_depth == SHM_10_BIT) {
if ((term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) ||
(term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010))
{
/*
* Use a high-res buffer type for emojis. We don't want to
* use an a2r10g0b10 type of surface, since we need more
* than 2 bits for alpha.
*/
options->color_glyphs.format = PIXMAN_rgba_float;
}
}
struct fcft_font *fonts[4];
struct font_load_data data[4] = {
{count_regular, names_regular, attrs[0], &fonts[0]},
{count_bold, names_bold, attrs[1], &fonts[1]},
{count_italic, names_italic, attrs[2], &fonts[2]},
{count_bold_italic, names_bold_italic, attrs[3], &fonts[3]},
{count_regular, names_regular, attrs[0], options, &fonts[0]},
{count_bold, names_bold, attrs[1], options, &fonts[1]},
{count_italic, names_italic, attrs[2], options, &fonts[2]},
{count_bold_italic, names_bold_italic, attrs[3], options, &fonts[3]},
};
thrd_t tids[4] = {0};
@ -1097,6 +1117,8 @@ reload_fonts(struct terminal *term, bool resize_grid)
success = false;
}
fcft_font_options_destroy(options);
for (size_t i = 0; i < 4; i++) {
for (size_t j = 0; j < counts[i]; j++)
free(names[i][j]);
@ -1237,6 +1259,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
goto err;
}
const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT;
/* Initialize configure-based terminal attributes */
*term = (struct terminal) {
.fdm = fdm,
@ -1320,13 +1344,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
.wl = wayl,
.render = {
.chains = {
.grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count),
.search = shm_chain_new(wayl->shm, false, 1),
.scrollback_indicator = shm_chain_new(wayl->shm, false, 1),
.render_timer = shm_chain_new(wayl->shm, false, 1),
.url = shm_chain_new(wayl->shm, false, 1),
.csd = shm_chain_new(wayl->shm, false, 1),
.overlay = shm_chain_new(wayl->shm, false, 1),
.grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count,
ten_bit_surfaces),
.search = shm_chain_new(wayl, false, 1 ,ten_bit_surfaces),
.scrollback_indicator = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.render_timer = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.url = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.csd = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
.overlay = shm_chain_new(wayl, false, 1, ten_bit_surfaces),
},
.scrollback_lines = conf->scrollback.lines,
.app_sync_updates.timer_fd = app_sync_updates_fd,
@ -1468,6 +1493,9 @@ term_window_configured(struct terminal *term)
if (!term->shutdown.in_progress) {
xassert(term->window->is_configured);
fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term);
const bool gamma_correct = render_do_linear_blending(term);
LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled");
}
}

View file

@ -777,6 +777,10 @@ struct terminal {
bool transparent_bg;
bool linear_blending;
bool use_10bit;
pixman_format_code_t pixman_fmt;
/* Application configurable */
unsigned palette_size; /* Number of colors in palette */
unsigned max_width; /* Maximum image width, in pixels */

182
wayland.c
View file

@ -237,6 +237,15 @@ seat_destroy(struct seat *seat)
static void
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
struct wayland *wayl = data;
switch (format) {
case WL_SHM_FORMAT_XRGB2101010: wayl->shm_have_xrgb2101010 = true; break;
case WL_SHM_FORMAT_ARGB2101010: wayl->shm_have_argb2101010 = true; break;
case WL_SHM_FORMAT_XBGR2101010: wayl->shm_have_xbgr2101010 = true; break;
case WL_SHM_FORMAT_ABGR2101010: wayl->shm_have_abgr2101010 = true; break;
}
#if defined(_DEBUG)
bool have_description = false;
@ -666,6 +675,91 @@ static const struct wp_presentation_listener presentation_listener = {
.clock_id = &clock_id,
};
#if defined(HAVE_WP_COLOR_MANAGEMENT)
static void
color_manager_create_image_description(struct wayland *wayl)
{
if (!wayl->color_management.have_feat_parametric)
return;
if (!wayl->color_management.have_primaries_srgb)
return;
if (!wayl->color_management.have_tf_ext_linear)
return;
struct wp_image_description_creator_params_v1 *params =
wp_color_manager_v1_create_parametric_creator(wayl->color_management.manager);
wp_image_description_creator_params_v1_set_tf_named(
params, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR);
wp_image_description_creator_params_v1_set_primaries_named(
params, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB);
wayl->color_management.img_description =
wp_image_description_creator_params_v1_create(params);
}
static void
color_manager_supported_intent(void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t render_intent)
{
struct wayland *wayl = data;
if (render_intent == WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL)
wayl->color_management.have_intent_perceptual = true;
}
static void
color_manager_supported_feature(void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t feature)
{
struct wayland *wayl = data;
if (feature == WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC)
wayl->color_management.have_feat_parametric = true;
}
static void
color_manager_supported_tf_named(void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t tf)
{
struct wayland *wayl = data;
if (tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR)
wayl->color_management.have_tf_ext_linear = true;
}
static void
color_manager_supported_primaries_named(void *data,
struct wp_color_manager_v1 *wp_color_manager_v1,
uint32_t primaries)
{
struct wayland *wayl = data;
if (primaries == WP_COLOR_MANAGER_V1_PRIMARIES_SRGB)
wayl->color_management.have_primaries_srgb = true;
}
static void
color_manager_done(void *data,
struct wp_color_manager_v1 *wp_color_manager_v1)
{
struct wayland *wayl = data;
color_manager_create_image_description(wayl);
}
static const struct wp_color_manager_v1_listener color_manager_listener = {
.supported_intent = &color_manager_supported_intent,
.supported_feature = &color_manager_supported_feature,
.supported_primaries_named = &color_manager_supported_primaries_named,
.supported_tf_named = &color_manager_supported_tf_named,
.done = &color_manager_done,
};
#endif
static bool
verify_iface_version(const char *iface, uint32_t version, uint32_t wanted)
{
@ -1385,6 +1479,20 @@ handle_global(void *data, struct wl_registry *registry,
}
#endif
#if defined(HAVE_WP_COLOR_MANAGEMENT)
else if (streq(interface, wp_color_manager_v1_interface.name)) {
const uint32_t required = 1;
if (!verify_iface_version(interface, version, required))
return;
wayl->color_management.manager = wl_registry_bind(
wayl->registry, name, &wp_color_manager_v1_interface, required);
wp_color_manager_v1_add_listener(
wayl->color_management.manager, &color_manager_listener, wayl);
}
#endif
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
else if (streq(interface, zwp_text_input_manager_v3_interface.name)) {
const uint32_t required = 1;
@ -1707,6 +1815,13 @@ wayl_destroy(struct wayland *wayl)
zwp_text_input_manager_v3_destroy(wayl->text_input_manager);
#endif
#if defined(HAVE_WP_COLOR_MANAGEMENT)
if (wayl->color_management.img_description != NULL)
wp_image_description_v1_destroy(wayl->color_management.img_description);
if (wayl->color_management.manager != NULL)
wp_color_manager_v1_destroy(wayl->color_management.manager);
#endif
#if defined(HAVE_XDG_SYSTEM_BELL)
if (wayl->system_bell != NULL)
xdg_system_bell_v1_destroy(wayl->system_bell);
@ -1847,6 +1962,38 @@ wayl_win_init(struct terminal *term, const char *token)
}
#endif
#if defined(HAVE_WP_COLOR_MANAGEMENT)
if (term->conf->gamma_correct != GAMMA_CORRECT_DISABLED) {
if (wayl->color_management.img_description != NULL) {
xassert(wayl->color_management.manager != NULL);
win->surface.color_management = wp_color_manager_v1_get_surface(
term->wl->color_management.manager, win->surface.surf);
wp_color_management_surface_v1_set_image_description(
win->surface.color_management, wayl->color_management.img_description,
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
} else if (term->conf->gamma_correct == GAMMA_CORRECT_ENABLED) {
if (wayl->color_management.manager == NULL) {
LOG_WARN(
"gamma-corrected-blending: disabling; "
"compositor does not implement the color-management protocol");
} else {
LOG_WARN(
"gamma-corrected-blending: disabling; "
"compositor does not implement all required color-management features");
LOG_WARN("use e.g. 'wayland-info' and verify the compositor implements:");
LOG_WARN(" - feature: parametric");
LOG_WARN(" - render intent: perceptual");
LOG_WARN(" - TF: ext_linear");
LOG_WARN(" - primaries: sRGB");
}
} else {
/* "auto" - don't warn */
}
}
#endif
if (conf->csd.preferred == CONF_CSD_PREFER_NONE) {
/* User specifically do *not* want decorations */
win->csd_mode = CSD_NO;
@ -1861,8 +2008,8 @@ wayl_win_init(struct terminal *term, const char *token)
zxdg_toplevel_decoration_v1_set_mode(
win->xdg_toplevel_decoration,
(conf->csd.preferred == CONF_CSD_PREFER_SERVER
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE));
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE));
zxdg_toplevel_decoration_v1_add_listener(
win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, win);
@ -1987,7 +2134,12 @@ wayl_win_destroy(struct wl_window *win)
free(it->item);
tll_remove(win->xdg_tokens, it);
}
}
#if defined(HAVE_WP_COLOR_MANAGEMENT)
if (win->surface.color_management != NULL)
wp_color_management_surface_v1_destroy(win->surface.color_management);
#endif
if (win->fractional_scale != NULL)
wp_fractional_scale_v1_destroy(win->fractional_scale);
@ -2308,6 +2460,7 @@ wayl_win_subsurface_new_with_custom_parent(
struct wayland *wayl = win->term->wl;
surf->surface.surf = NULL;
surf->surface.viewport = NULL;
surf->sub = NULL;
struct wl_surface *main_surface
@ -2318,6 +2471,22 @@ wayl_win_subsurface_new_with_custom_parent(
return false;
}
#if defined(HAVE_WP_COLOR_MANAGEMENT)
surf->surface.color_management = NULL;
if (win->term->conf->gamma_correct &&
wayl->color_management.img_description != NULL)
{
xassert(wayl->color_management.manager != NULL);
surf->surface.color_management = wp_color_manager_v1_get_surface(
wayl->color_management.manager, main_surface);
wp_color_management_surface_v1_set_image_description(
surf->surface.color_management, wayl->color_management.img_description,
WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL);
}
#endif
struct wl_subsurface *sub = wl_subcompositor_get_subsurface(
wayl->sub_compositor, main_surface, parent);
@ -2369,6 +2538,13 @@ wayl_win_subsurface_destroy(struct wayl_sub_surface *surf)
if (surf == NULL)
return;
#if defined(HAVE_WP_COLOR_MANAGEMENT)
if (surf->surface.color_management != NULL) {
wp_color_management_surface_v1_destroy(surf->surface.color_management);
surf->surface.color_management = NULL;
}
#endif
if (surf->surface.viewport != NULL) {
wp_viewport_destroy(surf->surface.viewport);
surf->surface.viewport = NULL;

View file

@ -28,6 +28,10 @@
#include <xdg-system-bell-v1.h>
#endif
#if defined(HAVE_WP_COLOR_MANAGEMENT)
#include <color-management-v1.h>
#endif
#include <fcft/fcft.h>
#include <tllist.h>
@ -61,6 +65,9 @@ enum touch_state {
struct wayl_surface {
struct wl_surface *surf;
struct wp_viewport *viewport;
#if defined(HAVE_WP_COLOR_MANAGEMENT)
struct wp_color_management_surface_v1 *color_management;
#endif
};
struct wayl_sub_surface {
@ -459,6 +466,17 @@ struct wayland {
struct xdg_system_bell_v1 *system_bell;
#endif
#if defined(HAVE_WP_COLOR_MANAGEMENT)
struct {
struct wp_color_manager_v1 *manager;
struct wp_image_description_v1 *img_description;
bool have_intent_perceptual;
bool have_feat_parametric;
bool have_tf_ext_linear;
bool have_primaries_srgb;
} color_management;
#endif
bool presentation_timings;
struct wp_presentation *presentation;
uint32_t presentation_clock_id;
@ -474,6 +492,11 @@ struct wayland {
/* WL_SHM >= 2 */
bool use_shm_release;
bool shm_have_argb2101010:1;
bool shm_have_xrgb2101010:1;
bool shm_have_abgr2101010:1;
bool shm_have_xbgr2101010:1;
};
struct wayland *wayl_init(