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

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

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

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

Глава 399 · После объектов и базовой работы с изображениями

Imagick: старый recolorImage, понятная матрица и прозрачные PNG

Переход от исторического recolorImage к явным цветовым пресетам: G/B, постоянная добавка, alpha и проверка записанных файлов.

Получить новую расцветку наклейки из понятного правила

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

Собственная наклейка до и после перестановки G/B. Клетки показывают прозрачность и не записаны в файл badge-swapped.png.
Собственная наклейка до и после перестановки G/B. Клетки показывают прозрачность и не записаны в файл badge-swapped.png.

Пиксель хранит красную R, зелёную G и синюю B компоненты; alpha A задаёт степень непрозрачности. В нижнем левом образце RGB равен 100,150,200. Требуется оставить R=100, взять новую G из прежнего B=200 и новую B из прежнего G=150. Матрица ниже — таблица коэффициентов этого правила. Единица означает «взять компоненту», ноль — «не добавлять её».

$swap = [
    1, 0, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 1,
];
$image = new Imagick(__DIR__ . "/badge.png");
$image->colorMatrixImage($swap);
$image->writeImage(__DIR__ . "/badge-swapped.png");

Переносы строк помогают читать таблицу, но PHP получает один массив из 25 чисел. Первые три строки определяют новые R, G, B. Четвёртое место зарезервировано для чёрной K компоненты CMYK; у нашего RGB-изображения его нет. Пятая строка оставляет alpha прежней. Таким образом, форма наклейки и полупрозрачная рамка сохраняются. new Imagick читает файл, -> вызывает метод объекта; writeImage записывает итоговую картинку по указанному пути.

Архив любого примера содержит badge.png.b64: example.php автоматически восстановит обычный badge.png. Короткий фрагмент выше можно выполнить после восстановления или со своим PNG рядом. Он нужен для понимания операции; полный запуск ниже дополнительно сохраняет PNG именно как RGBA8 и читает файл обратно, чтобы показать фактический результат.

Откройте badge-swapped.png: это то же изображение 400×320 с другой расцветкой. Контрольный RGB стал 100,200,150. Геометрия не поворачивается: меняются значения каналов. Старый recolorImage сохранён в справочной части как исторический API, но в нашей сборке отсутствует. Все четыре запускаемые программы выполняют действующий colorMatrixImage.

Перенести старую замену зелёного и синего в явную матрицу

Получен badge-swapped.png размером 400×320: красная компонента каждого пикселя прежняя, зелёная и синяя поменялись местами. Контрольный RGB 100,150,200 стал 100,200,150; alpha сохранилась. Реально выполнен colorMatrixImage, исторический recolorImage отсутствует в этой сборке.

