line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Image::PNG::Rewriter;
|
2
|
|
|
|
|
|
|
|
3
|
3
|
|
|
3
|
|
81435
|
use 5.010000;
|
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
132
|
|
4
|
3
|
|
|
3
|
|
17
|
use strict;
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
110
|
|
5
|
3
|
|
|
3
|
|
14
|
use warnings;
|
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
90
|
|
6
|
3
|
|
|
3
|
|
10101
|
use Compress::Zlib qw();
|
|
3
|
|
|
|
|
333052
|
|
|
3
|
|
|
|
|
71
|
|
7
|
3
|
|
|
3
|
|
27
|
use Carp;
|
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
205
|
|
8
|
3
|
|
|
3
|
|
4191
|
use POSIX qw/ceil/;
|
|
3
|
|
|
|
|
29901
|
|
|
3
|
|
|
|
|
20
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.9';
|
11
|
|
|
|
|
|
|
our $PNG_MAGIC = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
require XSLoader;
|
14
|
|
|
|
|
|
|
XSLoader::load('Image::PNG::Rewriter', $VERSION);
|
15
|
|
|
|
|
|
|
|
16
|
3
|
|
|
|
|
4146
|
use constant CHANNELS => {
|
17
|
|
|
|
|
|
|
0 => 1,
|
18
|
|
|
|
|
|
|
2 => 3,
|
19
|
|
|
|
|
|
|
3 => 1,
|
20
|
|
|
|
|
|
|
4 => 2,
|
21
|
|
|
|
|
|
|
6 => 4,
|
22
|
3
|
|
|
3
|
|
3758
|
};
|
|
3
|
|
|
|
|
7
|
|
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
sub new {
|
25
|
60
|
|
|
60
|
1
|
69245
|
my $class = shift;
|
26
|
60
|
|
|
|
|
321
|
my $self = bless {}, $class;
|
27
|
60
|
|
|
|
|
303
|
my %o = @_;
|
28
|
60
|
|
|
|
|
141
|
my $h = $o{handle};
|
29
|
|
|
|
|
|
|
|
30
|
60
|
50
|
|
|
|
199
|
die "No 'handle' specified" unless $h;
|
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
$o{zlib} //= sub {
|
33
|
120
|
|
|
120
|
|
641
|
my $data = shift;
|
34
|
120
|
|
|
|
|
540
|
my ($d, $status0) = Compress::Zlib::deflateInit();
|
35
|
120
|
50
|
|
|
|
48652
|
die unless $status0 == Compress::Zlib::Z_OK;
|
36
|
120
|
|
|
|
|
984
|
my ($out1, $status1) = $d->deflate($data);
|
37
|
120
|
50
|
|
|
|
215270
|
die unless $status1 == Compress::Zlib::Z_OK;
|
38
|
120
|
|
|
|
|
1083
|
my ($out2, $status2) = $d->flush();
|
39
|
120
|
50
|
|
|
|
5624
|
die unless $status2 == Compress::Zlib::Z_OK;
|
40
|
120
|
|
|
|
|
1971
|
return $out1 . $out2;
|
41
|
60
|
|
50
|
|
|
794
|
};
|
42
|
|
|
|
|
|
|
|
43
|
60
|
|
|
|
|
199
|
$self->{_zlib} = $o{zlib};
|
44
|
|
|
|
|
|
|
|
45
|
60
|
50
|
|
|
|
619
|
read($h, my $magic, 8) == 8 or die;
|
46
|
|
|
|
|
|
|
|
47
|
60
|
50
|
|
|
|
202
|
die "Not a PNG image" unless $magic eq $PNG_MAGIC;
|
48
|
|
|
|
|
|
|
|
49
|
60
|
|
|
|
|
144
|
$self->{_chunks} = [];
|
50
|
|
|
|
|
|
|
|
51
|
60
|
|
|
|
|
209
|
while (!eof($h)) {
|
52
|
|
|
|
|
|
|
# [size] [type] [data] [checksum]
|
53
|
436
|
50
|
|
|
|
1320
|
read($h, my $raw, 8) == 8 or die;
|
54
|
436
|
|
|
|
|
1681
|
my ($length, $type) = unpack 'Na4', $raw;
|
55
|
436
|
50
|
|
|
|
1323
|
read($h, my $data, $length) == $length or die;
|
56
|
436
|
50
|
|
|
|
12058
|
read($h, my $crc_raw, 4) == 4 or die;
|
57
|
436
|
|
|
|
|
1108
|
my $crc = unpack 'N', $crc_raw;
|
58
|
436
|
|
|
|
|
502
|
push @{ $self->{_chunks} }, {
|
|
436
|
|
|
|
|
2818
|
|
59
|
|
|
|
|
|
|
type => $type,
|
60
|
|
|
|
|
|
|
size => $length,
|
61
|
|
|
|
|
|
|
data => $data,
|
62
|
|
|
|
|
|
|
crc32 => $crc,
|
63
|
|
|
|
|
|
|
};
|
64
|
|
|
|
|
|
|
}
|
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# get the first IHDR chunk; only one is allowed
|
67
|
60
|
|
|
|
|
106
|
my ($ihdr) = grep { $_->{type} eq 'IHDR' } @{ $self->{_chunks} };
|
|
436
|
|
|
|
|
882
|
|
|
60
|
|
|
|
|
185
|
|
68
|
60
|
50
|
|
|
|
1088
|
die unless $ihdr;
|
69
|
|
|
|
|
|
|
|
70
|
60
|
|
|
|
|
279
|
my @ihdr_values = unpack 'NNccccc', $ihdr->{data};
|
71
|
60
|
50
|
|
|
|
176
|
die unless @ihdr_values == 7;
|
72
|
|
|
|
|
|
|
|
73
|
60
|
|
|
|
|
465
|
($self->{_width},
|
74
|
|
|
|
|
|
|
$self->{_height},
|
75
|
|
|
|
|
|
|
$self->{_depth},
|
76
|
|
|
|
|
|
|
$self->{_color},
|
77
|
|
|
|
|
|
|
$self->{_comp},
|
78
|
|
|
|
|
|
|
$self->{_filter},
|
79
|
|
|
|
|
|
|
$self->{_interlace}) = @ihdr_values;
|
80
|
|
|
|
|
|
|
|
81
|
60
|
50
|
|
|
|
231
|
die unless $self->{_width};
|
82
|
60
|
50
|
|
|
|
143
|
die unless $self->{_height};
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# TODO: validate depth/type restrictions?
|
85
|
|
|
|
|
|
|
|
86
|
60
|
50
|
|
|
|
156
|
die unless $self->{_comp} == 0;
|
87
|
60
|
50
|
|
|
|
141
|
die unless $self->{_filter} == 0;
|
88
|
|
|
|
|
|
|
|
89
|
60
|
50
|
|
|
|
158
|
confess "Interlaced images are not supported"
|
90
|
|
|
|
|
|
|
if $self->{_interlace};
|
91
|
|
|
|
|
|
|
|
92
|
60
|
|
|
|
|
253
|
$self->{_channels} = (CHANNELS)->{ $self->{_color} };
|
93
|
|
|
|
|
|
|
|
94
|
60
|
50
|
|
|
|
192
|
die unless defined $self->{_channels};
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# PNGs can have many IDAT chunks
|
97
|
60
|
|
|
|
|
229
|
my $coalesced = join '', map { $_->{data} }
|
|
436
|
|
|
|
|
918
|
|
98
|
60
|
|
|
|
|
163
|
grep { $_->{type} eq 'IDAT' } @{ $self->{_chunks} };
|
|
60
|
|
|
|
|
169
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# One IEND chunk is required
|
101
|
436
|
|
|
|
|
1245
|
die unless 1 == grep { $_->{type} eq 'IEND' }
|
|
60
|
|
|
|
|
119
|
|
102
|
60
|
50
|
|
|
|
101
|
@{ $self->{_chunks} };
|
103
|
|
|
|
|
|
|
|
104
|
60
|
|
|
|
|
254
|
my ($i, $status0) = Compress::Zlib::inflateInit;
|
105
|
60
|
50
|
|
|
|
7877
|
die unless $status0 == Compress::Zlib::Z_OK;
|
106
|
60
|
|
|
|
|
532
|
my ($inflated, $status1) = $i->inflate("$coalesced");
|
107
|
60
|
50
|
|
|
|
48629
|
die $status1 unless $status1 == Compress::Zlib::Z_STREAM_END;
|
108
|
|
|
|
|
|
|
|
109
|
60
|
|
|
|
|
438
|
$self->{_inflated} = $inflated;
|
110
|
60
|
|
|
|
|
148
|
$self->{_deflated} = $coalesced;
|
111
|
|
|
|
|
|
|
|
112
|
60
|
|
|
|
|
219
|
$self->{_new_deflated} = "$coalesced";
|
113
|
60
|
|
|
|
|
3713
|
$self->{_new_inflated} = "$inflated";
|
114
|
|
|
|
|
|
|
|
115
|
60
|
|
|
|
|
559
|
my $expected_bytes = $self->{_height} *
|
116
|
|
|
|
|
|
|
ceil(($self->{_width} * $self->{_channels} * $self->{_depth} + 8) / 8);
|
117
|
|
|
|
|
|
|
|
118
|
60
|
|
|
|
|
149
|
my $actual_bytes = length $self->{_inflated};
|
119
|
60
|
50
|
|
|
|
155
|
die unless $expected_bytes == $actual_bytes;
|
120
|
|
|
|
|
|
|
|
121
|
60
|
|
|
|
|
167
|
$self->{_scanline_width} = $expected_bytes / $self->{_height};
|
122
|
60
|
|
|
|
|
332
|
$self->{_scanline_delta} = $self->{_channels} * ceil($self->{_depth} / 8);
|
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# Destructive operation needs a copy
|
125
|
60
|
|
|
|
|
12488
|
$self->{_unfiltered} = "$inflated";
|
126
|
60
|
|
|
|
|
29463
|
_unfilter($self->{_unfiltered}, $self->{_height}, $self->{_scanline_delta}, $self->{_scanline_width});
|
127
|
|
|
|
|
|
|
|
128
|
60
|
|
|
|
|
621
|
$self;
|
129
|
|
|
|
|
|
|
}
|
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub refilter {
|
132
|
120
|
|
|
120
|
1
|
275
|
my $self = shift;
|
133
|
120
|
|
|
|
|
1477
|
my @filters = @_;
|
134
|
120
|
50
|
|
|
|
355
|
die unless @filters == $self->height;
|
135
|
|
|
|
|
|
|
|
136
|
120
|
|
|
|
|
17032
|
$self->{_new_inflated} = $self->{_unfiltered} . "";
|
137
|
120
|
|
|
|
|
8443
|
my $filter = join '', map chr, @filters;
|
138
|
|
|
|
|
|
|
|
139
|
120
|
|
|
|
|
36996
|
_filter($self->{_unfiltered}, $self->{_new_inflated},
|
140
|
|
|
|
|
|
|
$filter, $self->{_height}, $self->{_scanline_delta},
|
141
|
|
|
|
|
|
|
$self->{_scanline_width});
|
142
|
|
|
|
|
|
|
|
143
|
120
|
|
|
|
|
379
|
$self->{_new_filters} = \@filters;
|
144
|
120
|
|
|
|
|
963
|
$self->{_new_deflated} = $self->{_zlib}->($self->{_new_inflated});
|
145
|
120
|
|
|
|
|
579
|
return $self->{_new_deflated}, $self->{_new_inflated};
|
146
|
|
|
|
|
|
|
}
|
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub as_png {
|
149
|
0
|
|
|
0
|
1
|
0
|
my $self = shift;
|
150
|
0
|
|
|
|
|
0
|
my @other_chunks =
|
151
|
0
|
|
|
|
|
0
|
grep { $_->{type} ne 'IDAT' } $self->original_chunks;
|
152
|
0
|
|
|
|
|
0
|
my $data = $self->{_new_deflated};
|
153
|
|
|
|
|
|
|
|
154
|
0
|
|
|
|
|
0
|
my $idat = { type => 'IDAT', data => $data,
|
155
|
|
|
|
|
|
|
crc32 => Compress::Zlib::crc32("IDAT$data") };
|
156
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
0
|
my @chunks = map {
|
158
|
0
|
0
|
|
|
|
0
|
pack('Na4', length $_->{data}, $_->{type})
|
159
|
|
|
|
|
|
|
. $_->{data} . pack('N', $_->{crc32})
|
160
|
|
|
|
|
|
|
} map {
|
161
|
0
|
|
|
|
|
0
|
$_->{type} eq 'IEND' ? ($idat, $_) : $_
|
162
|
|
|
|
|
|
|
} @other_chunks;
|
163
|
|
|
|
|
|
|
|
164
|
0
|
|
|
|
|
0
|
return $PNG_MAGIC . join '', @chunks;
|
165
|
|
|
|
|
|
|
}
|
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub original_filters {
|
168
|
120
|
|
|
120
|
1
|
203
|
my $self = shift;
|
169
|
120
|
|
|
|
|
1395
|
map { ord(substr $self->{_inflated},
|
|
15420
|
|
|
|
|
39841
|
|
170
|
|
|
|
|
|
|
$_ * $self->{_scanline_width}, 1) }
|
171
|
|
|
|
|
|
|
0 .. $self->{_height} - 1;
|
172
|
|
|
|
|
|
|
}
|
173
|
|
|
|
|
|
|
|
174
|
0
|
|
|
0
|
1
|
0
|
sub original_chunks { @{ $_[0]->{_chunks} } }
|
|
0
|
|
|
|
|
0
|
|
175
|
60
|
|
|
60
|
1
|
1178
|
sub original_inflated { $_[0]->{_inflated} }
|
176
|
0
|
|
|
0
|
1
|
0
|
sub original_deflated { $_[0]->{_deflated} }
|
177
|
60
|
|
|
60
|
1
|
672
|
sub width { $_[0]->{_width} }
|
178
|
240
|
|
|
240
|
1
|
23703
|
sub height { $_[0]->{_height} }
|
179
|
60
|
|
|
60
|
1
|
336
|
sub color_mode { $_[0]->{_color} }
|
180
|
60
|
|
|
60
|
1
|
257
|
sub depth { $_[0]->{_depth} }
|
181
|
|
|
|
|
|
|
|
182
|
60
|
|
|
60
|
1
|
266
|
sub scanline_width { $_[0]->{_scanline_width} }
|
183
|
60
|
|
|
60
|
1
|
247
|
sub scanline_delta { $_[0]->{_scanline_delta} }
|
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
1;
|
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
__END__
|