Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 428 · После первых изображений Gmagick, объектов, массивов и циклов

Gmagick: палитра серии, области изменений и метки сцен

Ограничить цвета по всей серии, сохранить области изменений и отличить scene от индекса.

Согласовать восемь цветов по всей серии

Для серии с плавным цветным фоном нужна согласованная палитра: иначе каждый кадр может подобрать свои оттенки. quantizeImages рассматривает всю последовательность. Сравним исходные градиенты с восемью цветами и сохраним как GIF, так и обычные PNG кадров.

Согласовать восемь цветов по всей серии — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Согласовать восемь цветов по всей серии — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Исходный градиент с первым положением коробки
Исходный градиент с первым положением коробки — 320 × 200 px.
Общая палитра8: первый кадр
Общая палитра8: первый кадр — 320 × 200 px.
Общая палитра8: средний кадр
Общая палитра8: средний кадр — 320 × 200 px.
Общая палитра8: последний кадр
Общая палитра8: последний кадр — 320 × 200 px.
<?php
$sequence = new Gmagick(__DIR__ . '/ramp-views.ppm');
$sequence->quantizeImages(8, Gmagick::COLORSPACE_RGB, 0, false, true);
$sequence->setImageIndex(0);
do { $sequence->setImageFormat('GIF'); $sequence->setImageDelay(30); } while ($sequence->nextImage());
$sequence->setImageIndex(0);
$sequence->writeImage(__DIR__ . '/eight-colors.gif', true);

Первый аргумент ограничивает число цветов, второй задаёт RGB-пространство,0 даёт автоматическую глубину дерева. false отключает распределение ошибки; true в последнем аргументе запрашивает её измерение. Метод возвращает объект, а не число ошибки. Полный пример строит серию из исходных PPM без предварительной потери цвета в GIF.

Согласовать восемь цветов по всей серииСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$sequence=new Gmagick();
$sequence->readImageBlob(file_get_contents(__DIR__.'/ramp-views.ppm'));
$sequence->setImageIndex(0);
do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
$sequence->setImageIndex(0);
$sequence->setImageIndex(0);
$sequence->setImageDepth(8);
$sequence->setImageFormat('PNG');
$sequence->writeImage(__DIR__.'/gm100-gradient-source.png',false);
$same=$sequence->quantizeImages(8,Gmagick::COLORSPACE_RGB,0,false,true)===$sequence;
$counts=[];
foreach([0,1,2] as $i){$sequence->setImageIndex($i);$sequence->setImageDepth(8);$sequence->setImageFormat('PNG');$name='gm100-palette'.$i.'.png';$sequence->writeImage(__DIR__.'/'.$name,false);$counts[]=(new Gmagick(__DIR__.'/'.$name))->getImageColors();}
$sequence->setImageIndex(0);
do{$sequence->setImageFormat('GIF');}while($sequence->nextImage());
$sequence->setImageIndex(0);$sequence->writeImage(__DIR__.'/gm100-palette8.gif',true);

gmReport('gm100-palette-shared',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage", "gmagick.quantizeimages", "gmagick.getimagecolors"],["gm100-gradient-source.png", "gm100-palette0.png", "gm100-palette1.png", "gm100-palette2.png"],
    ['eight_each'=>$counts===[8,8,8],'same_object'=>$same,'size_gm100_gradient_source'=>gmInspect('gm100-gradient-source.png')['width']===320&&gmInspect('gm100-gradient-source.png')['height']===200,'size_gm100_palette0'=>gmInspect('gm100-palette0.png')['width']===320&&gmInspect('gm100-palette0.png')['height']===200,'size_gm100_palette1'=>gmInspect('gm100-palette1.png')['width']===320&&gmInspect('gm100-palette1.png')['height']===200,'size_gm100_palette2'=>gmInspect('gm100-palette2.png')['width']===320&&gmInspect('gm100-palette2.png')['height']===200],
    $errors ?? [], ['saved_color_counts'=>$counts,'measure_error_returns_scalar'=>false]);
