Добавление части png pHYs в php

Я пытаюсь использовать некоторую информацию о физических размерах для печати своих 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 байта для типа блока (значки «pHY»)
  • 9 байтов для данных куска: по 4 байта для x- и y-плотности и 1 байт для выбора единицы
  • 4 байта для CRC

Я написал все это как двоичную строку, заполняя байты 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 добавляется в позицию непосредственно перед 4 байтами IDAT блока IDAT .

Расширяя ответ Алекса и исправляя незначительные ошибки в его коде, я постараюсь предоставить рабочий пример о том, как добавить PNG-файл PNY, или установить разрешение изображения PNG в чистом PHP без повторной выборки изображения. Следующий код считывает данные из файла.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; ?> 

Единственное, что вы должны помнить: этот подход работает только с файлами PNG, у которых нет блока PHY . Например, генерируется PHP GD или JavaScript HTMLCanvasElement.toDataURL (). Если ваш PNG уже имеет кусок pHYs, вы должны найти и изменить существующий фрагмент .