File Coverage

blib/lib/Image/Pngslimmer.pm
Criterion Covered Total %
statement 814 974 83.5
branch 176 300 58.6
condition 28 39 71.7
subroutine 42 44 95.4
pod 0 38 0.0
total 1060 1395 75.9


line stmt bran cond sub pod time code
1             package Image::Pngslimmer;
2              
3 9     9   30029 use 5.008004;
  9         33  
  9         372  
4 9     9   46 use strict;
  9         13  
  9         331  
5 9     9   47 use warnings;
  9         23  
  9         243  
6 9     9   16971 use Compress::Zlib;
  9         816684  
  9         2480  
7 9     9   85 use Compress::Raw::Zlib;
  9         18  
  9         1383  
8 9     9   9166 use POSIX();
  9         85958  
  9         95270  
9              
10             require Exporter;
11              
12             our @ISA = qw(Exporter);
13              
14             # Items to export into callers namespace by default. Note: do not export
15             # names by default without a very good reason. Use EXPORT_OK instead.
16             # Do not simply export all your public functions/methods/constants.
17              
18             # This allows declaration use Image::Pngslimmer ':all';
19             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
20             # will save memory.
21             our %EXPORT_TAGS = ( 'all' => [qw()] );
22              
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24              
25             our @EXPORT = qw(
26              
27             );
28              
29             our $VERSION = '0.30';
30              
31             sub checkcrc {
32 196     196 0 455828 my $chunk = shift;
33 196         216 my ( $chunklength, $subtocheck, $generatedcrc, $readcrc );
34              
35             #get length of data
36 196         366 $chunklength = unpack( "N", substr( $chunk, 0, 4 ) );
37 196         471 $subtocheck = substr( $chunk, 4, $chunklength + 4 );
38 196         930 $generatedcrc = crc32($subtocheck);
39 196         313 $readcrc = unpack( "N", substr( $chunk, $chunklength + 8, 4 ) );
40 196 50       470 if ( $generatedcrc eq $readcrc ) { return 1; }
  196         14278  
41              
42             #don't match
43 0         0 return 0;
44             }
45              
46             sub ispng {
47 28     28 0 450 my ( $pngsig, $startpng, $ihdr_len, $pnglength, $searchindex );
48 0         0 my ( $idatfound, $nextindex );
49 28         46 my $blob = shift;
50              
51             #check for signature
52 28         61 $pngsig = pack( "C8", ( 137, 80, 78, 71, 13, 10, 26, 10 ) );
53 28         60 $startpng = substr( $blob, 0, 8 );
54 28 100       93 if ( $startpng ne $pngsig ) {
55 1         8 return 0;
56             }
57              
58             #check for IHDR
59 27 50       85 if ( substr( $blob, 12, 4 ) ne "IHDR" ) {
60 0         0 return 0;
61             }
62 27 50       109 if ( checkcrc( substr( $blob, 8 ) ) < 1 ) {
63 0         0 return 0;
64             }
65 27         93 $ihdr_len = unpack( "N", substr( $blob, 8, 4 ) );
66              
67             #check for IDAT - scanning CRCs as we go
68             #scan through all the chunks looking for an IDAT header
69 27         42 $pnglength = length($blob);
70              
71             #start searching from end of IHDR chunk
72 27         47 $searchindex = 16 + $ihdr_len + 4 + 4;
73 27         40 $idatfound = 0;
74 27         81 while ( $searchindex < ( $pnglength - 4 ) ) {
75 74 50       173 if ( checkcrc( substr( $blob, $searchindex - 4 ) ) < 1 ) {
76 0         0 return 0;
77             }
78 74 100       228 if ( substr( $blob, $searchindex, 4 ) eq "IDAT" ) {
79 27         35 $idatfound = 1;
80 27         46 last;
81             }
82 47         79 $nextindex = unpack( "N", substr( $blob, $searchindex - 4, 4 ) );
83 47 50       92 if ( $nextindex == 0 ) {
84 0         0 $searchindex += 5; #after a CRC if there is an empty
85             #chunk
86             }
87             else {
88 47         112 $searchindex += ( $nextindex + 4 + 4 + 4 );
89             }
90             }
91 27 50       67 if ( $idatfound == 0 ) {
92 0         0 return 0;
93             }
94              
95             #check for IEND chunk
96             #check CRC first
97 27 50       93 if ( checkcrc( substr( $blob, $pnglength - 12 ) ) < 1 ) { return 0; }
  0         0  
98 27 50       143 if ( substr( $blob, $pnglength - 8, 4 ) ne "IEND" ) {
99 0         0 return 0;
100             }
101              
102 27         112 return 1;
103             }
104              
105             sub shrinkchunk {
106 6     6 0 354 my ( $bitblob, $blobout, $status, $y );
107 6         22 my ( $blobin, $strategy, $level ) = @_;
108 6 50       33 unless ( defined($level) ) {
109 0         0 $level = Z_BEST_COMPRESSION;
110             }
111 6 50       170 if ( $strategy eq "Z_FILTERED" ) {
112 0         0 ( $y, $status ) = new Compress::Raw::Zlib::Deflate(
113             -Level => $level,
114             -WindowBits => -&MAX_WBITS(),
115             -Bufsize => 0x1000,
116             -Strategy => Z_FILTERED,
117             -AppendOutput => 1
118             );
119             }
120             else {
121 6         45 ( $y, $status ) = new Compress::Raw::Zlib::Deflate(
122             -Level => $level,
123             -WindowBits => -&MAX_WBITS(),
124             -Bufsize => 0x1000,
125             -AppendOutput => 1
126             );
127             }
128 6 50       2400791 unless ( $status == Z_OK ) {
129 0         0 return $blobin;
130             }
131 6         3545 $status = $y->deflate( $blobin, $bitblob );
132 6 50       35 unless ( $status == Z_OK ) {
133 0         0 return $blobin;
134             }
135 6         792 $status = $y->flush($bitblob);
136 6         28 $blobout = $blobout . $bitblob;
137 6 50       28 unless ( $status == Z_OK ) {
138 0         0 return $blobin;
139             }
140 6         455 return $blobout;
141             }
142              
143             sub getuncompressed_data {
144 8     8 0 28 my ( $output, $puredata, @idats, $x, $status, $outputlump );
145 0         0 my ( $calc_crc, $uncompcrc, $searchindex );
146 0         0 my ( $chunklength, $numberofidats, $chunknumber, $outlength );
147 8         14 my $blobin = shift;
148 8         17 my $pnglength = length($blobin);
149 8         14 $searchindex = 8 + 25; #start looking at the end of the IHDR
150 8         26 while ( $searchindex < ( $pnglength - 8 ) ) {
151 24         49 $chunklength = unpack( "N", substr( $blobin, $searchindex, 4 ) );
152 24 100       69 if ( substr( $blobin, $searchindex + 4, 4 ) eq "IDAT" ) {
153 8         35 push( @idats, $searchindex );
154             }
155 24         48 $searchindex += $chunklength + 12;
156             }
157 8         12 $numberofidats = @idats;
158 8 50       24 if ( $numberofidats == 0 ) {
159 0         0 return undef;
160             }
161 8         12 $chunknumber = 0;
162 8         23 while ( $chunknumber < $numberofidats ) {
163 8         22 $chunklength =
164             unpack( "N", substr( $blobin, $idats[$chunknumber], 4 ) );
165 8 50       26 if ( $chunknumber == 0 ) {
166 8 50       21 if ( $numberofidats == 1 ) {
167 8         66 $output = substr( $blobin, $idats[0] + 10, $chunklength - 2 );
168 8         15 last;
169             }
170             else {
171 0         0 $output = substr( $blobin, $idats[0] + 10, $chunklength - 2 );
172             }
173             }
174             else {
175 0 0       0 if ( ( $numberofidats - 1 ) == $chunknumber ) {
176 0         0 $puredata =
177             substr( $blobin, $idats[$chunknumber] + 8, $chunklength );
178 0         0 $output = $output . $puredata;
179 0         0 last;
180             }
181             else {
182 0         0 $puredata =
183             substr( $blobin, $idats[$chunknumber] + 8, $chunklength );
184 0         0 $output = $output . $puredata;
185             }
186             }
187 0         0 $chunknumber++;
188             }
189              
190             #have the output chunk now uncompress it
191 8 50       75 $x = new Compress::Raw::Zlib::Inflate(
192             -WindowBits => -&MAX_WBITS(),
193             -ADLER32 => 1,
194             -AppendOutput => 1
195             ) or return undef;
196 8         3197 $outlength = length($output);
197 8         27 $uncompcrc = unpack( "N", substr( $output, $outlength - 4 ) );
198 8         698 $status = $x->inflate( substr( $output, 0, $outlength - 4 ), $outputlump );
199 8 50       34 unless ( defined($outputlump) ) {
200 0         0 return undef;
201             }
202 8         56 $calc_crc = $x->adler32();
203 8 50       29 if ( $calc_crc != $uncompcrc ) {
204 0         0 return undef;
205             }
206 8         98 return $outputlump; # done
207             }
208              
209             sub crushdatachunk {
210              
211             #look to inner stream, uncompress that, then recompress
212 2     2 0 5 my ( $chunkin, $blobin ) = @_;
213 2         10 my $output = getuncompressed_data($blobin);
214 2 50       11 unless ( defined($output) ) {
215 0         0 return $chunkin;
216             }
217 2         5 my $rawlength = length($output);
218 2         19 my $purecrc = adler32($output);
219              
220             # now crush it at the maximum level
221 2         15 my $crusheddata = shrinkchunk( $output, Z_FILTERED, Z_BEST_COMPRESSION );
222 2         6 my $lencompo = length($crusheddata);
223 2 50       9 unless ( length($crusheddata) < $rawlength ) {
224 0         0 $crusheddata =
225             shrinkchunk( $output, Z_DEFAULT_STRATEGY, Z_BEST_COMPRESSION );
226             }
227 2         5 my $newlength = length($crusheddata) + 6;
228              
229             #now we have compressed the data, write the chunk
230 2         9 my $chunkout = pack( "N", $newlength );
231 2         4 my $rfc1950stuff = pack( "C2", ( 0x78, 0xDA ) );
232 2         14 my $output2 = "IDAT" . $rfc1950stuff . $crusheddata . pack( "N", $purecrc );
233 2         51 my $outcrc = crc32($output2);
234 2         18 $chunkout = $chunkout . $output2 . pack( "N", $outcrc );
235 2         8 return $chunkout;
236             }
237              
238             sub zlibshrink {
239 2     2 0 184 my $chunktocopy;
240 2         5 my ( $chunklength, $processedchunk, $lenidat );
241 2         4 my $blobin = shift;
242              
243             #find the data chunks
244             #decompress and then recompress
245             #work out the CRC and write it out
246             #but first check it is actually a PNG
247 2 50       8 if ( ispng($blobin) < 1 ) {
248 0         0 return undef;
249             }
250 2         4 my $pnglength = length($blobin);
251 2         5 my $ihdr_len = unpack( "N", substr( $blobin, 8, 4 ) );
252 2         5 my $searchindex = 16 + $ihdr_len + 4 + 4;
253              
254             #copy the start of the incoming blob
255 2         6 my $blobout = substr( $blobin, 0, 16 + $ihdr_len + 4 );
256 2         2 my $idatfound = 0;
257 2         8 while ( $searchindex < ( $pnglength - 4 ) ) {
258              
259             #Copy the chunk
260 8         20 $chunklength = unpack( "N", substr( $blobin, $searchindex - 4, 4 ) );
261 8         23 $chunktocopy = substr( $blobin, $searchindex - 4, $chunklength + 12 );
262 8 100       19 if ( substr( $blobin, $searchindex, 4 ) eq "IDAT" ) {
263 2 50       30 if ( $idatfound == 0 ) {
264 2         9 $processedchunk = crushdatachunk( $chunktocopy, $blobin );
265 2         5 $chunktocopy = $processedchunk;
266 2         3 $idatfound = 1;
267             }
268             else {
269 0         0 $chunktocopy = "";
270             }
271             }
272              
273 8         8 $lenidat = length($chunktocopy);
274 8         44 $blobout = $blobout . $chunktocopy;
275 8         18 $searchindex += $chunklength + 12;
276             }
277 2         12 return $blobout;
278             }
279              
280             sub linebyline {
281              
282             #analyze the data line by line
283 1     1 0 1 my ( $count, $return_filtered, $filtertype );
284 1         3 my ( $data, $ihdr ) = @_;
285 1         2 my $width = $ihdr->{"imagewidth"};
286 1         3 my $height = $ihdr->{"imageheight"};
287 1         2 my $depth = $ihdr->{"bitdepth"};
288 1         1 my $colourtype = $ihdr->{"colourtype"};
289 1 50 33     9 if ( ( $colourtype != 2 ) || ( $depth != 8 ) ) {
290 0         0 return -1;
291             }
292 1         2 $count = 0;
293 1         2 $return_filtered = 1;
294 1         3 while ( $count < $height ) {
295 32         48 $filtertype =
296             unpack( "C1", substr( $data, $count * $width * 3 + $count, 1 ) );
297 32 50       50 if ( $filtertype != 0 ) {
298              
299             #already filtered
300 0         0 $return_filtered = -1;
301 0         0 last;
302             }
303 32         53 $count++;
304             }
305 1         2 return $return_filtered; #can be filtered?
306             }
307              
308             sub comp_width {
309            
310             # ctypes:
311             # 0: greyscale
312             # 2: truecolour
313             # 3: indexed-colour
314             # 4: greyscale plus alpha
315             # 6: truecolour plus alpha
316              
317 18     18 0 39 my $ihdr = shift;
318 18         35 my $comp_width = 3;
319 18         25 my $alpha = 0;
320 18         38 my $ctype = $ihdr->{"colourtype"};
321 18         41 my $bdepth = $ihdr->{"bitdepth"};
322 18 50       51 if ( $ctype == 2 ) { #truecolour with no alpha
    0          
    0          
    0          
323 18 50       41 if ( $bdepth == 8 ) {
324 18         39 $comp_width = 3;
325             }
326             else {
327 0         0 $comp_width = 6;
328             }
329             }
330             elsif ( $ctype == 0 ) {
331 0 0       0 if ( $bdepth == 16 ) {
    0          
    0          
    0          
    0          
332              
333             #16 bit greyscale
334 0         0 $comp_width = 2;
335             }
336             elsif ( $bdepth == 8 ) {
337              
338             #8 bit greyscale
339 0         0 $comp_width = 1;
340             }
341              
342             #less than a byte greyscale
343             elsif ( $bdepth == 4 ) {
344 0         0 $comp_width = 0.5;
345             }
346             elsif ( $bdepth == 2 ) {
347 0         0 $comp_width = 0.25;
348             }
349             elsif ( $bdepth == 1 ) {
350 0         0 $comp_width = 0.125;
351             }
352             }
353             elsif ( $ctype == 4 ) { #grayscale with alpha
354 0         0 $alpha = 1;
355 0 0       0 if ( $bdepth == 8 ) {
356 0         0 $comp_width = 2;
357             }
358             else {
359 0         0 $comp_width = 4;
360             }
361             }
362             elsif ( $ctype == 6 ) { #truecolour with alpha
363 0         0 $alpha = 1;
364 0 0       0 if ( $bdepth == 8 ) {
365 0         0 $comp_width = 4;
366             }
367             else {
368 0         0 $comp_width = 8;
369             }
370             }
371              
372 18         44 return ( $comp_width, $alpha );
373             }
374              
375             sub filter_sub {
376              
377             #filter data schunk using Sub type
378             #http://www.w3.org/TR/PNG/#9Filters
379             #Filt(x) = Orig(x) - Orig(a)
380             #x is byte to be filtered, a is byte to left
381 1     1 0 2 my ( $origbyte, $leftbyte );
382 1         2 my $unfiltereddata = shift;
383 1         2 my $ihdr = shift;
384 1         2 my $count = 0;
385 1         2 my $count_width = 0;
386 1         1 my $newbyte = 0;
387 1         4 my ( $comp_width, $alpha ) = comp_width($ihdr);
388 1         2 my $totalwidth = $ihdr->{"imagewidth"} * $comp_width;
389 1         2 my $filtereddata = "";
390 1         2 my $lines = $ihdr->{"imageheight"};
391              
392 1         4 while ( $count < $lines ) {
393              
394             #start - add filtertype byte
395 32         33 $filtereddata = $filtereddata . "\1";
396 32         53 while ( $count_width < $totalwidth ) {
397 3072         4213 $origbyte = unpack(
398             "C",
399             substr(
400             $unfiltereddata,
401             1 + ( $count * $totalwidth ) + $count_width + $count, 1
402             )
403             );
404 3072 100       4021 if ( $count_width < $comp_width ) {
405 96         90 $leftbyte = 0;
406             }
407             else {
408 2976         4213 $leftbyte = unpack(
409             "C",
410             substr(
411             $unfiltereddata,
412             1 + $count + ( $count * $totalwidth ) + $count_width -
413             $comp_width,
414             1
415             )
416             );
417             }
418 3072         3163 $newbyte = ( $origbyte - $leftbyte ) % 256;
419 3072         3176 $filtereddata = $filtereddata . pack( "C", $newbyte );
420 3072         4780 $count_width++;
421             }
422 32         53 $count_width = 0;
423 32         52 $count++;
424             }
425 1         18 return $filtereddata;
426             }
427              
428             sub filter_up {
429              
430             #filter data schunk using Up type
431 1     1 0 2 my ( $origbyte, $upbyte );
432 1         2 my $unfiltereddata = shift;
433 1         2 my $ihdr = shift;
434 1         4 my ( $comp_width, $alpha ) = comp_width($ihdr);
435 1         35 my $count = 0;
436 1         2 my $count_width = 0;
437 1         2 my $newbyte = 0;
438 1         2 my $totalwidth = $ihdr->{"imagewidth"} * $comp_width;
439 1         8 my $filtereddata = "";
440 1         3 my $lines = $ihdr->{"imageheight"};
441 1         3 while ( $count < $lines ) {
442              
443             #start - add filtertype byte
444 32         36 $filtereddata = $filtereddata . "\2";
445 32         56 while ( $count_width < $totalwidth ) {
446 3072         4217 $origbyte = unpack(
447             "C",
448             substr(
449             $unfiltereddata,
450             1 + ( $count * $totalwidth ) + $count_width + $count, 1
451             )
452             );
453 3072 100       4057 if ( $count == 0 ) {
454 96         92 $upbyte = 0;
455             }
456             else {
457 2976         4190 $upbyte = unpack(
458             "C",
459             substr(
460             $unfiltereddata,
461             $count + ( ( $count - 1 ) * $totalwidth ) +
462             $count_width,
463             1
464             )
465             );
466             }
467 3072         3256 $newbyte = ( $origbyte - $upbyte ) % 256;
468 3072         3369 $filtereddata = $filtereddata . pack( "C", $newbyte );
469 3072         4651 $count_width++;
470             }
471 32         32 $count_width = 0;
472 32         45 $count++;
473             }
474 1         8 return $filtereddata;
475             }
476              
477             sub filter_ave {
478              
479             #filter data schunk using Ave type
480 1     1 0 1 my ( $origbyte, $avebyte );
481 0         0 my ( $top_predictor, $left_predictor );
482 1         2 my $unfiltereddata = shift;
483 1         3 my $ihdr = shift;
484 1         4 my ( $comp_width, $alpha ) = comp_width($ihdr);
485 1         2 my $count = 0;
486 1         2 my $count_width = 0;
487 1         2 my $newbyte = 0;
488 1         2 my $totalwidth = $ihdr->{"imagewidth"} * $comp_width;
489 1         3 my $filtereddata = "";
490 1         3 my $lines = $ihdr->{"imageheight"};
491 1         5 while ( $count < $lines ) {
492              
493             #start - add filtertype byte
494 32         34 $filtereddata = $filtereddata . "\3";
495 32         51 while ( $count_width < $totalwidth ) {
496 3072         4514 $origbyte = unpack(
497             "C",
498             substr(
499             $unfiltereddata,
500             1 + ( $count * $totalwidth ) + $count_width + $count, 1
501             )
502             );
503 3072 100       4239 if ( $count > 0 ) {
504 2976         4257 $top_predictor = unpack(
505             "C",
506             substr(
507             $unfiltereddata,
508             $count + ( ( $count - 1 ) * $totalwidth ) +
509             $count_width,
510             1
511             )
512             );
513             }
514 96         96 else { $top_predictor = 0; }
515 3072 100       4221 if ( $count_width >= $comp_width ) {
516 2976         4502 $left_predictor = unpack(
517             "C",
518             substr(
519             $unfiltereddata,
520             1 + $count + ( $count * $totalwidth ) + $count_width -
521             $comp_width,
522             1
523             )
524             );
525             }
526             else {
527 96         92 $left_predictor = 0;
528             }
529 3072         3402 $avebyte = ( $top_predictor + $left_predictor ) / 2;
530 3072         4109 $avebyte = POSIX::floor($avebyte);
531 3072         3154 $newbyte = ( $origbyte - $avebyte ) % 256;
532 3072         3595 $filtereddata = $filtereddata . pack( "C", $newbyte );
533 3072         4872 $count_width++;
534             }
535 32         32 $count_width = 0;
536 32         58 $count++;
537             }
538 1         32 return $filtereddata;
539             }
540              
541             sub filter_paeth { #paeth predictor type filtering
542 1     1 0 3 my ( $origbyte, $paethbyte_a, $paethbyte_b, $paethbyte_c, $paeth_p );
543 0         0 my ( $paeth_pa, $paeth_pb, $paeth_pc, $paeth_predictor );
544 1         3 my $unfiltereddata = shift;
545 1         2 my $ihdr = shift;
546 1         5 my ( $comp_width, $alpha ) = comp_width($ihdr);
547 1         3 my $count = 0;
548 1         1 my $count_width = 0;
549 1         2 my $newbyte = 0;
550 1         3 my $totalwidth = $ihdr->{"imagewidth"} * $comp_width;
551 1         4 my $filtereddata = "";
552 1         2 my $lines = $ihdr->{"imageheight"};
553 1         5 while ( $count < $lines ) {
554              
555             #start - add filtertype byte
556 32         32 $filtereddata = $filtereddata . "\4";
557 32         79 while ( $count_width < $totalwidth ) {
558 3072         4904 $origbyte = unpack(
559             "C",
560             substr(
561             $unfiltereddata,
562             1 + ( $count * $totalwidth ) + $count_width + $count, 1
563             )
564             );
565 3072 100       4412 if ( $count > 0 ) {
566 2976         4549 $paethbyte_b = unpack(
567             "C",
568             substr(
569             $unfiltereddata,
570             $count + ( ( $count - 1 ) * $totalwidth ) +
571             $count_width,
572             1
573             )
574             );
575             }
576 96         95 else { $paethbyte_b = 0; }
577 3072 100       4369 if ( $count_width >= $comp_width ) {
578 2976         4900 $paethbyte_a = unpack(
579             "C",
580             substr(
581             $unfiltereddata,
582             1 + $count + ( $count * $totalwidth ) + $count_width -
583             $comp_width,
584             1
585             )
586             );
587             }
588             else {
589 96         98 $paethbyte_a = 0;
590             }
591 3072 100 100     9875 if ( ( $count_width >= $comp_width ) && ( $count > 0 ) ) {
592 2883         4722 $paethbyte_c = unpack(
593             "C",
594             substr(
595             $unfiltereddata,
596             $count + ( ( $count - 1 ) * $totalwidth ) +
597             $count_width - $comp_width,
598             1
599             )
600             );
601             }
602             else {
603 189         183 $paethbyte_c = 0;
604             }
605 3072         6160 $paeth_p = $paethbyte_a + $paethbyte_b - $paethbyte_c;
606 3072         3058 $paeth_pa = abs( $paeth_p - $paethbyte_a );
607 3072         2936 $paeth_pb = abs( $paeth_p - $paethbyte_b );
608 3072         2901 $paeth_pc = abs( $paeth_p - $paethbyte_c );
609 3072 100 100     9379 if ( ( $paeth_pa <= $paeth_pb )
    100          
610             && ( $paeth_pa <= $paeth_pc ) )
611             {
612 1849         1916 $paeth_predictor = $paethbyte_a;
613             }
614             elsif ( $paeth_pb <= $paeth_pc ) {
615 361         358 $paeth_predictor = $paethbyte_b;
616             }
617             else {
618 862         1042 $paeth_predictor = $paethbyte_c;
619             }
620 3072         3302 $newbyte = ( $origbyte - $paeth_predictor ) % 256;
621 3072         3893 $filtereddata = $filtereddata . pack( "C", $newbyte );
622 3072         6058 $count_width++;
623             }
624 32         34 $count_width = 0;
625 32         53 $count++;
626             }
627 1         25 return $filtereddata;
628             }
629              
630             sub filterdata {
631 1     1 0 2 my ( $filtereddata, $finalfiltered );
632 1         2 my $unfiltereddata = shift;
633 1         2 my $ihdr = shift;
634 1         7 my $filtered_sub = filter_sub( $unfiltereddata, $ihdr );
635 1         5 my $filtered_up = filter_up( $unfiltereddata, $ihdr );
636 1         5 my $filtered_ave = filter_ave( $unfiltereddata, $ihdr );
637 1         7 my $filtered_paeth = filter_paeth( $unfiltereddata, $ihdr );
638              
639 1         7 my $pixels = $ihdr->{"imagewidth"};
640 1         4 my $rows = $ihdr->{"imageheight"};
641 1         5 my ( $comp_width, $alpha ) = comp_width($ihdr);
642 1         4 my $bytesperline = $pixels * $comp_width;
643 1         2 my $countout = 0;
644 1         2 my $rows_done = 0;
645 1         3 my $count_sub = 0;
646 1         2 my $count_up = 0;
647 1         4 my $count_ave = 0;
648 1         3 my $count_zero = 0;
649 1         2 my $count_paeth = 0;
650 1         6 while ( $rows_done < $rows ) {
651              
652 32         60 while ( ($countout) < $bytesperline ) {
653 3072         4378 $count_sub += unpack(
654             "c",
655             substr(
656             $filtered_sub,
657             1 + ( $rows_done * $bytesperline ) + $countout + $rows_done,
658             1
659             )
660             );
661 3072         4413 $count_up += unpack(
662             "c",
663             substr(
664             $filtered_up,
665             1 + ( $rows_done * $bytesperline ) + $countout + $rows_done,
666             1
667             )
668             );
669 3072         4146 $count_ave += unpack(
670             "c",
671             substr(
672             $filtered_ave,
673             1 + ( $rows_done * $bytesperline ) + $countout + $rows_done,
674             1
675             )
676             );
677 3072         4378 $count_zero += unpack(
678             "c",
679             substr(
680             $unfiltereddata,
681             1 + ( $rows_done * $bytesperline ) + $countout + $rows_done,
682             1
683             )
684             );
685 3072         4335 $count_paeth += unpack(
686             "c",
687             substr(
688             $filtered_paeth,
689             1 + ( $rows_done * $bytesperline ) + $countout + $rows_done,
690             1
691             )
692             );
693 3072         5037 $countout++;
694             }
695 32         34 $count_paeth = abs($count_paeth);
696 32         31 $count_zero = abs($count_zero);
697 32         37 $count_ave = abs($count_ave);
698 32         32 $count_up = abs($count_up);
699 32         29 $count_sub = abs($count_sub);
700 32 100 100     252 if ( ( $count_paeth <= $count_zero )
    100 100        
    100 100        
    50 100        
      100        
      66        
701             && ( $count_paeth <= $count_sub )
702             && ( $count_paeth <= $count_up )
703             && ( $count_paeth <= $count_ave ) )
704             {
705 22         59 $finalfiltered = $finalfiltered
706             . substr(
707             $filtered_paeth,
708             $rows_done + $rows_done * $bytesperline,
709             $bytesperline + 1
710             );
711             }
712             elsif (( $count_ave <= $count_zero )
713             && ( $count_ave <= $count_sub )
714             && ( $count_ave <= $count_up ) )
715             {
716 1         3 $finalfiltered = $finalfiltered
717             . substr(
718             $filtered_ave,
719             $rows_done + $rows_done * $bytesperline,
720             $bytesperline + 1
721             );
722             }
723             elsif (( $count_up <= $count_zero )
724             && ( $count_up <= $count_sub ) )
725             {
726 5         13 $finalfiltered = $finalfiltered
727             . substr(
728             $filtered_up,
729             $rows_done + $rows_done * $bytesperline,
730             $bytesperline + 1
731             );
732             }
733             elsif ( $count_sub <= $count_zero ) {
734 4         11 $finalfiltered = $finalfiltered
735             . substr(
736             $filtered_sub,
737             $rows_done + $rows_done * $bytesperline,
738             $bytesperline + 1
739             );
740             }
741             else {
742 0         0 $finalfiltered = $finalfiltered
743             . substr(
744             $unfiltereddata,
745             $rows_done + $rows_done * $bytesperline,
746             $bytesperline + 1
747             );
748             }
749 32         38 $countout = 0;
750 32         33 $count_up = 0;
751 32         31 $count_sub = 0;
752 32         29 $count_zero = 0;
753 32         27 $count_ave = 0;
754 32         34 $count_paeth = 0;
755 32         52 $rows_done++;
756             }
757 1         31 return $finalfiltered;
758             }
759              
760             sub getihdr {
761 11     11 0 17 my %ihdr;
762 11         18 my $blobin = shift;
763 11         39 $ihdr{"imagewidth"} = unpack( "N", substr( $blobin, 16, 4 ) );
764 11         24 $ihdr{"imageheight"} = unpack( "N", substr( $blobin, 20, 4 ) );
765 11         28 $ihdr{"bitdepth"} = unpack( "C", substr( $blobin, 24, 1 ) );
766 11         41 $ihdr{"colourtype"} = unpack( "C", substr( $blobin, 25, 1 ) );
767 11         26 $ihdr{"compression"} = unpack( "C", substr( $blobin, 26, 1 ) );
768 11         26 $ihdr{"filter"} = unpack( "C", substr( $blobin, 27, 1 ) );
769 11         22 $ihdr{"interlace"} = unpack( "C", substr( $blobin, 28, 1 ) );
770 11         36 return \%ihdr;
771             }
772              
773             sub filter {
774 1     1 0 8 my ( $chunklength, $chunktocopy, $rfc1950stuff, $output, $newlength );
775 0         0 my ( $outcrc, $processedchunk, $filtereddata );
776 1         3 my $blobin = shift;
777              
778             #basic check so we do not waste our time
779 1 50       3 if ( ispng($blobin) < 1 ) {
780 0         0 return undef;
781             }
782              
783             #read some basic info about the PNG
784 1         3 my $ihdr = getihdr($blobin);
785 1 50       14 if ( $ihdr->{"colourtype"} == 3 ) {
786              
787             #already palettized
788 0         0 return $blobin;
789             }
790 1 50       6 if ( $ihdr->{"bitdepth"} < 8 ) {
791              
792             #colour depth too low to be worth it
793 0         0 return $blobin;
794             }
795 1 50       4 if ( $ihdr->{"compression"} != 0 ) {
796              
797             #non-standard compression
798 0         0 return $blobin;
799             }
800 1 50       6 if ( $ihdr->{"filter"} != 0 ) {
801              
802             #non-standard filtering
803 0         0 return $blobin;
804             }
805 1 50       5 if ( $ihdr->{"interlace"} != 0 ) {
806              
807             #FIXME: support interlacing
808 0         0 return $blobin;
809             }
810 1         14 my $datachunk = getuncompressed_data($blobin);
811 1 50       5 unless ( defined($datachunk) ) {
812 0         0 return $blobin;
813             }
814 1         5 my $canfilter = linebyline( $datachunk, $ihdr );
815 1 50       4 if ( $canfilter > 0 ) {
816 1         4 $filtereddata = filterdata( $datachunk, $ihdr );
817             }
818             else {
819 0         0 return $blobin;
820             }
821              
822             #Now stick the uncompressed data into a chunk
823             #and return - leaving the compression to a different process
824 1         23 my $filteredcrc = adler32($filtereddata);
825 1         18 $filtereddata = shrinkchunk( $filtereddata, Z_FILTERED, Z_BEST_SPEED );
826 1         3 my $filterlen = length($filtereddata);
827              
828             #now push the data into the PNG
829 1         2 my $pnglength = length($blobin);
830 1         5 my $ihdr_len = unpack( "N", substr( $blobin, 8, 4 ) );
831 1         2 my $searchindex = 16 + $ihdr_len + 4 + 4;
832              
833             #copy the start of the incoming blob
834 1         3 my $blobout = substr( $blobin, 0, 16 + $ihdr_len + 4 );
835 1         2 my $foundidat = 0;
836 1         4 while ( $searchindex < ( $pnglength - 4 ) ) {
837              
838             #Copy the chunk
839 2         5 $chunklength = unpack( "N", substr( $blobin, $searchindex - 4, 4 ) );
840 2         7 $chunktocopy = substr( $blobin, $searchindex - 4, $chunklength + 12 );
841 2 100       7 if ( substr( $blobin, $searchindex, 4 ) eq "IDAT" ) {
842 1 50       5 if ( $foundidat == 0 ) {
843              
844             #ignore any additional IDAT chunks
845 1         2 $rfc1950stuff = pack( "C2", ( 0x78, 0x5E ) );
846 1         6 $output = "IDAT"
847             . $rfc1950stuff
848             . $filtereddata
849             . pack( "N", $filteredcrc );
850 1         3 $newlength = $filterlen + 6;
851 1         12 $outcrc = crc32($output);
852 1         6 $processedchunk =
853             pack( "N", $newlength ) . $output . pack( "N", $outcrc );
854 1         2 $chunktocopy = $processedchunk;
855 1         3 $foundidat = 1;
856             }
857             else {
858 0         0 $chunktocopy = "";
859             }
860             }
861 2         6 $blobout = $blobout . $chunktocopy;
862 2         5 $searchindex += $chunklength + 12;
863             }
864 1         8 return $blobout;
865             }
866              
867             sub discard_noncritical {
868 2     2 0 178 my $chunktext;
869             my $nextindex;
870 2         6 my $blob = shift;
871 2 50       10 if ( ispng($blob) < 1 ) {
872              
873             #not a PNG
874 0         0 return $blob;
875             }
876              
877             #we know we have a png = so go straight to the IHDR chunk
878             #copy signature and text + length from IHDR
879 2         4 my $cleanblob = substr( $blob, 0, 16 );
880              
881             #get length of IHDR
882 2         5 my $ihdr_len = unpack( "N", substr( $blob, 8, 4 ) );
883              
884             #copy IHDR data + CRC
885 2         6 $cleanblob = $cleanblob . substr( $blob, 16, $ihdr_len + 4 );
886              
887             #move on to next text field
888 2         4 my $searchindex = 16 + $ihdr_len + 8;
889 2         4 my $pnglength = length($blob);
890 2         6 while ( $searchindex < ( $pnglength - 4 ) ) {
891              
892             #how big is chunk?
893 12         19 $nextindex = unpack( "N", substr( $blob, $searchindex - 4, 4 ) );
894              
895             #is chunk critcial?
896 12         12 $chunktext = substr( $blob, $searchindex, 1 );
897 12 100       32 if ( ( ord($chunktext) & 0x20 ) == 0 ) {
898              
899             #critcial chunk so copy
900             #copy length (4), text (4), data, CRC (4)
901 4         29 $cleanblob = $cleanblob
902             . substr( $blob, $searchindex - 4, 4 + 4 + $nextindex + 4 );
903             }
904              
905             #update the searchpoint -
906             #4 + data length + CRC (4) + 4 to get to the text
907 12         24 $searchindex += $nextindex + 12;
908             }
909 2         18 return $cleanblob;
910             }
911              
912             sub ispalettized {
913 5     5 0 12 my $blobin = shift;
914 5         21 my $ihdr = getihdr($blobin);
915 5 50       43 return 0 unless $ihdr->{"colourtype"} == 3;
916 0         0 return 1;
917             }
918              
919             sub unfiltersub {
920 5     5 0 9 my $lineout;
921 5         7 my ( $addition, $reconbyte );
922 5         10 my ( $chunkin, $lines_done, $linelength, $comp_width ) = @_;
923 5         8 my $pointis = 1;
924 5         14 while ( $pointis < $linelength ) {
925 480         613 $reconbyte =
926             unpack( "C",
927             substr( $chunkin, $lines_done * $linelength + $pointis, 1 ) );
928 480 100       645 if ( $pointis > $comp_width ) {
929 465         600 $addition =
930             unpack( "C", substr( $lineout, $pointis - $comp_width - 1, 1 ) );
931             }
932             else {
933 15         22 $addition = 0;
934             }
935 480         498 $reconbyte = ( $reconbyte + $addition ) % 256;
936 480         508 $lineout = $lineout . pack( "C", $reconbyte );
937 480         1369 $pointis++;
938             }
939 5         15 $lineout = "\0" . $lineout;
940 5         16 return $lineout;
941             }
942              
943             sub unfilterup {
944 58     58 0 61 my $lineout;
945 58         64 my ( $addition, $reconbyte );
946 58         217 my ( $chunkin, $chunkout, $lines_done, $linelength ) = @_;
947 58         64 my $pointis = 1;
948 58         120 while ( $pointis < $linelength ) {
949 5568         9037 $reconbyte =
950             unpack( "C",
951             substr( $chunkin, $lines_done * $linelength + $pointis, 1 ) );
952 5568 50       8628 if ( $lines_done > 0 ) {
953 5568         9896 $addition = unpack(
954             "C",
955             substr(
956             $chunkout, ( $lines_done - 1 ) * $linelength + $pointis, 1
957             )
958             );
959             }
960             else {
961 0         0 $addition = 0;
962             }
963 5568         6551 $reconbyte = ( $reconbyte + $addition ) % 256;
964 5568         7421 $lineout = $lineout . pack( "C", $reconbyte );
965 5568         12299 $pointis++;
966             }
967 58         129 $lineout = "\0" . $lineout;
968 58         205 return $lineout;
969             }
970              
971             sub unfilterave {
972 0     0 0 0 my $lineout;
973 0         0 my ( $addition, $addition_up, $addition_left );
974 0         0 my $reconbyte;
975 0         0 my ( $chunkin, $chunkout, $lines_done, $linelength, $compwidth ) = @_;
976 0         0 my $pointis = 1;
977 0         0 while ( $pointis < $linelength ) {
978 0         0 $reconbyte =
979             unpack( "C",
980             substr( $chunkin, $lines_done * $linelength + $pointis, 1 ) );
981 0 0       0 if ( $lines_done > 0 ) {
982 0         0 $addition_up = unpack(
983             "C",
984             substr(
985             $chunkout, ( $lines_done - 1 ) * $linelength + $pointis, 1
986             )
987             );
988             }
989             else {
990 0         0 $addition_up = 0;
991             }
992 0 0       0 if ( $pointis > $compwidth ) {
993 0         0 $addition_left =
994             unpack( "C", substr( $lineout, $pointis - $compwidth - 1, 1 ) );
995             }
996             else {
997 0         0 $addition_left = 0;
998             }
999 0         0 $addition = POSIX::floor( ( $addition_up + $addition_left ) / 2 );
1000 0         0 $reconbyte = ( $reconbyte + $addition ) % 256;
1001 0         0 $lineout = $lineout . pack( "C", $reconbyte );
1002 0         0 $pointis++;
1003             }
1004 0         0 $lineout = "\0" . $lineout;
1005 0         0 return $lineout;
1006             }
1007              
1008             sub unfilterpaeth {
1009 97     97 0 88 my $lineout;
1010 97         104 my ( $addition, $addition_up, $addition_left );
1011 0         0 my ( $addition_uleft, $reconbyte, $paeth_p, $paeth_a, $paeth_b );
1012 0         0 my ( $paeth_c, $recbyte );
1013 97         244 my ( $chunkin, $chunkout, $lines_done, $linelength, $compwidth ) = @_;
1014 97         94 my $pointis = 1;
1015 97         215 while ( $pointis < $linelength ) {
1016 9312         14308 $reconbyte =
1017             unpack( "C",
1018             substr( $chunkin, $lines_done * $linelength + $pointis, 1 ) );
1019 9312 50       13562 if ( $lines_done > 0 ) {
1020 9312         13628 $addition_up = unpack(
1021             "C",
1022             substr(
1023             $chunkout, ( $lines_done - 1 ) * $linelength + $pointis, 1
1024             )
1025             );
1026 9312 100       13981 if ( $pointis > $compwidth ) {
1027 9021         14781 $addition_uleft = unpack(
1028             "C",
1029             substr(
1030             $chunkout,
1031             ( $lines_done - 1 ) * $linelength + $pointis -
1032             $compwidth,
1033             1
1034             )
1035             );
1036             }
1037             else {
1038 291         313 $addition_uleft = 0;
1039             }
1040             }
1041             else {
1042 0         0 $addition_up = 0;
1043 0         0 $addition_uleft = 0;
1044             }
1045 9312 100       12125 if ( $pointis > $compwidth ) {
1046 9021         12974 $addition_left =
1047             unpack( "C", substr( $lineout, $pointis - $compwidth - 1, 1 ) );
1048             }
1049             else {
1050 291         284 $addition_left = 0;
1051             }
1052 9312         9651 $paeth_p = $addition_up + $addition_left - $addition_uleft;
1053 9312         9488 $paeth_a = abs( $paeth_p - $addition_left );
1054 9312         8561 $paeth_b = abs( $paeth_p - $addition_up );
1055 9312         8674 $paeth_c = abs( $paeth_p - $addition_uleft );
1056 9312 100 66     27996 if ( ( $paeth_a <= $paeth_b ) && ( $paeth_a <= $paeth_c ) ) {
    50          
1057 6227         5878 $addition = $addition_left;
1058             }
1059             elsif ( $paeth_b <= $paeth_c ) {
1060 3085         3212 $addition = $addition_up;
1061             }
1062             else {
1063 0         0 $addition = $addition_uleft;
1064             }
1065 9312         9744 $recbyte = ( $reconbyte + $addition ) % 256;
1066 9312         12311 $lineout = $lineout . pack( "C", $recbyte );
1067 9312         18507 $pointis++;
1068             }
1069 97         202 $lineout = "\0" . $lineout;
1070 97         390 return $lineout;
1071             }
1072              
1073             sub unfilter {
1074 5     5 0 8 my $chunkout;
1075             my $filtertype;
1076 5         8 my $chunkin = shift;
1077 5         8 my $ihdr = shift;
1078 5         10 my $imageheight = $ihdr->{"imageheight"};
1079 5         8 my $imagewidth = $ihdr->{"imagewidth"};
1080              
1081             #get each line
1082 5         10 my $lines_done = 0;
1083 5         5 my $pixels_done = 0;
1084 5         21 my ( $comp_width, $alpha ) = comp_width($ihdr);
1085 5         21 my $linelength = $comp_width * $imagewidth + 1;
1086 5         15 while ( $lines_done < $imageheight ) {
1087 160         296 $filtertype =
1088             unpack( "C", substr( $chunkin, $lines_done * $linelength, 1 ) );
1089 160 50       552 if ( $filtertype == 0 ) {
    100          
    100          
    50          
1090              
1091             #line not filtered at all
1092 0         0 $chunkout = $chunkout
1093             . substr( $chunkin, $lines_done * $linelength, $linelength );
1094             }
1095             elsif ( $filtertype == 4 ) {
1096 97         340 $chunkout = $chunkout
1097             . unfilterpaeth( $chunkin, $chunkout, $lines_done, $linelength,
1098             $comp_width );
1099             }
1100             elsif ( $filtertype == 1 ) {
1101 5         25 $chunkout = $chunkout
1102             . unfiltersub( $chunkin, $lines_done, $linelength, $comp_width );
1103             }
1104             elsif ( $filtertype == 2 ) {
1105 58         122 $chunkout = $chunkout
1106             . unfilterup( $chunkin, $chunkout, $lines_done, $linelength );
1107             }
1108             else {
1109 0         0 $chunkout = $chunkout
1110             . unfilterave( $chunkin, $chunkout, $lines_done, $linelength,
1111             $comp_width );
1112             }
1113 160         403 $lines_done++;
1114             }
1115 5         63 return $chunkout;
1116             }
1117              
1118             sub countcolours {
1119 5     5 0 12 my ( $limit, $totallines, $width );
1120 0         0 my ( $cdepth, $x, $colourfound, $pixelpoint, $colour, $alpha, $ndepth );
1121 0         0 my %colourlist;
1122 0         0 my $bdepth;
1123 5         15 my ( $chunk, $ihdr ) = @_;
1124 5         19 $totallines = $ihdr->{"imageheight"};
1125 5         13 $width = $ihdr->{"imagewidth"};
1126 5         25 ( $cdepth, $alpha ) = comp_width($ihdr);
1127 5         11 my $linesdone = 0;
1128 5         12 my $linelength = $width * $cdepth + 1;
1129 5         8 my $coloursfound = 0;
1130 5         8 $ndepth = $cdepth;
1131              
1132 5 50       23 if ($alpha) {
1133              
1134             #truecolour first
1135 0 0       0 if ( $cdepth == 4 ) {
    0          
    0          
1136 0         0 $bdepth = $ihdr->{"bitdepth"};
1137 0 0       0 if ( $bdepth == 8 ) {
1138 0         0 $ndepth = 3;
1139             }
1140             else {
1141 0         0 $ndepth = 2;
1142             }
1143             }
1144             elsif ( $cdepth == 8 ) {
1145 0         0 $ndepth = 6;
1146             }
1147              
1148             #now greyscale
1149             elsif ( $cdepth == 2 ) {
1150 0         0 $ndepth = 1;
1151             }
1152             }
1153 5         28 while ( $linesdone < $totallines ) {
1154 160         161 $pixelpoint = 0;
1155 160         297 while ( $pixelpoint < $width ) {
1156 5120         7691 $colourfound =
1157             substr( $chunk,
1158             ( $pixelpoint * $cdepth ) + ( $linesdone * $linelength ) + 1,
1159             $ndepth );
1160 5120         10734 $colour = 0;
1161 5120         9909 for ( $x = 0 ; $x < $ndepth ; $x++ ) {
1162 15360         42981 $colour = $colour << 8 | ord( substr( $colourfound, $x, 1 ) );
1163             }
1164 5120 100       9325 if ( defined( $colourlist{$colour} ) ) {
1165 2022         2384 $colourlist{$colour}++;
1166             }
1167             else {
1168 3098         378459 $colourlist{$colour} = 1;
1169 3098         3115 $coloursfound++;
1170             }
1171 5120         9387 $pixelpoint++;
1172             }
1173 160         333 $linesdone++;
1174             }
1175              
1176 5         49 return ( $coloursfound, \%colourlist );
1177             }
1178              
1179             sub reportcolours {
1180 2     2 0 39 my $blobin = shift;
1181              
1182             #is it a PNG
1183 2 50       9 unless ( ispng($blobin) > 0 ) {
1184 0         0 print "Supplied image is not a PNG\n";
1185 0         0 return -1;
1186             }
1187              
1188             #is it already palettized?
1189 2 50       8 unless ( ispalettized($blobin) < 1 ) {
1190 0         0 print "Supplied image is indexed.\n";
1191 0         0 return -1;
1192             }
1193 2         10 my $filtereddata = getuncompressed_data($blobin);
1194 2         6 my $ihdr = getihdr($blobin);
1195 2         13 my $unfiltereddata = unfilter( $filtereddata, $ihdr );
1196 2         11 my ( $colours, $colourlist ) = countcolours( $unfiltereddata, $ihdr );
1197 2         31 return $colourlist;
1198             }
1199              
1200             sub indexcolours {
1201              
1202             # take PNG and count colours
1203 1     1 0 8 my $blobout;
1204 1         3 my ( $ihdr_chunk, $pal_chunk, $x, $palindex, $colourfound );
1205 0         0 my $ihdrcrc;
1206 0         0 my ( $searchindex, $pnglength, $foundidat, $chunklength, $chunktocopy );
1207 0         0 my ( $palcount, $pal_crc, $len_pal, $dataout, $linesdone, $totallines );
1208 0         0 my ( $width, $cdepth, $linelength, $pixelpoint, $colour, $rfc1950stuff );
1209 0         0 my ( $rfc1951stuff, $output, $newlength, $outcrc, $processedchunk );
1210 0         0 my ( $alpha, $ndepth, $bdepth );
1211              
1212 1         4 my $blobin = shift;
1213              
1214             #is it a PNG
1215 1 50       6 return $blobin unless ispng($blobin) > 0;
1216              
1217             #is it already palettized?
1218 1 50       6 return $blobin unless ispalettized($blobin) < 1;
1219 1         2 my $colour_limit = shift;
1220              
1221             #0 means no limit
1222 1 50       4 $colour_limit = 0 unless $colour_limit;
1223 1         4 my $filtereddata = getuncompressed_data($blobin);
1224 1         3 my $ihdr = getihdr($blobin);
1225 1         5 my $unfiltereddata = unfilter( $filtereddata, $ihdr );
1226 1         9 my ( $colours, $colourlist ) = countcolours( $unfiltereddata, $ihdr );
1227 1 50       11 if ( $colours < 1 ) { return $blobin }
  0         0  
1228              
1229             #to write out an indexed version $colours has to be less than 256
1230 1 50       6 if ( $colours < 256 ) {
1231              
1232             #have to rewrite the whole thing now
1233             #start with the PNG header
1234 1         3 $blobout = pack( "C8", ( 137, 80, 78, 71, 13, 10, 26, 10 ) );
1235              
1236             #now the IHDR
1237 1         5 $blobout = $blobout . pack( "N", 0x0D );
1238 1         3 $ihdr_chunk = "IHDR";
1239 1         12 $ihdr_chunk = $ihdr_chunk
1240             . pack( "N2", ( $ihdr->{"imagewidth"}, $ihdr->{"imageheight"} ) );
1241              
1242             #FIXME: Support index of less than 8 bits
1243             #8 bit indexed colour
1244 1         2 $ihdr_chunk = $ihdr_chunk . pack( "C2", ( 8, 3 ) );
1245 1         7 $ihdr_chunk = $ihdr_chunk
1246             . pack( "C3",
1247             ( $ihdr->{"compression"}, $ihdr->{"filter"}, $ihdr->{"interlace"} )
1248             );
1249 1         11 $ihdrcrc = crc32($ihdr_chunk);
1250 1         5 $blobout = $blobout . $ihdr_chunk . pack( "N", $ihdrcrc );
1251              
1252             #now any chunk before the IDAT
1253 1         3 $searchindex = 16 + 13 + 4 + 4;
1254 1         3 $pnglength = length($blobin);
1255 1         3 $foundidat = 0;
1256 1         7 while ( $searchindex < ( $pnglength - 4 ) ) {
1257              
1258             #Copy the chunk
1259 4         18 $chunklength =
1260             unpack( "N", substr( $blobin, $searchindex - 4, 4 ) );
1261 4         13 $chunktocopy =
1262             substr( $blobin, $searchindex - 4, $chunklength + 12 );
1263 4 100       15 if ( substr( $blobin, $searchindex, 4 ) eq "IDAT" ) {
1264 1 50       239 if ( $foundidat == 0 ) {
1265              
1266             #ignore any additional IDAT chunks
1267             #now the palette chunk
1268 1         4 $pal_chunk = "";
1269 1         2 my %colourlist = %{$colourlist};
  1         19  
1270 1         4 $palcount = 0;
1271 1         2 my %palindex;
1272 1         31 my @keyslist = keys(%colourlist);
1273 1         7 keys(%palindex) = scalar(@keyslist);
1274 1         12 foreach $x (@keyslist) {
1275 13         32 $pal_chunk = $pal_chunk
1276             . pack( "C3",
1277             ( $x >> 16, ( $x & 0xFF00 ) >> 8, $x & 0xFF ) );
1278              
1279             #use a second hash to record
1280             #where the colour is in the
1281             #palette
1282 13         19 $palindex{$x} = $palcount;
1283 13         18 $palcount++;
1284             }
1285 1         6 $pal_crc = crc32( "PLTE" . $pal_chunk );
1286 1         4 $len_pal = length($pal_chunk);
1287 1         13 $blobout = $blobout
1288             . pack( "N", $len_pal ) . "PLTE"
1289             . $pal_chunk
1290             . pack( "N", $pal_crc );
1291              
1292             #now process the IDAT
1293 1         2 $linesdone = 0;
1294 1         3 $totallines = $ihdr->{"imageheight"};
1295 1         4 $width = $ihdr->{"imagewidth"};
1296 1         6 ( $cdepth, $alpha ) = comp_width($ihdr);
1297 1         2 $ndepth = $cdepth;
1298              
1299 1 50       4 if ($alpha) {
1300              
1301             #truecolour first
1302 0 0       0 if ( $cdepth == 4 ) {
    0          
    0          
1303 0         0 $bdepth = $ihdr->{"bitdepth"};
1304 0 0       0 if ( $bdepth == 8 ) {
1305 0         0 $ndepth = 3;
1306             }
1307             else {
1308 0         0 $ndepth = 2;
1309             }
1310             }
1311             elsif ( $cdepth == 8 ) {
1312 0         0 $ndepth = 6;
1313             }
1314              
1315             #now greyscale
1316             elsif ( $cdepth == 2 ) {
1317 0         0 $ndepth = 1;
1318             }
1319             }
1320              
1321 1         3 $linelength = $width * $cdepth + 1;
1322 1         6 while ( $linesdone < $totallines ) {
1323 32         39 $dataout = $dataout . "\0";
1324 32         33 $pixelpoint = 0;
1325 32         58 while ( $pixelpoint < $width ) {
1326 1024         1523 $colourfound = substr(
1327             $unfiltereddata,
1328             ( $pixelpoint * $cdepth ) +
1329             ( $linesdone * $linelength ) + 1,
1330             $ndepth
1331             );
1332 1024         1112 $colour = 0;
1333 1024         1970 for ( $x = 0 ; $x < $ndepth ; $x++ ) {
1334 3072         6981 $colour =
1335             $colour << 8 |
1336             ord( substr( $colourfound, $x, 1 ) );
1337             }
1338 1024         1336662 $dataout =
1339             $dataout . pack( "C", $palindex{$colour} );
1340 1024         2027 $pixelpoint++;
1341             }
1342 32         64 $linesdone++;
1343             }
1344              
1345             #now to deflate $dataout to get
1346             #proper stream
1347              
1348 1         10 $rfc1950stuff = pack( "C2", ( 0x78, 0x5E ) );
1349 1         16 $rfc1951stuff =
1350             shrinkchunk( $dataout, Z_DEFAULT_STRATEGY, Z_BEST_SPEED );
1351 1         18 $output = "IDAT"
1352             . $rfc1950stuff
1353             . $rfc1951stuff
1354             . pack( "N", adler32($dataout) );
1355 1         4 $newlength = length($output) - 4;
1356 1         11 $outcrc = crc32($output);
1357 1         7 $processedchunk =
1358             pack( "N", $newlength ) . $output . pack( "N", $outcrc );
1359 1         3 $chunktocopy = $processedchunk;
1360 1         11 $foundidat = 1;
1361             }
1362             else {
1363 0         0 $chunktocopy = "";
1364             }
1365             }
1366 4         11 $blobout = $blobout . $chunktocopy;
1367 4         13 $searchindex += $chunklength + 12;
1368             }
1369             }
1370             else {
1371 0         0 return $blobin;
1372             }
1373 1         20 return $blobout;
1374             }
1375              
1376             sub convert_toxyz {
1377              
1378             #convert 24 bit number to cartesian point
1379 21326     21326 0 22595 my $inpoint = shift;
1380 21326         43126 return ( $inpoint >> 16, ( $inpoint & 0xFF00 ) >> 8, $inpoint & 0xFF );
1381             }
1382              
1383             sub convert_tocolour {
1384              
1385             #convert cartesian to RGB colour
1386 0     0 0 0 my ( $x, $y, $z ) = @_;
1387 0         0 return ( ( $x << 16 ) | ( $y << 8 ) | ($z) );
1388             }
1389              
1390             sub getcolour_ave {
1391 512     512 0 513 my ( $red, $green, $blue, $numb, $x, $rt, $gt, $bt );
1392 512         530 my $coloursin = shift;
1393 512         543 $numb = scalar(@$coloursin);
1394 512 50       918 if ( $numb == 0 ) { return ( 0, 0, 0 ) }
  0         0  
1395 512         1022 for ( $x = 0 ; $x < $numb ; $x++ ) {
1396 2048         3333 ( $rt, $gt, $bt ) = convert_toxyz( $coloursin->[$x] );
1397 2048         2416 $red += $rt;
1398 2048         1834 $green += $gt;
1399 2048         4041 $blue += $bt;
1400             }
1401 512         533 $red = ( $red / $numb );
1402 512         447 $green = ( $green / $numb );
1403 512         446 $blue = ( $blue / $numb );
1404 512         1322 return ( $red, $green, $blue );
1405             }
1406              
1407             sub getaxis_details {
1408              
1409             #return a reference to the longestaxis and its length
1410 1022     1022 0 1585 my ( $longestaxis, $length, $i, );
1411 1022         1007 my $boundingbox = shift;
1412 1022 100       2454 return ( 0, 0 ) unless defined( $boundingbox->[5] );
1413 1016         1036 $longestaxis = 0;
1414 1016         5335 my @lengths = (
1415             $boundingbox->[3] - $boundingbox->[0],
1416             $boundingbox->[4] - $boundingbox->[1],
1417             $boundingbox->[5] - $boundingbox->[2]
1418             );
1419 1016         2040 for ( $i = 1 ; $i < 3 ; $i++ ) {
1420 2032 100       4926 if ( $lengths[$i] > $lengths[$longestaxis] ) {
1421 650         1295 $longestaxis = $i;
1422             }
1423             }
1424 1016         1088 my $longestaxis_cor = 2 - $longestaxis;
1425 1016         2285 return ( $longestaxis_cor, $lengths[$longestaxis] );
1426             }
1427              
1428             sub getbiggestbox {
1429              
1430             #return the index to the biggest box
1431 508     508 0 637 my ( $boxesin, $n ) = @_;
1432 508         504 my $index = 0;
1433 508         451 my $length;
1434 508         630 my $biggest = $boxesin->[3];
1435 508 50       914 if ($n > 1) {
1436 508         970 for ( my $i = 1 ; $i < $n ; $i++ ) {
1437              
1438             #length is 4th item per box
1439 64770         68834 $length = $boxesin->[ $i * 4 + 3 ];
1440 64770 100       159960 if ($length > $biggest) {
1441 543         562 $index = $i;
1442 543         1031 $biggest = $length;
1443             }
1444             }
1445             }
1446 508         847 return $index;
1447             }
1448              
1449             sub sortonaxes {
1450 510     510 0 649 my ( $coloursref, $longestaxis ) = @_;
1451 510         3499 my @newcolours = @$coloursref;
1452              
1453             #FIXME: This only works for 24 bit colour
1454 510 100       1023 if ( $longestaxis == 2 ) {
1455              
1456             #can just sort on the whole number if red
1457 182         449 return [sort { $a <=> $b } @newcolours];
  38070         36258  
1458             }
1459 328         3107 my $colshift = 0xFFFFFF >> ( 16 - ( $longestaxis * 8 ) );
1460 328         306 my ( $x, %distances );
1461 328         813 keys(%distances) = scalar(@newcolours);
1462 328         855 foreach $x (@newcolours) {
1463 9508         13095 $distances{$x} = $x & $colshift;
1464             }
1465 328         2881 return [sort { $distances{$a} <=> $distances{$b} } keys %distances];
  47287         57418  
1466             }
1467              
1468             sub generate_box {
1469              
1470             #convert colours to cartesian points
1471             #and then return the bounding box
1472 1022 100   1022 0 2641 if ( scalar(@_) == 1 ) {
1473 6         17 return [ convert_toxyz( pop @_ ) ];
1474             }
1475 1016         968 my ( @reds, @greens, @blues );
1476 0         0 my ( $x, $rd, $gn, $bl, $boundref );
1477 1016         1417 foreach $x (@_) {
1478 18248         24808 ( $rd, $gn, $bl ) = convert_toxyz($x);
1479 18248         20555 push @reds, $rd;
1480 18248         16699 push @greens, $gn;
1481 18248         22070 push @blues, $bl;
1482             }
1483 1016         2319 @reds = sort { $a <=> $b } @reds;
  62697         56850  
1484 1016         1681 @greens = sort { $a <=> $b } @greens;
  59695         53463  
1485 1016         1848 @blues = sort { $a <=> $b } @blues;
  55914         49887  
1486 1016         2736 $boundref = [
1487             shift @reds,
1488             shift @greens,
1489             shift @blues,
1490             pop @reds,
1491             pop @greens,
1492             pop @blues
1493             ];
1494 1016         3326 return $boundref;
1495             }
1496              
1497             sub getpalette {
1498 2     2 0 6 my ( $x, @palette, %lookup, $lookup, $boxes, $z );
1499 0         0 my ( $colnumbers, $colours );
1500 2         269 my @boxes = @_;
1501              
1502             #eachbox has four references
1503 2         11 $colnumbers = scalar(@boxes) / 4;
1504 2         14 for ( $x = 0 ; $x < $colnumbers ; $x++ ) {
1505 512         824 $colours = $boxes[ $x * 4 + 1 ];
1506 512         1924 my @colours = @$colours;
1507 512         883 push @palette, getcolour_ave( \@colours );
1508 512         734 foreach $z (@colours) { $lookup{$z} = $x }
  2048         4605  
1509             }
1510 2         996 return ( \@palette, \%lookup );
1511             }
1512              
1513             sub closestmatch_inRGB {
1514 994     994 0 3909 my ( $colourin, $pr, $pg, $pb );
1515 0         0 my ( $maxindex, $x, $q );
1516 0         0 my ( $index, $distance, $newdistance );
1517              
1518 994         2560 $distance = 0xFFFFF;
1519 994         1831 my ( $palref, $cir, $cig, $cib ) = @_;
1520 994         94911 my @pallist = @$palref;
1521 994         1944 $maxindex = scalar(@pallist) / 3; # assuming three colours
1522 994         1202 $q = 0;
1523 994         3127 for ( $x = 0 ; $x < $maxindex ; $x++ ) {
1524 243007         471174 $pr = $pallist[ $q++ ] - $cir;
1525 243007         501711 $pg = $pallist[ $q++ ] - $cig;
1526 243007         490355 $pb = $pallist[ $q++ ] - $cib;
1527 243007         613033 $newdistance = $pr * $pr + $pg * $pg + $pb * $pb;
1528 243007 100       985269 if ( $newdistance < $distance ) {
1529 7362         11289 $distance = $newdistance;
1530 7362         18810 $index = $x;
1531              
1532             #approximate
1533 7362 100       31841 if ( $distance <= 12 ) { last }
  88         217  
1534             }
1535             }
1536 994         44433 return $index;
1537             }
1538              
1539             sub index_mediancut {
1540 2     2 0 5 my $colour_numbers;
1541 2         5 my ( @boundingbox, $colcount, @boxes );
1542 0         0 my ( $boxtocut, $median, $biggestbox );
1543 0         0 my ( $sortedcolours, $boxout, $refbigbox );
1544 2         7 my ( $colourlist, $colourspaces ) = @_;
1545 2 50 33     20 if ( !defined($colourspaces) || ( $colourspaces == 0 ) ) {
1546 2         4 $colourspaces = 256;
1547             }
1548 2         3 $colcount = 0;
1549 2         6 my %colourlist = %{$colourlist};
  2         1076  
1550 2         605 my @colourkeys = keys(%colourlist);
1551              
1552             #can now define the colour space
1553             # boxes data is
1554             # reftoboundingboxarray, reftocoloursarray, longest_axis,
1555             # length_of_longest_axis
1556 2         130 $refbigbox = generate_box(@colourkeys);
1557 2         9 push @boxes, $refbigbox;
1558 2         8 push @boxes, \@colourkeys;
1559 2         14 push @boxes, getaxis_details($refbigbox);
1560 2         5 $boxtocut = 0;
1561 2         6 do {
1562              
1563             #find the biggest box
1564 510 100       1423 $boxtocut = getbiggestbox( \@boxes, $colcount )
1565             unless $colcount == 0;
1566 510         1649 my @biggestbox = splice( @boxes, $boxtocut * 4, 4 );
1567              
1568             #now sort on the axis
1569 510         1073 $sortedcolours = sortonaxes( $biggestbox[1], $biggestbox[2] );
1570 510         4002 my @sortedcolours = @$sortedcolours;
1571 510         1664 $median = POSIX::floor( scalar(@sortedcolours) / 2 );
1572              
1573             #cut the colours in half
1574 510         2244 my @lowercolours = splice( @sortedcolours, 0, $median );
1575              
1576             #generate two boxes
1577 510         1334 my $refboxa = generate_box(@lowercolours);
1578 510         727 push @boxes, $refboxa;
1579 510         606 push @boxes, \@lowercolours;
1580 510         866 push @boxes, getaxis_details($refboxa);
1581 510         960 my $refboxb = generate_box(@sortedcolours);
1582 510         883 push @boxes, $refboxb;
1583 510         571 push @boxes, \@sortedcolours;
1584 510         829 push @boxes, getaxis_details($refboxb);
1585 510         3370 $colcount = scalar(@boxes) / 4;
1586             } until ( $colourspaces == $colcount );
1587 2         103 return getpalette(@boxes);
1588             }
1589              
1590             sub dither {
1591              
1592             #implement Floyd - Steinberg error diffusion dither
1593 1024     1024 0 1250 my ( $linelength, $rcomp, $gcomp, $bcomp, $palnumber );
1594 0         0 my ( $rp, $rg, $rb, $max_value, $currentoffset_w );
1595 0         0 my ( $currentoffset_h, $nextoffset_h, $ll );
1596              
1597             my (
1598 1024         3936 $colour, $unfiltereddata, $cdepth, $ndepth,
1599             $linesdone, $pixelpoint, $totallines, $pallookref,
1600             $paloutref, $pal_chunk, $width
1601             ) = @_;
1602 1024         1528 $linelength = $width * $cdepth + 1;
1603              
1604             #FIXME not just 24 bit depth
1605 1024         2386 ( $rcomp, $gcomp, $bcomp ) = convert_toxyz($colour);
1606 1024         9179 $palnumber = $pallookref->{$colour};
1607 1024 100       2621 if ( !$palnumber ) {
1608 994         2414 $palnumber = closestmatch_inRGB( $paloutref, $rcomp, $gcomp, $bcomp );
1609             }
1610              
1611 1024         6626 ( $rp, $rg, $rb ) = unpack( "C3", substr( $pal_chunk, $palnumber * 3, 3 ) );
1612              
1613             #calculate the errors
1614 1024         3361 my @colerror = ( $rcomp - $rp, $gcomp - $rg, $bcomp - $rb );
1615              
1616             #now diffuse the errors
1617 1024 50       2784 if ( $cdepth >= 6 ) {
1618 0         0 $max_value = 0xFFFF;
1619             }
1620             else {
1621 1024         1811 $max_value = 0xFF;
1622             }
1623 1024         1239 $currentoffset_w = $pixelpoint * $cdepth;
1624 1024         1750 $currentoffset_h = $linesdone * $linelength;
1625 1024         2848 $nextoffset_h = ( $linesdone + 1 ) * $linelength;
1626 1024         3274 for ( $ll = 0 ; $ll < $ndepth ; $ll++ ) {
1627 3072 100       6619 if ( $colerror[$ll] == 0 ) {
1628 608         2516 next;
1629             }
1630 2464         2781 my $sign = 1;
1631 2464 100       5802 if ( $colerror[$ll] < 1 ) {
1632 1189         1842 $sign = -1;
1633 1189         2665 $colerror[$ll] = abs( $colerror[$ll] );
1634             }
1635 2464 100       7592 if ( ( $pixelpoint + 1 ) < $width ) {
1636 2375         6292 my $unpacked = unpack(
1637             "C",
1638             substr(
1639             $unfiltereddata,
1640             $currentoffset_w + $currentoffset_h + 1 + $cdepth + $ll, 1
1641             )
1642             );
1643              
1644 2375         9665 $unpacked += ( ( $colerror[$ll] * 7 ) >> 4 ) * $sign;
1645 2375 100       23838 if ( $unpacked > $max_value ) {
    100          
1646 31         32 $unpacked = $max_value;
1647             }
1648             elsif ( $unpacked < 0 ) {
1649 27         41 $unpacked = 0;
1650             }
1651 2375         67970 substr( $unfiltereddata,
1652             $currentoffset_w + $currentoffset_h + 1 + $cdepth + $ll, 1 )
1653             = pack( "C", $unpacked );
1654 2375 100       16945 if ( ( $linesdone + 1 ) < $totallines ) {
1655 2285         5897 $unpacked = unpack(
1656             "C",
1657             substr(
1658             $unfiltereddata,
1659             $currentoffset_w + ( ( $linesdone + 1 ) * $linelength )
1660             + 1 + $cdepth + $ll,
1661             1
1662             )
1663             );
1664 2285         3884 $unpacked += ( $colerror[$ll] >> 4 ) * $sign;
1665 2285 50       6181 if ( $unpacked > $max_value ) {
    50          
1666 0         0 $unpacked = $max_value;
1667             }
1668             elsif ( $unpacked < 0 ) {
1669 0         0 $unpacked = 0;
1670             }
1671 2285         5908 substr( $unfiltereddata,
1672             $currentoffset_w + $nextoffset_h + 1 + $cdepth + $ll, 1 )
1673             = pack( "C", $unpacked );
1674             }
1675             }
1676 2464 100       6893 if ( ( $linesdone + 1 ) < $totallines ) {
1677 2371         5146 my $unpacked = unpack(
1678             "C",
1679             substr(
1680             $unfiltereddata, $currentoffset_w + $nextoffset_h + 1 + $ll,
1681             1
1682             )
1683             );
1684 2371         6543 $unpacked += ( ( $colerror[$ll] * 5 ) >> 4 ) * $sign;
1685 2371 100       7872 if ( $unpacked > $max_value ) {
    100          
1686 31         33 $unpacked = $max_value;
1687             }
1688             elsif ( $unpacked < 0 ) {
1689 13         35 $unpacked = 0;
1690             }
1691 2371         5052 substr( $unfiltereddata, $currentoffset_w + $nextoffset_h + 1 + $ll,
1692             1 )
1693             = pack( "C", $unpacked );
1694 2371 100       5812 if ( $pixelpoint > 0 ) {
1695 2308         5118 $unpacked = unpack(
1696             "C",
1697             substr(
1698             $unfiltereddata,
1699             $currentoffset_w + $nextoffset_h + 1 - $cdepth + $ll, 1
1700             )
1701             );
1702 2308         3505 $unpacked += ( ( $colerror[$ll] * 3 ) >> 4 ) * $sign;
1703 2308 50       6414 if ( $unpacked > $max_value ) {
    100          
1704 0         0 $unpacked = $max_value;
1705             }
1706             elsif ( $unpacked < 0 ) {
1707 1         4 $unpacked = 0;
1708             }
1709 2308         20408 substr( $unfiltereddata,
1710             $currentoffset_w + $nextoffset_h + 1 - $cdepth + $ll, 1 )
1711             = pack( "C", $unpacked );
1712             }
1713             }
1714             }
1715 1024         7871 return ( $palnumber, $unfiltereddata );
1716             }
1717              
1718             sub palettize {
1719              
1720             # take PNG and count colours
1721 2     2 0 5381 my ( $pal_chunk, $x, $colourfound );
1722 0         0 my $palnumb;
1723 0         0 my ( $chunklength, $chunktocopy );
1724 0         0 my ( $palcount, $pal_crc, $len_pal, $dataout, $linesdone, $totallines );
1725 0         0 my ( $width, $linelength, $colour, $palnumber );
1726 0         0 my ( $pixelpoint, $linemarker, $rfc1950stuff, $rfc1951stuff, $output );
1727 0         0 my ( $newlength, $outcrc, $processedchunk );
1728 0         0 my $bdepth;
1729              
1730 2         5 my $blobin = shift;
1731              
1732             #is it a PNG
1733 2 50       16 return $blobin unless ispng($blobin) > 0;
1734              
1735             #is it already palettized?
1736 2 50       9 return $blobin unless ispalettized($blobin) < 1;
1737 2         4 my $colour_limit = shift;
1738              
1739             #0 means no limit
1740 2 50       5 $colour_limit = 0 unless $colour_limit;
1741 2         3 my $dither = shift;
1742 2 100       8 $dither = 0 unless $dither;
1743 2         7 my $filtereddata = getuncompressed_data($blobin);
1744 2         7 my $ihdr = getihdr($blobin);
1745 2         10 my $unfiltereddata = unfilter( $filtereddata, $ihdr );
1746 2         17 my ( $colours, $colourlist ) = countcolours( $unfiltereddata, $ihdr );
1747 2 50       14 if ( $colours < 1 ) {
1748 0         0 return $blobin;
1749             }
1750 2 0 0     9 if (
      33        
1751             ( $colours < 256 )
1752             && ( ( $colours < $colour_limit )
1753             || ( $colour_limit == 0 ) )
1754             )
1755             {
1756 0         0 return indexcolours($blobin);
1757             }
1758 2 50       9 if ( $colour_limit > 256 ) {
1759 0         0 return undef;
1760             }
1761 2         13 my ( $paloutref, $pallookref ) =
1762             index_mediancut( $colourlist, $colour_limit );
1763              
1764             #have to rewrite the whole thing now
1765             #start with the PNG header
1766 2         10 my $blobout = pack( "C8", ( 137, 80, 78, 71, 13, 10, 26, 10 ) );
1767              
1768 2         12 my ( $cdepth, $alpha ) = comp_width($ihdr);
1769 2         5 my $ndepth = $cdepth;
1770 2 50       10 if ($alpha) {
1771              
1772             #truecolour first
1773 0 0       0 if ( $cdepth == 4 ) {
    0          
    0          
1774 0         0 $bdepth = $ihdr->{"bitdepth"};
1775 0 0       0 if ( $bdepth == 8 ) {
1776 0         0 $ndepth = 3;
1777             }
1778             else {
1779 0         0 $ndepth = 2;
1780             }
1781             }
1782             elsif ( $cdepth == 8 ) {
1783 0         0 $ndepth = 6;
1784             }
1785              
1786             #now greyscale
1787             elsif ( $cdepth == 2 ) {
1788 0         0 $ndepth = 1;
1789             }
1790             }
1791              
1792             #now the IHDR
1793 2         8 $blobout = $blobout . pack( "N", 0x0D );
1794 2         5 my $ihdr_chunk = "IHDR";
1795 2         23 $ihdr_chunk = $ihdr_chunk
1796             . pack( "N2", ( $ihdr->{"imagewidth"}, $ihdr->{"imageheight"} ) );
1797              
1798             #FIXME: Support index of less than 8 bits
1799 2         5 $ihdr_chunk = $ihdr_chunk . pack( "C2", ( 8, 3 ) ); #8 bit indexed colour
1800 2         13 $ihdr_chunk = $ihdr_chunk
1801             . pack( "C3",
1802             ( $ihdr->{"compression"}, $ihdr->{"filter"}, $ihdr->{"interlace"} ) );
1803 2         16 my $ihdrcrc = crc32($ihdr_chunk);
1804 2         10 $blobout = $blobout . $ihdr_chunk . pack( "N", $ihdrcrc );
1805              
1806             #now any chunk before the IDAT
1807 2         6 my $searchindex = 16 + 13 + 4 + 4;
1808 2         4 my $pnglength = length($blobin);
1809 2         5 my $foundidat = 0;
1810 2         11 while ( $searchindex < ( $pnglength - 4 ) ) {
1811              
1812             #Copy the chunk
1813 4         27 $chunklength = unpack( "N", substr( $blobin, $searchindex - 4, 4 ) );
1814 4         47 $chunktocopy = substr( $blobin, $searchindex - 4, $chunklength + 12 );
1815 4 100       18 if ( substr( $blobin, $searchindex, 4 ) eq "IDAT" ) {
1816 2 50       8 if ( $foundidat == 0 ) { #ignore any additional IDATs
1817             #now the palette chunk
1818 2         4 $pal_chunk = "";
1819 2         205 my @colourlist = @$paloutref;
1820 2         133 $palcount = 0;
1821 2         5 foreach $x (@colourlist) {
1822 1536         1907 $pal_chunk = $pal_chunk . pack( "C", $x );
1823             }
1824 2         21 $pal_crc = crc32( "PLTE" . $pal_chunk );
1825 2         5 $len_pal = length($pal_chunk);
1826 2         9 $blobout = $blobout
1827             . pack( "N", $len_pal ) . "PLTE"
1828             . $pal_chunk
1829             . pack( "N", $pal_crc );
1830              
1831             #now process the IDAT
1832 2         4 $linesdone = 0;
1833 2         4 $totallines = $ihdr->{"imageheight"};
1834 2         4 $width = $ihdr->{"imagewidth"};
1835              
1836 2         5 $linelength = $width * $cdepth + 1;
1837 2         4 my %colourlookup = %{$pallookref};
  2         698  
1838 2         104 while ( $linesdone < $totallines ) {
1839 64         78 $dataout = $dataout . "\0";
1840 64         81 $pixelpoint = 0;
1841 64         93 $linemarker = $linesdone * $linelength + 1;
1842 64         134 while ( $pixelpoint < $width ) {
1843 2048         3601 $colourfound =
1844             substr( $unfiltereddata,
1845             ( $pixelpoint * $cdepth ) + $linemarker, $ndepth );
1846 2048         2041 $colour = 0;
1847 2048         4371 for ( $x = 0 ; $x < $ndepth ; $x++ ) {
1848 6144         13455 $colour =
1849             ( $colour << 8 |
1850             ord( substr( $colourfound, $x, 1 ) ) );
1851             }
1852 2048 100       3649 if ( $dither == 1 ) {
1853              
1854             #add the new
1855             #match to
1856             #the palette if
1857             #required
1858 1024         16970 ( $palnumb, $unfiltereddata ) = dither(
1859             $colour, $unfiltereddata, $cdepth,
1860             $ndepth, $linesdone, $pixelpoint,
1861             $totallines, \%colourlookup, $paloutref,
1862             $pal_chunk, $width
1863             );
1864 1024 100       3891 if ( !$colourlookup{$colour} ) {
1865 994         5678 $colourlookup{$colour} = $palnumb;
1866             }
1867             }
1868             $dataout =
1869 2048         4117 $dataout . pack( "C", $colourlookup{$colour} );
1870 2048         4639 $pixelpoint++;
1871             }
1872 64         164 $linesdone++;
1873             }
1874              
1875             #now to deflate $dataout to get proper stream
1876 2         553 $rfc1950stuff = pack( "C2", ( 0x78, 0x5E ) );
1877 2         22 $rfc1951stuff =
1878             shrinkchunk( $dataout, Z_DEFAULT_STRATEGY, Z_BEST_SPEED );
1879 2         31 $output = "IDAT"
1880             . $rfc1950stuff
1881             . $rfc1951stuff
1882             . pack( "N", adler32($dataout) );
1883 2         7 $newlength = length($output) - 4;
1884 2         24 $outcrc = crc32($output);
1885 2         13 $processedchunk =
1886             pack( "N", $newlength ) . $output . pack( "N", $outcrc );
1887 2         5 $chunktocopy = $processedchunk;
1888 2         703 $foundidat = 1;
1889             }
1890             else {
1891 0         0 $chunktocopy = "";
1892             }
1893             }
1894 4         46 $blobout = $blobout . $chunktocopy;
1895 4         17 $searchindex += $chunklength + 12;
1896             }
1897 2         596 return $blobout;
1898             }
1899              
1900             sub analyze {
1901 15     15 0 10593 my ( $chunk_desc, $chunk_text, $chunk_length, $chunk_crc );
1902 0         0 my ( $crit_status, $pub_status, @chunk_array, $searchindex, $pnglength );
1903 0         0 my ( $chunk_crc_checked, $nextindex );
1904 15         36 my $blob = shift;
1905              
1906             #is it a PNG?
1907 15 50       51 if ( Image::Pngslimmer::ispng($blob) < 1 ) {
1908              
1909             #no it's not, so return a simple array stating so
1910 0         0 push( @chunk_array, "Not a PNG file" );
1911 0         0 return @chunk_array;
1912             }
1913              
1914             #ignore signature - it's not a chunk
1915             #so straight to IHDR
1916 15         24 $searchindex = 12;
1917 15         75 $pnglength = length($blob);
1918 15         45 while ( $searchindex < ( $pnglength - 4 ) ) {
1919              
1920             #get datalength
1921 68         120 $chunk_length = unpack( "N", substr( $blob, $searchindex - 4, 4 ) );
1922              
1923             #name of chunk
1924 68         97 $chunk_text = substr( $blob, $searchindex, 4 );
1925              
1926             #chunk CRC
1927 68         113 $chunk_crc =
1928             unpack( "N", substr( $blob, $searchindex + $chunk_length, 4 ) );
1929              
1930             #is CRC correct?
1931 68         160 $chunk_crc_checked = checkcrc( substr( $blob, $searchindex - 4 ) );
1932              
1933             #critcal chunk?
1934 68         126 $crit_status = 0;
1935 68 100       190 if ( ( ord($chunk_text) & 0x20 ) == 0 ) {
1936 48         64 $crit_status = 1;
1937             }
1938              
1939             #public or private chunk?
1940 68         81 $pub_status = 0;
1941 68 50       161 if ( ( ord( substr( $blob, $searchindex + 1, 1 ) ) & 0x20 ) == 0 ) {
1942 68         80 $pub_status = 1;
1943             }
1944 68         77 $nextindex = $searchindex - 4;
1945 68         278 $chunk_desc = $chunk_text
1946             . " begins at offset $nextindex has data length "
1947             . $chunk_length
1948             . " with CRC $chunk_crc";
1949 68 50       124 if ( $chunk_crc_checked == 1 ) {
1950 68         109 $chunk_desc = $chunk_desc . " and the CRC is good -";
1951             }
1952             else {
1953 0         0 $chunk_desc = $chunk_desc . " and there is an ERROR in the CRC -";
1954             }
1955 68 100       114 if ( $crit_status > 0 ) {
1956 48         80 $chunk_desc =
1957             $chunk_desc . " the chunk is critical to the display of the PNG";
1958             }
1959             else {
1960 20         37 $chunk_desc = $chunk_desc
1961             . " the chunk is not critical to the display of the PNG";
1962             }
1963 68 50       114 if ( $pub_status > 0 ) {
1964 68         99 $chunk_desc = $chunk_desc . " and is public\n";
1965             }
1966             else {
1967 0         0 $chunk_desc = $chunk_desc . " and is private\n";
1968             }
1969 68         97 push( @chunk_array, $chunk_desc );
1970 68         160 $searchindex += $chunk_length + 12;
1971             }
1972 15         2282 return @chunk_array;
1973             }
1974              
1975             1;
1976             __END__