line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Image::Info::GIF; |
2
|
|
|
|
|
|
|
$VERSION = '1.02'; |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# Copyright 1999-2000, Gisle Aas. |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# This library is free software; you can redistribute it and/or |
7
|
|
|
|
|
|
|
# modify it under the same terms as Perl itself. |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=begin register |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
MAGIC: /^GIF8[79]a/ |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Both GIF87a and GIF89a are supported and the version number is found |
14
|
|
|
|
|
|
|
as C for the first image. GIF files can contain multiple |
15
|
|
|
|
|
|
|
images, and information for all images will be returned if |
16
|
|
|
|
|
|
|
image_info() is called in list context. The Netscape-2.0 extension to |
17
|
|
|
|
|
|
|
loop animation sequences is represented by the C key for the |
18
|
|
|
|
|
|
|
first image. The value is either "forever" or a number indicating |
19
|
|
|
|
|
|
|
loop count. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=end register |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=cut |
24
|
|
|
|
|
|
|
|
25
|
2
|
|
|
2
|
|
14
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
2511
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub my_read |
28
|
|
|
|
|
|
|
{ |
29
|
425
|
|
|
425
|
0
|
689
|
my($source, $len) = @_; |
30
|
425
|
|
|
|
|
407
|
my $buf; |
31
|
425
|
|
|
|
|
2129
|
my $n = read($source, $buf, $len); |
32
|
425
|
50
|
|
|
|
864
|
die "read failed: $!" unless defined $n; |
33
|
425
|
50
|
|
|
|
588
|
die "short read ($len/$n) at pos " . tell($source) unless $n == $len; |
34
|
425
|
|
|
|
|
943
|
$buf; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub read_data_blocks |
38
|
|
|
|
|
|
|
{ |
39
|
7
|
|
|
7
|
0
|
11
|
my $source = shift; |
40
|
7
|
|
|
|
|
53
|
my @data; |
41
|
7
|
|
|
|
|
15
|
while (my $len = ord(my_read($source, 1))) { |
42
|
7
|
|
|
|
|
12
|
push(@data, my_read($source, $len)); |
43
|
|
|
|
|
|
|
} |
44
|
7
|
|
|
|
|
26
|
join("", @data); |
45
|
|
|
|
|
|
|
} |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub seek_data_blocks |
48
|
|
|
|
|
|
|
{ |
49
|
7
|
|
|
7
|
0
|
9
|
my $source = shift; |
50
|
7
|
|
|
|
|
13
|
while (my $len = ord(my_read($source, 1))) { |
51
|
343
|
|
|
|
|
2864
|
seek($source, $len, 1); |
52
|
|
|
|
|
|
|
} |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub process_file |
56
|
|
|
|
|
|
|
{ |
57
|
7
|
|
|
7
|
1
|
16
|
my($info, $fh) = @_; |
58
|
|
|
|
|
|
|
|
59
|
7
|
|
|
|
|
15
|
my $header = my_read($fh, 13); |
60
|
7
|
50
|
|
|
|
41
|
die "Bad GIF signature" |
61
|
|
|
|
|
|
|
unless $header =~ s/^GIF(8[79]a)//; |
62
|
7
|
|
|
|
|
20
|
my $version = $1; |
63
|
7
|
|
|
|
|
27
|
$info->push_info(0, "GIF_Version" => $version); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# process logical screen descriptor |
66
|
7
|
|
|
|
|
34
|
my($sw, $sh, $packed, $bg, $aspect) = unpack("vvCCC", $header); |
67
|
7
|
|
|
|
|
30
|
$info->push_info(0, "ScreenWidth" => $sw); |
68
|
7
|
|
|
|
|
18
|
$info->push_info(0, "ScreenHeight" => $sh); |
69
|
|
|
|
|
|
|
|
70
|
7
|
|
|
|
|
16
|
my $color_table_size = 1 << (($packed & 0x07) + 1); |
71
|
7
|
|
|
|
|
15
|
$info->push_info(0, "ColorTableSize" => $color_table_size); |
72
|
|
|
|
|
|
|
|
73
|
7
|
50
|
|
|
|
31
|
$info->push_info(0, "SortedColors" => ($packed & 0x08) ? 1 : 0) |
|
|
50
|
|
|
|
|
|
74
|
|
|
|
|
|
|
if $version eq "89a"; |
75
|
|
|
|
|
|
|
|
76
|
7
|
|
|
|
|
19
|
$info->push_info(0, "ColorResolution", (($packed & 0x70) >> 4) + 1); |
77
|
|
|
|
|
|
|
|
78
|
7
|
|
|
|
|
10
|
my $global_color_table = $packed & 0x80; |
79
|
7
|
50
|
|
|
|
24
|
$info->push_info(0, "GlobalColorTableFlag" => $global_color_table ? 1 : 0); |
80
|
7
|
50
|
|
|
|
14
|
if ($global_color_table) { |
81
|
7
|
|
|
|
|
13
|
$info->push_info(0, "BackgroundColor", $bg); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
7
|
50
|
|
|
|
14
|
if ($aspect) { |
85
|
0
|
|
|
|
|
0
|
$aspect = ($aspect + 15) / 64; |
86
|
0
|
|
|
|
|
0
|
$info->push_info(0, "PixelAspectRatio" => $aspect); |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# XXX is this correct???? |
89
|
0
|
|
|
|
|
0
|
$info->push_info(0, "resolution", "1/$aspect"); |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
else { |
92
|
7
|
|
|
|
|
14
|
$info->push_info(0, "resolution", "1/1"); |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
7
|
|
|
|
|
16
|
$info->push_info(0, "file_media_type" => "image/gif"); |
96
|
7
|
|
|
|
|
14
|
$info->push_info(0, "file_ext" => "gif"); |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# more?? |
99
|
7
|
50
|
|
|
|
15
|
if ($global_color_table) { |
100
|
7
|
|
|
|
|
17
|
my $color_table = my_read($fh, $color_table_size * 3); |
101
|
|
|
|
|
|
|
#$info->push_info(0, "GlobalColorTable", color_table($color_table)); |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
7
|
|
|
|
|
17
|
my $img_no = 0; |
105
|
7
|
|
|
|
|
11
|
my @comments; |
106
|
|
|
|
|
|
|
my @warnings; |
107
|
|
|
|
|
|
|
|
108
|
7
|
|
|
|
|
8
|
while (1) { |
109
|
21
|
100
|
|
|
|
52
|
last if eof($fh); # EOF |
110
|
19
|
|
|
|
|
28
|
my $intro = ord(my_read($fh, 1)); |
111
|
19
|
100
|
|
|
|
57
|
if ($intro == 0x3B) { # trailer (end of image) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
112
|
3
|
|
|
|
|
11
|
last; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
elsif ($intro == 0x2C) { # new image |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
|
117
|
7
|
100
|
|
|
|
17
|
if (@comments) { |
118
|
3
|
|
|
|
|
6
|
for (@comments) { |
119
|
3
|
|
|
|
|
9
|
$info->push_info(0, "Comment", $_); |
120
|
|
|
|
|
|
|
} |
121
|
3
|
|
|
|
|
15
|
@comments = (); |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
|
124
|
7
|
|
|
|
|
20
|
$info->push_info($img_no, "color_type" => "Indexed-RGB"); |
125
|
|
|
|
|
|
|
|
126
|
7
|
|
|
|
|
12
|
my($x_pos, $y_pos, $w, $h, $packed) = |
127
|
|
|
|
|
|
|
unpack("vvvvC", my_read($fh, 9)); |
128
|
7
|
|
|
|
|
21
|
$info->push_info($img_no, "XPosition", $x_pos); |
129
|
7
|
|
|
|
|
15
|
$info->push_info($img_no, "YPosition", $y_pos); |
130
|
7
|
|
|
|
|
13
|
$info->push_info($img_no, "width", $w); |
131
|
7
|
|
|
|
|
13
|
$info->push_info($img_no, "height", $h); |
132
|
|
|
|
|
|
|
|
133
|
7
|
50
|
|
|
|
23
|
if ($packed & 0x80) { |
134
|
|
|
|
|
|
|
# yes, we have a local color table |
135
|
0
|
|
|
|
|
0
|
my $ct_size = 1 << (($packed & 0x07) + 1); |
136
|
0
|
|
|
|
|
0
|
$info->push_info($img_no, "LColorTableSize" => $ct_size); |
137
|
0
|
|
|
|
|
0
|
my $color_table = my_read($fh, $ct_size * 3); |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
7
|
100
|
|
|
|
25
|
$info->push_info($img_no, "Interlace" => "GIF") |
141
|
|
|
|
|
|
|
if $packed & 0x40; |
142
|
|
|
|
|
|
|
|
143
|
7
|
|
|
|
|
12
|
my $lzw_code_size = ord(my_read($fh, 1)); |
144
|
|
|
|
|
|
|
#$info->push_info($img_no, "LZW_MininmCodeSize", $lzw_code_size); |
145
|
7
|
|
|
|
|
16
|
seek_data_blocks($fh); # skip image data |
146
|
7
|
|
|
|
|
14
|
$img_no++; |
147
|
|
|
|
|
|
|
} |
148
|
|
|
|
|
|
|
elsif ($intro == 0x21) { # GIF89a extension |
149
|
7
|
50
|
|
|
|
18
|
push(@warnings, "GIF 89a extensions in 87a") |
150
|
|
|
|
|
|
|
if $version eq "87a"; |
151
|
|
|
|
|
|
|
|
152
|
7
|
|
|
|
|
11
|
my $label = ord(my_read($fh, 1)); |
153
|
7
|
|
|
|
|
29
|
my $data = read_data_blocks($fh); |
154
|
7
|
100
|
66
|
|
|
31
|
if ($label == 0xF9 && length($data) == 4) { # Graphic Control |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
155
|
4
|
|
|
|
|
10
|
my($packed, $delay, $trans_color) = unpack("CvC", $data); |
156
|
4
|
|
|
|
|
8
|
my $disposal_method = ($packed >> 2) & 0x07; |
157
|
4
|
50
|
|
|
|
9
|
$info->push_info($img_no, "DisposalMethod", $disposal_method) |
158
|
|
|
|
|
|
|
if $disposal_method; |
159
|
4
|
50
|
|
|
|
6
|
$info->push_info($img_no, "UserInput", 1) |
160
|
|
|
|
|
|
|
if $packed & 0x02; |
161
|
4
|
50
|
|
|
|
7
|
$info->push_info($img_no, "Delay" => $delay/100) if $delay; |
162
|
4
|
100
|
|
|
|
11
|
$info->push_info($img_no, "TransparencyIndex" => $trans_color) |
163
|
|
|
|
|
|
|
if $packed & 0x01; |
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
elsif ($label == 0xFE) { # Comment |
166
|
3
|
|
|
|
|
8
|
$data =~ s/\0+$//; # is often NUL-terminated |
167
|
3
|
|
|
|
|
7
|
push(@comments, $data); |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
elsif ($label == 0xFF) { # Application |
170
|
0
|
|
|
|
|
0
|
my $app = substr($data, 0, 11, ""); |
171
|
0
|
|
|
|
|
0
|
my $auth = substr($app, -3, 3, ""); |
172
|
0
|
0
|
0
|
|
|
0
|
if ($app eq "NETSCAPE" && $auth eq "2.0" |
|
|
|
0
|
|
|
|
|
173
|
|
|
|
|
|
|
&& $data =~ /^\01/) { |
174
|
0
|
|
|
|
|
0
|
my $loop = unpack("xv", $data); |
175
|
0
|
0
|
|
|
|
0
|
$loop = "forever" unless $loop; |
176
|
0
|
|
|
|
|
0
|
$info->push_info(0, "GIF_Loop" => $loop); |
177
|
|
|
|
|
|
|
} else { |
178
|
0
|
|
|
|
|
0
|
$info->push_info(0, "APP-$app-$auth" => $data); |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
else { |
182
|
0
|
|
|
|
|
0
|
$info->push_info($img_no, "GIF_Extension-$label" => $data); |
183
|
|
|
|
|
|
|
} |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
else { |
186
|
2
|
|
|
|
|
7
|
push @warnings, "Unknown introduced code $intro, ignoring following chunks"; |
187
|
2
|
|
|
|
|
4
|
last; |
188
|
|
|
|
|
|
|
} |
189
|
|
|
|
|
|
|
} |
190
|
|
|
|
|
|
|
|
191
|
7
|
|
|
|
|
16
|
for (@comments) { |
192
|
0
|
|
|
|
|
0
|
$info->push_info(0, "Comment", $_); |
193
|
|
|
|
|
|
|
} |
194
|
|
|
|
|
|
|
|
195
|
7
|
|
|
|
|
20
|
for (@warnings) { |
196
|
2
|
|
|
|
|
5
|
$info->push_info(0, "Warn", $_); |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
sub color_table |
201
|
|
|
|
|
|
|
{ |
202
|
0
|
|
|
0
|
0
|
|
my @n = unpack("C*", shift); |
203
|
0
|
0
|
|
|
|
|
die "Color table not a multiple of 3" if @n % 3; |
204
|
0
|
|
|
|
|
|
my @table; |
205
|
0
|
|
|
|
|
|
while (@n) { |
206
|
0
|
|
|
|
|
|
my @triple = splice(@n, -3); |
207
|
0
|
|
|
|
|
|
push(@table, sprintf("#%02x%02x%02x", @triple)); |
208
|
|
|
|
|
|
|
} |
209
|
0
|
|
|
|
|
|
[reverse @table]; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
1; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
__END__ |