aboutsummaryrefslogtreecommitdiffstats
path: root/src/lcms.c
diff options
context:
space:
mode:
authorMark Oteiza2017-09-26 17:13:36 -0400
committerMark Oteiza2017-09-30 08:00:53 -0400
commit645ff6c7029daef082b3a558407121207fa64ff5 (patch)
tree2a19d973cdb446169347fb3b7848ce753a8cac51 /src/lcms.c
parent157007b58e41afc848f79c99aced0f09109dfdac (diff)
downloademacs-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.
Diffstat (limited to 'src/lcms.c')
-rw-r--r--src/lcms.c303
1 files changed, 269 insertions, 34 deletions
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
28typedef 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));
37DEF_DLL_FN (void, cmsCIECAM02Forward, 44DEF_DLL_FN (void, cmsCIECAM02Forward,
38 (cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut)); 45 (cmsHANDLE hModel, const cmsCIEXYZ* pIn, cmsJCh* pOut));
46DEF_DLL_FN (void, cmsCIECAM02Reverse,
47 (cmsHANDLE hModel, const cmsJCh* pIn, cmsCIEXYZ* pOut));
39DEF_DLL_FN (void, cmsCIECAM02Done, (cmsHANDLE hModel)); 48DEF_DLL_FN (void, cmsCIECAM02Done, (cmsHANDLE hModel));
40DEF_DLL_FN (cmsBool, cmsWhitePointFromTemp, 49DEF_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
160static double
161rad2deg (double radians)
162{
163 return 180.0 * radians / M_PI;
164}
165
148static cmsCIEXYZ illuminant_d65 = { .X = 95.0455, .Y = 100.0, .Z = 108.8753 }; 166static cmsCIEXYZ illuminant_d65 = { .X = 95.0455, .Y = 100.0, .Z = 108.8753 };
149 167
150static void 168static void
@@ -181,6 +199,46 @@ parse_xyz_list (Lisp_Object xyz_list, cmsCIEXYZ *color)
181} 199}
182 200
183static bool 201static bool
202parse_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
222static bool
223parse_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
241static bool
184parse_viewing_conditions (Lisp_Object view, const cmsCIEXYZ *wp, 242parse_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
277static void
278xyz_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
287static void
288jch_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
297static void
298jch_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
306static void
307jab_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
318DEFUN ("lcms-xyz->jch", Flcms_xyz_to_jch, Slcms_xyz_to_jch, 1, 3, 0,
319 doc: /* Convert CIE CAM02 JCh to CIE XYZ.
320COLOR is a list (X Y Z), with Y scaled about unity.
321Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs',
322which 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
354DEFUN ("lcms-jch->xyz", Flcms_jch_to_xyz, Slcms_jch_to_xyz, 1, 3, 0,
355 doc: /* Convert CIE XYZ to CIE CAM02 JCh.
356COLOR is a list (J C h), where lightness of white is equal to 100, and hue
357is given in degrees.
358Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs',
359which 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
393DEFUN ("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'.
395COLOR is a list (J C h) as described in `lcms-jch->xyz', which see.
396Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs',
397which 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
434DEFUN ("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.
436COLOR is a list (J' a' b'), where white corresponds to lightness J equal to 100.
437Optional arguments WHITEPOINT and VIEW are the same as in `lcms-cam02-ucs',
438which 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
307DEFUN ("lcms-temp->white-point", Flcms_temp_to_white_point, Slcms_temp_to_white_point, 1, 1, 0, 538DEFUN ("lcms-temp->white-point", Flcms_temp_to_white_point, Slcms_temp_to_white_point, 1, 1, 0,
@@ -359,6 +590,10 @@ void
359syms_of_lcms2 (void) 590syms_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);