我正试图在生成PNG之前添加一些关于物理大小的信息。
阅读libpng文档和pHYs区块规范很有帮助,但我似乎无法破解它
我尝试过以最手动、最简单的方式添加这个区块,但是,.png文件最终被破坏了。我是不是错过了一个编码技巧?
对于CRC计算,我使用了这个网站的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位的正确方法?
花了一天的时间之后,我发现这样做的方法是逐字节地翻译所有内容。在文档之后,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个字节之前的位置。
扩展Alex的答案并更正他代码中的小错误,我将尝试提供一个关于如何附加PNG pHYs块的工作示例,或者在纯PHP中设置PNG图像分辨率而不重新采样图像。以下代码从文件.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已经有一个pHYs区块,则必须查找并修改现有区块。