echo "gm100-gradient-source.png 320x200 gm100-palette0.png 320x200 gm100-palette1.png 320x200 gm100-palette2.png 320x200\n";
Ожидаемый результат
gm100-gradient-source.png 320x200 gm100-palette0.png 320x200 gm100-palette1.png 320x200 gm100-palette2.png 320x200

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-gradient-source.png 320x200 gm100-palette0.png 320x200 gm100-palette1.png 320x200 gm100-palette2.png 320x200

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

Сравнить запросы распределения ошибки

Узор dithering может скрывать ступени ограниченной палитры. Сохраним два варианта одной серии с одинаковым пределом цветов: без распределения ошибки и с ним. Результаты показываются рядом, а не оцениваются по одному булеву флагу.

Исходный градиент с первым положением коробки
Исходный градиент с первым положением коробки — 320 × 200 px.
Сравнить запросы распределения ошибки — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Сравнить запросы распределения ошибки — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Сравнить запросы распределения ошибки — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Сравнить запросы распределения ошибки — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Запрошены dither=false и treeDepth0: фактический средний кадр
Запрошены dither=false и treeDepth0: фактический средний кадр — 320 × 200 px.
Запрошены dither=true и treeDepth8: здесь те же пиксели
Запрошены dither=true и treeDepth8: здесь те же пиксели — 320 × 200 px.
<?php
$sequence = new Gmagick(__DIR__ . '/ramp-views.ppm');
$sequence->quantizeImages(8, Gmagick::COLORSPACE_RGB, 8, true, false);
$sequence->setImageIndex(0);
do { $sequence->setImageFormat('GIF'); $sequence->setImageDelay(30); } while ($sequence->nextImage());
$sequence->setImageIndex(0);
$sequence->writeImage(__DIR__ . '/dither.gif', true);

Глубина8 раскрывает дерево полностью. Dither=true распределяет ошибку выбранного цвета по соседям; новые оттенки могут казаться присутствующими из-за пространственного узора, хотя палитра ограничена. MeasureError=false не запрашивает расчёт общей ошибки.

Сравнить запросы распределения ошибкиСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$counts=[];
foreach([false,true] as $dither){
    $sequence=new Gmagick();$sequence->readImageBlob(file_get_contents(__DIR__.'/ramp-views.ppm'));$sequence->setImageIndex(0);
    do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
    $sequence->quantizeImages(8,Gmagick::COLORSPACE_RGB,$dither?8:0,$dither,false);
    $sequence->setImageIndex(1);$tag=$dither?'on':'off';$sequence->setImageDepth(8);$sequence->setImageFormat('PNG');
    $sequence->writeImage(__DIR__.'/gm100-dither-'.$tag.'.png',false);
    $counts[]=(new Gmagick(__DIR__.'/gm100-dither-'.$tag.'.png'))->getImageColors();
    $sequence->setImageIndex(0);do{$sequence->setImageFormat('GIF');}while($sequence->nextImage());$sequence->setImageIndex(0);$sequence->writeImage(__DIR__.'/gm100-dither-'.$tag.'.gif',true);
}
$off=new Gmagick(__DIR__.'/gm100-dither-off.png');$on=new Gmagick(__DIR__.'/gm100-dither-on.png');
$samePixels=$off->getImageSignature()===$on->getImageSignature();

gmReport('gm100-palette-dither',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.quantizeimages", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage", "gmagick.getimagecolors", "gmagick.getimagesignature"],["gm100-dither-off.png", "gm100-dither-on.png"],
    ['eight_each'=>$counts===[8,8],'observed_same_pixels'=>$samePixels,'size_gm100_dither_off'=>gmInspect('gm100-dither-off.png')['width']===320&&gmInspect('gm100-dither-off.png')['height']===200,'size_gm100_dither_on'=>gmInspect('gm100-dither-on.png')['width']===320&&gmInspect('gm100-dither-on.png')['height']===200],
    $errors ?? [], ['saved_counts'=>$counts,'observed_equal_pixels'=>$samePixels,'scope'=>'this input and fixed runtime; no general equivalence claim']);
