Subject: 26.0.50; lcms2 bindings

From: Mark Oteiza <[email-masked]>
Date: Sat, 09 Sep 2017 11:50:34 -0400

Wishlist.

Hi,

Some time ago I wrote some integration with lcms2 in the interest of
replacing tty-color-approximate, color-distance, etc. with superior
(more perceptually uniform) color metrics.  This would presumably
improve Emacs' color picking on smaller color palettes (e.g. 256 color
term) and potentially provide access to many useful color-related
functions in Lisp---not to discount color.el.

I am not sure how this might be added--perhaps in its own .c file and
exposing a feature?

Attached is a patch splicing lcms2 CIE DE2000 into color-distance.
I don't know what x_alloc_nearest_color_1 does but I apparently patched
that as well.

Also attached is a small module with CIE DE2000 and CAM02-UCS color
distance functions.

diff --git a/configure.ac b/configure.ac
index 5aaf006c54..e85c1e4d97 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3151,7 +3151,7 @@ AC_DEFUN
 	OLD_LIBS="$LIBS"
 	CPPFLAGS="$CPPFLAGS $XFT_CFLAGS"
 	CFLAGS="$CFLAGS $XFT_CFLAGS"
-	XFT_LIBS="-lXrender $XFT_LIBS"
+	XFT_LIBS="-lXrender $XFT_LIBS -llcms2"
 	LIBS="$XFT_LIBS $LIBS"
 	AC_CHECK_HEADER(X11/Xft/Xft.h,
 	  AC_CHECK_LIB(Xft, XftFontOpen, HAVE_XFT=yes, , $XFT_LIBS) , ,
diff --git a/src/xfaces.c b/src/xfaces.c
index accb98bf4c..604404aafd 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -205,6 +205,8 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <sys/types.h>
 #include <sys/stat.h>
 
+#include <lcms2.h>
+
 #include "lisp.h"
 #include "character.h"
 #include "frame.h"
@@ -352,7 +354,7 @@ static struct face_cache *make_face_cache (struct frame *);
 static void free_face_cache (struct face_cache *);
 static bool merge_face_ref (struct frame *, Lisp_Object, Lisp_Object *,
 			    bool, struct named_merge_point *);
-static int color_distance (XColor *x, XColor *y);
+static double color_distance (XColor *x, XColor *y);
 
 #ifdef HAVE_WINDOW_SYSTEM
 static void set_font_frame_param (Lisp_Object, Lisp_Object);
@@ -4063,35 +4065,41 @@ prepare_face_for_display (struct frame *f, struct face *face)
 
 /* Returns the `distance' between the colors X and Y.  */
 
-static int
+static double
 color_distance (XColor *x, XColor *y)
 {
-  /* This formula is from a paper titled `Colour metric' by Thiadmer Riemersma.
-     Quoting from that paper:
-
-	 This formula has results that are very close to L*u*v* (with the
-	 modified lightness curve) and, more importantly, it is a more even
-	 algorithm: it does not have a range of colors where it suddenly
-	 gives far from optimal results.
-
-     See <http://www.compuphase.com/cmetric.htm> for more info.  */
-
-  long r = (x->red   - y->red)   >> 8;
-  long g = (x->green - y->green) >> 8;
-  long b = (x->blue  - y->blue)  >> 8;
-  long r_mean = (x->red + y->red) >> 9;
-
-  return
-    (((512 + r_mean) * r * r) >> 8)
-    + 4 * g * g
-    + (((767 - r_mean) * b * b) >> 8);
+  /* http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf> */
+  cmsHPROFILE profile_in, profile_out;
+  cmsHTRANSFORM transform;
+  cmsCIELab Labx, Laby;
+  cmsUInt16Number rgbx[3], rgby[3];
+  cmsFloat64Number delta;
+
+  profile_in = cmsCreate_sRGBProfile();
+  profile_out = cmsCreateLab4Profile(NULL);
+  transform = cmsCreateTransform(profile_in, TYPE_RGB_16,
+                                 profile_out, TYPE_Lab_DBL,
+                                 INTENT_PERCEPTUAL, 0);
+  cmsCloseProfile(profile_in);
+  cmsCloseProfile(profile_out);
+  rgbx[0] = x->red;
+  rgbx[1] = x->green;
+  rgbx[2] = x->blue;
+  rgby[0] = y->red;
+  rgby[1] = y->green;
+  rgby[2] = y->blue;
+  cmsDoTransform(transform, rgbx, &Labx, 1);
+  cmsDoTransform(transform, rgby, &Laby, 1);
+  cmsDeleteTransform(transform);
+  delta = cmsCIE2000DeltaE(&Labx, &Laby, 1.0, 1.0, 1.0);
+  return delta;
 }

 DEFUN ("color-distance", Fcolor_distance, Scolor_distance, 2, 3, 0,
-       doc: /* Return an integer distance between COLOR1 and COLOR2 on FRAME.
+       doc: /* Return a float distance between COLOR1 and COLOR2 on FRAME.
 COLOR1 and COLOR2 may be either strings containing the color name,
-or lists of the form (RED GREEN BLUE).
+or lists of the form (RED GREEN BLUE), in the range 0 to 65355 inclusive.
 If FRAME is unspecified or nil, the current frame is used.  */)
   (Lisp_Object color1, Lisp_Object color2, Lisp_Object frame)
 {
@@ -4107,7 +4115,7 @@ If FRAME is unspecified or nil, the current frame is used.  */)
 	   && defined_color (f, SSDATA (color2), &cdef2, false)))
     signal_error ("Invalid color", color2);
 
