#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+/* =========================== termcap interface =========================== */
+
/* Including <curses.h> or <term.h> is dangerous, because it also declares
a lot of junk, such as variables PC, UP, and other. */
#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
/* 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:
/* Inferred values. */
bool supports_foreground;
bool supports_background;
+ colormodel_t colormodel;
bool supports_weight;
bool supports_posture;
bool supports_underline;
&& 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. */
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;
}
{
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;
/* 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)
{
{
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);
(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 =