Создание анимированного изображения средствами PHP "Кошка и светлячки"


Задание было выполнено с помощью встроенной графической библиотеки GD2, в изображение была добавлена анимация с использованием дополнительной библиотеки APNG_Creator.php. Библиотека позволяет собрать набор изображений в единый файл формата .png, который внутри будет анимированным за счёт APNG-технологии.
На каждом шаге на текущий «холст» переносится статичная часть изображения, после чего к нему добавляются элементы анимации: двигающиеся светлячки и зрачки кошки.
Первая часть кода – подключение библиотеки и инициализация глобальных переменных.

require_once "./APNG_Creator.php";
$fd = array();
$frX = array();
$frY = array();
$chunks = array();
$len = 10;
$fs = 5;
$timer = 20;
$e = 0;
$clock = 0;
$frX[0]=35;$frY[0]=45;
$frX[1]=465;$frY[1]=405;
$frX[2]=145;$frY[2]=365;
$frX[3]=255;$frY[3]=175;
$frX[4]=375;$frY[4]=265;
$fd[0]=0;$fd[1]=1;$fd[2]=0;$fd[3]=1;$fd[4]=0;
$frX[5]=rand(0,545);$frY[5]=rand(0,545);
$frX[6]=rand(0,545);$frY[6]=rand(0,545);
$frX[7]=rand(0,545);$frY[7]=rand(0,545);
$frX[8]=rand(0,545);$frY[8]=rand(0,545);
$frX[9]=rand(0,545);$frY[9]=rand(0,545);
$fd[5]=1;$fd[6]=0;$fd[7]=1;$fd[8]=0;$fd[9]=1;

Здесь первая строка отвечает за подключение библиотеки, лежащей в одной папке с данной страницей на сервере. Далее объявляются массив направлений светлячков fd (0 – слева направо сверху вниз, 1 – обратно), два массива координат светлячков fdX и fdY и массив картинок chunks. Также задаются количество светлячков len, радиус fs, а ещё таймер движения глаз timer и текущее положение e. Последняя объявленная переменная – номер текущего «холста» clock, на котором отображается текущая часть анимации.
Для пересчёта координат глаз реализована следующая функция:

function eyes() {
  global $chunks;
  global $clock;
  global $e;
  $eyeColor = imagecolorallocate($chunks[$clock],32,29,37);
  imagefilledellipse($chunks[$clock],315+$e,205,10,30,$eyeColor);
  imagefilledellipse($chunks[$clock],375+$e,205,10,30,$eyeColor);
}

Получив необходимые глобальные переменные, функция устанавливает цвет для конкретного «холста» и рисует два эллипса зрачков.
Далее следует функция обработки перемещения светлячка с номером ind на вектор (kx,ky):

function fly($ind,$kx,$ky) {
  global $frX;
  global $frY;
  global $fs;
  global $chunks;
  global $clock;
  $frX[$ind]+=$kx;
  $frY[$ind]+=$ky;
  $flyColor = imagecolorallocate($chunks[$clock],250,190,40);
  imagefilledellipse($chunks[$clock],$frX[$ind],$frY[$ind],2*$fs,2*$fs,$flyColor);
}

Функция получает глобальные переменные, обновляет координаты данного светлячка и рисует круг.
Отдельно прописана функция построения на текущем «холсте» фоновой части изображения.

