Шреддер анимированных GIF на PHP

Илья777

Новичок
Здравствуйте, Уважаемые!
Задача довольно специфическая. Делаю карту для браузерной игры, состоящую из квадратиков со сторонами 100x100 пикселов.
В случаях, если изображение статическое - прекрасно работает скрипт,использующий GD:

PHP:
 $i = 0;
    //- Путь к файлу карты
    $im = imagecreatefromjpeg("125.jpeg");
    for ($x=0;$x<58;$x++) // количество клеток вертикаль
    for ($y=0;$y<58;$y++){ // количество клеток  горизонталь
    //- Тут и ниже измени путь куда сохранять будешь, если файла не будет существовать - будет создание нового)
    if (!file_exists($_SERVER["DOCUMENT_ROOT"]."/map2/day2/".($x)."_".($y).".jpg"))
    {
    $id = imagecreatetruecolor(100,100);
    imagecopy($id,$im,0,0,100*$x,$y*100,100,100);
    Imagejpeg($id,$_SERVER["DOCUMENT_ROOT"]."/images/map2/day2/".($x)."_".($y).".jpg",100);
    }
    $i++;
    }
    //- тест
    echo $i;


Но, в моём случае, мне необходимо сделать то-же самое - но с большой анимированной картинкой.
Попробовал реализовать сходный функционал с помощью Imagick:
PHP:
<?php

    set_time_limit(0);
    ini_set("memory_limit", "24G");
    $i = 0;
    //- Путь к файлу карты
    $imagick = new Imagick('source.gif');
    for ($x=0;$x<28;$x++) // количество клеток вертикаль
    for ($y=0;$y<51;$y++){ // количество клеток  горизонталь
    //- Тут и ниже измени путь куда сохранять будешь, если файла не будет существовать - будет создание нового)
    // Разбить gif анимацию на изображение
    $imagick = $imagick->coalesceImages();
    if (!file_exists("".($x)."_".($y).".gif"))
    {
    // Обрезка изображений одно за другим                
    do {
         // Первые две пары-это ширина и высота урожая, а третья и четвертая-начальные точки
         // Например, если последние два параметра равны 0,0, это означает, что обрезка начнется с верхнего левого угла
         $imagick->cropImage(100,100,$x,$y);
         // После обрезки на изображении остается некоторое пустое пространство, так как холст изображения остался того же размера
         // Эта функция изменяет размер холста изображения и таким образом устраняет пустое пространство
         $imagick->setImagePage(100,100,$x,$y);
                       
    } while ($imagick->nextImage());

    // Собираем анимацию заново            
    $imagick = $imagick->deconstructImages();  
    $imagick->writeImage("gif:".($x)."_".($y).".gif");
    }
    $i++;
    }
    //- тест
    echo $i;
   
?>

Получается полная ахинея....


Уважаемые, подскажите, пожалуйста, что я сделал не так?
 
PHP:
// пустая карта
$map = new Imagick();
$map->setFormat('gif');

// гифка
$gif = new Imagick('giphy.gif');
$frames = $gif->coalesceImages();

foreach ($frames as $frame) {
    
    // каждый фрейм уменьшаем до размера 50x50
    $frame->scaleImage(50, 50, 0, 0);
    $frame->thumbnailImage(50, 50);
    $frame->setImagePage(50, 50, 0, 0);
    
    // создаем новый итоговый фрейм
    $mapFrame = new Imagick();
    $mapFrame->newImage(500, 250, new ImagickPixel('black'));
    
    // распределяем фреймы гифки на новый итоговый фрейм
    for($y = 0; $y<5; $y++){
        for($x = 0; $x<10; $x++){
            $mapFrame->compositeImage($frame, Imagick::COMPOSITE_DEFAULT, $x*50, $y*50);
        }
    }
    // задержка
    $mapFrame->setImageDelay($frame->getImageDelay());
    
    // складывем в карту
    $map->addImage($mapFrame);
    $map->nextImage();
}
$map->writeImages('map.gif', true);
 
Исходный файл - анимированный gif 5120x2880 пикселов.
Нужно его разбить на квадратики со сторонами 100 px и генерацией названий вида '.$nomerstroki.'_'.$nomerstolbca.'.gif
Потом в самой игре карта собирается средствами Js.

Другими словами, я пытался переписать функционал вот этого кода - для анимированного исходного gif и на выходе анимированных-же часей карты:
PHP:
$i = 0;
    //- Путь к файлу карты
    $im = imagecreatefromjpeg("125.jpeg");
    for ($x=0;$x<58;$x++) // количество клеток вертикаль
    for ($y=0;$y<58;$y++){ // количество клеток  горизонталь
    //- Тут и ниже измени путь куда сохранять будешь, если файла не будет существовать - будет создание нового)
    if (!file_exists($_SERVER["DOCUMENT_ROOT"]."/map2/day2/".($x)."_".($y).".jpg"))
    {
    $id = imagecreatetruecolor(100,100);
    imagecopy($id,$im,0,0,100*$x,$y*100,100,100);
    Imagejpeg($id,$_SERVER["DOCUMENT_ROOT"]."/images/map2/day2/".($x)."_".($y).".jpg",100);
    }
    $i++;
    }
    //- тест
    echo $i;

