在php中添加一个png pHYs块

时间:2014-08-27 13:18:49

标签: php binary png chunks

我正试图在生成PNG之前打印一些有关打印PNG的物理尺寸的信息。

阅读libpng doc和pHYs块规格有帮助,但我似乎无法破解它。

我尝试以最手动和最简单的方式添加此块,但是.png文件最终会损坏。我错过了编码技巧吗?

对于CRC计算,我使用了this site的32位结果,插入了以下代码给我的块的ASCII值。

$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
$dppu=bindec($ppu);
$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)
    .'IDAT';
$fintest=implode($test);

echo $fintest;

请让我知道是否像这样进行黑客攻击可能会有效。我也不确定我的32位整数:是否填充它们,因为我正在做正确的方法使它们成为32位?

2 个答案:

答案 0 :(得分:2)

在这一天花了一天之后,我发现这样做的方法是逐字节翻译所有内容。在文档之后,pHYs块需要:

  • 4个字节用于块(仅数据)长度
  • 4个字节用于块类型(字符' pHYs')
  • 块数据的9个字节:x和y密度各4个字节,单位选择1个字节
  • CRC的4个字节

我把所有这些写成二进制字符串,根据需要用0填充字节。 然后证明chr()函数正确编码了这个在PNG文件中使用的函数,这与我在问题中使用的sprintf("%c",$string)方法不同。这是代码

$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) {
    $on.=chr(bindec($b));
}

然后将$ on变量附加到IDAT块长度的4个字节之前的位置。

答案 1 :(得分:1)

扩展Alex的答案并纠正他的代码中的小错误,我将尝试提供如何附加PNG pHYs块,或者在纯PHP中设置PNG图像分辨率的实例,而无需重新采样图像。以下代码从 file.png 读取数据,并将修改后的图像发送到标准输出。

<?php

  // Read file data
  $data = file_get_contents('file.png');

  // Pixels per inch
  $ppi = 300;

  // Unit conversion PPI to PPM
  $ppm = round($ppi * 100 / 2.54);

  $ppm_bin = str_pad(base_convert($ppm, 10, 2), 32, '0', STR_PAD_LEFT);

  // Split PNG data at first IDAT chunk
  $data_splitted = explode('IDAT', $data, 2);

  // Generate "pHYs" chunk data

  // 4-byte data length
  $length_bin = '00000000000000000000000000001001';
  // 4-byte type or 'pHYs'
  $chunk_name_bin = '01110000010010000101100101110011';
  // 9-byte data
  $ppu_data_bin = 
           $ppm_bin // Pixels per unit, x axis
          .$ppm_bin // Pixels per unit, y axis
          .'00000001'; // units - 1 for meters
  // Calculate 4-byte CRC
  $hash_buffer = '';
  foreach(str_split($chunk_name_bin.$ppu_data_bin, 8) as $b)
    $hash_buffer .= chr(bindec($b));
  $crc_bin = str_pad(base_convert(crc32($hash_buffer), 10, 2), 32, '0', STR_PAD_LEFT);
  // Create chunk binary string
  $binstring = $length_bin
          . $chunk_name_bin
          . $ppu_data_bin
          . $crc_bin;

  // Convert binary string to raw
  $phys_chunk_raw = '';
  foreach(str_split($binstring, 8) as $b)
    $phys_chunk_raw .= chr(bindec($b));

  // Insert "pHYs" chunk before first IDAT tag
  $new_image_data = substr($data_splitted[0], 0, strlen($data_splitted[0]) - 4)
        . $phys_chunk_raw
        . substr($data_splitted[0], strlen($data_splitted[0]) - 4, 4)
        . 'IDAT'
        . $data_splitted[1];

  // Output modified image
  header("Content-Type: image/png");
  echo $new_image_data;

?>

或者,更优雅的片段做同样的事情(在habrahabr.ru找到):

<?php

  // Read file data
  $data = file_get_contents('file.png');

  // Pixels per inch
  $ppi = 300;

  $incPos = strpos($data, 'IDAT') - 4; // Get chunk position
  $chunk = 'pHYs'.pack('NNc', round($ppi/0.0254), round($ppi/0.0254), 1); // Pack chunk type + chunk data
  $incData = pack('N', 9).$chunk.pack('N', crc32($chunk)); // Append chunk's size at beginning, it's crc at the end
  $new_image_data = substr_replace($data, $incData, $incPos, 0);

  // Output modified image
  header("Content-Type: image/png");
  echo $new_image_data;

?>

您唯一必须记住的事情是:此方法仅适用于没有pHYs块的PNG文件。例如,由PHP GD或JavaScript HTMLCanvasElement.toDataURL()生成。如果你的PNG已经有一个pHY块,你必须找到并修改现有的块