echo "gm100-dither-off.png 320x200 gm100-dither-on.png 320x200\n";
Ожидаемый результат
gm100-dither-off.png 320x200 gm100-dither-on.png 320x200

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-dither-off.png 320x200 gm100-dither-on.png 320x200

На этом входе и фиксированной сборке декодированные пиксели всех трёх кадров совпали для запросов false/depth0 и true/depth8, хотя байты файлов GIF различаются. Обе картинки имеют зернистые переходы. Здесь нельзя подписывать один результат «гладкий», а другой «с шумом», или выводить универсальное правило о флаге. Полная программа сравнивает подписи пикселей среднего PNG; причину равенства не приписываем непроверенному внутреннему механизму.

Сохранить только области изменений

У неподвижного фона изменяется только товар между двумя позициями. Посмотрим, какие области действительно требуются следующему кадру. Новый объект deconstructImages содержит первый полный кадр и ограничивающие прямоугольники изменений; исходная последовательность остаётся доступной.

Собрать три ракурса и вставить недостающий средний — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Собрать три ракурса и вставить недостающий средний — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Сохранить только области изменений — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Сохранить только области изменений — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Первый фрагмент изменений: место ушедшей и новой коробки, смещение30,60
Первый фрагмент изменений: место ушедшей и новой коробки, смещение30,60 — 160 × 80 px.
Следующий фрагмент: смещение130,60 на холсте320×200
Следующий фрагмент: смещение130,60 на холсте320×200 — 160 × 80 px.
<?php
$sequence = new Gmagick(__DIR__ . '/views.gif');
$patches = $sequence->deconstructImages();
$patches->setImageIndex(1);
$width = $patches->getImageWidth();
$height = $patches->getImageHeight();
$patches->setImageFormat('PNG');
$patches->writeImage(__DIR__ . '/difference.png', false);

Переменная $patches содержит новый Gmagick. Ширина и высота описывают сохранённый фрагмент, а не весь холст320×200. В область отличий входят и место, откуда предмет ушёл, и место, куда пришёл: надо восстановить старый фон и нарисовать новое положение.

Сохранить только области измененийСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$sequence=new Gmagick();
$sequence->readImageBlob(file_get_contents(__DIR__.'/views.ppm'));
$sequence->setImageIndex(0);
do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
$sequence->setImageIndex(0);
$patches=$sequence->deconstructImages();$newObject=$patches!==$sequence;
$geometries=[];
foreach([0,1,2] as $i){$patches->setImageIndex($i);$geometries[]=[$patches->getImageWidth(),$patches->getImageHeight(),$patches->getImagePage()];}
$patches->setImageIndex(1);
$patches->setImageDepth(8);
$patches->setImageFormat('PNG');
$patches->writeImage(__DIR__.'/gm100-difference1.png',false);
$patches->setImageIndex(2);
$patches->setImageDepth(8);
$patches->setImageFormat('PNG');
$patches->writeImage(__DIR__.'/gm100-difference2.png',false);
$patches->setImageIndex(0);
do{$patches->setImageFormat('GIF');}while($patches->nextImage());
$patches->setImageIndex(0);$patches->writeImage(__DIR__.'/gm100-differences.gif',true);

gmReport('gm100-palette-differences',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.deconstructimages", "gmagick.getimagewidth", "gmagick.getimageheight", "gmagick.getimagepage", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage"],["gm100-difference1.png", "gm100-difference2.png"],
    ['new_object'=>$newObject,'source_full'=>$sequence->getImageWidth()===320&&$sequence->getImageHeight()===200,'patch_geometry'=>$geometries[1][0]===160&&$geometries[1][1]===80&&$geometries[1][2]['x']===30&&$geometries[2][2]['x']===130,'size_gm100_difference1'=>gmInspect('gm100-difference1.png')['width']===160&&gmInspect('gm100-difference1.png')['height']===80,'size_gm100_difference2'=>gmInspect('gm100-difference2.png')['width']===160&&gmInspect('gm100-difference2.png')['height']===80],
    $errors ?? [], ['patch_geometry'=>$geometries,'original_unchanged_geometry'=>true]);
