File Coverage

blib/lib/Data/ParseBinary/Graphics/PNG.pm
Criterion Covered Total %
statement 15 20 75.0
branch 0 2 0.0
condition 1 2 50.0
subroutine 5 6 83.3
pod 0 2 0.0
total 21 32 65.6


line stmt bran cond sub pod time code
1             package Data::ParseBinary::Graphics::PNG;
2 1     1   1261 use strict;
  1         1  
  1         33  
3 1     1   4 use warnings;
  1         1  
  1         28  
4 1     1   4 use Data::ParseBinary;
  1         1  
  1         374  
5 1     1   7 use Data::ParseBinary qw{GreedyRange};
  1         2  
  1         1821  
6            
7             # Portable Network Graphics (PNG) file format
8             # Official spec: http://www.w3.org/TR/PNG
9             #
10             # Original code contributed by Robin Munn (rmunn at pobox dot com)
11             # (although the code has been extensively reorganized to meet Construct's
12             # coding conventions)
13            
14            
15             #===============================================================================
16             # utils
17             #===============================================================================
18             sub Coord {
19 4     4 0 6 my ($name, $field) = @_;
20 4   50     9 $field ||= \&UBInt8;
21 4         9 return Struct($name,
22             &$field("x"),
23             &$field("y"),
24             );
25             }
26            
27             my $compression_method = Enum(UBInt8("compression_method"),
28             deflate => 0,
29             _default_ => $DefaultPass
30             );
31            
32            
33             #===============================================================================
34             # 11.2.3: PLTE - Palette
35             #===============================================================================
36             my $plte_info = Struct("plte_info",
37             Value("num_entries", sub { $_->ctx(1)->{length} / 3}),
38             Array(sub { $_->ctx->{num_entries} },
39             Struct("palette_entries",
40             UBInt8("red"),
41             UBInt8("green"),
42             UBInt8("blue"),
43             ),
44             ),
45             );
46            
47             #===============================================================================
48             # 11.2.4: IDAT - Image data
49             #===============================================================================
50             #my $idat_info = OnDemand(
51             # Field("idat_info", sub { $_->ctx->{length} }),
52             #);
53             my $idat_info = Field("idat_info", sub { $_->ctx->{length} });
54            
55             #===============================================================================
56             # 11.3.2.1: tRNS - Transparency
57             #===============================================================================
58             my $trns_info = Switch("trns_info", sub { $_->ctx(1)->{image_header}->{color_type} },
59             {
60             "greyscale" => Struct("data",
61             UBInt16("grey_sample")
62             ),
63             "truecolor" => Struct("data",
64             UBInt16("red_sample"),
65             UBInt16("blue_sample"),
66             UBInt16("green_sample"),
67             ),
68             "indexed" => Array(sub { $_->ctx->{length} },
69             UBInt8("alpha"),
70             ),
71             }
72             );
73            
74             #===============================================================================
75             # 11.3.3.1: cHRM - Primary chromacities and white point
76             #===============================================================================
77             my $chrm_info = Struct("chrm_info",
78             Coord("white_point", \&UBInt32),
79             Coord("red", \&UBInt32),
80             Coord("green", \&UBInt32),
81             Coord("blue", \&UBInt32),
82             );
83            
84             #===============================================================================
85             # 11.3.3.2: gAMA - Image gamma
86             #===============================================================================
87             my $gama_info = Struct("gama_info",
88             UBInt32("gamma"),
89             );
90            
91             #===============================================================================
92             # 11.3.3.3: iCCP - Embedded ICC profile
93             #===============================================================================
94             my $iccp_info = Struct("iccp_info",
95             CString("name"),
96             $compression_method,
97             Field("compressed_profile",
98             sub { $_->ctx(1)->{length} - (length( $_->ctx->{name}) + 2) }
99             ),
100             );
101            
102             #===============================================================================
103             # 11.3.3.4: sBIT - Significant bits
104             #===============================================================================
105             my $sbit_info = Switch("sbit_info", sub { $_->ctx(1)->{image_header}->{color_type} },
106             {
107             "greyscale" => Struct("data",
108             UBInt8("significant_grey_bits"),
109             ),
110             "truecolor" => Struct("data",
111             UBInt8("significant_red_bits"),
112             UBInt8("significant_green_bits"),
113             UBInt8("significant_blue_bits"),
114             ),
115             "indexed" => Struct("data",
116             UBInt8("significant_red_bits"),
117             UBInt8("significant_green_bits"),
118             UBInt8("significant_blue_bits"),
119             ),
120             "greywithalpha" => Struct("data",
121             UBInt8("significant_grey_bits"),
122             UBInt8("significant_alpha_bits"),
123             ),
124             "truewithalpha" => Struct("data",
125             UBInt8("significant_red_bits"),
126             UBInt8("significant_green_bits"),
127             UBInt8("significant_blue_bits"),
128             UBInt8("significant_alpha_bits"),
129             ),
130             }
131             );
132            
133             #===============================================================================
134             # 11.3.3.5: sRGB - Standard RPG color space
135             #===============================================================================
136             my $srgb_info = Struct("srgb_info",
137             Enum(UBInt8("rendering_intent"),
138             perceptual => 0,
139             relative_colorimetric => 1,
140             saturation => 2,
141             absolute_colorimetric => 3,
142             _default_ => $DefaultPass,
143             ),
144             );
145            
146             #===============================================================================
147             # 11.3.4.3: tEXt - Textual data
148             #===============================================================================
149             my $text_info = Struct("text_info",
150             CString("keyword"),
151             Field("text", sub { $_->ctx(1)->{length} - (length($_->ctx->{keyword}) + 1) }),
152             );
153            
154             #===============================================================================
155             # 11.3.4.4: zTXt - Compressed textual data
156             #===============================================================================
157             my $ztxt_info = Struct("ztxt_info",
158             CString("keyword"),
159             $compression_method,
160             # OnDemand(
161             Field("compressed_text",
162             # As with iCCP, length is chunk length, minus length of
163             # keyword, minus two: one byte for the null terminator,
164             # and one byte for the compression method.
165             sub { $_->ctx(1)->{length} - (length($_->ctx->{keyword}) + 2) },
166             ),
167             # ),
168             );
169            
170             #===============================================================================
171             # 11.3.4.5: iTXt - International textual data
172             #===============================================================================
173             my $itxt_info = Struct("itxt_info",
174             CString("keyword"),
175             UBInt8("compression_flag"),
176             $compression_method,
177             CString("language_tag"),
178             CString("translated_keyword"),
179             # OnDemand(
180             Field("text",
181             sub { $_->ctx(1)->{length} - (length($_->ctx->{keyword}) + length($_->ctx->{language_tag}) + length($_->ctx->{translated_keyword}) + 5) },
182             ),
183             # ),
184             );
185            
186             #===============================================================================
187             # 11.3.5.1: bKGD - Background color
188             #===============================================================================
189             my $bkgd_info = Switch("bkgd_info", sub { $_->ctx(1)->{image_header}->{color_type} },
190             {
191             "greyscale" => Struct("data",
192             UBInt16("background_greyscale_value"),
193             Alias("grey", "background_greyscale_value"),
194             ),
195             "greywithalpha" => Struct("data",
196             UBInt16("background_greyscale_value"),
197             Alias("grey", "background_greyscale_value"),
198             ),
199             "truecolor" => Struct("data",
200             UBInt16("background_red_value"),
201             UBInt16("background_green_value"),
202             UBInt16("background_blue_value"),
203             Alias("red", "background_red_value"),
204             Alias("green", "background_green_value"),
205             Alias("blue", "background_blue_value"),
206             ),
207             "truewithalpha" => Struct("data",
208             UBInt16("background_red_value"),
209             UBInt16("background_green_value"),
210             UBInt16("background_blue_value"),
211             Alias("red", "background_red_value"),
212             Alias("green", "background_green_value"),
213             Alias("blue", "background_blue_value"),
214             ),
215             "indexed" => Struct("data",
216             UBInt16("background_palette_index"),
217             Alias("index", "background_palette_index"),
218             ),
219             }
220             );
221            
222             #===============================================================================
223             # 11.3.5.2: hIST - Image histogram
224             #===============================================================================
225             my $hist_info = Array(sub { $_->ctx(1)->{length} / 2 },
226             UBInt16("frequency"),
227             );
228            
229             #===============================================================================
230             # 11.3.5.3: pHYs - Physical pixel dimensions
231             #===============================================================================
232             my $phys_info = Struct("phys_info",
233             UBInt32("pixels_per_unit_x"),
234             UBInt32("pixels_per_unit_y"),
235             Enum(UBInt8("unit"),
236             unknown => 0,
237             meter => 1,
238             _default_ => $DefaultPass,
239             ),
240             );
241            
242             #===============================================================================
243             # 11.3.5.4: sPLT - Suggested palette
244             #===============================================================================
245             sub splt_info_data_length {
246 0     0 0   my $entry_size;
247 0 0         if ($_->ctx->{sample_depth} == 8) {
248 0           $entry_size = 6;
249             } else {
250 0           $entry_size = 10;
251             }
252 0           return ($_->ctx(1)->{length} - length($_->ctx->{name}) - 2) / $entry_size;
253             }
254            
255             my $splt_info = Struct("data",
256             CString("name"),
257             UBInt8("sample_depth"),
258             Array(\&splt_info_data_length,
259             IfThenElse("table", sub { $_->ctx->{sample_depth} == 8 },
260             # Sample depth 8
261             Struct("table",
262             UBInt8("red"),
263             UBInt8("green"),
264             UBInt8("blue"),
265             UBInt8("alpha"),
266             UBInt16("frequency"),
267             ),
268             # Sample depth 16
269             Struct("table",
270             UBInt16("red"),
271             UBInt16("green"),
272             UBInt16("blue"),
273             UBInt16("alpha"),
274             UBInt16("frequency"),
275             ),
276             ),
277             ),
278             );
279            
280             #===============================================================================
281             # 11.3.6.1: tIME - Image last-modification time
282             #===============================================================================
283             my $time_info = Struct("data",
284             UBInt16("year"),
285             UBInt8("month"),
286             UBInt8("day"),
287             UBInt8("hour"),
288             UBInt8("minute"),
289             UBInt8("second"),
290             );
291            
292             #===============================================================================
293             # chunks
294             #===============================================================================
295             my $default_chunk_info =
296             # OnDemand(HexDumpAdapter(
297             Field(undef, sub {$_->ctx->{length} }
298             # ))
299             );
300            
301             my $chunk = Struct("chunk",
302             UBInt32("length"),
303             String("type", 4),
304             Switch("data", sub { $_->ctx->{type} },
305             {
306             "PLTE" => $plte_info,
307             "IEND" => $DefaultPass,
308             "IDAT" => $idat_info,
309             "tRNS" => $trns_info,
310             "cHRM" => $chrm_info,
311             "gAMA" => $gama_info,
312             "iCCP" => $iccp_info,
313             "sBIT" => $sbit_info,
314             "sRGB" => $srgb_info,
315             "tEXt" => $text_info,
316             "zTXt" => $ztxt_info,
317             "iTXt" => $itxt_info,
318             "bKGD" => $bkgd_info,
319             "hIST" => $hist_info,
320             "pHYs" => $phys_info,
321             "sPLT" => $splt_info,
322             "tIME" => $time_info,
323             },
324             default => $default_chunk_info,
325             ),
326             UBInt32("crc"),
327             );
328            
329             my $image_header_chunk = Struct("image_header",
330             UBInt32("length"),
331             Const(String("type", 4), "IHDR"),
332             UBInt32("width"),
333             UBInt32("height"),
334             UBInt8("bit_depth"),
335             Enum(UBInt8("color_type"),
336             greyscale => 0,
337             truecolor => 2,
338             indexed => 3,
339             greywithalpha => 4,
340             truewithalpha => 6,
341             _default_ => $DefaultPass,
342             ),
343             $compression_method,
344             Enum(UBInt8("filter_method"),
345             # "adaptive filtering with five basic filter types"
346             adaptive5 => 0,
347             _default_ => $DefaultPass,
348             ),
349             Enum(UBInt8("interlace_method"),
350             none => 0,
351             adam7 => 1,
352             _default_ => $DefaultPass,
353             ),
354             UBInt32("crc"),
355             );
356            
357            
358             #===============================================================================
359             # the complete PNG file
360             #===============================================================================
361             our $png_parser = Struct("png",
362             Magic("\x89PNG\r\n\x1a\n"),
363             $image_header_chunk,
364             GreedyRange($chunk),
365             );
366            
367             require Exporter;
368             our @ISA = qw(Exporter);
369             our @EXPORT = qw($png_parser);
370            
371             1;
372            
373             __END__