Robochameleon  v1.0
export_fig.m
1 %EXPORT_FIG Exports figures suitable for publication
2 %
3 % Examples:
4 % im = export_fig
5 % [im alpha] = export_fig
6 % export_fig filename
7 % export_fig filename -format1 -format2
8 % export_fig ... -nocrop
9 % export_fig ... -transparent
10 % export_fig ... -native
11 % export_fig ... -m<val>
12 % export_fig ... -r<val>
13 % export_fig ... -a<val>
14 % export_fig ... -q<val>
15 % export_fig ... -<renderer>
16 % export_fig ... -<colorspace>
17 % export_fig ... -append
18 % export_fig ... -bookmark
19 % export_fig(..., handle)
20 %
21 % This function saves a figure or single axes to one or more vector and/or
22 % bitmap file formats, and/or outputs a rasterized version to the
23 % workspace, with the following properties:
24 % - Figure/axes reproduced as it appears on screen
25 % - Cropped borders (optional)
26 % - Embedded fonts (vector formats)
27 % - Improved line and grid line styles
28 % - Anti-aliased graphics (bitmap formats)
29 % - Render images at native resolution (optional for bitmap formats)
30 % - Transparent background supported (pdf, eps, png)
31 % - Semi-transparent patch objects supported (png only)
32 % - RGB, CMYK or grayscale output (CMYK only with pdf, eps, tiff)
33 % - Variable image compression, including lossless (pdf, eps, jpg)
34 % - Optionally append to file (pdf, tiff)
35 % - Vector formats: pdf, eps
36 % - Bitmap formats: png, tiff, jpg, bmp, export to workspace
37 %
38 % This function is especially suited to exporting figures for use in
39 % publications and presentations, because of the high quality and
40 % portability of media produced.
41 %
42 % Note that the background color and figure dimensions are reproduced
43 % (the latter approximately, and ignoring cropping & magnification) in the
44 % output file. For transparent background (and semi-transparent patch
45 % objects), use the -transparent option or set the figure 'Color' property
46 % to 'none'. To make axes transparent set the axes 'Color' property to
47 % 'none'. Pdf, eps and png are the only file formats to support a
48 % transparent background, whilst the png format alone supports transparency
49 % of patch objects.
50 %
51 % The choice of renderer (opengl, zbuffer or painters) has a large impact
52 % on the quality of output. Whilst the default value (opengl for bitmaps,
53 % painters for vector formats) generally gives good results, if you aren't
54 % satisfied then try another renderer. Notes: 1) For vector formats (eps,
55 % pdf), only painters generates vector graphics. 2) For bitmaps, only
56 % opengl can render transparent patch objects correctly. 3) For bitmaps,
57 % only painters will correctly scale line dash and dot lengths when
58 % magnifying or anti-aliasing. 4) Fonts may be substitued with Courier when
59 % using painters.
60 %
61 % When exporting to vector format (pdf & eps) and bitmap format using the
62 % painters renderer, this function requires that ghostscript is installed
63 % on your system. You can download this from:
64 % http://www.ghostscript.com
65 % When exporting to eps it additionally requires pdftops, from the Xpdf
66 % suite of functions. You can download this from:
67 % http://www.foolabs.com/xpdf
68 %
69 %IN:
70 % filename - string containing the name (optionally including full or
71 % relative path) of the file the figure is to be saved as. If
72 % a path is not specified, the figure is saved in the current
73 % directory. If no name and no output arguments are specified,
74 % the default name, 'export_fig_out', is used. If neither a
75 % file extension nor a format are specified, a ".png" is added
76 % and the figure saved in that format.
77 % -format1, -format2, etc. - strings containing the extensions of the
78 % file formats the figure is to be saved as.
79 % Valid options are: '-pdf', '-eps', '-png',
80 % '-tif', '-jpg' and '-bmp'. All combinations
81 % of formats are valid.
82 % -nocrop - option indicating that the borders of the output are not to
83 % be cropped.
84 % -transparent - option indicating that the figure background is to be
85 % made transparent (png, pdf and eps output only).
86 % -m<val> - option where val indicates the factor to magnify the
87 % on-screen figure pixel dimensions by when generating bitmap
88 % outputs. Default: '-m1'.
89 % -r<val> - option val indicates the resolution (in pixels per inch) to
90 % export bitmap and vector outputs at, keeping the dimensions
91 % of the on-screen figure. Default: '-r864' (for vector output
92 % only). Note that the -m option overides the -r option for
93 % bitmap outputs only.
94 % -native - option indicating that the output resolution (when outputting
95 % a bitmap format) should be such that the vertical resolution
96 % of the first suitable image found in the figure is at the
97 % native resolution of that image. To specify a particular
98 % image to use, give it the tag 'export_fig_native'. Notes:
99 % This overrides any value set with the -m and -r options. It
100 % also assumes that the image is displayed front-to-parallel
101 % with the screen. The output resolution is approximate and
102 % should not be relied upon. Anti-aliasing can have adverse
103 % effects on image quality (disable with the -a1 option).
104 % -a1, -a2, -a3, -a4 - option indicating the amount of anti-aliasing to
105 % use for bitmap outputs. '-a1' means no anti-
106 % aliasing; '-a4' is the maximum amount (default).
107 % -<renderer> - option to force a particular renderer (painters, opengl
108 % or zbuffer) to be used over the default: opengl for
109 % bitmaps; painters for vector formats.
110 % -<colorspace> - option indicating which colorspace color figures should
111 % be saved in: RGB (default), CMYK or gray. CMYK is only
112 % supported in pdf, eps and tiff output.
113 % -q<val> - option to vary bitmap image quality (in pdf, eps and jpg
114 % files only). Larger val, in the range 0-100, gives higher
115 % quality/lower compression. val > 100 gives lossless
116 % compression. Default: '-q95' for jpg, ghostscript prepress
117 % default for pdf & eps. Note: lossless compression can
118 % sometimes give a smaller file size than the default lossy
119 % compression, depending on the type of images.
120 % -append - option indicating that if the file (pdfs only) already
121 % exists, the figure is to be appended as a new page, instead
122 % of being overwritten (default).
123 % -bookmark - option to indicate that a bookmark with the name of the
124 % figure is to be created in the output file (pdf only).
125 % handle - The handle of the figure, axes or uipanels (can be an array of
126 % handles, but the objects must be in the same figure) to be
127 % saved. Default: gcf.
128 %
129 %OUT:
130 % im - MxNxC uint8 image array of the figure.
131 % alpha - MxN single array of alphamatte values in range [0,1], for the
132 % case when the background is transparent.
133 %
134 % Some helpful examples and tips can be found at:
135 % http://sites.google.com/site/oliverwoodford/software/export_fig
136 %
137 % See also PRINT, SAVEAS.
138 
139 % Copyright (C) Oliver Woodford 2008-2014
140 
141 % The idea of using ghostscript is inspired by Peder Axensten's SAVEFIG
142 % (fex id: 10889) which is itself inspired by EPS2PDF (fex id: 5782).
143 % The idea for using pdftops came from the MATLAB newsgroup (id: 168171).
144 % The idea of editing the EPS file to change line styles comes from Jiro
145 % Doke's FIXPSLINESTYLE (fex id: 17928).
146 % The idea of changing dash length with line width came from comments on
147 % fex id: 5743, but the implementation is mine :)
148 % The idea of anti-aliasing bitmaps came from Anders Brun's MYAA (fex id:
149 % 20979).
150 % The idea of appending figures in pdfs came from Matt C in comments on the
151 % FEX (id: 23629)
152 
153 % Thanks to Roland Martin for pointing out the colour MATLAB
154 % bug/feature with colorbar axes and transparent backgrounds.
155 % Thanks also to Andrew Matthews for describing a bug to do with the figure
156 % size changing in -nodisplay mode. I couldn't reproduce it, but included a
157 % fix anyway.
158 % Thanks to Tammy Threadgill for reporting a bug where an axes is not
159 % isolated from gui objects.
160 
161 % 23/02/12: Ensure that axes limits don't change during printing
162 % 14/03/12: Fix bug in fixing the axes limits (thanks to Tobias Lamour for
163 % reporting it).
164 % 02/05/12: Incorporate patch of Petr Nechaev (many thanks), enabling
165 % bookmarking of figures in pdf files.
166 % 09/05/12: Incorporate patch of Arcelia Arrieta (many thanks), to keep
167 % tick marks fixed.
168 % 12/12/12: Add support for isolating uipanels. Thanks to michael for
169 % suggesting it.
170 % 25/09/13: Add support for changing resolution in vector formats. Thanks
171 % to Jan Jaap Meijer for suggesting it.
172 % 07/05/14: Add support for '~' at start of path. Thanks to Sally Warner
173 % for suggesting it.
174 
175 function [im, alpha] = export_fig(varargin)
176 % Make sure the figure is rendered correctly _now_ so that properties like
177 % axes limits are up-to-date.
178 drawnow;
179 % Parse the input arguments
180 [fig, options] = parse_args(nargout, varargin{:});
181 % Isolate the subplot, if it is one
182 cls = all(ismember(get(fig, 'Type'), {'axes', 'uipanel'}));
183 if cls
184  % Given handles of one or more axes, so isolate them from the rest
185  fig = isolate_axes(fig);
186 else
187  % Check we have a figure
188  if ~isequal(get(fig, 'Type'), 'figure');
189  error('Handle must be that of a figure, axes or uipanel');
190  end
191  % Get the old InvertHardcopy mode
192  old_mode = get(fig, 'InvertHardcopy');
193 end
194 % Hack the font units where necessary (due to a font rendering bug in
195 % print?). This may not work perfectly in all cases. Also it can change the
196 % figure layout if reverted, so use a copy.
197 magnify = options.magnify * options.aa_factor;
198 if isbitmap(options) && magnify ~= 1
199  fontu = findobj(fig, 'FontUnits', 'normalized');
200  if ~isempty(fontu)
201  % Some normalized font units found
202  if ~cls
203  fig = copyfig(fig);
204  set(fig, 'Visible', 'off');
205  fontu = findobj(fig, 'FontUnits', 'normalized');
206  cls = true;
207  end
208  set(fontu, 'FontUnits', 'points');
209  end
210 end
211 % MATLAB "feature": axes limits and tick marks can change when printing
212 Hlims = findall(fig, 'Type', 'axes');
213 if ~cls
214  % Record the old axes limit and tick modes
215  Xlims = make_cell(get(Hlims, 'XLimMode'));
216  Ylims = make_cell(get(Hlims, 'YLimMode'));
217  Zlims = make_cell(get(Hlims, 'ZLimMode'));
218  Xtick = make_cell(get(Hlims, 'XTickMode'));
219  Ytick = make_cell(get(Hlims, 'YTickMode'));
220  Ztick = make_cell(get(Hlims, 'ZTickMode'));
221 end
222 % Set all axes limit and tick modes to manual, so the limits and ticks can't change
223 set(Hlims, 'XLimMode', 'manual', 'YLimMode', 'manual', 'ZLimMode', 'manual', 'XTickMode', 'manual', 'YTickMode', 'manual', 'ZTickMode', 'manual');
224 % Set to print exactly what is there
225 set(fig, 'InvertHardcopy', 'off');
226 % Set the renderer
227 switch options.renderer
228  case 1
229  renderer = '-opengl';
230  case 2
231  renderer = '-zbuffer';
232  case 3
233  renderer = '-painters';
234  otherwise
235  renderer = '-opengl'; % Default for bitmaps
236 end
237 % Do the bitmap formats first
238 if isbitmap(options)
239  % Get the background colour
240  if options.transparent && (options.png || options.alpha)
241  % Get out an alpha channel
242  % MATLAB "feature": black colorbar axes can change to white and vice versa!
243  hCB = findobj(fig, 'Type', 'axes', 'Tag', 'Colorbar');
244  if isempty(hCB)
245  yCol = [];
246  xCol = [];
247  else
248  yCol = get(hCB, 'YColor');
249  xCol = get(hCB, 'XColor');
250  if iscell(yCol)
251  yCol = cell2mat(yCol);
252  xCol = cell2mat(xCol);
253  end
254  yCol = sum(yCol, 2);
255  xCol = sum(xCol, 2);
256  end
257  % MATLAB "feature": apparently figure size can change when changing
258  % colour in -nodisplay mode
259  pos = get(fig, 'Position');
260  % Set the background colour to black, and set size in case it was
261  % changed internally
262  tcol = get(fig, 'Color');
263  set(fig, 'Color', 'k', 'Position', pos);
264  % Correct the colorbar axes colours
265  set(hCB(yCol==0), 'YColor', [0 0 0]);
266  set(hCB(xCol==0), 'XColor', [0 0 0]);
267  % Print large version to array
268  B = print2array(fig, magnify, renderer);
269  % Downscale the image
270  B = downsize(single(B), options.aa_factor);
271  % Set background to white (and set size)
272  set(fig, 'Color', 'w', 'Position', pos);
273  % Correct the colorbar axes colours
274  set(hCB(yCol==3), 'YColor', [1 1 1]);
275  set(hCB(xCol==3), 'XColor', [1 1 1]);
276  % Print large version to array
277  A = print2array(fig, magnify, renderer);
278  % Downscale the image
279  A = downsize(single(A), options.aa_factor);
280  % Set the background colour (and size) back to normal
281  set(fig, 'Color', tcol, 'Position', pos);
282  % Compute the alpha map
283  alpha = round(sum(B - A, 3)) / (255 * 3) + 1;
284  A = alpha;
285  A(A==0) = 1;
286  A = B ./ A(:,:,[1 1 1]);
287  clear B
288  % Convert to greyscale
289  if options.colourspace == 2
290  A = rgb2grey(A);
291  end
292  A = uint8(A);
293  % Crop the background
294  if options.crop
295  [alpha, v] = crop_background(alpha, 0);
296  A = A(v(1):v(2),v(3):v(4),:);
297  end
298  if options.png
299  % Compute the resolution
300  res = options.magnify * get(0, 'ScreenPixelsPerInch') / 25.4e-3;
301  % Save the png
302  imwrite(A, [options.name '.png'], 'Alpha', double(alpha), 'ResolutionUnit', 'meter', 'XResolution', res, 'YResolution', res);
303  % Clear the png bit
304  options.png = false;
305  end
306  % Return only one channel for greyscale
307  if isbitmap(options)
308  A = check_greyscale(A);
309  end
310  if options.alpha
311  % Store the image
312  im = A;
313  % Clear the alpha bit
314  options.alpha = false;
315  end
316  % Get the non-alpha image
317  if isbitmap(options)
318  alph = alpha(:,:,ones(1, size(A, 3)));
319  A = uint8(single(A) .* alph + 255 * (1 - alph));
320  clear alph
321  end
322  if options.im
323  % Store the new image
324  im = A;
325  end
326  else
327  % Print large version to array
328  if options.transparent
329  % MATLAB "feature": apparently figure size can change when changing
330  % colour in -nodisplay mode
331  pos = get(fig, 'Position');
332  tcol = get(fig, 'Color');
333  set(fig, 'Color', 'w', 'Position', pos);
334  A = print2array(fig, magnify, renderer);
335  set(fig, 'Color', tcol, 'Position', pos);
336  tcol = 255;
337  else
338  [A, tcol] = print2array(fig, magnify, renderer);
339  end
340  % Crop the background
341  if options.crop
342  A = crop_background(A, tcol);
343  end
344  % Downscale the image
345  A = downsize(A, options.aa_factor);
346  if options.colourspace == 2
347  % Convert to greyscale
348  A = rgb2grey(A);
349  else
350  % Return only one channel for greyscale
351  A = check_greyscale(A);
352  end
353  % Outputs
354  if options.im
355  im = A;
356  end
357  if options.alpha
358  im = A;
359  alpha = zeros(size(A, 1), size(A, 2), 'single');
360  end
361  end
362  % Save the images
363  if options.png
364  res = options.magnify * get(0, 'ScreenPixelsPerInch') / 25.4e-3;
365  imwrite(A, [options.name '.png'], 'ResolutionUnit', 'meter', 'XResolution', res, 'YResolution', res);
366  end
367  if options.bmp
368  imwrite(A, [options.name '.bmp']);
369  end
370  % Save jpeg with given quality
371  if options.jpg
372  quality = options.quality;
373  if isempty(quality)
374  quality = 95;
375  end
376  if quality > 100
377  imwrite(A, [options.name '.jpg'], 'Mode', 'lossless');
378  else
379  imwrite(A, [options.name '.jpg'], 'Quality', quality);
380  end
381  end
382  % Save tif images in cmyk if wanted (and possible)
383  if options.tif
384  if options.colourspace == 1 && size(A, 3) == 3
385  A = double(255 - A);
386  K = min(A, [], 3);
387  K_ = 255 ./ max(255 - K, 1);
388  C = (A(:,:,1) - K) .* K_;
389  M = (A(:,:,2) - K) .* K_;
390  Y = (A(:,:,3) - K) .* K_;
391  A = uint8(cat(3, C, M, Y, K));
392  clear C M Y K K_
393  end
394  append_mode = {'overwrite', 'append'};
395  imwrite(A, [options.name '.tif'], 'Resolution', options.magnify*get(0, 'ScreenPixelsPerInch'), 'WriteMode', append_mode{options.append+1});
396  end
397 end
398 % Now do the vector formats
399 if isvector(options)
400  % Set the default renderer to painters
401  if ~options.renderer
402  renderer = '-painters';
403  end
404  % Generate some filenames
405  tmp_nam = [tempname '.eps'];
406  if options.pdf
407  pdf_nam = [options.name '.pdf'];
408  else
409  pdf_nam = [tempname '.pdf'];
410  end
411  % Generate the options for print
412  p2eArgs = {renderer, sprintf('-r%d', options.resolution)};
413  if options.colourspace == 1
414  p2eArgs = [p2eArgs {'-cmyk'}];
415  end
416  if ~options.crop
417  p2eArgs = [p2eArgs {'-loose'}];
418  end
419  try
420  % Generate an eps
421  print2eps(tmp_nam, fig, p2eArgs{:});
422  % Remove the background, if desired
423  if options.transparent && ~isequal(get(fig, 'Color'), 'none')
424  eps_remove_background(tmp_nam, 1 + using_hg2(fig));
425  end
426  % Add a bookmark to the PDF if desired
427  if options.bookmark
428  fig_nam = get(fig, 'Name');
429  if isempty(fig_nam)
430  warning('export_fig:EmptyBookmark', 'Bookmark requested for figure with no name. Bookmark will be empty.');
431  end
432  add_bookmark(tmp_nam, fig_nam);
433  end
434  % Generate a pdf
435  eps2pdf(tmp_nam, pdf_nam, 1, options.append, options.colourspace==2, options.quality);
436  catch ex
437  % Delete the eps
438  delete(tmp_nam);
439  rethrow(ex);
440  end
441  % Delete the eps
442  delete(tmp_nam);
443  if options.eps
444  try
445  % Generate an eps from the pdf
446  pdf2eps(pdf_nam, [options.name '.eps']);
447  catch ex
448  if ~options.pdf
449  % Delete the pdf
450  delete(pdf_nam);
451  end
452  rethrow(ex);
453  end
454  if ~options.pdf
455  % Delete the pdf
456  delete(pdf_nam);
457  end
458  end
459 end
460 if cls
461  % Close the created figure
462  close(fig);
463 else
464  % Reset the hardcopy mode
465  set(fig, 'InvertHardcopy', old_mode);
466  % Reset the axes limit and tick modes
467  for a = 1:numel(Hlims)
468  set(Hlims(a), 'XLimMode', Xlims{a}, 'YLimMode', Ylims{a}, 'ZLimMode', Zlims{a}, 'XTickMode', Xtick{a}, 'YTickMode', Ytick{a}, 'ZTickMode', Ztick{a});
469  end
470 end
471 return
472 
473 function [fig, options] = parse_args(nout, varargin)
474 % Parse the input arguments
475 % Set the defaults
476 fig = get(0, 'CurrentFigure');
477 options = struct('name', 'export_fig_out', ...
478  'crop', true, ...
479  'transparent', false, ...
480  'renderer', 0, ... % 0: default, 1: OpenGL, 2: ZBuffer, 3: Painters
481  'pdf', false, ...
482  'eps', false, ...
483  'png', false, ...
484  'tif', false, ...
485  'jpg', false, ...
486  'bmp', false, ...
487  'colourspace', 0, ... % 0: RGB/gray, 1: CMYK, 2: gray
488  'append', false, ...
489  'im', nout == 1, ...
490  'alpha', nout == 2, ...
491  'aa_factor', 0, ...
492  'magnify', [], ...
493  'resolution', [], ...
494  'bookmark', false, ...
495  'quality', []);
496 native = false; % Set resolution to native of an image
497 
498 % Go through the other arguments
499 for a = 1:nargin-1
500  if all(ishandle(varargin{a}))
501  fig = varargin{a};
502  elseif ischar(varargin{a}) && ~isempty(varargin{a})
503  if varargin{a}(1) == '-'
504  switch lower(varargin{a}(2:end))
505  case 'nocrop'
506  options.crop = false;
507  case {'trans', 'transparent'}
508  options.transparent = true;
509  case 'opengl'
510  options.renderer = 1;
511  case 'zbuffer'
512  options.renderer = 2;
513  case 'painters'
514  options.renderer = 3;
515  case 'pdf'
516  options.pdf = true;
517  case 'eps'
518  options.eps = true;
519  case 'png'
520  options.png = true;
521  case {'tif', 'tiff'}
522  options.tif = true;
523  case {'jpg', 'jpeg'}
524  options.jpg = true;
525  case 'bmp'
526  options.bmp = true;
527  case 'rgb'
528  options.colourspace = 0;
529  case 'cmyk'
530  options.colourspace = 1;
531  case {'gray', 'grey'}
532  options.colourspace = 2;
533  case {'a1', 'a2', 'a3', 'a4'}
534  options.aa_factor = str2double(varargin{a}(3));
535  case 'append'
536  options.append = true;
537  case 'bookmark'
538  options.bookmark = true;
539  case 'native'
540  native = true;
541  otherwise
542  val = str2double(regexp(varargin{a}, '(?<=-(m|M|r|R|q|Q))(\d*\.)?\d+(e-?\d+)?', 'match'));
543  if ~isscalar(val)
544  error('option %s not recognised', varargin{a});
545  end
546  switch lower(varargin{a}(2))
547  case 'm'
548  options.magnify = val;
549  case 'r'
550  options.resolution = val;
551  case 'q'
552  options.quality = max(val, 0);
553  end
554  end
555  else
556  [p, options.name, ext] = fileparts(varargin{a});
557  if ~isempty(p)
558  options.name = [p filesep options.name];
559  end
560  switch lower(ext)
561  case {'.tif', '.tiff'}
562  options.tif = true;
563  case {'.jpg', '.jpeg'}
564  options.jpg = true;
565  case '.png'
566  options.png = true;
567  case '.bmp'
568  options.bmp = true;
569  case '.eps'
570  options.eps = true;
571  case '.pdf'
572  options.pdf = true;
573  otherwise
574  options.name = varargin{a};
575  end
576  end
577  end
578 end
579 
580 % Set default anti-aliasing now we know the renderer
581 if options.aa_factor == 0
582  options.aa_factor = 1 + 2 * (~using_hg2(fig) | (options.renderer == 3));
583 end
584 
585 % Convert user dir '~' to full path
586 if numel(options.name) > 2 && options.name(1) == '~' && (options.name(2) == '/' || options.name(2) == '\')
587  options.name = fullfile(char(java.lang.System.getProperty('user.home')), options.name(2:end));
588 end
589 
590 % Compute the magnification and resolution
591 if isempty(options.magnify)
592  if isempty(options.resolution)
593  options.magnify = 1;
594  options.resolution = 864;
595  else
596  options.magnify = options.resolution ./ get(0, 'ScreenPixelsPerInch');
597  end
598 elseif isempty(options.resolution)
599  options.resolution = 864;
600 end
601 
602 % Check we have a figure handle
603 if isempty(fig)
604  error('No figure found');
605 end
606 
607 % Set the default format
608 if ~isvector(options) && ~isbitmap(options)
609  options.png = true;
610 end
611 
612 % Check whether transparent background is wanted (old way)
613 if isequal(get(ancestor(fig(1), 'figure'), 'Color'), 'none')
614  options.transparent = true;
615 end
616 
617 % If requested, set the resolution to the native vertical resolution of the
618 % first suitable image found
619 if native && isbitmap(options)
620  % Find a suitable image
621  list = findobj(fig, 'Type', 'image', 'Tag', 'export_fig_native');
622  if isempty(list)
623  list = findobj(fig, 'Type', 'image', 'Visible', 'on');
624  end
625  for hIm = list(:)'
626  % Check height is >= 2
627  height = size(get(hIm, 'CData'), 1);
628  if height < 2
629  continue
630  end
631  % Account for the image filling only part of the axes, or vice
632  % versa
633  yl = get(hIm, 'YData');
634  if isscalar(yl)
635  yl = [yl(1)-0.5 yl(1)+height+0.5];
636  else
637  if ~diff(yl)
638  continue
639  end
640  yl = yl + [-0.5 0.5] * (diff(yl) / (height - 1));
641  end
642  hAx = get(hIm, 'Parent');
643  yl2 = get(hAx, 'YLim');
644  % Find the pixel height of the axes
645  oldUnits = get(hAx, 'Units');
646  set(hAx, 'Units', 'pixels');
647  pos = get(hAx, 'Position');
648  set(hAx, 'Units', oldUnits);
649  if ~pos(4)
650  continue
651  end
652  % Found a suitable image
653  % Account for stretch-to-fill being disabled
654  pbar = get(hAx, 'PlotBoxAspectRatio');
655  pos = min(pos(4), pbar(2)*pos(3)/pbar(1));
656  % Set the magnification to give native resolution
657  options.magnify = (height * diff(yl2)) / (pos * diff(yl));
658  break
659  end
660 end
661 return
662 
663 function A = downsize(A, factor)
664 % Downsample an image
665 if factor == 1
666  % Nothing to do
667  return
668 end
669 try
670  % Faster, but requires image processing toolbox
671  A = imresize(A, 1/factor, 'bilinear');
672 catch
673  % No image processing toolbox - resize manually
674  % Lowpass filter - use Gaussian as is separable, so faster
675  % Compute the 1d Gaussian filter
676  filt = (-factor-1:factor+1) / (factor * 0.6);
677  filt = exp(-filt .* filt);
678  % Normalize the filter
679  filt = single(filt / sum(filt));
680  % Filter the image
681  padding = floor(numel(filt) / 2);
682  for a = 1:size(A, 3)
683  A(:,:,a) = conv2(filt, filt', single(A([ones(1, padding) 1:end repmat(end, 1, padding)],[ones(1, padding) 1:end repmat(end, 1, padding)],a)), 'valid');
684  end
685  % Subsample
686  A = A(1+floor(mod(end-1, factor)/2):factor:end,1+floor(mod(end-1, factor)/2):factor:end,:);
687 end
688 return
689 
690 function A = rgb2grey(A)
691 A = cast(reshape(reshape(single(A), [], 3) * single([0.299; 0.587; 0.114]), size(A, 1), size(A, 2)), class(A));
692 return
693 
694 function A = check_greyscale(A)
695 % Check if the image is greyscale
696 if size(A, 3) == 3 && ...
697  all(reshape(A(:,:,1) == A(:,:,2), [], 1)) && ...
698  all(reshape(A(:,:,2) == A(:,:,3), [], 1))
699  A = A(:,:,1); % Save only one channel for 8-bit output
700 end
701 return
702 
703 function [A, v] = crop_background(A, bcol)
704 % Map the foreground pixels
705 [h, w, c] = size(A);
706 if isscalar(bcol) && c > 1
707  bcol = bcol(ones(1, c));
708 end
709 bail = false;
710 for l = 1:w
711  for a = 1:c
712  if ~all(A(:,l,a) == bcol(a))
713  bail = true;
714  break;
715  end
716  end
717  if bail
718  break;
719  end
720 end
721 bail = false;
722 for r = w:-1:l
723  for a = 1:c
724  if ~all(A(:,r,a) == bcol(a))
725  bail = true;
726  break;
727  end
728  end
729  if bail
730  break;
731  end
732 end
733 bail = false;
734 for t = 1:h
735  for a = 1:c
736  if ~all(A(t,:,a) == bcol(a))
737  bail = true;
738  break;
739  end
740  end
741  if bail
742  break;
743  end
744 end
745 bail = false;
746 for b = h:-1:t
747  for a = 1:c
748  if ~all(A(b,:,a) == bcol(a))
749  bail = true;
750  break;
751  end
752  end
753  if bail
754  break;
755  end
756 end
757 % Crop the background, leaving one boundary pixel to avoid bleeding on
758 % resize
759 v = [max(t-1, 1) min(b+1, h) max(l-1, 1) min(r+1, w)];
760 A = A(v(1):v(2),v(3):v(4),:);
761 return
762 
763 function eps_remove_background(fname, count)
764 % Remove the background of an eps file
765 % Open the file
766 fh = fopen(fname, 'r+');
767 if fh == -1
768  error('Not able to open file %s.', fname);
769 end
770 % Read the file line by line
771 while count
772  % Get the next line
773  l = fgets(fh);
774  if isequal(l, -1)
775  break; % Quit, no rectangle found
776  end
777  % Check if the line contains the background rectangle
778  if isequal(regexp(l, ' *0 +0 +\d+ +\d+ +r[fe] *[\n\r]+', 'start'), 1)
779  % Set the line to whitespace and quit
780  l(1:regexp(l, '[\n\r]', 'start', 'once')-1) = ' ';
781  fseek(fh, -numel(l), 0);
782  fprintf(fh, l);
783  % Reduce the count
784  count = count - 1;
785  end
786 end
787 % Close the file
788 fclose(fh);
789 return
790 
791 function b = isvector(options)
792 b = options.pdf || options.eps;
793 return
794 
795 function b = isbitmap(options)
796 b = options.png || options.tif || options.jpg || options.bmp || options.im || options.alpha;
797 return
798 
799 % Helper function
800 function A = make_cell(A)
801 if ~iscell(A)
802  A = {A};
803 end
804 return
805 
806 function add_bookmark(fname, bookmark_text)
807 % Adds a bookmark to the temporary EPS file after %%EndPageSetup
808 % Read in the file
809 fh = fopen(fname, 'r');
810 if fh == -1
811  error('File %s not found.', fname);
812 end
813 try
814  fstrm = fread(fh, '*char')';
815 catch ex
816  fclose(fh);
817  rethrow(ex);
818 end
819 fclose(fh);
820 
821 % Include standard pdfmark prolog to maximize compatibility
822 fstrm = strrep(fstrm, '%%BeginProlog', sprintf('%%%%BeginProlog\n/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse'));
823 % Add page bookmark
824 fstrm = strrep(fstrm, '%%EndPageSetup', sprintf('%%%%EndPageSetup\n[ /Title (%s) /OUT pdfmark',bookmark_text));
825 
826 % Write out the updated file
827 fh = fopen(fname, 'w');
828 if fh == -1
829  error('Unable to open %s for writing.', fname);
830 end
831 try
832  fwrite(fh, fstrm, 'char*1');
833 catch ex
834  fclose(fh);
835  rethrow(ex);
836 end
837 fclose(fh);
838 return