function [h,s,v] = rgb2hsv_fast(r,g,b,opt_typ,opt_sel)
%RGB2HSV_FAST does the same as RGB2HSV
%but faster and less memory exhaustive,
%especially on images which are stored as integers.
%
%RGB2HSV_FAST(IMG,'single')
%You can specify the optional argument 'single'
%to use single precision.
%This saves memory and is sufficiently exact, anyhow.
%
%RGB2HSV_FAST(IMG,'','H')
%Optionally, you can specify which channels to calculate:
%'HSV' is default.
%'H' calculates hue only.
%'S' calculates saturation only.
%'V' calculates value only.
%'HS', 'HV', or 'SV' are also possible.

% How to make it efficient?
%  -  Don't cast integer images to floating point too soon.
%     Operate on integers as far as possible and
%     take advantage of functions like IMLINCOMB().
%  -  Avoid FIND(). Directly index via binary arrays instead.
%  -  Avoid RESHAPE().
%     http://blogs.mathworks.com/loren/?p=28

%
% Alexander Ihlow, Germany, Oct 2006
% Version 1.1
% 2006-11-02 added support for individual channel selection
%            version 1.0 saved to rgb2hsv_fast_10.m
%
%
% RGB2HSV_FAST has a high cyclomatic complexity
% due to lots of case checking.
%
% Please report bugs or suggestions to ml-user@web.de.



threeD = ndims(r) >= 3;

if (nargin < 4), opt_typ = ''; end
if (nargin < 5), opt_sel = ''; end
if (nargin > 1) && ~isnumeric(g), opt_typ = g; end
if (nargin > 2) && ~isnumeric(b), opt_sel = b; end

%disp(['threeD = ' num2str(threeD)])
%disp(['opt_typ = ' opt_typ])
%disp(['opt_sel = ' opt_sel])

vv = version; vv=str2double(vv(1));
% Check input class
switch (class(r))
  case 'uint8'
    immax = 255;
  case 'uint16'
    immax = 65535;
  case 'double'
    immax = 1;
  case 'single'
    if vv < 7
      error('Matlab version < 7 cannot perform calculations with single precision. Please cast to double first!')
    else
      immax = 1;
      % If input is single, output single per default.
      if isempty(opt_typ), opt_typ = 'single'; end
    end
  otherwise
    if vv < 7
      error('Only images of type uint8, uint16, and double are supported!')
    else
      error('Only images of type uint8, uint16, single, and double are supported!')
    end
end

% Default output is double.
if isempty(opt_typ), opt_typ = 'double'; end

% Use single precision if optionally specified.
% This saves 1/2 of memory and is exact enough
% for those simple colorspace transformations.
% single : out_typ = 1
% double : out_typ = 2
switch lower(opt_typ(1))
  case 's'
    out_typ = 1;
  otherwise
    out_typ = 2;
end

% For Matlab < version 7, no single precision is supported.
if (out_typ == 1) && (vv < 7)
  disp('Single precision is not available in this Matlab version. Using double.')
  out_typ = 2;
end

% Helper functions to map float either to single or double.
if out_typ == 1
  im2float = inline('im2single(x)');
  float = inline('single(x)');
  floatstr = 'single';
else
  im2float = inline('im2double(x)');
  float = inline('double(x)');
  floatstr = 'double';
end

immax = float(immax);


if threeD
  % Split RGB image into R G B channels and
  % thereby support arbitrary dimensional images.
  % R G B is assumed to be contained in the third dimension.
  sizc = num2cell(size(r));
  for k=1:numel(sizc), sizc{k} = ':'; end
  sizc{3} = 2;
  g = r(sizc{:});
  sizc{3} = 3;
  b = r(sizc{:});
  sizc{3} = 1;
  r = r(sizc{:});
elseif nargin==1
  % split N x 3 matrix into three vectors
  g = r(:,2); b = r(:,3); r = r(:,1);
end
siz = size(r);


if isempty(opt_sel), opt_sel = 'HSV'; else opt_sel = upper(opt_sel); end

