|
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 |