view scripts/plot/appearance/whitebg.m @ 22679:e70551dacef6

whitebg.m: Completely overhaul function. * whitebg.m: Rewrite docstring adding more calling forms, more detail, and a Programming Note about how function works. Add FIXME notes about unclear parts of Matlab compatibility. Add input validation. Add Input validation tests. Adjust BIST tests to test new behavior. Add new input 'none' to return colors to factory defaults. Use regexp for performance instead of multiple cellfun calls. Correct typo in error() text messages. * whitebg.m (calc_contrast_color): New function to attempt to find a contrasting color to the one specified by the user for the background.
author Rik <rik@octave.org>
date Thu, 27 Oct 2016 10:41:31 -0700
parents bac0d6f07a3e
children 5c04055aa767
line wrap: on
line source

## Copyright (C) 2010-2016 David Bateman
##
## This file is part of Octave.
##
## Octave 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.
##
## Octave 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 Octave; see the file COPYING.  If not, see
## <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {} {} whitebg ()
## @deftypefnx {} {} whitebg (@var{color})
## @deftypefnx {} {} whitebg ("none")
## @deftypefnx {} {} whitebg (@var{hfig})
## @deftypefnx {} {} whitebg (@var{hfig}, @var{color})
## @deftypefnx {} {} whitebg (@var{hfig}, "none")
## Invert the colors in the current color scheme.
##
## The root properties are also inverted such that all subsequent plots will
## use the new color scheme.
##
## If the optional argument @var{color} is present then the background color
## is set to @var{color} rather than inverted.  @var{color} may be a string
## representing one of the eight known colors or an RGB triplet.  The special
## string argument @qcode{"none"} restores the plot to the factory default
## colors.
##
## If the first argument @var{hfig} is a figure handle or list of figure
## handles, then operate on these figures rather than the current figure
## returned by @code{gcf}.  The root properties will not be changed unless 0
## is in the list of figures.
##
## Programming Note: @code{whitebg} operates by changing the color properties
## of the children of the specified figures.  Only objects with a single color
## are affected.  For example, a patch with a single @qcode{"FaceColor"} will
## be changed, but a patch with shading (@qcode{"interp"}) will not be
## modified.  For inversion, the new color is simply the inversion in RGB
## space: @code{@var{cnew} = [1-@var{R} 1-@var{G} 1-@var{B}]}.  When a color
## is specified, the axes and figure are set to the new color, and the color
## of child objects are then adjusted to have some contrast (visibility)
## against the new background.
## @seealso{reset, get, set}
## @end deftypefn

## FIXME: It's not clear whether Matlab also changes color properties
## of the figure object itself, or only the children.  However, visually,
## it looks better to change the figure along with the axes background.

