Robochameleon  v1.0
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);
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 return
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 return
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 return
187 
188 %% Crop the borders
189 function A = crop_borders(A)
190 [h, w, c, n] = size(A);
191 bcol = A(ceil(end/2),1,:,1);
192 bail = false;
193 for l = 1:w
194  for a = 1:c
195  if ~all(col(A(:,l,a,:)) == bcol(a))
196  bail = true;
197  break;
198  end
199  end
200  if bail
201  break;
202  end
203 end
204 bcol = A(ceil(end/2),w,:,1);
205 bail = false;
206 for r = w:-1:l
207  for a = 1:c
208  if ~all(col(A(:,r,a,:)) == bcol(a))
209  bail = true;
210  break;
211  end
212  end
213  if bail
214  break;
215  end
216 end
217 bcol = A(1,ceil(end/2),:,1);
218 bail = false;
219 for t = 1:h
220  for a = 1:c
221  if ~all(col(A(t,:,a,:)) == bcol(a))
222  bail = true;
223  break;
224  end
225  end
226  if bail
227  break;
228  end
229 end
230 bcol = A(h,ceil(end/2),:,1);
231 bail = false;
232 for b = h:-1:t
233  for a = 1:c
234  if ~all(col(A(b,:,a,:)) == bcol(a))
235  bail = true;
236  break;
237  end
238  end
239  if bail
240  break;
241  end
242 end
243 A = A(t:b,l:r,:,:);
244 return
245 
246 function A = col(A)
247 A = A(:);
248 return