Сохранение Portable Anymap (PPM) на PHP

в 9:12, , рубрики: Песочница, метки: , , , ,
Предисловие

Однажды возникла необходимость распознавания небольшой числовой капчи: всегда 6 цифр, шума нет, но есть искажения (поворот и «линза»). Простой алгоритм распознавания не сработал достаточно хорошо, поэтому пришлось искать готовые программы. Среди них встретилась небольшая утилита gocr (http://jocr.sourceforge.net/). Но в качестве входного файла она захотела диковинный для меня формат — pnm/pgm/pbm/ppm. Как оказалось, этот неофициальный формат придуман для работы с изображениями на разных платформах. Формат прост в программировании, однако зачем тратить бесценное время попусту в современном мире? Я решил поделиться с вами небольшой функцией на PHP.

Функция int function convert2ppm(string$fname_input, string $ver) принимает на вход имя файла $fname_input в формате jpg или png (легко добавить другие форматы, если нужно) и тип выходного формата $ver из следующих:
P1, P2, P3 (текстовые), P4, P5, P6 (двоичные).

В результате работы функция сохраняет в эту же папку файл указанного формата.
Возвращаемый результат — размер сохраненного файла.

Если у читателей проявится интерес к моим наработкам, могу продолжить публикации: например, реализую функционал для открытия PPM-файлов и сохранения в других форматах.

P.S.
Понимаю, что операции конвертации данных и особенно в больших объемах лучше проводить на более низкоуровневых языках, но иногда удобно взять и «прилепить» определенный функционал копированием лишь одной функции для вашего рабочего языка. В данном случае — PHP. Тем более, что в интернете такой функции раньше не было. Теперь есть. Хабр не даст ей засохнуть.

Ссылки

1. ru.wikipedia.org/wiki/Portable_anymap — красочное описание с примером
2. netpbm.sourceforge.net/doc/ppm.html — спецификация
3. www.daubnet.com/en/file-format-pbm — спецификация на английском, но более подробно

Код

function convert2ppm($fname_input, $ver) {
  $fname_input=strtolower($fname_input);
  $fname_output=substr($fname_input, 0, strpos($fname_input, '.')). '.ppm';

  // определяем формат, открываем файл
  if (strpos($fname_input, '.png')!==false)
    $im = imagecreatefrompng($fname_input);
  elseif (strpos($fname_input, '.jpg')!==false)
    $im = imagecreatefromjpeg($fname_input);
  $ver=strtoupper($ver);
  $w=imagesx($im);
  $h=imagesy($im);

  $white_value= ($ver=='P1' OR $ver=='P4') ? '' : ' 255'; // значение белого цвета
  if ($ver=='P1' OR $ver=='P2' OR $ver=='P3') { // текстовый формат
    $s="$ver $w $h$white_value"; // строка для записи
    for($y=0; $y<$h; $y++) {
      $s.="n";
      for($x=0; $x<$w; $x++) {
        $rgb = imagecolorsforindex($im, imageColorAt($im, $x, $y));
        switch ($ver) {
        case "P1": // ч/б изображение - 1 или 0 на пиксел (но в файле все равно ASCII-символ занимает 1 байт)
          $s.=(0.3*$rgb['red']+0.59*$rgb['green']+0.11*$rgb['blue']<128) ? '1 ' : '0 ';
          break;
        case "P2": // серое изображение 1 байт на пиксел
          $s.=round( (0.3*$rgb['red']+0.59*$rgb['green']+0.11*$rgb['blue'])).' ';
          break;
        case "P3": // цветное изображение 3 байта на пиксел
          $s.="{$rgb['red']} {$rgb['green']} {$rgb['blue']}n";
          break;
        };
      };
      $s=trim($s);
    };
  }
  else { // двоичный формат
    $s="$ver $w $h$white_value"; // строка для записи
    for($y=0; $y<$h; $y++) {
      $b=''; // двоичная строка битов для P4
      for($x=0; $x<$w; $x++) {
        $rgb = imagecolorsforindex($im, imageColorAt($im, $x, $y));
        switch ($ver) {
        case "P4":
          $b.=(0.3*$rgb['red']+0.59*$rgb['green']+0.11*$rgb['blue']<128) ? '1' : '0';
          break;
        case "P5": // изображение оттенками серого формата 1байт на пиксел
          $s.=pack('c', round(0.3*$rgb['red']+0.59*$rgb['green']+0.11*$rgb['blue']));
          break;
        case "P6": // цветное изображение 3 байта на пиксел
          $s.=pack('ccc', $rgb['red'], $rgb['green'], $rgb['blue']);
          break;
        };
      };
      if ($ver=='P4') {
        // дополняем последний байт
        $d=strlen($b) % 8;
        $b=substr($b, 0, -$d). str_pad(substr($b, -$d), 8, '0', STR_PAD_LEFT);
        // каждые 8бит упаковываем в байт
        for($i=0; $i<strlen($b); $i+=8)
          $s.=pack('c', bindec(substr($b, $i, 8))); // пишем упакованный байт в строку
      };
    };
  };

  imagedestroy($im);
  return file_put_contents($fname_output, $s);
};

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js