render: dim and brighten using linear rgb interpolation

Adds setting tweak.dim-amount, similar to bold-text-in-bright-amount.

Closes #2006
This commit is contained in:
Sam McCall 2025-03-22 20:11:23 +01:00 committed by Daniel Eklöf
parent 5f72f51ae8
commit 663c9082db
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
7 changed files with 51 additions and 66 deletions

View file

@ -64,6 +64,12 @@
### Changed ### Changed
* UTF-8 error recovery now discards fewer bytes. * UTF-8 error recovery now discards fewer bytes.
* Auto-calculated dimmed and brightened colors (e.g. when custom dim
colors has not configured) is now done by linear RGB interpolation,
rather than converting to HSL and adjusting the luminance
([#2006][2006]).
[2006]: https://codeberg.org/dnkl/foot/issues/2006
### Deprecated ### Deprecated

View file

@ -2759,6 +2759,9 @@ parse_section_tweak(struct context *ctx)
else if (streq(key, "sixel")) else if (streq(key, "sixel"))
return value_to_bool(ctx, &conf->tweak.sixel); return value_to_bool(ctx, &conf->tweak.sixel);
else if (streq(key, "dim-amount"))
return value_to_float(ctx, &conf->dim.amount);
else if (streq(key, "bold-text-in-bright-amount")) else if (streq(key, "bold-text-in-bright-amount"))
return value_to_float(ctx, &conf->bold_in_bright.amount); return value_to_float(ctx, &conf->bold_in_bright.amount);
@ -3288,6 +3291,7 @@ config_load(struct config *conf, const char *conf_path,
.resize_by_cells = true, .resize_by_cells = true,
.resize_keep_grid = true, .resize_keep_grid = true,
.resize_delay_ms = 100, .resize_delay_ms = 100,
.dim = { .amount = 1.5 },
.bold_in_bright = { .bold_in_bright = {
.enabled = false, .enabled = false,
.palette_based = false, .palette_based = false,

View file

@ -155,6 +155,10 @@ struct config {
uint16_t resize_delay_ms; uint16_t resize_delay_ms;
struct {
float amount;
} dim;
struct { struct {
bool enabled; bool enabled;
bool palette_based; bool palette_based;

View file

@ -396,7 +396,7 @@ empty string to be set, but it must be quoted: *KEY=""*)
*bold-text-in-bright* *bold-text-in-bright*
Semi-boolean. When enabled, bold text is rendered in a brighter Semi-boolean. When enabled, bold text is rendered in a brighter
color (in addition to using a bold font). The color is brightened color (in addition to using a bold font). The color is brightened
by increasing its luminance. by blending it with white.
If set to *palette-based*, rather than a simple *yes|true*, colors If set to *palette-based*, rather than a simple *yes|true*, colors
matching one of the 8 regular palette colors will be brightened matching one of the 8 regular palette colors will be brightened
@ -986,8 +986,8 @@ can configure the background transparency with the _alpha_ option.
an entry in the color palette. Applications emit them by combining an entry in the color palette. Applications emit them by combining
a color value, and a "dim" attribute. a color value, and a "dim" attribute.
By default, foot implements this by reducing the luminance of the By default, foot implements this by blending the current color
current color. This is a generic approach that applies to both with black. This is a generic approach that applies to both
colors from the 256-color palette, as well as 24-bit RGB colors. colors from the 256-color palette, as well as 24-bit RGB colors.
You can change this behavior by setting the *dimN* options. When You can change this behavior by setting the *dimN* options. When
@ -999,7 +999,7 @@ can configure the background transparency with the _alpha_ option.
the corresponding *regularN* color will be used. the corresponding *regularN* color will be used.
If the current color does not match any known color, it is dimmed If the current color does not match any known color, it is dimmed
by reducing the luminance (i.e. the same behavior as if the *dimN* by blending with black (i.e. the same behavior as if the *dimN*
options are unconfigured). 24-bit RGB colors will typically fall options are unconfigured). 24-bit RGB colors will typically fall
into this category. into this category.
@ -1940,6 +1940,9 @@ any of these options.
Boolean. When enabled, foot will process sixel images. Default: Boolean. When enabled, foot will process sixel images. Default:
_yes_ _yes_
*dim-amount*
Amount by which dimmed text is darkened. Default: _1.5_.
*bold-text-in-bright-amount* *bold-text-in-bright-amount*
Amount by which bold fonts are brightened when Amount by which bold fonts are brightened when
*bold-text-in-bright* is set to *yes* (the *palette-based* variant *bold-text-in-bright* is set to *yes* (the *palette-based* variant

35
hsl.c
View file

@ -2,41 +2,6 @@
#include <math.h> #include <math.h>
#include "util.h"
void
rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum)
{
double r = (double)((rgb >> 16) & 0xff) / 255.;
double g = (double)((rgb >> 8) & 0xff) / 255.;
double b = (double)((rgb >> 0) & 0xff) / 255.;
double x_max = max(max(r, g), b);
double x_min = min(min(r, g), b);
double V = x_max;
double C = x_max - x_min;
double L = (x_max + x_min) / 2.;
*lum = 100 * L;
if (C == 0.0)
*hue = 0;
else if (V == r)
*hue = 60. * (0. + (g - b) / C);
else if (V == g)
*hue = 60. * (2. + (b - r) / C);
else if (V == b)
*hue = 60. * (4. + (r - g) / C);
if (*hue < 0)
*hue += 360;
double S = C == 0.0
? 0
: C / (1. - fabs(2. * L - 1.));
*sat = 100 * S;
}
uint32_t uint32_t
hsl_to_rgb(int hue, int sat, int lum) hsl_to_rgb(int hue, int sat, int lum)
{ {

1
hsl.h
View file

@ -2,5 +2,4 @@
#include <stdint.h> #include <stdint.h>
void rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum);
uint32_t hsl_to_rgb(int hue, int sat, int lum); uint32_t hsl_to_rgb(int hue, int sat, int lum);

View file

@ -34,7 +34,6 @@
#include "config.h" #include "config.h"
#include "cursor-shape.h" #include "cursor-shape.h"
#include "grid.h" #include "grid.h"
#include "hsl.h"
#include "ime.h" #include "ime.h"
#include "quirks.h" #include "quirks.h"
#include "search.h" #include "search.h"
@ -271,13 +270,23 @@ color_hex_to_pixman(uint32_t color, bool srgb)
return color_hex_to_pixman_with_alpha(color, 0xffff, srgb); return color_hex_to_pixman_with_alpha(color, 0xffff, srgb);
} }
static inline int i_lerp(int from, int to, float t) {
return from + (to - from) * t;
}
static inline uint32_t static inline uint32_t
color_decrease_luminance(uint32_t color) color_blend_towards(uint32_t from, uint32_t to, float amount)
{ {
uint32_t alpha = color & 0xff000000; if (unlikely(amount == 0))
int hue, sat, lum; return from;
rgb_to_hsl(color, &hue, &sat, &lum); float t = 1 - 1/amount;
return alpha | hsl_to_rgb(hue, sat, lum / 1.5);
uint32_t alpha = from & 0xff000000;
uint8_t r = i_lerp((from>>16)&0xff, (to>>16)&0xff, t);
uint8_t g = i_lerp((from>>8)&0xff, (to>>8)&0xff, t);
uint8_t b = i_lerp((from>>0)&0xff, (to>>0)&0xff, t);
return alpha | (r<<16) | (g<<8) | (b<<0);
} }
static inline uint32_t static inline uint32_t
@ -286,25 +295,24 @@ color_dim(const struct terminal *term, uint32_t color)
const struct config *conf = term->conf; const struct config *conf = term->conf;
const uint8_t custom_dim = conf->colors.use_custom.dim; const uint8_t custom_dim = conf->colors.use_custom.dim;
if (likely(custom_dim == 0)) if (unlikely(custom_dim != 0)) {
return color_decrease_luminance(color); for (size_t i = 0; i < 8; i++) {
if (((custom_dim >> i) & 1) == 0)
continue;
for (size_t i = 0; i < 8; i++) { if (term->colors.table[0 + i] == color) {
if (((custom_dim >> i) & 1) == 0) /* "Regular" color, return the corresponding "dim" */
continue; return conf->colors.dim[i];
}
if (term->colors.table[0 + i] == color) { else if (term->colors.table[8 + i] == color) {
/* "Regular" color, return the corresponding "dim" */ /* "Bright" color, return the corresponding "regular" */
return conf->colors.dim[i]; return term->colors.table[i];
} }
else if (term->colors.table[8 + i] == color) {
/* "Bright" color, return the corresponding "regular" */
return term->colors.table[i];
} }
} }
return color_decrease_luminance(color); return color_blend_towards(color, 0x00000000, conf->dim.amount);
} }
static inline uint32_t static inline uint32_t
@ -322,11 +330,7 @@ color_brighten(const struct terminal *term, uint32_t color)
return color; return color;
} }
int hue, sat, lum; return color_blend_towards(color, 0x00ffffff, term->conf->bold_in_bright.amount);
rgb_to_hsl(color, &hue, &sat, &lum);
lum = (int)roundf(lum * term->conf->bold_in_bright.amount);
return hsl_to_rgb(hue, sat, min(lum, 100));
} }
static void static void
@ -798,7 +802,7 @@ render_cell(struct terminal *term, pixman_image_t *pix,
_fg = color_brighten(term, _fg); _fg = color_brighten(term, _fg);
if (cell->attrs.blink && term->blink.state == BLINK_OFF) if (cell->attrs.blink && term->blink.state == BLINK_OFF)
_fg = color_decrease_luminance(_fg); _fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount);
const bool gamma_correct = render_do_linear_blending(term); const bool gamma_correct = render_do_linear_blending(term);
pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct);