1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-10-13 05:43:37 -04:00
elinks/src/terminal/color.c

381 lines
9.3 KiB
C

/* Terminal color composing. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "elinks.h"
#include "terminal/color.h"
#include "terminal/draw.h"
#include "util/color.h"
#include "util/error.h"
struct rgb {
unsigned char r, g, b;
unsigned char pad;
};
#include "terminal/palette.inc"
struct rgb_cache_entry {
int color;
int level;
color_T rgb;
};
static inline int
color_distance(struct rgb *c1, struct rgb *c2)
{
int r = c1->r - c2->r;
int g = c1->g - c2->g;
int b = c1->b - c2->b;
return (3 * r * r) + (4 * g * g) + (2 * b * b);
}
#define RED_COLOR_MASK 0x00FF0000
#define GREEN_COLOR_MASK 0x0000FF00
#define BLUE_COLOR_MASK 0x000000FF
#define RED_COLOR(color) (((color) & RED_COLOR_MASK) >> 16)
#define GREEN_COLOR(color) (((color) & GREEN_COLOR_MASK) >> 8)
#define BLUE_COLOR(color) (((color) & BLUE_COLOR_MASK) >> 0)
#define RED(color) (RED_COLOR(color) << 3)
#define GREEN(color) (GREEN_COLOR(color) << 2)
#define BLUE(color) (BLUE_COLOR(color) << 0)
#define RGBCOLOR(color) (RED(color) + GREEN(color) + BLUE(color))
#define RGB_HASH_SIZE 4096
#define HASH_RGB(color, l) ((RGBCOLOR(color) + (l)) & (RGB_HASH_SIZE - 1))
/* Initialize a rgb struct from a color_T */
#define INIT_RGB(color) \
{ RED_COLOR(color), GREEN_COLOR(color), BLUE_COLOR(color) }
/* Locates the nearest terminal color. */
static inline unsigned char
get_color(color_T color, struct rgb *palette, int level)
{
static struct rgb_cache_entry cache[RGB_HASH_SIZE];
struct rgb_cache_entry *rgb_cache = &cache[HASH_RGB(color, level)];
/* Uninitialized cache entries have level set to zero. */
if (rgb_cache->level == 0
|| rgb_cache->level != level
|| rgb_cache->rgb != color) {
struct rgb rgb = INIT_RGB(color);
unsigned char nearest_color = 0;
int min_dist = color_distance(&rgb, &palette[0]);
int i;
#ifdef CONFIG_DEBUG
if (level <= 0)
INTERNAL("get_color() called with @level <= 0");
#endif
for (i = 1; i < level; i++) {
int dist = color_distance(&rgb, &palette[i]);
if (dist < min_dist) {
min_dist = dist;
nearest_color = i;
}
}
rgb_cache->color = nearest_color;
rgb_cache->level = level;
rgb_cache->rgb = color;
}
return rgb_cache->color;
}
#undef INIT_RGB
#undef HASH_RGB
#undef RGB_HASH_SIZE
#undef RED
#undef GREEN
#undef BLUE
#undef RED_COLOR
#undef GREEN_COLOR
#undef BLUE_COLOR
#undef RED_COLOR_MASK
#undef GREEN_COLOR_MASK
#undef BLUE_COLOR_MASK
/* Controls what color ranges to use when setting the terminal color. */
/* TODO: Part of the 256 color palette is gray scale, maybe we could experiment
* with a grayscale mode. ;) --jonas */
enum palette_range {
PALETTE_FULL = 0,
PALETTE_HALF,
PALETTE_RANGES, /* XXX: Keep last */
};
struct color_mode_info {
struct rgb *palette;
struct {
int bg;
int fg;
} palette_range[PALETTE_RANGES];
};
static struct color_mode_info color_mode_16 = {
palette16,
{
/* PALETTE_FULL */ { 8, 16 },
/* PALETTE_HALF */ { 8, 8 },
}
};
#ifdef CONFIG_88_COLORS
static struct color_mode_info color_mode_88 = {
palette88,
{
/* PALETTE_FULL */ { 88, 88 },
/* PALETTE_HALF */ { 88, 44 },
}
};
#endif
#ifdef CONFIG_256_COLORS
static struct color_mode_info color_mode_256 = {
palette256,
{
/* PALETTE_FULL */ { 256, 256 },
/* PALETTE_HALF */ { 256, 128 },
}
};
#endif
static struct color_mode_info *color_modes[] = {
/* COLOR_MODE_MONO */ &color_mode_16,
/* COLOR_MODE_16 */ &color_mode_16,
#ifdef CONFIG_88_COLORS
/* COLOR_MODE_88 */ &color_mode_88,
#endif
#ifdef CONFIG_256_COLORS
/* COLOR_MODE_256 */ &color_mode_256,
#endif
};
/* Colors values used in the foreground color table:
*
* 0 == black 8 == darkgrey (brightblack ;)
* 1 == red 9 == brightred
* 2 == green 10 == brightgreen
* 3 == brown 11 == brightyellow
* 4 == blue 12 == brightblue
* 5 == magenta 13 == brightmagenta
* 6 == cyan 14 == brightcyan
* 7 == white 15 == brightwhite
*
* Bright colors will be rendered bold. */
/* This table is based mostly on wild guesses of mine. Feel free to
* correct it. --pasky */
/* Indexed by [fg][bg]->fg: */
static unsigned char fg_color[16][8] = {
/* bk r gr br bl m c w */
/* 0 (black) */
{ 7, 0, 0, 0, 7, 0, 0, 0 },
/* 1 (red) */
{ 1, 9, 1, 9, 9, 9, 1, 1 },
/* 2 (green) */
{ 2, 2, 10, 2, 2, 2, 10, 10 },
/* 3 (brown) */
{ 3, 11, 3, 11, 3, 11, 3, 3 },
/* 4 (blue) */
{ 12, 12, 6, 4, 12, 6, 4, 4 },
/* 5 (magenta) */
{ 5, 13, 5, 13, 13, 13, 5, 5 },
/* 6 (cyan) */
{ 6, 6, 14, 6, 6, 6, 14, 14 },
/* 7 (grey) */
{ 7, 7, 0, 7, 7, 7, 0, 0 }, /* Don't s/0/8/, messy --pasky */
/* 8 (darkgrey) */
{ 15, 15, 8, 15, 15, 15, 8, 8 },
/* 9 (brightred) */
{ 9, 9, 1, 9, 9, 9, 1, 9 }, /* I insist on 7->9 --pasky */
/* 10 (brightgreen) */
{ 10, 10, 10, 10, 10, 10, 10, 10 },
/* 11 (brightyellow) */
{ 11, 11, 11, 11, 11, 11, 11, 11 },
/* 12 (brightblue) */
{ 12, 12, 6, 4, 6, 6, 4, 12 },
/* 13 (brightmagenta) */
{ 13, 13, 5, 13, 13, 13, 5, 5 },
/* 14 (brightcyan) */
{ 14, 14, 14, 14, 14, 14, 14, 14 },
/* 15 (brightwhite) */
{ 15, 15, 15, 15, 15, 15, 15, 15 },
};
/* When determining wether to use negative image we make the most significant
* be least significant. */
#define CMPCODE(c) (((c) << 1 | (c) >> 2) & TERM_COLOR_MASK)
#define use_inverse(bg, fg) CMPCODE(fg & TERM_COLOR_MASK) < CMPCODE(bg)
inline void
set_term_color16(struct screen_char *schar, enum color_flags flags,
unsigned char fg, unsigned char bg)
{
/* Adjusts the foreground color to be more visible. */
if (flags & COLOR_INCREASE_CONTRAST) {
fg = fg_color[fg][bg];
}
/* Add various color enhancement based on the attributes. */
if (schar->attr) {
if (schar->attr & SCREEN_ATTR_ITALIC)
fg ^= 0x01;
if (schar->attr & SCREEN_ATTR_BOLD)
fg |= SCREEN_ATTR_BOLD;
if ((schar->attr & SCREEN_ATTR_UNDERLINE)
&& (flags & COLOR_ENHANCE_UNDERLINE)) {
fg |= SCREEN_ATTR_BOLD;
fg ^= 0x04;
}
}
/* Adjusts the foreground color to be more visible. */
if ((flags & COLOR_INCREASE_CONTRAST)
|| (bg == fg && (flags & COLOR_ENSURE_CONTRAST))) {
if (flags & COLOR_ENSURE_INVERTED_CONTRAST) {
unsigned char contrastbg = fg_color[fg][bg];
fg = bg;
bg = contrastbg;
} else {
fg = fg_color[fg][bg];
}
}
if (fg & SCREEN_ATTR_BOLD) {
schar->attr |= SCREEN_ATTR_BOLD;
}
if (use_inverse(bg, fg)) {
schar->attr |= SCREEN_ATTR_STANDOUT;
}
#if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
/* With 256 color support we use memcmp() when comparing color in
* terminal/screen.c:add_char*() so we need to clear this byte. */
TERM_COLOR_FOREGROUND(schar->color) = (fg & TERM_COLOR_MASK);
TERM_COLOR_BACKGROUND(schar->color) = bg;
#else
schar->color[0] = (bg << 4 | fg);
#endif
}
void
set_term_color(struct screen_char *schar, struct color_pair *pair,
enum color_flags flags, enum color_mode color_mode)
{
struct color_mode_info *mode;
enum palette_range palette_range = PALETTE_FULL;
unsigned char fg, bg;
assert(color_mode >= COLOR_MODE_DUMP && color_mode < COLOR_MODES);
/* Options for the various color modes. */
switch (color_mode) {
case COLOR_MODE_MONO:
/* TODO: A better way if possible to find out whether to
* inverse the fore- and backgroundcolor. Else figure out what:
*
* CMPCODE(c) (((c) << 1 | (c) >> 2) & TERM_COLOR_MASK)
*
* mean. :) --jonas */
/* Decrease the range of the 16 palette to not include
* bright colors. */
if (flags & COLOR_DECREASE_LIGHTNESS) {
palette_range = PALETTE_HALF;
schar->attr |= SCREEN_ATTR_STANDOUT;
}
break;
case COLOR_MODE_16:
/* Decrease the range of the 16 palette to not include
* bright colors. */
if (flags & COLOR_DECREASE_LIGHTNESS)
palette_range = PALETTE_HALF;
break;
#if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
#ifdef CONFIG_88_COLORS
case COLOR_MODE_88:
#endif
#ifdef CONFIG_256_COLORS
case COLOR_MODE_256:
#endif
/* TODO: Handle decrease lightness by converting to
* hue-ligthness-saturation color model */
break;
#endif
case COLOR_MODE_DUMP:
return;
case COLOR_MODES:
/* This is caught by the assert() above. */
return;
}
assert(schar);
mode = color_modes[color_mode];
fg = get_color(pair->foreground, mode->palette, mode->palette_range[palette_range].fg);
bg = get_color(pair->background, mode->palette, mode->palette_range[palette_range].bg);
switch (color_mode) {
case COLOR_MODES:
case COLOR_MODE_DUMP:
INTERNAL("Bad color mode, it should _never_ occur here.");
break;
#if defined(CONFIG_88_COLORS) || defined(CONFIG_256_COLORS)
#ifdef CONFIG_88_COLORS
case COLOR_MODE_88:
#endif
#ifdef CONFIG_256_COLORS
case COLOR_MODE_256:
#endif
/* Adjusts the foreground color to be more visible. */
/* TODO: Be smarter! Here we just choose either black or white
* ANSI color to make sure the color is visible. Pasky
* mentioned maybe calculating a distance and choosing some
* intermediate color. --jonas */
/* TODO: Maybe also do something to honour the increase_contrast
* option. --jonas */
if (bg == fg && (flags & COLOR_ENSURE_CONTRAST)) {
if (flags & COLOR_ENSURE_INVERTED_CONTRAST) {
bg = (fg == 0) ? 15 : 0;
} else {
fg = (bg == 0) ? 15 : 0;
}
}
TERM_COLOR_FOREGROUND(schar->color) = fg;
TERM_COLOR_BACKGROUND(schar->color) = bg;
break;
#endif
case COLOR_MODE_MONO:
case COLOR_MODE_16:
set_term_color16(schar, flags, fg, bg);
break;
}
}