]> git.eshelyaron.com Git - emacs.git/commitdiff
Expose viewing conditions in CAM02-UCS metric
authorMark Oteiza <mvoteiza@udel.edu>
Fri, 22 Sep 2017 02:47:24 +0000 (22:47 -0400)
committerMark Oteiza <mvoteiza@udel.edu>
Fri, 22 Sep 2017 03:06:00 +0000 (23:06 -0400)
Also add tests from the colorspacious library.  Finally, catch an
errant calculation, where degrees were not being converted to radians.
* src/lcms.c (deg2rad, default_viewing_conditions):
(parse_viewing_conditions): New functions.
(lcms-cam02-ucs): Add comments pointing to references used.  Expand
the docstring and explain viewing conditions.  JCh hue is given in
degrees and needs to be converted to radians.
(lcms-d65-xyz): Remove.  No need to duplicate this in Lisp or make the
API needlessly impure.
* test/src/lcms-tests.el: Reword commentary.
(lcms-rgb255->xyz): New function.
(lcms-cri-cam02-ucs): Fix let-binding.
(lcms-dE-cam02-ucs-silver): New test, assimilated from colorspacious.

src/lcms.c
test/src/lcms-tests.el

index f543a03039939a40b3ed336f5fd17184f828f331..a5e527911ef5e1a9204cb5035f84a6269a82f040 100644 (file)
@@ -139,6 +139,26 @@ chroma, and hue, respectively. The parameters each default to 1.  */)
   return make_float (cmsCIE2000DeltaE (&Lab1, &Lab2, Kl, Kc, Kh));
 }
 
+static double
+deg2rad (double degrees)
+{
+  return M_PI * degrees / 180.0;
+}
+
+static cmsCIEXYZ illuminant_d65 = { .X = 95.0455, .Y = 100.0, .Z = 108.8753 };
+
+static void
+default_viewing_conditions (const cmsCIEXYZ *wp, cmsViewingConditions *vc)
+{
+  vc->whitePoint.X = wp->X;
+  vc->whitePoint.Y = wp->Y;
+  vc->whitePoint.Z = wp->Z;
+  vc->Yb = 20;
+  vc->La = 100;
+  vc->surround = AVG_SURROUND;
+  vc->D_value = 1.0;
+}
+
 /* FIXME: code duplication */
 
 static bool
@@ -160,11 +180,62 @@ parse_xyz_list (Lisp_Object xyz_list, cmsCIEXYZ *color)
   return true;
 }
 