function whitebg (varargin)

  if (nargin > 2)
    print_usage ();
  endif

  h = 0;
  color = NaN;
  have_fig = false;

  if (nargin > 0)
    if (all (ishandle (varargin{1})))
      h = varargin{1};
      have_fig = true;
      if (nargin == 2)
        color = varargin{2};
      endif
    elseif (nargin == 1)
      color = varargin{1};
    else
      print_usage ();
    endif
  endif

  if (! have_fig)
    fig = gcf ();
    do_root = true;
  elseif (all (isfigure (h) | h == 0))
    fig = h(h != 0);
    do_root = any (h == 0);
  else
    error ("whitebg: HFIG must be a valid figure handle");
  endif

  if (isnan (color))
    if (do_root)
      ## Set the default axes and figure properties on root
      ## so that subsequent plots have the new color scheme
      fac = get (0, "factory");
      fields = fieldnames (fac);
      idx = ! cellfun ("isempty", regexp (fields,
                                          '^factory(axes|figure).*color$'));

      ## Use default value in place of factory value if specified.
      for field = fields(idx)'
        defaultfield = strrep (field{1}, "factory", "default");
        try
          defaultvalue = 1 - get (0, defaultfield);
        catch
          defaultvalue = 1 - fac.(field{1});
        end_try_catch
        set (0, defaultfield, defaultvalue);
      endfor
    endif

    ## The sort is necessary so that child legend objects are acted on
    ## before the legend axes object.
    hlist = sort (findobj (fig));

    for h = hlist'
      props = get (h);
      fields = fieldnames (props);
      ## Find all fields with the word color in them and invert.
      idx = find (! cellfun ("isempty", regexp (fields, 'color$')));
      for field = fields(idx)';
        c = props.(field{1});
        if (! ischar (c) && columns (c) == 3)
          set (h, field{1}, 1 - c);
        endif
      endfor
    endfor

  else  # 2nd argument such as a color or "none"

    if (! strcmp (color, "none"))
      ## Set the specified color on the figure and all axes objects
      hlist = [fig; findobj(fig, "type", "axes")];
      set (hlist, "color", color);
      if (do_root)
        defs = get (0, "default");
        if (isfield (defs, "defaultaxescolor")
            && strcmp (defs.defaultaxescolor, "none"))
          set (0, "defaultaxescolor", color);
        endif
      endif

      ## Adjust colors of remaining objects to have some contrast
      bg = rgb2hsv (get (fig, "color"));
      ## List of children without the figure and axes objects already changed
      hlist = setdiff (findobj (fig), hlist);
      for h = hlist'
        props = get (h);
        fields = fieldnames (props);
        ## Find all fields with the word color in them and adjust HSV.
        idx = find (! cellfun ("isempty", regexp (fields, 'color$')));
        for field = fields(idx)';
          c = props.(field{1});
          if (! ischar (c) && columns (c) == 3)
            set (h, field{1}, calc_contrast_color (bg, c));
          endif
        endfor
      endfor

    else
      ## Reset colors to factory defaults
      if (do_root)
        fac = get (0, "factory");
        fields = fieldnames (fac);
        idx = ! cellfun ("isempty", regexp (fields,
                                            '^factory(axes|figure).*color$'));
        for field = fields(idx)'
          factoryfield = field{1};
          factoryvalue = fac.(factoryfield);
          if (strncmp (factoryfield, "factoryfigure", 13))
            ## Strip off "factoryfigure" part of fieldname before applying
            set (fig, factoryfield(14:end), factoryvalue);
          endif
          ## Remove applied default from root
          defaultfield = strrep (factoryfield, "factory", "default");
          set (0, defaultfield, "remove");
        endfor 
      endif

      hlist = sort (findobj (fig));
      for h = hlist'
        props = get (h);
        fields = fieldnames (props);
        ## Find all fields with the word color in them and restore to factory.
        idx = find (! cellfun ("isempty", regexp (fields, 'color$')));
        for field = fields(idx)'
          set (h, field{1}, "factory");
        endfor
      endfor
    endif
  endif

endfunction

## Calculate a new color which contrasts with the supplied bg color and is
## perceptually related to the original color.
##
## Input color bg must be in HSV space.  Input color corig is in RGB space.
##
## FIXME: Calculation is segregated into its own function in case anyone wants
## to try and improve the selection of a "contrasting" color.
##
## This algorithm maintains at least 90 degrees separation between corig and bg
## in Hue rotation space.  No modifications are done for saturation or value.
function cnew = calc_contrast_color (bg, corig)
  
  hsv = rgb2hsv (corig);
  contrast_hue = mod (bg(1) + 0.5, 1);  # Generate a contrasting bg color

  ## If close to existing contrast color, leave alone
  delta = abs (hsv(1) - contrast_hue);
  if (delta < 0.25 || delta > 0.75)
    cnew(1) = hsv(1);
  else
    cnew(1) = mod (hsv(1) + 0.5, 1);
  endif

  ## No modifications to saturation or value.
  cnew(2:3) = hsv(2:3);

  cnew = hsv2rgb (cnew);   

endfunction


%!test
%! dac = get (0, "defaultaxescolor");
%! dfc = get (0, "defaultfigurecolor");
%! hf = figure ("visible", "off");
%! unwind_protect
%!   hl = line ("Color", "r");
%!   assert (get (hf, "color"), dfc);
%!   assert (get (gca, "color"), dac);
%!   assert (get (hl, "color"), [1 0 0]);
%!   whitebg (hf);
%!   assert (get (hf, "color"), 1 - dfc);
%!   assert (get (gca, "color"), 1 - dac);
%!   assert (get (hl, "color"), [0 1 1]);
%!   c = [0.2 0.2 0.2];
%!   whitebg (hf, c);
%!   assert (get (hf, "color"), c);
%!   assert (get (gca, "color"), c);
%! unwind_protect_cleanup
%!   close (hf);
%! end_unwind_protect

## Test input validation
%!error whitebg (1,2,3)
%!error whitebg ({1}, 2)