%IM2GIF Convert a multiframe image to an animated GIF file
%
% Examples:
% im2gif infile
% im2gif infile outfile
% im2gif(A, outfile)
% im2gif(..., '-nocrop')
% im2gif(..., '-nodither')
% im2gif(..., '-ncolors', n)
% im2gif(..., '-loops', n)
% im2gif(..., '-delay', n)
%
% This function converts a multiframe image to an animated GIF.
%
% To create an animation from a series of figures, export to a multiframe
% TIFF file using export_fig, then convert to a GIF, as follows:
%
% for a = 2 .^ (3:6)
% peaks(a);
% export_fig test.tif -nocrop -append
% end
% im2gif('test.tif', '-delay', 0.5);
%
%IN:
% infile - string containing the name of the input image.
% outfile - string containing the name of the output image (must have the
% .gif extension). Default: infile, with .gif extension.
% A - HxWxCxN array of input images, stacked along fourth dimension, to
% be converted to gif.
% -nocrop - option indicating that the borders of the output are not to
% be cropped.
% -nodither - option indicating that dithering is not to be used when
% converting the image.
% -ncolors - option pair, the value of which indicates the maximum number
% of colors the GIF can have. This can also be a quantization
% tolerance, between 0 and 1. Default/maximum: 256.
% -loops - option pair, the value of which gives the number of times the
% animation is to be looped. Default: 65535.
% -delay - option pair, the value of which gives the time, in seconds,
% between frames. Default: 1/15.
% Copyright (C) Oliver Woodford 2011
%{
% 14/02/18: Merged issue #235: reduced memory usage, improved performance (thanks to @numb7rs)
%}
function im2gif(A, varargin)
% Parse the input arguments
[A, options] = parse_args(A, varargin{:});
if options.crop ~= 0
% Crop
A = crop_borders(A, A(ceil(end/2),1,:,1));
end
% Convert to indexed image
[h, w, c, n] = size(A);
% Issue #235: Using unique(A,'rows') on the whole image stack at once causes
% massive memory usage when dealing with large images (at least on Matlab 2017b).
% Running unique(...) on individual frames, then again on the results drastically
% reduces the memory usage & slightly improves the execution time (@numb7rs).
uns = cell(1,size(A,4));
for nn=1:size(A,4)
uns{nn}=unique(reshape(A(:,:,:,nn), h*w, c),'rows');
end
map=unique(cell2mat(uns'),'rows');
A = reshape(permute(A, [1 2 4 3]), h, w*n, c);
if size(map, 1) > 256
dither_str = {'dither', 'nodither'};
dither_str = dither_str{1+(options.dither==0)};
if options.ncolors <= 1
[B, map] = rgb2ind(A, options.ncolors, dither_str);
if size(map, 1) > 256
[B, map] = rgb2ind(A, 256, dither_str);
end
else
[B, map] = rgb2ind(A, min(round(options.ncolors), 256), dither_str);
end
else
if max(map(:)) > 1
map = double(map) / 255;
A = double(A) / 255;
end
B = rgb2ind(im2double(A), map);
end
B = reshape(B, h, w, 1, n);
% Bug fix to rgb2ind
map(B(1)+1,:) = im2double(A(1,1,:));
% Save as a gif
imwrite(B, map, options.outfile, 'LoopCount', round(options.loops(1)), 'DelayTime', options.delay);
end
%% Parse the input arguments
function [A, options] = parse_args(A, varargin)
% Set the defaults
options = struct('outfile', '', ...
'dither', true, ...
'crop', true, ...
'ncolors', 256, ...
'loops', 65535, ...
'delay', 1/15);
% Go through the arguments
a = 0;
n = numel(varargin);
while a < n
a = a + 1;
if ischar(varargin{a}) && ~isempty(varargin{a})
if varargin{a}(1) == '-'
opt = lower(varargin{a}(2:end));
switch opt
case 'nocrop'
options.crop = false;
case 'nodither'
options.dither = false;
otherwise
if ~isfield(options, opt)
error('Option %s not recognized', varargin{a});
end
a = a + 1;
if ischar(varargin{a}) && ~ischar(options.(opt))
options.(opt) = str2double(varargin{a});
else
options.(opt) = varargin{a};
end
end
else
options.outfile = varargin{a};
end
end
end
if isempty(options.outfile)
if ~ischar(A)
error('No output filename given.');
end
% Generate the output filename from the input filename
[path, outfile] = fileparts(A);
options.outfile = fullfile(path, [outfile '.gif']);
end
if ischar(A)
% Read in the image
A = imread_rgb(A);
end
end
%% Read image to uint8 rgb array
function [A, alpha] = imread_rgb(name)
% Get file info
info = imfinfo(name);
% Special case formats
switch lower(info(1).Format)
case 'gif'
[A, map] = imread(name, 'frames', 'all');
if ~isempty(map)
map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
A = permute(A, [1 2 5 4 3]);
end
case {'tif', 'tiff'}
A = cell(numel(info), 1);
for a = 1:numel(A)
[A{a}, map] = imread(name, 'Index', a, 'Info', info);
if ~isempty(map)
map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
A{a} = reshape(map(uint32(A{a})+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
end
if size(A{a}, 3) == 4
% TIFF in CMYK colourspace - convert to RGB
if isfloat(A{a})
A{a} = A{a} * 255;
else
A{a} = single(A{a});
end
A{a} = 255 - A{a};
A{a}(:,:,4) = A{a}(:,:,4) / 255;
A{a} = uint8(A(:,:,1:3) .* A{a}(:,:,[4 4 4]));
end
end
A = cat(4, A{:});
otherwise
[A, map, alpha] = imread(name);
A = A(:,:,:,1); % Keep only first frame of multi-frame files
if ~isempty(map)
map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
elseif size(A, 3) == 4
% Assume 4th channel is an alpha matte
alpha = A(:,:,4);
A = A(:,:,1:3);
end
end
end