From: Bruno Haible Date: Fri, 1 Dec 2006 12:48:21 +0000 (+0000) Subject: Add support for xterm's 16-, 88-, 256-color modes. X-Git-Tag: v0.17~615 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5d10597d5469e615de16d3486a0b14d0c9d388ea;p=thirdparty%2Fgettext.git Add support for xterm's 16-, 88-, 256-color modes. --- diff --git a/gnulib-local/ChangeLog b/gnulib-local/ChangeLog index a31eb4a2d..e8b21f707 100644 --- a/gnulib-local/ChangeLog +++ b/gnulib-local/ChangeLog @@ -1,3 +1,32 @@ +2006-11-30 Bruno Haible + + Add special color support for xterm-16color, xterm-88color, + xterm-256color. + * lib/term-ostream.oo.h (term_color_t): Define as int. + (COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, COLOR_RED, + COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE): Remove enum items. + (term_ostream): Add method rgb_to_color. + * lib/term-ostream.oo.c (rgb_t, hsv_t): New types. + (rgb_to_hsv, color_distance, nearest_color, color_luminance): New + functions. + (colormodel_t): New type. + (rgb_to_color_monochrome): New function. + (rgb_to_color_common8): New function. + (rgb_to_color_xterm8): New function. + (colors_of_xterm16): New variable. + (rgb_to_color_xterm16): New function. + (colors_of_xterm88): New variable. + (rgb_to_color_xterm88): New function. + (colors_of_xterm256): New variable. + (rgb_to_color_xterm256): New function. + (attributes_t): Reserve more bits for the colors. + (term_ostream): Add colormodel field. + (out_error): New function. + (out_char): Use it. + (out_attr_change): Add support for the xterm color models. + (term_ostream::rgb_to_color): New function. + (term_ostream_create): Initialize the colormodel field. + 2006-11-28 Bruno Haible * lib/term-ostream.oo.c (out_attr_change): Fix uses of color_bgr. diff --git a/gnulib-local/lib/term-ostream.oo.c b/gnulib-local/lib/term-ostream.oo.c index 9269b3a62..0b8787a89 100644 --- a/gnulib-local/lib/term-ostream.oo.c +++ b/gnulib-local/lib/term-ostream.oo.c @@ -42,6 +42,8 @@ #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) +/* =========================== termcap interface =========================== */ + /* Including or is dangerous, because it also declares a lot of junk, such as variables PC, UP, and other. */ @@ -90,6 +92,808 @@ extern void tputs (const char *cp, int affcnt, int (*outcharfun) (int)); #endif +/* =========================== Color primitives =========================== */ + +/* A color in RGB format. */ +typedef struct +{ + unsigned int red : 8; /* range 0..255 */ + unsigned int green : 8; /* range 0..255 */ + unsigned int blue : 8; /* range 0..255 */ +} rgb_t; + +/* A color in HSV (a.k.a. HSB) format. */ +typedef struct +{ + float hue; /* normalized to interval [0,6) */ + float saturation; /* normalized to interval [0,1] */ + float brightness; /* a.k.a. value, normalized to interval [0,1] */ +} hsv_t; + +/* Conversion of a color in RGB to HSV format. */ +static void +rgb_to_hsv (rgb_t c, hsv_t *result) +{ + unsigned int r = c.red; + unsigned int g = c.green; + unsigned int b = c.blue; + + if (r > g) + { + if (b > r) + { + /* b > r > g, so max = b, min = g */ + result->hue = 4.0f + (float) (r - g) / (float) (b - g); + result->saturation = 1.0f - (float) g / (float) b; + result->brightness = (float) b / 255.0f; + } + else if (b <= g) + { + /* r > g >= b, so max = r, min = b */ + result->hue = 0.0f + (float) (g - b) / (float) (r - b); + result->saturation = 1.0f - (float) b / (float) r; + result->brightness = (float) r / 255.0f; + } + else + { + /* r >= b > g, so max = r, min = g */ + result->hue = 6.0f - (float) (b - g) / (float) (r - g); + result->saturation = 1.0f - (float) g / (float) r; + result->brightness = (float) r / 255.0f; + } + } + else + { + if (b > g) + { + /* b > g >= r, so max = b, min = r */ + result->hue = 4.0f - (float) (g - r) / (float) (b - r); + result->saturation = 1.0f - (float) r / (float) b; + result->brightness = (float) b / 255.0f; + } + else if (b < r) + { + /* g >= r > b, so max = g, min = b */ + result->hue = 2.0f - (float) (r - b) / (float) (g - b); + result->saturation = 1.0f - (float) b / (float) g; + result->brightness = (float) g / 255.0f; + } + else if (g > r) + { + /* g >= b >= r, g > r, so max = g, min = r */ + result->hue = 2.0f + (float) (b - r) / (float) (g - r); + result->saturation = 1.0f - (float) r / (float) g; + result->brightness = (float) g / 255.0f; + } + else + { + /* r = g = b. A grey color. */ + result->hue = 0; /* arbitrary */ + result->saturation = 0; + result->brightness = (float) r / 255.0f; + } + } +} + +/* Square of distance of two colors. */ +static float +color_distance (const hsv_t *color1, const hsv_t *color2) +{ +#if 0 + /* Formula taken from "John Smith: Color Similarity", + http://www.ctr.columbia.edu/~jrsmith/html/pubs/acmmm96/node8.html. */ + float angle1 = color1->hue * 1.04719755f; /* normalize to [0,2π] */ + float angle2 = color2->hue * 1.04719755f; /* normalize to [0,2π] */ + float delta_x = color1->saturation * cosf (angle1) + - color2->saturation * cosf (angle2); + float delta_y = color1->saturation * sinf (angle1) + - color2->saturation * sinf (angle2); + float delta_v = color1->brightness + - color2->brightness; + + return delta_x * delta_x + delta_y * delta_y + delta_v * delta_v; +#else + /* Formula that considers hue differences with more weight than saturation + or brightness differences, like the human eye does. */ + float delta_hue = + (color1->hue >= color2->hue + ? (color1->hue - color2->hue >= 3.0f + ? 6.0f + color2->hue - color1->hue + : color1->hue - color2->hue) + : (color2->hue - color1->hue >= 3.0f + ? 6.0f + color1->hue - color2->hue + : color2->hue - color1->hue)); + float min_saturation = + (color1->saturation < color2->saturation + ? color1->saturation + : color2->saturation); + float delta_saturation = color1->saturation - color2->saturation; + float delta_brightness = color1->brightness - color2->brightness; + + return delta_hue * delta_hue * min_saturation + + delta_saturation * delta_saturation * 0.2f + + delta_brightness * delta_brightness * 0.8f; +#endif +} + +/* Return the index of the color in a color table that is nearest to a given + color. */ +static unsigned int +nearest_color (rgb_t given, const rgb_t *table, unsigned int table_size) +{ + hsv_t given_hsv; + unsigned int best_index; + float best_distance; + unsigned int i; + + assert (table_size > 0); + + rgb_to_hsv (given, &given_hsv); + + best_index = 0; + best_distance = 1000000.0f; + for (i = 0; i < table_size; i++) + { + hsv_t i_hsv; + + rgb_to_hsv (table[i], &i_hsv); + + /* Avoid converting a color to grey, or fading out a color too much. */ + if (i_hsv.saturation > given_hsv.saturation * 0.5f) + { + float distance = color_distance (&given_hsv, &i_hsv); + if (distance < best_distance) + { + best_index = i; + best_distance = distance; + } + } + } + +#if 0 /* Debugging code */ + hsv_t best_hsv; + rgb_to_hsv (table[best_index], &best_hsv); + fprintf (stderr, "nearest: (%d,%d,%d) = (%f,%f,%f)\n -> (%f,%f,%f) = (%d,%d,%d)\n", + given.red, given.green, given.blue, + (double)given_hsv.hue, (double)given_hsv.saturation, (double)given_hsv.brightness, + (double)best_hsv.hue, (double)best_hsv.saturation, (double)best_hsv.brightness, + table[best_index].red, table[best_index].green, table[best_index].blue); +#endif + + return best_index; +} + +/* The luminance of a color. This is the brightness of the color, as it + appears to the human eye. This must be used in color to grey conversion. */ +static float +color_luminance (int r, int g, int b) +{ + /* Use the luminance model used by NTSC and JPEG. + Taken from http://www.fho-emden.de/~hoffmann/gray10012001.pdf . + No need to care about rounding errors leading to luminance > 1; + this cannot happen. */ + return (0.299f * r + 0.587f * g + 0.114f * b) / 255.0f; +} + + +/* ============================= Color models ============================= */ + +/* The color model used by the terminal. */ +typedef enum +{ + cm_monochrome, /* No colors. */ + cm_common8, /* Usual terminal with at least 8 colors. */ + cm_xterm8, /* TERM=xterm, with 8 colors. */ + cm_xterm16, /* TERM=xterm-16color, with 16 colors. */ + cm_xterm88, /* TERM=xterm-88color, with 88 colors. */ + cm_xterm256 /* TERM=xterm-256color, with 256 colors. */ +} colormodel_t; + +/* ----------------------- cm_monochrome color model ----------------------- */ + +/* A non-default color index doesn't exist in this color model. */ +static inline term_color_t +rgb_to_color_monochrome () +{ + return COLOR_DEFAULT; +} + +/* ------------------------ cm_common8 color model ------------------------ */ + +/* A non-default color index is in the range 0..7. + RGB components + COLOR_BLACK 000 + COLOR_BLUE 001 + COLOR_GREEN 010 + COLOR_CYAN 011 + COLOR_RED 100 + COLOR_MAGENTA 101 + COLOR_YELLOW 110 + COLOR_WHITE 111 */ +static inline term_color_t +rgb_to_color_common8 (int r, int g, int b) +{ + return (r >= 128 ? 4 : 0) + (g >= 128 ? 2 : 0) + (b >= 128 ? 1 : 0); +} + +/* Convert a cm_common8 color in RGB encoding to BGR encoding. + See the ncurses terminfo(5) manual page, section "Color Handling", for an + explanation why this is needed. */ +static inline int +color_bgr (term_color_t color) +{ + return ((color & 4) >> 2) | (color & 2) | ((color & 1) << 2); +} + +/* ------------------------- cm_xterm8 color model ------------------------- */ + +/* A non-default color index is in the range 0..7. + BGR components + COLOR_BLACK 000 + COLOR_RED 001 + COLOR_GREEN 010 + COLOR_YELLOW 011 + COLOR_BLUE 100 + COLOR_MAGENTA 101 + COLOR_CYAN 110 + COLOR_WHITE 111 */ +static inline term_color_t +rgb_to_color_xterm8 (int r, int g, int b) +{ + return (r >= 128 ? 1 : 0) + (g >= 128 ? 2 : 0) + (b >= 128 ? 4 : 0); +} + +/* ------------------------ cm_xterm16 color model ------------------------ */ + +/* A non-default color index is in the range 0..15. + The RGB values come from xterm's XTerm-col.ad. */ +static const rgb_t colors_of_xterm16[16] = +{ + /* R G B grey index */ + { 0, 0, 0 }, /* 0.000 0 */ + { 205, 0, 0 }, + { 0, 205, 0 }, + { 205, 205, 0 }, + { 0, 0, 205 }, + { 205, 0, 205 }, + { 0, 205, 205 }, + { 229, 229, 229 }, /* 0.898 7 */ + { 77, 77, 77 }, /* 0.302 8 */ + { 255, 0, 0 }, + { 0, 255, 0 }, + { 255, 255, 0 }, + { 0, 0, 255 }, + { 255, 0, 255 }, + { 0, 255, 255 }, + { 255, 255, 255 } /* 1.000 15 */ +}; + +static inline term_color_t +rgb_to_color_xterm16 (int r, int g, int b) +{ + rgb_t color; + hsv_t hsv; + + color.red = r; color.green = g; color.blue = b; + rgb_to_hsv (color, &hsv); + + if (hsv.saturation < 0.065f) + { + /* Greyscale approximation. */ + float luminance = color_luminance (r, g, b); + if (luminance < 0.151f) + return 0; + else if (luminance < 0.600f) + return 8; + else if (luminance < 0.949f) + return 7; + else + return 15; + } + else + /* Color approximation. */ + return nearest_color (color, colors_of_xterm16, 16); +} + +/* ------------------------ cm_xterm88 color model ------------------------ */ + +/* A non-default color index is in the range 0..87. + Colors 0..15 are the same as in the cm_xterm16 color model. + Colors 16..87 are defined in xterm's 88colres.h. */ + +static const rgb_t colors_of_xterm88[88] = +{ + /* R G B grey index */ + { 0, 0, 0 }, /* 0.000 0 */ + { 205, 0, 0 }, + { 0, 205, 0 }, + { 205, 205, 0 }, + { 0, 0, 205 }, + { 205, 0, 205 }, + { 0, 205, 205 }, + { 229, 229, 229 }, /* 0.898 7 */ + { 77, 77, 77 }, /* 0.302 8 */ + { 255, 0, 0 }, + { 0, 255, 0 }, + { 255, 255, 0 }, + { 0, 0, 255 }, + { 255, 0, 255 }, + { 0, 255, 255 }, + { 255, 255, 255 }, /* 1.000 15 */ + { 0, 0, 0 }, /* 0.000 16 */ + { 0, 0, 139 }, + { 0, 0, 205 }, + { 0, 0, 255 }, + { 0, 139, 0 }, + { 0, 139, 139 }, + { 0, 139, 205 }, + { 0, 139, 255 }, + { 0, 205, 0 }, + { 0, 205, 139 }, + { 0, 205, 205 }, + { 0, 205, 255 }, + { 0, 255, 0 }, + { 0, 255, 139 }, + { 0, 255, 205 }, + { 0, 255, 255 }, + { 139, 0, 0 }, + { 139, 0, 139 }, + { 139, 0, 205 }, + { 139, 0, 255 }, + { 139, 139, 0 }, + { 139, 139, 139 }, /* 0.545 37 */ + { 139, 139, 205 }, + { 139, 139, 255 }, + { 139, 205, 0 }, + { 139, 205, 139 }, + { 139, 205, 205 }, + { 139, 205, 255 }, + { 139, 255, 0 }, + { 139, 255, 139 }, + { 139, 255, 205 }, + { 139, 255, 255 }, + { 205, 0, 0 }, + { 205, 0, 139 }, + { 205, 0, 205 }, + { 205, 0, 255 }, + { 205, 139, 0 }, + { 205, 139, 139 }, + { 205, 139, 205 }, + { 205, 139, 255 }, + { 205, 205, 0 }, + { 205, 205, 139 }, + { 205, 205, 205 }, /* 0.804 58 */ + { 205, 205, 255 }, + { 205, 255, 0 }, + { 205, 255, 139 }, + { 205, 255, 205 }, + { 205, 255, 255 }, + { 255, 0, 0 }, + { 255, 0, 139 }, + { 255, 0, 205 }, + { 255, 0, 255 }, + { 255, 139, 0 }, + { 255, 139, 139 }, + { 255, 139, 205 }, + { 255, 139, 255 }, + { 255, 205, 0 }, + { 255, 205, 139 }, + { 255, 205, 205 }, + { 255, 205, 255 }, + { 255, 255, 0 }, + { 255, 255, 139 }, + { 255, 255, 205 }, + { 255, 255, 255 }, /* 1.000 79 */ + { 46, 46, 46 }, /* 0.180 80 */ + { 92, 92, 92 }, /* 0.361 81 */ + { 115, 115, 115 }, /* 0.451 82 */ + { 139, 139, 139 }, /* 0.545 83 */ + { 162, 162, 162 }, /* 0.635 84 */ + { 185, 185, 185 }, /* 0.725 85 */ + { 208, 208, 208 }, /* 0.816 86 */ + { 231, 231, 231 } /* 0.906 87 */ +}; + +static inline term_color_t +rgb_to_color_xterm88 (int r, int g, int b) +{ + rgb_t color; + hsv_t hsv; + + color.red = r; color.green = g; color.blue = b; + rgb_to_hsv (color, &hsv); + + if (hsv.saturation < 0.065f) + { + /* Greyscale approximation. */ + float luminance = color_luminance (r, g, b); + if (luminance < 0.090f) + return 0; + else if (luminance < 0.241f) + return 80; + else if (luminance < 0.331f) + return 8; + else if (luminance < 0.406f) + return 81; + else if (luminance < 0.498f) + return 82; + else if (luminance < 0.585f) + return 37; + else if (luminance < 0.680f) + return 84; + else if (luminance < 0.764f) + return 85; + else if (luminance < 0.810f) + return 58; + else if (luminance < 0.857f) + return 86; + else if (luminance < 0.902f) + return 7; + else if (luminance < 0.953f) + return 87; + else + return 15; + } + else + /* Color approximation. */ + return nearest_color (color, colors_of_xterm88, 88); +} + +/* ------------------------ cm_xterm256 color model ------------------------ */ + +/* A non-default color index is in the range 0..255. + Colors 0..15 are the same as in the cm_xterm16 color model. + Colors 16..255 are defined in xterm's 256colres.h. */ + +static const rgb_t colors_of_xterm256[256] = +{ + /* R G B grey index */ + { 0, 0, 0 }, /* 0.000 0 */ + { 205, 0, 0 }, + { 0, 205, 0 }, + { 205, 205, 0 }, + { 0, 0, 205 }, + { 205, 0, 205 }, + { 0, 205, 205 }, + { 229, 229, 229 }, /* 0.898 7 */ + { 77, 77, 77 }, /* 0.302 8 */ + { 255, 0, 0 }, + { 0, 255, 0 }, + { 255, 255, 0 }, + { 0, 0, 255 }, + { 255, 0, 255 }, + { 0, 255, 255 }, + { 255, 255, 255 }, /* 1.000 15 */ + { 0, 0, 0 }, /* 0.000 16 */ + { 0, 0, 42 }, + { 0, 0, 85 }, + { 0, 0, 127 }, + { 0, 0, 170 }, + { 0, 0, 212 }, + { 0, 42, 0 }, + { 0, 42, 42 }, + { 0, 42, 85 }, + { 0, 42, 127 }, + { 0, 42, 170 }, + { 0, 42, 212 }, + { 0, 85, 0 }, + { 0, 85, 42 }, + { 0, 85, 85 }, + { 0, 85, 127 }, + { 0, 85, 170 }, + { 0, 85, 212 }, + { 0, 127, 0 }, + { 0, 127, 42 }, + { 0, 127, 85 }, + { 0, 127, 127 }, + { 0, 127, 170 }, + { 0, 127, 212 }, + { 0, 170, 0 }, + { 0, 170, 42 }, + { 0, 170, 85 }, + { 0, 170, 127 }, + { 0, 170, 170 }, + { 0, 170, 212 }, + { 0, 212, 0 }, + { 0, 212, 42 }, + { 0, 212, 85 }, + { 0, 212, 127 }, + { 0, 212, 170 }, + { 0, 212, 212 }, + { 42, 0, 0 }, + { 42, 0, 42 }, + { 42, 0, 85 }, + { 42, 0, 127 }, + { 42, 0, 170 }, + { 42, 0, 212 }, + { 42, 42, 0 }, + { 42, 42, 42 }, /* 0.165 59 */ + { 42, 42, 85 }, + { 42, 42, 127 }, + { 42, 42, 170 }, + { 42, 42, 212 }, + { 42, 85, 0 }, + { 42, 85, 42 }, + { 42, 85, 85 }, + { 42, 85, 127 }, + { 42, 85, 170 }, + { 42, 85, 212 }, + { 42, 127, 0 }, + { 42, 127, 42 }, + { 42, 127, 85 }, + { 42, 127, 127 }, + { 42, 127, 170 }, + { 42, 127, 212 }, + { 42, 170, 0 }, + { 42, 170, 42 }, + { 42, 170, 85 }, + { 42, 170, 127 }, + { 42, 170, 170 }, + { 42, 170, 212 }, + { 42, 212, 0 }, + { 42, 212, 42 }, + { 42, 212, 85 }, + { 42, 212, 127 }, + { 42, 212, 170 }, + { 42, 212, 212 }, + { 85, 0, 0 }, + { 85, 0, 42 }, + { 85, 0, 85 }, + { 85, 0, 127 }, + { 85, 0, 170 }, + { 85, 0, 212 }, + { 85, 42, 0 }, + { 85, 42, 42 }, + { 85, 42, 85 }, + { 85, 42, 127 }, + { 85, 42, 170 }, + { 85, 42, 212 }, + { 85, 85, 0 }, + { 85, 85, 42 }, + { 85, 85, 85 }, /* 0.333 102 */ + { 85, 85, 127 }, + { 85, 85, 170 }, + { 85, 85, 212 }, + { 85, 127, 0 }, + { 85, 127, 42 }, + { 85, 127, 85 }, + { 85, 127, 127 }, + { 85, 127, 170 }, + { 85, 127, 212 }, + { 85, 170, 0 }, + { 85, 170, 42 }, + { 85, 170, 85 }, + { 85, 170, 127 }, + { 85, 170, 170 }, + { 85, 170, 212 }, + { 85, 212, 0 }, + { 85, 212, 42 }, + { 85, 212, 85 }, + { 85, 212, 127 }, + { 85, 212, 170 }, + { 85, 212, 212 }, + { 127, 0, 0 }, + { 127, 0, 42 }, + { 127, 0, 85 }, + { 127, 0, 127 }, + { 127, 0, 170 }, + { 127, 0, 212 }, + { 127, 42, 0 }, + { 127, 42, 42 }, + { 127, 42, 85 }, + { 127, 42, 127 }, + { 127, 42, 170 }, + { 127, 42, 212 }, + { 127, 85, 0 }, + { 127, 85, 42 }, + { 127, 85, 85 }, + { 127, 85, 127 }, + { 127, 85, 170 }, + { 127, 85, 212 }, + { 127, 127, 0 }, + { 127, 127, 42 }, + { 127, 127, 85 }, + { 127, 127, 127 }, /* 0.498 145 */ + { 127, 127, 170 }, + { 127, 127, 212 }, + { 127, 170, 0 }, + { 127, 170, 42 }, + { 127, 170, 85 }, + { 127, 170, 127 }, + { 127, 170, 170 }, + { 127, 170, 212 }, + { 127, 212, 0 }, + { 127, 212, 42 }, + { 127, 212, 85 }, + { 127, 212, 127 }, + { 127, 212, 170 }, + { 127, 212, 212 }, + { 170, 0, 0 }, + { 170, 0, 42 }, + { 170, 0, 85 }, + { 170, 0, 127 }, + { 170, 0, 170 }, + { 170, 0, 212 }, + { 170, 42, 0 }, + { 170, 42, 42 }, + { 170, 42, 85 }, + { 170, 42, 127 }, + { 170, 42, 170 }, + { 170, 42, 212 }, + { 170, 85, 0 }, + { 170, 85, 42 }, + { 170, 85, 85 }, + { 170, 85, 127 }, + { 170, 85, 170 }, + { 170, 85, 212 }, + { 170, 127, 0 }, + { 170, 127, 42 }, + { 170, 127, 85 }, + { 170, 127, 127 }, + { 170, 127, 170 }, + { 170, 127, 212 }, + { 170, 170, 0 }, + { 170, 170, 42 }, + { 170, 170, 85 }, + { 170, 170, 127 }, + { 170, 170, 170 }, /* 0.667 188 */ + { 170, 170, 212 }, + { 170, 212, 0 }, + { 170, 212, 42 }, + { 170, 212, 85 }, + { 170, 212, 127 }, + { 170, 212, 170 }, + { 170, 212, 212 }, + { 212, 0, 0 }, + { 212, 0, 42 }, + { 212, 0, 85 }, + { 212, 0, 127 }, + { 212, 0, 170 }, + { 212, 0, 212 }, + { 212, 42, 0 }, + { 212, 42, 42 }, + { 212, 42, 85 }, + { 212, 42, 127 }, + { 212, 42, 170 }, + { 212, 42, 212 }, + { 212, 85, 0 }, + { 212, 85, 42 }, + { 212, 85, 85 }, + { 212, 85, 127 }, + { 212, 85, 170 }, + { 212, 85, 212 }, + { 212, 127, 0 }, + { 212, 127, 42 }, + { 212, 127, 85 }, + { 212, 127, 127 }, + { 212, 127, 170 }, + { 212, 127, 212 }, + { 212, 170, 0 }, + { 212, 170, 42 }, + { 212, 170, 85 }, + { 212, 170, 127 }, + { 212, 170, 170 }, + { 212, 170, 212 }, + { 212, 212, 0 }, + { 212, 212, 42 }, + { 212, 212, 85 }, + { 212, 212, 127 }, + { 212, 212, 170 }, + { 212, 212, 212 }, /* 0.831 231 */ + { 8, 8, 8 }, /* 0.031 232 */ + { 18, 18, 18 }, /* 0.071 233 */ + { 28, 28, 28 }, /* 0.110 234 */ + { 38, 38, 38 }, /* 0.149 235 */ + { 48, 48, 48 }, /* 0.188 236 */ + { 58, 58, 58 }, /* 0.227 237 */ + { 68, 68, 68 }, /* 0.267 238 */ + { 78, 78, 78 }, /* 0.306 239 */ + { 88, 88, 88 }, /* 0.345 240 */ + { 98, 98, 98 }, /* 0.384 241 */ + { 108, 108, 108 }, /* 0.424 242 */ + { 118, 118, 118 }, /* 0.463 243 */ + { 128, 128, 128 }, /* 0.502 244 */ + { 138, 138, 138 }, /* 0.541 245 */ + { 148, 148, 148 }, /* 0.580 246 */ + { 158, 158, 158 }, /* 0.620 247 */ + { 168, 168, 168 }, /* 0.659 248 */ + { 178, 178, 178 }, /* 0.698 249 */ + { 188, 188, 188 }, /* 0.737 250 */ + { 198, 198, 198 }, /* 0.776 251 */ + { 208, 208, 208 }, /* 0.816 252 */ + { 218, 218, 218 }, /* 0.855 253 */ + { 228, 228, 228 }, /* 0.894 254 */ + { 238, 238, 238 } /* 0.933 255 */ +}; + +static inline term_color_t +rgb_to_color_xterm256 (int r, int g, int b) +{ + rgb_t color; + hsv_t hsv; + + color.red = r; color.green = g; color.blue = b; + rgb_to_hsv (color, &hsv); + + if (hsv.saturation < 0.065f) + { + /* Greyscale approximation. */ + float luminance = color_luminance (r, g, b); + if (luminance < 0.015f) + return 0; + else if (luminance < 0.051f) + return 232; + else if (luminance < 0.090f) + return 233; + else if (luminance < 0.129f) + return 234; + else if (luminance < 0.157f) + return 235; + else if (luminance < 0.177f) + return 59; + else if (luminance < 0.207f) + return 236; + else if (luminance < 0.247f) + return 237; + else if (luminance < 0.284f) + return 238; + else if (luminance < 0.304f) + return 8; + else if (luminance < 0.319f) + return 239; + else if (luminance < 0.339f) + return 102; + else if (luminance < 0.364f) + return 240; + else if (luminance < 0.404f) + return 241; + else if (luminance < 0.443f) + return 242; + else if (luminance < 0.480f) + return 243; + else if (luminance < 0.500f) + return 145; + else if (luminance < 0.521f) + return 244; + else if (luminance < 0.560f) + return 245; + else if (luminance < 0.600f) + return 246; + else if (luminance < 0.639f) + return 247; + else if (luminance < 0.663f) + return 248; + else if (luminance < 0.682f) + return 188; + else if (luminance < 0.717f) + return 249; + else if (luminance < 0.756f) + return 250; + else if (luminance < 0.796f) + return 251; + else if (luminance < 0.823f) + return 252; + else if (luminance < 0.843f) + return 231; + else if (luminance < 0.874f) + return 253; + else if (luminance < 0.896f) + return 254; + else if (luminance < 0.915f) + return 7; + else if (luminance < 0.966f) + return 255; + else + return 15; + } + else + /* Color approximation. */ + return nearest_color (color, colors_of_xterm256, 256); +} + + +/* ============================= attributes_t ============================= */ + /* ANSI C and ISO C99 6.7.2.1.(4) forbid use of bit fields for types other than 'int' or 'unsigned int'. On the other hand, C++ forbids conversion between enum types and integer @@ -103,13 +907,16 @@ extern void tputs (const char *cp, int affcnt, int (*outcharfun) (int)); /* Attributes that can be set on a character. */ typedef struct { - BITFIELD_TYPE(term_color_t, signed int) color : 4; - BITFIELD_TYPE(term_color_t, signed int) bgcolor : 4; + BITFIELD_TYPE(term_color_t, signed int) color : 9; + BITFIELD_TYPE(term_color_t, signed int) bgcolor : 9; BITFIELD_TYPE(term_weight_t, unsigned int) weight : 1; BITFIELD_TYPE(term_posture_t, unsigned int) posture : 1; BITFIELD_TYPE(term_underline_t, unsigned int) underline : 1; } attributes_t; + +/* ============================ term_ostream_t ============================ */ + struct term_ostream : struct ostream { fields: @@ -137,6 +944,7 @@ fields: /* Inferred values. */ bool supports_foreground; bool supports_background; + colormodel_t colormodel; bool supports_weight; bool supports_posture; bool supports_underline; @@ -285,13 +1093,11 @@ equal_attributes (attributes_t attr1, attributes_t attr2) && attr1.underline == attr2.underline); } -/* Convert a color in RGB encoding to BGR encoding. - See the ncurses terminfo.5 manual page, section "Color Handling", for an - explanation why this is needed. */ -static inline int -color_bgr (term_color_t color) +/* Signal error after full_write failed. */ +static void +out_error () { - return ((color & 4) >> 2) | (color & 2) | ((color & 1) << 2); + error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename); } /* Output a single char to out_fd. */ @@ -305,7 +1111,7 @@ out_char (int c) the same destination, because of the padding and sleeping that tputs() does. */ if (full_write (out_fd, bytes, 1) < 1) - error (EXIT_FAILURE, errno, _("error writing to %s"), out_filename); + out_error (); return 0; } @@ -330,23 +1136,177 @@ out_attr_change (term_ostream_t stream, { assert (stream->supports_foreground); assert (new_attr.color != COLOR_DEFAULT); - if (stream->set_a_foreground != NULL) - tputs (tparm (stream->set_a_foreground, color_bgr (new_attr.color)), - 1, out_char); - else - tputs (tparm (stream->set_foreground, new_attr.color), - 1, out_char); + switch (stream->colormodel) + { + case cm_common8: + assert (new_attr.color >= 0 && new_attr.color < 8); + if (stream->set_a_foreground != NULL) + tputs (tparm (stream->set_a_foreground, + color_bgr (new_attr.color)), + 1, out_char); + else + tputs (tparm (stream->set_foreground, new_attr.color), + 1, out_char); + break; + /* When we are dealing with an xterm, there is no need to go through + tputs() because we know there is no padding and sleeping. */ + case cm_xterm8: + assert (new_attr.color >= 0 && new_attr.color < 8); + { + char bytes[5]; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '3'; bytes[3] = '0' + new_attr.color; + bytes[4] = 'm'; + if (full_write (out_fd, bytes, 5) < 5) + out_error (); + } + break; + case cm_xterm16: + assert (new_attr.color >= 0 && new_attr.color < 16); + { + char bytes[5]; + bytes[0] = 0x1B; bytes[1] = '['; + if (new_attr.color < 8) + { + bytes[2] = '3'; bytes[3] = '0' + new_attr.color; + } + else + { + bytes[2] = '9'; bytes[3] = '0' + (new_attr.color - 8); + } + bytes[4] = 'm'; + if (full_write (out_fd, bytes, 5) < 5) + out_error (); + } + break; + case cm_xterm88: + assert (new_attr.color >= 0 && new_attr.color < 88); + { + char bytes[10]; + char *p; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';'; + bytes[5] = '5'; bytes[6] = ';'; + p = bytes + 7; + if (new_attr.color >= 10) + *p++ = '0' + (new_attr.color / 10); + *p++ = '0' + (new_attr.color % 10); + *p++ = 'm'; + if (full_write (out_fd, bytes, p - bytes) < p - bytes) + out_error (); + } + break; + case cm_xterm256: + assert (new_attr.color >= 0 && new_attr.color < 256); + { + char bytes[11]; + char *p; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '3'; bytes[3] = '8'; bytes[4] = ';'; + bytes[5] = '5'; bytes[6] = ';'; + p = bytes + 7; + if (new_attr.color >= 100) + *p++ = '0' + (new_attr.color / 100); + if (new_attr.color >= 10) + *p++ = '0' + ((new_attr.color % 100) / 10); + *p++ = '0' + (new_attr.color % 10); + *p++ = 'm'; + if (full_write (out_fd, bytes, p - bytes) < p - bytes) + out_error (); + } + break; + default: + abort (); + } } if (new_attr.bgcolor != old_attr.bgcolor) { assert (stream->supports_background); assert (new_attr.bgcolor != COLOR_DEFAULT); - if (stream->set_a_background != NULL) - tputs (tparm (stream->set_a_background, color_bgr (new_attr.bgcolor)), - 1, out_char); - else - tputs (tparm (stream->set_background, new_attr.bgcolor), - 1, out_char); + switch (stream->colormodel) + { + case cm_common8: + assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8); + if (stream->set_a_background != NULL) + tputs (tparm (stream->set_a_background, + color_bgr (new_attr.bgcolor)), + 1, out_char); + else + tputs (tparm (stream->set_background, new_attr.bgcolor), + 1, out_char); + /* When we are dealing with an xterm, there is no need to go through + tputs() because we know there is no padding and sleeping. */ + case cm_xterm8: + assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 8); + { + char bytes[5]; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '4'; bytes[3] = '0' + new_attr.bgcolor; + bytes[4] = 'm'; + if (full_write (out_fd, bytes, 5) < 5) + out_error (); + } + break; + case cm_xterm16: + assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 16); + { + char bytes[6]; + bytes[0] = 0x1B; bytes[1] = '['; + if (new_attr.bgcolor < 8) + { + bytes[2] = '3'; bytes[3] = '0' + new_attr.bgcolor; + bytes[4] = 'm'; + if (full_write (out_fd, bytes, 5) < 5) + out_error (); + } + else + { + bytes[2] = '1'; bytes[3] = '0'; + bytes[4] = '0' + (new_attr.bgcolor - 8); bytes[5] = 'm'; + if (full_write (out_fd, bytes, 6) < 6) + out_error (); + } + } + break; + case cm_xterm88: + assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 88); + { + char bytes[10]; + char *p; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';'; + bytes[5] = '5'; bytes[6] = ';'; + p = bytes + 7; + if (new_attr.bgcolor >= 10) + *p++ = '0' + (new_attr.bgcolor / 10); + *p++ = '0' + (new_attr.bgcolor % 10); + *p++ = 'm'; + if (full_write (out_fd, bytes, p - bytes) < p - bytes) + out_error (); + } + break; + case cm_xterm256: + assert (new_attr.bgcolor >= 0 && new_attr.bgcolor < 256); + { + char bytes[11]; + char *p; + bytes[0] = 0x1B; bytes[1] = '['; + bytes[2] = '4'; bytes[3] = '8'; bytes[4] = ';'; + bytes[5] = '5'; bytes[6] = ';'; + p = bytes + 7; + if (new_attr.bgcolor >= 100) + *p++ = '0' + (new_attr.bgcolor / 100); + if (new_attr.bgcolor >= 10) + *p++ = '0' + ((new_attr.bgcolor % 100) / 10); + *p++ = '0' + (new_attr.bgcolor % 10); + *p++ = 'm'; + if (full_write (out_fd, bytes, p - bytes) < p - bytes) + out_error (); + } + break; + default: + abort (); + } } cleared_attributes = false; @@ -496,6 +1456,28 @@ output_buffer (term_ostream_t stream) /* Implementation of ostream_t methods. */ +static term_color_t +term_ostream::rgb_to_color (term_ostream_t stream, int red, int green, int blue) +{ + switch (stream->colormodel) + { + case cm_monochrome: + return rgb_to_color_monochrome (); + case cm_common8: + return rgb_to_color_common8 (red, green, blue); + case cm_xterm8: + return rgb_to_color_xterm8 (red, green, blue); + case cm_xterm16: + return rgb_to_color_xterm16 (red, green, blue); + case cm_xterm88: + return rgb_to_color_xterm88 (red, green, blue); + case cm_xterm256: + return rgb_to_color_xterm256 (red, green, blue); + default: + abort (); + } +} + static void term_ostream::write_mem (term_ostream_t stream, const void *data, size_t len) { @@ -690,7 +1672,7 @@ term_ostream_create (int fd, const char *filename) { struct { char buf[1024]; char canary[4]; } termcapbuf; int retval; - + /* Call tgetent, being defensive against buffer overflow. */ memcpy (termcapbuf.canary, "CnRy", 4); retval = tgetent (termcapbuf.buf, term); @@ -723,6 +1705,15 @@ term_ostream_create (int fd, const char *filename) (stream->max_colors >= 8 && (stream->set_a_background != NULL || stream->set_background != NULL) && stream->orig_pair != NULL); + stream->colormodel = + (stream->supports_foreground || stream->supports_background + ? (term != NULL && strlen (term) >= 5 && memcmp (term, "xterm", 5) == 0 + ? (stream->max_colors == 256 ? cm_xterm256 : + stream->max_colors == 88 ? cm_xterm88 : + stream->max_colors == 16 ? cm_xterm16 : + cm_xterm8) + : cm_common8) + : cm_monochrome); stream->supports_weight = (stream->enter_bold_mode != NULL && stream->exit_attribute_mode != NULL); stream->supports_posture = diff --git a/gnulib-local/lib/term-ostream.oo.h b/gnulib-local/lib/term-ostream.oo.h index 38dddfb65..7c13082c9 100644 --- a/gnulib-local/lib/term-ostream.oo.h +++ b/gnulib-local/lib/term-ostream.oo.h @@ -30,18 +30,13 @@ For example, xterm cannot render POSTURE_ITALIC nor the combination of WEIGHT_BOLD and UNDERLINE_ON. */ -typedef enum -{ /* RGB components */ - COLOR_BLACK = 0, /* 000 */ - COLOR_BLUE, /* 001 */ - COLOR_GREEN, /* 010 */ - COLOR_CYAN, /* 011 */ - COLOR_RED, /* 100 */ - COLOR_MAGENTA, /* 101 */ - COLOR_YELLOW, /* 110 */ - COLOR_WHITE, /* 111 */ +/* Colors are represented by indices >= 0 in a stream dependent format. */ +typedef int term_color_t; +/* The value -1 denotes the default (foreground or background) color. */ +enum +{ COLOR_DEFAULT = -1 /* unknown */ -} term_color_t; +}; typedef enum { @@ -68,6 +63,11 @@ struct term_ostream : struct ostream { methods: + /* Convert an RGB value (red, green, blue in [0..255]) to a color, valid + for this stream only. */ + term_color_t rgb_to_color (term_ostream_t stream, + int red, int green, int blue); + /* Get/set the text color. */ term_color_t get_color (term_ostream_t stream); void set_color (term_ostream_t stream, term_color_t color);