I'm trying to understand how data are stored into IDAT chunk. I'm writing a little PHP class and I can retrieve most of chunks information but what I get for IDAT doesn't match pixels of my image :
It is 2×2px truecolour with alpha (bitdepth 8).
But when I interpret IDAT data like this:
current(unpack('H*',gzuncompress($idat_data)));
I get
00000000ffffff00ffffff000000
I don't understand how it can match pixels. Or is it my code which corrupts data?
Thanks for your help!
EDIT: I get
08d705c101010000008010ff4f1750a93029e405fb
as hex compressed data, so it seems I loss several bytes after uncompressing.
Use gzinflate but skip the first 2 bytes and the last 4 first.
$contents = file_get_contents($in_filename);
$pos = 8; // skip header
$color_types = array('Greyscale','unknown','Truecolour','Indexed-color','Greyscale with alpha','unknown','Truecolor with alpha');
$len = strlen($contents);
$safety = 1000;
do {
list($unused,$chunk_len) = unpack('N', substr($contents,$pos,4));
$chunk_type = substr($contents,$pos+4,4);
$chunk_data = substr($contents,$pos+8,$chunk_len);
list($unused,$chunk_crc) = unpack('N', substr($contents,$pos+8+$chunk_len,4));
echo "chunk length:$chunk_len(dec) 0x" . sprintf('%08x',$chunk_len) . "h<br>\n";
echo "chunk crc :0x" . sprintf('%08x',$chunk_crc) . "h<br>\n";
echo "chunk type :$chunk_type<br>\n";
echo "chunk data $chunk_type bytes:<br>\n" . chunk_split(bin2hex($chunk_data)) . "<br>\n";
switch($chunk_type) {
case 'IHDR':
list($unused,$width,$height) = unpack('N2', substr($chunk_data,0,8));
list($unused,$depth,$Color_type,$Compression_method,$Filter_method,$Interlace_method) = unpack('C*', substr($chunk_data,8));
echo "Width:$width,Height:$height,depth:$depth,Color_type:$Color_type(" . $color_types[$Color_type] . "),Compression_method:$Compression_method,Filter_method:$Filter_method,Interlace_method:$Interlace_method<br>\n";
$bytes_per_pixel = $depth / 8;
break;
case 'PLTE':
$palette = array();
for($i=0;$i<$chunk_len;$i+=3) {
$tupl = bin2hex(substr($chunk_data,$i,3));
$palette[] = $tupl;
if($i && ($i % 30 == 0)) {
echo "<br>\n";
}
echo '<span style="color:' . $tupl . ';">[' . $tupl . ']</span>';
}
echo print_r($palette,true) . "<br>";
break;
case 'IDAT':
$compressed = substr($chunk_data,2,$chunk_len - 6); // 2 bytes on the front and 4 at the end
$decompressed = gzinflate($compressed);
echo "decompressed chunk data " . strlen($decompressed) . " bytes:<br>\n" . chunk_split(bin2hex($decompressed),2 + $width * $bytes_per_pixel * 2) . "<br>\n";
for($row=0; $row<$height; $row++) {
for($col=1; $col<=$width; $col++) {
$index = (int)substr($decompressed,((int)$row*($width+1)+$col),1);
echo '<span style="color:' . $palette[$index] . ';">' . $index . '</span>';
}
echo "<br>\n";
}
// TODO use filters described here:
// http://www.w3.org/TR/PNG/#9Filters
// first byte of scan line is filter type
break;
}
$pos += $chunk_len + 12;
echo "<hr>";
} while(($pos < $len) && --$safety);
00000000 ffffff00 ffffff00 0000xxxx
black white white black
That's what I can tell (which is correct) ... but you are missing 2 bytes at the end.
To add to @Andreas (+1) parsing, two things to note:
A PNG file can have (and often has) many IDAT chunks, they must be concatenated to recover the compressed zlib stream. http://www.w3.org/TR/PNG/#10CompressionFSL
Gzip/Compress/Deflate are all related but are not exactly the same. PNG uses deflate/inflate. I'd try with gzdeflate/gzinflate
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With