File: whitebg.m

package info (click to toggle)
octave 9.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 144,300 kB
  • sloc: cpp: 332,784; ansic: 77,239; fortran: 20,963; objc: 9,396; sh: 8,213; yacc: 4,925; lex: 4,389; perl: 1,544; java: 1,366; awk: 1,259; makefile: 648; xml: 189
file content (257 lines) | stat: -rw-r--r-- 8,800 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
########################################################################
##
## Copyright (C) 2010-2024 The Octave Project Developers
##
## See the file COPYRIGHT.md in the top-level directory of this
## distribution or <https://octave.org/copyright/>.
##
## 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
## <https://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 (ishghandle (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)