-DEFUN ("lcms-cam02-ucs", Flcms_cam02_ucs, Slcms_cam02_ucs, 2, 3, 0,
+static bool
+parse_viewing_conditions (Lisp_Object view, const cmsCIEXYZ *wp,
+                          cmsViewingConditions *vc)
+{
+#define PARSE_VIEW_CONDITION_FLOAT(field)                              \
+  if (CONSP (view) && NUMBERP (XCAR (view)))                           \
+    {                                                                  \
+      vc->field = XFLOATINT (XCAR (view));                             \
+      view = XCDR (view);                                              \
+    }                                                                  \
+  else                                                                 \
+    return false;
+#define PARSE_VIEW_CONDITION_INT(field)                                        \
+  if (CONSP (view) && NATNUMP (XCAR (view)))                           \
+    {                                                                  \
+      CHECK_RANGED_INTEGER (XCAR (view), 1, 4);                                \
+      vc->field = XINT (XCAR (view));                                  \
+      view = XCDR (view);                                              \
+    }                                                                  \
+  else                                                                 \
+    return false;
+
+  PARSE_VIEW_CONDITION_FLOAT (Yb);
+  PARSE_VIEW_CONDITION_FLOAT (La);
+  PARSE_VIEW_CONDITION_INT (surround);
+  PARSE_VIEW_CONDITION_FLOAT (D_value);
+
+  if (! NILP (view))
+    return false;
+
+  vc->whitePoint.X = wp->X;
+  vc->whitePoint.Y = wp->Y;
+  vc->whitePoint.Z = wp->Z;
+  return true;
+}
+
+/* References:
+   Li, Luo et al. "The CRI-CAM02UCS colour rendering index." COLOR research
+   and application, 37 No.3, 2012.
+   Luo et al. "Uniform colour spaces based on CIECAM02 colour appearance
+   model." COLOR research and application, 31 No.4, 2006. */
+
+DEFUN ("lcms-cam02-ucs", Flcms_cam02_ucs, Slcms_cam02_ucs, 2, 4, 0,
        doc: /* Compute CAM02-UCS metric distance between COLOR1 and COLOR2.
-Each color is a list of XYZ coordinates, with Y scaled about unity.
-Optional argument is the XYZ white point, which defaults to illuminant D65.  */)
-  (Lisp_Object color1, Lisp_Object color2, Lisp_Object whitepoint)
+Each color is a list of XYZ tristimulus values, with Y scaled about unity.
+Optional argument WHITEPOINT is the XYZ white point, which defaults to
+illuminant D65.
+Optional argument VIEW is a list containing the viewing conditions, and
+is of the form (YB LA SURROUND DVALUE) where SURROUND corresponds to
+  1   AVG_SURROUND
+  2   DIM_SURROUND
+  3   DARK_SURROUND
+  4   CUTSHEET_SURROUND
+The default viewing conditions are (20 100 1 1).  */)
+  (Lisp_Object color1, Lisp_Object color2, Lisp_Object whitepoint,
+   Lisp_Object view)
 {
   cmsViewingConditions vc;
   cmsJCh jch1, jch2;
@@ -188,17 +259,13 @@ Optional argument is the XYZ white point, which defaults to illuminant D65.  */)
   if (!(CONSP (color2) && parse_xyz_list (color2, &xyz2)))
     signal_error ("Invalid color", color2);
   if (NILP (whitepoint))
-    parse_xyz_list (Vlcms_d65_xyz, &xyzw);
+    xyzw = illuminant_d65;
   else if (!(CONSP (whitepoint) && parse_xyz_list (whitepoint, &xyzw)))
     signal_error ("Invalid white point", whitepoint);
-
-  vc.whitePoint.X = xyzw.X;
-  vc.whitePoint.Y = xyzw.Y;
-  vc.whitePoint.Z = xyzw.Z;
-  vc.Yb = 20;
-  vc.La = 100;
-  vc.surround = AVG_SURROUND;
-  vc.D_value = 1.0;
+  if (NILP (view))
+    default_viewing_conditions (&xyzw, &vc);
+  else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc)))
+    signal_error ("Invalid view conditions", view);
 
   h1 = cmsCIECAM02Init (0, &vc);
   h2 = cmsCIECAM02Init (0, &vc);
@@ -227,10 +294,10 @@ Optional argument is the XYZ white point, which defaults to illuminant D65.  */)
   Mp2 = 43.86 * log (1.0 + 0.0228 * (jch2.C * sqrt (sqrt (FL))));
   Jp1 = 1.7 * jch1.J / (1.0 + (0.007 * jch1.J));
   Jp2 = 1.7 * jch2.J / (1.0 + (0.007 * jch2.J));
