Expose viewing conditions in CAM02-UCS metric
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.
This commit is contained in:
parent
a81d5a3d3f
commit
d24ec58540
2 changed files with 116 additions and 28 deletions
107
src/lcms.c
107
src/lcms.c
|
@ -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);
|
||||
|
|
|
@ -21,9 +21,11 @@
|
|||
|
||||
;;; 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
|
||||
|
|
Loading…
Add table
Reference in a new issue