function varargout = colorspace(Conversion,varargin)
%COLORSPACE  Transform a color image between color representations.
%   B = COLORSPACE(S,A) transforms the color representation of image A
%   where S is a string specifying the conversion.  The input array A
%   should be a real full double array of size Mx3 or MxNx3.  The output B
%   is the same size as A.
%
%   S tells the source and destination color spaces, S = 'dest<-src', or
%   alternatively, S = 'src->dest'.  Supported color spaces are
%
%     'RGB'              sRGB IEC 61966-2-1
%     'YCbCr'            Luma + Chroma ("digitized" version of Y'PbPr)
%     'JPEG-YCbCr'       Luma + Chroma space used in JFIF JPEG
%     'YDbDr'            SECAM Y'DbDr Luma + Chroma
%     'YPbPr'            Luma (ITU-R BT.601) + Chroma
%     'YUV'              NTSC PAL Y'UV Luma + Chroma
%     'YIQ'              NTSC Y'IQ Luma + Chroma
%     'HSV' or 'HSB'     Hue Saturation Value/Brightness
%     'HSL' or 'HLS'     Hue Saturation Luminance
%     'HSI'              Hue Saturation Intensity
%     'XYZ'              CIE 1931 XYZ
%     'Lab'              CIE 1976 L*a*b* (CIELAB)
%     'Luv'              CIE L*u*v* (CIELUV)
%     'LCH'              CIE L*C*H* (CIELCH)
%     'CAT02 LMS'        CIE CAT02 LMS
%
%  All conversions assume 2 degree observer and D65 illuminant.
%
%  Color space names are case insensitive and spaces are ignored.  When
%  sRGB is the source or destination, it can be omitted. For example
%  'yuv<-' is short for 'yuv<-rgb'.
%
%  For sRGB, the values should be scaled between 0 and 1.  Beware that
%  transformations generally do not constrain colors to be "in gamut."
%  Particularly, transforming from another space to sRGB may obtain
%  R'G'B' values outside of the [0,1] range.  So the result should be
%  clamped to [0,1] before displaying:
%     image(min(max(B,0),1));  % Clamp B to [0,1] and display
%
%  sRGB (Red Green Blue) is the (ITU-R BT.709 gamma-corrected) standard
%  red-green-blue representation of colors used in digital imaging.  The
%  components should be scaled between 0 and 1.  The space can be
%  visualized geometrically as a cube.
%
%  Y'PbPr, Y'CbCr, Y'DbDr, Y'UV, and Y'IQ are related to sRGB by linear
%  transformations.  These spaces separate a color into a grayscale
%  luminance component Y and two chroma components.  The valid ranges of
%  the components depends on the space.
%
%  HSV (Hue Saturation Value) is related to sRGB by
%     H = hexagonal hue angle   (0 <= H < 360),
%     S = C/V                   (0 <= S <= 1),
%     V = max(R',G',B')         (0 <= V <= 1),
%  where C = max(R',G',B') - min(R',G',B').  The hue angle H is computed on
%  a hexagon.  The space is geometrically a hexagonal cone.
%
%  HSL (Hue Saturation Lightness) is related to sRGB by
%     H = hexagonal hue angle                (0 <= H < 360),
%     S = C/(1 - |2L-1|)                     (0 <= S <= 1),
%     L = (max(R',G',B') + min(R',G',B'))/2  (0 <= L <= 1),
%  where H and C are the same as in HSV.  Geometrically, the space is a
%  double hexagonal cone.
%
%  HSI (Hue Saturation Intensity) is related to sRGB by
%     H = polar hue angle        (0 <= H < 360),
%     S = 1 - min(R',G',B')/I    (0 <= S <= 1),
%     I = (R'+G'+B')/3           (0 <= I <= 1).
%  Unlike HSV and HSL, the hue angle H is computed on a circle rather than
%  a hexagon.
%
%  CIE XYZ is related to sRGB by inverse gamma correction followed by a
%  linear transform.  Other CIE color spaces are defined relative to XYZ.
%
%  CIE L*a*b*, L*u*v*, and L*C*H* are nonlinear functions of XYZ.  The L*
%  component is designed to match closely with human perception of
%  lightness.  The other two components describe the chroma.
%
%  CIE CAT02 LMS is the linear transformation of XYZ using the MCAT02
%  chromatic adaptation matrix.  The space is designed to model the
%  response of the three types of cones in the human eye, where L, M, S,
%  correspond respectively to red ("long"), green ("medium"), and blue
%  ("short").

% Copyright © 2005-2010 Pascal Getreuer
% Copyright © 2017-2023 Dynare Team
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in
% the documentation and/or other materials provided with the distribution
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.

%%% Input parsing %%%
if nargin < 2, error('Not enough input arguments.'); end
[SrcSpace,DestSpace] = parse(Conversion);

if nargin == 2
    Image = varargin{1};
elseif nargin >= 3
    Image = cat(3,varargin{:});
else
    error('Invalid number of input arguments.');
end

FlipDims = (size(Image,3) == 1);

if FlipDims, Image = permute(Image,[1,3,2]); end
if ~isa(Image,'double'), Image = double(Image)/255; end
if size(Image,3) ~= 3, error('Invalid input size.'); end

SrcT = gettransform(SrcSpace);
DestT = gettransform(DestSpace);

if ~ischar(SrcT) && ~ischar(DestT)
    % Both source and destination transforms are affine, so they
    % can be composed into one affine operation
    T = [DestT(:,1:3)*SrcT(:,1:3),DestT(:,1:3)*SrcT(:,4)+DestT(:,4)];
    Temp = zeros(size(Image));
    Temp(:,:,1) = T(1)*Image(:,:,1) + T(4)*Image(:,:,2) + T(7)*Image(:,:,3) + T(10);
    Temp(:,:,2) = T(2)*Image(:,:,1) + T(5)*Image(:,:,2) + T(8)*Image(:,:,3) + T(11);
    Temp(:,:,3) = T(3)*Image(:,:,1) + T(6)*Image(:,:,2) + T(9)*Image(:,:,3) + T(12);
    Image = Temp;
elseif ~ischar(DestT)
    Image = rgb(Image,SrcSpace);
    Temp = zeros(size(Image));
    Temp(:,:,1) = DestT(1)*Image(:,:,1) + DestT(4)*Image(:,:,2) + DestT(7)*Image(:,:,3) + DestT(10);
    Temp(:,:,2) = DestT(2)*Image(:,:,1) + DestT(5)*Image(:,:,2) + DestT(8)*Image(:,:,3) + DestT(11);
    Temp(:,:,3) = DestT(3)*Image(:,:,1) + DestT(6)*Image(:,:,2) + DestT(9)*Image(:,:,3) + DestT(12);
    Image = Temp;
else
    Image = feval(DestT,Image,SrcSpace);
end

%%% Output format %%%
if nargout > 1
    varargout = {Image(:,:,1),Image(:,:,2),Image(:,:,3)};
else
    if FlipDims, Image = permute(Image,[1,3,2]); end
    varargout = {Image};
end

return


function [SrcSpace,DestSpace] = parse(Str)
% Parse conversion argument

if ischar(Str)
    Str = lower(strrep(strrep(Str,'-',''),'=',''));
    k = find(Str == '>');

    if length(k) == 1         % Interpret the form 'src->dest'
        SrcSpace = Str(1:k-1);
        DestSpace = Str(k+1:end);
    else
        k = find(Str == '<');

        if length(k) == 1      % Interpret the form 'dest<-src'
            DestSpace = Str(1:k-1);
            SrcSpace = Str(k+1:end);
        else
            error(['Invalid conversion, ''',Str,'''.']);
        end
    end

    SrcSpace = alias(SrcSpace);
    DestSpace = alias(DestSpace);
else
    SrcSpace = 1;             % No source pre-transform
    DestSpace = Conversion;
    if any(size(Conversion) ~= 3), error('Transformation matrix must be 3x3.'); end
end
return


function Space = alias(Space)
Space = strrep(strrep(Space,'cie',''),' ','');

if isempty(Space)
    Space = 'rgb';
end

switch Space
  case {'ycbcr','ycc'}
    Space = 'ycbcr';
  case {'hsv','hsb'}
    Space = 'hsv';
  case {'hsl','hsi','hls'}
    Space = 'hsl';
  case {'rgb','yuv','yiq','ydbdr','jpegycbcr','xyz','lab','luv','lch'}
    return
end
return


function T = gettransform(Space)
% Get a colorspace transform: either a matrix describing an affine transform,
% or a string referring to a conversion subroutine
switch Space
  case 'ypbpr'
    T = [0.299,0.587,0.114,0;-0.1687367,-0.331264,0.5,0;0.5,-0.418688,-0.081312,0];
  case 'yuv'
    % sRGB to NTSC/PAL YUV
    % Wikipedia: http://en.wikipedia.org/wiki/YUV
    T = [0.299,0.587,0.114,0;-0.147,-0.289,0.436,0;0.615,-0.515,-0.100,0];
  case 'ydbdr'
    % sRGB to SECAM YDbDr
    % Wikipedia: http://en.wikipedia.org/wiki/YDbDr
    T = [0.299,0.587,0.114,0;-0.450,-0.883,1.333,0;-1.333,1.116,0.217,0];
  case 'yiq'
    % sRGB in [0,1] to NTSC YIQ in [0,1];[-0.595716,0.595716];[-0.522591,0.522591];
    % Wikipedia: http://en.wikipedia.org/wiki/YIQ
    T = [0.299,0.587,0.114,0;0.595716,-0.274453,-0.321263,0;0.211456,-0.522591,0.311135,0];
  case 'ycbcr'
    % sRGB (range [0,1]) to ITU-R BRT.601 (CCIR 601) Y'CbCr
    % Wikipedia: http://en.wikipedia.org/wiki/YCbCr
    % Poynton, Equation 3, scaling of R'G'B to Y'PbPr conversion
    T = [65.481,128.553,24.966,16;-37.797,-74.203,112.0,128;112.0,-93.786,-18.214,128];
  case 'jpegycbcr'
    % Wikipedia: http://en.wikipedia.org/wiki/YCbCr
    T = [0.299,0.587,0.114,0;-0.168736,-0.331264,0.5,0.5;0.5,-0.418688,-0.081312,0.5]*255;
  case {'rgb','xyz','hsv','hsl','lab','luv','lch','cat02lms'}
    T = Space;
  otherwise
    error(['Unknown color space, ''',Space,'''.']);
end
return


function Image = rgb(Image,SrcSpace)
% Convert to sRGB from 'SrcSpace'
switch SrcSpace
  case 'rgb'
    return
  case 'hsv'
    % Convert HSV to sRGB
    Image = huetorgb((1 - Image(:,:,2)).*Image(:,:,3),Image(:,:,3),Image(:,:,1));
  case 'hsl'
    % Convert HSL to sRGB
    L = Image(:,:,3);
    Delta = Image(:,:,2).*min(L,1-L);
    Image = huetorgb(L-Delta,L+Delta,Image(:,:,1));
  case {'xyz','lab','luv','lch','cat02lms'}
    % Convert to CIE XYZ
    Image = xyz(Image,SrcSpace);
    % Convert XYZ to RGB
    T = [3.2406, -1.5372, -0.4986; -0.9689, 1.8758, 0.0415; 0.0557, -0.2040, 1.057];
    R = T(1)*Image(:,:,1) + T(4)*Image(:,:,2) + T(7)*Image(:,:,3);  % R
    G = T(2)*Image(:,:,1) + T(5)*Image(:,:,2) + T(8)*Image(:,:,3);  % G
    B = T(3)*Image(:,:,1) + T(6)*Image(:,:,2) + T(9)*Image(:,:,3);  % B
                                                                    % Desaturate and rescale to constrain resulting RGB values to [0,1]
    AddWhite = -min(min(min(R,G),B),0);
    R = R + AddWhite;
    G = G + AddWhite;
    B = B + AddWhite;
    % Apply gamma correction to convert linear RGB to sRGB
    Image(:,:,1) = gammacorrection(R);  % R'
    Image(:,:,2) = gammacorrection(G);  % G'
    Image(:,:,3) = gammacorrection(B);  % B'
  otherwise  % Conversion is through an affine transform
    T = gettransform(SrcSpace);
    temp = inv(T(:,1:3));
    T = [temp,-temp*T(:,4)];
    R = T(1)*Image(:,:,1) + T(4)*Image(:,:,2) + T(7)*Image(:,:,3) + T(10);
    G = T(2)*Image(:,:,1) + T(5)*Image(:,:,2) + T(8)*Image(:,:,3) + T(11);
    B = T(3)*Image(:,:,1) + T(6)*Image(:,:,2) + T(9)*Image(:,:,3) + T(12);
    Image(:,:,1) = R;
    Image(:,:,2) = G;
    Image(:,:,3) = B;
end

% Clip to [0,1]
Image = min(max(Image,0),1);
return


function Image = xyz(Image,SrcSpace)
% Convert to CIE XYZ from 'SrcSpace'
WhitePoint = [0.950456,1,1.088754];

switch SrcSpace
  case 'xyz'
    return
  case 'luv'
    % Convert CIE L*uv to XYZ
    WhitePointU = (4*WhitePoint(1))./(WhitePoint(1) + 15*WhitePoint(2) + 3*WhitePoint(3));
    WhitePointV = (9*WhitePoint(2))./(WhitePoint(1) + 15*WhitePoint(2) + 3*WhitePoint(3));
    L = Image(:,:,1);
    Y = (L + 16)/116;
    Y = invf(Y)*WhitePoint(2);
    U = Image(:,:,2)./(13*L + 1e-6*(L==0)) + WhitePointU;
    V = Image(:,:,3)./(13*L + 1e-6*(L==0)) + WhitePointV;
    Image(:,:,1) = -(9*Y.*U)./((U-4).*V - U.*V);                  % X
    Image(:,:,2) = Y;                                             % Y
    Image(:,:,3) = (9*Y - (15*V.*Y) - (V.*Image(:,:,1)))./(3*V);  % Z
  case {'lab','lch'}
    Image = lab(Image,SrcSpace);
    % Convert CIE L*ab to XYZ
    fY = (Image(:,:,1) + 16)/116;
    fX = fY + Image(:,:,2)/500;
    fZ = fY - Image(:,:,3)/200;
    Image(:,:,1) = WhitePoint(1)*invf(fX);  % X
    Image(:,:,2) = WhitePoint(2)*invf(fY);  % Y
    Image(:,:,3) = WhitePoint(3)*invf(fZ);  % Z
  case 'cat02lms'
    % Convert CAT02 LMS to XYZ
    T = inv([0.7328, 0.4296, -0.1624;-0.7036, 1.6975, 0.0061; 0.0030, 0.0136, 0.9834]);
    L = Image(:,:,1);
    M = Image(:,:,2);
    S = Image(:,:,3);
    Image(:,:,1) = T(1)*L + T(4)*M + T(7)*S;  % X
    Image(:,:,2) = T(2)*L + T(5)*M + T(8)*S;  % Y
    Image(:,:,3) = T(3)*L + T(6)*M + T(9)*S;  % Z
  otherwise   % Convert from some gamma-corrected space
              % Convert to sRGB
    Image = rgb(Image,SrcSpace);
    % Undo gamma correction
    R = invgammacorrection(Image(:,:,1));
    G = invgammacorrection(Image(:,:,2));
    B = invgammacorrection(Image(:,:,3));
    % Convert RGB to XYZ
    T = inv([3.2406, -1.5372, -0.4986; -0.9689, 1.8758, 0.0415; 0.0557, -0.2040, 1.057]);
    Image(:,:,1) = T(1)*R + T(4)*G + T(7)*B;  % X
    Image(:,:,2) = T(2)*R + T(5)*G + T(8)*B;  % Y
    Image(:,:,3) = T(3)*R + T(6)*G + T(9)*B;  % Z
end
return


function Image = hsv(Image,SrcSpace)
% Convert to HSV
Image = rgb(Image,SrcSpace);
V = max(Image,[],3);
S = (V - min(Image,[],3))./(V + (V == 0));
Image(:,:,1) = rgbtohue(Image);
Image(:,:,2) = S;
Image(:,:,3) = V;
return


function Image = hsl(Image,SrcSpace)
% Convert to HSL
switch SrcSpace
  case 'hsv'
    % Convert HSV to HSL
    MaxVal = Image(:,:,3);
    MinVal = (1 - Image(:,:,2)).*MaxVal;
    L = 0.5*(MaxVal + MinVal);
    temp = min(L,1-L);
    Image(:,:,2) = 0.5*(MaxVal - MinVal)./(temp + (temp == 0));
    Image(:,:,3) = L;
  otherwise
    Image = rgb(Image,SrcSpace);  % Convert to sRGB
                                  % Convert sRGB to HSL
    MinVal = min(Image,[],3);
    MaxVal = max(Image,[],3);
    L = 0.5*(MaxVal + MinVal);
    temp = min(L,1-L);
    S = 0.5*(MaxVal - MinVal)./(temp + (temp == 0));
    Image(:,:,1) = rgbtohue(Image);
    Image(:,:,2) = S;
    Image(:,:,3) = L;
end
return


function Image = lab(Image,SrcSpace)
% Convert to CIE L*a*b* (CIELAB)
WhitePoint = [0.950456,1,1.088754];

switch SrcSpace
  case 'lab'
    return
  case 'lch'
    % Convert CIE L*CH to CIE L*ab
    C = Image(:,:,2);
    Image(:,:,2) = cos(Image(:,:,3)*pi/180).*C;  % a*
    Image(:,:,3) = sin(Image(:,:,3)*pi/180).*C;  % b*
  otherwise
    Image = xyz(Image,SrcSpace);  % Convert to XYZ
                                  % Convert XYZ to CIE L*a*b*
    X = Image(:,:,1)/WhitePoint(1);
    Y = Image(:,:,2)/WhitePoint(2);
    Z = Image(:,:,3)/WhitePoint(3);
    fX = f(X);
    fY = f(Y);
    fZ = f(Z);
    Image(:,:,1) = 116*fY - 16;    % L*
    Image(:,:,2) = 500*(fX - fY);  % a*
    Image(:,:,3) = 200*(fY - fZ);  % b*
end
return


function Image = luv(Image,SrcSpace)
% Convert to CIE L*u*v* (CIELUV)
WhitePoint = [0.950456,1,1.088754];
WhitePointU = (4*WhitePoint(1))./(WhitePoint(1) + 15*WhitePoint(2) + 3*WhitePoint(3));
WhitePointV = (9*WhitePoint(2))./(WhitePoint(1) + 15*WhitePoint(2) + 3*WhitePoint(3));

Image = xyz(Image,SrcSpace); % Convert to XYZ
Denom = Image(:,:,1) + 15*Image(:,:,2) + 3*Image(:,:,3);
U = (4*Image(:,:,1))./(Denom + (Denom == 0));
V = (9*Image(:,:,2))./(Denom + (Denom == 0));
Y = Image(:,:,2)/WhitePoint(2);
L = 116*f(Y) - 16;
Image(:,:,1) = L;                        % L*
Image(:,:,2) = 13*L.*(U - WhitePointU);  % u*
Image(:,:,3) = 13*L.*(V - WhitePointV);  % v*
return


function Image = lch(Image,SrcSpace)
% Convert to CIE L*ch
Image = lab(Image,SrcSpace);  % Convert to CIE L*ab
H = atan2(Image(:,:,3),Image(:,:,2));
H = H*180/pi + 360*(H < 0);
Image(:,:,2) = sqrt(Image(:,:,2).^2 + Image(:,:,3).^2);  % C
Image(:,:,3) = H;                                        % H
return


function Image = cat02lms(Image,SrcSpace)
% Convert to CAT02 LMS
Image = xyz(Image,SrcSpace);
T = [0.7328, 0.4296, -0.1624;-0.7036, 1.6975, 0.0061; 0.0030, 0.0136, 0.9834];
X = Image(:,:,1);
Y = Image(:,:,2);
Z = Image(:,:,3);
Image(:,:,1) = T(1)*X + T(4)*Y + T(7)*Z;  % L
Image(:,:,2) = T(2)*X + T(5)*Y + T(8)*Z;  % M
Image(:,:,3) = T(3)*X + T(6)*Y + T(9)*Z;  % S
return


function Image = huetorgb(m0,m2,H)
% Convert HSV or HSL hue to RGB
N = size(H);
H = min(max(H(:),0),360)/60;
m0 = m0(:);
m2 = m2(:);
F = H - round(H/2)*2;
M = [m0, m0 + (m2-m0).*abs(F), m2];
Num = length(m0);
j = [2 1 0;1 2 0;0 2 1;0 1 2;1 0 2;2 0 1;2 1 0]*Num;
k = floor(H) + 1;
Image = reshape([M(j(k,1)+(1:Num).'),M(j(k,2)+(1:Num).'),M(j(k,3)+(1:Num).')],[N,3]);
return


function H = rgbtohue(Image)
% Convert RGB to HSV or HSL hue
[M,i] = sort(Image,3);
i = i(:,:,3);
Delta = M(:,:,3) - M(:,:,1);
Delta = Delta + (Delta == 0);
R = Image(:,:,1);
G = Image(:,:,2);
B = Image(:,:,3);
H = zeros(size(R));
k = (i == 1);
H(k) = (G(k) - B(k))./Delta(k);
k = (i == 2);
H(k) = 2 + (B(k) - R(k))./Delta(k);
k = (i == 3);
H(k) = 4 + (R(k) - G(k))./Delta(k);
H = 60*H + 360*(H < 0);
H(Delta == 0) = nan;
return


function Rp = gammacorrection(R)
Rp = zeros(size(R));
i = (R <= 0.0031306684425005883);
Rp(i) = 12.92*R(i);
Rp(~i) = real(1.055*R(~i).^0.416666666666666667 - 0.055);
return


function R = invgammacorrection(Rp)
R = zeros(size(Rp));
i = (Rp <= 0.0404482362771076);
R(i) = Rp(i)/12.92;
R(~i) = real(((Rp(~i) + 0.055)/1.055).^2.4);
return


function fY = f(Y)
fY = real(Y.^(1/3));
i = (Y < 0.008856);
fY(i) = Y(i)*(841/108) + (4/29);
return


function Y = invf(fY)
Y = fY.^3;
i = (Y < 0.008856);
Y(i) = (fY(i) - 4/29)*(108/841);
return