Перенести старую замену зелёного и синего в явную матрицуПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$swap = [
    1, 0, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 1,
];
$result = clone $source;
$ok = $result->colorMatrixImage($swap);
$memory = rcState($result);
$saved = rcSave($result, 'badge-swapped.png');
$read = new Imagick(__DIR__ . '/badge-swapped.png');
$readState = rcState($read);
$views = [rcView($source),rcView($read)];
$images = ['source'=>rcImage($views[0],'first-source8.png'),'result'=>rcImage($views[1],'first-result8.png')];
$rects = rcCompare($views,'legacy-recolor-first.png');
rcDiagnostic('legacy-recolor-first',[
 'matrix'=>$swap,'set_return'=>$ok,'memory_state'=>$memory,'saved'=>$saved,'readback'=>$readState,
 'images'=>$images,'panel_keys'=>['source','result'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['legacy_unavailable'=>!method_exists(Imagick::class,'recolorImage'),'current_available'=>method_exists(Imagick::class,'colorMatrixImage'),'return_true'=>$ok===true,'swap_sample'=>$readState['samples']['opaque']===[25700,51400,38550,65535],'alpha_preserved'=>rcHash($read,'A')===rcHash($source,'A'),'geometry_preserved'=>$readState['geometry']===[400,320],'saved_pixels_equal'=>$memory['RGBA16_sha256']===$readState['RGBA16_sha256']]
]);
echo "legacy-recolor-first.png\nfirst-source8.png\nfirst-result8.png\nbadge-swapped.png\nmetrics.json\nlegacy=unavailable current=colorMatrixImage return=true sample-RGB=100,200,150 alpha=preserved\n";

Получен badge-swapped.png размером 400×320: красная компонента каждого пикселя прежняя, зелёная и синяя поменялись местами. Контрольный RGB 100,150,200 стал 100,200,150; alpha сохранилась. Реально выполнен colorMatrixImage, исторический recolorImage отсутствует в этой сборке. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-first.png
first-source8.png
first-result8.png
badge-swapped.png
metrics.json
legacy=unavailable current=colorMatrixImage return=true sample-RGB=100,200,150 alpha=preserved

Разобраться с пятым столбцом, alpha и постоянной добавкой

Исходник и две разные операции: R плюс 0.1 от alpha в 5×5; R плюс постоянные 0.1 в 6×6. Сравниваются действительно сохранённые PNG.
Исходник и две разные операции: R плюс 0.1 от alpha в 5×5; R плюс постоянные 0.1 в 6×6. Сравниваются действительно сохранённые PNG.

Частая ошибка переноса из Canvas или другого редактора — вставить добавку яркости в пятый столбец. У текущей операции Imagick порядок входных величин R, G, B, K, A, постоянная. Значит, пятый столбец умножается на alpha, а постоянная добавка располагается в шестом. Одна и та же запись 0.1 в этих местах даёт разные правила.

Правило для RПервая строкаОстальные строки
Добавить десятую часть alpha1, 0, 0, 0, 0.1 — матрица 5×5Единицы на диагонали сохраняют G, B, K и A
Добавить постоянные 0.11, 0, 0, 0, 0, 0.1 — матрица 6×6Единицы на диагонали сохраняют остальные компоненты; шестая строка оставлена стандартной

Нормированная единица означает полный диапазон канала. Поэтому постоянные 0.1 — это около 25.5 шага в 8-битной шкале, а не 0.1 шага и не число 25.5 прямо в массиве. Если внешний формат задаёт добавку d в шкале 0…255, соответствующая нормированная добавка равна d/255 при совпадении остальных соглашений. Нельзя механически переносить массив из другой программы: сначала нужны порядок каналов, размер, единицы и смысл alpha.

На нашем образце два квадрата имеют одинаковые RGB 100,150,200, но alpha у первого 255, у второго 128. В red-from-alpha.png красная компонента после записи стала 125 и 112: полупрозрачный пиксель получил меньшую добавку. В red-with-offset.png оба получили R=125. До 8-битной записи вычисления ведутся в Q16, поэтому промежуточные дроби и окончательные целые значения нужно различать.

В прозрачном углу разница тоже существует: постоянное смещение оставило сохранённый R=25 при A=0, а добавка от alpha оставила R=0. Глаз не видит этот цвет, пока alpha нулевая. Это объясняет, почему одинаково выглядящие прозрачные участки могут иметь разные хранимые RGB; проверка только скриншота не отвечает на вопрос о данных.

Различить добавку от alpha и постоянное смещение красного

При 5×5 добавка R зависит от alpha: в двух исходно одинаковых RGB образцах сохранённый R равен 125 и 112. При постоянном смещении 6×6 оба получили 125. Получены red-from-alpha.png и red-with-offset.png; четыре некорректных текущих вызова перехвачены.

Различить добавку от alpha и постоянное смещение красногоПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$alphaColumn = rcIdentity(5);
$alphaColumn[4] = 0.1;
$constantOffset = rcIdentity(6);
$constantOffset[5] = 0.1;
$identity = clone $source;
$identity->colorMatrixImage(rcIdentity(5));
$fromAlpha = clone $source;
$fromAlpha->colorMatrixImage($alphaColumn);
$fromConstant = clone $source;
$fromConstant->colorMatrixImage($constantOffset);
$states = ['identity'=>rcState($identity),'alpha_column'=>rcState($fromAlpha),'constant_offset'=>rcState($fromConstant)];
$saved = [
 'alpha_column'=>rcSave($fromAlpha,'red-from-alpha.png'),
 'constant_offset'=>rcSave($fromConstant,'red-with-offset.png'),
];
$alphaRead = new Imagick(__DIR__ . '/red-from-alpha.png');
$offsetRead = new Imagick(__DIR__ . '/red-with-offset.png');
$readback = ['alpha_column'=>rcState($alphaRead),'constant_offset'=>rcState($offsetRead)];
$errors = [
 'nine_elements'=>rcTry(fn()=>$source->colorMatrixImage([1,0,0,0,0,1,0,1,0])),
 'empty_matrix'=>rcTry(fn()=>$source->colorMatrixImage([])),
 'empty_image'=>rcTry(fn()=>(new Imagick())->colorMatrixImage(rcIdentity(5))),
 'wrong_type'=>rcTry(fn()=>$source->colorMatrixImage('identity')),
];
$views = [rcView($source),rcView($alphaRead),rcView($offsetRead)];
$images = ['source'=>rcImage($views[0],'migration-source8.png'),'alpha_column'=>rcImage($views[1],'migration-alpha-column8.png'),'constant_offset'=>rcImage($views[2],'migration-offset8.png')];
$rects = rcCompare($views,'legacy-recolor-migration.png');
rcDiagnostic('legacy-recolor-migration',[
 'matrices'=>['alpha_column'=>$alphaColumn,'constant_offset'=>$constantOffset],'states'=>$states,'saved'=>$saved,'readback'=>$readback,'errors'=>$errors,'strict_types'=>true,
 'images'=>$images,'panel_keys'=>['source','alpha_column','constant_offset'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['identity_unchanged'=>rcHash($identity)===rcHash($source),'alpha_column_depends_on_alpha'=>$readback['alpha_column']['samples']['opaque'][0]===32125&&$readback['alpha_column']['samples']['half'][0]===28784,'constant_same_increment'=>$readback['constant_offset']['samples']['opaque'][0]===32125&&$readback['constant_offset']['samples']['half'][0]===32125,'transparent_RGB_distinct'=>$readback['alpha_column']['samples']['corner']===[0,0,0,0]&&$readback['constant_offset']['samples']['corner']===[6425,0,0,0],'both_alpha_preserved'=>rcHash($alphaRead,'A')===rcHash($source,'A')&&rcHash($offsetRead,'A')===rcHash($source,'A'),'nine_rejected'=>($errors['nine_elements']['error']['class']??null)==='ImagickException','empty_matrix_rejected'=>($errors['empty_matrix']['error']['class']??null)==='ImagickException','empty_image_rejected'=>($errors['empty_image']['error']['class']??null)==='ImagickException','wrong_type_rejected'=>($errors['wrong_type']['error']['class']??null)==='TypeError']
]);
echo "legacy-recolor-migration.png\nmigration-source8.png\nmigration-alpha-column8.png\nmigration-offset8.png\nred-from-alpha.png\nred-with-offset.png\nmetrics.json\nidentity=unchanged alpha-column-R=125,112 constant-R=125,125 rejected-inputs=4\n";

При 5×5 добавка R зависит от alpha: в двух исходно одинаковых RGB образцах сохранённый R равен 125 и 112. При постоянном смещении 6×6 оба получили 125. Получены red-from-alpha.png и red-with-offset.png; четыре некорректных текущих вызова перехвачены. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-migration.png
migration-source8.png
migration-alpha-column8.png
migration-offset8.png
red-from-alpha.png
red-with-offset.png
metrics.json
identity=unchanged alpha-column-R=125,112 constant-R=125,125 rejected-inputs=4

rcIdentity(5) в полном примере создаёт таблицу с единицами на диагонали; контрольный вызов с ней не изменил изображение. Массивы индексируются с нуля: [4] — пятый элемент первой строки 5×5, [5] — шестой элемент первой строки 6×6. Helper здесь сокращает повторение уже разобранной таблицы. Он не скрывает смысл коэффициентов: полные матрицы сохранены в metrics.json.

Четыре проверки неправильных входов находятся после полезного преобразования. Девять чисел из исторического примера, пустой массив и пустое изображение дали ImagickException; строка вместо массива дала TypeError. Это наблюдения текущей сборки. Ошибки не подавляются @. Простая замена названия метода без пересмотра размера и смысла таблицы не является корректной миграцией.

Сделать отдельную полупрозрачную версию и увидеть результат

Та же наклейка с alpha, умноженной на 0.5: просвечивает больше фона, а RGB остаются прежними. Сохранённый badge-faded.png не содержит клеток.
Та же наклейка с alpha, умноженной на 0.5: просвечивает больше фона, а RGB остаются прежними. Сохранённый badge-faded.png не содержит клеток.

Для полупрозрачной версии значка нужно ослабить покрытие изображения, сохранив его краски. Alpha здесь — непрозрачность: 0 означает полную прозрачность, максимум — полную непрозрачность. Умножение alpha на 0.5 делает каждый пиксель прозрачнее; это не то же самое, что умножить RGB на 0.5 и получить тёмную картинку.

$fadeMatrix = [
    1, 0, 0, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 0.5,
];
$faded = clone $source;
$faded->colorMatrixImage($fadeMatrix);

Изменяется только пятая строка: новое A равно половине старого A. clone создаёт отдельный экземпляр нашего одиночного изображения, поэтому исходник остаётся для сравнения. Предыдущие строки оставляют RGB неизменными. У исходника уже есть alpha; общий разбор включения канала, фона и композиции находится в главе об alpha и фоне.

Сделать наклейку полупрозрачной и прочитать записанный PNG

В badge-faded.png RGB не изменились, а наклейка стала прозрачнее. Непрозрачный образец дал alpha 32768 в Q16 до записи и 127 из 255 в готовом PNG8; это разные шкалы и стадии обработки. Прозрачный угол остался прозрачным, клетки присутствуют только в просмотре.

Сделать наклейку полупрозрачной и прочитать записанный PNGПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$fadeMatrix = [
    1, 0, 0, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 0.5,
];
$faded = clone $source;
$ok = $faded->colorMatrixImage($fadeMatrix);
$memory = rcState($faded);
$saved = rcSave($faded,'badge-faded.png');
$read = new Imagick(__DIR__ . '/badge-faded.png');
$readState = rcState($read);
$views = [rcView($source),rcView($read)];
$images = ['source'=>rcImage($views[0],'alpha-source8.png'),'faded'=>rcImage($views[1],'alpha-faded8.png')];
$rects = rcCompare($views,'legacy-recolor-alpha.png');
rcDiagnostic('legacy-recolor-alpha',[
 'matrix'=>$fadeMatrix,'set_return'=>$ok,'memory_state'=>$memory,'saved'=>$saved,'readback'=>$readState,'display'=>['checkerboard_cell'=>20,'checkerboard_is_output_alpha'=>false],
 'images'=>$images,'panel_keys'=>['source','faded'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['return_true'=>$ok===true,'memory_RGB_unchanged'=>$memory['RGB16_sha256']===$sourceBefore['RGB16_sha256'],'saved_RGB_unchanged'=>$readState['RGB16_sha256']===$sourceBefore['RGB16_sha256'],'memory_opaque_alpha'=>$memory['samples']['opaque'][3]===32768,'saved_opaque_alpha'=>$readState['samples']['opaque'][3]===32639,'saved_half_alpha'=>$readState['samples']['half'][3]===16448,'corner_stays_transparent'=>$readState['samples']['corner'][3]===0,'quantization_recorded'=>$memory['alpha16_sha256']!==$readState['alpha16_sha256']]
]);
echo "legacy-recolor-alpha.png\nalpha-source8.png\nalpha-faded8.png\nbadge-faded.png\nmetrics.json\nRGB=unchanged alpha-memory=32768 alpha-PNG8=127 transparent-corner=0\n";

В badge-faded.png RGB не изменились, а наклейка стала прозрачнее. Непрозрачный образец дал alpha 32768 в Q16 до записи и 127 из 255 в готовом PNG8; это разные шкалы и стадии обработки. Прозрачный угол остался прозрачным, клетки присутствуют только в просмотре. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-alpha.png
alpha-source8.png
alpha-faded8.png
badge-faded.png
metrics.json
RGB=unchanged alpha-memory=32768 alpha-PNG8=127 transparent-corner=0

После выполнения откройте badge-faded.png поверх цветного фона: сам фон отсутствует в файле, наклейка лишь просвечивает. Для понятного сравнения программа отдельно рисует серые клетки с шагом 20 пикселей и накладывает на них файл. Это вспомогательный просмотр, а не изменение готовой прозрачной картинки.

Один и тот же непрозрачный образец дал A=32768 в памяти Q16 с максимумом 65535, затем A=127 в записанном PNG8 с максимумом 255. При повторном экспорте значений PNG в шкале Q16 это 32639, то есть 127×257. Исходно полупрозрачный образец записался с A=64. Значения не объявляются побитово одинаковыми до и после кодирования: сохранение в 8 бит допускает квантование. RGB при этом в нашем примере действительно совпали.

Этот приём подходит декоративной прозрачной версии значка. Он не является защитой изображения, удалением фона или гарантией контраста интерфейса. В HTML иногда достаточно CSS opacity для всего элемента; здесь нужен отдельный PNG, который будет использоваться независимо от страницы. Для другой глубины или версии кодера проверяйте готовый файл, а не переносите наблюдённое округление как универсальное правило.

Сохранить два понятных пресета вместе с настоящими PNG

Два готовых файла одной наклейки: variant-swap.png меняет G/B, variant-offset.png добавляет красный компонент. Оба сохраняют исходную alpha.
Два готовых файла одной наклейки: variant-swap.png меняет G/B, variant-offset.png добавляет красный компонент. Оба сохраняют исходную alpha.

Теперь оформим повторяемый экспорт для каталога вариантов. У старого массива из девяти чисел нет сведений о его намерении; автоматически угадывать любую старую конфигурацию рискованно. Вместо этого задаём два новых пресета с ясными именами: swap-gb и red-plus-0.1. Каждый хранит полный массив подходящего размера и проходит фактическое сохранение и чтение.

$exports = [
    "swap-gb" => exportMatrixPNG($source, $swap, "variant-swap.png"),
    "red-plus-0.1" => exportMatrixPNG($source, $offset, "variant-offset.png"),
];

Массив $exports связывает имя пресета с результатом функции. exportMatrixPNG ниже принимает исходный Imagick, явную матрицу и имя файла. Она работает с копией, проверяет размер массива, применяет преобразование, сохраняет RGBA8 и читает новый файл. Для этих двух цветовых пресетов действует отдельное требование: геометрия и alpha должны остаться исходными. Поэтому вариант с уменьшением alpha сознательно не передаётся этой функции.

Выпустить две версии наклейки с читаемым описанием пресетов

Записаны variant-swap.png, variant-offset.png и recolor-export.json. Функция проверила геометрию и исходную alpha обоих файлов. JSON хранит явные матрицы, действующий API, хеш исходника и фактические хеши выходов; историческое выполнение не заявлено.

Выпустить две версии наклейки с читаемым описанием пресетовПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$swap = [
    1, 0, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 1,
];
function exportMatrixPNG(Imagick $source, array $matrix, string $filename): array
{
    if (!in_array(count($matrix), [25, 36], true)) {
        throw new InvalidArgumentException('Use an explicit 5x5 or 6x6 matrix');
    }
    $result = clone $source;
    $result->colorMatrixImage($matrix);
    $memory = rcState($result);
    $saved = rcSave($result, $filename);
    $read = new Imagick(__DIR__ . '/' . $filename);
    if ($read->getImageGeometry() !== $source->getImageGeometry()) {
        throw new RuntimeException('Unexpected output geometry');
    }
    if (rcHash($read, 'A') !== rcHash($source, 'A')) {
        throw new RuntimeException('This color-only preset must preserve alpha');
    }
    return ['matrix'=>$matrix,'memory_state'=>$memory,'saved'=>$saved,
        'readback'=>rcState($read),'alpha_preserved'=>true];
}
$offset = rcIdentity(6);
$offset[5] = 0.1;
$exports = [
    'swap-gb'=>exportMatrixPNG($source,$swap,'variant-swap.png'),
    'red-plus-0.1'=>exportMatrixPNG($source,$offset,'variant-offset.png'),
];
$manifest = [
    'schema'=>'own-recolor-presets-v1',
    'source'=>'badge.png',
    'source_sha256'=>$sourceHash,
    'api'=>'colorMatrixImage',
    'legacy_execution_claimed'=>false,
    'channel_order'=>['R','G','B','K','A','constant'],
    'exports'=>$exports,
];
file_put_contents(__DIR__ . '/recolor-export.json', json_encode(
    $manifest, JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR
) . "\n");
$readManifest = json_decode((string)file_get_contents(__DIR__.'/recolor-export.json'),true,512,JSON_THROW_ON_ERROR);
$swapRead = new Imagick(__DIR__.'/variant-swap.png');
$offsetRead = new Imagick(__DIR__.'/variant-offset.png');
$views = [rcView($swapRead),rcView($offsetRead)];
$images = ['swap'=>rcImage($views[0],'workflow-swap8.png'),'offset'=>rcImage($views[1],'workflow-offset8.png')];
$rects = rcCompare($views,'legacy-recolor-workflow.png');
rcDiagnostic('legacy-recolor-workflow',[
 'exports'=>$exports,'manifest'=>$readManifest,'manifest_sha256'=>hash_file('sha256',__DIR__.'/recolor-export.json'),'readback'=>['swap'=>rcState($swapRead),'offset'=>rcState($offsetRead)],
 'images'=>$images,'panel_keys'=>['swap','offset'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['exports_differ'=>rcHash($swapRead)!==rcHash($offsetRead),'swap_alpha_preserved'=>$exports['swap-gb']['alpha_preserved'],'offset_alpha_preserved'=>$exports['red-plus-0.1']['alpha_preserved'],'source_bound'=>$readManifest['source_sha256']===hash_file('sha256',__DIR__.'/badge.png'),'file_hashes_bound'=>$readManifest['exports']['swap-gb']['saved']['sha256']===hash_file('sha256',__DIR__.'/variant-swap.png')&&$readManifest['exports']['red-plus-0.1']['saved']['sha256']===hash_file('sha256',__DIR__.'/variant-offset.png'),'matrices_bound'=>$readManifest['exports']['swap-gb']['matrix']===$swap&&$readManifest['exports']['red-plus-0.1']['matrix']===$offset]
]);
echo "legacy-recolor-workflow.png\nworkflow-swap8.png\nworkflow-offset8.png\nvariant-swap.png\nvariant-offset.png\nrecolor-export.json\nmetrics.json\nexports=2 explicit-presets=yes alpha=preserved manifest=read-back\n";

Записаны variant-swap.png, variant-offset.png и recolor-export.json. Функция проверила геометрию и исходную alpha обоих файлов. JSON хранит явные матрицы, действующий API, хеш исходника и фактические хеши выходов; историческое выполнение не заявлено. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-workflow.png
workflow-swap8.png
workflow-offset8.png
variant-swap.png
variant-offset.png
recolor-export.json
metrics.json
exports=2 explicit-presets=yes alpha=preserved manifest=read-back

Получатся variant-swap.png, variant-offset.png и recolor-export.json. JSON можно открыть текстовым редактором: в нём указаны действующий API, порядок каналов, источник, коэффициенты каждого пресета и контрольные суммы выходов. При записи используется JSON_PRESERVE_ZERO_FRACTION, чтобы 1.0 и 0.0 при строгом обратном чтении не превратились в целые 1 и 0. Это объяснение формата нашего отчёта, а не дополнительное условие самой матрицы.

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

Матрица применяется ко всем пикселям текущего кадра и может менять чёрные и белые области; постоянная положительная добавка осветляет даже нулевую компоненту. Большие или отрицательные коэффициенты могут привести к ограничению диапазона и потере деталей, особенно в целочисленной сборке. Художественное преобразование не восстанавливает утраченную информацию и не заменяет управление цветом. Для точного внешнего цвета сначала согласуйте пространство и ICC-профили; наша синтетическая картинка создана в sRGB, а helper пишет её без профилей.

RGB, K и alpha — разные величины. Пример не конвертирует RGB в CMYK и не доказывает пригодность для печати. Здесь также не реализуется полный перевод YUV или перенос произвольной Canvas-матрицы: такие задачи требуют дополнительных соглашений. Для дальнейшей работы перейдите к вычислениям над каналами, цветовому пространству или диагностике печатных параметров.

Исторический recolorImage, ограничения и проверяемый перенос

Imagick::recolorImage: полный исторический контракт

Imagick::recolorImage(array $matrix): bool

recolorImage применяет матричное преобразование к цветам текущего изображения. Официальное описание перечисляет сдвиг, масштабирование, сдвиг по осям и поворот цветов: речь о компонентах цвета, а не о перемещении или повороте самой картинки. Исторический метод поддерживал матрицы переменного квадратного размера; мануал обычно рекомендует 5×5 для RGBA и 6×6 для CMYK и отдельно упоминает нормированные значения последней строки. Это краткое историческое описание нельзя превращать в правило «пятый столбец всегда является добавкой». Конкретное устройство нынешней операции показано выше и подтверждено исходниками и пикселями.

Часть контрактаЧто нужно знать
matrixОбязательный array с числовыми коэффициентами. Матрица передаётся плоским массивом по строкам, а не массивом вложенных строк.
Результатbool: true при успешном выполнении. Результат изображения остаётся в текущем Imagick; присваивать возвращённый bool вместо изображения нельзя.
ВерсииОфициальная доступность из каталога: PECL imagick 2 >= 2.3.0, PECL imagick 3. Страница дополнительно требует ImageMagick 6.3.6 или новее для исторического метода.
УстареваниеМетод помечен deprecated с Imagick 3.4.4. В проверенной Imagick 3.8.1 + ImageMagick 7.1.1-46 его нет: method_exists возвращает false.

В исходниках Imagick 3.8.1 recolorImage находится в ветке для ImageMagick ниже 7, если устаревший API не исключён при сборке. Исторический wrapper проверяет наличие изображения, преобразует числовой массив, требует квадратное число элементов и вызывает MagickRecolorImage. В исследованной ImageMagick 6.9.13-25 этот библиотечный мост передаёт матрицу в ColorMatrixImage. Это чтение исходников, а не запуск старой среды: равенство результата любых старых и новых сборок здесь не обещается.

Официальный пример recolorImage читает изображение по realpath, задаёт девять коэффициентов, меняющих местами G и B, вызывает метод с оператором подавления ошибок @, затем отправляет Content-Type image/jpg и getImageBlob. Там же оставлен закомментированный другой набор коэффициентов. Этот пример объясняет происхождение старого кода, но не служит готовым рецептом переноса: текущий PHP-wrapper colorMatrixImage принимает только 25 или 36 чисел. Девять чисел в нашей проверке вызвали ImagickException. @ не исправит неправильную матрицу; формат файла и HTTP-заголовок тоже нужно согласовать явно.

// Историческая форма из старого кода; здесь недоступна.
$image->recolorImage([1,0,0, 0,0,1, 0,1,0]);

// Текущий вызов после явного построения 25 коэффициентов.
$image->colorMatrixImage($swap);

На официальной странице recolorImage нет отдельного раздела Errors. Поэтому мы не выдаём ошибки нынешней альтернативы за выполненные тесты старого метода. В реально выполненном colorMatrixImage получены ImagickException для девяти коэффициентов, пустой матрицы и пустого изображения, TypeError для строки вместо массива. Сообщения приведены в metrics.json. В рабочих обработчиках проверяйте доступность API и входные данные, а затем перехватывайте подходящие исключения; используйте главу об исключениях для подробного разбора try/catch.

Полный текущий контракт colorMatrixImage и другие операции над каналами уже находятся в главе о математике каналов. Эта глава добавляет историческую карточку recolorImage и практику переноса. Для подготовки цветового пространства нужна отдельная глава о цветовом пространстве и профилях; матрица сама по себе не является ICC-преобразованием. Официальная страница также отсылает к Imagick::displayImage: этот метод не заменяет сохранение PNG или показ изображения браузером.

Как запустить, упражнения и ответы

Распакуйте example.php, badge.png.b64 и recolor-fixture.php в одну папку. Выполните php example.php; исходный badge.png восстановится автоматически. Нужны расширение Imagick и доступный colorMatrixImage. Четыре программы проверены в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Исторический recolorImage недоступен: его запуск не заявляется. Исходник свой, 400×320; PNG данных сохраняются как RGBA8, вспомогательные изображения — RGB8.

Упражнение 1: почему нельзя передать девять коэффициентов из старого примера в текущий метод? Ответ: PHP-wrapper требует ровно 25 или 36; нужно явно выразить нужное правило в поддерживаемой форме. Упражнение 2: что означает 0.1 в пятом месте первой строки 5×5? Добавку 0.1×A к R, а не постоянное увеличение. Упражнение 3: как добавить постоянные 20 единиц шкалы 0…255? Использовать шестой столбец 6×6 с коэффициентом 20/255 при соответствующих цветовых соглашениях. Упражнение 4: почему alpha до записи 32768, а после чтения PNG 32639? Вычисление Q16 затем квантовано в PNG8; готовое значение 127 преобразовано обратно в Q16 как 127×257. Упражнение 5: одинаковая картинка на экране означает одинаковые RGBA? Нет: полностью прозрачные пиксели могут хранить разные RGB.

Полный вспомогательный PHP из каждого архива
<?php
declare(strict_types=1);
function rcRestore():void {
    $hashes=json_decode('{"badge.png":"4d3d22834e3654434e5d64eb8e31952a6572ec81f8132f3d183ba71ee55b9797"}',true,512,JSON_THROW_ON_ERROR);
    foreach($hashes as $name=>$hash){$raw=base64_decode((string)file_get_contents(__DIR__.'/'.$name.'.b64'),true);if($raw===false||hash('sha256',$raw)!==$hash||file_put_contents(__DIR__.'/'.$name,$raw)===false)throw new RuntimeException('Input missing or changed');}
    if(!method_exists(Imagick::class,'colorMatrixImage'))throw new RuntimeException('This example requires colorMatrixImage');
}
function rcHash(Imagick $im,string $map='RGBA'):string{return hash('sha256',json_encode($im->exportImagePixels(0,0,$im->getImageWidth(),$im->getImageHeight(),$map,Imagick::PIXEL_SHORT),JSON_THROW_ON_ERROR));}
function rcState(Imagick $im):array {
    $coords=['opaque'=>[40,290],'half'=>[140,290],'corner'=>[0,0]];$samples=[];
    foreach($coords as $key=>[$x,$y])$samples[$key]=$im->exportImagePixels($x,$y,1,1,'RGBA',Imagick::PIXEL_SHORT);
    $alpha=$im->exportImagePixels(0,0,$im->getImageWidth(),$im->getImageHeight(),'A',Imagick::PIXEL_SHORT);
    return ['geometry'=>[$im->getImageWidth(),$im->getImageHeight()],'RGBA16_sha256'=>rcHash($im),'RGB16_sha256'=>rcHash($im,'RGB'),'alpha16_sha256'=>rcHash($im,'A'),'alpha_enabled'=>$im->getImageAlphaChannel(),'alpha_range'=>[min($alpha),max($alpha)],'sample_coordinates'=>$coords,'samples'=>$samples];
}
function rcIdentity(int $n):array{$a=array_fill(0,$n*$n,0.0);for($i=0;$i<$n;$i++)$a[$i*$n+$i]=1.0;return $a;}
function rcSave(Imagick $im,string $name,int $type=6):array {
    $copy=clone $im;$copy->setImageFormat('png');$copy->setImageDepth(8);$copy->setOption('png:bit-depth','8');$copy->setOption('png:color-type',(string)$type);$copy->setOption('png:exclude-chunk','all');
    if(!$copy->writeImage(__DIR__.'/'.$name))throw new RuntimeException('Cannot save PNG');
    return ['filename'=>$name,'sha256'=>hash_file('sha256',__DIR__.'/'.$name),'bytes'=>filesize(__DIR__.'/'.$name),'png_color_type'=>$type,'state'=>rcState(new Imagick(__DIR__.'/'.$name))];
}
function rcView(Imagick $im):Imagick {
    $view=new Imagick();$view->newImage(400,320,'#f4f5f2','png');$draw=new ImagickDraw();$draw->setStrokeAntialias(false);$draw->setFillColor('#d5deda');
    for($y=0;$y<320;$y+=20)for($x=0;$x<400;$x+=20)if((intdiv($x,20)+intdiv($y,20))%2===0)$draw->rectangle($x,$y,$x+19,$y+19);
    $view->drawImage($draw);$view->compositeImage($im,Imagick::COMPOSITE_OVER,0,0);return $view;
}
function rcImage(Imagick $im,string $name):array {$saved=rcSave($im,$name,2);return ['geometry'=>[$im->getImageWidth(),$im->getImageHeight()],'RGBA16_sha256'=>rcHash($im),'encoded_png'=>['filename'=>$name,'RGBA16_sha256'=>$saved['state']['RGBA16_sha256']],'display_basis'=>'Actual PHP readback view; checkerboard is display only, not data PNG'];}
function rcCompare(array $images,string $name):array {$canvas=new Imagick();$canvas->newImage(400*count($images),320,'white','png');$rects=[];foreach($images as $i=>$im){$canvas->compositeImage($im,Imagick::COMPOSITE_COPY,400*$i,0);$rects[]=['x'=>400*$i,'y'=>0,'width'=>400,'height'=>320];}rcSave($canvas,$name,2);return $rects;}
function rcTry(callable $fn):array{try{$v=$fn();return ['type'=>get_debug_type($v),'value'=>$v];}catch(Throwable $e){return ['error'=>['class'=>get_class($e),'message'=>$e->getMessage()]];}}
function rcDiagnostic(string $id,array $data):void {
    $doc=['schema'=>'aen-legacy-recolor-program-diagnostics-v1','example_id'=>$id,'runtime'=>['php'=>PHP_VERSION,'imagick'=>phpversion('imagick'),'imagemagick'=>Imagick::getVersion()],'legacy_available'=>method_exists(Imagick::class,'recolorImage'),'legacy_executed'=>false,'current_available'=>method_exists(Imagick::class,'colorMatrixImage'),'executed_api'=>'colorMatrixImage']+$data;
    if(file_put_contents(__DIR__.'/metrics.json',json_encode($doc,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR)."\n")===false)throw new RuntimeException('Cannot save metrics');
}

rcRestore восстанавливает вход, rcIdentity создаёт единичную таблицу, rcSave сохраняет клон и читает записанный PNG, rcState раздельно измеряет RGB и alpha в фиксированных точках. rcView добавляет клетки только отдельному изображению просмотра. Эти функции сопровождают доказуемый запуск; минимальная полезная матрица и вызов разобраны в начале главы.

Практические вопросы, из которых выбраны ситуации

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

Recolor images with imagick: Цвет получается подбором нескольких операций и короткого массива, но разработчик не понимает, почему. Начинаем с заранее сформулированного правила преобразования каждого канала; новый пресет имеет понятное имя и сохранённые коэффициенты вместо случайного подбора.

Imagick & PHP: Colorize an image without loss of contrast: При тонировании теряется чёрный или белый, и последовательность инверсий не даёт понятной гарантии. Объясняем, что постоянная добавка меняет даже нулевую компоненту, а матрица может ограничить диапазон; не обещаем сохранить контраст любыми коэффициентами.

Imagemagick convert color-matrix: Массив коэффициентов Canvas не переносится в ImageMagick без изменений. Разделяем форму матрицы, положение alpha и постоянной добавки, нормированные единицы и действительное ограничение PHP-wrapper 25/36; произвольный конвертер Canvas не заявляем.

How does Imagick::recolorImage function work?: Девять коэффициентов в старом примере не объясняют смысл строк и компонентов. Разбираем G/B на конкретном пикселе, затем строим явную таблицу 5×5 для текущего API и честно отделяем историческую доступность.

Converting limited range YUV to sRGB using ImageMagick: Для видеоданных требуется преобразование с конкретными диапазонами, коэффициентами и постоянными добавками. Используем проблему как напоминание о единицах: показываем нормированную добавку 0.1 и границы переноса. Полную конверсию YUV/BT.709 наша глава не реализует.

Change the RGB numbers of images using ImageMagick: Заданные RGB путаются с выражением изменения каналов и визуальным тонированием. Отделяем замену каналов, постоянную добавку и художественный результат; показываем фактические пиксели и два явно определённых пресета.

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

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

Imagick::recolorImageПеренести старую цветовую матрицу и понять каждый компонент

PECL imagick 2 >= 2.3.0, PECL imagick 3 · imagick

Imagick::recolorImage(array $matrix): bool

Imagick::recolorImage: полный исторический контракт

Imagick::recolorImage(array $matrix): bool

recolorImage применяет матричное преобразование к цветам текущего изображения. Официальное описание перечисляет сдвиг, масштабирование, сдвиг по осям и поворот цветов: речь о компонентах цвета, а не о перемещении или повороте самой картинки. Исторический метод поддерживал матрицы переменного квадратного размера; мануал обычно рекомендует 5×5 для RGBA и 6×6 для CMYK и отдельно упоминает нормированные значения последней строки. Это краткое историческое описание нельзя превращать в правило «пятый столбец всегда является добавкой». Конкретное устройство нынешней операции показано выше и подтверждено исходниками и пикселями.

Часть контрактаЧто нужно знать
matrixОбязательный array с числовыми коэффициентами. Матрица передаётся плоским массивом по строкам, а не массивом вложенных строк.
Результатbool: true при успешном выполнении. Результат изображения остаётся в текущем Imagick; присваивать возвращённый bool вместо изображения нельзя.
ВерсииОфициальная доступность из каталога: PECL imagick 2 >= 2.3.0, PECL imagick 3. Страница дополнительно требует ImageMagick 6.3.6 или новее для исторического метода.
УстареваниеМетод помечен deprecated с Imagick 3.4.4. В проверенной Imagick 3.8.1 + ImageMagick 7.1.1-46 его нет: method_exists возвращает false.

В исходниках Imagick 3.8.1 recolorImage находится в ветке для ImageMagick ниже 7, если устаревший API не исключён при сборке. Исторический wrapper проверяет наличие изображения, преобразует числовой массив, требует квадратное число элементов и вызывает MagickRecolorImage. В исследованной ImageMagick 6.9.13-25 этот библиотечный мост передаёт матрицу в ColorMatrixImage. Это чтение исходников, а не запуск старой среды: равенство результата любых старых и новых сборок здесь не обещается.

Официальный пример recolorImage читает изображение по realpath, задаёт девять коэффициентов, меняющих местами G и B, вызывает метод с оператором подавления ошибок @, затем отправляет Content-Type image/jpg и getImageBlob. Там же оставлен закомментированный другой набор коэффициентов. Этот пример объясняет происхождение старого кода, но не служит готовым рецептом переноса: текущий PHP-wrapper colorMatrixImage принимает только 25 или 36 чисел. Девять чисел в нашей проверке вызвали ImagickException. @ не исправит неправильную матрицу; формат файла и HTTP-заголовок тоже нужно согласовать явно.

// Историческая форма из старого кода; здесь недоступна.
$image->recolorImage([1,0,0, 0,0,1, 0,1,0]);

// Текущий вызов после явного построения 25 коэффициентов.
$image->colorMatrixImage($swap);

На официальной странице recolorImage нет отдельного раздела Errors. Поэтому мы не выдаём ошибки нынешней альтернативы за выполненные тесты старого метода. В реально выполненном colorMatrixImage получены ImagickException для девяти коэффициентов, пустой матрицы и пустого изображения, TypeError для строки вместо массива. Сообщения приведены в metrics.json. В рабочих обработчиках проверяйте доступность API и входные данные, а затем перехватывайте подходящие исключения; используйте главу об исключениях для подробного разбора try/catch.

Полный текущий контракт colorMatrixImage и другие операции над каналами уже находятся в главе о математике каналов. Эта глава добавляет историческую карточку recolorImage и практику переноса. Для подготовки цветового пространства нужна отдельная глава о цветовом пространстве и профилях; матрица сама по себе не является ICC-преобразованием. Официальная страница также отсылает к Imagick::displayImage: этот метод не заменяет сохранение PNG или показ изображения браузером.

Перенести старую замену зелёного и синего в явную матрицу

Получен badge-swapped.png размером 400×320: красная компонента каждого пикселя прежняя, зелёная и синяя поменялись местами. Контрольный RGB 100,150,200 стал 100,200,150; alpha сохранилась. Реально выполнен colorMatrixImage, исторический recolorImage отсутствует в этой сборке.

Перенести старую замену зелёного и синего в явную матрицуПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$swap = [
    1, 0, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 1,
];
$result = clone $source;
$ok = $result->colorMatrixImage($swap);
$memory = rcState($result);
$saved = rcSave($result, 'badge-swapped.png');
$read = new Imagick(__DIR__ . '/badge-swapped.png');
$readState = rcState($read);
$views = [rcView($source),rcView($read)];
$images = ['source'=>rcImage($views[0],'first-source8.png'),'result'=>rcImage($views[1],'first-result8.png')];
$rects = rcCompare($views,'legacy-recolor-first.png');
rcDiagnostic('legacy-recolor-first',[
 'matrix'=>$swap,'set_return'=>$ok,'memory_state'=>$memory,'saved'=>$saved,'readback'=>$readState,
 'images'=>$images,'panel_keys'=>['source','result'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['legacy_unavailable'=>!method_exists(Imagick::class,'recolorImage'),'current_available'=>method_exists(Imagick::class,'colorMatrixImage'),'return_true'=>$ok===true,'swap_sample'=>$readState['samples']['opaque']===[25700,51400,38550,65535],'alpha_preserved'=>rcHash($read,'A')===rcHash($source,'A'),'geometry_preserved'=>$readState['geometry']===[400,320],'saved_pixels_equal'=>$memory['RGBA16_sha256']===$readState['RGBA16_sha256']]
]);
echo "legacy-recolor-first.png\nfirst-source8.png\nfirst-result8.png\nbadge-swapped.png\nmetrics.json\nlegacy=unavailable current=colorMatrixImage return=true sample-RGB=100,200,150 alpha=preserved\n";

Получен badge-swapped.png размером 400×320: красная компонента каждого пикселя прежняя, зелёная и синяя поменялись местами. Контрольный RGB 100,150,200 стал 100,200,150; alpha сохранилась. Реально выполнен colorMatrixImage, исторический recolorImage отсутствует в этой сборке. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-first.png
first-source8.png
first-result8.png
badge-swapped.png
metrics.json
legacy=unavailable current=colorMatrixImage return=true sample-RGB=100,200,150 alpha=preserved

Выпустить две версии наклейки с читаемым описанием пресетов

Записаны variant-swap.png, variant-offset.png и recolor-export.json. Функция проверила геометрию и исходную alpha обоих файлов. JSON хранит явные матрицы, действующий API, хеш исходника и фактические хеши выходов; историческое выполнение не заявлено.

Выпустить две версии наклейки с читаемым описанием пресетовПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__ . '/recolor-fixture.php';
rcRestore();
$source = new Imagick(__DIR__ . '/badge.png');
$sourceBefore = rcState($source);
$sourceHash = hash_file('sha256', __DIR__ . '/badge.png');
$swap = [
    1, 0, 0, 0, 0,
    0, 0, 1, 0, 0,
    0, 1, 0, 0, 0,
    0, 0, 0, 1, 0,
    0, 0, 0, 0, 1,
];
function exportMatrixPNG(Imagick $source, array $matrix, string $filename): array
{
    if (!in_array(count($matrix), [25, 36], true)) {
        throw new InvalidArgumentException('Use an explicit 5x5 or 6x6 matrix');
    }
    $result = clone $source;
    $result->colorMatrixImage($matrix);
    $memory = rcState($result);
    $saved = rcSave($result, $filename);
    $read = new Imagick(__DIR__ . '/' . $filename);
    if ($read->getImageGeometry() !== $source->getImageGeometry()) {
        throw new RuntimeException('Unexpected output geometry');
    }
    if (rcHash($read, 'A') !== rcHash($source, 'A')) {
        throw new RuntimeException('This color-only preset must preserve alpha');
    }
    return ['matrix'=>$matrix,'memory_state'=>$memory,'saved'=>$saved,
        'readback'=>rcState($read),'alpha_preserved'=>true];
}
$offset = rcIdentity(6);
$offset[5] = 0.1;
$exports = [
    'swap-gb'=>exportMatrixPNG($source,$swap,'variant-swap.png'),
    'red-plus-0.1'=>exportMatrixPNG($source,$offset,'variant-offset.png'),
];
$manifest = [
    'schema'=>'own-recolor-presets-v1',
    'source'=>'badge.png',
    'source_sha256'=>$sourceHash,
    'api'=>'colorMatrixImage',
    'legacy_execution_claimed'=>false,
    'channel_order'=>['R','G','B','K','A','constant'],
    'exports'=>$exports,
];
file_put_contents(__DIR__ . '/recolor-export.json', json_encode(
    $manifest, JSON_PRETTY_PRINT | JSON_PRESERVE_ZERO_FRACTION | JSON_THROW_ON_ERROR
) . "\n");
$readManifest = json_decode((string)file_get_contents(__DIR__.'/recolor-export.json'),true,512,JSON_THROW_ON_ERROR);
$swapRead = new Imagick(__DIR__.'/variant-swap.png');
$offsetRead = new Imagick(__DIR__.'/variant-offset.png');
$views = [rcView($swapRead),rcView($offsetRead)];
$images = ['swap'=>rcImage($views[0],'workflow-swap8.png'),'offset'=>rcImage($views[1],'workflow-offset8.png')];
$rects = rcCompare($views,'legacy-recolor-workflow.png');
rcDiagnostic('legacy-recolor-workflow',[
 'exports'=>$exports,'manifest'=>$readManifest,'manifest_sha256'=>hash_file('sha256',__DIR__.'/recolor-export.json'),'readback'=>['swap'=>rcState($swapRead),'offset'=>rcState($offsetRead)],
 'images'=>$images,'panel_keys'=>['swap','offset'],'panel_geometry'=>$rects,'source_before'=>$sourceBefore,'source_after'=>rcState($source),'source_sha256'=>$sourceHash,'source_unchanged'=>rcState($source)===$sourceBefore&&hash_file('sha256',__DIR__.'/badge.png')===$sourceHash,
 'checks'=>['exports_differ'=>rcHash($swapRead)!==rcHash($offsetRead),'swap_alpha_preserved'=>$exports['swap-gb']['alpha_preserved'],'offset_alpha_preserved'=>$exports['red-plus-0.1']['alpha_preserved'],'source_bound'=>$readManifest['source_sha256']===hash_file('sha256',__DIR__.'/badge.png'),'file_hashes_bound'=>$readManifest['exports']['swap-gb']['saved']['sha256']===hash_file('sha256',__DIR__.'/variant-swap.png')&&$readManifest['exports']['red-plus-0.1']['saved']['sha256']===hash_file('sha256',__DIR__.'/variant-offset.png'),'matrices_bound'=>$readManifest['exports']['swap-gb']['matrix']===$swap&&$readManifest['exports']['red-plus-0.1']['matrix']===$offset]
]);
echo "legacy-recolor-workflow.png\nworkflow-swap8.png\nworkflow-offset8.png\nvariant-swap.png\nvariant-offset.png\nrecolor-export.json\nmetrics.json\nexports=2 explicit-presets=yes alpha=preserved manifest=read-back\n";

Записаны variant-swap.png, variant-offset.png и recolor-export.json. Функция проверила геометрию и исходную alpha обоих файлов. JSON хранит явные матрицы, действующий API, хеш исходника и фактические хеши выходов; историческое выполнение не заявлено. В архиве example.php, badge.png.b64 и recolor-fixture.php. Распакуйте всё рядом и выполните php example.php: обычный badge.png восстановится автоматически. Полный helper находится в конце главы. Проверенная среда: PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16, Windows. Запускается современный colorMatrixImage; отсутствующий recolorImage не выполняется.

Ожидаемый вывод настоящего запуска
legacy-recolor-workflow.png
workflow-swap8.png
workflow-offset8.png
variant-swap.png
variant-offset.png
recolor-export.json
metrics.json
exports=2 explicit-presets=yes alpha=preserved manifest=read-back
Источники и обсуждения

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

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

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

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