1## Copyright (C) 2015 Hartmut Gimpel
2##
3## This program is free software; you can redistribute it and/or
4## modify it under the terms of the GNU General Public License as
5## published by the Free Software Foundation; either version 3 of the
6## License, or (at your option) any later version.
7##
8## This program is distributed in the hope that it will be useful, but
9## WITHOUT ANY WARRANTY; without even the implied warranty of
10## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11## General Public License for more details.
12##
13## You should have received a copy of the GNU General Public License
14## along with this program; if not, see
15## <http:##www.gnu.org/licenses/>.
16
17## -*- texinfo -*-
18## @deftypefn  {Function File} {@var{lab} =} xyz2lab (@var{xyz})
19## @deftypefnx {Function File} {@var{lab_map} =} xyz2lab (@var{xyz_map})
20## Transform a colormap or image from CIE XYZ to CIE L*a*b* color space.
21##
22## A color in the CIE XYZ color space consists of three values X, Y and Z.
23## Those values are designed to be colorimetric, meaning that their values
24## do not depend on the display device hardware.
25##
26## A color in the CIE L*a*b* (or CIE Lab) space consists of lightness L* and
27## two color-opponent dimensions a* and b*. The whitepoint is taken as D65.
28## The CIE L*a*b* colorspace is also a colorimetric colorspace. It is designed
29## to incorporate the human perception of color differences.
30##
31## Input values of class single and double are accepted.
32## The shape and the class of the input are conserved.
33##
34## The return values of L* are normally in the inteval [0, 100]
35## and the values of a* and b* in the interval [-127, 127]
36##
37## @seealso{lab2xyz, rgb2lab, rgb2hsv, rgb2ind, rgb2ntsc}
38## @end deftypefn
39
40## Author: Hartmut Gimpel <hg_code@gmx.de>
41## algorithm taken from the following book:
42## Burger, Burge "Digitale Bildverarbeitung", 3rd edition (2015)
43
44function lab = xyz2lab (xyz)
45
46  if (nargin != 1)
47    print_usage ();
48  endif
49
50  [xyz, cls, sz, is_im, is_nd, is_int] ...
51    = colorspace_conversion_input_check ("xyz2lab", "XYZ", xyz, 1);
52  #  only accept single and double inputs because valid xyz values can be >1
53
54  ## normalize with the whitepoint D65
55  ## (reference: en.wikipedia.org/wiki/Illuminant_D65)
56  D65 = [0.95047, 1, 1.08883];
57  xyz_D65 = xyz ./ D65;
58  # Matlab truncates to D65_Matlab = [0.9504, 1.0000, 1.0888];
59
60  ## transformation xyz -> xyz'
61  epsilon = (6/29)^3;
62  kappa = 1/116 * (29/3)^3;
63  xyz_prime = xyz_D65;
64  mask = xyz_D65 <= epsilon;
65  xyz_prime(mask) = kappa .* xyz_D65(mask) + 16/116;
66  xyz_prime(! mask) = xyz_D65(! mask) .^(1/3);
67  x_prime = xyz_prime(:,1);
68  y_prime = xyz_prime(:,2);
69  z_prime = xyz_prime(:,3);
70
71  ## transformation xyz' -> lab
72  L = 116 .* y_prime - 16;
73  a = 500 .* (x_prime - y_prime);
74  b = 200 .* (y_prime -  z_prime);
75
76  lab = [L, a, b];
77
78  # always return values of type double for Matlab compatibility (exception: type single)
79  lab = colorspace_conversion_revert (lab, cls, sz, is_im, is_nd, is_int, 1);
80
81endfunction
82
83## Test pure colors, gray and some other colors
84## (This set of test values is taken from the book by Burger.)
85%!assert (xyz2lab ([0, 0, 0]), [0 0 0], 5e-2)
86%!assert (xyz2lab ([0.4125, 0.2127, 0.0193]), [53.24, 80.09, 67.20], 5e-2)
87%!assert (xyz2lab ([0.7700, 0.9278, 0.1385]), [97.14, -21.55, 94.48], 5e-2)
88%!assert (xyz2lab ([0.3576, 0.7152, 0.1192]), [87.74, -86.18, 83.18], 5e-2)
89%!assert (xyz2lab ([0.5380, 0.7873, 1.0694]), [91.11, -48.09, -14.13], 5e-2)
90%!assert (xyz2lab ([0.1804, 0.07217, 0.9502]), [32.30, 79.19, -107.86], 5e-2)
91%!assert (xyz2lab ([0.5929, 0.28484, 0.9696]), [60.32, 98.24, -60.83], 5e-2)
92%!assert (xyz2lab ([0.9505, 1.0000, 1.0888]), [100, 0.00, 0.00], 5e-2)
93%!assert (xyz2lab ([0.2034, 0.2140, 0.2330]), [53.39, 0.00, 0.00], 5e-2)
94%!assert (xyz2lab ([0.2155, 0.1111, 0.0101]), [39.77, 64.51, 54.13], 5e-2)
95%!assert (xyz2lab ([0.0883, 0.0455, 0.0041]), [25.42, 47.91, 37.91], 5e-2)
96%!assert (xyz2lab ([0.02094, 0.0108, 0.00098]), [9.66, 29.68, 15.24], 5e-2)
97%!assert (xyz2lab ([0.5276, 0.3812, 0.2482]), [68.11, 48.39, 22.83], 5e-2)
98
99## Test tolarant input checking on floats
100%!assert (xyz2lab ([1.5 1 1]), [100, 82.15, 5.60], 5e-2)
101
102%! xyz_map = rand (64, 3);
103%! assert (lab2xyz (xyz2lab (xyz_map)), xyz_map, 1e-5);
104
105%!test
106%! xyz_img = rand (64, 64, 3);
107%! assert (lab2xyz (xyz2lab (xyz_img)), xyz_img, 1e-5);
108
109## support sparse input  (the only useful xyz value with zeros is black)
110%!assert (xyz2lab (sparse ([0 0 0])), [0 0 0], 5e-2)
111
112## conserve class of single input
113%!assert (class (xyz2lab (single([0.5 0.5 0.5]))), 'single')
114
115## Test input validation
116%!error xyz2lab ()
117%!error xyz2lab (1,2)
118%!error <invalid data type 'cell'> xyz2lab ({1})
119%!error <XYZ must be a colormap or XYZ image> xyz2lab (ones (2,2))
120
121## Test ND input
122%!test
123%! xyz = rand (16, 16, 3, 5);
124%! lab = zeros (size (xyz));
125%! for i = 1:5
126%!   lab(:,:,:,i) = xyz2lab (xyz(:,:,:,i));
127%! endfor
128%! assert (xyz2lab (xyz), lab)
129