-  ap1 = Mp1 * cos (jch1.h);
-  ap2 = Mp2 * cos (jch2.h);
-  bp1 = Mp1 * sin (jch1.h);
-  bp2 = Mp2 * sin (jch2.h);
+  ap1 = Mp1 * cos (deg2rad (jch1.h));
+  ap2 = Mp2 * cos (deg2rad (jch2.h));
+  bp1 = Mp1 * sin (deg2rad (jch1.h));
+  bp2 = Mp2 * sin (deg2rad (jch2.h));
 
   return make_float (sqrt ((Jp2 - Jp1) * (Jp2 - Jp1) +
                            (ap2 - ap1) * (ap2 - ap1) +
@@ -291,12 +358,6 @@ DEFUN ("lcms2-available-p", Flcms2_available_p, Slcms2_available_p, 0, 0, 0,
 void
 syms_of_lcms2 (void)
 {
-  DEFVAR_LISP ("lcms-d65-xyz", Vlcms_d65_xyz,
-               doc: /* D65 illuminant as a CIE XYZ triple.  */);
-  Vlcms_d65_xyz = list3 (make_float (0.950455),
-                         make_float (1.0),
-                         make_float (1.088753));
-
   defsubr (&Slcms_cie_de2000);
   defsubr (&Slcms_cam02_ucs);
   defsubr (&Slcms2_available_p);
index 3d0942c8d151c1faea62e2c950e50bf636e10b90..d6d1d16b9ada47aa2e1fd350335c88bd6d66b54e 100644 (file)
 
 ;;; Commentary:
 
-;; Some "exact" values computed using the colorspacious python library
-;; written by Nathaniel J. Smith.  See
-;; https://colorspacious.readthedocs.io/en/v1.1.0/
+;; Some reference values computed using the colorspacious python
+;; library, assimilated from its test suite, or adopted from its
+;; aggregation of gold values.
+;; See https://colorspacious.readthedocs.io/en/v1.1.0/ and
+;; https://github.com/njsmith/colorspacious
 
 ;; Other references:
 ;; http://www.babelcolor.com/index_htm_files/A%20review%20of%20RGB%20color%20spaces.pdf
@@ -49,6 +51,11 @@ B is considered the exact value."
         (lcms-approx-p a2 b2 delta)
         (lcms-approx-p a3 b3 delta))))
 
+(defun lcms-rgb255->xyz (rgb)
+  "Return XYZ tristimulus values corresponding to RGB."
+  (let ((rgb1 (mapcar (lambda (x) (/ x 255.0)) rgb)))
+    (apply #'color-srgb-to-xyz rgb1)))
+
 (ert-deftest lcms-cri-cam02-ucs ()
   "Test use of `lcms-cam02-ucs'."
   (skip-unless (featurep 'lcms2))
@@ -56,8 +63,8 @@ B is considered the exact value."
   (should-error (lcms-cam02-ucs '(0 0 0) 'error))
   (should-not
    (lcms-approx-p
-    (let ((lcms-d65-xyz '(0.44757 1.0 0.40745)))
-      (lcms-cam02-ucs '(0.5 0.5 0.5) '(0 0 0)))
+    (let ((wp '(0.44757 1.0 0.40745)))
+      (lcms-cam02-ucs '(0.5 0.5 0.5) '(0 0 0) wp))
     (lcms-cam02-ucs '(0.5 0.5 0.5) '(0 0 0))))
   (should (eql 0.0 (lcms-cam02-ucs '(0.5 0.5 0.5) '(0.5 0.5 0.5))))
   (should
@@ -87,4 +94,24 @@ B is considered the exact value."
     (apply #'color-xyz-to-xyy (lcms-temp->white-point 7504))
     '(0.29902 0.31485 1.0))))
 
+(ert-deftest lcms-dE-cam02-ucs-silver ()
+  "Test CRI-CAM02-UCS deltaE metric values from colorspacious."
+  (skip-unless (featurep 'lcms2))
+  (should
+   (lcms-approx-p
+    (lcms-cam02-ucs (lcms-rgb255->xyz '(173 52 52))
+                    (lcms-rgb255->xyz '(59 120 51))
+                    lcms-colorspacious-d65
+                    (list 20 (/ 64 float-pi 5) 1 1))
+    44.698469808449964
+    0.03))
+  (should
+   (lcms-approx-p
+    (lcms-cam02-ucs (lcms-rgb255->xyz '(69 100 52))
+                    (lcms-rgb255->xyz '(59 120 51))
+                    lcms-colorspacious-d65
+                    (list 20 (/ 64 float-pi 5) 1 1))
+    8.503323264883667
+    0.04)))
+
 ;;; lcms-tests.el ends here