Alex Alex - 3 years ago 242
PHP Question

Appending a png pHYs chunk in php

I'm trying to tack on some information about the physical size for printing my PNGs just before they are generated.

Reading the libpng doc and the pHYs chunk specifications has been helpful but I just can't seem to crack it.

I have tried adding this chunk in the most manual and simplest way possible, however, the .png file ends up corrupted. Am I missing an encoding trick?

For the CRC computation I have used the 32-bit result of this site, having plugged in the ASCII values for the chunk that the code below gives me.

$encoded = $_POST['imgdata'];
$encoded = str_replace(' ', '+', $encoded);
$decoded= base64_decode($encoded);

$test = explode('IDAT',$decoded);
$ppu='00000000000000000010111000100011'; //32-bit integer for the pixels per unit
$test[0].=sprintf("%c",bindec('00000000000000000000000000001001')) //length, also 32-bit
.'pHYs' //type
. sprintf("%c",$dppu) //Pixels per unit, x axis
. sprintf("%c",$dppu) //Pixels per unit, y axis
.'1' //Units in metres (1 byte)
. sprintf("%c", bindec(base_convert('0x0BFAAA7E', 16, 2))) //CRC (32-bit)

echo $fintest;

Please let me know whether hacking it in like this is likely to work. I am also unsure about my 32-bit integers: is zero-padding them as I am doing the correct way to make them 32-bit?

Answer Source

After having spent the day on this, I found that the way to do this was to translate everything byte by byte. Following the docs, the pHYs chunk takes:

  • 4 bytes for chunk (only the data) length
  • 4 bytes for chunk type (the characters 'pHYs')
  • 9 bytes for chunk data: 4 bytes each for x- and y-density and 1 byte for unit choice
  • 4 bytes for the CRC

I wrote all this as a binary string, padding the bytes with 0 as necessary. It then turns out that the chr() function correctly encodes this to be used in the PNG file, unlike the sprintf("%c",$string) method I use in the question. Here is the code

$encoded = $_POST['imgdata'];
$encoded = str_replace(' ', '+', $encoded);
$decoded=  base64_decode($encoded);

$binstring='00000000000000000000000000001001' //4-byte length
        . '01110000010010000101100101110011' //4-byte type or 'pHYs'
        . '000000000000000000101110001000110000000000000000001011100010001100000001' //9-byte data
        . base_convert('0x0BFAAA7E', 16, 2); //4-byte CRC
foreach (str_split($binstring,8) as $b) {

The $on variable is then appended to the position just before the 4 bytes of the IDAT chunk length.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download