a b/util/export_fig/im2gif.m
1
%IM2GIF Convert a multiframe image to an animated GIF file
2
%
3
% Examples:
4
%   im2gif infile
5
%   im2gif infile outfile
6
%   im2gif(A, outfile)
7
%   im2gif(..., '-nocrop')
8
%   im2gif(..., '-nodither')
9
%   im2gif(..., '-ncolors', n)
10
%   im2gif(..., '-loops', n)
11
%   im2gif(..., '-delay', n) 
12
%   
13
% This function converts a multiframe image to an animated GIF.
14
%
15
% To create an animation from a series of figures, export to a multiframe
16
% TIFF file using export_fig, then convert to a GIF, as follows:
17
%
18
%    for a = 2 .^ (3:6)
19
%       peaks(a);
20
%       export_fig test.tif -nocrop -append
21
%    end
22
%    im2gif('test.tif', '-delay', 0.5);
23
%
24
%IN:
25
%   infile - string containing the name of the input image.
26
%   outfile - string containing the name of the output image (must have the
27
%             .gif extension). Default: infile, with .gif extension.
28
%   A - HxWxCxN array of input images, stacked along fourth dimension, to
29
%       be converted to gif.
30
%   -nocrop - option indicating that the borders of the output are not to
31
%             be cropped.
32
%   -nodither - option indicating that dithering is not to be used when
33
%               converting the image.
34
%   -ncolors - option pair, the value of which indicates the maximum number
35
%              of colors the GIF can have. This can also be a quantization
36
%              tolerance, between 0 and 1. Default/maximum: 256.
37
%   -loops - option pair, the value of which gives the number of times the
38
%            animation is to be looped. Default: 65535.
39
%   -delay - option pair, the value of which gives the time, in seconds,
40
%            between frames. Default: 1/15.
41
42
% Copyright (C) Oliver Woodford 2011
43
44
function im2gif(A, varargin)
45
46
% Parse the input arguments
47
[A, options] = parse_args(A, varargin{:});
48
49
if options.crop ~= 0
50
    % Crop
51
    A = crop_borders(A, A(ceil(end/2),1,:,1));
52
end
53
54
% Convert to indexed image
55
[h, w, c, n] = size(A);
56
A = reshape(permute(A, [1 2 4 3]), h, w*n, c);
57
map = unique(reshape(A, h*w*n, c), 'rows');
58
if size(map, 1) > 256
59
    dither_str = {'dither', 'nodither'};
60
    dither_str = dither_str{1+(options.dither==0)};
61
    if options.ncolors <= 1
62
        [B, map] = rgb2ind(A, options.ncolors, dither_str);
63
        if size(map, 1) > 256
64
            [B, map] = rgb2ind(A, 256, dither_str);
65
        end
66
    else
67
        [B, map] = rgb2ind(A, min(round(options.ncolors), 256), dither_str);
68
    end
69
else
70
    if max(map(:)) > 1
71
        map = double(map) / 255;
72
        A = double(A) / 255;
73
    end
74
    B = rgb2ind(im2double(A), map);
75
end
76
B = reshape(B, h, w, 1, n);
77
78
% Bug fix to rgb2ind
79
map(B(1)+1,:) = im2double(A(1,1,:));
80
81
% Save as a gif
82
imwrite(B, map, options.outfile, 'LoopCount', round(options.loops(1)), 'DelayTime', options.delay);
83
end
84
85
%% Parse the input arguments
86
function [A, options] = parse_args(A, varargin)
87
% Set the defaults
88
options = struct('outfile', '', ...
89
                 'dither', true, ...
90
                 'crop', true, ...
91
                 'ncolors', 256, ...
92
                 'loops', 65535, ...
93
                 'delay', 1/15);
94
95
% Go through the arguments
96
a = 0;
97
n = numel(varargin);
98
while a < n
99
    a = a + 1;
100
    if ischar(varargin{a}) && ~isempty(varargin{a})
101
        if varargin{a}(1) == '-'
102
            opt = lower(varargin{a}(2:end));
103
            switch opt
104
                case 'nocrop'
105
                    options.crop = false;
106
                case 'nodither'
107
                    options.dither = false;
108
                otherwise
109
                    if ~isfield(options, opt)
110
                        error('Option %s not recognized', varargin{a});
111
                    end
112
                    a = a + 1;
113
                    if ischar(varargin{a}) && ~ischar(options.(opt))
114
                        options.(opt) = str2double(varargin{a});
115
                    else
116
                        options.(opt) = varargin{a};
117
                    end
118
            end
119
        else
120
            options.outfile = varargin{a};
121
        end
122
    end
123
end
124
125
if isempty(options.outfile)
126
    if ~ischar(A)
127
        error('No output filename given.');
128
    end
129
    % Generate the output filename from the input filename
130
    [path, outfile] = fileparts(A);
131
    options.outfile = fullfile(path, [outfile '.gif']);
132
end
133
134
if ischar(A)
135
    % Read in the image
136
    A = imread_rgb(A);
137
end
138
end
139
140
%% Read image to uint8 rgb array
141
function [A, alpha] = imread_rgb(name)
142
% Get file info
143
info = imfinfo(name);
144
% Special case formats
145
switch lower(info(1).Format)
146
    case 'gif'
147
        [A, map] = imread(name, 'frames', 'all');
148
        if ~isempty(map)
149
            map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
150
            A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
151
            A = permute(A, [1 2 5 4 3]);
152
        end
153
    case {'tif', 'tiff'}
154
        A = cell(numel(info), 1);
155
        for a = 1:numel(A)
156
            [A{a}, map] = imread(name, 'Index', a, 'Info', info);
157
            if ~isempty(map)
158
                map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
159
                A{a} = reshape(map(uint32(A{a})+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
160
            end
161
            if size(A{a}, 3) == 4
162
                % TIFF in CMYK colourspace - convert to RGB
163
                if isfloat(A{a})
164
                    A{a} = A{a} * 255;
165
                else
166
                    A{a} = single(A{a});
167
                end
168
                A{a} = 255 - A{a};
169
                A{a}(:,:,4) = A{a}(:,:,4) / 255;
170
                A{a} = uint8(A(:,:,1:3) .* A{a}(:,:,[4 4 4]));
171
            end
172
        end
173
        A = cat(4, A{:});
174
    otherwise
175
        [A, map, alpha] = imread(name);
176
        A = A(:,:,:,1); % Keep only first frame of multi-frame files
177
        if ~isempty(map)
178
            map = uint8(map * 256 - 0.5); % Convert to uint8 for storage
179
            A = reshape(map(uint32(A)+1,:), [size(A) size(map, 2)]); % Assume indexed from 0
180
        elseif size(A, 3) == 4
181
            % Assume 4th channel is an alpha matte
182
            alpha = A(:,:,4);
183
            A = A(:,:,1:3);
184
        end
185
end
186
end