GD тоже может работать с GIF, но анимация не поддерживается и на выходе только корректно порезанные части без анимации (только первый фрейм).
 
Последнее редактирование:
PHP:
$imagick = new Imagick('giphy.gif');
$frames = $imagick->coalesceImages();

for($y = 0; $y<16; $y++){
    for($x = 0; $x<16; $x++){
        
        $out = new Imagick();
        $out->newImage(16, 16, new ImagickPixel('black'));
        
        foreach ($frames as $frame) {
            $region = $frame->getImageRegion(16, 16, $x*16, $y*16);
            $region->setImagePage(16, 16, 0, 0);
            $out->addImage($region);
            $out->nextImage();
        }
        $out->writeImages('result/'.$x.'_'.$y.'.gif', true);
        
    }
}
 
С анимированными gif - не работает gd. Проблема в том, что gif - проприетарный формат, и GD брезгует с ним работать... Или в лом разработчикам... или фазы луны... Проблема решается множеством способов. например так - только ресайз, но, наверное, можно и допилить... или вызовом консольной ImageMagic (пост выше).
 
PHP:
<?php
    set_time_limit(0);
    ini_set("memory_limit", "24G"); 

    $imagick = new Imagick('source.gif');
    $frames = $imagick->coalesceImages();

    for($y = 0; $y<52; $y++){
        for($x = 0; $x<29; $x++){
         
            $out = new Imagick();
            $out->newImage(100, 100, new ImagickPixel('black'));
         
            foreach ($frames as $frame) {
                $region = $frame->getImageRegion(100, 100, $x*100, $y*100);
                $region->setImagePage(100, 100, 0, 0);
                $out->addImage($region);
                $out->nextImage();
            }
            $out->writeImages('result/'.$x.'_'.$y.'.gif', true);
         
        }
    }
?>

Этот вариант почти рабочий. Благодарю! :)
Как теперь избавиться от черноты в первом кадре или удалить первый кадр?
1434

da34c52815b689a4ef333deacd207f36.gif


UPDATE!!!
Решение найдено. Вот итоговый рабочий код!

PHP:
<?php
    set_time_limit(0);
    ini_set("memory_limit", "24G");  

    $imagick = new Imagick('source.gif');
    $frames = $imagick->coalesceImages();

    for($y = 0; $y<52; $y++){
        for($x = 0; $x<29; $x++){
           
            $out = new Imagick();
            //$out->newImage(100, 100, new ImagickPixel('black'));
           
            foreach ($frames as $frame) {
                $region = $frame->getImageRegion(100, 100, $x*100, $y*100);
                $region->setImagePage(100, 100, 0, 0);
                $out->addImage($region);
                $out->nextImage();              
            }
            $out->writeImages('result/'.$x.'_'.$y.'.gif', true);
            $out->clear();
            $out->destroy();          
        }
    }
?>

Сердечно благодарю WMix за найденное решение!
 
Последнее редактирование:
ну слушай, уж найди сам эту строчку, не надо просто капипастить всякую хрень без понимания
 
ну слушай, уж найди сам эту строчку, не надо просто капипастить всякую хрень без понимания
Уже всё нашёл и исправил. Теперь запустил в консоли файл через CLI и жду. :))) Ждать долго. Файл большой и 60 кадров (30 кадров в секунду)
Рендер локации в Unity3d. :)

Всё чудесно режется.
9427c18a82a481091d6e6a5311656c31.gif
99a1e842bb1d4b0d8d53e64ee4a9d96f.gif

54af2f642adacbaf92b9cd1d6fc1ff2c.gif
51cc48232d6e46aecf9ea2e0974c4469.gif

bb5f892374bca1be791a45761e873101.gif

Ещё раз благодарю!
 
Последнее редактирование:
прилагаю готовый скрипт с индикацией прогресса - может кому-нибудь пригодится. запускать через консоль.
2f8171d40d5f2f9a7bbaacfdb84df368.png