echo "gm100-difference1.png 160x80 gm100-difference2.png 160x80\n";
Ожидаемый результат
gm100-difference1.png 160x80 gm100-difference2.png 160x80

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-difference1.png 160x80 gm100-difference2.png 160x80

Размер прямоугольника — не единственный критерий качества. Сравните воспроизведение полного GIF и сокращённого GIF, а также их реальные байты. Не обещаем, что любой вход после deconstructImages уменьшится или сохранит правильность при произвольном dispose.

Для этих кадров получены области320×200,160×80 и160×80; смещения последних —(30,60) и(130,60). Исходный полный GIF в исследованном варианте занимал2342 байта, сокращённый1760. Это измерение конкретных файлов, не обещание коэффициента сжатия. Смещение задаёт место фрагмента на холсте, а размеры подписи PNG относятся к самому фрагменту.

Отделить метку сцены от позиции списка

Номера кадров во внешней системе могут быть10,20,30, хотя позиции списка остаются0,1,2. Назначим метки scene и проверим это различие. Видимый средний ракурс останется средним после назначения ему метки20.

Отделить метку сцены от позиции списка — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0
Отделить метку сцены от позиции списка — анимированный GIF: 300ms, 300ms, 300ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
В памяти scene20 находится на позиции1; рисунок не переставлен
В памяти scene20 находится на позиции1; рисунок не переставлен — 320 × 200 px.
<?php
$sequence = new Gmagick(__DIR__ . '/views.gif');
$sequence->setImageIndex(1);
$sequence->setImageScene(20);
$scene = $sequence->getImageScene();
$index = $sequence->getImageIndex();
echo $index, ':', $scene, "\n";

getImageIndex сообщает положение указателя, getImageScene — метаданные выбранного кадра. Setter scene не сортирует последовательность. Полная программа присваивает метки всем кадрам, сохраняет GIF и отдельно показывает метки после чтения файла.

Отделить метку сцены от позиции спискаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$sequence=new Gmagick();
$sequence->readImageBlob(file_get_contents(__DIR__.'/views.ppm'));
$sequence->setImageIndex(0);
do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
$sequence->setImageIndex(0);
$before=[];
foreach([10,20,30] as $i=>$scene){$sequence->setImageIndex($i);$sequence->setImageScene($scene);$before[]=[$sequence->getImageIndex(),$sequence->getImageScene()];}
$sequence->setImageIndex(1);
$sequence->setImageDepth(8);
$sequence->setImageFormat('PNG');
$sequence->writeImage(__DIR__.'/gm100-scene20.png',false);
$sequence->setImageIndex(0);
do{$sequence->setImageFormat('GIF');}while($sequence->nextImage());
$sequence->setImageIndex(0);$sequence->writeImage(__DIR__.'/gm100-scene-tags.gif',true);
$read=new Gmagick(__DIR__.'/gm100-scene-tags.gif');$after=[];$read->setImageIndex(0);
do{$after[]=[$read->getImageIndex(),$read->getImageScene()];}while($read->nextImage());

gmReport('gm100-palette-scenes',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.setimagescene", "gmagick.getimageindex", "gmagick.getimagescene", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage"],["gm100-scene20.png"],
    ['memory_tags'=>$before===[[0,10],[1,20],[2,30]],'gif_scenes_renumbered'=>$after===[[0,0],[1,1],[2,2]],'size_gm100_scene20'=>gmInspect('gm100-scene20.png')['width']===320&&gmInspect('gm100-scene20.png')['height']===200],
    $errors ?? [], ['before'=>$before,'gif_readback'=>$after]);
