function savefig(fname, varargin) % Usage: savefig(filename, fighdl, options) % % Saves a pdf, eps, png, jpeg, and/or tiff of the contents of the fighandle's (or current) figure. % It saves an eps of the figure and the uses Ghostscript to convert to the other formats. % The result is a cropped, clean picture. There are options for using rgb or cmyk colours, % or grayscale. You can also choose the resolution. % % The advantage of savefig is that there is very little empty space around the figure in the % resulting files, you can export to more than one format at once, and Ghostscript generates % trouble-free files. % % If you find any errors, please let me know! (peder at axensten dot se) % % filename: File name without suffix. % % fighdl: (default: gcf) Integer handle to figure. % % options: (default: '-r300', '-lossless', '-rgb') You can define your own % defaults in a global variable savefig_defaults, if you want to, i.e. % savefig_defaults= {'-r200','-gray'};. % 'eps': Output in Encapsulated Post Script (no preview yet). % 'pdf': Output in (Adobe) Portable Document Format. % 'png': Output in Portable Network Graphics. % 'jpeg': Output in Joint Photographic Experts Group format. % 'tiff': Output in Tagged Image File Format (no compression: huge files!). % '-rgb': Output in rgb colours. % '-cmyk': Output in cmyk colours (not yet 'png' or 'jpeg' -- '-rgb' is used). % '-gray': Output in grayscale (not yet 'eps' -- '-rgb' is used). % '-fonts': Include fonts in eps or pdf. Includes only the subset needed. % '-lossless': Use lossless compression, works on most formats. same as '-c0', below. % '-c': Set compression for non-indexed bitmaps in PDFs - % 0: lossless; 0.1: high quality; 0.5: medium; 1: high compression. % '-r': Set resolution. % '-crop': Removes points and line segments outside the viewing area -- permanently. % Only use this on figures where many points and/or line segments are outside % the area zoomed in to. This option will result in smaller vector files (has no % effect on pixel files). % '-dbg': Displays gs command line(s). % % EXAMPLE: % savefig('nicefig', 'pdf', 'jpeg', '-cmyk', '-c0.1', '-r250'); % Saves the current figure to nicefig.pdf and nicefig.png, both in cmyk and at 250 dpi, % with high quality lossy compression. % % REQUIREMENT: Ghostscript. Version 8.57 works, probably older versions too, but '-dEPSCrop' % must be supported. I think version 7.32 or newer is ok. % % HISTORY: % Version 1.0, 2006-04-20. % Version 1.1, 2006-04-27: % - No 'epstopdf' stuff anymore! Using '-dEPSCrop' option in gs instead! % Version 1.2, 2006-05-02: % - Added a '-dbg' option (see options, above). % - Now looks for a global variable 'savefig_defaults' (see options, above). % - More detailed Ghostscript options (user will not really notice). % - Warns when there is no device for a file-type/color-model combination. % Version 1.3, 2006-06-06: % - Added a check to see if there actually is a figure handle. % - Now works in Matlab 6.5.1 (R13SP1) (maybe in 6.5 too). % - Now compatible with Ghostscript 8.54, released 2006-06-01. % Version 1.4, 2006-07-20: % - Added an option '-soft' that enables anti-aliasing on pixel graphics (on by default). % - Added an option '-hard' that don't do anti-aliasing on pixel graphics. % Version 1.5, 2006-07-27: % - Fixed a bug when calling with a figure handle argument. % Version 1.6, 2006-07-28: % - Added a crop option, see above. % Version 1.7, 2007-03-31: % - Fixed bug: calling print with invalid renderer value '-none'. % - Removed GhostScript argument '-dUseCIEColor' as it sometimes discoloured things. % Version 1.8, 2008-01-03: % - Added MacIntel: 'MACI'. % - Added 64bit PC (I think, can't test it myself). % - Added option '-nointerpolate' (use it to prevent blurring of pixelated). % - Removed '-hard' and '-soft'. Use '-nointerpolate' for '-hard', default for '-soft'. % - Fixed the gs 8.57 warning on UseCIEColor (it's now set). % - Added '-gray' for pdf, but gs 8.56 or newer is needed. % - Added '-gray' and '-cmyk' for eps, but you a fairly recent gs might be needed. % Version 1.9, 2008-07-27: % - Added lossless compression, see option '-lossless', above. Works on most formats. % - Added lossy compression, see options '-c...', above. Works on 'pdf'. % Thanks to Olly Woodford for idea and implementation! % - Removed option '-nointerpolate' -- now savefig never interpolates. % - Fixed a few small bugs and removed some mlint comments. % Version 2.0, 2008-11-07: % - Added the possibility to include fonts into eps or pdf. % % TO DO: (Need Ghostscript support for these, so don't expect anything soon...) % - svg output. % - '-cmyk' for 'jpeg' and 'png'. % - Preview in 'eps'. % - Embedded vector fonts, not bitmap, in 'eps'. % % Copyright (C) Peder Axensten (peder at axensten dot se), 2006. % KEYWORDS: eps, pdf, jpg, jpeg, png, tiff, eps2pdf, epstopdf, ghostscript % % INSPIRATION: eps2pdf (5782), eps2xxx (6858) % % REQUIREMENTS: Works in Matlab 6.5.1 (R13SP1) (maybe in 6.5 too). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% op_dbg= false; % Default value. % Compression compr= [' -dUseFlateCompression=true -dLZWEncodePages=true -dCompatibilityLevel=1.6' ... ' -dAutoFilterColorImages=false -dAutoFilterGrayImages=false ' ... ' -dColorImageFilter=%s -dGrayImageFilter=%s']; % Compression. lossless= sprintf (compr, '/FlateEncode', '/FlateEncode'); lossy= sprintf (compr, '/DCTEncode', '/DCTEncode' ); lossy= [lossy ' -c ".setpdfwrite << /ColorImageDict << /QFactor %g ' ... '/Blend 1 /HSample [%s] /VSample [%s] >> >> setdistillerparams"']; % Create gs command. cmdEnd= ' -sDEVICE=%s -sOutputFile="%s"'; % Essential. epsCmd= ''; epsCmd= [epsCmd ' -dSubsetFonts=true -dNOPLATFONTS']; % Future support? epsCmd= [epsCmd ' -dUseCIEColor=true -dColorConversionStrategy=/UseDeviceIndependentColor']; epsCmd= [epsCmd ' -dProcessColorModel=/%s']; % Color conversion. pdfCmd= [epsCmd ' -dAntiAliasColorImages=false' cmdEnd]; epsCmd= [epsCmd cmdEnd]; % Get file name. if((nargin < 1) || isempty(fname) || ~ischar(fname)) % Check file name. error('No file name specified.'); end [pathstr, namestr] = fileparts(fname); if(isempty(pathstr)), fname= fullfile(cd, namestr); end % Get handle. fighdl= get(0, 'CurrentFigure'); % See gcf. % Get figure handle. if((nargin >= 2) && (numel(varargin{1}) == 1) && isnumeric(varargin{1})) fighdl= varargin{1}; varargin= {varargin{2:end}}; end if(isempty(fighdl)), error('There is no figure to save!?'); end set(fighdl, 'Units', 'centimeters') % Set paper stuff. sz= get(fighdl, 'Position'); sz(1:2)= 0; set(fighdl, 'PaperUnits', 'centimeters', 'PaperSize', sz(3:4), 'PaperPosition', sz); % Set up the various devices. % Those commented out are not yet supported by gs (nor by savefig). % pdf-cmyk works due to the Matlab '-cmyk' export being carried over from eps to pdf. device.eps.rgb= sprintf(epsCmd, 'DeviceRGB', 'epswrite', [fname '.eps']); device.jpeg.rgb= sprintf(cmdEnd, 'jpeg', [fname '.jpeg']); % device.jpeg.cmyk= sprintf(cmdEnd, 'jpegcmyk', [fname '.jpeg']); device.jpeg.gray= sprintf(cmdEnd, 'jpeggray', [fname '.jpeg']); device.pdf.rgb= sprintf(pdfCmd, 'DeviceRGB', 'pdfwrite', [fname '.pdf']); device.pdf.cmyk= sprintf(pdfCmd, 'DeviceCMYK', 'pdfwrite', [fname '.pdf']); device.pdf.gray= sprintf(pdfCmd, 'DeviceGray', 'pdfwrite', [fname '.pdf']); device.png.rgb= sprintf(cmdEnd, 'png16m', [fname '.png']); % device.png.cmyk= sprintf(cmdEnd, 'png???', [fname '.png']); device.png.gray= sprintf(cmdEnd, 'pnggray', [fname '.png']); device.tiff.rgb= sprintf(cmdEnd, 'tiff24nc', [fname '.tiff']); device.tiff.cmyk= sprintf(cmdEnd, 'tiff32nc', [fname '.tiff']); device.tiff.gray= sprintf(cmdEnd, 'tiffgray', [fname '.tiff']); % Get options. global savefig_defaults; % Add global defaults. if( iscellstr(savefig_defaults)), varargin= {savefig_defaults{:}, varargin{:}}; elseif(ischar(savefig_defaults)), varargin= {savefig_defaults, varargin{:}}; end varargin= {'-r300', '-lossless', '-rgb', varargin{:}}; % Add defaults. res= ''; types= {}; fonts= 'false'; crop= false; for n= 1:length(varargin) % Read options. if(ischar(varargin{n})) switch(lower(varargin{n})) case {'eps','jpeg','pdf','png','tiff'}, types{end+1}= lower(varargin{n}); case '-rgb', color= 'rgb'; deps= {'-depsc2'}; case '-cmyk', color= 'cmyk'; deps= {'-depsc2', '-cmyk'}; case '-gray', color= 'gray'; deps= {'-deps2'}; case '-fonts', fonts= 'true'; case '-lossless', comp= 0; case '-crop', crop= true; case '-dbg', op_dbg= true; otherwise if(regexp(varargin{n}, '^\-r[0-9]+$')), res= varargin{n}; elseif(regexp(varargin{n}, '^\-c[0-9.]+$')), comp= str2double(varargin{n}(3:end)); else warning('pax:savefig:inputError', 'Unknown option in argument: ''%s''.', varargin{n}); end end else warning('pax:savefig:inputError', 'Wrong type of argument: ''%s''.', class(varargin{n})); end end types= unique(types); if(isempty(types)), error('No output format given.'); end if (comp == 0) % Lossless compression gsCompr= lossless; elseif (comp <= 0.1) % High quality lossy gsCompr= sprintf(lossy, comp, '1 1 1 1', '1 1 1 1'); else % Normal lossy gsCompr= sprintf(lossy, comp, '2 1 1 2', '2 1 1 2'); end % Generate the gs command. switch(computer) % Get gs command. case {'MAC','MACI'}, gs= '/usr/local/bin/gs'; case {'PCWIN','PCWIN64'}, gs= 'gswin32c.exe'; otherwise, gs= 'gs'; end gs= [gs ' -q -dNOPAUSE -dBATCH -dEPSCrop']; % Essential. gs= [gs ' -dPDFSETTINGS=/prepress -dEmbedAllFonts=' fonts]; % Must be first? gs= [gs ' -dUseFlateCompression=true']; % Useful stuff. gs= [gs ' -dAutoRotatePages=/None']; % Probably good. gs= [gs ' -dHaveTrueTypes']; % Probably good. gs= [gs ' ' res]; % Add resolution to cmd. if(crop && ismember(types, {'eps', 'pdf'})) % Crop the figure. fighdl= do_crop(fighdl); end % Output eps from Matlab. renderer= ['-' lower(get(fighdl, 'Renderer'))]; % Use same as in figure. if(strcmpi(renderer, '-none')), renderer= '-painters'; end % We need a valid renderer. print(fighdl, deps{:}, '-noui', renderer, res, [fname '-temp']); % Output the eps. % Convert to other formats. for n= 1:length(types) % Output them. if(isfield(device.(types{n}), color)) cmd= device.(types{n}).(color); % Colour model exists. else cmd= device.(types{n}).rgb; % Use alternative. if(~strcmp(types{n}, 'eps')) % It works anyways for eps (VERY SHAKY!). warning('pax:savefig:deviceError', ... 'No device for %s using %s. Using rgb instead.', types{n}, color); end end cmp= lossless; if (strcmp(types{n}, 'pdf')), cmp= gsCompr; end % Lossy compr only for pdf. if (strcmp(types{n}, 'eps')), cmp= ''; end % eps can't use lossless. cmd= sprintf('%s %s %s -f "%s-temp.eps"', gs, cmd, cmp, fname);% Add up. status= system(cmd); % Run Ghostscript. if (op_dbg || status), display (cmd), end end delete([fname '-temp.eps']); % Clean up. end function fig= do_crop(fig) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Remove line segments that are outside the view. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% haxes= findobj(fig, 'Type', 'axes', '-and', 'Tag', ''); for n=1:length(haxes) xl= get(haxes(n), 'XLim'); yl= get(haxes(n), 'YLim'); lines= findobj(haxes(n), 'Type', 'line'); for m=1:length(lines) x= get(lines(m), 'XData'); y= get(lines(m), 'YData'); inx= (xl(1) <= x) & (x <= xl(2)); % Within the x borders. iny= (yl(1) <= y) & (y <= yl(2)); % Within the y borders. keep= inx & iny; % Within the box. if(~strcmp(get(lines(m), 'LineStyle'), 'none')) crossx= ((x(1:end-1) < xl(1)) & (xl(1) < x(2:end))) ... % Crossing border x1. | ((x(1:end-1) < xl(2)) & (xl(2) < x(2:end))) ... % Crossing border x2. | ((x(1:end-1) > xl(1)) & (xl(1) > x(2:end))) ... % Crossing border x1. | ((x(1:end-1) > xl(2)) & (xl(2) > x(2:end))); % Crossing border x2. crossy= ((y(1:end-1) < yl(1)) & (yl(1) < y(2:end))) ... % Crossing border y1. | ((y(1:end-1) < yl(2)) & (yl(2) < y(2:end))) ... % Crossing border y2. | ((y(1:end-1) > yl(1)) & (yl(1) > y(2:end))) ... % Crossing border y1. | ((y(1:end-1) > yl(2)) & (yl(2) > y(2:end))); % Crossing border y2. crossp= [( (crossx & iny(1:end-1) & iny(2:end)) ... % Crossing a x border within y limits. | (crossy & inx(1:end-1) & inx(2:end)) ... % Crossing a y border within x limits. | crossx & crossy ... % Crossing a x and a y border (corner). ), false ... ]; crossp(2:end)= crossp(2:end) | crossp(1:end-1); % Add line segment's secont end point. keep= keep | crossp; end set(lines(m), 'XData', x(keep)) set(lines(m), 'YData', y(keep)) end end end