-  return make_number (color_distance (&cdef1, &cdef2));
+  return make_float (color_distance (&cdef1, &cdef2));
 }
 
 
@@ -4627,9 +4635,9 @@ DEFUN ("face-attributes-as-vector", Fface_attributes_as_vector,
 
 /* If the distance (as returned by color_distance) between two colors is
    less than this, then they are considered the same, for determining
-   whether a color is supported or not.  The range of values is 0-65535.  */
+   whether a color is supported or not.  The range of values is 0-100.  */
 
-#define TTY_SAME_COLOR_THRESHOLD  10000
+#define TTY_SAME_COLOR_THRESHOLD  2.3
 
 #ifdef HAVE_WINDOW_SYSTEM
 
@@ -4897,7 +4905,7 @@ tty_supports_face_attributes_p (struct frame *f,
      distance between the standard foreground and background.  */
   if (STRINGP (fg) && STRINGP (bg))
     {
-      int delta_delta
+      double delta_delta
 	= (color_distance (&fg_std_color, &bg_std_color)
 	   - color_distance (&fg_tty_color, &bg_tty_color));
       if (delta_delta > TTY_SAME_COLOR_THRESHOLD
diff --git a/src/xterm.c b/src/xterm.c
index bdc21e6de0..8cb203d418 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -27,6 +27,8 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #include <math.h>
 #endif
 
+#include <lcms2.h>
+
 #include "lisp.h"
 #include "blockinput.h"
 
@@ -2372,18 +2374,37 @@ x_alloc_nearest_color_1 (Display *dpy, Colormap cmap, XColor *color)
 	 a least-squares matching, which is what X uses for closest
 	 color matching with StaticColor visuals.  */
       int nearest, i;
-      int max_color_delta = 255;
-      int max_delta = 3 * max_color_delta;
-      int nearest_delta = max_delta + 1;
+      cmsFloat64Number max_color_delta = 100.0f;
+      cmsFloat64Number max_delta = 3 * max_color_delta;
+      cmsFloat64Number nearest_delta = max_delta + 1;
       int ncells;
       const XColor *cells = x_color_cells (dpy, &ncells);
-
+      cmsHPROFILE profile_in, profile_out;
+      cmsHTRANSFORM transform;
+      cmsCIELab Lab;
+      cmsUInt16Number rgb[3];
+
+      profile_in = cmsCreate_sRGBProfile();
+      profile_out = cmsCreateLab4Profile(NULL);
+      transform = cmsCreateTransform(profile_in, TYPE_RGB_16,
+                                     profile_out, TYPE_Lab_DBL,
+                                     INTENT_PERCEPTUAL, 0);
+      cmsCloseProfile(profile_in);
+      cmsCloseProfile(profile_out);
+      rgb[0] = color->red;
+      rgb[1] = color->green;
+      rgb[2] = color->blue;
+      cmsDoTransform(transform, rgb, &Lab, 1);
       for (nearest = i = 0; i < ncells; ++i)
 	{
-	  int dred   = (color->red   >> 8) - (cells[i].red   >> 8);
-	  int dgreen = (color->green >> 8) - (cells[i].green >> 8);
-	  int dblue  = (color->blue  >> 8) - (cells[i].blue  >> 8);
-	  int delta = dred * dred + dgreen * dgreen + dblue * dblue;
+          cmsCIELab Labi;
+          cmsUInt16Number rgbi[3];
+
+          rgbi[0] = cells[i].red;
+          rgbi[1] = cells[i].green;
+          rgbi[2] = cells[i].blue;
+          cmsDoTransform(transform, rgbi, &Labi, 1);
+          cmsFloat64Number delta = cmsCIE2000DeltaE(&Lab, &Labi, 1.0f, 1.0f, 1.0f);
 
 	  if (delta < nearest_delta)
 	    {
@@ -2391,7 +2412,7 @@ x_alloc_nearest_color_1 (Display *dpy, Colormap cmap, XColor *color)
 	      nearest_delta = delta;
 	    }
 	}
-
+      cmsDeleteTransform(transform);
       color->red   = cells[nearest].red;
       color->green = cells[nearest].green;
       color->blue  = cells[nearest].blue;