echo "gm100-scene20.png 320x200\n";
Ожидаемый результат
gm100-scene20.png 320x200

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-scene20.png 320x200

Не рассчитывайте на перенос произвольной scene-метки через любой формат. Для устойчивого соответствия внешним ID используйте отдельные данные приложения. Для выбора позиции нужен setImageIndex, а не setImageScene.

В нашем GIF метки10,20,30 после чтения стали0,1,2. Это наблюдаемый контракт выбранного кодера: произвольная метка памяти не переносится этим способом. Порядок картинок при этом остался прежним.

Найти кадр по метке приложения

В каталоге просят кадр с меткой200, а не «второй файл». Функция поиска обходит последовательность и сравнивает getImageScene. После обнаружения сохраняем выбранный ракурс; отсутствие нужной метки становится понятной ошибкой приложения.

Найден ракурс с меткой200, индекс1
Найден ракурс с меткой200, индекс1 — 320 × 200 px.
<?php
$sequence = new Gmagick(__DIR__ . '/views.gif');
$sequence->setImageIndex(1);
$sequence->setImageScene(200);
$label = $sequence->getImageScene();
$sequence->setImageFormat('PNG');
$sequence->writeImage(__DIR__ . '/scene-200.png', false);

Короткий фрагмент показывает назначение и чтение метки. В полной функции do/while проходит все кадры, а if сравнивает метку с искомой. Возвращаемая позиция затем явно выбирается перед записью PNG. Метки формируются в памяти из наших входных данных, а не извлекаются из неподтверждённых метаданных GIF.

Найти кадр по метке приложенияСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$sequence=new Gmagick();
$sequence->readImageBlob(file_get_contents(__DIR__.'/views.ppm'));
$sequence->setImageIndex(0);
do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
$sequence->setImageIndex(0);
foreach([100,200,300] as $i=>$scene){$sequence->setImageIndex($i);$sequence->setImageScene($scene);}
function sceneIndex(Gmagick $sequence,int $wanted):int{
    $sequence->setImageIndex(0);
    do{if($sequence->getImageScene()===$wanted)return $sequence->getImageIndex();}while($sequence->nextImage());
    throw new OutOfBoundsException('Scene label not found');
}
$found=sceneIndex($sequence,200);$sequence->setImageIndex($found);$scene=$sequence->getImageScene();
$sequence->setImageDepth(8);
$sequence->setImageFormat('PNG');
$sequence->writeImage(__DIR__.'/gm100-found-scene.png',false);
$errors=['missing'=>gmTry(fn()=>sceneIndex($sequence,999))];

gmReport('gm100-palette-find-scene',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.setimagescene", "gmagick.getimagescene", "gmagick.getimageindex", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage"],["gm100-found-scene.png"],
    ['selected_label'=>$found===1&&$scene===200,'missing_rejected'=>$errors['missing']['class']==='OutOfBoundsException','size_gm100_found_scene'=>gmInspect('gm100-found-scene.png')['width']===320&&gmInspect('gm100-found-scene.png')['height']===200],
    $errors ?? [], ['selected_index'=>$found,'selected_scene'=>$scene]);
echo "gm100-found-scene.png 320x200\n";
Ожидаемый результат
gm100-found-scene.png 320x200

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-found-scene.png 320x200

Если метки могут повторяться, определите правило: эта функция возвращает первое совпадение. Она не выдаёт случайный текущий кадр, когда ничего не найдено. Для массового каталога соответствие ID и файлов удобно держать отдельно от визуального контейнера.

Подготовить компактную анимацию и обложку

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