if any( (opt_sel ~= 'H') & (opt_sel ~= 'S') & (opt_sel ~= 'V') )
  error('Invalid optional argument! Please use ''H'', ''S'', or ''V''')
end


if exist('imsubtract','file')
  % Image Processing Toolbox available
  % --> The calculation of HUE and SAT
  %     is performed without precasting R, G, and B to floats.
  v = max(max(r,g),b);
  if any(opt_sel == 'H') || any(opt_sel == 'S')
    s = imsubtract(v, min(min(r,g),b));
    z = ~s;
    s = im2float(s);
    s(z) = 1;
    
    if any(opt_sel == 'H')
      % Calculating HUE via IMLINCOMB() is highly efficient.
      sizc = num2cell(siz);
      h(sizc{:}) = float(0);
      k = (r == v);
      h(k) =     imlincomb(1,g(k),-1,b(k),floatstr)./(immax*s(k));
      k = (g == v);
      h(k) = 2 + imlincomb(1,b(k),-1,r(k),floatstr)./(immax*s(k));
      k = (b == v);
      h(k) = 4 + imlincomb(1,r(k),-1,g(k),floatstr)./(immax*s(k));
      
      h = (1/6) * h;
      k = (h < 0);
      h(k) = h(k) + 1;
      
      h(z) = 0;
    end
    
    if any(opt_sel == 'S') || any(opt_sel == 'V')
      k = (v ~= 0);
      v = im2float(v);
      if any(opt_sel == 'S')
        s(k) = (~z(k)).*s(k)./v(k);
        s(~k) = 0;
      end
    end
  else
    v = im2float(v);
  end
  
else
  % Image Processing Toolbox not available
  % --> Use suboptimal procedure.
  v = im2float(max(max(r,g),b));
  if any(opt_sel == 'H') || any(opt_sel == 'S')
    s = v - im2float(min(min(r,g),b));
    z = ~s;
    s(z) = 1;
    
    sizc = num2cell(siz);
    h(sizc{:}) = float(0);
    
    if any(opt_sel == 'H')
      k = (im2float(r) == v);
      h(k) =     (im2float(g(k))-im2float(b(k)))./s(k);
      k = (im2float(g) == v);
      h(k) = 2 + (im2float(b(k))-im2float(r(k)))./s(k);
      k = (im2float(b) == v);
      h(k) = 4 + (im2float(r(k))-im2float(g(k)))./s(k);
      
      h = (1/6) * h;
      k = (h < 0);
      h(k) = h(k) + 1;
      
      h(z) = 0;
    end
    
    if any(opt_sel == 'S')
      k = (v ~= 0);
      s(k) = (~z(k)).*s(k)./v(k);
      s(~k) = 0;
    end
  end

end

if ~any(opt_sel == 'H'), h = float([]); end
if ~any(opt_sel == 'S'), s = float([]); end
if ~any(opt_sel == 'V'), v = float([]); end

if nargout <= 1,
  if (threeD || nargin>=3),
    h = cat(3,h,s,v);
  else
    h = [h s v];
  end
else
  switch opt_sel
    case 'HSV'
      %
    case 'H'
      disp('There is more than one output variable given, but only HUE is assigned to the first one!')
    case 'S'
      h = s;
      disp('There is more than one output variable given, but only SAT is assigned to the first one!')
    case 'V'
      h = v;
      disp('There is more than one output variable given, but only VAL is assigned to the first one!')
    case 'HS'
      if nargout > 2
        disp('There are more than two output variables given, but only HUE and SAT are assigned to the first two!')
      end
    case 'HV'
      s = v;
      if nargout > 2
        disp('There are more than two output variables given, but only HUE and VAL are assigned to the first two!')
      end
    case 'SV'
      h = s;
      s = v;
      if nargout > 2
        disp('There are more than two output variables given, but only SAT and VAL are assigned to the first two!')
      end
    otherwise
      disp('Unrecognized output - please check what happened!')
  end
end
