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
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;
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);
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) +
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);
;;; 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
(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))
(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
(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