Исходный градиент с первым положением коробки
Исходный градиент с первым положением коробки — 320 × 200 px.
Подготовить компактную анимацию и обложку — анимированный GIF: 400ms, 400ms, 400ms; dispose 1,1,1; поле loop=0
Подготовить компактную анимацию и обложку — анимированный GIF: 400ms, 400ms, 400ms; dispose 1,1,1; поле loop=0 — 320 × 200 px.
Полный средний кадр после общей палитры16, пригодный для неподвижной обложки
Полный средний кадр после общей палитры16, пригодный для неподвижной обложки — 320 × 200 px.
<?php
$sequence = new Gmagick(__DIR__ . '/ramp-views.ppm');
$sequence->quantizeImages(16, Gmagick::COLORSPACE_RGB, 0, false, false);
$compact = $sequence->deconstructImages();
$compact->setImageIndex(0);
do { $compact->setImageFormat('GIF'); $compact->setImageDelay(30); } while ($compact->nextImage());
$compact->setImageIndex(0);
$compact->writeImage(__DIR__ . '/catalog-compact.gif', true);

Сначала выбираются цвета по полным кадрам, затем сравниваются уже приведённые к ним изображения. Обратный порядок может включить в анализ цвета только обрезанных областей. Полная функция явно назначает время и dispose, получает новый объект областей и сохраняет оба вида результата.

