##  Copyright (C) 2014  Daniel Kraft <d@domob.eu>
##  GNU Octave level-set package.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn   {Function File} ls_sign_colourmap (@var{type} = @qcode{"sign"})
## @deftypefnx  {Function File} ls_sign_colourmap (@var{colours})
## @deftypefnx  {Function File} {@var{cmap} =} ls_sign_colourmap (@var{ax}, @var{type} = @qcode{"sign"})
## @deftypefnx  {Function File} {@var{cmap} =} ls_sign_colourmap (@var{ax}, @var{colours})
## 
## Construct a colour map that can visibly distinguish between
## positive and negative values.
## These colour maps are especially suited to show the distinction between
## above-zero and below-zero of a level-set function or for speed fields
## between moving outward and inward.
##
## The colour axis range for which it is used should be given in @var{ax}.
## There will always be 1024 entries in the constructed colour map.
##
## The map is ``defined'' by four colours, with a colour gradient between
## the first two for positive values and a gradient between the third and
## fourth for negative values.  These colours can be explicitly given
## in @var{colours} as a 4 x 3 matrix.  Predefined ``types'' of maps
## can be loaded with a string as @var{type} argument.  It can be:
##
## @table @qcode
## @item "sign"
## The default value.  Values above zero will be yellow--red, and values
## below zero cyan--blue.  There is a visible discontinuity in colour
## at the zero level-set, with yellow and cyan on both ``sides'' of the
## transition.
##
## @item "highlight"
## Show zero as white (independent of the sign).  Positive and negative
## values are marked as red and blue, respectively.  This is useful to
## show speed fields and just highlight where they are most active.
## (Like a heat map.)
## @end table
##
## The forms without @var{ax} and output arguments use @code{caxis ()} of the
## current figure and set the figure's colour map to the result, instead of
## returning the constructed colour map.
##
## Use @code{demo ls_sign_colourmap} to get an overview of how the
## predefined maps look like.
##
## @seealso{colormap, colorbar}
## @end deftypefn

function cmap = ls_sign_colourmap (varargin)

  % Start by determining which form of the function was called and doing
  % some usage checks, setting default arguments and all that.

  n = 1024;
  type = "sign";

  if (length (varargin) == 0)
    % No arguments at all:  First form, keep defaults.
    setFig = true;

  elseif (strcmp (typeinfo (varargin{1}), "string"))
    % First form with type given as string.
    if (length (varargin) != 1)
      print_usage ();
    endif
    setFig = true;
    type = varargin{1};

  elseif (min (size (varargin{1})) > 1)
    % First form with colours given.
    if (length (varargin) != 1)
      print_usage ();
    endif
    setFig = true;

    type = "custom";
    colours = varargin{1};

  else
    % Should be second form.
    setFig = false;
    ax = varargin{1};

    if (length (varargin) > 1)
      if (length (varargin) != 2)
        print_usage ();
      endif

      if (strcmp (typeinfo (varargin{2}), "string"))
        type = varargin{2};
      else
        type = "custom";
        colours = varargin{2};
      endif
    endif
  endif

  if (setFig && nargout () > 0)
    print_usage ();
  endif
  if (!setFig && (min (size (ax)) != 1 || length (ax) != 2))
    print_usage ();
  endif

  switch (type)
    case "sign"
      colours = [1, 0, 0; 1, 1, 0; 0, 1, 1; 0, 0, 1];
    case "highlight"
      colours = [1, 0, 0; 1, 1, 1; 1, 1, 1; 0, 0, 1];
    case "custom"
      if (size (colours, 1) != 4 || size (colours, 2) != 3)
        print_usage ();
      endif
    otherwise
      error ("unknown type of colour map: '%s'", type);
  endswitch

  if (setFig)
    ax = caxis ();
  endif

  % Now comes the real calculation of the map.

  cmap = NA (n, 3);

  if (ax(1) >= 0)
    nBelow = 0;
    nAbove = n;
  elseif (ax(2) <= 0)
    nBelow = n;
    nAbove = 0;
  else
    assert (ax(1) < 0 && ax(2) > 0);
    nBelow = round (n * -ax(1) / (ax(2) - ax(1)));
    nAbove = n - nBelow;
    assert (nAbove, round (n * ax(2) / (ax(2) - ax(1))), 1);
  endif

  for i = 1 : nBelow
    theta = (i - 1) / (nBelow - 1);
    cmap(i, :) = theta * colours(3, :) + (1 - theta) * colours(4, :);
  endfor

  for i = 1 : nAbove
    theta = (i - 1) / (nAbove - 1);
    cmap(i + nBelow, :) = theta * colours(1, :) + (1 - theta) * colours(2, :);
  endfor

  if (setFig)
    colormap (cmap);
  endif
endfunction

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Tests.

% The tests check the various usage patterns, but not really
% the functionality.  This can be best checked by a visual inspection
% of the demos.

%!shared colours
%!  colours = [1, 1, 1; 0, 0, 0; 1, 1, 1; 0, 0, 0];

% Test usage of the "first form".
%!error <Invalid call to>
%!  ls_sign_colourmap (1, 2);
%!error <Invalid call to>
%!  ls_sign_colourmap (1);
%!error <Invalid call to>
%!  cmap = ls_sign_colourmap ();
%!error <unknown type of colour map>
%!  ls_sign_colourmap ("foo");
%!test
%!  figure ();
%!  ls_sign_colourmap ();
%!  ls_sign_colourmap ("highlight");
%!  ls_sign_colourmap (colours);
%!  close ();

% Test usage of the "second form".
%!error <Invalid call to>
%!  ls_sign_colourmap (1, 2, 3);
%!error <Invalid call to>
%!  ls_sign_colourmap ([1, 2, 3]);
%!error <Invalid call to>
%!  ls_sign_colourmap ([1, 2], 1);
%!error <unknown type of colour map>
%!  ls_sign_colourmap ([1, 2], "foo");
%!test
%!  cmap = ls_sign_colourmap ([1, 2]);
%!  cmap = ls_sign_colourmap ([1; 2], "highlight");
%!  cmap = ls_sign_colourmap ([1; 2], colours);

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Demos.

% Demo function for the "sign" map.
%!demo
%!  n = 100;
%!  x = linspace (-2, 2, n);
%!  y = linspace (-1, 3, n);
%!  [XX, YY] = meshgrid (x, y);
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 1], 1);
%!
%!  figure ();
%!  imagesc (x, y, phi);
%!  set (gca (), "ydir", "normal");
%!  ls_sign_colourmap ();
%!  colorbar ();
%!  title ("Type 'sign'");

% Demo function for the "highlight" version.
%!demo
%!  n = 100;
%!  x = linspace (-2, 2, n);
%!  h = x(2) - x(1);
%!  [XX, YY] = meshgrid (x, x);
%!
%!  phi = ls_genbasic (XX, YY, "sphere", [0, 0], 1);
%!  d = ls_signed_distance (phi, h);
%!  F = YY .* exp (-10 * d.^2);
%!
%!  figure ();
%!  hold ("on");
%!  imagesc (x, x, F);
%!  set (gca (), "ydir", "normal");
%!  ls_sign_colourmap ("highlight");
%!  colorbar ();
%!  contour (XX, YY, phi, [0, 0], "k", "LineWidth", 2);
%!  hold ("off");
%!  title ("Type 'highlight'");
