]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Add support for xterm's 16-, 88-, 256-color modes.
authorBruno Haible <bruno@clisp.org>
Fri, 1 Dec 2006 12:48:21 +0000 (12:48 +0000)
committerBruno Haible <bruno@clisp.org>
Tue, 23 Jun 2009 10:14:26 +0000 (12:14 +0200)
gnulib-local/ChangeLog
gnulib-local/lib/term-ostream.oo.c
gnulib-local/lib/term-ostream.oo.h

index a31eb4a2da1ee26e5b689014a194cd5dc0e5026c..e8b21f70751f03c950c1162291d13fd6bdd9d690 100644 (file)
@@ -1,3 +1,32 @@
+2006-11-30  Bruno Haible  <bruno@clisp.org>
+
+       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  <bruno@clisp.org>
 
        * lib/term-ostream.oo.c (out_attr_change): Fix uses of color_bgr.
index 9269b3a625e5c24aa298b5615bd1b4badd6957b4..0b8787a898147b4592e2bc82768b90b68dc3e285 100644 (file)
@@ -42,6 +42,8 @@
 #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.  */
 
@@ -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 =
index 38dddfb65b5e8e572dbf5333167e8798d17cec68..7c13082c9a82c7c184eb6ac1ade73a113a3a3728 100644 (file)
    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);