PHP:
<?php
    set_time_limit(0);
    ini_set("memory_limit", "24G"); 
    include ('../connect.php');

        class Colors {
        private $foreground_colors = array();
        private $background_colors = array();

        public function __construct() {
            // Set up shell colors
            $this->foreground_colors['black'] = '0;30';
            $this->foreground_colors['dark_gray'] = '1;30';
            $this->foreground_colors['blue'] = '0;34';
            $this->foreground_colors['light_blue'] = '1;34';
            $this->foreground_colors['green'] = '0;32';
            $this->foreground_colors['light_green'] = '1;32';
            $this->foreground_colors['cyan'] = '0;36';
            $this->foreground_colors['light_cyan'] = '1;36';
            $this->foreground_colors['red'] = '0;31';
            $this->foreground_colors['light_red'] = '1;31';
            $this->foreground_colors['purple'] = '0;35';
            $this->foreground_colors['light_purple'] = '1;35';
            $this->foreground_colors['brown'] = '0;33';
            $this->foreground_colors['yellow'] = '1;33';
            $this->foreground_colors['light_gray'] = '0;37';
            $this->foreground_colors['white'] = '1;37';

            $this->background_colors['black'] = '40';
            $this->background_colors['red'] = '41';
            $this->background_colors['green'] = '42';
            $this->background_colors['yellow'] = '43';
            $this->background_colors['blue'] = '44';
            $this->background_colors['magenta'] = '45';
            $this->background_colors['cyan'] = '46';
            $this->background_colors['light_gray'] = '47';
        }

        // Returns colored string
        public function getColoredString($string, $foreground_color = null, $background_color = null) {
            $colored_string = "";

            // Check if given foreground color found
            if (isset($this->foreground_colors[$foreground_color])) {
                $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m";
            }
            // Check if given background color found
            if (isset($this->background_colors[$background_color])) {
                $colored_string .= "\033[" . $this->background_colors[$background_color] . "m";
            }

            // Add string and end coloring
            $colored_string .=  $string . "\033[0m";

            return $colored_string;
        }

        // Returns all foreground color names
        public function getForegroundColors() {
            return array_keys($this->foreground_colors);
        }

        // Returns all background color names
        public function getBackgroundColors() {
            return array_keys($this->background_colors);
        }
    }

  
    $imagick = new Imagick('cropped.gif');
    $width_s = $imagick->getImageWidth();
    $height_s = $imagick->getImageHeight();
  
    $height = floor($height_s/100);
    $width = floor($width_s/100);
  
  
  
    $frames = $imagick->coalesceImages();
    $colors = new Colors();
    for($y = 0; $y<$height; $y++){
        for($x = 0; $x<$width; $x++){                 
            $out = new Imagick();         
            foreach ($frames as $frame) {
                $region = $frame->getImageRegion(100, 100, $x*100, $y*100);
                $region->setImagePage(100, 100, 0, 0);
                $out->addImage($region);
                $out->nextImage();             
            }
            $out->writeImages('/полный_путь_к_директории/'.$x.'_'.$y.'.gif', true); 
            popen('clear', 'w');
            $gotovosey4as = DB::run('SELECT COUNT(newid) FROM `nature`;')->fetchColumn();
            $stoproc = $height * $width;
            $procentovsey4as = number_format(($gotovosey4as/$stoproc) * 100,2);
            echo $colors->getColoredString(''.$x.'_'.$y.'.gif   Готово на:'.$procentovsey4as.'% ('.$gotovosey4as.' файл из '.$stoproc.') ', 'light_green','white');
            $out->clear();
            $out->destroy();
            DB::run('INSERT INTO `nature` (`newid`,`x`,`y`,`city`) VALUES (NULL,?,?,?);',[$x,$y,iconv('utf-8','windows-1251','Земли Авалона')]);
        }     
    }
    ?>
 
Код:
composer require league/climate
PHP:
require_once('vendor/autoload.php');

$climate = new League\CLImate\CLImate; // !!!

$imagick = new Imagick('cropped.gif');
$width_s = $imagick->getImageWidth();
$height_s = $imagick->getImageHeight();

$height = floor($height_s/100);
$width = floor($width_s/100);

$progress = $climate->progress()->total($height*$width); // !!!

$frames = $imagick->coalesceImages();
$colors = new Colors();
for($y = 0; $y<$height; $y++){
    for($x = 0; $x<$width; $x++){              
        $out = new Imagick();      
        foreach ($frames as $frame) {
            $region = $frame->getImageRegion(100, 100, $x*100, $y*100);
            $region->setImagePage(100, 100, 0, 0);
            $out->addImage($region);
            $out->nextImage();          
        }
        $out->writeImages('/полный_путь_к_директории/'.$x.'_'.$y.'.gif', true);

        $progress->current($x*$y); // !!!

        $out->clear();
        $out->destroy();
        DB::run('INSERT INTO `nature` (`newid`,`x`,`y`,`city`) VALUES (NULL,?,?,?);',[$x,$y,iconv('utf-8','windows-1251','Земли Авалона')]);
    }  
}

progress.gif
 
Сверху