diff options
| author | Mark Oteiza | 2017-09-26 17:13:36 -0400 |
|---|---|---|
| committer | Mark Oteiza | 2017-09-30 08:00:53 -0400 |
| commit | 645ff6c7029daef082b3a558407121207fa64ff5 (patch) | |
| tree | 2a19d973cdb446169347fb3b7848ce753a8cac51 | |
| parent | 157007b58e41afc848f79c99aced0f09109dfdac (diff) | |
| download | emacs-645ff6c7029daef082b3a558407121207fa64ff5.tar.gz emacs-645ff6c7029daef082b3a558407121207fa64ff5.zip | |
Add CAM02 JCh and CAM02-UCS J'a'b' conversions
* src/lcms.c (rad2deg, parse_jch_list, parse_jab_list, xyz_to_jch):
(jch_to_xyz, jch_to_jab, jab_to_jch): New functions.
(lcms-jch->xyz, lcms-jch->xyz, lcms-jch->jab, lcms-jab->jch): New Lisp
functions.
(lcms-cam02-ucs): Refactor.
(syms_of_lcms2): Declare new functions.
* test/src/lcms-tests.el (lcms-roundtrip, lcms-ciecam02-gold):
(lcms-jmh->cam02-ucs-silver): New tests.
* etc/NEWS: Mention new functions.
| -rw-r--r-- | etc/NEWS | 3 | ||||
| -rw-r--r-- | src/lcms.c | 303 | ||||
| -rw-r--r-- | test/src/lcms-tests.el | 44 |
3 files changed, 315 insertions, 35 deletions
| @@ -76,7 +76,8 @@ If the lcms2 library is installed, Emacs will enable features built on | |||
| 76 | top of that library. The new configure option '--without-lcms2' can | 76 | top of that library. The new configure option '--without-lcms2' can |
| 77 | be used to build without lcms2 support even if it is installed. Emacs | 77 | be used to build without lcms2 support even if it is installed. Emacs |
| 78 | linked to Little CMS exposes color management functions in Lisp: the | 78 | linked to Little CMS exposes color management functions in Lisp: the |
| 79 | color metrics 'lcms-cie-de2000' and 'lcms-cam02-ucs'. | 79 | color metrics 'lcms-cie-de2000' and 'lcms-cam02-ucs', as well as |
| 80 | functions for conversion to and from CIE CAM02 and CAM02-UCS. | ||
| 80 | 81 | ||
| 81 | ** The configure option '--with-gameuser' now defaults to 'no', | 82 | ** The configure option '--with-gameuser' now defaults to 'no', |
| 82 | as this appears to be the most common configuration in practice. | 83 | as this appears to be the most common configuration in practice. |
diff --git a/src/lcms.c b/src/lcms.c index a5e527911ef..c7da57658a9 100644 --- a/src/lcms.c +++ b/src/lcms.c | |||
| @@ -25,6 +25,13 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ | |||
| 25 | 25 | ||
| 26 | #include "lisp.h" | 26 | #include "lisp.h" |
| 27 | 27 | ||
| 28 | typedef struct | ||
| 29 | { | ||
| 30 | double J; | ||
| 31 | double a; | ||
| 32 | double b; | ||
| 33 | } lcmsJab_t; | ||
| 34 | |||
| 28 | #ifdef WINDOWSNT | 35 | #ifdef WINDOWSNT |
| 29 | # include <windows.h> | 36 | # include <windows.h> |
| 30 | # include "w32.h" | 37 | # include "w32.h" |
| @@ -36,6 +43,8 @@ DEF_DLL_FN (cmsHANDLE, cmsCIECAM02Init, | |||
| 36 | (cmsContext ContextID, const cmsViewingConditions* pVC)); | 43 | (cmsContext ContextID, const cmsViewingConditions* pVC)); |
| 37 | DEF_DLL_FN (void, cmsCIECAM02Forward, | 44 | DEF_DLL_FN (void, cmsCIECAM02Forward, |
| 38 | (cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut)); | 45 | (cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut)); |
| 46 | DEF_DLL_FN (void, cmsCIECAM02Reverse, | ||
| 47 | (cmsHANDLE hModel, const cmsJCh* pIn, cmsCIEXYZ* pOut)); | ||
| 39 | DEF_DLL_FN (void, cmsCIECAM02Done, (cmsHANDLE hModel)); | 48 | DEF_DLL_FN (void, cmsCIECAM02Done, (cmsHANDLE hModel)); |
| 40 | DEF_DLL_FN (cmsBool, cmsWhitePointFromTemp, | 49 | DEF_DLL_FN (cmsBool, cmsWhitePointFromTemp, |
| 41 | (cmsCIExyY* WhitePoint, cmsFloat64Number TempK)); | 50 | (cmsCIExyY* WhitePoint, cmsFloat64Number TempK)); |
| @@ -54,6 +63,7 @@ init_lcms_functions (void) | |||
| 54 | LOAD_DLL_FN (library, cmsCIE2000DeltaE); | 63 | LOAD_DLL_FN (library, cmsCIE2000DeltaE); |
| 55 | LOAD_DLL_FN (library, cmsCIECAM02Init); | 64 | LOAD_DLL_FN (library, cmsCIECAM02Init); |
| 56 | LOAD_DLL_FN (library, cmsCIECAM02Forward); | 65 | LOAD_DLL_FN (library, cmsCIECAM02Forward); |
| 66 | LOAD_DLL_FN (library, cmsCIECAM02Reverse); | ||
| 57 | LOAD_DLL_FN (library, cmsCIECAM02Done); | 67 | LOAD_DLL_FN (library, cmsCIECAM02Done); |
| 58 | LOAD_DLL_FN (library, cmsWhitePointFromTemp); | 68 | LOAD_DLL_FN (library, cmsWhitePointFromTemp); |
| 59 | LOAD_DLL_FN (library, cmsxyY2XYZ); | 69 | LOAD_DLL_FN (library, cmsxyY2XYZ); |
| @@ -63,6 +73,7 @@ init_lcms_functions (void) | |||
| 63 | # undef cmsCIE2000DeltaE | 73 | # undef cmsCIE2000DeltaE |
| 64 | # undef cmsCIECAM02Init | 74 | # undef cmsCIECAM02Init |
| 65 | # undef cmsCIECAM02Forward | 75 | # undef cmsCIECAM02Forward |
| 76 | # undef cmsCIECAM02Reverse | ||
| 66 | # undef cmsCIECAM02Done | 77 | # undef cmsCIECAM02Done |
| 67 | # undef cmsWhitePointFromTemp | 78 | # undef cmsWhitePointFromTemp |
| 68 | # undef cmsxyY2XYZ | 79 | # undef cmsxyY2XYZ |
| @@ -70,6 +81,7 @@ init_lcms_functions (void) | |||
| 70 | # define cmsCIE2000DeltaE fn_cmsCIE2000DeltaE | 81 | # define cmsCIE2000DeltaE fn_cmsCIE2000DeltaE |
| 71 | # define cmsCIECAM02Init fn_cmsCIECAM02Init | 82 | # define cmsCIECAM02Init fn_cmsCIECAM02Init |
| 72 | # define cmsCIECAM02Forward fn_cmsCIECAM02Forward | 83 | # define cmsCIECAM02Forward fn_cmsCIECAM02Forward |
| 84 | # define cmsCIECAM02Reverse fn_cmsCIECAM02Reverse | ||
| 73 | # define cmsCIECAM02Done fn_cmsCIECAM02Done | 85 | # define cmsCIECAM02Done fn_cmsCIECAM02Done |
| 74 | # define cmsWhitePointFromTemp fn_cmsWhitePointFromTemp | 86 | # define cmsWhitePointFromTemp fn_cmsWhitePointFromTemp |
| 75 | # define cmsxyY2XYZ fn_cmsxyY2XYZ | 87 | # define cmsxyY2XYZ fn_cmsxyY2XYZ |
| @@ -145,6 +157,12 @@ deg2rad (double degrees) | |||
| 145 | return M_PI * degrees / 180.0; | 157 | return M_PI * degrees / 180.0; |
| 146 | } | 158 | } |
| 147 | 159 | ||
| 160 | static double | ||
| 161 | rad2deg (double radians) | ||
| 162 | { | ||
| 163 | return 180.0 * radians / M_PI; | ||
| 164 | } | ||
| 165 | |||
| 148 | static cmsCIEXYZ illuminant_d65 = { .X = 95.0455, .Y = 100.0, .Z = 108.8753 }; | 166 | static cmsCIEXYZ illuminant_d65 = { .X = 95.0455, .Y = 100.0, .Z = 108.8753 }; |
| 149 | 167 | ||
| 150 | static void | 168 | static void |
| @@ -181,6 +199,46 @@ parse_xyz_list (Lisp_Object xyz_list, cmsCIEXYZ *color) | |||
| 181 | } | 199 | } |
| 182 | 200 | ||
| 183 | static bool | 201 | static bool |
| 202 | parse_jch_list (Lisp_Object jch_list, cmsJCh *color) | ||
| 203 | { | ||
| 204 | #define PARSE_JCH_LIST_FIELD(field) \ | ||
| 205 | if (CONSP (jch_list) && NUMBERP (XCAR (jch_list))) \ | ||
| 206 | { \ | ||
| 207 | color->field = XFLOATINT (XCAR (jch_list)); \ | ||
| 208 | jch_list = XCDR (jch_list); \ | ||
| 209 | } \ | ||
| 210 | else \ | ||
| 211 | return false; | ||
| 212 | |||
| 213 | PARSE_JCH_LIST_FIELD (J); | ||
| 214 | PARSE_JCH_LIST_FIELD (C); | ||
| 215 | PARSE_JCH_LIST_FIELD (h); | ||
| 216 | |||
| 217 | if (! NILP (jch_list)) | ||
| 218 | return false; | ||
| 219 | return true; | ||
| 220 | } | ||
| 221 | |||
| 222 | static bool | ||
| 223 | parse_jab_list (Lisp_Object jab_list, lcmsJab_t *color) | ||
| 224 | { | ||
| 225 | #define PARSE_JAB_LIST_FIELD(field) \ | ||
| 226 | if (CONSP (jab_list) && NUMBERP (XCAR (jab_list))) \ | ||
| 227 | { \ | ||
| 228 | color->field = XFLOATINT (XCAR (jab_list)); \ | ||
| 229 | jab_list = XCDR (jab_list); \ | ||
| 230 | } \ | ||
| 231 | else \ | ||
| 232 | return false; | ||
| 233 | |||
| 234 | PARSE_JAB_LIST_FIELD (J); | ||
| 235 | PARSE_JAB_LIST_FIELD (a); | ||
| 236 | PARSE_JAB_LIST_FIELD (b); | ||
| 237 | |||
| 238 | return true; | ||
| 239 | } | ||
| 240 | |||
| 241 | static bool | ||
| 184 | parse_viewing_conditions (Lisp_Object view, const cmsCIEXYZ *wp, | 242 | parse_viewing_conditions (Lisp_Object view, const cmsCIEXYZ *wp, |
| 185 | cmsViewingConditions *vc) | 243 | cmsViewingConditions *vc) |
| 186 | { | 244 | { |
| @@ -216,6 +274,204 @@ parse_viewing_conditions (Lisp_Object view, const cmsCIEXYZ *wp, | |||
| 216 | return true; | 274 | return true; |
| 217 | } | 275 | } |
| 218 | 276 | ||
| 277 | static void | ||
| 278 | xyz_to_jch (const cmsCIEXYZ *xyz, cmsJCh *jch, const cmsViewingConditions *vc) | ||
| 279 | { | ||
| 280 | cmsHANDLE h; | ||
| 281 | |||
| 282 | h = cmsCIECAM02Init (0, vc); | ||
| 283 | cmsCIECAM02Forward (h, xyz, jch); | ||
| 284 | cmsCIECAM02Done (h); | ||
| 285 | } | ||
| 286 | |||
| 287 | static void | ||
| 288 | jch_to_xyz (const cmsJCh *jch, cmsCIEXYZ *xyz, const cmsViewingConditions *vc) | ||
| 289 | { | ||
| 290 | cmsHANDLE h; | ||
| 291 | |||
| 292 | h = cmsCIECAM02Init (0, vc); | ||
| 293 | cmsCIECAM02Reverse (h, jch, xyz); | ||
| 294 | cmsCIECAM02Done (h); | ||
| 295 | } | ||
| 296 | |||
| 297 | static void | ||
| 298 | jch_to_jab (const cmsJCh *jch, lcmsJab_t *jab, double FL, double c1, double c2) | ||
| 299 | { | ||
| 300 | double Mp = 43.86 * log (1.0 + c2 * (jch->C * sqrt (sqrt (FL)))); | ||
| 301 | jab->J = 1.7 * jch->J / (1.0 + (c1 * jch->J)); | ||
| 302 | jab->a = Mp * cos (deg2rad (jch->h)); | ||
| 303 | jab->b = Mp * sin (deg2rad (jch->h)); | ||
| 304 | } | ||
| 305 | |||
| 306 | static void | ||
| 307 | jab_to_jch (const lcmsJab_t *jab, cmsJCh *jch, double FL, double c1, double c2) | ||
| 308 | { | ||
| 309 | jch->J = jab->J / (1.0 + c1 * (100.0 - jab->J)); | ||
| 310 | jch->h = atan2 (jab->b, jab->a); | ||
| 311 | double Mp = hypot (jab->a, jab->b); | ||
| 312 | jch->h = rad2deg (jch->h); | ||
| 313 | if (jch->h < 0.0) | ||
| 314 | jch->h += 360.0; | ||
| 315 | jch->C = (exp (c2 * Mp) - 1.0) / (c2 * sqrt (sqrt (FL))); | ||
| 316 | } | ||
| 317 | |||
| 318 | DEFUN ("lcms-xyz->jch", Flcms_xyz_to_jch, Slcms_xyz_to_jch, 1, 3, 0, | ||
| 319 | doc: /* Convert CIE CAM02 JCh to CIE XYZ. | ||
| 320 | COLOR is a list (X Y Z), with Y scaled about unity. | ||
| 321 | Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs', | ||
| 322 | which see. */) | ||
| 323 | (Lisp_Object color, Lisp_Object whitepoint, Lisp_Object view) | ||
| 324 | { | ||
| 325 | cmsViewingConditions vc; | ||
| 326 | cmsJCh jch; | ||
| 327 | cmsCIEXYZ xyz, xyzw; | ||
| 328 | |||
| 329 | #ifdef WINDOWSNT | ||
| 330 | if (!lcms_initialized) | ||
| 331 | lcms_initialized = init_lcms_functions (); | ||
| 332 | if (!lcms_initialized) | ||
| 333 | { | ||
| 334 | message1 ("lcms2 library not found"); | ||
| 335 | return Qnil; | ||
| 336 | } | ||
| 337 | #endif | ||
| 338 | |||
| 339 | if (!(CONSP (color) && parse_xyz_list (color, &xyz))) | ||
| 340 | signal_error ("Invalid color", color); | ||
| 341 | if (NILP (whitepoint)) | ||
| 342 | xyzw = illuminant_d65; | ||
| 343 | else if (!(CONSP (whitepoint) && parse_xyz_list (whitepoint, &xyzw))) | ||
| 344 | signal_error ("Invalid white point", whitepoint); | ||
| 345 | if (NILP (view)) | ||
| 346 | default_viewing_conditions (&xyzw, &vc); | ||
| 347 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) | ||
| 348 | signal_error ("Invalid viewing conditions", view); | ||
| 349 | |||
| 350 | xyz_to_jch(&xyz, &jch, &vc); | ||
| 351 | return list3 (make_float (jch.J), make_float (jch.C), make_float (jch.h)); | ||
| 352 | } | ||
| 353 | |||
| 354 | DEFUN ("lcms-jch->xyz", Flcms_jch_to_xyz, Slcms_jch_to_xyz, 1, 3, 0, | ||
| 355 | doc: /* Convert CIE XYZ to CIE CAM02 JCh. | ||
| 356 | COLOR is a list (J C h), where lightness of white is equal to 100, and hue | ||
| 357 | is given in degrees. | ||
| 358 | Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs', | ||
| 359 | which see. */) | ||
| 360 | (Lisp_Object color, Lisp_Object whitepoint, Lisp_Object view) | ||
| 361 | { | ||
| 362 | cmsViewingConditions vc; | ||
| 363 | cmsJCh jch; | ||
| 364 | cmsCIEXYZ xyz, xyzw; | ||
| 365 | |||
| 366 | #ifdef WINDOWSNT | ||
| 367 | if (!lcms_initialized) | ||
| 368 | lcms_initialized = init_lcms_functions (); | ||
| 369 | if (!lcms_initialized) | ||
| 370 | { | ||
| 371 | message1 ("lcms2 library not found"); | ||
| 372 | return Qnil; | ||
| 373 | } | ||
| 374 | #endif | ||
| 375 | |||
| 376 | if (!(CONSP (color) && parse_jch_list (color, &jch))) | ||
| 377 | signal_error ("Invalid color", color); | ||
| 378 | if (NILP (whitepoint)) | ||
| 379 | xyzw = illuminant_d65; | ||
| 380 | else if (!(CONSP (whitepoint) && parse_xyz_list (whitepoint, &xyzw))) | ||
| 381 | signal_error ("Invalid white point", whitepoint); | ||
| 382 | if (NILP (view)) | ||
| 383 | default_viewing_conditions (&xyzw, &vc); | ||
| 384 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) | ||
| 385 | signal_error ("Invalid viewing conditions", view); | ||
| 386 | |||
| 387 | jch_to_xyz(&jch, &xyz, &vc); | ||
| 388 | return list3 (make_float (xyz.X / 100.0), | ||
| 389 | make_float (xyz.Y / 100.0), | ||
| 390 | make_float (xyz.Z / 100.0)); | ||
| 391 | } | ||
| 392 | |||
| 393 | DEFUN ("lcms-jch->jab", Flcms_jch_to_jab, Slcms_jch_to_jab, 1, 3, 0, | ||
| 394 | doc: /* Convert CIE CAM02 JCh to CAM02-UCS J'a'b'. | ||
| 395 | COLOR is a list (J C h) as described in `lcms-jch->xyz', which see. | ||
| 396 | Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs', | ||
| 397 | which see. */) | ||
| 398 | (Lisp_Object color, Lisp_Object whitepoint, Lisp_Object view) | ||
| 399 | { | ||
| 400 | cmsViewingConditions vc; | ||
| 401 | lcmsJab_t jab; | ||
| 402 | cmsJCh jch; | ||
| 403 | cmsCIEXYZ xyzw; | ||
| 404 | double FL, k, k4; | ||
| 405 | |||
| 406 | #ifdef WINDOWSNT | ||
| 407 | if (!lcms_initialized) | ||
| 408 | lcms_initialized = init_lcms_functions (); | ||
| 409 | if (!lcms_initialized) | ||
| 410 | { | ||
| 411 | message1 ("lcms2 library not found"); | ||
| 412 | return Qnil; | ||
| 413 | } | ||
| 414 | #endif | ||
| 415 | |||
| 416 | if (!(CONSP (color) && parse_jch_list (color, &jch))) | ||
| 417 | signal_error ("Invalid color", color); | ||
| 418 | if (NILP (whitepoint)) | ||
| 419 | xyzw = illuminant_d65; | ||
| 420 | else if (!(CONSP (whitepoint) && parse_xyz_list (whitepoint, &xyzw))) | ||
| 421 | signal_error ("Invalid white point", whitepoint); | ||
| 422 | if (NILP (view)) | ||
| 423 | default_viewing_conditions (&xyzw, &vc); | ||
| 424 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) | ||
| 425 | signal_error ("Invalid viewing conditions", view); | ||
| 426 | |||
| 427 | k = 1.0 / (1.0 + (5.0 * vc.La)); | ||
| 428 | k4 = k * k * k * k; | ||
| 429 | FL = vc.La * k4 + 0.1 * (1 - k4) * (1 - k4) * cbrt (5.0 * vc.La); | ||
| 430 | jch_to_jab (&jch, &jab, FL, 0.007, 0.0228); | ||
| 431 | return list3 (make_float (jab.J), make_float (jab.a), make_float (jab.b)); | ||
| 432 | } | ||
| 433 | |||
| 434 | DEFUN ("lcms-jab->jch", Flcms_jab_to_jch, Slcms_jab_to_jch, 1, 3, 0, | ||
| 435 | doc: /* Convert CAM02-UCS J'a'b' to CIE CAM02 JCh. | ||
| 436 | COLOR is a list (J' a' b'), where white corresponds to lightness J equal to 100. | ||
| 437 | Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs', | ||
| 438 | which see. */) | ||
| 439 | (Lisp_Object color, Lisp_Object whitepoint, Lisp_Object view) | ||
| 440 | { | ||
| 441 | cmsViewingConditions vc; | ||
| 442 | cmsJCh jch; | ||
| 443 | lcmsJab_t jab; | ||
| 444 | cmsCIEXYZ xyzw; | ||
| 445 | double FL, k, k4; | ||
| 446 | |||
| 447 | #ifdef WINDOWSNT | ||
| 448 | if (!lcms_initialized) | ||
| 449 | lcms_initialized = init_lcms_functions (); | ||
| 450 | if (!lcms_initialized) | ||
| 451 | { | ||
| 452 | message1 ("lcms2 library not found"); | ||
| 453 | return Qnil; | ||
| 454 | } | ||
| 455 | #endif | ||
| 456 | |||
| 457 | if (!(CONSP (color) && parse_jab_list (color, &jab))) | ||
| 458 | signal_error ("Invalid color", color); | ||
| 459 | if (NILP (whitepoint)) | ||
| 460 | xyzw = illuminant_d65; | ||
| 461 | else if (!(CONSP (whitepoint) && parse_xyz_list (whitepoint, &xyzw))) | ||
| 462 | signal_error ("Invalid white point", whitepoint); | ||
| 463 | if (NILP (view)) | ||
| 464 | default_viewing_conditions (&xyzw, &vc); | ||
| 465 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) | ||
| 466 | signal_error ("Invalid viewing conditions", view); | ||
| 467 | |||
| 468 | k = 1.0 / (1.0 + (5.0 * vc.La)); | ||
| 469 | k4 = k * k * k * k; | ||
| 470 | FL = vc.La * k4 + 0.1 * (1 - k4) * (1 - k4) * cbrt (5.0 * vc.La); | ||
| 471 | jab_to_jch (&jab, &jch, FL, 0.007, 0.0228); | ||
| 472 | return list3 (make_float (jch.J), make_float (jch.C), make_float (jch.h)); | ||
| 473 | } | ||
| 474 | |||
| 219 | /* References: | 475 | /* References: |
| 220 | Li, Luo et al. "The CRI-CAM02UCS colour rendering index." COLOR research | 476 | Li, Luo et al. "The CRI-CAM02UCS colour rendering index." COLOR research |
| 221 | and application, 37 No.3, 2012. | 477 | and application, 37 No.3, 2012. |
| @@ -239,10 +495,9 @@ The default viewing conditions are (20 100 1 1). */) | |||
| 239 | { | 495 | { |
| 240 | cmsViewingConditions vc; | 496 | cmsViewingConditions vc; |
| 241 | cmsJCh jch1, jch2; | 497 | cmsJCh jch1, jch2; |
| 242 | cmsHANDLE h1, h2; | ||
| 243 | cmsCIEXYZ xyz1, xyz2, xyzw; | 498 | cmsCIEXYZ xyz1, xyz2, xyzw; |
| 244 | double Jp1, ap1, bp1, Jp2, ap2, bp2; | 499 | lcmsJab_t jab1, jab2; |
| 245 | double Mp1, Mp2, FL, k, k4; | 500 | double FL, k, k4; |
| 246 | 501 | ||
| 247 | #ifdef WINDOWSNT | 502 | #ifdef WINDOWSNT |
| 248 | if (!lcms_initialized) | 503 | if (!lcms_initialized) |
| @@ -267,41 +522,17 @@ The default viewing conditions are (20 100 1 1). */) | |||
| 267 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) | 522 | else if (!(CONSP (view) && parse_viewing_conditions (view, &xyzw, &vc))) |
| 268 | signal_error ("Invalid view conditions", view); | 523 | signal_error ("Invalid view conditions", view); |
| 269 | 524 | ||
| 270 | h1 = cmsCIECAM02Init (0, &vc); | 525 | xyz_to_jch (&xyz1, &jch1, &vc); |
| 271 | h2 = cmsCIECAM02Init (0, &vc); | 526 | xyz_to_jch (&xyz2, &jch2, &vc); |
| 272 | cmsCIECAM02Forward (h1, &xyz1, &jch1); | ||
| 273 | cmsCIECAM02Forward (h2, &xyz2, &jch2); | ||
| 274 | cmsCIECAM02Done (h1); | ||
| 275 | cmsCIECAM02Done (h2); | ||
| 276 | 527 | ||
| 277 | /* Now have colors in JCh, need to calculate J'a'b' | ||
| 278 | |||
| 279 | M = C * F_L^0.25 | ||
| 280 | J' = 1.7 J / (1 + 0.007 J) | ||
| 281 | M' = 43.86 ln(1 + 0.0228 M) | ||
| 282 | a' = M' cos(h) | ||
| 283 | b' = M' sin(h) | ||
| 284 | |||
| 285 | where | ||
| 286 | |||
| 287 | F_L = 0.2 k^4 (5 L_A) + 0.1 (1 - k^4)^2 (5 L_A)^(1/3), | ||
| 288 | k = 1/(5 L_A + 1) | ||
| 289 | */ | ||
| 290 | k = 1.0 / (1.0 + (5.0 * vc.La)); | 528 | k = 1.0 / (1.0 + (5.0 * vc.La)); |
| 291 | k4 = k * k * k * k; | 529 | k4 = k * k * k * k; |
| 292 | FL = vc.La * k4 + 0.1 * (1 - k4) * (1 - k4) * cbrt (5.0 * vc.La); | 530 | FL = vc.La * k4 + 0.1 * (1 - k4) * (1 - k4) * cbrt (5.0 * vc.La); |
| 293 | Mp1 = 43.86 * log (1.0 + 0.0228 * (jch1.C * sqrt (sqrt (FL)))); | 531 | jch_to_jab (&jch1, &jab1, FL, 0.007, 0.0228); |
| 294 | Mp2 = 43.86 * log (1.0 + 0.0228 * (jch2.C * sqrt (sqrt (FL)))); | 532 | jch_to_jab (&jch2, &jab2, FL, 0.007, 0.0228); |
| 295 | Jp1 = 1.7 * jch1.J / (1.0 + (0.007 * jch1.J)); | 533 | |
| 296 | Jp2 = 1.7 * jch2.J / (1.0 + (0.007 * jch2.J)); | 534 | return make_float (hypot (jab2.J - jab1.J, |
| 297 | ap1 = Mp1 * cos (deg2rad (jch1.h)); | 535 | hypot (jab2.a - jab1.a, jab2.b - jab1.b))); |
| 298 | ap2 = Mp2 * cos (deg2rad (jch2.h)); | ||
| 299 | bp1 = Mp1 * sin (deg2rad (jch1.h)); | ||
| 300 | bp2 = Mp2 * sin (deg2rad (jch2.h)); | ||
| 301 | |||
| 302 | return make_float (sqrt ((Jp2 - Jp1) * (Jp2 - Jp1) + | ||
| 303 | (ap2 - ap1) * (ap2 - ap1) + | ||
| 304 | (bp2 - bp1) * (bp2 - bp1))); | ||
| 305 | } | 536 | } |
| 306 | 537 | ||
| 307 | DEFUN ("lcms-temp->white-point", Flcms_temp_to_white_point, Slcms_temp_to_white_point, 1, 1, 0, | 538 | DEFUN ("lcms-temp->white-point", Flcms_temp_to_white_point, Slcms_temp_to_white_point, 1, 1, 0, |
| @@ -359,6 +590,10 @@ void | |||
| 359 | syms_of_lcms2 (void) | 590 | syms_of_lcms2 (void) |
| 360 | { | 591 | { |
| 361 | defsubr (&Slcms_cie_de2000); | 592 | defsubr (&Slcms_cie_de2000); |
| 593 | defsubr (&Slcms_xyz_to_jch); | ||
| 594 | defsubr (&Slcms_jch_to_xyz); | ||
| 595 | defsubr (&Slcms_jch_to_jab); | ||
| 596 | defsubr (&Slcms_jab_to_jch); | ||
| 362 | defsubr (&Slcms_cam02_ucs); | 597 | defsubr (&Slcms_cam02_ucs); |
| 363 | defsubr (&Slcms2_available_p); | 598 | defsubr (&Slcms2_available_p); |
| 364 | defsubr (&Slcms_temp_to_white_point); | 599 | defsubr (&Slcms_temp_to_white_point); |
diff --git a/test/src/lcms-tests.el b/test/src/lcms-tests.el index d6d1d16b9ad..cc324af68ba 100644 --- a/test/src/lcms-tests.el +++ b/test/src/lcms-tests.el | |||
| @@ -94,6 +94,38 @@ B is considered the exact value." | |||
| 94 | (apply #'color-xyz-to-xyy (lcms-temp->white-point 7504)) | 94 | (apply #'color-xyz-to-xyy (lcms-temp->white-point 7504)) |
| 95 | '(0.29902 0.31485 1.0)))) | 95 | '(0.29902 0.31485 1.0)))) |
| 96 | 96 | ||
| 97 | (ert-deftest lcms-roundtrip () | ||
| 98 | "Test accuracy of converting to and from different color spaces" | ||
| 99 | (skip-unless (featurep 'lcms2)) | ||
| 100 | (should | ||
| 101 | (let ((color '(.5 .3 .7))) | ||
| 102 | (lcms-triple-approx-p (lcms-jch->xyz (lcms-xyz->jch color)) | ||
| 103 | color | ||
| 104 | 0.0001))) | ||
| 105 | (should | ||
| 106 | (let ((color '(.8 -.2 .2))) | ||
| 107 | (lcms-triple-approx-p (lcms-jch->jab (lcms-jab->jch color)) | ||
| 108 | color | ||
| 109 | 0.0001)))) | ||
| 110 | |||
| 111 | (ert-deftest lcms-ciecam02-gold () | ||
| 112 | "Test CIE CAM02 JCh gold values" | ||
| 113 | (skip-unless (featurep 'lcms2)) | ||
| 114 | (should | ||
| 115 | (lcms-triple-approx-p | ||
| 116 | (lcms-xyz->jch '(0.1931 0.2393 0.1014) | ||
| 117 | '(0.9888 0.900 0.3203) | ||
| 118 | '(18 200 1 1.0)) | ||
| 119 | '(48.0314 38.7789 191.0452) | ||
| 120 | 0.02)) | ||
| 121 | (should | ||
| 122 | (lcms-triple-approx-p | ||
| 123 | (lcms-xyz->jch '(0.1931 0.2393 0.1014) | ||
| 124 | '(0.9888 0.90 0.3203) | ||
| 125 | '(18 20 1 1.0)) | ||
| 126 | '(47.6856 36.0527 185.3445) | ||
| 127 | 0.09))) | ||
| 128 | |||
| 97 | (ert-deftest lcms-dE-cam02-ucs-silver () | 129 | (ert-deftest lcms-dE-cam02-ucs-silver () |
| 98 | "Test CRI-CAM02-UCS deltaE metric values from colorspacious." | 130 | "Test CRI-CAM02-UCS deltaE metric values from colorspacious." |
| 99 | (skip-unless (featurep 'lcms2)) | 131 | (skip-unless (featurep 'lcms2)) |
| @@ -114,4 +146,16 @@ B is considered the exact value." | |||
| 114 | 8.503323264883667 | 146 | 8.503323264883667 |
| 115 | 0.04))) | 147 | 0.04))) |
| 116 | 148 | ||
| 149 | (ert-deftest lcms-jmh->cam02-ucs-silver () | ||
| 150 | "Compare JCh conversion to CAM02-UCS to values from colorspacious." | ||
| 151 | (skip-unless (featurep 'lcms2)) | ||
| 152 | (should | ||
| 153 | (lcms-triple-approx-p (lcms-jch->jab '(50 20 10)) | ||
| 154 | '(62.96296296 16.22742674 2.86133316) | ||
| 155 | 0.05)) | ||
| 156 | (should | ||
| 157 | (lcms-triple-approx-p (lcms-jch->jab '(10 60 100)) | ||
| 158 | '(15.88785047 -6.56546789 37.23461867) | ||
| 159 | 0.04))) | ||
| 160 | |||
| 117 | ;;; lcms-tests.el ends here | 161 | ;;; lcms-tests.el ends here |