Подготовить компактную анимацию и обложкуСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__ . '/gm-fixture.php';
gmRestore();
$sequence=new Gmagick();
$sequence->readImageBlob(file_get_contents(__DIR__.'/ramp-views.ppm'));
$sequence->setImageIndex(0);
do{$sequence->setImageDelay(30);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
$sequence->setImageIndex(0);
function compactCatalog(Gmagick $sequence,int $colors):Gmagick{
    if($colors<2||$colors>256)throw new InvalidArgumentException('Palette must be2..256 colors');
    $sequence->quantizeImages($colors,Gmagick::COLORSPACE_RGB,0,false,false);
    $sequence->setImageIndex(0);do{$sequence->setImageDelay(40);$sequence->setImageDispose(Gmagick::DISPOSE_NONE);$sequence->setImageIterations(0);}while($sequence->nextImage());
    return $sequence->deconstructImages();
}
$compact=compactCatalog($sequence,16);$sequence->setImageIndex(1);
$sequence->setImageDepth(8);
$sequence->setImageFormat('PNG');
$sequence->writeImage(__DIR__.'/gm100-catalog-cover.png',false);
$compact->setImageIndex(0);
do{$compact->setImageFormat('GIF');}while($compact->nextImage());
$compact->setImageIndex(0);$compact->writeImage(__DIR__.'/gm100-catalog-compact.gif',true);
$geometry=[];$compact->setImageIndex(0);do{$geometry[]=[$compact->getImageWidth(),$compact->getImageHeight()];}while($compact->nextImage());
$errors=['colors'=>gmTry(fn()=>compactCatalog($sequence,1))];

gmReport('gm100-palette-publish',["gmagick.construct", "gmagick.readimageblob", "gmagick.setimageindex", "gmagick.setimagedelay", "gmagick.setimagedispose", "gmagick.setimageiterations", "gmagick.nextimage", "gmagick.quantizeimages", "gmagick.deconstructimages", "gmagick.setimagedepth", "gmagick.setimageformat", "gmagick.writeimage", "gmagick.getimagewidth", "gmagick.getimageheight"],["gm100-catalog-cover.png"],
    ['compact_geometry'=>$geometry===[[320,200],[256,200],[192,192]],'palette_limit'=>(new Gmagick(__DIR__.'/gm100-catalog-cover.png'))->getImageColors()===16,'range_rejected'=>$errors['colors']['class']==='InvalidArgumentException','size_gm100_catalog_cover'=>gmInspect('gm100-catalog-cover.png')['width']===320&&gmInspect('gm100-catalog-cover.png')['height']===200],
    $errors ?? [], ['patch_geometry'=>$geometry,'source'=>'full frames; shared palette before difference extraction']);
echo "gm100-catalog-cover.png 320x200\n";
Ожидаемый результат
gm100-catalog-cover.png 320x200

Самостоятельный ZIP: php example.php с Gmagick. Окружение: PHP8.3.6 / Gmagick2.0.6RC1 / GraphicsMagick1.3.42 Q16. Собственные PPM и готовый views.gif восстанавливаются из .b64. gm-fixture.php проверяет файлы и записывает metrics.json; изучаемые операции видны в самой программе. PNG и GIF сохраняются рядом.

Вывод успешного запуска
gm100-catalog-cover.png 320x200

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

Упражнение: возврат quantizeImages — число ошибки или объект? Ответ:объект. Совпадает ли scene200 с индексом200? Ответ:нет. Почему видимый фон может увеличивать область различий после квантизации? Ответ:пиксели фона тоже могут различаться; нужно измерять результат. В нашем маршруте16 цветов дали области320×200,256×200 и192×192, а не прямоугольники только коробки. Дальнейшие шаги: имена и состояние файла, реальное кодирование экспорта и состояние и очистка объектов. Для одиночной картинки сравните quantizeImage и mapImage.

Практический вопрос и наш ответ

Effective gif/image color quantization?: При создании GIF сокращение цветов меняет заметные оттенки, а разные кадры дают цветовое мерцание; автор ищет более подходящий алгоритм. Мы рассматриваем всю последовательность одним quantizeImages, сохраняем исходный градиент и каждый результат, сравниваем ограничения палитры и фактическое действие параметров. На нашей сборке два запроса dither дали одинаковые пиксели; это наблюдение явно отделено от описанного назначения аргумента и не превращено в универсальное правило.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

Gmagick::deconstructimagesСравнивает соседние изображения последовательности и возвращает новый Gmagick с ограничивающими областями обнаруженных различий

PECL gmagick >= Unknown · gmagick

Gmagick::deconstructimages(): Gmagick

Сравнивает соседние изображения последовательности и возвращает новый Gmagick с ограничивающими областями обнаруженных различий. Это геометрия изменений, а не универсальный оптимизатор любого GIF. Сохраняйте положение области на общем холсте; один обрезанный PNG ещё не является полным кадром анимации.

Gmagick::deconstructimages(): Gmagick

Аргументов нет. Getter или навигация относятся к текущему изображению и списку, как описано выше.

Возврат: Новый объект Gmagick с областями отличий; исходный объект не заменяется автоматически.

PHP.net указывает GmagickException при ошибке. Неверный тип аргумента может быть отклонён PHP как TypeError до выполнения операции. Проверки диапазона в функциях примеров — отдельные правила приложения. Для nextImage/previousImage обычная граница обхода также выражается false.

Доступность: PECL Gmagick; нижняя граница на этих официальных страницах указана Unknown. Это не означает определённую версию PHP. Примеры привязаны к указанной Q16-сборке, ограничения которой разобраны в уроке.

Два применения: Сохранить только области изменений; Подготовить компактную анимацию и обложку.

Gmagick::getimagesceneЧитает целую метку сцены активного изображения

PECL gmagick >= Unknown · gmagick

Gmagick::getimagescene(): int

Читает целую метку сцены активного изображения. Она позволяет сопоставить кадр с внешним номером съёмки, но не заменяет индекс обхода. Если нужен устойчивый идентификатор во внешнем формате, сохраняйте соответствие отдельно.

Gmagick::getimagescene(): int

Аргументов нет. Getter или навигация относятся к текущему изображению и списку, как описано выше.

Возврат: Целое число с указанным выше смыслом.

PHP.net указывает GmagickException при ошибке. Неверный тип аргумента может быть отклонён PHP как TypeError до выполнения операции. Проверки диапазона в функциях примеров — отдельные правила приложения. Для nextImage/previousImage обычная граница обхода также выражается false.

Доступность: PECL Gmagick; нижняя граница на этих официальных страницах указана Unknown. Это не означает определённую версию PHP. Примеры привязаны к указанной Q16-сборке, ограничения которой разобраны в уроке.

Два применения: Отделить метку сцены от позиции списка; Найти кадр по метке приложения.

Gmagick::quantizeimagesАнализирует цвета последовательности и выбирает фиксированное ограниченное множество представительных цветов, стремясь уменьшить цветовую ошибку и время обработки

PECL gmagick >= Unknown · gmagick

Gmagick::quantizeimages(int $numColors, int $colorspace, int $treeDepth, bool $dither, bool $measureError): Gmagick

Анализирует цвета последовательности и выбирает фиксированное ограниченное множество представительных цветов, стремясь уменьшить цветовую ошибку и время обработки. В отличие от quantizeImage, единицей анализа служит вся серия. Число цветов в сохранённых кадрах и рисунок градиента проверяем отдельно.

Gmagick::quantizeimages(int $numColors, int $colorspace, int $treeDepth, bool $dither, bool $measureError): Gmagick
АргументТипСмысл
numColorsint — обязательныйМаксимальное желаемое число цветов, выбираемых по всей последовательности. Это ограничение палитры, а не число кадров; фактически использованных цветов может быть меньше.
colorspaceint — обязательныйПространство, в котором выполняется сокращение цветов; обычный вариант — Gmagick::COLORSPACE_RGB.
treeDepthint — обязательныйГлубина дерева:0 или1 поручают алгоритму выбрать примерно log4(numColors), обычно с хорошим балансом памяти, скорости и представления цветов. Для малой дисперсии цветов иной уровень может быть полезнее.8 раскрывает дерево полностью.
ditherbool — обязательныйБулев флаг распределения ошибки: ненулевое значение передаёт разницу между исходным и выбранным цветом соседям по кривой Гильберта. Узор может маскировать полосы градиента; новых цветов сверх палитры он не создаёт.
measureErrorbool — обязательныйБулев запрос расчёта общей ошибки квантизации: сумма квадратов RGB-расстояний исходных и полученных пикселей. Возвратом метода остаётся Gmagick, а не вычисленная сумма.

Возврат: Изменённый Gmagick; объект можно использовать для следующей операции.

PHP.net указывает GmagickException при ошибке. Неверный тип аргумента может быть отклонён PHP как TypeError до выполнения операции. Проверки диапазона в функциях примеров — отдельные правила приложения. Для nextImage/previousImage обычная граница обхода также выражается false.

Доступность: PECL Gmagick; нижняя граница на этих официальных страницах указана Unknown. Это не означает определённую версию PHP. Примеры привязаны к указанной Q16-сборке, ограничения которой разобраны в уроке.

Два применения: Согласовать восемь цветов по всей серии; Сравнить запросы распределения ошибки.

Gmagick::setimagesceneНазначает целую метку сцены текущему изображению и возвращает Gmagick

PECL gmagick >= Unknown · gmagick

Gmagick::setimagescene(int $scene): Gmagick

Назначает целую метку сцены текущему изображению и возвращает Gmagick. Метка не означает перемещение изображения в такую позицию. После записи GIF проверяем, что именно сохранил формат; номер в памяти не является обещанием произвольных метаданных в файле.

Gmagick::setimagescene(int $scene): Gmagick
АргументТипСмысл
sceneint — обязательныйЦелая метка сцены у активного изображения. Её изменение само по себе не переставляет кадры и не передвигает указатель. Сохранение такой метки зависит от формата.

Возврат: Изменённый Gmagick; объект можно использовать для следующей операции.

PHP.net указывает GmagickException при ошибке. Неверный тип аргумента может быть отклонён PHP как TypeError до выполнения операции. Проверки диапазона в функциях примеров — отдельные правила приложения. Для nextImage/previousImage обычная граница обхода также выражается false.

Доступность: PECL Gmagick; нижняя граница на этих официальных страницах указана Unknown. Это не означает определённую версию PHP. Примеры привязаны к указанной Q16-сборке, ограничения которой разобраны в уроке.

Два применения: Отделить метку сцены от позиции списка; Найти кадр по метке приложения.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.