ParaMonte MATLAB 3.0.0
Parallel Monte Carlo and Machine Learning Library
See the latest version documentation.
savefig.m
Go to the documentation of this file.
1%> \brief
2%> Export figures in a publication-quality format.<br>
3%>
4%> \details
5%> This function saves a figure or single axes to one or more vector and/or
6%> bitmap file formats, and/or outputs a rasterized version to the workspace,
7%> with the following properties:<br>
8%> <ol>
9%> <li> Figure/axes reproduced as it appears on screen.
10%> <li> Cropped/padded borders (optional).
11%> <li> Embedded fonts (vector formats).
12%> <li> Improved line and grid line styles.
13%> <li> Anti-aliased graphics (bitmap formats).
14%> <li> Render images at native resolution (optional for bitmap formats).
15%> <li> Transparent background supported (pdf, eps, png, tif, gif).
16%> <li> Semi-transparent patch objects supported (png, tif).
17%> <li> RGB, CMYK or grayscale output (CMYK only with pdf, eps, tif).
18%> <li> Variable image compression, including lossless (pdf, eps, jpg).
19%> <li> Optional rounded line-caps (pdf, eps).
20%> <li> Optionally append to file (pdf, tif, gif).
21%> <li> Vector formats: pdf, eps, emf, svg.
22%> <li> Bitmap formats: png, tif, jpg, bmp, gif, clipboard, export to workspace.
23%> </ol>
24%>
25%> This function is especially suited to exporting figures for use
26%> in publications and presentations, because of the high quality
27%> and portability of media produced.<br>
28%>
29%> Note that the background color and figure dimensions are reproduced
30%> (the latter approximately, and ignoring cropping & magnification) in the output file.<br>
31%> For transparent background (and semi-transparent patch objects), use the ``-transparent``
32%> option or set the figure ``'Color'`` property to ``'none'``.<br>
33%> To make axes transparent set the axes ``'Color'`` property to ``'none'``.<br>
34%> PDF, EPS, TIF, and PNG are the only formats that support a transparent background.<br>
35%> Only TIF and PNG formats support transparency of patch objects.<br>
36%>
37%> The choice of renderer (opengl/zbuffer/painters) has a large impact on the output quality.<br>
38%> The default value (opengl for bitmaps, painters for vector formats) generally gives good results,
39%> but if you are not satisfied then try another renderer.<br>
40%>
41%> \note
42%> <ol>
43%> <li> For vector formats (EPS,PDF), only painters generates vector graphics.<br>
44%> <li> For bitmap formats, only opengl correctly renders transparent patches.<br>
45%> <li> For bitmap formats, only painters correctly scales line dash and dot
46%> lengths when magnifying or anti-aliasing.<br>
47%> <li> Fonts may be substitued with Courier when using painters.<br>
48%> </ol>
49%>
50%> When exporting to vector format (PDF & EPS) and bitmap format using the painters
51%> renderer, this function requires that ghostscript is installed on your system.<br>
52%> You can download this from: http://www.ghostscript.com<br>
53%> When exporting to EPS it additionally requires pdftops, from the Xpdf suite of functions.<br>
54%> You can download this from: http://xpdfreader.com<br>
55%>
56%> SVG output uses MATLAB's built-in SVG export if available, or otherwise the
57%> fig2svg (https://github.com/kupiqu/fig2svg) or plot2svg
58%> (https://github.com/jschwizer99/plot2svg) utilities, if available.<br>
59%>
60%> \note
61%> Cropping and padding are not supported in SVG and EMF output.<br>
62%>
63%> \param[in] varargin : Any ``property, value`` pair of the following set:<br>
64%> <ol>
65%> <li> ``filename`` - string containing the name (optionally including full or
66%> relative path) of the file the figure is to be saved as.<br>
67%> If no path is specified, the figure is saved in the current folder.<br>
68%> If no name and no output arguments are specified, the figure ``FileName`` property is used.<br>
69%> If this property is empty, then the default name ``'export_fig_out'`` is used.<br>
70%> If neither file extension nor a format parameter are specified, a ``".png"``
71%> is added to the filename and the figure saved in PNG format.<br>
72%>
73%> <li> ``-<format>`` - string(s) containing the output file extension(s).<br>
74%> Options are ``'-pdf'``, ``'-eps'``, ``'emf'``, ``'-svg'``, ``'-png'``, ``'-tif'``, ``'-jpg'``, ``'-gif'``, ``'-bmp'``.<br>
75%> Multiple formats can be specified, without restriction.<br>
76%> For example: ``savefig('-jpg', '-pdf', '-png', ...)``.<br>
77%> Note that ``'-tif'``, ``'-tiff'`` are equivalent, and so are '-jpg'``, ``'-jpeg'.<br>
78%>
79%> <li> ``-transparent`` - option indicating that the figure background is to be made
80%> transparent (PNG, PDF, TIF, EPS, EMF formats only). Implies ``-noinvert``.<br>
81%>
82%> <li> ``-nocrop`` - option indicating that empty margins should not be cropped.<br>
83%>
84%> <li> ``-c[<val>,<val>,<val>,<val>]`` - option indicating crop amounts.<br>
85%> It must be a 4-element vector of numeric values: ``[top, right, bottom, left]``.<br>
86%> where NaN/Inf indicates auto-cropping, 0 means no cropping, any
87%> other value means cropping in pixel amounts. e.g. ``'-c7,15,0,NaN'``
88%> Note: this option is not supported by SVG and EMF formats.<br>
89%>
90%> <li> ``-p<val>`` - option to pad a border of width val to exported files,
91%> where ``val`` is either a relative size with respect to cropped image
92%> size (i.e. ``p=0.01`` adds a ``1%`` border).<br>
93%> For EPS and PDF formats, ``val`` can also be integer in units of ``1/72`` points (``abs(val)>1``).<br>
94%> The input ``val`` can be positive (padding) or negative (extra cropping).<br>
95%> If used, the ``-nocrop`` flag will be ignored, i.e. the image will
96%> always be cropped and then padded. Default: 0 (i.e. no padding).<br>
97%> Note: this option is not supported by SVG and EMF formats.<br>
98%>
99%> <li> ``-m<val>`` - option val indicates the factor to magnify the figure dimensions
100%> when generating bitmap outputs (does not affect vector formats).<br>
101%> Default: ``'-m1'`` (i.e. ``val=1``). Note: ``val~=1`` slows down savefig.<br>
102%>
103%> <li> ``-r<val>`` - option val indicates the resolution (in pixels per inch) to
104%> export bitmap and vector outputs, without changing dimensions of
105%> the on-screen figure. Default: ``'-r864'`` (for vector output only).<br>
106%> Note: ``-m`` option overrides ``-r`` option for bitmap exports only.<br>
107%>
108%> <li> ``-native`` - option indicating that the output resolution
109%> (when outputting a bitmap format) should be such that the vertical resolution
110%> of the first suitable image found in the figure is at the native resolution of that image.<br>
111%> To specify a particular image to use, give it the tag ``'export_fig_native'``.<br>
112%> Notes: This overrides any value set with the -m and -r options.<br>
113%> It also assumes that the image is displayed front-to-parallel with the screen.<br>
114%> The output resolution is approximate and should not be relied upon.<br>
115%> Anti-aliasing can have adverse effects on image quality (disable with the ``-a1`` option).<br>
116%>
117%> <li> ``-a1, -a2, -a3, -a4`` - option indicating the amount of anti-aliasing (AA) to
118%> use for bitmap outputs, when GraphicsSmoothing is not available.<br>
119%> ``'-a1'=no AA; '-a4'=max``.<br>
120%> Default: 3 for HG1, 1 for HG2.<br>
121%>
122%> <li> ``-<renderer>`` - option to force a particular renderer
123%> (painters, opengl or [in R2014a or older] zbuffer).<br>
124%> Default value: opengl for bitmap formats or figures with patches and/or
125%> transparent annotations and painters for vector formats without patches/transparencies.<br>
126%>
127%> <li> ``-<colorspace>`` - option indicating which colorspace color figures should
128%> be saved in: RGB (default), CMYK or gray. Usage example: ``'-gray'``.<br>
129%> Note: CMYK is only supported in PDF, EPS and TIF formats.<br>
130%>
131%> <li> ``-q<val>`` - option to vary bitmap image quality (PDF, EPS, JPG formats only).<br>
132%> A larger val, in the range ``0-100``, produces higher quality and
133%> lower compression. ``val > 100`` results in lossless compression.<br>
134%> Default: '-q95' for JPG, ghostscript prepress default for PDF,EPS.<br>
135%> Note: lossless compression can sometimes give a smaller file size
136%> than the default lossy compression, depending on the image type.<br>
137%>
138%> <li> ``-n<val>`` - option to set minimum output image size (bitmap formats only).<br>
139%> The output size can be specified as a single value (for both rows
140%> and cols, e.g. ``-n200``) or comma-separated values (e.g. ``-n300,400``).<br>
141%> Use an Inf value to keep a dimension unchanged (e.g. ``-n50,inf``).<br>
142%> Use a NaN value to keep aspect ratio unchanged (e.g. ``-n50,nan``).<br>
143%>
144%> <li> ``-x<val>`` - option to set maximum output image size (bitmap formats only).<br>
145%> The output size can be specified as a single value (for both rows
146%> and cols, e.g. ``-x200``) or comma-separated values (e.g. ``-x300,400``).<br>
147%> Use an ``Inf`` value to keep a dimension unchanged (e.g. ``-x50,inf``).<br>
148%> Use a ``NaN`` value to keep aspect ratio unchanged (e.g. ``-x50,nan``).<br>
149%>
150%> <li> ``-s<val>`` - option to scale output image to specific size (bitmap formats only).<br>
151%> The fixed size can be specified as a single value (for rows=cols) or
152%> comma-separated values (e.g. ``-s300,400``).<br>
153%> Each value can be a scalar integer (signifying pixels) or percentage (e.g. ``-s125%``).<br>
154%> The scaling is done last, after any other cropping/rescaling due to other params.<br>
155%>
156%> <li> ``-append`` - option indicating that if the file already exists the figure is to
157%> be appended as a new page, instead of being overwritten (default).<br>
158%> PDF, TIF & GIF output formats only (multi-image GIF = animated).<br>
159%>
160%> <li> ``-bookmark`` - option to indicate that a bookmark with the name of the
161%> figure is to be created in the output file (PDF format only).<br>
162%>
163%> <li> ``-clipboard`` - option to save output as an image on the system clipboard.<br>
164%>
165%> <li> ``-clipboard<:format>`` - copies to clipboard in the specified format:
166%> image (default), bitmap, emf, or pdf.<br>
167%> Note that Only ``-clipboard`` (or ``-clipboard:image``, which is the same)
168%> applies savefig parameters such as cropping, padding etc:<br>
169%> <ol>
170%> <li> ``-clipboard:image`` create a bitmap image using savefig processing.<br>
171%> <li> ``-clipboard:bitmap`` create a bitmap image as-is (no auto-cropping etc.).<br>
172%> <li> ``-clipboard:emf`` is vector format without auto-cropping; Windows-only.<br>
173%> <li> ``-clipboard:pdf`` is vector format without cropping; not universally supported.<br>
174%> </ol>
175%>
176%> <li> ``-d<gs_option>`` - option to indicate a ghostscript setting. For example,
177%> ``-dMaxBitmap=0`` or ``-dNoOutputFonts`` (Ghostscript 9.15+).<br>
178%>
179%> <li> ``-depsc`` - option to use EPS level-3 rather than the default level-2 print
180%> device. This solves some bugs with MATLAB's default ``-depsc2`` device
181%> such as discolored subplot lines on images (vector formats only).<br>
182%>
183%> <li> ``-metadata <metaDataInfo>`` - adds the specified meta-data information to the
184%> exported file (PDF format only). metaDataInfo must be either a struct
185%> or a cell array with pairs of values: {``'fieldName'``, ``fieldValue``, ...}.<br>
186%> Common metadata fields: Title, Author, Creator, Producer, Subject, Keywords.<br>
187%>
188%> <li> ``-update`` - option to download and install the latest version of savefig.<br>
189%>
190%> <li> ``-version`` - return the current savefig version, without any figure export.<br>
191%>
192%> <li> ``-nofontswap`` - option to avoid font swapping. Font swapping is automatically
193%> done in vector formats (only): 11 standard MATLAB fonts are
194%> replaced by the original figure fonts. This option prevents this.<br>
195%>
196%> <li> ``-font_space <char>`` - option to set a spacer character for font-names that
197%> contain spaces, used by EPS/PDF. Default: ``''``.<br>
198%>
199%> <li> ``-linecaps`` - option to create rounded line-caps (vector formats only).<br>
200%>
201%> <li> ``-noinvert`` - option to avoid setting figure's InvertHardcopy property to
202%> 'off' during output (this solves some problems of empty outputs).<br>
203%>
204%> <li> ``-preserve_size`` - option to preserve the figure's PaperSize property in output
205%> file (PDF/EPS formats only; default is to not preserve it).<br>
206%>
207%> <li> ``-options <optionsStruct>`` - format-specific parameters as defined in MATLAB
208%> documentation of the ``imwrite`` function, contained in a struct under
209%> the format name. For example to specify the JPG Comment parameter,
210%> pass a struct such as this: ``options.JPG.Comment='abc'``. Similarly,
211%> ``options.PNG.BitDepth=4``. Only used by PNG,TIF,JPG,GIF output formats.<br>
212%> Options can also be specified as a cell array of name-value pairs,
213%> e.g. ``{'BitDepth',4, 'Author','Yair'}`` - these options will be used
214%> by all supported output formats of the savefig command.<br>
215%>
216%> <li> ``-silent`` - option to avoid various warning and informational messages, such
217%> as version update checks, transparency or renderer issues, etc.<br>
218%>
219%> <li> ``-notify`` - option to notify the user when export is done, in both a console
220%> message and a popup dialog (allow opening the exported file/folder).<br>
221%>
222%> <li> ``-regexprep <old> <new>`` - replaces all occurances of <old> (a regular expression
223%> string or array of strings; case-sensitive), with the corresponding
224%> <new> string(s), in EPS/PDF files (only). See regexp function doc.<br>
225%> Warning: invalid replacement can make your EPS/PDF file unreadable!<br>
226%>
227%> <li> ``-toolbar`` - adds an interactive export button to the figure toolbar.<br>
228%>
229%> <li> ``-menubar`` - adds an interactive export menu to the figure menubar.<br>
230%>
231%> <li> ``-contextmenu`` - adds interactive export menu to figure context-menu (right-click)<br>
232%>
233%> <li> ``handle`` - handle of the figure, axes or uipanels (can be an array of handles
234%> but all the objects must be in the same figure) to be exported.<br>
235%> Default: ``gcf`` (handle of current figure).<br>
236%>
237%> <li> ``figName`` - name (title) of the figure to export (e.g. ``'Figure 1'`` or ``'My fig'``).<br>
238%> Overridden by handle (if specified); Default: current figure.<br>
239%> </ol>
240%>
241%> \return
242%> ``imageData`` : The output image cube of type ``uint8`` of
243%> shape ``[M, N, C]`` containing the exported figure data.<br>
244%> ``alpha`` : The output image matrix of shape ``[M, N]`` of alpha-matte
245%> values in the range ``[0, 1]`` for the case of transparent background.<br>
246%>
247%> \interface{savefig}
248%> \code{.m}
249%>
250%> imageData = pm.vis.figure.savefig(varargin)
251%> [imageData, alpha] = pm.vis.figure.savefig(varargin)
252%> pm.vis.figure.savefig filename
253%> pm.vis.figure.savefig ... -<format>
254%> pm.vis.figure.savefig ... -nocrop
255%> pm.vis.figure.savefig ... -c[<val>,<val>,<val>,<val>]
256%> pm.vis.figure.savefig ... -transparent
257%> pm.vis.figure.savefig ... -native
258%> pm.vis.figure.savefig ... -m<val>
259%> pm.vis.figure.savefig ... -r<val>
260%> pm.vis.figure.savefig ... -a<val>
261%> pm.vis.figure.savefig ... -q<val>
262%> pm.vis.figure.savefig ... -p<val>
263%> pm.vis.figure.savefig ... -n<val> or: -n<val>,<val>
264%> pm.vis.figure.savefig ... -x<val> or: -x<val>,<val>
265%> pm.vis.figure.savefig ... -s<val> or: -s<val>,<val>
266%> pm.vis.figure.savefig ... -d<gs_option>
267%> pm.vis.figure.savefig ... -depsc
268%> pm.vis.figure.savefig ... -metadata <metaDataInfo>
269%> pm.vis.figure.savefig ... -<renderer>
270%> pm.vis.figure.savefig ... -<colorspace>
271%> pm.vis.figure.savefig ... -append
272%> pm.vis.figure.savefig ... -bookmark
273%> pm.vis.figure.savefig ... -clipboard<:format>
274%> pm.vis.figure.savefig ... -update
275%> pm.vis.figure.savefig ... -version
276%> pm.vis.figure.savefig ... -nofontswap
277%> pm.vis.figure.savefig ... -font_space <char>
278%> pm.vis.figure.savefig ... -linecaps
279%> pm.vis.figure.savefig ... -noinvert
280%> pm.vis.figure.savefig ... -preserve_size
281%> pm.vis.figure.savefig ... -options <optionsStruct>
282%> pm.vis.figure.savefig ... -silent
283%> pm.vis.figure.savefig ... -notify
284%> pm.vis.figure.savefig ... -regexprep <pattern> <replace>
285%> pm.vis.figure.savefig ... -toolbar
286%> pm.vis.figure.savefig ... -menubar
287%> pm.vis.figure.savefig ... -contextmenu
288%> pm.vis.figure.savefig(..., handle)
289%> pm.vis.figure.savefig(..., figName)
290%>
291%> \endcode
292%>
293%> \note
294%> This function is a reimplementation of the MATLAB package
295%> [export_fig](@ref https://github.com/altmany/export_fig).<br>
296%>
297%> \example{savefig}
298%> \code{.m}
299%>
300%> pm.vis.figure.savefig(); % export the current figure with the default name.
301%> pm.vis.figure.savefig("gridplot.pdf") % export figure to the specified PDF file.
302%> pm.vis.figure.savefig("gridplot.png", "-m4 -transparent") % export a large png plot of magnitude 4 with transparency.
303%>
304%> \endcode
305%>
306%> \final{savefig}
307%>
308%> Copyright (C) Oliver Woodford 2008-2014, Yair Altman 2015-
309%>
310%> \verbatim
311%> The idea of using ghostscript is inspired by Peder Axensten's SAVEFIG
312%> (fex id: 10889) which is itself inspired by EPS2PDF (fex id: 5782).
313%> The idea for using pdftops came from the MATLAB newsgroup (id: 168171).
314%> The idea of editing the EPS file to change line styles comes from Jiro
315%> Doke's FIXPSLINESTYLE (fex id: 17928).
316%> The idea of changing dash length with line width came from comments on
317%> fex id: 5743, but the implementation is mine :)
318%> The idea of anti-aliasing bitmaps came from Anders Brun's MYAA (fex id: 20979).
319%> The idea of appending figures in pdfs came from Matt C in comments on the
320%> FEX (id: 23629)
321%> Thanks to Roland Martin for pointing out the colour MATLAB
322%> bug/feature with colorbar axes and transparent backgrounds.
323%> Thanks also to Andrew Matthews for describing a bug to do with the figure
324%> size changing in -nodisplay mode. I couldn't reproduce it, but included a
325%> fix anyway.
326%> Thanks to Tammy Threadgill for reporting a bug where an axes is not
327%> isolated from gui objects.
328%>
329%> 23/02/12: Ensure that axes limits don't change during printing
330%> 14/03/12: Fix bug in fixing the axes limits (thanks to Tobias Lamour for reporting it).
331%> 02/05/12: Incorporate patch of Petr Nechaev (many thanks), enabling bookmarking of figures in pdf files.
332%> 09/05/12: Incorporate patch of Arcelia Arrieta (many thanks), to keep tick marks fixed.
333%> 12/12/12: Add support for isolating uipanels. Thanks to michael for suggesting it.
334%> 25/09/13: Add support for changing resolution in vector formats. Thanks to Jan Jaap Meijer for suggesting it.
335%> 07/05/14: Add support for '~' at start of path. Thanks to Sally Warner for suggesting it.
336%> 24/02/15: Fix MATLAB R2014b bug (issue #34): plot markers are not displayed when ZLimMode='manual'
337%> 25/02/15: Fix issue #4 (using HG2 on R2014a and earlier)
338%> 25/02/15: Fix issue #21 (bold TeX axes labels/titles in R2014b)
339%> 26/02/15: If temp dir is not writable, use the user-specified folder for temporary EPS/PDF files (Javier Paredes)
340%> 27/02/15: Modified repository URL from github.com/ojwoodford to /altmany; Indented main function; Added top-level try-catch block to display useful workarounds
341%> 28/02/15: Enable users to specify optional ghostscript options (issue #36)
342%> 06/03/15: Improved image padding & cropping thanks to Oscar Hartogensis
343%> 26/03/15: Fixed issue #49 (bug with transparent grayscale images); fixed out-of-memory issue
344%> 26/03/15: Fixed issue #42: non-normalized annotations on HG1
345%> 26/03/15: Fixed issue #46: Ghostscript crash if figure units <> pixels
346%> 27/03/15: Fixed issue #39: bad export of transparent annotations/patches
347%> 28/03/15: Fixed issue #50: error on some MATLAB versions with the fix for issue #42
348%> 29/03/15: Fixed issue #33: bugs in MATLAB's print() function with -cmyk
349%> 29/03/15: Improved processing of input args (accept space between param name & value, related to issue #51)
350%> 30/03/15: When exporting *.fig files, then saveas *.fig if figure is open, otherwise export the specified fig file
351%> 30/03/15: Fixed edge case bug introduced yesterday (commit #ae1755bd2e11dc4e99b95a7681f6e211b3fa9358)
352%> 09/04/15: Consolidated header comment sections; initialize output vars only if requested (nargout>0)
353%> 14/04/15: Workaround for issue #45: lines in image subplots are exported in invalid color
354%> 15/04/15: Fixed edge-case in parsing input parameters; fixed help section to show the -depsc option (issue #45)
355%> 21/04/15: Bug fix: Ghostscript croaks on % chars in output PDF file (reported by Sven on FEX page, 15-Jul-2014)
356%> 22/04/15: Bug fix: Pdftops croaks on relative paths (reported by Tintin Milou on FEX page, 19-Jan-2015)
357%> 04/05/15: Merged fix #63 (Kevin Mattheus Moerman): prevent tick-label changes during export
358%> 07/05/15: Partial fix for issue #65: PDF export used painters rather than opengl renderer (thanks Nguyenr)
359%> 08/05/15: Fixed issue #65: bad PDF append since commit #e9f3cdf 21/04/15 (thanks Robert Nguyen)
360%> 12/05/15: Fixed issue #67: exponent labels cropped in export, since fix #63 (04/05/15)
361%> 28/05/15: Fixed issue #69: set non-bold label font only if the string contains symbols (\beta etc.), followup to issue #21
362%> 29/05/15: Added informative error message in case user requested SVG output (issue #72)
363%> 09/06/15: Fixed issue #58: -transparent removed anti-aliasing when exporting to PNG
364%> 19/06/15: Added -update option to download and install the latest version of savefig
365%> 07/07/15: Added -nofontswap option to avoid font-swapping in EPS/PDF
366%> 16/07/15: Fixed problem with anti-aliasing on old MATLAB releases
367%> 11/09/15: Fixed issue #103: magnification must never become negative; also fixed reported error msg in parsing input params
368%> 26/09/15: Alert if trying to export transparent patches/areas to non-PNG outputs (issue #108)
369%> 04/10/15: Do not suggest workarounds for certain errors that have already been handled previously
370%> 01/11/15: Fixed issue #112: use same renderer in print2eps as savefig (thanks to Jesús Pestana Puerta)
371%> 10/11/15: Custom GS installation webpage for MacOS. Thanks to Andy Hueni via FEX
372%> 19/11/15: Fixed clipboard export in R2015b (thanks to Dan K via FEX)
373%> 21/02/16: Added -c option for indicating specific crop amounts (idea by Cedric Noordam on FEX)
374%> 08/05/16: Added message about possible error reason when groot.Units~=pixels (issue #149)
375%> 17/05/16: Fixed case of image YData containing more than 2 elements (issue #151)
376%> 08/08/16: Enabled exporting transparency to TIF, in addition to PNG/PDF (issue #168)
377%> 11/12/16: Added alert in case of error creating output PDF/EPS file (issue #179)
378%> 13/12/16: Minor fix to the commit for issue #179 from 2 days ago
379%> 22/03/17: Fixed issue #187: only set manual ticks when no exponent is present
380%> 09/04/17: Added -linecaps option (idea by Baron Finer, issue #192)
381%> 15/09/17: Fixed issue #205: incorrect tick-labels when Ticks number don't match the TickLabels number
382%> 15/09/17: Fixed issue #210: initialize alpha map to ones instead of zeros when -transparent is not used
383%> 18/09/17: Added -font_space option to replace font-name spaces in EPS/PDF (workaround for issue #194)
384%> 18/09/17: Added -noinvert option to solve some export problems with some graphic cards (workaround for issue #197)
385%> 08/11/17: Fixed issue #220: axes exponent is removed in HG1 when TickMode is 'manual' (internal MATLAB bug)
386%> 08/11/17: Fixed issue #221: alert if the requested folder does not exist
387%> 19/11/17: Workaround for issue #207: alert when trying to use transparent bgcolor with -opengl
388%> 29/11/17: Workaround for issue #206: warn if exporting PDF/EPS for a figure that contains an image
389%> 11/12/17: Fixed issue #230: use OpenGL renderer when exported image contains transparency (also see issue #206)
390%> 30/01/18: Updated SVG message to point to https://github.com/kupiqu/plot2svg and display user-selected filename if available
391%> 27/02/18: Fixed issue #236: axes exponent cropped from output if on right-hand axes
392%> 29/05/18: Fixed issue #245: process "string" inputs just like 'char' inputs
393%> 13/08/18: Fixed issue #249: correct black axes color to off-black to avoid extra cropping with -transparent
394%> 27/08/18: Added a possible file-open reason in EPS/PDF write-error message (suggested by "craq" on FEX page)
395%> 22/09/18: Xpdf website changed to xpdfreader.com
396%> 23/09/18: Fixed issue #243: only set non-bold font (workaround for issue #69) in R2015b or earlier; warn if changing font
397%> 23/09/18: Workaround for issue #241: don't use -r864 in EPS/PDF outputs when -native is requested (solves black lines problem)
398%> 18/11/18: Issue #261: Added informative alert when trying to export a uifigure (which is not currently supported)
399%> 13/12/18: Issue #261: Fixed last commit for cases of specifying axes/panel handle as input, rather than a figure handle
400%> 13/01/19: Issue #72: Added basic SVG output support
401%> 04/02/19: Workaround for issues #207 and #267: -transparent implies -noinvert
402%> 08/03/19: Issue #269: Added ability to specify format-specific options for PNG,TIF,JPG outputs; fixed help section
403%> 21/03/19: Fixed the workaround for issues #207 and #267 from 4/2/19 (-transparent now does *NOT* imply -noinvert; -transparent output should now be ok in all formats)
404%> 12/06/19: Issue #277: Enabled preservation of figure's PaperSize in output PDF/EPS file
405%> 06/08/19: Remove warning message about obsolete JavaFrame in R2019b
406%> 30/10/19: Fixed issue #261: added support for exporting uifigures and uiaxes (thanks to idea by @MarvinILA)
407%> 12/12/19: Added warning in case user requested anti-aliased output on an aliased HG2 figure (issue #292)
408%> 15/12/19: Added promo message
409%> 08/01/20: (3.00) Added check for newer version online (initialized to version 3.00)
410%> 15/01/20: (3.01) Clarified/fixed error messages; Added error IDs; easier -update; various other small fixes
411%> 20/01/20: (3.02) Attempted fix for issue #285 (unsupported patch transparency in some Ghostscript versions); Improved suggested fixes message upon error
412%> 03/03/20: (3.03) Suggest to upload problematic EPS file in case of a Ghostscript error in eps2pdf (& don't delete this file)
413%> 22/03/20: (3.04) Workaround for issue #15; Alert if ghostscript file not found on MATLAB path
414%> 10/05/20: (3.05) Fix the generated SVG file, based on Cris Luengo's SVG_FIX_VIEWBOX; Don't generate PNG when only SVG is requested
415%> 02/07/20: (3.06) Significantly improved performance (speed) and fidelity of bitmap images; Return alpha matrix for bitmap images; Fixed issue #302 (-update bug); Added EMF output; Added -clipboard formats (image,bitmap,emf,pdf); Added hints for exportgraphics/copygraphics usage in certain use-cases; Added description of new version features in the update message; Fixed issue #306 (yyaxis cropping); Fixed EPS/PDF auto-cropping with -transparent
416%> 06/07/20: (3.07) Fixed issue #307 (bug in padding of bitmap images); Fixed axes transparency in -clipboard:emf with -transparent
417%> 07/07/20: (3.08) Fixed issue #308 (bug in R2019a and earlier)
418%> 18/07/20: (3.09) Fixed issue #310 (bug with tiny image on HG1); Fixed title cropping bug
419%> 23/07/20: (3.10) Fixed issues #313,314 (figure position changes if units ~= pixels); Display multiple versions change-log, if relevant; Fixed issue #312 (PNG: only use alpha channel if -transparent was requested)
420%> 30/07/20: (3.11) Fixed issue #317 (bug when exporting figure with non-pixels units); Potential solve also of issue #303 (size change upon export)
421%> 14/08/20: (3.12) Fixed some exportgraphics/copygraphics compatibility messages; Added -silent option to suppress non-critical messages; Reduced promo message display rate to once a week; Added progress messages during savefig('-update')
422%> 07/10/20: (3.13) Added version info and change-log links to update message (issue #322); Added -version option to return the current savefig version; Avoid JavaFrame warning message; Improved exportgraphics/copygraphics infomercial message inc. support of upcoming MATLAB R2021a
423%> 10/12/20: (3.14) Enabled user-specified regexp replacements in generated EPS/PDF files (issue #324)
424%> 01/07/21: (3.15) Added informative message in case of setopacityalpha error (issue #285)
425%> 26/08/21: (3.16) Fixed problem of white elements appearing transparent (issue #330); clarified some error messages
426%> 27/09/21: (3.17) Made MATLAB's builtin export the default for SVG, rather than fig2svg/plot2svg (issue #316); updated transparency error message (issues #285, #343); reduced promo message frequency
427%> 03/10/21: (3.18) Fixed warning about invalid escaped character when the output folder does not exist (issue #345)
428%> 25/10/21: (3.19) Fixed print error when exporting a specific subplot (issue #347); avoid duplicate error messages
429%> 11/12/21: (3.20) Added GIF support, including animated & transparent-background; accept format options as cell-array, not just nested struct
430%> 20/12/21: (3.21) Speedups; fixed exporting non-current figure (hopefully fixes issue #318); fixed warning when appending to animated GIF
431%> 02/03/22: (3.22) Fixed small potential memory leak during screen-capture; expanded exportgraphics message for vector exports; fixed rotated tick labels on R2021a+
432%> 02/03/22: (3.23) Added -toolbar and -menubar options to add figure toolbar/menubar items for interactive figure export (issue #73); fixed edge-case bug with GIF export
433%> 14/03/22: (3.24) Added support for specifying figure name in addition to handle; added warning when trying to export TIF/JPG/BMP with transparency; use current figure as default handle even when its HandleVisibility is not 'on'
434%> 16/03/22: (3.25) Fixed occasional empty files due to excessive cropping (issues #318, #350, #351)
435%> 01/05/22: (3.26) Added -transparency option for TIFF files
436%> 15/05/22: (3.27) Fixed EPS bounding box (issue #356)
437%> 04/12/22: (3.28) Added -metadata option to add custom info to PDF files; fixed -clipboard export (transparent and gray-scale images; deployed apps; old Matlabs)
438%> 03/01/23: (3.29) Use silent mode by default in deployed apps; suggest installing ghostscript/pdftops if required yet missing; fixed invalid chars in export filename; reuse existing figure toolbar if available
439%> 03/02/23: (3.30) Added -contextmenu option to add interactive context-menu items; fixed: -menubar,-toolbar created the full default figure menubar/toolbar if not shown; enlarged toolbar icon; support adding savefig icon to custom toolbars; alert if specifying multiple or invalid handle(s)
440%> 20/02/23: (3.31) Fixed PDF quality issues as suggested by @scholnik (issues #285, #368); minor fixes for MacOS/Linux; use figure's FileName property (if available) as the default export filename; added -gif optional format parameter; Display the export folder (full pathname) in menu items when using -toolbar, -menubar and/or -contextmenu
441%> 21/02/23: (3.32) Fixed EPS export error handling in deployed apps; use MATLAB's builtin EPS export if pdftops is not installed or fails; disabled EMF export option on MacOS/Linux; fixed some EMF warning messages; don't export PNG if only -toolbar etc were specified
442%> 23/02/23: (3.33) Fixed PDF -append (issue #369); Added -notify option to notify user when the export is done; propagate all specified savefig options to -toolbar,-menubar,-contextmenu exports; -silent is no longer set by default in deployed apps (i.e. you need to call -silent explicitly)
443%> 23/03/23: (3.34) Fixed error when exporting axes handle to clipboard without filename (issue #372)
444%> 11/04/23: (3.35) Added -n,-x,-s options to set min, max, and fixed output image size (issue #315)
445%> 13/04/23: (3.36) Reduced (hopefully fixed) unintended EPS/PDF image cropping (issues #97, #318); clarified warning in case of PDF/EPS export of transparent patches (issues #94, #106, #108)
446%> 23/04/23: (3.37) Fixed run-time error with old MATLAB releases (issue #374); -notify console message about exported image now displays black (STDOUT) not red (STDERR)
447%> 15/05/23: (3.38) Fixed endless recursion when using savefig in Live Scripts (issue #375); don't warn about exportgraphics/copygraphics alternatives in deployed mode
448%> 30/05/23: (3.39) Fixed exported bgcolor of uifigures or figures in Live Scripts (issue #377)
449%> 06/07/23: (3.40) For Tiff compression, use AdobeDeflate codec (if available) instead of Deflate (issue #379)
450%> 29/11/23: (3.41) Fixed error when no filename is specified nor available in the exported figure (issue #381)
451%> 05/12/23: (3.42) Fixed unintended cropping of colorbar title in PDF export with -transparent (issues #382, #383)
452%> 07/12/23: (3.43) Fixed unintended modification of colorbar in bitmap export (issue #385)
453%> \endverbatim
454%> <p></p>
455%>
456%> \author
457%> \FatemehBagheri, May 20 2024, 1:25 PM, NASA Goddard Space Flight Center (GSFC), Washington, D.C.<br>
458%> \AmirShahmoradi, May 16 2016, 9:03 AM, Oden Institute for Computational Engineering and Sciences (ICES), UT Austin<br>
459function [imageData, alpha] = savefig(varargin) %#ok<*STRCL1,*DATST,*TNOW1>
460
461 %%%%
462 %%%% Ensure all input string values are characters.
463 %%%%
464
465 for i = 1:length(varargin)
466 if isa(varargin{i}, "string")
467 varargin{i} = convertStringsToChars(varargin{i});
468 end
469 end
470
471 %%%%
472 %%%% Continue.
473 %%%%
474
475 if nargout
476 [imageData, alpha] = deal([]);
477 end
478 displaySuggestedWorkarounds = true;
479
480 % Ensure the figure is rendered correctly _now_ so that properties like axes limits are up-to-date
481 drawnow;
482 pause(0.05); % this solves timing issues with Java Swing's EDT (http://undocumentedmatlab.com/blog/solving-a-matlab-hang-problem)
483
484 % Display promo (just once every 10 days!)
485 persistent promo_time
486 if isempty(promo_time)
487 try promo_time = getpref('savefig','promo_time'); catch, promo_time=-inf; end
488 end
489 if abs(now-promo_time) > 10 && ~isdeployed
490 programsCrossCheck;
491 msg = char('Gps!qspgfttjpobm!Nbumbc!bttjtubodf-!qmfbtf!dpoubdu!=%?'-1);
492 url = char('iuuqt;00VoepdvnfoufeNbumbc/dpn0dpotvmujoh'-1);
493 displayPromoMsg(msg, url);
494 promo_time = now;
495 setpref('savefig','promo_time',now)
496 end
497
498 % Use the current figure as the default figure handle
499 % temporarily set ShowHiddenHandles='on' to access figure with HandleVisibility='off'
500 try oldValue = get(0,'ShowHiddenHandles'); set(0,'ShowHiddenHandles','on'); catch, end
501 fig = get(0, 'CurrentFigure');
502 try set(0,'ShowHiddenHandles',oldValue); catch, end
503
504 % Parse the input arguments
505 argNames = {};
506 for idx = nargin:-1:1, argNames{idx} = inputname(idx); end
507 [fig, options] = parse_args(nargout, fig, argNames, varargin{:});
508
509 % Check for newer version and exportgraphics/copygraphics compatibility
510 currentVersion = 3.43;
511 if options.version % savefig's version requested - return it and bail out
512 imageData = currentVersion;
513 return
514 end
515 if ~options.silent && ~isdeployed
516 % Check for newer version (not too often)
517 checkForNewerVersion(currentVersion); % this breaks in version 3.05- due to regexp limitation in checkForNewerVersion()
518
519 % Hint to users to use exportgraphics/copygraphics in certain cases
520 alertForExportOrCopygraphics(options);
521 %return
522 end
523
524 % Ensure that we have a scalar valid figure handle
525 if isequal(fig,-1)
526 return % silent bail-out
527 elseif isempty(fig)
528 error('savefig:NoFigure','No figure found');
529 elseif numel(fig) > 1
530 error('savefig:MultipleFigures','savefig can only process one figure at a time');
531 elseif ~ishandle(fig)
532 error('savefig:InvalidHandle','invalid figure handle specified to savefig');
533 elseif ~isequal(getappdata(fig,'isExportFigCopy'),true)
534 oldWarn = warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
535 warning off MATLAB:ui:javaframe:PropertyToBeRemoved
536 hFig = handle(ancestor(fig,'figure'));
537 try jf = get(hFig,'JavaFrame_I'); catch, try jf = get(hFig,'JavaFrame'); catch, jf=1; end, end %#ok<JAVFM>
538 warning(oldWarn);
539 if isempty(jf) % this is a uifigure
540 %error('savefig:uifigures','Figures created using the uifigure command or App Designer are not supported by savefig. See %s for details.', hyperlink('https://github.com/altmany/savefig/issues/261','issue #261'));
541 if numel(fig) > 1
542 error('savefig:uifigure:multipleHandles', 'savefig only supports exporting a single uifigure handle at a time; array of handles is not currently supported.')
543 elseif ~any(strcmpi(fig.Type,{'figure','axes'}))
544 error('savefig:uifigure:notFigureOrAxes', 'savefig only supports exporting a uifigure or uiaxes handle; other handles of a uifigure are not currently supported.')
545 end
546 % fig is either a uifigure or uiaxes handle
547 isUiaxes = strcmpi(fig.Type,'axes');
548 if isUiaxes
549 % Label the specified axes so that we can find it in the legacy figure
550 oldUserData = fig.UserData;
551 tempStr = tempname;
552 fig.UserData = tempStr;
553 end
554 try
555 % Create an invisible legacy figure at the same position/size as the uifigure
556 hNewFig = figure('Visible','off', 'Color',hFig.Color, ...
557 'Units',hFig.Units, 'Position',hFig.Position, ...
558 'MenuBar','none', 'ToolBar','none');
559 % Copy the uifigure contents onto the new invisible legacy figure
560 try
561 hChildren = allchild(hFig); %=uifig.Children;
562 copyobj(hChildren,hNewFig);
563 catch
564 if ~options.silent
565 warning('savefig:uifigure:controls', 'Some uifigure controls cannot be exported by savefig and will not appear in the generated output.');
566 end
567 end
568 try fig.UserData = oldUserData; catch, end % restore axes UserData, if modified above
569 setappdata(hNewFig,'isExportFigCopy',true); % avoid endless recursion (issue #375)
570 % Replace the uihandle in the input args with the legacy handle
571 if isUiaxes % uiaxes
572 % Locate the corresponding axes handle in the new legacy figure
573 hAxes = findall(hNewFig,'type','axes','UserData',tempStr);
574 if isempty(hAxes) % should never happen, check just in case
575 hNewHandle = hNewFig; % export the figure instead of the axes
576 else
577 hNewHandle = hAxes; % new axes handle found: use it instead of the uiaxes
578 end
579 else % uifigure
580 hNewHandle = hNewFig;
581 end
582 varargin(cellfun(@(c)isequal(c,fig),varargin)) = {hNewHandle};
583 % Rerun savefig on the legacy figure (with the replaced handle)
584 [imageData, alpha] = savefig(varargin{:});
585 % Delete the temp legacy figure and bail out
586 try delete(hNewFig); catch, end
587 return
588 catch err
589 % Clean up the temp legacy figure and report the error
590 try delete(hNewFig); catch, end
591 rethrow(err)
592 end
593 end
594 end
595
596 % If toolbar button was requested, add it to the specified figure(s)
597 if options.toolbar
598 addToolbarButton(hFig, options);
599 end
600
601 % If menubar menu was requested, add it to the specified figure(s)
602 if options.menubar
603 addMenubarMenu(hFig, options);
604 end
605
606 % If context-menu was requested, add it to the specified handle(s)
607 if options.contextmenu
608 addContextMenu(hFig, options);
609 end
610
611 % Isolate the subplot, if it is one
612 cls = all(ismember(get(fig, 'Type'), {'axes', 'uipanel'}));
613 if cls
614 % Given handles of one or more axes, so isolate them from the rest
615 fig = isolate_axes(fig);
616 else
617 % Check we have a figure
618 if ~isequal(get(fig, 'Type'), 'figure')
619 error('savefig:BadHandle','Handle must be that of a figure, axes or uipanel');
620 end
621 % Get the old InvertHardcopy mode
622 old_mode = get(fig, 'InvertHardcopy');
623 end
624 % from this point onward, fig is assured to be a figure handle
625
626 % Hack the font units where necessary (due to a font rendering bug in print?).
627 % This may not work perfectly in all cases.
628 % Also it can change the figure layout if reverted, so use a copy.
629 magnify = options.magnify * options.aa_factor;
630 if isbitmap(options) && magnify ~= 1
631 fontu = findall(fig, 'FontUnits', 'normalized');
632 if ~isempty(fontu)
633 % Some normalized font units found
634 if ~cls
635 fig = copyfig(fig);
636 set(fig, 'Visible', 'off');
637 fontu = findall(fig, 'FontUnits', 'normalized');
638 cls = true;
639 end
640 set(fontu, 'FontUnits', 'points');
641 end
642 end
643
644 try
645 % MATLAB "feature": axes limits and tick marks can change when printing
646 Hlims = findall(fig, 'Type', 'axes');
647 if ~cls
648 % Record the old axes limit and tick modes
649 Xlims = make_cell(get(Hlims, 'XLimMode'));
650 Ylims = make_cell(get(Hlims, 'YLimMode'));
651 Zlims = make_cell(get(Hlims, 'ZLimMode'));
652 Xtick = make_cell(get(Hlims, 'XTickMode'));
653 Ytick = make_cell(get(Hlims, 'YTickMode'));
654 Ztick = make_cell(get(Hlims, 'ZTickMode'));
655 Xlabel = make_cell(get(Hlims, 'XTickLabelMode'));
656 Ylabel = make_cell(get(Hlims, 'YTickLabelMode'));
657 Zlabel = make_cell(get(Hlims, 'ZTickLabelMode'));
658 try % XTickLabelRotation etc. was added in R2021a
659 Xtkrot = make_cell(get(Hlims, 'XTickLabelRotationMode'));
660 Ytkrot = make_cell(get(Hlims, 'YTickLabelRotationMode'));
661 Ztkrot = make_cell(get(Hlims, 'ZTickLabelRotationMode'));
662 catch
663 end % only in R2021a+
664 end
665
666 % Set all axes limit and tick modes to manual, so the limits and ticks can't change
667 % Fix MATLAB R2014b bug (issue #34): plot markers are not displayed when ZLimMode='manual'
668 set_manual_axes_modes(Hlims, 'X');
669 set_manual_axes_modes(Hlims, 'Y');
670 if ~using_hg2(fig)
671 set_manual_axes_modes(Hlims, 'Z');
672 end
673 catch
674 % ignore - fix issue #4 (using HG2 on R2014a and earlier)
675 end
676
677 % Fix issue #21 (bold TeX axes labels/titles in R2014b when exporting to EPS/PDF)
678 try
679 if using_hg2(fig) && isvector(options)
680 % Set the FontWeight of axes labels/titles to 'normal'
681 % Fix issue #69: set non-bold font only if the string contains symbols (\beta etc.)
682 % Issue #243: only set non-bold font (workaround for issue #69) in R2015b or earlier
683 try isPreR2016a = verLessThan('matlab','8.7'); catch, isPreR2016a = true; end
684 if isPreR2016a
685 texLabels = findall(fig, 'type','text', 'FontWeight','bold');
686 symbolIdx = ~cellfun('isempty',strfind({texLabels.String},'\'));
687 if ~isempty(symbolIdx)
688 set(texLabels(symbolIdx), 'FontWeight','normal');
689 if ~options.silent
690 warning('savefig:BoldTexLabels', 'Bold labels with Tex symbols converted into non-bold in savefig (fix for issue #69)');
691 end
692 end
693 end
694 end
695 catch
696 % ignore
697 end
698
699 % Fix issue #42: non-normalized annotations on HG1 (internal MATLAB bug)
700 annotationHandles = [];
701 try
702 if ~using_hg2(fig)
703 annotationHandles = findall(fig,'Type','hggroup','-and','-property','Units','-and','-not','Units','norm');
704 try % suggested by Jesús Pestana Puerta (jespestana) 30/9/2015
705 originalUnits = get(annotationHandles,'Units');
706 set(annotationHandles,'Units','norm');
707 catch
708 end
709 end
710 catch
711 % should never happen, but ignore in any case - issue #50
712 end
713
714 % Fix issue #46: Ghostscript crash if figure units <> pixels
715 pos = get(fig, 'Position'); % Fix issues #313, #314
716 oldFigUnits = get(fig,'Units');
717 set(fig,'Units','pixels');
718 pixelpos = get(fig, 'Position'); %=getpixelposition(fig);
719
720 tcol = get(fig, 'Color');
721 tcol_orig = tcol;
722
723 % Set to print exactly what is there
724 if options.invert_hardcopy
725 try set(fig, 'InvertHardcopy', 'off'); catch, end % fail silently in uifigures
726 end
727
728 % Set the renderer
729 switch options.renderer
730 case 1
731 renderer = '-opengl';
732 case 2
733 renderer = '-zbuffer';
734 case 3
735 renderer = '-painters';
736 otherwise
737 renderer = '-opengl'; % Default for bitmaps
738 end
739
740 % Initialize
741 tmp_nam = '';
742 exported_files = 0;
743
744 % Main processing
745 try
746 oldWarn = warning;
747
748 % Export bitmap formats first
749 if isbitmap(options)
750 if abs(options.bb_padding) > 1
751 displaySuggestedWorkarounds = false;
752 error('savefig:padding','For bitmap output (png,jpg,tif,bmp) the padding value (-p) must be between -1<p<1')
753 end
754 % Print large version to array
755 [A, tcol, alpha] = getFigImage(fig, magnify, renderer, options, pixelpos);
756 % Get the background colour
757 if options.transparent
758 if (options.png || options.alpha || options.gif || options.tif)
759 try %options.aa_factor < 4 % default, faster but lines are not anti-aliased
760 % If all pixels are indicated as opaque (i.e. something went wrong with the Java screen-capture)
761 isBgColor = A(:,:,1) == tcol(1) & ...
762 A(:,:,2) == tcol(2) & ...
763 A(:,:,3) == tcol(3);
764 % Set the bgcolor pixels to be fully-transparent
765 A(repmat(isBgColor,[1,1,3])) = 254; %=off-white % TODO: more memory efficient without repmat
766 alpha(isBgColor) = 0;
767 catch % older logic - much slower and causes figure flicker
768 if true % to fold the code below...
769 % Get out an alpha channel
770 % MATLAB "feature": black colorbar axes can change to white and vice versa!
771 hCB = findall(fig, 'Type','axes', 'Tag','Colorbar');
772 if isempty(hCB)
773 yCol = [];
774 xCol = [];
775 else
776 yCol = get(hCB, 'YColor');
777 xCol = get(hCB, 'XColor');
778 if iscell(yCol)
779 yCol = cell2mat(yCol);
780 xCol = cell2mat(xCol);
781 end
782 yCol = sum(yCol, 2);
783 xCol = sum(xCol, 2);
784 end
785 % MATLAB "feature": apparently figure size can change when changing
786 % colour in -nodisplay mode
787 % Set the background colour to black, and set size in case it was
788 % changed internally
789 set(fig, 'Color', 'k', 'Position', pos);
790 % Correct the colorbar axes colours
791 set(hCB(yCol==0), 'YColor', [0 0 0]);
792 set(hCB(xCol==0), 'XColor', [0 0 0]);
793 % Correct black axes color to off-black (issue #249)
794 hAxes = findall(fig, 'Type','axes');
795 [hXs,hXrs] = fixBlackAxle(hAxes, 'XColor');
796 [hYs,hYrs] = fixBlackAxle(hAxes, 'YColor');
797 [hZs,hZrs] = fixBlackAxle(hAxes, 'ZColor');
798
799 % The following code might cause out-of-memory errors
800 try
801 % Print large version to array
802 B = print2array(fig, magnify, renderer);
803 % Downscale the image
804 B = downsize(single(B), options.aa_factor);
805 catch
806 % This is more conservative in memory, but kills transparency (issue #58)
807 B = single(print2array(fig, magnify/options.aa_factor, renderer));
808 end
809
810 % Set background to white (and set size)
811 set(fig, 'Color', 'w', 'Position', pos);
812 % Correct the colorbar axes colours
813 set(hCB(yCol==3), 'YColor', [1 1 1]);
814 set(hCB(xCol==3), 'XColor', [1 1 1]);
815 % Revert the black axes colors
816 set(hXs, 'XColor', [0,0,0]);
817 set(hYs, 'YColor', [0,0,0]);
818 set(hZs, 'ZColor', [0,0,0]);
819 set(hXrs, 'Color', [0,0,0]);
820 set(hYrs, 'Color', [0,0,0]);
821 set(hZrs, 'Color', [0,0,0]);
822
823 % The following code might cause out-of-memory errors
824 try
825 % Print large version to array
826 A = print2array(fig, magnify, renderer);
827 % Downscale the image
828 A = downsize(single(A), options.aa_factor);
829 catch
830 % This is more conservative in memory, but kills transparency (issue #58)
831 A = single(print2array(fig, magnify/options.aa_factor, renderer));
832 end
833
834 % Workaround for issue #15
835 szA = size(A);
836 szB = size(B);
837 if ~isequal(szA,szB)
838 A = A(1:min(szA(1),szB(1)), 1:min(szA(2),szB(2)), :);
839 B = B(1:min(szA(1),szB(1)), 1:min(szA(2),szB(2)), :);
840 if ~options.silent
841 warning('savefig:bitmap:sizeMismatch','Problem detected by savefig generation of a bitmap image; the generated export may look bad. Try to reduce the figure size to fit the screen, or avoid using savefig''s -transparent option.')
842 end
843 end
844 % Compute the alpha map
845 alpha = round(sum(B - A, 3)) / (255 * 3) + 1;
846 A = alpha;
847 A(A==0) = 1;
848 A = B ./ A(:,:,[1 1 1]);
849 clear B
850 end %folded code...
851 end
852 %A = uint8(A);
853 else % JPG,BMP
854 warning('savefig:unsupported:background','MATLAB cannot set transparency when exporting JPG/BMP image files (see imwrite function documentation)')
855 end
856 end
857 % Downscale the image if its size was increased (for anti-aliasing)
858 if size(A,1) > 1.1 * options.magnify * pixelpos(4) %1.1 to avoid edge-cases
859 % Downscale the image
860 A = downsize(A, options.aa_factor);
861 alpha = downsize(alpha, options.aa_factor);
862 end
863 % Crop the margins based on the bgcolor, if requested
864 if options.crop
865 %[alpha, v] = crop_borders(alpha, 0, 1, options.crop_amounts);
866 %A = A(v(1):v(2),v(3):v(4),:);
867 [A, vA, vB] = crop_borders(A, tcol, options.bb_padding, options.crop_amounts);
868 if ~any(isnan(vB)) % positive padding
869 sz = size(A); % Fix issue #308
870 B = repmat(uint8(zeros(1,1,size(alpha,3))),sz([1,2])); % Fix issue #307 %=zeros(sz([1,2]),'uint8');
871 B(vB(1):vB(2), vB(3):vB(4), :) = alpha(vA(1):vA(2), vA(3):vA(4), :); % ADDED BY OH
872 alpha = B;
873 else % negative padding
874 alpha = alpha(vA(1):vA(2), vA(3):vA(4), :);
875 end
876 end
877 % Get the non-alpha image (presumably unneeded with Java-based screen-capture)
878 %{
879 if isbitmap(options)
880 % Modify the intensity of the pixels' RGB values based on their alpha transparency
881 % TODO: not sure that we want this with Java screen-capture values!
882 alph = alpha(:,:,ones(1, size(A, 3)));
883 A = uint8(single(A) .* alph + 255 * (1 - alph));
884 end
885 %}
886 % Revert the figure properties back to their original values
887 set(fig, 'Units',oldFigUnits, 'Position',pos, 'Color',tcol_orig);
888 % Check for greyscale images
889 if options.colourspace == 2
890 % Convert to greyscale
891 A = rgb2grey(A);
892 else
893 % Return only one channel for greyscale
894 A = check_greyscale(A);
895 end
896 % Change alpha from [0:255] uint8 => [0:1] single from here onward:
897 alpha = single(alpha) / 255;
898 % Clamp the image's min/max size (if specified)
899 sz = size(A); sz(3:end) = []; %sz=size(A,1:2); %issue #374 & X. Xu PM
900 szNew = options.min_size;
901 if numel(szNew) == 2
902 szNew(isinf(szNew)) = 0;
903 szNew = round(max(sz,szNew,'includenan'));
904 idxToCompare = ~isnan(szNew);
905 if ~isequal(sz(idxToCompare), szNew(idxToCompare))
906 A = imresize(A,szNew);
907 alpha = imresize(alpha,szNew);
908 end
909 end
910 szNew = options.max_size;
911 if numel(szNew) == 2
912 szNew(szNew <= 1) = inf;
913 szNew = round(min(sz,szNew,'includenan'));
914 idxToCompare = ~isnan(szNew);
915 if ~isequal(sz(idxToCompare), szNew(idxToCompare))
916 A = imresize(A,szNew);
917 alpha = imresize(alpha,szNew);
918 end
919 end
920 % Clamp the image's fixed size (if specified) - has to be done last!
921 szNew = options.fixed_size;
922 if numel(szNew) == 2
923 if szNew(1) < 0, szNew(1) = round(-sz(1)*szNew(1)/100); end
924 if szNew(2) < 0, szNew(2) = round(-sz(2)*szNew(2)/100); end
925 szNew(szNew <= 1) = inf;
926 szNew(isinf(szNew)) = sz(isinf(szNew)); % unchanged dimensions
927 idxToCompare = ~isnan(szNew);
928 if ~isequal(sz(idxToCompare), szNew(idxToCompare))
929 A = imresize(A,szNew);
930 alpha = imresize(alpha,szNew);
931 end
932 end
933 % Outputs
934 if options.im
935 imageData = A;
936 end
937 if options.alpha
938 imageData = A;
939 %alpha = ones(size(A, 1), size(A, 2), 'single'); %=all pixels opaque
940 end
941 % Save the images
942 if options.png
943 % Compute the resolution
944 res = options.magnify * get(0, 'ScreenPixelsPerInch') / 25.4e-3;
945 % Save the png
946 [format_options, bitDepth] = getFormatOptions(options, 'png'); %Issue #269
947 filename = [options.name '.png'];
948 pngOptions = {filename, 'ResolutionUnit','meter', 'XResolution',res, 'YResolution',res, format_options{:}}; %#ok<CCAT>
949 if options.transparent % Fix issue #312: only use alpha channel if -transparent was requested
950 pngOptions = [pngOptions 'Alpha',double(alpha)];
951 end
952 if ~isempty(bitDepth) && bitDepth < 16 && size(A,3) == 3
953 % BitDepth specification requires using a color-map
954 [img, map] = rgb2ind(A, 256);
955 imwrite(img, map, pngOptions{:});
956 else
957 imwrite(A, pngOptions{:});
958 end
959 if options.notify, notify(filename); end
960 end
961 if options.bmp
962 filename = [options.name '.bmp'];
963 imwrite(A, filename);
964 if options.notify, notify(filename); end
965 end
966 if options.jpg
967 % Save jpeg with the specified quality
968 quality = options.quality;
969 if isempty(quality)
970 quality = 95;
971 end
972 format_options = getFormatOptions(options, 'jpg'); %Issue #269
973 filename = [options.name '.jpg'];
974 if quality > 100
975 imwrite(A, filename, 'Mode','lossless', format_options{:});
976 else
977 imwrite(A, filename, 'Quality',quality, format_options{:});
978 end
979 if options.notify, notify(filename); end
980 end
981 if options.tif
982 % Save tif images in cmyk if wanted (and possible)
983 if options.colourspace == 1 && size(A, 3) == 3
984 img = double(255 - A);
985 K = min(img, [], 3);
986 K_ = 255 ./ max(255 - K, 1);
987 C = (img(:,:,1) - K) .* K_;
988 M = (img(:,:,2) - K) .* K_;
989 Y = (img(:,:,3) - K) .* K_;
990 img = uint8(cat(3, C, M, Y, K));
991 clear C M Y K K_
992 else
993 img = A;
994 end
995 resolution = options.magnify * get(0,'ScreenPixelsPerInch');
996 filename = [options.name '.tif'];
997 if options.transparent && any(alpha(:) < 1) && any(isBgColor(:))
998 % Need to use low-level Tiff library since imwrite/writetif doesn't support alpha channel
999 alpha8 = uint8(alpha*255);
1000 tag = ['MATLAB ' version ' savefig v' num2str(currentVersion)];
1001 mode = 'w'; if options.append, mode = 'a'; end
1002 t = Tiff(filename,mode); %R2009a or newer
1003 %See https://www.awaresystems.be/imaging/tiff/tifftags/baseline.html
1004 t.setTag('ImageLength', size(img,1));
1005 t.setTag('ImageWidth', size(img,2));
1006 t.setTag('Photometric', Tiff.Photometric.RGB);
1007 try %issue #379 use Tiff.Compression.AdobeDeflate by default
1008 compressionMode = Tiff.Compression.AdobeDeflate;
1009 catch
1010 warning off imageio:tiffmexutils:libtiffWarning %issue #379
1011 compressionMode = Tiff.Compression.Deflate;
1012 end
1013 t.setTag('Compression', compressionMode);
1014 t.setTag('PlanarConfiguration', Tiff.PlanarConfiguration.Chunky);
1015 t.setTag('ExtraSamples', Tiff.ExtraSamples.AssociatedAlpha);
1016 t.setTag('ResolutionUnit', Tiff.ResolutionUnit.Inch);
1017 t.setTag('BitsPerSample', 8);
1018 t.setTag('SamplesPerPixel',size(img,3)+1); %+1=alpha channel
1019 t.setTag('XResolution', resolution);
1020 t.setTag('YResolution', resolution);
1021 t.setTag('Software', tag);
1022 t.write(cat(3,img,alpha8));
1023 t.close;
1024 else
1025 % Use the builtin imwrite/writetif function
1026 append_mode = {'overwrite', 'append'};
1027 mode = append_mode{options.append+1};
1028 format_options = getFormatOptions(options, 'tif'); %Issue #269
1029 imwrite(img, filename, 'Resolution',resolution, 'WriteMode',mode, format_options{:});
1030 end
1031 if options.notify, notify(filename); end
1032 end
1033 if options.gif
1034 % TODO - merge contents with im2gif.m
1035 % Convert to color-map image required by GIF specification
1036 [img, map] = rgb2ind(A, 256);
1037 % Handle the case of trying to append to non-existing GIF file
1038 % (imwrite() croaks when asked to append to a non-existing file)
1039 filename = [options.name '.gif'];
1040 options.append = options.append && existFile(filename);
1041 % Set the default GIF options for imwrite()
1042 append_mode = {'overwrite', 'append'};
1043 writeMode = append_mode{options.append+1};
1044 gifOptions = {'WriteMode',writeMode};
1045 if options.transparent % only use alpha channel if -transparent was requested
1046 exp = 256 .^ (0:2);
1047 mapVals = sum(round(map*255).*exp,2);
1048 tcolVal = sum(round(double(tcol)).*exp);
1049 alphaIdx = find(mapVals==tcolVal,1);
1050 if isempty(alphaIdx) || alphaIdx <= 0, alphaIdx = 1; end
1051 % GIF color index of uint8/logical images starts at 0, not 1
1052 if ~isfloat(img), alphaIdx = alphaIdx - 1; end
1053 gifOptions = [gifOptions, 'TransparentColor',alphaIdx, ...
1054 'DisposalMethod','restoreBG'];
1055 else
1056 alphaIdx = 1;
1057 end
1058 if ~options.append
1059 % LoopCount and BackgroundColor can only be specified in the
1060 % 1st GIF frame (not in append mode)
1061 % Set default LoopCount=65535 to enable looping within MS Office
1062 gifOptions = [gifOptions, 'LoopCount',65535, 'BackgroundColor',alphaIdx];
1063 end
1064 % Set GIF-specific options specified by the user (if any)
1065 format_options = getFormatOptions(options, 'gif');
1066 gifOptions = [gifOptions, format_options{:}];
1067 % Save the gif file
1068 imwrite(img, map, filename, gifOptions{:});
1069 if options.notify, notify(filename); end
1070 end
1071 end
1072
1073 % Now export the vector formats which are based on EPS
1074 if isvector(options)
1075 hImages = findall(fig,'type','image');
1076 % Set the default renderer to painters
1077 if ~options.renderer
1078 % Handle transparent patches
1079 hasTransparency = ~isempty(findall(fig,'-property','FaceAlpha','-and','-not','FaceAlpha',1));
1080 if hasTransparency
1081 % Alert if trying to export transparent patches/areas to non-supported outputs (issues #94, #106, #108)
1082 % http://www.mathworks.com/matlabcentral/answers/265265-can-savefig-or-else-draw-vector-graphics-with-transparent-surfaces
1083 % TODO - use transparency when exporting to PDF by not passing via print2eps
1084 if options.pdf, format = 'PDF'; else, format = 'non-PNG formats'; end
1085 msg = 'savefig supports transparent patches/areas best in PNG output format. ';
1086 msg = sprintf('%s\nExporting figures with transparency to %s sometimes renders incorrectly. ', msg, format);
1087 msg = sprintf('%s\nIn such cases, try to use the', msg);
1088 if options.pdf && ~options.silent
1089 warning('savefig:transparency', '%s print command: print(gcf, ''-dpdf'', ''%s.pdf'');', msg, options.name);
1090 elseif ~options.png && ~options.tif && ~options.silent % issue #168
1091 warning('savefig:transparency', '%s ScreenCapture utility on the MATLAB File Exchange: http://bit.ly/1QFrBip', msg);
1092 end
1093 elseif ~isempty(hImages)
1094 % Fix for issue #230: use OpenGL renderer when exported image contains transparency
1095 for idx = 1 : numel(hImages)
1096 cdata = get(hImages(idx),'CData');
1097 if any(isnan(cdata(:)))
1098 hasTransparency = true;
1099 break
1100 end
1101 end
1102 end
1103 hasPatches = ~isempty(findall(fig,'type','patch'));
1104 if hasTransparency || hasPatches
1105 % This is *MUCH* slower, but more accurate for patches and transparent annotations (issue #39)
1106 renderer = '-opengl';
1107 else
1108 renderer = '-painters';
1109 end
1110 end
1111 options.rendererStr = renderer; % fix for issue #112
1112 % Generate some filenames
1113 tmp_nam = [tempname '.eps'];
1114 try
1115 % Ensure that the temp dir is writable (Javier Paredes 30/1/15)
1116 fid = fopen(tmp_nam,'w');
1117 fwrite(fid,1);
1118 fclose(fid);
1119 delete(tmp_nam);
1120 pdf_nam_tmp = [tempname '.pdf'];
1121 catch
1122 % Temp dir is not writable, so use the user-specified folder
1123 [dummy,fname,fext] = fileparts(tmp_nam); %#ok<ASGLU>
1124 fpath = fileparts(options.name);
1125 tmp_nam = fullfile(fpath,[fname fext]);
1126 pdf_nam_tmp = fullfile(fpath,[fname '.pdf']);
1127 end
1128 if options.pdf
1129 pdf_nam = [options.name '.pdf'];
1130 try copyfile(pdf_nam, pdf_nam_tmp, 'f'); catch, end % fix for issue #65
1131 else
1132 pdf_nam = pdf_nam_tmp;
1133 end
1134 % Generate the options for print
1135 printArgs = {renderer};
1136 if ~isempty(options.resolution) % issue #241
1137 printArgs{end+1} = sprintf('-r%d', options.resolution);
1138 end
1139 if options.colourspace == 1 % CMYK
1140 % Issue #33: due to internal bugs in MATLAB's print() function, we can't use its -cmyk option
1141 %printArgs{end+1} = '-cmyk';
1142 end
1143 if ~options.crop
1144 % Issue #56: due to internal bugs in MATLAB's print() function, we can't use its internal cropping mechanism,
1145 % therefore we always use '-loose' (in print2eps.m) and do our own cropping (with crop_borders.m)
1146 %printArgs{end+1} = '-loose';
1147 end
1148 if any(strcmpi(varargin,'-depsc'))
1149 % Issue #45: lines in image subplots are exported in invalid color.
1150 % The workaround is to use the -depsc parameter instead of the default -depsc2
1151 printArgs{end+1} = '-depsc';
1152 end
1153 % Print to base EPS file (if this fails, we cannot proceed further)
1154 try
1155 % Remove background if requested (issue #207)
1156 originalBgColor = get(fig, 'Color');
1157 [hXs, hXrs, hYs, hYrs, hZs, hZrs] = deal([]);
1158 if options.transparent %&& ~isequal(get(fig, 'Color'), 'none')
1159 if options.renderer == 1 && ~options.silent % OpenGL
1160 warning('savefig:openglTransparentBG', '-opengl sometimes fails to produce transparent backgrounds; in such a case, try to use -painters instead');
1161 end
1162
1163 % Fix for issue #207, #267 (corrected)
1164 set(fig,'Color','none');
1165
1166 % Correct black axes color to off-black (issue #249)
1167 hAxes = findall(fig, 'Type','axes');
1168 [hXs,hXrs] = fixBlackAxle(hAxes, 'XColor');
1169 [hYs,hYrs] = fixBlackAxle(hAxes, 'YColor');
1170 [hZs,hZrs] = fixBlackAxle(hAxes, 'ZColor');
1171
1172 % Correct black titles to off-black
1173 % https://www.mathworks.com/matlabcentral/answers/567027-matlab-savefig-crops-title
1174 hTitle = fixBlackText(hAxes,'Title');
1175 try hCBs = getappdata(hAxes,'LayoutPeers'); catch, hCBs=[]; end %issue #383
1176 hCBs = unique([findall(fig,'tag','Colorbar'), hCBs]);
1177 hCbTxt = fixBlackText(hCBs,'Title'); % issue #382
1178 end
1179 % Generate an eps
1180 print2eps(tmp_nam, fig, options, printArgs{:}); %winopen(tmp_nam)
1181 % {
1182 % Remove the background, if desired
1183 if options.transparent %&& ~isequal(get(fig, 'Color'), 'none')
1184 eps_remove_background(tmp_nam, 1 + using_hg2(fig));
1185
1186 % Revert the black axles/titles colors
1187 set(hXs, 'XColor', [0,0,0]);
1188 set(hYs, 'YColor', [0,0,0]);
1189 set(hZs, 'ZColor', [0,0,0]);
1190 set(hXrs, 'Color', [0,0,0]);
1191 set(hYrs, 'Color', [0,0,0]);
1192 set(hZrs, 'Color', [0,0,0]);
1193 set(hTitle,'Color',[0,0,0]);
1194 set(hCbTxt,'Color',[0,0,0]);
1195 end
1196 %}
1197 % Restore the figure's previous background color (if modified)
1198 try set(fig,'Color',originalBgColor); drawnow; catch, end
1199 % Fix colorspace to CMYK, if requested (workaround for issue #33)
1200 if options.colourspace == 1 % CMYK
1201 % Issue #33: due to internal bugs in MATLAB's print() function, we can't use its -cmyk option
1202 change_rgb_to_cmyk(tmp_nam);
1203 end
1204 % Add a bookmark to the PDF if desired
1205 if options.bookmark
1206 fig_nam = get(fig, 'Name');
1207 if isempty(fig_nam) && ~options.silent
1208 warning('savefig:EmptyBookmark', 'Bookmark requested for figure with no name. Bookmark will be empty.');
1209 end
1210 add_bookmark(tmp_nam, fig_nam);
1211 end
1212 catch ex
1213 % Restore the figure's previous background color (in case it was not already restored)
1214 try set(fig,'Color',originalBgColor); drawnow; catch, end
1215 % Delete the temporary eps file - NOT! (Yair 3/3/2020)
1216 %delete(tmp_nam);
1217 % Rethrow the EPS-generation error
1218 rethrow(ex);
1219 end
1220 % Generate a PDF file from the base EPS
1221 % (if this fails, we can still proceed if only EPS was requested)
1222 try
1223 %if existFile(pdf_nam_tmp), delete(pdf_nam_tmp); end %issue #369
1224 eps2pdf(tmp_nam, pdf_nam_tmp, 1, options.append, options.colourspace==2, options.quality, options.gs_options);
1225 % Ghostscript croaks on % chars in the output PDF file, so use tempname and then rename the file
1226 try
1227 % Rename the file (except if it is already the same)
1228 % Abbie K's comment on the commit for issue #179 (#commitcomment-20173476)
1229 if ~isequal(pdf_nam_tmp, pdf_nam)
1230 movefile(pdf_nam_tmp, pdf_nam, 'f');
1231 end
1232 catch %movefile failed
1233 % Alert in case of error creating output PDF file (issue #179)
1234 if existFile(pdf_nam_tmp)
1235 fpath = fileparts(pdf_nam);
1236 if ~isempty(fpath) && exist(fpath,'dir')==0
1237 errMsg = ['Could not create ' pdf_nam ' - folder "' fpath '" does not exist'];
1238 else % output folder exists
1239 errMsg = ['Could not create ' pdf_nam ' - perhaps you do not have write permissions, or the file is open in another application'];
1240 end
1241 error('savefig:PDF:create',errMsg);
1242 else % impossible: movefile succeeded but still an error
1243 error('savefig:NoEPS','Could not generate intermediary PDF file from %s.',tmp_name);
1244 end
1245 end
1246 catch ex
1247 % If EPS export was requested, use base EPS file without passing through PDF
1248 if options.eps
1249 eps_filename = [options.name '.eps'];
1250 warning('savefig:EPSviaPDF','Could not generate intermediary PDF file, %s might be sub-optimal',eps_filename);
1251 movefile(tmp_nam,eps_filename,'f');
1252 end
1253 % If PDF export was requested, rethrow the error
1254 if options.pdf
1255 % Rethrow the PDF-generation error
1256 rethrow(ex);
1257 end
1258 end
1259 % Convert the PDF to EPS, if EPS export was requested
1260 % (if this fails, use the base EPS file, without going through PDF)
1261 if options.eps || options.linecaps
1262 try
1263 % Generate an EPS from the PDF using the pdftops utility
1264 % since pdftops can't handle relative paths (e.g., '..\'), use a temp file
1265 eps_nam_tmp = strrep(pdf_nam_tmp,'.pdf','.eps');
1266 try
1267 pdf2eps(pdf_nam, eps_nam_tmp);
1268 catch % pdftops failed - use original eps file
1269 movefile(tmp_nam,eps_nam_tmp,'f');
1270 end
1271
1272 % Issue #192: enable rounded line-caps
1273 if options.linecaps
1274 fstrm = read_write_entire_textfile(eps_nam_tmp);
1275 fstrm = regexprep(fstrm, '[02] J', '1 J');
1276 read_write_entire_textfile(eps_nam_tmp, fstrm);
1277 if options.pdf
1278 eps2pdf(eps_nam_tmp, pdf_nam, 1, options.append, options.colourspace==2, options.quality, options.gs_options);
1279 end
1280 end
1281
1282 % Move the EPS file from temp folder/name to target location
1283 if options.eps
1284 filename = [options.name '.eps'];
1285 movefile(eps_nam_tmp, filename, 'f');
1286 if options.notify, notify(filename); end
1287 else % if options.pdf
1288 try delete(eps_nam_tmp); catch, end
1289 end
1290 catch ex
1291 if ~options.pdf
1292 % Delete the pdf
1293 delete(pdf_nam);
1294 end
1295 try delete(eps_nam_tmp); catch, end
1296 rethrow(ex);
1297 end
1298 if ~options.pdf && existFile(pdf_nam)
1299 % Delete the pdf
1300 try delete(pdf_nam); catch, end
1301 end
1302 end
1303 % Delete the base EPS file if it still exists
1304 try if existFile(tmp_nam), delete(tmp_nam); end, catch, end
1305 % Notify user about the file export (if -notify option specified)
1306 if options.pdf && options.notify, filename=pdf_nam; notify(filename); end
1307 % Issue #206: warn if the figure contains an image
1308 if ~isempty(hImages) && strcmpi(renderer,'-opengl') && ~options.silent % see addendum to issue #206
1309 warnMsg = ['exporting images to PDF/EPS may result in blurry images on some viewers. ' ...
1310 'If so, try to change viewer, or increase the image''s CData resolution, or use -opengl renderer, or export via the print function. ' ...
1311 'See ' hyperlink('https://github.com/altmany/savefig/issues/206', 'issue #206') ' for details.'];
1312 warning('savefig:pdf_eps:blurry_image', warnMsg);
1313 end
1314 end
1315
1316 % SVG format
1317 if options.svg
1318 filename = [options.name '.svg'];
1319 % Adapted from Dan Joshea's https://github.com/djoshea/matlab-save-figure :
1320 try %if ~verLessThan('matlab', '8.4')
1321 % Try MATLAB's built-in svg engine (from Batik Graphics2D for java)
1322 set(fig,'Units','pixels'); % All data in the svg-file is saved in pixels
1323 printArgs = {renderer};
1324 if ~isempty(options.resolution)
1325 printArgs{end+1} = sprintf('-r%d', options.resolution);
1326 end
1327 try
1328 print(fig, '-dsvg', printArgs{:}, filename);
1329 catch
1330 % built-in print() failed, try saveas()
1331 % Note: saveas() currently just calls print(fig,filename,'-dsvg')
1332 % so since print() failed, saveas() will probably also fail
1333 saveas(fig, filename);
1334 end
1335 if ~options.silent
1336 warning('savefig:SVG:print', 'savefig used MATLAB''s built-in SVG output engine. Better results may be gotten via the fig2svg utility (https://github.com/kupiqu/fig2svg).');
1337 end
1338 catch %else % built-in print()/saveas() failed - maybe an old MATLAB release (no -dsvg)
1339 % Try using the fig2svg/plot2svg utilities
1340 try
1341 try
1342 fig2svg(filename, fig); %https://github.com/kupiqu/fig2svg
1343 catch
1344 plot2svg(filename, fig); %https://github.com/jschwizer99/plot2svg
1345 if ~options.silent
1346 warning('savefig:SVG:plot2svg', 'savefig used the plot2svg utility for SVG output. Better results may be gotten via the fig2svg utility (https://github.com/kupiqu/fig2svg).');
1347 end
1348 end
1349 catch err
1350 filename = strrep(filename,'export_fig_out','filename');
1351 msg = ['SVG output is not supported for your figure: ' err.message '\n' ...
1352 'Try one of the following alternatives:\n' ...
1353 ' 1. saveas(gcf,''' filename ''')\n' ...
1354 ' 2. fig2svg utility: https://github.com/kupiqu/fig2svg\n' ... % Note: replaced defunct https://github.com/jschwizer99/plot2svg with up-to-date fork on https://github.com/kupiqu/fig2svg
1355 ' 3. savefig to EPS/PDF, then convert to SVG using non-MATLAB tools\n'];
1356 error('savefig:SVG:error',msg);
1357 end
1358 end
1359 % SVG output was successful if we reached this point
1360 % Add warning about unsupported savefig options with SVG output
1361 if ~options.silent && (any(~isnan(options.crop_amounts)) || any(options.bb_padding))
1362 warning('savefig:SVG:options', 'savefig''s SVG output does not [currently] support cropping/padding.');
1363 end
1364
1365 % Fix the generated SVG file, based on Cris Luengo's SVG_FIX_VIEWBOX:
1366 % https://www.mathworks.com/matlabcentral/fileexchange/49617-svg_fix_viewbox-in_name-varargin
1367 try
1368 % Read SVG file
1369 s = read_write_entire_textfile(filename);
1370 % Fix fonts #1: 'SansSerif' doesn't work on my browser, the correct CSS is 'sans-serif'
1371 s = regexprep(s,'font-family:SansSerif;|font-family:''SansSerif'';','font-family:''sans-serif'';');
1372 % Fix fonts #1: The document-wide default font is 'Dialog'. What is this anyway?
1373 s = regexprep(s,'font-family:''Dialog'';','font-family:''sans-serif'';');
1374 % Replace 'width="xxx" height="yyy"' with 'width="100%" viewBox="0 0 xxx yyy"'
1375 t = regexp(s,'<svg.* width="(?<width>[0-9]*)" height="(?<height>[0-9]*)"','names');
1376 if ~isempty(t)
1377 relativeWidth = 100; %TODO - user-settable via input parameter?
1378 s = regexprep(s,'(?<=<svg[^\n]*) width="[0-9]*" height="[0-9]*"',sprintf(' width="%d\\%%" viewBox="0 0 %s %s"',relativeWidth,t.width,t.height));
1379 end
1380 % Write updated SVG file
1381 read_write_entire_textfile(filename, s);
1382 catch
1383 % never mind - ignore
1384 end
1385 if options.notify, notify(filename); end
1386 end
1387
1388 % EMF format
1389 if options.emf
1390 try
1391 anythingChanged = false;
1392 % Handle transparent bgcolor request
1393 if options.transparent && ~isequal(tcol_orig,'none')
1394 anythingChanged = true;
1395 set(fig, 'Color','none');
1396 end
1397 if ~options.silent
1398 isDefaultMag = options.resolution==864 && options.magnify==1;
1399 if ~ispc
1400 warning('savefig:EMF:NotWindows', 'EMF is only supported on Windows; exporting to EMF format on this machine may result in unexpected behavior.');
1401 elseif isequal(renderer,'-painters') && ~isDefaultMag
1402 warning('savefig:EMF:Painters', 'savefig -r and -m options are ignored for EMF export using the -painters renderer.');
1403 elseif ~isDefaultMag && abs(get(0,'ScreenPixelsPerInch')*options.magnify - options.resolution) > 1e-6
1404 warning('savefig:EMF:Magnify', 'savefig -m option is ignored for EMF export.');
1405 end
1406 if ~isequal(options.bb_padding,0) || ~isempty(options.quality)
1407 warning('savefig:EMF:Options', 'savefig cropping, padding and quality options are ignored for EMF export.');
1408 end
1409 if ~anythingChanged && ~isdeployed
1410 warning('savefig:EMF:print', 'For a figure without background transparency, savefig uses MATLAB''s built-in print(''-dmeta'') function without any extra processing, so try using it directly.');
1411 end
1412 end
1413 printArgs = {renderer};
1414 if ~isempty(options.resolution)
1415 printArgs{end+1} = sprintf('-r%d', options.resolution);
1416 end
1417 filename = [options.name '.emf'];
1418 print(fig, '-dmeta', printArgs{:}, filename);
1419 if options.notify, notify(filename); end
1420 catch err % built-in print() failed - maybe an old MATLAB release (no -dsvg)
1421 msg = ['EMF output is not supported: ' err.message '\n' ...
1422 'Try to use savefig with other formats, such as PDF or EPS.\n'];
1423 error('savefig:EMF:error',msg);
1424 end
1425 end
1426
1427 % Revert the figure or close it (if requested)
1428 if cls || options.closeFig
1429 % Close the created figure
1430 close(fig);
1431 else
1432 % Reset the hardcopy mode
1433 try set(fig, 'InvertHardcopy', old_mode); catch, end % fail silently in uifigures
1434 % Reset the axes limit and tick modes
1435 for a = 1:numel(Hlims)
1436 try
1437 set(Hlims(a), 'XLimMode', Xlims{a}, 'YLimMode', Ylims{a}, 'ZLimMode', Zlims{a},...
1438 'XTickMode', Xtick{a}, 'YTickMode', Ytick{a}, 'ZTickMode', Ztick{a},...
1439 'XTickLabelMode', Xlabel{a}, 'YTickLabelMode', Ylabel{a}, 'ZTickLabelMode', Zlabel{a});
1440 try % only in R2021a+
1441 set(Hlims(a), 'XTickLabelRotationMode', Xtkrot{a}, ...
1442 'YTickLabelRotationMode', Ytkrot{a}, ...
1443 'ZTickLabelRotationMode', Ztkrot{a});
1444 catch
1445 % ignore - possibly R2020b or earlier
1446 end
1447 catch
1448 % ignore - fix issue #4 (using HG2 on R2014a and earlier)
1449 end
1450 end
1451 % Revert the tex-labels font weights
1452 try set(texLabels, 'FontWeight','bold'); catch, end
1453 % Revert annotation units
1454 for handleIdx = 1 : numel(annotationHandles)
1455 try
1456 oldUnits = originalUnits{handleIdx};
1457 catch
1458 oldUnits = originalUnits;
1459 end
1460 try set(annotationHandles(handleIdx),'Units',oldUnits); catch, end
1461 end
1462 % Revert figure properties in case they were changed
1463 try set(fig, 'Units',oldFigUnits, 'Position',pos, 'Color',tcol_orig); catch, end
1464 end
1465
1466 % Output to clipboard (if requested)
1467 if options.clipboard
1468 % Use Java clipboard by default
1469 if strcmpi(options.clipformat,'image')
1470 % Save the image in the system clipboard
1471 % credit: Jiro Doke's IMCLIPBOARD: http://www.mathworks.com/matlabcentral/fileexchange/28708-imclipboard
1472 try
1473 error(javachk('awt', 'savefig -clipboard output'));
1474 catch
1475 if ~options.silent
1476 warning('savefig:clipboardJava', 'savefig -clipboard output failed: requires Java to work');
1477 end
1478 return
1479 end
1480 try
1481 % Import necessary Java classes
1482 import java.awt.Toolkit %#ok<SIMPT>
1483 import java.awt.image.BufferedImage %#ok<SIMPT>
1484 import java.awt.datatransfer.DataFlavor %#ok<SIMPT>
1485
1486 % Get System Clipboard object (java.awt.Toolkit)
1487 cb = Toolkit.getDefaultToolkit.getSystemClipboard; % can't use () in ML6!
1488
1489 % Add java class (ImageSelection) to the path
1490 if ~exist('ImageSelection', 'class')
1491 % Obtain the directory of the executable (or of the M-file if not deployed)
1492 %javaaddpath(fileparts(which(mfilename)), '-end');
1493 if isdeployed % Stand-alone mode
1494 [status, result] = system('path'); %#ok<ASGLU>
1495 MatLabFilePath = char(regexpi(result, 'Path=(.*?);', 'tokens', 'once'));
1496 else % MATLAB mode.
1497 MatLabFilePath = fileparts(mfilename('fullpath'));
1498 end
1499 javaaddpath(MatLabFilePath, '-end');
1500 end
1501
1502 % Get image size
1503 ht = size(imageData, 1);
1504 wd = size(imageData, 2);
1505
1506 % Convert to Blue-Green-Red format
1507 try
1508 imageData2 = imageData(:, :, [3 2 1]);
1509 catch
1510 % Probably gray-scaled image (2D, without the 3rd [RGB] dimension)
1511 imageData2 = imageData(:, :, [1 1 1]);
1512 end
1513
1514 % Convert to 3xWxH format
1515 imageData2 = permute(imageData2, [3, 2, 1]);
1516
1517 % Append Alpha data (unused - transparency is not supported in clipboard copy)
1518 alphaData2 = uint8(permute(255*alpha,[3,2,1])); %=255*ones(1,wd,ht,'uint8')
1519 imageData2 = cat(1, imageData2, alphaData2);
1520
1521 % Create image buffer
1522 % Note: contrary to print2array & screencapture, which convert
1523 % ^^^^ a Java screencaptured BufferedImage into RGBA and must
1524 % use TYPE_INT_RGB for this, here we do the reverse and
1525 % must use TYPE_INT_ARGB to preserve the alpha channel.
1526 imBuffer = BufferedImage(wd, ht, BufferedImage.TYPE_INT_ARGB);
1527 imBuffer.setRGB(0, 0, wd, ht, typecast(imageData2(:), 'int32'), 0, wd);
1528
1529 % Create ImageSelection object from the image buffer
1530 imSelection = ImageSelection(imBuffer);
1531
1532 % Set clipboard content to the image
1533 cb.setContents(imSelection, []);
1534 catch
1535 if ~options.silent
1536 warning('savefig:clipboardFailed', 'savefig -clipboard output failed: %s', lasterr); %#ok<LERR>
1537 end
1538 end
1539 else % use one of print()'s builtin clipboard formats
1540 % Remove background if requested (EMF format only)
1541 if options.transparent && strcmpi(options.clipformat,'meta')
1542 % Set figure bgcolor to none
1543 originalBgColor = get(fig, 'Color');
1544 set(fig,'Color','none');
1545
1546 % Set axes bgcolor to none
1547 hAxes = findall(fig, 'Type','axes');
1548 originalAxColor = get(hAxes, 'Color');
1549 set(hAxes,'Color','none');
1550
1551 drawnow; %repaint before export
1552 end
1553
1554 % Call print() to create the clipboard output
1555 clipformat = ['-d' options.clipformat];
1556 printArgs = {renderer};
1557 if ~isempty(options.resolution)
1558 printArgs{end+1} = sprintf('-r%d', options.resolution);
1559 end
1560 print(fig, '-clipboard', clipformat, printArgs{:});
1561
1562 % Restore the original background color
1563 try set(fig, 'Color',originalBgColor); catch, end
1564 try set(hAxes, 'Color',originalAxColor); catch, end
1565 drawnow;
1566 end
1567 %if options.notify, notify('system clipboard'); end
1568 end
1569
1570 % Delete the output file if unchanged from the default name ('export_fig_out.png')
1571 % and clipboard/toolbar/menubar/contextmenu were requested
1572 if options.clipboard || options.toolbar || options.menubar || options.contextmenu
1573 if strcmpi(options.name,'export_fig_out')
1574 try
1575 fileInfo = dir('export_fig_out.png');
1576 if ~isempty(fileInfo)
1577 timediff = now - fileInfo.datenum;
1578 ONE_SEC = 1/24/60/60;
1579 if timediff < ONE_SEC
1580 delete('export_fig_out.png');
1581 end
1582 end
1583 catch
1584 % never mind...
1585 end
1586 end
1587 end
1588
1589 % Notify user by popup, if -notify option was specified
1590 if options.notify && exported_files > 0
1591 % TODO don't notify when exporting to file just for clipboard output
1592 folder = fileparts(filename);
1593 if isempty(folder), folder = pwd; end
1594 options = {};
1595 if exported_files == 1
1596 msg = ['Exported screenshot image to ' filename];
1597 if ispc, options = {'Open image','Open folder'}; end
1598 else % > 1
1599 msg = sprintf('Exported %d screenshot images to %s', exported_files, folder);
1600 if ispc, options = {'Open folder'}; end
1601 end
1602 answer = questdlg(msg,'Screenshot export',options{:},'OK','OK');
1603 drawnow; pause(0.05); % avoid MATLAB hang
1604 switch answer
1605 case 'Open image', winopen(filename);
1606 case 'Open folder', winopen(folder);
1607 otherwise % do nothing
1608 end
1609 end
1610
1611 % Don't output the data to console unless requested
1612 if ~nargout
1613 clear imageData alpha
1614 end
1615
1616 % Revert warnings state
1617 warning(oldWarn);
1618 catch err
1619 % Revert warnings state
1620 warning(oldWarn);
1621 % Revert figure properties in case they were changed
1622 try set(fig,'Units',oldFigUnits, 'Position',pos, 'Color',tcol_orig); catch, end
1623 % Display possible workarounds before the error message
1624 if ~isempty(regexpi(err.message,'setopacityalpha')) %#ok<RGXPI>
1625 % Alert the user that transparency is not supported (issue #285)
1626 try
1627 [unused, msg] = ghostscript('-v'); %#ok<ASGLU>
1628 verStr = regexprep(msg, '.*hostscript ([\d.]+).*', '$1');
1629 if isempty(verStr) || any(verStr==' ')
1630 verStr = '';
1631 else
1632 verStr = [' (' verStr ')'];
1633 end
1634 catch
1635 verStr = '';
1636 end
1637 url = 'https://github.com/altmany/savefig/issues/285#issuecomment-815008561';
1638 urlStr = hyperlink(url,'details');
1639 errMsg = sprintf('Transparancy is not supported by your savefig (%s) and Ghostscript%s versions. \nInstall GS version 9.28 or earlier to use transparency (%s).', num2str(currentVersion), verStr, urlStr);
1640 %fprintf(2,'%s\n',errMsg);
1641 error('savefig:setopacityalpha',errMsg) %#ok<SPERR>
1642 elseif displaySuggestedWorkarounds && ~strcmpi(err.message,'savefig error')
1643 isNewerVersionAvailable = checkForNewerVersion(currentVersion); % alert if a newer version exists
1644 if isempty(regexpi(err.message,'Ghostscript')) %#ok<RGXPI>
1645 fprintf(2, 'savefig error. ');
1646 end
1647 fprintf(2, 'Please ensure:\n');
1648 %if ~isdeployed
1649 fprintf(2, ' * that the function you used (%s.m) version %s is from the expected location\n', mfilename('fullpath'), num2str(currentVersion));
1650 paths = which(mfilename,'-all');
1651 if iscell(paths) && numel(paths) > 1
1652 fprintf(2, ' (you appear to have %s of savefig installed)\n', hyperlink('matlab:which savefig -all','multiple versions'));
1653 end
1654 %end
1655 if isNewerVersionAvailable
1656 fprintf(2, ' * and that you are using the %s of savefig (you are not: run %s to update it)\n', ...
1657 hyperlink('https://github.com/altmany/savefig/archive/master.zip','latest version'), ...
1658 hyperlink('matlab:savefig(''-update'')','savefig(''-update'')'));
1659 end
1660 fprintf(2, ' * and that you did not made a mistake in savefig''s %s\n', hyperlink('matlab:help savefig','expected input arguments'));
1661 if isvector(options) % EPS/PDF require ghostscipt
1662 if ismac
1663 url = 'http://pages.uoregon.edu/koch';
1664 else
1665 url = 'http://ghostscript.com';
1666 end
1667 fpath = user_string('ghostscript');
1668 fpath_link = fpath;
1669 if ispc % winopen only works on Windows
1670 fpath_link = hyperlink(['matlab:winopen(''' fileparts(fpath) ''')'], fpath);
1671 end
1672 fprintf(2, ' * and that %s is properly installed in %s\n', ...
1673 hyperlink(url,'ghostscript'), fpath_link);
1674 if isempty(strtrim(char(fpath)))
1675 selectUtilityPath('Ghostscript',url,'Exporting to vector format (EPS, PDF etc.)');
1676 return
1677 end
1678 end
1679 try % EPS require pdftops
1680 if options.eps
1681 url = 'http://xpdfreader.com/download.html';
1682 fpath = user_string('pdftops');
1683 fpath_link = fpath;
1684 if ispc % winopen only works on Windows
1685 fpath_link = hyperlink(['matlab:winopen(''' fileparts(fpath) ''')'], fpath);
1686 end
1687 fprintf(2, ' * and that %s is properly installed in %s\n', ...
1688 hyperlink(url,'pdftops'), fpath_link);
1689 if isempty(fpath)
1690 selectUtilityPath('pdftops',url,'Exporting to EPS format');
1691 return
1692 end
1693 end
1694 catch
1695 % ignore - probably an error in parse_args
1696 end
1697 try
1698 % Alert per issue #149
1699 if ~strncmpi(get(0,'Units'),'pixel',5)
1700 fprintf(2, ' * or try to set groot''s Units property back to its default value of ''pixels'' (%s)\n', hyperlink('https://github.com/altmany/savefig/issues/149','details'));
1701 end
1702 catch
1703 % ignore - maybe an old MAtlab release
1704 end
1705 fprintf(2, '\nIf the problem persists, then please %s.\n', hyperlink('https://github.com/altmany/savefig/issues','report a new issue'));
1706 if existFile(tmp_nam)
1707 fprintf(2, 'In your report, please upload the problematic EPS file: %s (you can then delete this file).\n', tmp_nam);
1708 end
1709 fprintf(2, '\n');
1710 end
1711 rethrow(err)
1712 end
1713
1714 %> \cond excluded
1715 % Notify user about a successful file export
1716 function notify(filename)
1717 fprintf('Exported screenshot image to %s\n', filename)
1718 exported_files = exported_files + 1;
1719 end
1720 %> \endcond
1721end
1722
1723%> \cond excluded
1724
1725function isOk = selectUtilityPath(utilName,url,msg)
1726 isOk = false;
1727 msg = [msg ' requires the ' utilName ' utility from ' url];
1728 fprintf(2,'\n%s\n',msg);
1729 while ~isOk
1730 answer = questdlg(msg,utilName,'Use local installation','Go to website','Cancel','Cancel');
1731 drawnow; pause(0.01); % avoid MATLAB hang
1732 switch strtok(char(answer))
1733 case 'Go', web(url,'-browser');
1734 case 'Use'
1735 filter = {'*.*','Executable files'};
1736 title = ['Specify the ' utilName ' executable'];
1737 [fPath,fName,fExt] = uigetfile(filter,title);
1738 if ~ischar(fPath), return, end
1739 fName = fullfile(fPath,[fName,fExt]);
1740 isOk = exist(fName,'file') && user_string('ghostscript',fName);
1741 otherwise, return
1742 end
1743 end
1744end
1745
1746function options = default_options()
1747 % Default options used by savefig
1748 options = struct(...
1749 'name', '', ...
1750 'crop', true, ...
1751 'crop_amounts', nan(1,4), ... % auto-crop all 4 image sides
1752 'transparent', false, ...
1753 'renderer', 0, ... % 0: default, 1: OpenGL, 2: ZBuffer, 3: Painters
1754 'pdf', false, ...
1755 'eps', false, ...
1756 'emf', false, ...
1757 'svg', false, ...
1758 'png', false, ...
1759 'tif', false, ...
1760 'jpg', false, ...
1761 'bmp', false, ...
1762 'gif', false, ...
1763 'clipboard', false, ...
1764 'clipformat', 'image', ...
1765 'colourspace', 0, ... % 0: RGB/gray, 1: CMYK, 2: gray
1766 'append', false, ...
1767 'im', false, ...
1768 'alpha', false, ...
1769 'aa_factor', 0, ...
1770 'bb_padding', 0, ...
1771 'magnify', [], ...
1772 'resolution', [], ...
1773 'bookmark', false, ...
1774 'closeFig', false, ...
1775 'quality', [], ...
1776 'update', false, ...
1777 'version', false, ...
1778 'fontswap', true, ...
1779 'font_space', '', ...
1780 'linecaps', false, ...
1781 'invert_hardcopy', true, ...
1782 'format_options', struct, ...
1783 'preserve_size', false, ...
1784 'silent', false, ...
1785 'notify', false, ...
1786 'regexprep', [], ...
1787 'toolbar', false, ...
1788 'menubar', false, ...
1789 'contextmenu', false, ...
1790 'min_size', [], ...
1791 'max_size', [], ...
1792 'fixed_size', [], ...
1793 'gs_options', {{}}, ...
1794 'propagatedOpts', {{}});
1795end
1796
1797function [fig, options] = parse_args(nout, fig, argNames, varargin)
1798 % Parse the input arguments
1799
1800 % Convert strings => chars
1801 varargin = cellfun(@str2char,varargin,'un',false);
1802
1803 % Set the defaults
1804 native = false; % Set resolution to native of an image
1805 defaultOptions = default_options();
1806 options = defaultOptions;
1807 options.im = (nout == 1); % user requested imageData output
1808 options.alpha = (nout == 2); % user requested alpha output
1809 options.handleName = ''; % default handle name
1810 wasOutputRequested = false;
1811
1812 % Go through the other arguments
1813 skipNext = 0;
1814 for a = 1:nargin-3 % only process varargin, no other parse_args() arguments
1815 if skipNext > 0
1816 skipNext = skipNext-1;
1817 continue;
1818 end
1819 thisArg = varargin{a};
1820 if isempty(thisArg) % skip empty args
1821 continue
1822 elseif ~ischar(thisArg)
1823 if ~all(ishandle(thisArg))
1824 error('savefig:InvalidHandle','invalid figure handle specified to savefig');
1825 else
1826 fig = thisArg;
1827 options.handleName = argNames{a};
1828 end
1829 else %if ischar(thisArg) && ~isempty(thisArg)
1830 if thisArg(1) == '-'
1831 addToOptionsStr = true;
1832 switch lower(thisArg(2:end))
1833 case 'nocrop'
1834 options.crop = false;
1835 options.crop_amounts = [0,0,0,0];
1836 case {'trans', 'transparent'}
1837 options.transparent = true;
1838 case 'opengl'
1839 options.renderer = 1;
1840 case 'zbuffer'
1841 options.renderer = 2;
1842 case 'painters'
1843 options.renderer = 3;
1844 case 'pdf'
1845 options.pdf = true;
1846 addToOptionsStr = false;
1847 wasOutputRequested = true;
1848 case 'eps'
1849 options.eps = true;
1850 addToOptionsStr = false;
1851 wasOutputRequested = true;
1852 case {'emf','meta'}
1853 options.emf = true;
1854 addToOptionsStr = false;
1855 wasOutputRequested = true;
1856 case 'svg'
1857 options.svg = true;
1858 addToOptionsStr = false;
1859 wasOutputRequested = true;
1860 case 'png'
1861 options.png = true;
1862 addToOptionsStr = false;
1863 wasOutputRequested = true;
1864 case {'tif', 'tiff'}
1865 options.tif = true;
1866 addToOptionsStr = false;
1867 wasOutputRequested = true;
1868 case {'jpg', 'jpeg'}
1869 options.jpg = true;
1870 addToOptionsStr = false;
1871 wasOutputRequested = true;
1872 case 'bmp'
1873 options.bmp = true;
1874 addToOptionsStr = false;
1875 wasOutputRequested = true;
1876 case 'gif'
1877 options.gif = true;
1878 addToOptionsStr = false;
1879 wasOutputRequested = true;
1880 case 'rgb'
1881 options.colourspace = 0;
1882 case 'cmyk'
1883 options.colourspace = 1;
1884 case {'gray', 'grey'}
1885 options.colourspace = 2;
1886 case {'a1', 'a2', 'a3', 'a4'}
1887 options.aa_factor = str2double(thisArg(3));
1888 case 'append'
1889 options.append = true;
1890 case 'bookmark'
1891 options.bookmark = true;
1892 case 'native'
1893 native = true;
1894 case {'clipboard','clipboard:image'}
1895 options.clipboard = true;
1896 options.clipformat = 'image';
1897 options.im = true; %ensure that imageData is created
1898 options.alpha = true;
1899 addToOptionsStr = false;
1900 case 'clipboard:bitmap'
1901 options.clipboard = true;
1902 options.clipformat = 'bitmap';
1903 addToOptionsStr = false;
1904 case {'clipboard:emf','clipboard:meta'}
1905 options.clipboard = true;
1906 options.clipformat = 'meta';
1907 addToOptionsStr = false;
1908 case 'clipboard:pdf'
1909 options.clipboard = true;
1910 options.clipformat = 'pdf';
1911 addToOptionsStr = false;
1912 case 'update'
1913 warning('The update feature of the savefig package is deliberately disabled in the ParaMonte library.');
1914 %updateInstalledVersion();
1915 fig = -1; % silent bail-out
1916 addToOptionsStr = false;
1917 case 'version'
1918 options.version = true;
1919 return % ignore any additional args
1920 case 'nofontswap'
1921 options.fontswap = false;
1922 case 'font_space'
1923 options.font_space = varargin{a+1};
1924 skipNext = 1;
1925 case 'linecaps'
1926 options.linecaps = true;
1927 case 'noinvert'
1928 options.invert_hardcopy = false;
1929 case 'preserve_size'
1930 options.preserve_size = true;
1931 case 'options'
1932 % Issue #269: format-specific options
1933 inputOptions = varargin{a+1};
1934 %options.format_options = inputOptions;
1935 if isempty(inputOptions), continue, end
1936 if iscell(inputOptions)
1937 fields = inputOptions(1:2:end)';
1938 values = inputOptions(2:2:end)';
1939 options.format_options.all = cell2struct(values, fields);
1940 elseif isstruct(inputOptions)
1941 formats = fieldnames(inputOptions(1));
1942 for idx = 1 : numel(formats)
1943 optionsStruct = inputOptions.(formats{idx});
1944 %optionsCells = [fieldnames(optionsStruct) struct2cell(optionsStruct)]';
1945 formatName = regexprep(lower(formats{idx}),{'tiff','jpeg'},{'tif','jpg'});
1946 options.format_options.(formatName) = optionsStruct; %=optionsCells(:)';
1947 end
1948 else
1949 warning('savefig:options','savefig -options argument is not in the expected format - ignored');
1950 end
1951 skipNext = 1;
1952 case 'silent'
1953 options.silent = true;
1954 case 'notify'
1955 options.notify = true;
1956 case 'regexprep'
1957 options.regexprep = varargin(a+1:a+2);
1958 skipNext = 2;
1959 case 'toolbar'
1960 options.toolbar = true;
1961 addToOptionsStr = false;
1962 case 'menubar'
1963 options.menubar = true;
1964 addToOptionsStr = false;
1965 case 'contextmenu'
1966 options.contextmenu = true;
1967 addToOptionsStr = false;
1968 case 'metadata'
1969 % https://unix.stackexchange.com/questions/489230/where-is-metadata-for-pdf-files-can-i-insert-metadata-into-any-pdf-file
1970 % https://www.sejda.com/edit-pdf-metadata
1971 metadata = varargin{a+1};
1972 if isstruct(metadata)
1973 metadata = [fieldnames(metadata),struct2cell(metadata)]';
1974 elseif ~iscell(metadata) || ~ischar(metadata{1}) || mod(length(metadata),2)==1
1975 error('savefig:BadOptionValue','savefig metadata must be a struct or cell-array of name-value pairs');
1976 end
1977 metadata = cellfun(@num2str,metadata(:)','uniform',0);
1978 str = sprintf(' /%s (%s)', metadata{:});
1979 options.gs_options{end+1} = ['-c "[' str ' /DOCINFO pdfmark"'];
1980 skipNext = 1;
1981 otherwise
1982 try
1983 wasError = false;
1984 if strcmpi(thisArg(1:2),'-d')
1985 thisArg(2) = 'd'; % ensure lowercase 'd'
1986 options.gs_options{end+1} = thisArg;
1987 elseif strcmpi(thisArg(1:2),'-c')
1988 if strncmpi(thisArg,'-clipboard:',11)
1989 wasError = true;
1990 error('savefig:BadOptionValue','option ''%s'' cannot be parsed: only image, bitmap, emf and pdf formats are supported',thisArg);
1991 end
1992 if numel(thisArg)==2
1993 skipNext = 1;
1994 vals = str2num(varargin{a+1}); %#ok<ST2NM>
1995 else
1996 vals = str2num(thisArg(3:end)); %#ok<ST2NM>
1997 end
1998 if numel(vals)~=4
1999 wasError = true;
2000 error('savefig:BadOptionValue','option -c cannot be parsed: must be a 4-element numeric vector');
2001 end
2002 options.crop_amounts = vals;
2003 options.crop = true;
2004 elseif thisArg(1)=='-' && any(thisArg(2)=='nNxXsS') %issue #315
2005 if numel(thisArg)==2
2006 skipNext = 1;
2007 valsStr = varargin{a+1};
2008 else
2009 valsStr = thisArg(3:end);
2010 end
2011 numVals = sum(valsStr==',');
2012 if any(valsStr=='-')
2013 wasError = true;
2014 error('savefig:BadOptionValue','option -%s must be a positive scalar value or 2-value vector',thisArg(2));
2015 elseif numVals > 1
2016 wasError = true;
2017 error('savefig:BadOptionValue','option -%s must be a scalar value or 2-value vector',thisArg(2));
2018 elseif numVals == 0 % -s100 => -s100,100
2019 valsStr = [valsStr ',' valsStr]; %#ok<AGROW>
2020 end
2021 valsStr = regexprep(valsStr,'([\d\.]*)%','-$1'); % -s95%,100 => [-95,100]
2022 vals = str2num(valsStr); %#ok<ST2NM>
2023 if any(vals < 0) && lower(thisArg(2)) ~= 's'
2024 wasError = true;
2025 error('savefig:BadOptionValue','option -%s cannot use percentage values',thisArg(2));
2026 end
2027 switch lower(thisArg(2))
2028 case 'n', options.min_size = vals;
2029 case 'x', options.max_size = vals;
2030 case 's', options.fixed_size = vals;
2031 end
2032 else % scalar parameter value
2033 val = str2double(regexpi(thisArg, '(?<=-([mrqp]))-?\d*.?\d+', 'match'));
2034 if isempty(val) || isnan(val)
2035 % Issue #51: improved processing of input args (accept space between param name & value)
2036 val = str2double(varargin{a+1});
2037 if isscalar(val) && ~isnan(val)
2038 skipNext = 1;
2039 end
2040 end
2041 if ~isscalar(val) || isnan(val)
2042 wasError = true;
2043 error('savefig:BadOptionValue','option %s is not recognised or cannot be parsed', thisArg);
2044 end
2045 switch lower(thisArg(2))
2046 case 'm'
2047 % Magnification may never be negative
2048 if val <= 0
2049 wasError = true;
2050 error('savefig:BadMagnification','Bad magnification value: %g (must be positive)', val);
2051 end
2052 options.magnify = val;
2053 case 'r'
2054 options.resolution = val;
2055 case 'q'
2056 options.quality = max(val, 0);
2057 case 'p'
2058 options.bb_padding = val;
2059 end
2060 end
2061 catch err
2062 % We might have reached here by raising an intentional error
2063 if wasError % intentional raise
2064 rethrow(err)
2065 else % unintentional
2066 error('savefig:BadOption',['Unrecognized savefig input option: ''' thisArg '''']);
2067 end
2068 end
2069 end
2070 if addToOptionsStr
2071 options.propagatedOpts{end+1} = thisArg;
2072 if skipNext
2073 options.propagatedOpts{end+1} = varargin{a+1};
2074 end
2075 end
2076 else
2077 % test for case of figure name rather than export filename
2078 isFigName = false;
2079 if isempty(options.handleName)
2080 try
2081 if strncmpi(thisArg,'Figure',6)
2082 [~,~,~,~,e] = regexp(thisArg,'figure\s*(\d+)\s*(:\s*(.*))?','ignorecase');
2083 figNumber = str2double(e{1}{1});
2084 figName = regexprep(e{1}{2},':\s*','');
2085 findProps = {'Number',figNumber};
2086 if ~isempty(figName)
2087 findProps = [findProps,'Name',figName]; %#ok<AGROW>
2088 end
2089 else
2090 findProps = {'Name',thisArg};
2091 end
2092 possibleFig = findall(0,'-depth',1,'Type','figure',findProps{:});
2093 if ~isempty(possibleFig)
2094 fig = possibleFig(1); % return the 1st figure found
2095 if ~strcmpi(options.name, defaultOptions.name)
2096 continue % export fname was already specified
2097 else
2098 isFigName = true; %use figure name as export fname
2099 end
2100 end
2101 catch
2102 % ignore - treat as export filename, not figure name
2103 end
2104 end
2105 % parse the input as a filename, alert if requested folder does not exist
2106 [p, options.name, ext] = fileparts(thisArg);
2107 if ~isempty(p) % export folder name/path was specified
2108 % Issue #221: alert if the requested folder does not exist
2109 if exist(p,'dir')
2110 options.name = fullfile(p, options.name); %without ext
2111 elseif ~isFigName
2112 error('savefig:BadPath','Folder %s does not exist, nor is it the name of any active figure!',p);
2113 else % isFigName
2114 % specified a figure name so ignore the bad folder part
2115 end
2116 end
2117 switch lower(ext(2:end))
2118 case {'tif', 'tiff','jpg', 'jpeg','png','bmp','eps','emf','pdf','svg','gif'}
2119 options = setOptionsFormat(options, ext);
2120 wasOutputRequested = true;
2121 case '.fig'
2122 % If no open figure, then load the specified .fig file and continue
2123 figFilename = thisArg;
2124 if isempty(fig)
2125 fig = openfig(figFilename,'invisible');
2126 %varargin{a} = fig;
2127 options.closeFig = true;
2128 options.handleName = ['openfig(''' figFilename ''')'];
2129 else
2130 % save the current figure as the specified .fig file and exit
2131 saveas(fig(1),figFilename);
2132 fig = -1;
2133 return
2134 end
2135 otherwise
2136 options.name = thisArg;
2137 wasOutputRequested = true;
2138 end
2139 end
2140 end
2141 end
2142
2143 % Quick bail-out if no figure found
2144 if isempty(fig), return; end
2145
2146 % Do border padding with repsect to a cropped image
2147 if options.bb_padding
2148 options.crop = true;
2149 end
2150
2151 % Set default anti-aliasing now we know the renderer
2152 try isAA = strcmp(get(ancestor(fig, 'figure'), 'GraphicsSmoothing'), 'on'); catch, isAA = false; end
2153 if isAA
2154 if options.aa_factor > 1 && ~options.silent
2155 warning('savefig:AntiAliasing','You requested anti-aliased savefig output of a figure that is already anti-aliased - your -a option in savefig is ignored.')
2156 end
2157 options.aa_factor = 1; % ignore -a option when the figure is already anti-aliased (HG2)
2158 elseif options.aa_factor == 0 % default
2159 %options.aa_factor = 1 + 2 * (~(using_hg2(fig) && isAA) | (options.renderer == 3));
2160 options.aa_factor = 1 + 2 * (~using_hg2(fig)); % =1 in HG2, =3 in HG1
2161 end
2162 if options.aa_factor > 1 && ~isAA && using_hg2(fig) && ~options.silent
2163 warning('savefig:AntiAliasing','You requested anti-aliased savefig output of an aliased figure (''GraphicsSmoothing''=''off''). You will see better results if you set your figure''s GraphicsSmoothing property to ''on'' before calling savefig.')
2164 end
2165
2166 % Use the figure's FileName property as the default export filename
2167 if isempty(options.name)
2168 options.name = get(ancestor(fig(1),'figure'),'FileName'); %fix issue #372
2169 options.name = char(options.name); %fix issue #381
2170 options.name = regexprep(options.name,'[*?"<>|]+','-'); %remove illegal filename chars, but not folder seperators!
2171 if isempty(options.name)
2172 % No FileName property specified for the figure, use 'export_fig_out'
2173 options.name = 'export_fig_out';
2174 elseif wasOutputRequested % if no output requested, don't force it!
2175 % Ensure the filepath is valid
2176 [p, options.name, ext] = fileparts(options.name);
2177 options = setOptionsFormat(options, ext);
2178 if ~isempty(p) % export folder name/path was specified
2179 if exist(p,'dir')
2180 options.name = fullfile(p, options.name); %without ext
2181 else % only warn, don't error
2182 warning('savefig:BadPath','Folder %s does not exist - exporting %s%s to current folder',p,options.name,ext);
2183 end
2184 end
2185 end
2186 end
2187
2188 % Convert user dir '~' to full path
2189 if numel(options.name) > 2 && options.name(1) == '~' && (options.name(2) == '/' || options.name(2) == '\')
2190 options.name = fullfile(char(java.lang.System.getProperty('user.home')), options.name(2:end));
2191 end
2192
2193 % Compute the magnification and resolution
2194 if isempty(options.magnify)
2195 if isempty(options.resolution)
2196 options.magnify = 1;
2197 options.resolution = 864;
2198 else
2199 options.magnify = options.resolution ./ get(0, 'ScreenPixelsPerInch');
2200 end
2201 elseif isempty(options.resolution)
2202 options.resolution = 864;
2203 end
2204
2205 % Set the format to PNG, if no other format was specified but filename provided
2206 if ~isvector(options) && ~isbitmap(options) && ~options.svg && ~options.emf
2207 if wasOutputRequested
2208 options.png = true;
2209 end
2210 end
2211
2212 % Check whether transparent background is wanted (old way)
2213 if isequal(get(ancestor(fig(1), 'figure'), 'Color'), 'none')
2214 options.transparent = true;
2215 end
2216
2217 % If requested, set the resolution to the native vertical resolution of the
2218 % first suitable image found
2219 if native
2220 if isbitmap(options)
2221 % Find a suitable image
2222 list = findall(fig, 'Type','image', 'Tag','export_fig_native');
2223 if isempty(list)
2224 list = findall(fig, 'Type','image', 'Visible','on');
2225 end
2226 for hIm = list(:)'
2227 % Check height is >= 2
2228 height = size(get(hIm, 'CData'), 1);
2229 if height < 2
2230 continue
2231 end
2232 % Account for the image filling only part of the axes, or vice versa
2233 yl = get(hIm, 'YData');
2234 if isscalar(yl)
2235 yl = [yl(1)-0.5 yl(1)+height+0.5];
2236 else
2237 yl = [min(yl), max(yl)]; % fix issue #151 (case of yl containing more than 2 elements)
2238 if ~diff(yl)
2239 continue
2240 end
2241 yl = yl + [-0.5 0.5] * (diff(yl) / (height - 1));
2242 end
2243 hAx = get(hIm, 'Parent');
2244 yl2 = get(hAx, 'YLim');
2245 % Find the pixel height of the axes
2246 oldUnits = get(hAx, 'Units');
2247 set(hAx, 'Units', 'pixels');
2248 pos = get(hAx, 'Position');
2249 set(hAx, 'Units', oldUnits);
2250 if ~pos(4)
2251 continue
2252 end
2253 % Found a suitable image
2254 % Account for stretch-to-fill being disabled
2255 pbar = get(hAx, 'PlotBoxAspectRatio');
2256 pos = min(pos(4), pbar(2)*pos(3)/pbar(1));
2257 % Set the magnification to give native resolution
2258 options.magnify = abs((height * diff(yl2)) / (pos * diff(yl))); % magnification must never be negative: issue #103
2259 break
2260 end
2261 elseif options.resolution == 864 % don't use -r864 in vector mode if user asked for -native
2262 options.resolution = []; % issue #241 (internal MATLAB bug produces black lines with -r864)
2263 end
2264 end
2265end
2266function options = setOptionsFormat(options, ext)
2267 switch lower(ext(2:end))
2268 case {'tif', 'tiff'}, options.tif = true;
2269 case {'jpg', 'jpeg'}, options.jpg = true;
2270 case 'png', options.png = true;
2271 case 'bmp', options.bmp = true;
2272 case 'eps', options.eps = true;
2273 case 'emf', options.emf = true;
2274 case 'pdf', options.pdf = true;
2275 case 'svg', options.svg = true;
2276 case 'gif', options.gif = true;
2277 otherwise % do nothing
2278 end
2279end
2280
2281% Convert a possible string => char (issue #245)
2282function value = str2char(value)
2283 if isa(value,'string')
2284 value = char(value);
2285 end
2286end
2287
2288function A = downsize(A, factor)
2289 % Downsample an image
2290 if factor <= 1 || isempty(A) %issue #310
2291 % Nothing to do
2292 return
2293 end
2294 try
2295 % Faster, but requires image processing toolbox
2296 A = imresize(A, 1/factor, 'bilinear');
2297 catch
2298 % No image processing toolbox - resize manually
2299 % Lowpass filter - use Gaussian as is separable, so faster
2300 % Compute the 1d Gaussian filter
2301 filt = (-factor-1:factor+1) / (factor * 0.6);
2302 filt = exp(-filt .* filt);
2303 % Normalize the filter
2304 filt = single(filt / sum(filt));
2305 % Filter the image
2306 padding = floor(numel(filt) / 2);
2307 if padding < 1 || isempty(A), return, end %issue #310
2308 onesPad = ones(1, padding);
2309 for a = 1:size(A,3)
2310 A2 = single(A([onesPad 1:end repmat(end,1,padding)], ...
2311 [onesPad 1:end repmat(end,1,padding)], a));
2312 A(:,:,a) = conv2(filt, filt', A2, 'valid');
2313 end
2314 % Subsample
2315 A = A(1+floor(mod(end-1, factor)/2):factor:end,1+floor(mod(end-1, factor)/2):factor:end,:);
2316 end
2317end
2318
2319function A = rgb2grey(A)
2320 A = cast(reshape(reshape(single(A), [], 3) * single([0.299; 0.587; 0.114]), size(A, 1), size(A, 2)), class(A)); % #ok<ZEROLIKE>
2321end
2322
2323function A = check_greyscale(A)
2324 % Check if the image is greyscale
2325 if size(A, 3) == 3 && ...
2326 all(reshape(A(:,:,1) == A(:,:,2), [], 1)) && ...
2327 all(reshape(A(:,:,2) == A(:,:,3), [], 1))
2328 A = A(:,:,1); % Save only one channel for 8-bit output
2329 end
2330end
2331
2332function eps_remove_background(fname, count)
2333 % Remove the background of an eps file
2334 % Open the file
2335 fh = fopen(fname, 'r+');
2336 if fh == -1
2337 error('savefig:EPS:open','Cannot open file %s.', fname);
2338 end
2339 % Read the file line by line
2340 while count
2341 % Get the next line
2342 l = fgets(fh);
2343 if isequal(l, -1)
2344 break; % Quit, no rectangle found
2345 end
2346 % Check if the line contains the background rectangle
2347 if isequal(regexp(l, ' *0 +0 +\d+ +\d+ +r[fe] *[\n\r]+', 'start'), 1)
2348 % Set the line to whitespace and quit
2349 l(1:regexp(l, '[\n\r]', 'start', 'once')-1) = ' ';
2350 fseek(fh, -numel(l), 0);
2351 fprintf(fh, l);
2352 % Reduce the count
2353 count = count - 1;
2354 end
2355 end
2356 % Close the file
2357 fclose(fh);
2358end
2359
2360function b = isvector(options) % this only includes EPS-based vector formats (so not SVG,EMF)
2361 b = options.pdf || options.eps;
2362end
2363
2364function b = isbitmap(options)
2365 b = options.png || options.tif || options.jpg || options.bmp || ...
2366 options.gif || options.im || options.alpha;
2367end
2368
2369function [A, tcol, alpha] = getFigImage(fig, magnify, renderer, options, pos)
2370 if options.transparent
2371 % MATLAB "feature": figure size can change when changing color in -nodisplay mode
2372 % Note: figure background is set to off-white, not 'w', to handle common white elements (issue #330)
2373 set(fig, 'Color',254/255*[1,1,1], 'Position',pos);
2374 % repaint figure, otherwise Java screencapture will see black bgcolor
2375 % Yair 19/12/21 - unnecessary: drawnow is called at top of print2array
2376 %drawnow;
2377 end
2378 % Print large version to array
2379 try
2380 % The following code might cause out-of-memory errors
2381 [A, tcol, alpha] = print2array(fig, magnify, renderer);
2382 catch
2383 % This is more conservative in memory, but perhaps kills transparency(?)
2384 [A, tcol, alpha] = print2array(fig, magnify/options.aa_factor, renderer, 'retry');
2385 end
2386 % In transparent mode, set the bgcolor to white
2387 if options.transparent
2388 % Note: tcol should already be [255,255,255] here, but just in case it's not...
2389 tcol = uint8(254*[1,1,1]); %=off-white
2390 end
2391end
2392
2393function A = make_cell(A)
2394 if ~iscell(A)
2395 A = {A};
2396 end
2397end
2398
2399function add_bookmark(fname, bookmark_text)
2400 % Adds a bookmark to the temporary EPS file after %%EndPageSetup
2401 % Read in the file
2402 fh = fopen(fname, 'r');
2403 if fh == -1
2404 error('savefig:bookmark:FileNotFound','File %s not found.', fname);
2405 end
2406 try
2407 fstrm = fread(fh, '*char')';
2408 catch ex
2409 fclose(fh);
2410 rethrow(ex);
2411 end
2412 fclose(fh);
2413
2414 % Include standard pdfmark prolog to maximize compatibility
2415 fstrm = strrep(fstrm, '%%BeginProlog', sprintf('%%%%BeginProlog\n/pdfmark where {pop} {userdict /pdfmark /cleartomark load put} ifelse'));
2416 % Add page bookmark
2417 fstrm = strrep(fstrm, '%%EndPageSetup', sprintf('%%%%EndPageSetup\n[ /Title (%s) /OUT pdfmark',bookmark_text));
2418
2419 % Write out the updated file
2420 fh = fopen(fname, 'w');
2421 if fh == -1
2422 error('savefig:bookmark:permission','Unable to open %s for writing.', fname);
2423 end
2424 try
2425 fwrite(fh, fstrm, 'char*1');
2426 catch ex
2427 fclose(fh);
2428 rethrow(ex);
2429 end
2430 fclose(fh);
2431end
2432
2433function set_manual_axes_modes(Hlims, ax)
2434 % Set the axes limits mode to manual
2435 set(Hlims, [ax 'LimMode'], 'manual');
2436
2437 % Set the tick mode of linear axes to manual
2438 % Leave log axes alone as these are tricky
2439 M = get(Hlims, [ax 'Scale']);
2440 if ~iscell(M)
2441 M = {M};
2442 end
2443 %idx = cellfun(@(c) strcmp(c, 'linear'), M);
2444 idx = find(strcmp(M,'linear'));
2445 %set(Hlims(idx), [ax 'TickMode'], 'manual'); % issue #187
2446 %set(Hlims(idx), [ax 'TickLabelMode'], 'manual'); % this hides exponent label in HG2!
2447 for idx2 = 1 : numel(idx)
2448 try
2449 % Fix for issue #187 - only set manual ticks when no exponent is present
2450 hAxes = Hlims(idx(idx2));
2451 props = {[ax 'TickMode'],'manual', [ax 'TickLabelMode'],'manual'};
2452 tickVals = get(hAxes,[ax 'Tick']);
2453 tickStrs = get(hAxes,[ax 'TickLabel']);
2454 try % TickLabelRotation is available since R2021a
2455 propName = [ax,'TickLabelRotationMode'];
2456 if ~isempty(get(hAxes,propName)) %this will croak in R2020b-
2457 props = [props, propName,'manual']; %#ok<AGROW>
2458 end
2459 catch
2460 % ignore - probably R2020b or older
2461 end
2462 try % Fix issue #236
2463 exponents = [hAxes.([ax 'Axis']).SecondaryLabel];
2464 catch
2465 exponents = [hAxes.([ax 'Ruler']).SecondaryLabel];
2466 end
2467 if isempty([exponents.String])
2468 % Fix for issue #205 - only set manual ticks when the Ticks number match the TickLabels number
2469 if numel(tickVals) == numel(tickStrs)
2470 set(hAxes, props{:}); % no exponent and matching ticks, so update both ticks and tick labels to manual
2471 drawnow % issue #385
2472 end
2473 end
2474 catch % probably HG1
2475 % Fix for issue #220 - exponent is removed in HG1 when TickMode is 'manual' (internal MATLAB bug)
2476 if isequal(tickVals, str2num(tickStrs)') %#ok<ST2NM>
2477 set(hAxes, props{:}); % revert back to old behavior
2478 end
2479 end
2480 end
2481end
2482
2483function change_rgb_to_cmyk(fname) % convert RGB => CMYK within an EPS file
2484 % Do post-processing on the eps file
2485 try
2486 % Read the EPS file into memory
2487 fstrm = read_write_entire_textfile(fname);
2488
2489 % Replace all gray-scale colors
2490 fstrm = regexprep(fstrm, '\n([\d.]+) +GC\n', '\n0 0 0 ${num2str(1-str2num($1))} CC\n');
2491
2492 % Replace all RGB colors
2493 fstrm = regexprep(fstrm, '\n[0.]+ +[0.]+ +[0.]+ +RC\n', '\n0 0 0 1 CC\n'); % pure black
2494 fstrm = regexprep(fstrm, '\n([\d.]+) +([\d.]+) +([\d.]+) +RC\n', '\n${sprintf(''%.4g '',[1-[str2num($1),str2num($2),str2num($3)]/max([str2num($1),str2num($2),str2num($3)]),1-max([str2num($1),str2num($2),str2num($3)])])} CC\n');
2495
2496 % Overwrite the file with the modified contents
2497 read_write_entire_textfile(fname, fstrm);
2498 catch
2499 % never mind - leave as is...
2500 end
2501end
2502
2503function [hBlackAxles, hBlackRulers] = fixBlackAxle(hAxes, axleName)
2504 hBlackAxles = [];
2505 hBlackRulers = [];
2506 for idx = 1 : numel(hAxes)
2507 ax = hAxes(idx);
2508 axleColor = get(ax, axleName);
2509 if isequal(axleColor,[0,0,0]) || isequal(axleColor,'k')
2510 hBlackAxles(end+1) = ax; %#ok<AGROW>
2511 try % Fix issue #306 - black yyaxis
2512 if strcmpi(axleName,'Color'), continue, end %ruler, not axle
2513 rulerName = strrep(axleName,'Color','Axis');
2514 hRulers = get(ax, rulerName);
2515 newBlackRulers = fixBlackAxle(hRulers,'Color');
2516 hBlackRulers = [hBlackRulers newBlackRulers]; %#ok<AGROW>
2517 catch
2518 end
2519 end
2520 end
2521 set(hBlackAxles, axleName, [0,0,0.01]); % off-black
2522end
2523
2524function hText = fixBlackText(hObject, propName)
2525 try
2526 hText = get(hObject, propName);
2527 try hText = [hText{:}]; catch, end %issue #383
2528 for idx = numel(hText) : -1 : 1
2529 hThisText = hText(idx);
2530 try hThisText = hThisText{1}; catch, end
2531 color = get(hThisText,'Color');
2532 if isequal(color,[0,0,0]) || isequal(color,'k')
2533 set(hThisText, 'Color', [0,0,0.01]); %off-black
2534 else
2535 hText(idx) = []; % remove from list
2536 end
2537 end
2538 catch
2539 hText = [];
2540 end
2541end
2542
2543% Issue #269: format-specific options
2544function [optionsCells, bitDepth] = getFormatOptions(options, formatName)
2545 bitDepth = [];
2546 try
2547 optionsStruct = options.format_options.(lower(formatName));
2548 catch
2549 try
2550 % Perhaps user specified the options in cell array format
2551 optionsStruct = options.format_options.all;
2552 catch
2553 % User did not specify any extra parameters for this format
2554 optionsCells = {};
2555 return
2556 end
2557 end
2558 optionNames = fieldnames(optionsStruct);
2559 optionVals = struct2cell(optionsStruct);
2560 optionsCells = [optionNames, optionVals]';
2561 if nargout < 2, return, end % bail out if BitDepth is not required
2562 try
2563 idx = find(strcmpi(optionNames,'BitDepth'), 1, 'last');
2564 if ~isempty(idx)
2565 bitDepth = optionVals{idx};
2566 end
2567 catch
2568 % never mind - ignore
2569 end
2570end
2571
2572% Check for newer version (only once a day)
2573function isNewerVersionAvailable = checkForNewerVersion(currentVersion)
2574 persistent lastCheckTime lastVersion
2575 isNewerVersionAvailable = false;
2576 return; % Amir Shahmoradi: This `return` was added to suppress update checks by `savefig` permanently.
2577 if isdeployed, return, end
2578 if nargin < 1 || isempty(lastCheckTime) || now - lastCheckTime > 1
2579 url = 'https://raw.githubusercontent.com/altmany/savefig/master/savefig.m';
2580 try
2581 str = readURL(url);
2582 [unused,unused,unused,unused,latestVerStrs] = regexp(str, '\n[^:]+: \‍(([^)]+)\‍) ([^%]+)(?=\n)'); %#ok<ASGLU>
2583 latestVersion = str2double(latestVerStrs{end}{1});
2584 if nargin < 1
2585 currentVersion = lastVersion;
2586 else
2587 currentVersion = currentVersion + 1e3*eps;
2588 end
2589 isNewerVersionAvailable = latestVersion > currentVersion;
2590 if isNewerVersionAvailable
2591 try
2592 verStrs = strtrim(reshape([latestVerStrs{:}],2,[]));
2593 verNums = arrayfun(@(c)str2double(c{1}),verStrs(1,:));
2594 isValid = verNums > currentVersion;
2595 versionDesc = strjoin(flip(verStrs(2,isValid)),';');
2596 catch
2597 % Something bad happened - only display the latest version description
2598 versionDesc = latestVerStrs{1}{2};
2599 end
2600 try versionDesc = strjoin(strrep(strcat(' ***', strtrim(strsplit(versionDesc,';'))),'***','* '), char(10)); catch, end %#ok<CHARTEN>
2601 msg = sprintf(['You are using version %.2f of savefig. ' ...
2602 'A newer version (%g) is available, with the following improvements/fixes:\n' ...
2603 '%s\n' ...
2604 'A change-log of recent releases is available here; the complete change-log is included at the top of the savefig.m file.\n' ... % issue #322
2605 'You can download the new version from GitHub or MATLAB File Exchange, ' ...
2606 'or run savefig(''-update'') to install it directly.' ...
2607 ], currentVersion, latestVersion, versionDesc);
2608 msg = hyperlink('https://github.com/altmany/savefig', 'GitHub', msg);
2609 msg = hyperlink('https://www.mathworks.com/matlabcentral/fileexchange/23629-savefig', 'MATLAB File Exchange', msg);
2610 msg = hyperlink('matlab:savefig(''-update'')', 'savefig(''-update'')', msg);
2611 msg = hyperlink('https://github.com/altmany/savefig/releases', 'available here', msg);
2612 msg = hyperlink('https://github.com/altmany/savefig/blob/master/savefig.m#L300', 'savefig.m file', msg);
2613 warning('savefig:version',msg);
2614 end
2615 catch
2616 % ignore
2617 end
2618 lastCheckTime = now;
2619 lastVersion = currentVersion;
2620 end
2621end
2622
2623% Update the installed version of savefig from the latest version online
2624function updateInstalledVersion()
2625 % Download the latest version of savefig into the savefig folder
2626 zipFileName = 'https://github.com/altmany/savefig/archive/master.zip';
2627 fprintf('Downloading latest version of %s from %s...\n', mfilename, zipFileName);
2628 folderName = fileparts(which(mfilename('fullpath')));
2629 targetFileName = fullfile(folderName, datestr(now,'yyyy-mm-dd.zip'));
2630 folder = folderName;
2631 if ispc % winopen only works on Windows
2632 try
2633 folder = hyperlink(['matlab:winopen(''' folderName ''')'], folderName);
2634 catch % hyperlink.m is not properly installed
2635 end
2636 end
2637 try
2638 urlwrite(zipFileName,targetFileName); %#ok<URLWR>
2639 catch err
2640 error('savefig:update:download','Error downloading %s into %s: %s\n',zipFileName,targetFileName,err.message);
2641 end
2642
2643 % Unzip the downloaded zip file in the savefig folder
2644 fprintf('Extracting %s...\n', targetFileName);
2645 try
2646 unzip(targetFileName,folderName);
2647 % Fix issue #302 - zip file uses an internal folder savefig-master
2648 subFolder = fullfile(folderName,'savefig-master');
2649 try movefile(fullfile(subFolder,'*.*'),folderName, 'f'); catch, end %All OSes
2650 try movefile(fullfile(subFolder,'*'), folderName, 'f'); catch, end %MacOS/Unix
2651 try movefile(fullfile(subFolder,'.*'), folderName, 'f'); catch, end %MacOS/Unix
2652 try rmdir(subFolder); catch, end
2653 catch err
2654 error('savefig:update:unzip','Error unzipping %s: %s\n',targetFileName,err.message);
2655 end
2656
2657 % Notify the user and rehash
2658 fprintf('Successfully installed the latest %s version in %s\n', mfilename, folder);
2659 clear functions %#ok<CLFUNC>
2660 rehash
2661end
2662
2663% Read a file from the web
2664function str = readURL(url)
2665 try
2666 str = char(webread(url));
2667 catch err %if isempty(which('webread'))
2668 if isempty(strfind(err.message,'404'))
2669 v = version; % '9.6.0.1072779 (R2019a)'
2670 if v(1) >= '8' % '8.0 (R2012b)' https://www.mathworks.com/help/matlab/release-notes.html?rntext=urlread&searchHighlight=urlread&startrelease=R2012b&endrelease=R2012b
2671 str = urlread(url, 'Timeout',5); %#ok<URLRD>
2672 else
2673 str = urlread(url); %#ok<URLRD> % R2012a or older (no Timeout parameter)
2674 end
2675 else
2676 rethrow(err)
2677 end
2678 end
2679 if size(str,1) > 1 % ensure a row-wise string
2680 str = str';
2681 end
2682end
2683
2684% Display a promo message in the MATLAB console
2685function displayPromoMsg(msg, url)
2686 %msg = [msg url];
2687 msg = strrep(msg,'<$>',url);
2688 link = ['<a href="' url];
2689 msg = regexprep(msg,url,[link '">$0</a>']);
2690 %msg = regexprep(msg,{'consulting','training'},[link '/$0">$0</a>']);
2691 %warning('savefig:promo',msg);
2692 disp(['[' 8 msg ']' 8]);
2693end
2694
2695% Cross-check existance of other programs
2696function programsCrossCheck()
2697 if isdeployed, return, end % don't check in deployed mode
2698 try
2699 % IQ
2700 hasTaskList = false;
2701 if ispc && ~exist('IQML','file')
2702 hasIQ = exist('C:/Progra~1/DTN/IQFeed','dir') || ...
2703 exist('C:/Progra~2/DTN/IQFeed','dir');
2704 if ~hasIQ
2705 [status,tasksStr] = system('tasklist'); %#ok<ASGLU>
2706 tasksStr = lower(tasksStr);
2707 hasIQ = ~isempty(strfind(tasksStr,'iqconnect')) || ...
2708 ~isempty(strfind(tasksStr,'iqlink')); %#ok<STREMP>
2709 hasTaskList = true;
2710 end
2711 if hasIQ
2712 displayPromoMsg('To connect MATLAB to IQFeed, try the IQML connector <$>', 'https://UndocumentedMatlab.com/IQML');
2713 end
2714 end
2715
2716 % IB
2717 if ~exist('IBMatlab','file')
2718 hasIB = false;
2719 possibleFolders = {'C:/Jts','C:/Programs/Jts','C:/Progra~1/Jts','C:/Progra~2/Jts','~/IBJts','~/IBJts/IBJts'};
2720 for folderIdx = 1 : length(possibleFolders)
2721 if exist(possibleFolders{folderIdx},'dir')
2722 hasIB = true;
2723 break
2724 end
2725 end
2726 if ~hasIB
2727 if ~hasTaskList
2728 if ispc % Windows
2729 [status,tasksStr] = system('tasklist'); %#ok<ASGLU>
2730 else % Linux/MacOS
2731 [status,tasksStr] = system('ps -e'); %#ok<ASGLU>
2732 end
2733 tasksStr = lower(tasksStr);
2734 end
2735 hasIB = ~isempty(strfind(tasksStr,'tws')) || ...
2736 ~isempty(strfind(tasksStr,'ibgateway')); %#ok<STREMP>
2737 end
2738 if hasIB
2739 displayPromoMsg('To connect MATLAB to IB try the IB-MATLAB connector <$>', 'https://UndocumentedMatlab.com/IB-MATLAB');
2740 end
2741 end
2742 catch
2743 % never mind - ignore error
2744 end
2745end
2746
2747% Hint to users to use exportgraphics/copygraphics in certain cases
2748function alertForExportOrCopygraphics(options)
2749 %matlabVerNum = str2num(regexprep(version,'(\d+\.\d+).*','$1'));
2750 if isdeployed, return, end % don't check in deployed mode
2751 try
2752 % Bail out on R2019b- (copygraphics/exportgraphics not available/reliable)
2753 if verLessThan('matlab','9.8') % 9.8 = R2020a
2754 return
2755 end
2756
2757 isNoRendererSpecified = options.renderer == 0;
2758 isPainters = options.renderer == 3;
2759 noResolutionSpecified = isempty(options.resolution) || isequal(options.resolution,864);
2760
2761 % First check for copygraphics compatibility (export to clipboard)
2762 params = ',';
2763 if options.clipboard
2764 if options.transparent % -transparent was requested
2765 if isPainters || isNoRendererSpecified % painters or default renderer
2766 if noResolutionSpecified
2767 params = '''BackgroundColor'',''none'',''ContentType'',''vector'',';
2768 else % exception: no message
2769 params = ',';
2770 end
2771 else % opengl/zbuffer renderer
2772 if options.invert_hardcopy % default
2773 params = '''BackgroundColor'',''white'',';
2774 else % -noinvert was requested
2775 params = '''BackgroundColor'',''current'','; % 'none' is 'white' when ContentType='image'
2776 end
2777 params = [params '''ContentType'',''image'','];
2778 if ~noResolutionSpecified
2779 params = [params '''Resolution'',' num2str(options.resolution) ','];
2780 else
2781 % don't add a resolution param
2782 end
2783 end
2784 else % no -transparent
2785 if options.invert_hardcopy % default
2786 params = '''BackgroundColor'',''white'',';
2787 else % -noinvert was requested
2788 params = '''BackgroundColor'',''current'',';
2789 end
2790 if isPainters % painters (but not default!) renderer
2791 if noResolutionSpecified
2792 params = [params '''ContentType'',''vector'','];
2793 else % exception: no message
2794 params = ',';
2795 end
2796 else % opengl/zbuffer/default renderer
2797 params = [params '''ContentType'',''image'','];
2798 if ~noResolutionSpecified
2799 params = [params '''Resolution'',' num2str(options.resolution) ','];
2800 else
2801 % don't add a resolution param
2802 end
2803 end
2804 end
2805
2806 % If non-RGB colorspace was requested on R2021a+
2807 if ~verLessThan('matlab','9.10') % 9.10 = R2021a
2808 if options.colourspace == 2 % gray
2809 params = [params '''Colorspace'',''gray'','];
2810 end
2811 end
2812 end
2813 displayMsg(params, 'copygraphics', 'clipboard', '');
2814
2815 % Next check for exportgraphics compatibility (export to file)
2816 % Note: not <else>, since -clipboard can be combined with file export
2817 params = ',';
2818 if ~options.clipboard
2819 if options.transparent % -transparent was requested
2820 if isvector(options) % vector output
2821 if isPainters || isNoRendererSpecified % painters or default renderer
2822 if noResolutionSpecified
2823 params = '''BackgroundColor'',''none'',''ContentType'',''vector'',';
2824 else % exception: no message
2825 params = ',';
2826 end
2827 else % opengl/zbuffer renderer
2828 params = '''BackgroundColor'',''none'',''ContentType'',''vector'',';
2829 end
2830 else % non-vector output
2831 params = ',';
2832 end
2833 else % no -transparent
2834 if options.invert_hardcopy % default
2835 params = '''BackgroundColor'',''white'',';
2836 else % -noinvert was requested
2837 params = '''BackgroundColor'',''current'',';
2838 end
2839 if isvector(options) % vector output
2840 if isPainters || isNoRendererSpecified % painters or default renderer
2841 if noResolutionSpecified
2842 params = [params '''ContentType'',''vector'','];
2843 else % exception: no message
2844 params = ',';
2845 end
2846 else % opengl/zbuffer renderer
2847 if noResolutionSpecified
2848 params = [params '''ContentType'',''image'','];
2849 else % exception: no message
2850 params = ',';
2851 end
2852 end
2853 else % non-vector output
2854 if isPainters % painters (but not default!) renderer
2855 % exception: no message
2856 params = ',';
2857 else % opengl/zbuffer/default renderer
2858 if ~noResolutionSpecified
2859 params = [params '''Resolution'',' num2str(options.resolution) ','];
2860 end
2861 end
2862 end
2863 end
2864
2865 % If non-RGB colorspace was requested on R2021a+
2866 if ~verLessThan('matlab','9.10') % 9.10 = R2021a
2867 if options.colourspace == 2 % gray
2868 params = [params '''Colorspace'',''gray'','];
2869 elseif options.colourspace == 1 && options.eps % cmyk (eps only)
2870 params = [params '''Colorspace'',''cmyk'','];
2871 end
2872 end
2873 end
2874 filenameParam = 'filename,'; %=[options.name ','];
2875 displayMsg(params, 'exportgraphics', 'file', filenameParam);
2876 catch
2877 % Ignore errors - do not stop savefig processing
2878 end
2879
2880 % Utility function to display an alert message
2881 function displayMsg(params, funcName, type, filenameParam)
2882 if length(params) > 1
2883 % strip default param values from the message
2884 params = strrep(params, '''BackgroundColor'',''white'',', '');
2885 % strip the trailing ,
2886 if ~isempty(params) && params(end)==',', params(end)=''; end
2887 % if this message was not already displayed
2888 try prevParams = getpref('savefig',funcName); catch, prevParams = ''; end
2889 if ~strcmpi(params, prevParams)
2890 % display the message (TODO: perhaps replace warning() with fprintf()?)
2891 if ~isempty([filenameParam params])
2892 filenameParam = [',' filenameParam];
2893 end
2894 if ~isempty(filenameParam) && filenameParam(end)==',' && isempty(params)
2895 filenameParam(end) = '';
2896 end
2897 handleName = options.handleName;
2898 if isempty(options.handleName) % handle was either not specified, or via gca()/gcf() etc. [i.e. not by variable]
2899 handleName = 'hFigure';
2900 end
2901 msg = ['In MATLAB R2020a+ you can also use ' funcName '(' handleName filenameParam params ') for simple ' type ' export'];
2902 if ~isempty(strfind(params,'''vector''')) %#ok<STREMP>
2903 msg = [msg ', which could also improve image vectorization, solving rasterization/pixelization problems.'];
2904 end
2905 oldWarn = warning('on','verbose');
2906 warning(['savefig:' funcName], msg);
2907 warning(oldWarn);
2908 setpref('savefig',funcName,params);
2909 end
2910 end
2911 end
2912end
2913
2914% Does a file exist?
2915function flag = existFile(filename)
2916 try
2917 % isfile() is faster than exist(), but does not report files on path
2918 flag = isfile(filename);
2919 catch
2920 flag = exist(filename,'file') ~= 0;
2921 end
2922end
2923
2924% Add interactive export button to the figure's toolbar
2925function addToolbarButton(hFig, options)
2926 % Ensure we have a valid toolbar handle
2927 if isempty(hFig) || ~ishandle(hFig)
2928 if options.silent
2929 return
2930 else
2931 error('savefig:noFigure','not a valid GUI handle');
2932 end
2933 end
2934 hToolbar = findall(hFig,'type','uifigure','-or','tag','uitoolbar','-or','tag','FigureToolBar','-depth',1);
2935 % Don't create the figure toolbar - perhaps there's a custom user toolbar
2936 % If there isn't any toolbar, uisplittool() below will create a new one
2937 if isempty(hToolbar)
2938 %{
2939 set(hFig,'ToolBar','figure');
2940 hToolbar = findall(hFig,'tag','uitoolbar','-or','tag','FigureToolBar','-depth',1);
2941 if isempty(hToolbar)
2942 if ~options.silent
2943 warning('savefig:noToolbar','cannot add toolbar button to the specified figure');
2944 end
2945 end
2946 %}
2947 else
2948 hToolbar = hToolbar(1); % just in case there are several toolbars... - use only the first
2949 end
2950
2951 % Bail out silently if the savefig button already exists
2952 hButton = findall(hToolbar, 'Tag','savefig');
2953 if ~isempty(hButton)
2954 return
2955 end
2956
2957 % Prepare the camera icon
2958 icon = ['3333333333333333'; ...
2959 '3333300000033333'; ...
2960 '3333065555603333'; ...
2961 '3000000000000003'; ...
2962 '3022222222222203'; ...
2963 '3022222222222203'; ...
2964 '3022220000222203'; ...
2965 '3022203113022203'; ...
2966 '3022201111022203'; ...
2967 '3022204444022203'; ...
2968 '3022220000222203'; ...
2969 '3022222222222203'; ...
2970 '3022222222222203'; ...
2971 '3000000000000003'; ...
2972 '3333333333333333'; ...
2973 '3333333333333333'];
2974 cm = [ 0 0 0; ... % black
2975 0 0.60 1; ... % light blue
2976 0.53 0.53 0.53; ... % light gray
2977 NaN NaN NaN; ... % transparent
2978 0 0.73 0; ... % light green
2979 0.27 0.27 0.27; ... % gray
2980 0.13 0.13 0.13]; % dark gray
2981 cdata = ind2rgb(uint8(icon-'0'),cm);
2982
2983 % If the button does not already exit
2984 tooltip = 'Export this figure to a pdf or image file';
2985
2986 % Add the button with the icon to the figure's toolbar
2987 props = {'CData',cdata, 'Tag','savefig', ...
2988 'Tooltip',tooltip, 'ClickedCallback',@(h,e)interactiveExport(hFig,options)};
2989 if ~isempty(hToolbar)
2990 props = {props{:}, 'Parent',hToolbar}; %#ok<CCAT> %[props,...] cause a runtime-error! (internal MATLAB bug)
2991 end
2992 try
2993 hButton = []; % just in case we croak below
2994
2995 % Create a new split-button with the open-file button's data
2996 oldWarn = warning('off','MATLAB:uisplittool:DeprecatedFunction');
2997 hButton = uisplittool(props{:});
2998 warning(oldWarn);
2999
3000 % Add the split-button's menu items
3001 drawnow; pause(0.01); % allow the buttom time to render
3002 jButton = get(hButton,'JavaContainer'); %#ok<JAVCT>
3003 jButtonMenu = jButton.getMenuComponent;
3004
3005 tooltip = [tooltip ' (specify filename/format)'];
3006 try jButtonMenu.setToolTipText(tooltip); catch, end
3007 try jButton.getComponentPeer.getComponent(1).setToolTipText(tooltip); catch, end
3008
3009 [folder,defaultFname] = fileparts(get(hFig,'FileName'));
3010 if ~isempty(folder) && exist(folder,'dir')
3011 folder = regexprep(folder,'[/\]$','');
3012 else
3013 folder = pwd;
3014 end
3015 if isempty(defaultFname), defaultFname = get(hFig,'Name'); end
3016 defaultFname = regexprep(defaultFname,'[*?"<>|:/\\]','-'); %remove illegal filename chars
3017 if isempty(defaultFname), defaultFname = 'figure'; end
3018 defaultFname = fullfile(folder,defaultFname);
3019 imFormats = {'pdf','eps','svg','png','jpg','tif','gif','bmp'};
3020 if ispc, imFormats{end+1} = 'emf'; end
3021 for idx = 1 : numel(imFormats)
3022 thisFormat = imFormats{idx};
3023 filename = [defaultFname '.' thisFormat];
3024 label = [upper(thisFormat) ' image file (' filename ')'];
3025 jMenuItem = handle(jButtonMenu.add(label),'CallbackProperties');
3026 callback = @(h,e) savefig(hFig, filename, options.propagatedOpts{:});
3027 set(jMenuItem,'ActionPerformedCallback',callback);
3028 end
3029 jButtonMenu.addSeparator();
3030 if ispc % winopen only works on Windows
3031 label = ['Open export folder: ' folder];
3032 jMenuItem = handle(jButtonMenu.add(label),'CallbackProperties');
3033 set(jMenuItem,'ActionPerformedCallback',@(h,e)winopen(folder));
3034 jButtonMenu.addSeparator();
3035 end
3036 cbFormats = {'image','bitmap','meta','pdf'};
3037 for idx = 1 : numel(cbFormats)
3038 thisFormat = cbFormats{idx};
3039 exFormat = ['-clipboard:' thisFormat];
3040 label = ['Clipboard (' thisFormat ' format)'];
3041 jMenuItem = handle(jButtonMenu.add(label),'CallbackProperties');
3042 callback = @(h,e) savefig(hFig, exFormat, options.propagatedOpts{:});
3043 set(jMenuItem,'ActionPerformedCallback',callback);
3044 end
3045 jButtonMenu.addSeparator();
3046 jMenuItem = handle(jButtonMenu.add('Select file name, location and format'),'CallbackProperties');
3047 set(jMenuItem,'ActionPerformedCallback',@(h,e)interactiveExport(hFig,options));
3048 catch % revert to a simple documented toolbar pushbutton
3049 warning(oldWarn);
3050 if isempty(hButton) %avoid duplicate toolbar buttons (keep the splittool)
3051 hButton = uipushtool(props{:}); %#ok<NASGU>
3052 end
3053 end
3054end
3055
3056% Add interactive export menu to the figure's menubar
3057function addMenubarMenu(hFig, options)
3058 % Ensure we have a valid figure handle
3059 if isempty(hFig) || ~ishandle(hFig)
3060 if options.silent
3061 return
3062 else
3063 error('savefig:noFigure','not a valid GUI handle');
3064 end
3065 end
3066 %set(hFig,'MenuBar','figure'); % Don't create the default figure menubar!
3067
3068 % Bail out silently if the savefig menu already exists
3069 hMainMenu = findall(hFig, '-depth',1, 'type','uimenu', 'Tag','savefig');
3070 if ~isempty(hMainMenu)
3071 return
3072 end
3073
3074 % Add the savefig menu to the figure's menubar
3075 hMainMenu = uimenu(hFig, 'Text','E&xport', 'Tag','savefig');
3076 addMenuItems(hMainMenu, hFig, options);
3077end
3078
3079% Add savefig menu item to a parent menu
3080function addMenuItems(hMainMenu, hFig, options)
3081 [folder,defaultFname] = fileparts(get(hFig,'FileName'));
3082 if ~isempty(folder) && exist(folder,'dir')
3083 folder = regexprep(folder,'[/\]$','');
3084 else
3085 folder = pwd;
3086 end
3087 if isempty(defaultFname), defaultFname = get(hFig,'Name'); end
3088 defaultFname = regexprep(defaultFname,'[*?"<>|:/\\]','-'); %remove illegal filename chars
3089 if isempty(defaultFname), defaultFname = 'figure'; end
3090 defaultFname = fullfile(folder,defaultFname);
3091 imFormats = {'pdf','eps','svg','png','jpg','tif','gif','bmp'};
3092 if ispc, imFormats{end+1} = 'emf'; end
3093 for idx = 1 : numel(imFormats)
3094 thisFormat = imFormats{idx};
3095 filename = [defaultFname '.' thisFormat];
3096 label = [upper(thisFormat) ' image file (' filename ')'];
3097 callback = @(h,e) savefig(hFig, filename, options.propagatedOpts{:});
3098 uimenu(hMainMenu, 'Text',label, 'MenuSelectedFcn',callback);
3099 end
3100 if ispc % winopen only works on Windows
3101 uimenu(hMainMenu, 'Text',['Open export folder: ' folder], 'Separator','on', ...
3102 'MenuSelectedFcn',@(h,e)winopen(folder));
3103 end
3104 cbFormats = {'image','bitmap','meta','pdf'};
3105 for idx = 1 : numel(cbFormats)
3106 thisFormat = cbFormats{idx};
3107 exFormat = ['-clipboard:' thisFormat];
3108 label = ['Clipboard (' thisFormat ' format)'];
3109 sep = 'off'; if idx==1, sep = 'on'; end
3110 callback = @(h,e) savefig(hFig, exFormat, options.propagatedOpts{:});
3111 uimenu(hMainMenu, 'Text',label, 'Separator',sep, ...
3112 'MenuSelectedFcn',callback);
3113 end
3114 uimenu(hMainMenu, 'Text','Select file name, location and format', 'Separator','on', ...
3115 'MenuSelectedFcn',@(h,e)interactiveExport(hFig,options));
3116end
3117
3118% Add interactive export context-menu to the specified figure
3119function addContextMenu(hFig, options)
3120 % Ensure we have a valid figure handle
3121 if isempty(hFig) || ~ishandle(hFig)
3122 if options.silent
3123 return
3124 else
3125 error('savefig:noHandle','not a valid GUI handle');
3126 end
3127 end
3128
3129 % Get the figure's current context-menu (if defined)
3130 % Note: The UIContextMenu property name changed sometime in the late 2010s
3131 try
3132 propName = 'ContextMenu';
3133 cm = get(hFig,propName);
3134 catch
3135 propName = 'UIContextMenu';
3136 cm = get(hFig,propName);
3137 end
3138
3139 % If no context menu is defined, attach the basic savefig one
3140 if isempty(cm)
3141 % Get the standard savefig context menu for this figure
3142 std_cm = findall(hFig, '-depth',1, 'type','uicontextmenu', 'Tag','savefig');
3143 if isempty(std_cm)
3144 % Basic savefig context-menu not yet defined - create it
3145 std_cm = uicontextmenu(hFig,'Tag','savefig');
3146 hMenu = uimenu(std_cm, 'Text','Export', 'Tag','savefig');
3147 addMenuItems(hMenu, hFig, options);
3148 end
3149 %Attach the standard savefig context menu to this figure
3150 set(hFig,propName,std_cm);
3151 else % a context-menu is already defined for this figure
3152 % Ensure that the context-menu doesn't already have an savefig sub-menu
3153 hMenu = findall(cm,'tag','savefig');
3154 if isempty(hMenu)
3155 % Attach the savefig sub-menu to the figure's context-menu
3156 hMenu = uimenu(cm, 'Text','Export', 'Tag','savefig');
3157 addMenuItems(hMenu, hFig, options);
3158 end
3159 end
3160end
3161
3162% Callback functions for toolbar/menubar actions
3163function interactiveExport(hObject, options)
3164 % Get the exported figure handle
3165 hFig = gcbf;
3166 if isempty(hFig)
3167 hFig = ancestor(hObject, 'figure');
3168 end
3169 if isempty(hFig)
3170 return % bail out silently if no figure is available
3171 end
3172
3173 % Display a Save-as dialog to let the user select the export name & type
3174 [folder,defaultFname] = fileparts(get(hFig,'FileName'));
3175 if ~isempty(folder) && exist(folder,'dir')
3176 folder = regexprep(folder,'[/\]$','');
3177 else
3178 folder = pwd;
3179 end
3180 if isempty(defaultFname), defaultFname = get(hFig,'Name'); end
3181 defaultFname = regexprep(defaultFname,'[*?"<>|:/\\]+','-'); %remove illegal filename chars
3182 if isempty(defaultFname), defaultFname = 'figure'; end
3183 defaultFname = fullfile(folder,defaultFname);
3184 %formats = imformats;
3185 formats = {'pdf','eps','svg','png','jpg','tif','gif','bmp'};
3186 if ispc, formats{end+1} = 'emf'; end
3187 formats = [formats,'clipboard:image','clipboard:bitmap','clipboard:meta','clipboard:pdf'];
3188 for idx = 1 : numel(formats)
3189 thisFormat = formats{idx};
3190 ext = sprintf('*.%s',thisFormat);
3191 if ~any(thisFormat==':') % image file format
3192 description = [upper(thisFormat) ' image file (' ext ')'];
3193 format(idx,1:2) = {ext, description}; %#ok<AGROW>
3194 else % clipboard format
3195 description = [strrep(thisFormat,':',' (') ' format *.)'];
3196 format(idx,1:2) = {'*.*', description}; %#ok<AGROW>
3197 end
3198 end
3199 %format
3200 [filename,pathname,idx] = uiputfile(format,'Save figure export as',defaultFname);
3201 drawnow; pause(0.01); % prevent a MATLAB hang
3202 if ~isequal(filename,0)
3203 thisFormat = formats{idx};
3204 if ~any(thisFormat==':') % export to image file
3205 filename = fullfile(pathname,filename);
3206 savefig(hFig, filename, options.propagatedOpts{:});
3207 else % export to clipboard
3208 savefig(hFig, ['-' thisFormat], options.propagatedOpts{:});
3209 end
3210 else
3211 % User canceled the dialog - bail out silently
3212 end
3213end
3214
3215%> \endcond
function name(in vendor)
Return the MPI library name as used in naming the ParaMonte MATLAB shared libraries.
function list()
Return a list of MATLAB strings containing the names of OS platforms supported by the ParaMonte MATLA...
function index(in array)
Given array(1 : n), return the array index indx(1 : n) such that array(indx) is in ascending order.
function version(in silent)
Return a scalar MATLAB string containing the latest available ParaMonte MATLAB version newer than the...
function abs(in path, in style)
Return the Get absolute canonical path of a file or folder.
function str2char(in value)
function choice()
Return the ParaMonte-preferred MPI library vendor name as used in naming the ParaMonte MATLAB shared ...
This is the abstract class for generating instances of objects that contain the specifications of var...
Definition: Figure.m:27
This is the base class for generating subclass of MATLAB handle superclass whose annoying methods are...
Definition: Handle.m:24
function copy(in from, in to, in fields)
Copy the contents of the struct/object from to the struct/object to recursively and without destroyin...
function copyfig(in fh)
function crop_borders(in A, in bcol, in padding, in crop_amounts)
function find(in vendor)
Return a list of scalar MATLAB strings containing the paths to all detected mpiexec binaries installe...
function ghostscript(in cmd)
function href(in url)
Return an HTML-style decoration of the input URL if the ParaMonte MATLAB library is used in GUI,...
function parse_args(in A, in varargin)
function im2gif(in A, in varargin)
function info()
Return a MATLAB string containing the current system information.
function isolate_axes(in ah, in vis)
function map()
Return a scalar MATLAB logical that is true if and only if the current installation of MATLAB contain...
function pdftops(in cmd)
function print2array(in fig, in res, in renderer, in gs_options)
function print2eps(in name, in fig, in export_options, in varargin)
function read_write_entire_textfile(in fname, in fstrm)
function release(in type)
Return a scalar MATLAB string containing the MATLAB release version, year, or season as requested.
function savefig(in varargin)
Export figures in a publication-quality format.
function show(in obj, in name, in hidden)
Display the components of an input MATLAB variable on MATLAB Console recursively.
excluded
Definition: show.m:173
function user_string(in string_name, in string)
function using_hg2(in fig)
function which(in vendor)
Return the a MATLAB string containing the path to the first mpiexec executable binary found in system...