function main() {
  global $chunks;
  global $clock;
  $drawColor = imagecolorallocate($chunks[$clock],46,61,108);
  imagefilledrectangle($chunks[$clock],0,0,550,550,$drawColor);
  $drawColor = imagecolorallocate($chunks[$clock],50,49,63);
  imagefilledrectangle($chunks[$clock],10,400,535,450,$drawColor);
  $drawColor = imagecolorallocate($chunks[$clock],33,47,89);
  imagefilledpolygon($chunks[$clock],array(10,450,534,450,534,500),3,$drawColor);
  $drawColor = imagecolorallocate($chunks[$clock],32,29,37);
  imagefilledellipse($chunks[$clock],51,370,60,60,$drawColor);
  imagefilledellipse($chunks[$clock],270,310,193,181,$drawColor);
  imagefilledellipse($chunks[$clock],336,310,187,181,$drawColor);
  imagefilledellipse($chunks[$clock],334,214,190,181,$drawColor);
  imagefilledrectangle($chunks[$clock],47,340,342,400,$drawColor);
  imagefilledrectangle($chunks[$clock],240,210,430,314,$drawColor);
  imagefilledpolygon($chunks[$clock],array(274,207,274,108,381,158),3,$drawColor);
  imagefilledpolygon($chunks[$clock],array(300,160,414,210,414,110),3,$drawColor);		
  $drawColor = imagecolorallocate($chunks[$clock],16,198,50);
  imagefilledellipse($chunks[$clock],315,205,30,30,$drawColor);
  imagefilledellipse($chunks[$clock],375,205,30,30,$drawColor);
  $drawColor = imagecolorallocate($chunks[$clock],140,198,16);
  imagefilledellipse($chunks[$clock],315,205,24,24,$drawColor);
  imagefilledellipse($chunks[$clock],375,205,24,24,$drawColor);
  $drawColor = imagecolorallocate($chunks[$clock],170,198,16);
  imagefilledellipse($chunks[$clock],315,205,18,18,$drawColor);
  imagefilledellipse($chunks[$clock],375,205,18,18,$drawColor);
}

В ней мы просто последовательно рисуем фон, затем полочку и тень, шесть элементов, составляющих тело и голову кошки, два треугольника ушей и глаза с радужками.
Программа стартует с инициализации первого кадра:

$chunks[0] = imagecreatetruecolor(550,550);
main();
eyes();
for ($i=0;$i<$len;$i++)
  fly($i,0,0);

Затем запускается цикл, который строит все последующие кадры анимации.
for ($clock=1;$clock<=99;$clock++) {
  $chunks[$clock] = imagecreatetruecolor(550,550);
  main();
  if ($clock%$timer==0) {
    $e=rand(0,10)-rand(0,9);
  }
  eyes();
  for ($i=0;$i<$len;$i++) {
    fly($i,rand(0,5-$fd[$i])-rand(0,5-(1-$fd[$i])),rand(0,5-$fd[$i])-rand(0,5-(1-$fd[$i])));
    if ($frX[$i]>550 && $fd[$i]==0)
      $frX[$i]=-10;
    if ($frY[$i]>550 && $fd[$i]==0)
      $frY[$i]=-10;
    if ($frX[$i]<-$fs && $fd[$i]==1)
      $frX[$i]=550;
    if ($frY[$i]<-$fs && $fd[$i]==1)
      $frY[$i]=550;
  }
}

Анимация проходит 99 кадров. На каждом кадре рисуется фон, по достижении нужного числа шагов срабатывает таймер движения глаз, который обновляет координаты зрачков. После чего обновляется каждый из светлячков. Если координаты светлячка выходят за границы рамки, то для каждой из границ и направления движения светлячка обрабатывается появление с противоположной стороны.
В конце картинка собирается в единый файл.

$len=count($chunks);
  $animation = new APNG_Creator(); 
  $animation->save_alpha = false; 
  for ($i=0;$i<$len;$i++)
    $animation->add_image($chunks[$i], null, 5);
  $animation->save("./cat.png");
  $animation->destroy_images();
  echo '<html>
    <head>
      <meta charset="UTF-8">
      <title>Кошка и светлячки</title>
    </head>
    <body>
      <img src="' . './cat.png' . '" />
    </body>
  </html>';

Создаётся объект класса APNG_Creator, для которого задаётся параметр save_alpha=false, чтобы картинки сменяли одна другую без эффектов затухания. Далее все картинки складываются в объект с помощью add_image, где первый параметр задаёт изображение, второй определяет позицию картинки, а третий определяет число миллисекунд, которые картинка будет находиться на экране.
Затем файл сохраняется на сервере, память очищается, после чего создаётся страница, на которой отображается этот файл.
Картинка весьма объёмная в плане количества выполняемых действий, а потому иногда может не собираться. Код не оптимален, но в полной мере демонстрирует возможности анимации на PHP без необходимости использовать .gif.