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

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

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

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

Глава 394 · После объектов, пикселей и цветовых пространств

Imagick: единицы разрешения и максимальное покрытие CMYK

Размер карточки на бумаге, настоящие каналы TIFF, отдельный перегруженный пиксель и отчёт с понятными ограничениями.

Показать размер карточки: пиксели, плотность и единицы

Пользователь загрузил картинку для печати. В карточке заказа нужно честно показать её физический размер и обратить внимание на подозрительное покрытие краской. Надпись «300 DPI» сама по себе ничего не улучшает: нужны число пикселей, единицы измерения и понятный результат проверки. Начнём с собственной картинки 400×320 и увидим, почему одинаковые пиксели могут соответствовать разным размерам на бумаге.

Настоящая карточка 400×320 и схема её размера: сверху 72 пикселя на дюйм, снизу 300; схема использует 18 экранных пикселей на сантиметр
Настоящая карточка 400×320 и схема её размера: сверху 72 пикселя на дюйм, снизу 300; схема использует 18 экранных пикселей на сантиметр

Рисунок card.png и CMYK-образец patches.tiff приложены в base64; полный example.php восстановит их автоматически. Короткий фрагмент предполагает, что файлы уже восстановлены. new Imagick открывает изображение. Переменная $image хранит объект; стрелка -> вызывает его метод, пустые скобки означают отсутствие аргументов, знак = сохраняет возвращённое значение.

$image = new Imagick(__DIR__ . "/card.png");
$units = $image->getImageUnits();
$resolution = $image->getImageResolution();

getImageUnits сообщает «на дюйм», «на сантиметр» или «неизвестно». Числа плотности находятся отдельно в $resolution["x"] и $resolution["y"]. У исходной учебной карточки единицы неизвестны. Для сравнения ниже создаём копии и явно задаём 72 и 300 пикселей на дюйм:

$image->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);
$image->setImageResolution(300, 300);
$widthCm = $image->getImageWidth() / 300 * 2.54;

Получается 400 ÷ 300 × 2,54 = 3,3867 см. При 72 получится 14,1111 см. Это предполагаемый размер при соблюдении этих метаданных, а не команда принтеру. Ширина массива пикселей остаётся 400; новых деталей не появилось. Для фотографий точнее говорить PPI — пиксели на дюйм; DPI часто используют разговорно, хотя это также термин для точек печатающего устройства.

Прочитать единицы и вычислить размер без изменения пикселей

Один рисунок 400×320 соответствует ширине 14,11 см при 72 пикселях на дюйм и 3,39 см при 300. Настройки не добавили деталей. После записи PNG единицы стали «на сантиметр», плотность — 118,11; пересчёт даёт 299,9994 на дюйм, RGB остались прежними.

Прочитать единицы и вычислить размер без изменения пикселейПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
$at72=clone $card;$at72->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);$at72->setImageResolution(72,72);
$at300=clone $card;$at300->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);$at300->setImageResolution(300,300);
$units=$at300->getImageUnits();$density=$at300->getImageResolution();
$size72=pdPhysical($at72);$size300=pdPhysical($at300);
$saved=pdSavePNG($at300,'first-300.png');$read=new Imagick(__DIR__.'/first-300.png');$readSize=pdPhysical($read);
$equivalent=clone $card;$equivalent->setImageUnits(Imagick::RESOLUTION_PIXELSPERCENTIMETER);$equivalent->setImageResolution(300/2.54,300/2.54);
$relabel=clone $at300;$relabel->setImageUnits(Imagick::RESOLUTION_PIXELSPERCENTIMETER);
$model=pdSizeModel($size72,$size300);
$images=['card'=>pdImage($card,'first-card8.png'),'size_model'=>pdImage($model,'first-size-model8.png')];
$rects=pdCompare([$card,$model],'print-first.png');
pdDiagnostic('print-first',['executed_api'=>['getImageUnits'],'returned_type'=>get_debug_type($units),'declared_return'=>(string)(new ReflectionMethod(Imagick::class,'getImageUnits'))->getReturnType(),'units'=>$units,'density'=>$density,'before_card'=>$beforeCard,'at72'=>pdState($at72),'at300'=>pdState($at300),'saved'=>$saved,'readback'=>pdState($read),'physical72'=>$size72,'physical300'=>$size300,'readback_physical'=>$readSize,'cm_equivalent'=>pdState($equivalent),'cm_physical'=>pdPhysical($equivalent),'relabel_only'=>pdState($relabel),'relabel_physical'=>pdPhysical($relabel),'diagram'=>['order'=>['72ppi','300ppi'],'display_pixels_per_cm'=>18],
'images'=>$images,'panel_keys'=>['card','size_model'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['int_units'=>$units===Imagick::RESOLUTION_PIXELSPERINCH,'same_pixels'=>pdHash($card)===pdHash($at300)&&pdHash($card)===pdHash($read),'same_geometry'=>$read->getImageGeometry()===$card->getImageGeometry(),'PNG_cm'=>$read->getImageUnits()===Imagick::RESOLUTION_PIXELSPERCENTIMETER,'equivalent_ppi'=>abs($readSize['ppi_x']-300)<0.001,'explicit_cm_equivalent'=>abs(pdPhysical($equivalent)['width_cm']-$size300['width_cm'])<1e-10,'relabel_does_not_convert_density'=>$relabel->getImageResolution()['x']===300.0]]);
echo "print-first.png\nfirst-card8.png\nfirst-size-model8.png\nfirst-300.png\nmetrics.json\n";
printf("units=%d raster=400x320 width72=%.4fcm width300=%.4fcm png-ppi=%.4f\n",$units,$size72['width_cm'],$size300['width_cm'],$readSize['ppi_x']);

Один рисунок 400×320 соответствует ширине 14,11 см при 72 пикселях на дюйм и 3,39 см при 300. Настройки не добавили деталей. После записи PNG единицы стали «на сантиметр», плотность — 118,11; пересчёт даёт 299,9994 на дюйм, RGB остались прежними. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-first.png
first-card8.png
first-size-model8.png
first-300.png
metrics.json
units=1 raster=400x320 width72=14.1111cm width300=3.3867cm png-ppi=299.9994

Сложить CMYK в одном пикселе и отличить максимум от среднего

Настоящие четыре CMYK-полосы и столбики их состава: C, M, Y, затем смесь; цветные части столбиков соответствуют C/M/Y/K
Настоящие четыре CMYK-полосы и столбики их состава: C, M, Y, затем смесь; цветные части столбиков соответствуют C/M/Y/K

Для краски нужна другая модель. У одного CMYK-пикселя есть четыре значения: голубая C, пурпурная M, жёлтая Y и чёрная K. Складываем доли в одном месте. Например, 60% + 50% + 40% + 30% дают около 180%. Это допустимое арифметическое значение: четыре канала могут вместе превышать 100%. Представьте четыре подписанных стакана для одной точки; сумма показывает совместное покрытие, но не физический объём жидкости.

Наш TIFF содержит четыре равные вертикальные полосы. Первые три имеют по одному каналу 100%, четвёртая — смесь 60/50/40/30. Метод находит самую большую сумму в одном пикселе. Из разных полос нельзя брать максимальные C, M и Y и складывать их: такого сочетания в исходной картинке нет.

$image = new Imagick(__DIR__ . "/patches.tiff");
$raw = $image->getImageTotalInkDensity();
$quantum = Imagick::getQuantumRange()["quantumRangeLong"];
$percent = 100 * $raw / $quantum;

Результат — число float. В проверенной Q16-сборке исходные доли округляются до целых значений каналов: смесь стала [39321, 32768, 26214, 19661]. Сумма 117964 даёт 180,0015%, а не ровно 180. Небольшая разница объясняется дискретной шкалой. Исходный TIFF сохраняет CMYK; показанная на странице RGB-копия сделана отдельно для экрана и не участвует в измерении.

Измерить максимум суммы четырёх красок в одном пикселе

Метод вернул 117964 в шкале Q16. После деления на 65535 и умножения на 100 получено примерно 180%. Это максимум в смешанной полосе; среднее около 120%, а неверное сложение отдельных максимумов каналов дало бы 330%.

Измерить максимум суммы четырёх красок в одном пикселеПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
$raw=$ink->getImageTotalInkDensity();
$quantum=(float)Imagick::getQuantumRange()['quantumRangeLong'];
$percent=100*$raw/$quantum;
$stats=pdInkStats($ink);$view=pdScreen($ink);$bars=pdInkBars($stats['samples_CMYK16'],$quantum);
$images=['cmyk_view'=>pdImage($view,'minimal-cmyk-view8.png'),'ink_bars'=>pdImage($bars,'minimal-ink-bars8.png')];
$rects=pdCompare([$view,$bars],'print-minimal.png');
pdDiagnostic('print-minimal',['executed_api'=>['getImageTotalInkDensity'],'returned_type'=>get_debug_type($raw),'declared_return'=>(string)(new ReflectionMethod(Imagick::class,'getImageTotalInkDensity'))->getReturnType(),'before_ink'=>$beforeInk,'after_ink'=>pdState($ink,'CMYK'),'raw'=>$raw,'percent'=>$percent,'stats'=>$stats,'diagram'=>['rows'=>['C100','M100','Y100','C60_M50_Y40_K30'],'segment_order'=>['C','M','Y','K'],'full_width_percent'=>400],
'images'=>$images,'panel_keys'=>['cmyk_view','ink_bars'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['actual_float'=>is_float($raw),'expected_Q16'=>$quantum===65535.0,'raw_sum'=>$raw===117964.0,'independent_max'=>$raw===$stats['raw_max']*1.0,'not_mean'=>abs($percent-$stats['mean_percent'])>59,'not_separate_channel_maxima'=>abs($percent-$stats['sum_channel_maxima_percent'])>149,'source_preserved'=>pdState($ink,'CMYK')===$beforeInk]]);
echo "print-minimal.png\nminimal-cmyk-view8.png\nminimal-ink-bars8.png\nmetrics.json\n";
printf("raw=%.0f maximum=%.4f%% mean=%.4f%% channel-maxima-sum=%.4f%%\n",$raw,$percent,$stats['mean_percent'],$stats['sum_channel_maxima_percent']);

Метод вернул 117964 в шкале Q16. После деления на 65535 и умножения на 100 получено примерно 180%. Это максимум в смешанной полосе; среднее около 120%, а неверное сложение отдельных максимумов каналов дало бы 330%. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-minimal.png
minimal-cmyk-view8.png
minimal-ink-bars8.png
metrics.json
raw=117964 maximum=180.0015% mean=120.0004% channel-maxima-sum=330.0008%

Проверить редкую перегрузку и ошибочные трактовки чисел

Один изменённый пиксель (30,30), его настоящий участок 40×32 в увеличении 10× и сравнение максимума со средним
Один изменённый пиксель (30,30), его настоящий участок 40×32 в увеличении 10× и сравнение максимума со средним

Допустим, в небольшой детали оказался пиксель со всеми четырьмя каналами по 100%. В целом картинка почти прежняя, среднее осталось около 120%, но максимум уже 400%. Поэтому усреднение не отвечает на вопрос о самом насыщенном месте. Ниже этот пиксель действительно записан в копию TIFF, а его увеличенный участок получен из настоящих данных.

Первые два изображения — полный экранный вид и участок от (10,14) размером 40×32, увеличенный в 10 раз без сглаживания. Чёрная клетка — исходный пиксель (30,30); увеличение меняет только иллюстрацию. Четыре горизонтальные полосы справа идут в порядке: исходный максимум, исходное среднее, новый максимум, новое среднее.

Найти единственный пиксель 400% и не довериться нулю для RGB

Один пиксель (30,30) изменён на C=M=Y=K=100%. Максимум вырос до 400%, среднее почти не изменилось. Настоящий участок 40×32 увеличен PHP в 10 раз. В проверенной сборке вызов для RGB вернул 0.0 без исключения: это не подтверждение отсутствия краски.

Найти единственный пиксель 400% и не довериться нулю для RGBПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
$baseRaw=$ink->getImageTotalInkDensity();$baseStats=pdInkStats($ink);
$hot=clone $ink;$hot->importImagePixels(30,30,1,1,'CMYK',Imagick::PIXEL_DOUBLE,[1.0,1.0,1.0,1.0]);
$hotRaw=$hot->getImageTotalInkDensity();$hotStats=pdInkStats($hot);
$rgbResult=$card->getImageTotalInkDensity();
$view=pdScreen($hot);$crop=pdLocation($hot);$bars=pdCompareBars([$baseStats['max_percent'],$baseStats['mean_percent'],$hotStats['max_percent'],$hotStats['mean_percent']]);
$images=['hot_view'=>pdImage($view,'controls-hot-view8.png'),'hot_crop'=>pdImage($crop,'controls-hot-location8.png'),'comparison'=>pdImage($bars,'controls-ink-bars8.png')];
$rects=pdCompare([$view,$crop,$bars],'print-controls.png');
pdDiagnostic('print-controls',['executed_api'=>['getImageTotalInkDensity'],'before_ink'=>$beforeInk,'hot'=>pdState($hot,'CMYK'),'base_raw'=>$baseRaw,'hot_raw'=>$hotRaw,'base_stats'=>$baseStats,'hot_stats'=>$hotStats,'rgb_result'=>$rgbResult,'rgb_result_type'=>get_debug_type($rgbResult),'hot_pixel'=>['x'=>30,'y'=>30,'CMYK16'=>$hot->exportImagePixels(30,30,1,1,'CMYK',Imagick::PIXEL_SHORT)],'crop'=>['x'=>10,'y'=>14,'width'=>40,'height'=>32,'scale'=>10],'diagram'=>['rows'=>['base maximum','base mean','hot maximum','hot mean'],'full_width_percent'=>400],
'images'=>$images,'panel_keys'=>['hot_view','hot_crop','comparison'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['hot_400'=>$hotRaw===262140.0,'one_flag'=>$hotStats['flagged_count']===1,'right_coordinate'=>$hotStats['flagged_positions']===[['x'=>30,'y'=>30]],'mean_hides_small_area'=>$hotStats['mean_percent']<121,'RGB_zero_not_valid_measurement'=>$rgbResult===0.0&&$card->getImageColorspace()!==Imagick::COLORSPACE_CMYK,'source_preserved'=>pdState($ink,'CMYK')===$beforeInk]]);
echo "print-controls.png\ncontrols-hot-view8.png\ncontrols-hot-location8.png\ncontrols-ink-bars8.png\nmetrics.json\n";
printf("maximum=%.4f%% mean=%.4f%% flagged=%d RGB-return=%.1f\n",$hotStats['max_percent'],$hotStats['mean_percent'],$hotStats['flagged_count'],$rgbResult);

Один пиксель (30,30) изменён на C=M=Y=K=100%. Максимум вырос до 400%, среднее почти не изменилось. Настоящий участок 40×32 увеличен PHP в 10 раз. В проверенной сборке вызов для RGB вернул 0.0 без исключения: это не подтверждение отсутствия краски. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-controls.png
controls-hot-view8.png
controls-hot-location8.png
controls-ink-bars8.png
metrics.json
maximum=400.0000% mean=120.0027% flagged=1 RGB-return=0.0
ЛовушкаКак её распознать
Вернулся 0 для RGBСначала проверьте CMYK. В этой сборке вызов вернул число без исключения; это не измерение краски для RGB.
Весь файл кажется нормальнымМаксимум может относиться к одному пикселю. Для координат нужна отдельная проверка пикселей.
PNG показывает 118,11 вместо 300Прочитайте единицы: 118,11 на сантиметр примерно равно 300 на дюйм.
Поменяли только units на сантиметрыЧисло 300 осталось прежним и стало означать 300 на сантиметр. Для сохранения прежнего физического размера требуется пересчёт.

Собрать отчёт после реального сохранения и чтения TIFF

Сохранённый CMYK TIFF после повторного чтения и увеличенный участок карты пикселей выше учебного порога 250%
Сохранённый CMYK TIFF после повторного чтения и увеличенный участок карты пикселей выше учебного порога 250%

Следующий шаг — функция inspectForPrint для карточки заказа. Она получает уже открытое CMYK-изображение и выбранный предел, читает единицы, вычисляет размер, измеряет максимум, находит превышения и проверяет наличие ICC-профиля. Полный пример сохраняет TIFF, открывает его заново, записывает отчёт JSON и также читает его заново. В нём не происходит отправки файла в типографию.

$image = new Imagick(__DIR__ . "/delivery-cmyk.tiff");
$report = inspectForPrint($image, 250.0);
echo $report["status"]; // needs-review

Функция объявлена ниже в полном примере: return передаёт массив отчёта туда, где она вызвана, а $report сохраняет этот массив. Число 250.0 — демонстрационный параметр, который в настоящем заказе нужно заменить согласованным правилом. Наш искусственный образец не содержит ICC-профиля и не изображает конкретную печатную машину.

Сохранить CMYK TIFF и сформировать понятный отчёт для проверки

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно.

Сохранить CMYK TIFF и сформировать понятный отчёт для проверкиПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
function inspectForPrint(Imagick $image,float $exampleLimit):array {
    if($image->getImageColorspace()!==Imagick::COLORSPACE_CMYK)throw new InvalidArgumentException('CMYK input required');
    $units=$image->getImageUnits();$physical=pdPhysical($image);
    $raw=$image->getImageTotalInkDensity();$stats=pdInkStats($image,$exampleLimit);
    $profiles=$image->getImageProfiles('icc',false);$warnings=[];
    if($stats['max_percent']>$exampleLimit)$warnings[]='illustrative-ink-threshold-exceeded';
    if(count($profiles)===0)$warnings[]='missing-ICC-profile';
    return ['status'=>'needs-review','units'=>$units,'physical'=>$physical,'raw_ink'=>$raw,'ink'=>$stats,'ICC_present'=>count($profiles)>0,'warnings'=>$warnings,'threshold_is_example_not_press_standard'=>true];
}
$delivery=clone $ink;$delivery->importImagePixels(30,30,1,1,'CMYK',Imagick::PIXEL_DOUBLE,[1.0,1.0,1.0,1.0]);
$delivery->setImageFormat('tiff');$delivery->setImageDepth(16);$delivery->setImageCompression(Imagick::COMPRESSION_ZIP);$delivery->writeImage(__DIR__.'/delivery-cmyk.tiff');
$read=new Imagick(__DIR__.'/delivery-cmyk.tiff');$report=inspectForPrint($read,250.0);
file_put_contents(__DIR__.'/print-report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR)."\n");
$readReport=json_decode((string)file_get_contents(__DIR__.'/print-report.json'),true,512,JSON_THROW_ON_ERROR);
$view=pdScreen($read);$mask=pdFlagMap($report['ink']['flagged_positions']);$savedMask=pdSavePNG($mask,'workflow-mask.png',true);
$zoom=$mask->getImageRegion(40,32,10,14);$zoom->sampleImage(400,320);
$images=['readback'=>pdImage($view,'workflow-readback-view8.png'),'flag_crop'=>pdImage($zoom,'workflow-flag-map8.png')];
$rects=pdCompare([$view,$zoom],'print-workflow.png');
$errors=['empty_units'=>pdTry(fn()=>(new Imagick())->getImageUnits()),'empty_ink'=>pdTry(fn()=>(new Imagick())->getImageTotalInkDensity())];
$policies=['RGB'=>pdTry(fn()=>inspectForPrint($card,250.0)),'undefined_units'=>pdTry(fn()=>pdPhysical($card))];
pdDiagnostic('print-workflow',['executed_api'=>['getImageUnits','getImageTotalInkDensity'],'before_ink'=>$beforeInk,'delivery'=>pdState($delivery,'CMYK'),'readback'=>pdState($read,'CMYK'),'saved_tiff'=>['filename'=>'delivery-cmyk.tiff','sha256'=>hash_file('sha256',__DIR__.'/delivery-cmyk.tiff'),'bytes'=>filesize(__DIR__.'/delivery-cmyk.tiff')],'report'=>$report,'read_report'=>$readReport,'saved_mask'=>$savedMask,'errors'=>$errors,'policies'=>$policies,'crop'=>['x'=>10,'y'=>14,'width'=>40,'height'=>32,'scale'=>10],
'images'=>$images,'panel_keys'=>['readback','flag_crop'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['TIFF_exact_CMYK'=>pdHash($delivery,'CMYK')===pdHash($read,'CMYK'),'TIFF_units'=>$read->getImageUnits()===Imagick::RESOLUTION_PIXELSPERINCH,'report_readback'=>$report===$readReport,'one_warning_pixel'=>$report['ink']['flagged_count']===1,'manual_review'=>$report['status']==='needs-review'&&count($report['warnings'])===2,'empty_units_caught'=>($errors['empty_units']['error']['class']??null)==='ImagickException','empty_ink_caught'=>($errors['empty_ink']['error']['class']??null)==='ImagickException','RGB_policy_caught'=>($policies['RGB']['error']['class']??null)==='InvalidArgumentException','unknown_units_caught'=>($policies['undefined_units']['error']['class']??null)==='InvalidArgumentException']]);
echo "print-workflow.png\nworkflow-readback-view8.png\nworkflow-flag-map8.png\ndelivery-cmyk.tiff\nworkflow-mask.png\nprint-report.json\nmetrics.json\nstatus=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2\n";

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-workflow.png
workflow-readback-view8.png
workflow-flag-map8.png
delivery-cmyk.tiff
workflow-mask.png
print-report.json
metrics.json
status=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2

Результат содержит два замечания: один пиксель выше заданного предела и отсутствие профиля. Наличие профиля само по себе тоже не доказывает правильный профиль или качество. Для реальной печати согласуют исходное цветовое пространство, выходной ICC-профиль, материал, оборудование и масштаб. Математическая RGB-копия здесь только помогает увидеть образец. Цветопроба и соответствующее преобразование профилей — отдельная задача.

Два исключения ImagickException получены от пустых объектов. Ещё два InvalidArgumentException создаёт наша функция: вход RGB и неизвестные единицы. Эти отказы различаются по причине. Для большого файла не экспортируйте все каналы без оценки памяти: здесь pdInkStats делает это намеренно для прозрачности учебного расчёта. Сам метод покрытия тоже обходит изображение, тогда как чтение units получает метаданные.

Полные контракты, ошибки, ограничения и следующий шаг

Imagick::getImageUnits: полный контракт и применение

Imagick::getImageUnits(): int

Метод читает единицы разрешения текущего изображения и возвращает int. Параметров нет: пустые скобки означают, что состояние уже находится в объекте. Официальная документация предусматривает ImagickException при ошибке. Доступность: PECL imagick 2, PECL imagick 3. В принятом официальном инвентаре метод не помечен устаревшим. Для пустого Imagick исключение воспроизведено в этой главе.

РезультатСмысл
Imagick::RESOLUTION_UNDEFINEDЕдиницы неизвестны. Значение разрешения само по себе не определяет сантиметры.
Imagick::RESOLUTION_PIXELSPERINCHЧисла getImageResolution относятся к одному дюйму.
Imagick::RESOLUTION_PIXELSPERCENTIMETERЧисла getImageResolution относятся к одному сантиметру.

В проверенной сборке коды равны 0, 1 и 2, но в программах лучше использовать именованные константы. getImageUnits не возвращает число DPI, не возвращает ширину изображения и не пересчитывает пиксели. Константы PATHUNITS относятся к другому понятию и здесь не применяются. Сначала прочитайте units, затем x/y resolution, затем getImageWidth/getImageHeight. Для сантиметров: pixels ÷ pixels-per-cm; для дюймов дополнительно умножьте на 2,54.

Нулевое или отрицательное разрешение и неизвестные единицы наша функция отклоняет до деления. Это правило нашего отчёта, а не обещание исключения от getImageUnits. Смена только setImageUnits переименовывает единицу без преобразования самого числа; смена метаданных не делает низкое разрешение детальнее. Сохранённый PNG действительно читается повторно: pHYs хранит целые пиксели на метр, поэтому библиотека может показать сантиметры и небольшое округление.

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

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

Imagick::getImageTotalInkDensity: полный контракт и применение

Imagick::getImageTotalInkDensity(): float

Метод читает показатель суммарного покрытия краской текущего изображения и возвращает float. Параметров нет. Официальная документация предусматривает ImagickException при ошибке. Доступность: PECL imagick 2, PECL imagick 3. В принятом официальном инвентаре метод не помечен устаревшим. Для пустого Imagick исключение воспроизведено в этой главе.

Официальная короткая запись не раскрывает масштаб числа. В исходниках ImageMagick 7.1.1-46 метод обходит пиксели CMYK, для каждого складывает C+M+Y+K и выбирает наибольшую сумму. Он не суммирует всю страницу, не берёт среднее и не складывает максимумы каналов из разных мест. Полученное число задано в шкале Quantum; процент = raw ÷ quantumRange × 100. Для Q16 quantumRange равен 65535, а четыре полностью заполненных канала дают 262140, то есть 400%.

Проверяйте getImageColorspace перед интерпретацией результата. В нашей сборке RGB вернул 0.0 без PHP-исключения: внутренний код ImageMagick записывает ошибку и возвращает ноль, а обёртка отдаёт число. Белый CMYK без краски тоже может вернуть ноль, поэтому одного значения недостаточно для вывода. Не превращайте такое поведение в общее обещание для всех версий; официальный контракт ошибок сохранён, реальные условия запуска указаны.

Два применения: найти максимальное покрытие в четырёх цветных полосах и отметить опасное для выбранного правила место в сохранённом CMYK-файле. Учебные 250% — только параметр примера, не универсальная норма печати. Подходящий предел, профиль, бумагу и процесс определяет конкретное задание типографии. Число не измеряет миллилитры расхода, качество печати или достоверность ICC-профиля. Экранное sRGB-превью не является цветопробой.

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

Самостоятельный запуск и упражнение

Все четыре архива самодостаточны. Требуются PHP с Imagick, PNG/TIFF-кодеры и право записи в каталог. Выполните php example.php. Полные примеры проверены в PHP 8.5.11 / Imagick 3.8.1 / ImageMagick 7.1.1-46 Q16. Значения для другой сборки не следует подменять нашими константами: программа читает QuantumRange, а точные ожидаемые числа указаны для проверенной среды.

Упражнение: файл шириной 1200 пикселей имеет 300 пикселей на дюйм. Какая ширина в сантиметрах и изменится ли она после правильного перевода в единицы на сантиметр? Ответ: 1200 ÷ 300 × 2,54 = 10,16 см; эквивалентная плотность 300 ÷ 2,54 сохраняет этот размер. Если заменить только имя единицы, получится другое значение. Второе упражнение: среднее покрытие низкое, но один пиксель имеет C=M=Y=K=100%. Ответ: максимум 400%, независимо от малой площади этого места.

Дальше свяжите отчёт с приёмом файла и обработкой ошибок. Сохраняйте исходный CMYK-файл отдельно от экранного превью. Выводите пользователю конкретный размер, обнаруженные условия и понятный следующий шаг — уточнить профиль и требования задания, а не общее сообщение «плохая картинка».

Полный вспомогательный PHP из каждого архива
<?php
declare(strict_types=1);
function pdRestore():void {
    $hashes=json_decode('{"card.png":"fc1c24afc6aaa497eb30758ac511a44063f68f13a7d7be32bb44df0bd39af987","patches.tiff":"ded0d14fa14c4f5ccebca3865d5ebe31d13e872a8232f1a391cf4238a955c4ee"}',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');}
}
function pdHash(Imagick $i,string $map='RGB'):string{return hash('sha256',json_encode($i->exportImagePixels(0,0,$i->getImageWidth(),$i->getImageHeight(),$map,Imagick::PIXEL_SHORT),JSON_THROW_ON_ERROR));}
function pdState(Imagick $i,string $map='RGB'):array{return ['geometry'=>[$i->getImageWidth(),$i->getImageHeight()],'map'=>$map,'pixels_sha256'=>pdHash($i,$map),'colorspace'=>$i->getImageColorspace(),'units'=>$i->getImageUnits(),'resolution'=>$i->getImageResolution()];}
function pdSavePNG(Imagick $i,string $name,bool $preview=false):array {
    $c=clone $i;$c->setImageFormat('png');$c->setImageDepth(8);$c->setOption('png:bit-depth','8');$c->setOption('png:color-type','2');if($preview)$c->setOption('png:exclude-chunk','all');
    if(!$c->writeImage(__DIR__.'/'.$name))throw new RuntimeException('Cannot save PNG');$r=new Imagick(__DIR__.'/'.$name);
    return ['filename'=>$name,'sha256'=>hash_file('sha256',__DIR__.'/'.$name),'bytes'=>filesize(__DIR__.'/'.$name),'state'=>pdState($r)];
}
function pdImage(Imagick $i,string $name):array {$s=pdSavePNG($i,$name,true);return ['geometry'=>[$i->getImageWidth(),$i->getImageHeight()],'RGB16_sha256'=>pdHash($i),'encoded_png'=>['filename'=>$name,'RGB16_sha256'=>$s['state']['pixels_sha256']],'display_basis'=>'Actual PHP RGB preview or explanatory diagram; not a press proof'];}
function pdScreen(Imagick $i):Imagick {$v=clone $i;if($v->getImageColorspace()===Imagick::COLORSPACE_CMYK)$v->transformImageColorspace(Imagick::COLORSPACE_SRGB);return $v;}
function pdCompare(array $images,string $name):array {$i=new Imagick();$i->newImage(400*count($images),320,'white','png');$rects=[];foreach($images as$n=>$v){$i->compositeImage($v,Imagick::COMPOSITE_COPY,400*$n,0);$rects[]=['x'=>400*$n,'y'=>0,'width'=>400,'height'=>320];}pdSavePNG($i,$name,true);return $rects;}
function pdPhysical(Imagick $i):array {
    $u=$i->getImageUnits();$r=$i->getImageResolution();
    if(!in_array($u,[Imagick::RESOLUTION_PIXELSPERINCH,Imagick::RESOLUTION_PIXELSPERCENTIMETER],true)||$r['x']<=0||$r['y']<=0)throw new InvalidArgumentException('Known units and positive density are required');
    $factor=$u===Imagick::RESOLUTION_PIXELSPERINCH?2.54:1.0;
    return ['width_cm'=>$i->getImageWidth()/$r['x']*$factor,'height_cm'=>$i->getImageHeight()/$r['y']*$factor,'ppi_x'=>$r['x']*2.54/$factor,'ppi_y'=>$r['y']*2.54/$factor];
}
function pdInkStats(Imagick $i,float $limit=250.0):array {
    if($i->getImageColorspace()!==Imagick::COLORSPACE_CMYK)throw new InvalidArgumentException('CMYK is required before interpreting ink density');
    $q=(float)Imagick::getQuantumRange()['quantumRangeLong'];$v=$i->exportImagePixels(0,0,$i->getImageWidth(),$i->getImageHeight(),'CMYK',Imagick::PIXEL_SHORT);$max=0;$total=0;$channelMax=[0,0,0,0];$flagged=[];$samples=[];
    for($p=0;$p<count($v);$p+=4){$sum=0;for($c=0;$c<4;$c++){$sum+=$v[$p+$c];$channelMax[$c]=max($channelMax[$c],$v[$p+$c]);}$max=max($max,$sum);$total+=$sum;if(100*$sum/$q>$limit){$n=intdiv($p,4);$flagged[]=['x'=>$n%$i->getImageWidth(),'y'=>intdiv($n,$i->getImageWidth())];}}
    foreach([50,150,250,350]as$x)$samples[]=$i->exportImagePixels($x,160,1,1,'CMYK',Imagick::PIXEL_SHORT);
    $n=$i->getImageWidth()*$i->getImageHeight();return ['quantum'=>$q,'raw_max'=>$max,'max_percent'=>100*$max/$q,'mean_percent'=>100*$total/$n/$q,'sum_channel_maxima_percent'=>100*array_sum($channelMax)/$q,'CMYK16_sha256'=>pdHash($i,'CMYK'),'samples_CMYK16'=>$samples,'sample_x'=>[50,150,250,350],'sample_y'=>160,'threshold_percent'=>$limit,'flagged_count'=>count($flagged),'flagged_positions'=>$flagged];
}
function pdCanvas():Imagick {$i=new Imagick();$i->newImage(400,320,'#f7faf9','png');return $i;}
function pdSizeModel(array $a,array $b):Imagick {$i=pdCanvas();$d=new ImagickDraw();$d->setStrokeAntialias(false);$d->setFillColor('#2c8993');$d->rectangle(20,15,20+round($a['width_cm']*18),15+round($a['height_cm']*18));$d->setFillColor('#d48a42');$d->rectangle(20,245,20+round($b['width_cm']*18),245+round($b['height_cm']*18));$i->drawImage($d);return $i;}
function pdInkBars(array $samples,float $q):Imagick {$i=pdCanvas();$d=new ImagickDraw();$d->setStrokeAntialias(false);$colors=['#00a7c4','#d9498a','#e9c83f','#263a40'];foreach($samples as$n=>$values){$x=20;foreach($values as$c=>$v){$w=350*$v/$q/4;if($w>0){$d->setFillColor($colors[$c]);$d->rectangle($x,35+$n*65,$x+$w,65+$n*65);}$x+=$w;}}$i->drawImage($d);return $i;}
function pdCompareBars(array $values):Imagick {$i=pdCanvas();$d=new ImagickDraw();$d->setStrokeAntialias(false);$colors=['#155e68','#4b9aa5','#bb3c38','#d48a42'];foreach($values as$n=>$v){$d->setFillColor($colors[$n]);$d->rectangle(20,35+$n*65,20+350*$v/400,65+$n*65);}$i->drawImage($d);return $i;}
function pdLocation(Imagick $i):Imagick {$v=pdScreen($i);$v=$v->getImageRegion(40,32,10,14);$v->sampleImage(400,320);return $v;}
function pdFlagMap(array $positions):Imagick {$i=pdCanvas();$d=new ImagickDraw();$d->setFillColor('#c94922');$d->setStrokeAntialias(false);foreach($positions as$p)$d->point($p['x'],$p['y']);$i->drawImage($d);return $i;}
function pdTry(callable $f):array{try{$v=$f();return ['type'=>get_debug_type($v),'returned'=>$v];}catch(Throwable $e){return ['error'=>['class'=>get_class($e),'message'=>$e->getMessage()]];}}
function pdDiagnostic(string $id,array $data):void {$doc=['schema'=>'aen-print-diagnostics-program-diagnostics-v1','example_id'=>$id,'runtime'=>['php'=>PHP_VERSION,'imagick'=>phpversion('imagick'),'imagemagick'=>Imagick::getVersion()]]+$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');}

pdPhysical читает единицы и считает размер, pdInkStats отдельно складывает реальные значения CMYK и сохраняет координаты. pdScreen преобразует только копию для показа. pdSizeModel и диаграммы рисуют модель по вычисленным числам; pdLocation увеличивает реальный участок. Изучаемые getImageUnits и getImageTotalInkDensity остаются видны в основном example.php.

Реальные вопросы разработчиков

Обсуждения показывают исходные затруднения читателей; нормативные сведения проверены по PHP.net, спецификации PNG и исходникам библиотеки. Ответы и примеры в этой главе написаны самостоятельно.

PHP Imagick::resampleImage resizes image instead of resampling: Изменение разрешения путают с изменением числа пикселей. Показываем одинаковый растровый вход при 72 и 300 пикселях на дюйм, считаем физический размер и проверяем сохранность RGB.

'-units PixelsPerInch' when changing density of a PNG image: Непонятно, зачем явно задавать единицы при записи PNG. Читаем единицы вместе с числами разрешения, показываем неопределённые единицы и настоящий PNG после сохранения.

Imagick vs Adobe Photoshop vs Windows - Detecting Resolution (Pixel Per Cm or Pixel Per Inch): PNG показывает 118.11 на сантиметр вместо ожидаемых 300 на дюйм. Пересчитываем через 2.54 и показываем допустимое округление сохранённых данных; единицы не считаем повреждением картинки.

Image print quality check: Нужна проверка загруженного изображения перед печатью. Собираем небольшой диагностический отчёт: физический размер, CMYK, максимальное покрытие и отсутствие профиля. Результат направляет на проверку, а не сертифицирует печать.

Convert image from RGB to CMYK with Imagick: После попытки сделать CMYK PNG получаются неверные цвета. Используем настоящий CMYK TIFF для измерений и отдельную sRGB-копию только для экрана; не предлагаем смену метки пространства как универсальное преобразование.

Understanding CMYK to RGB conversion code: Неясна роль входного и выходного ICC-профилей. Отделяем математическую демонстрацию каналов от согласованного профиля печатного процесса. Без профиля файл остаётся учебным образцом, а экранная копия не является цветопробой.

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

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

Imagick::getImageUnitsПрочитать единицы и размер для печати

PECL imagick 2, PECL imagick 3 · imagick

Imagick::getImageUnits(): int

Imagick::getImageUnits: полный контракт и применение

Imagick::getImageUnits(): int

Метод читает единицы разрешения текущего изображения и возвращает int. Параметров нет: пустые скобки означают, что состояние уже находится в объекте. Официальная документация предусматривает ImagickException при ошибке. Доступность: PECL imagick 2, PECL imagick 3. В принятом официальном инвентаре метод не помечен устаревшим. Для пустого Imagick исключение воспроизведено в этой главе.

РезультатСмысл
Imagick::RESOLUTION_UNDEFINEDЕдиницы неизвестны. Значение разрешения само по себе не определяет сантиметры.
Imagick::RESOLUTION_PIXELSPERINCHЧисла getImageResolution относятся к одному дюйму.
Imagick::RESOLUTION_PIXELSPERCENTIMETERЧисла getImageResolution относятся к одному сантиметру.

В проверенной сборке коды равны 0, 1 и 2, но в программах лучше использовать именованные константы. getImageUnits не возвращает число DPI, не возвращает ширину изображения и не пересчитывает пиксели. Константы PATHUNITS относятся к другому понятию и здесь не применяются. Сначала прочитайте units, затем x/y resolution, затем getImageWidth/getImageHeight. Для сантиметров: pixels ÷ pixels-per-cm; для дюймов дополнительно умножьте на 2,54.

Нулевое или отрицательное разрешение и неизвестные единицы наша функция отклоняет до деления. Это правило нашего отчёта, а не обещание исключения от getImageUnits. Смена только setImageUnits переименовывает единицу без преобразования самого числа; смена метаданных не делает низкое разрешение детальнее. Сохранённый PNG действительно читается повторно: pHYs хранит целые пиксели на метр, поэтому библиотека может показать сантиметры и небольшое округление.

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

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

Прочитать единицы и вычислить размер без изменения пикселей

Один рисунок 400×320 соответствует ширине 14,11 см при 72 пикселях на дюйм и 3,39 см при 300. Настройки не добавили деталей. После записи PNG единицы стали «на сантиметр», плотность — 118,11; пересчёт даёт 299,9994 на дюйм, RGB остались прежними.

Прочитать единицы и вычислить размер без изменения пикселейПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
$at72=clone $card;$at72->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);$at72->setImageResolution(72,72);
$at300=clone $card;$at300->setImageUnits(Imagick::RESOLUTION_PIXELSPERINCH);$at300->setImageResolution(300,300);
$units=$at300->getImageUnits();$density=$at300->getImageResolution();
$size72=pdPhysical($at72);$size300=pdPhysical($at300);
$saved=pdSavePNG($at300,'first-300.png');$read=new Imagick(__DIR__.'/first-300.png');$readSize=pdPhysical($read);
$equivalent=clone $card;$equivalent->setImageUnits(Imagick::RESOLUTION_PIXELSPERCENTIMETER);$equivalent->setImageResolution(300/2.54,300/2.54);
$relabel=clone $at300;$relabel->setImageUnits(Imagick::RESOLUTION_PIXELSPERCENTIMETER);
$model=pdSizeModel($size72,$size300);
$images=['card'=>pdImage($card,'first-card8.png'),'size_model'=>pdImage($model,'first-size-model8.png')];
$rects=pdCompare([$card,$model],'print-first.png');
pdDiagnostic('print-first',['executed_api'=>['getImageUnits'],'returned_type'=>get_debug_type($units),'declared_return'=>(string)(new ReflectionMethod(Imagick::class,'getImageUnits'))->getReturnType(),'units'=>$units,'density'=>$density,'before_card'=>$beforeCard,'at72'=>pdState($at72),'at300'=>pdState($at300),'saved'=>$saved,'readback'=>pdState($read),'physical72'=>$size72,'physical300'=>$size300,'readback_physical'=>$readSize,'cm_equivalent'=>pdState($equivalent),'cm_physical'=>pdPhysical($equivalent),'relabel_only'=>pdState($relabel),'relabel_physical'=>pdPhysical($relabel),'diagram'=>['order'=>['72ppi','300ppi'],'display_pixels_per_cm'=>18],
'images'=>$images,'panel_keys'=>['card','size_model'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['int_units'=>$units===Imagick::RESOLUTION_PIXELSPERINCH,'same_pixels'=>pdHash($card)===pdHash($at300)&&pdHash($card)===pdHash($read),'same_geometry'=>$read->getImageGeometry()===$card->getImageGeometry(),'PNG_cm'=>$read->getImageUnits()===Imagick::RESOLUTION_PIXELSPERCENTIMETER,'equivalent_ppi'=>abs($readSize['ppi_x']-300)<0.001,'explicit_cm_equivalent'=>abs(pdPhysical($equivalent)['width_cm']-$size300['width_cm'])<1e-10,'relabel_does_not_convert_density'=>$relabel->getImageResolution()['x']===300.0]]);
echo "print-first.png\nfirst-card8.png\nfirst-size-model8.png\nfirst-300.png\nmetrics.json\n";
printf("units=%d raster=400x320 width72=%.4fcm width300=%.4fcm png-ppi=%.4f\n",$units,$size72['width_cm'],$size300['width_cm'],$readSize['ppi_x']);

Один рисунок 400×320 соответствует ширине 14,11 см при 72 пикселях на дюйм и 3,39 см при 300. Настройки не добавили деталей. После записи PNG единицы стали «на сантиметр», плотность — 118,11; пересчёт даёт 299,9994 на дюйм, RGB остались прежними. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-first.png
first-card8.png
first-size-model8.png
first-300.png
metrics.json
units=1 raster=400x320 width72=14.1111cm width300=3.3867cm png-ppi=299.9994

Сохранить CMYK TIFF и сформировать понятный отчёт для проверки

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно.

Сохранить CMYK TIFF и сформировать понятный отчёт для проверкиПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
function inspectForPrint(Imagick $image,float $exampleLimit):array {
    if($image->getImageColorspace()!==Imagick::COLORSPACE_CMYK)throw new InvalidArgumentException('CMYK input required');
    $units=$image->getImageUnits();$physical=pdPhysical($image);
    $raw=$image->getImageTotalInkDensity();$stats=pdInkStats($image,$exampleLimit);
    $profiles=$image->getImageProfiles('icc',false);$warnings=[];
    if($stats['max_percent']>$exampleLimit)$warnings[]='illustrative-ink-threshold-exceeded';
    if(count($profiles)===0)$warnings[]='missing-ICC-profile';
    return ['status'=>'needs-review','units'=>$units,'physical'=>$physical,'raw_ink'=>$raw,'ink'=>$stats,'ICC_present'=>count($profiles)>0,'warnings'=>$warnings,'threshold_is_example_not_press_standard'=>true];
}
$delivery=clone $ink;$delivery->importImagePixels(30,30,1,1,'CMYK',Imagick::PIXEL_DOUBLE,[1.0,1.0,1.0,1.0]);
$delivery->setImageFormat('tiff');$delivery->setImageDepth(16);$delivery->setImageCompression(Imagick::COMPRESSION_ZIP);$delivery->writeImage(__DIR__.'/delivery-cmyk.tiff');
$read=new Imagick(__DIR__.'/delivery-cmyk.tiff');$report=inspectForPrint($read,250.0);
file_put_contents(__DIR__.'/print-report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR)."\n");
$readReport=json_decode((string)file_get_contents(__DIR__.'/print-report.json'),true,512,JSON_THROW_ON_ERROR);
$view=pdScreen($read);$mask=pdFlagMap($report['ink']['flagged_positions']);$savedMask=pdSavePNG($mask,'workflow-mask.png',true);
$zoom=$mask->getImageRegion(40,32,10,14);$zoom->sampleImage(400,320);
$images=['readback'=>pdImage($view,'workflow-readback-view8.png'),'flag_crop'=>pdImage($zoom,'workflow-flag-map8.png')];
$rects=pdCompare([$view,$zoom],'print-workflow.png');
$errors=['empty_units'=>pdTry(fn()=>(new Imagick())->getImageUnits()),'empty_ink'=>pdTry(fn()=>(new Imagick())->getImageTotalInkDensity())];
$policies=['RGB'=>pdTry(fn()=>inspectForPrint($card,250.0)),'undefined_units'=>pdTry(fn()=>pdPhysical($card))];
pdDiagnostic('print-workflow',['executed_api'=>['getImageUnits','getImageTotalInkDensity'],'before_ink'=>$beforeInk,'delivery'=>pdState($delivery,'CMYK'),'readback'=>pdState($read,'CMYK'),'saved_tiff'=>['filename'=>'delivery-cmyk.tiff','sha256'=>hash_file('sha256',__DIR__.'/delivery-cmyk.tiff'),'bytes'=>filesize(__DIR__.'/delivery-cmyk.tiff')],'report'=>$report,'read_report'=>$readReport,'saved_mask'=>$savedMask,'errors'=>$errors,'policies'=>$policies,'crop'=>['x'=>10,'y'=>14,'width'=>40,'height'=>32,'scale'=>10],
'images'=>$images,'panel_keys'=>['readback','flag_crop'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['TIFF_exact_CMYK'=>pdHash($delivery,'CMYK')===pdHash($read,'CMYK'),'TIFF_units'=>$read->getImageUnits()===Imagick::RESOLUTION_PIXELSPERINCH,'report_readback'=>$report===$readReport,'one_warning_pixel'=>$report['ink']['flagged_count']===1,'manual_review'=>$report['status']==='needs-review'&&count($report['warnings'])===2,'empty_units_caught'=>($errors['empty_units']['error']['class']??null)==='ImagickException','empty_ink_caught'=>($errors['empty_ink']['error']['class']??null)==='ImagickException','RGB_policy_caught'=>($policies['RGB']['error']['class']??null)==='InvalidArgumentException','unknown_units_caught'=>($policies['undefined_units']['error']['class']??null)==='InvalidArgumentException']]);
echo "print-workflow.png\nworkflow-readback-view8.png\nworkflow-flag-map8.png\ndelivery-cmyk.tiff\nworkflow-mask.png\nprint-report.json\nmetrics.json\nstatus=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2\n";

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-workflow.png
workflow-readback-view8.png
workflow-flag-map8.png
delivery-cmyk.tiff
workflow-mask.png
print-report.json
metrics.json
status=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2
Imagick::getImageTotalInkDensityИзмерить максимальную сумму CMYK в одном пикселе

PECL imagick 2, PECL imagick 3 · imagick

Imagick::getImageTotalInkDensity(): float

Imagick::getImageTotalInkDensity: полный контракт и применение

Imagick::getImageTotalInkDensity(): float

Метод читает показатель суммарного покрытия краской текущего изображения и возвращает float. Параметров нет. Официальная документация предусматривает ImagickException при ошибке. Доступность: PECL imagick 2, PECL imagick 3. В принятом официальном инвентаре метод не помечен устаревшим. Для пустого Imagick исключение воспроизведено в этой главе.

Официальная короткая запись не раскрывает масштаб числа. В исходниках ImageMagick 7.1.1-46 метод обходит пиксели CMYK, для каждого складывает C+M+Y+K и выбирает наибольшую сумму. Он не суммирует всю страницу, не берёт среднее и не складывает максимумы каналов из разных мест. Полученное число задано в шкале Quantum; процент = raw ÷ quantumRange × 100. Для Q16 quantumRange равен 65535, а четыре полностью заполненных канала дают 262140, то есть 400%.

Проверяйте getImageColorspace перед интерпретацией результата. В нашей сборке RGB вернул 0.0 без PHP-исключения: внутренний код ImageMagick записывает ошибку и возвращает ноль, а обёртка отдаёт число. Белый CMYK без краски тоже может вернуть ноль, поэтому одного значения недостаточно для вывода. Не превращайте такое поведение в общее обещание для всех версий; официальный контракт ошибок сохранён, реальные условия запуска указаны.

Два применения: найти максимальное покрытие в четырёх цветных полосах и отметить опасное для выбранного правила место в сохранённом CMYK-файле. Учебные 250% — только параметр примера, не универсальная норма печати. Подходящий предел, профиль, бумагу и процесс определяет конкретное задание типографии. Число не измеряет миллилитры расхода, качество печати или достоверность ICC-профиля. Экранное sRGB-превью не является цветопробой.

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

Измерить максимум суммы четырёх красок в одном пикселе

Метод вернул 117964 в шкале Q16. После деления на 65535 и умножения на 100 получено примерно 180%. Это максимум в смешанной полосе; среднее около 120%, а неверное сложение отдельных максимумов каналов дало бы 330%.

Измерить максимум суммы четырёх красок в одном пикселеПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
$raw=$ink->getImageTotalInkDensity();
$quantum=(float)Imagick::getQuantumRange()['quantumRangeLong'];
$percent=100*$raw/$quantum;
$stats=pdInkStats($ink);$view=pdScreen($ink);$bars=pdInkBars($stats['samples_CMYK16'],$quantum);
$images=['cmyk_view'=>pdImage($view,'minimal-cmyk-view8.png'),'ink_bars'=>pdImage($bars,'minimal-ink-bars8.png')];
$rects=pdCompare([$view,$bars],'print-minimal.png');
pdDiagnostic('print-minimal',['executed_api'=>['getImageTotalInkDensity'],'returned_type'=>get_debug_type($raw),'declared_return'=>(string)(new ReflectionMethod(Imagick::class,'getImageTotalInkDensity'))->getReturnType(),'before_ink'=>$beforeInk,'after_ink'=>pdState($ink,'CMYK'),'raw'=>$raw,'percent'=>$percent,'stats'=>$stats,'diagram'=>['rows'=>['C100','M100','Y100','C60_M50_Y40_K30'],'segment_order'=>['C','M','Y','K'],'full_width_percent'=>400],
'images'=>$images,'panel_keys'=>['cmyk_view','ink_bars'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['actual_float'=>is_float($raw),'expected_Q16'=>$quantum===65535.0,'raw_sum'=>$raw===117964.0,'independent_max'=>$raw===$stats['raw_max']*1.0,'not_mean'=>abs($percent-$stats['mean_percent'])>59,'not_separate_channel_maxima'=>abs($percent-$stats['sum_channel_maxima_percent'])>149,'source_preserved'=>pdState($ink,'CMYK')===$beforeInk]]);
echo "print-minimal.png\nminimal-cmyk-view8.png\nminimal-ink-bars8.png\nmetrics.json\n";
printf("raw=%.0f maximum=%.4f%% mean=%.4f%% channel-maxima-sum=%.4f%%\n",$raw,$percent,$stats['mean_percent'],$stats['sum_channel_maxima_percent']);

Метод вернул 117964 в шкале Q16. После деления на 65535 и умножения на 100 получено примерно 180%. Это максимум в смешанной полосе; среднее около 120%, а неверное сложение отдельных максимумов каналов дало бы 330%. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-minimal.png
minimal-cmyk-view8.png
minimal-ink-bars8.png
metrics.json
raw=117964 maximum=180.0015% mean=120.0004% channel-maxima-sum=330.0008%

Сохранить CMYK TIFF и сформировать понятный отчёт для проверки

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно.

Сохранить CMYK TIFF и сформировать понятный отчёт для проверкиПример для указанного окружения
<?php
declare(strict_types=1);
require __DIR__.'/print-fixture.php';
pdRestore();
$card=new Imagick(__DIR__.'/card.png');
$ink=new Imagick(__DIR__.'/patches.tiff');
$beforeCard=pdState($card);$beforeInk=pdState($ink,'CMYK');
function inspectForPrint(Imagick $image,float $exampleLimit):array {
    if($image->getImageColorspace()!==Imagick::COLORSPACE_CMYK)throw new InvalidArgumentException('CMYK input required');
    $units=$image->getImageUnits();$physical=pdPhysical($image);
    $raw=$image->getImageTotalInkDensity();$stats=pdInkStats($image,$exampleLimit);
    $profiles=$image->getImageProfiles('icc',false);$warnings=[];
    if($stats['max_percent']>$exampleLimit)$warnings[]='illustrative-ink-threshold-exceeded';
    if(count($profiles)===0)$warnings[]='missing-ICC-profile';
    return ['status'=>'needs-review','units'=>$units,'physical'=>$physical,'raw_ink'=>$raw,'ink'=>$stats,'ICC_present'=>count($profiles)>0,'warnings'=>$warnings,'threshold_is_example_not_press_standard'=>true];
}
$delivery=clone $ink;$delivery->importImagePixels(30,30,1,1,'CMYK',Imagick::PIXEL_DOUBLE,[1.0,1.0,1.0,1.0]);
$delivery->setImageFormat('tiff');$delivery->setImageDepth(16);$delivery->setImageCompression(Imagick::COMPRESSION_ZIP);$delivery->writeImage(__DIR__.'/delivery-cmyk.tiff');
$read=new Imagick(__DIR__.'/delivery-cmyk.tiff');$report=inspectForPrint($read,250.0);
file_put_contents(__DIR__.'/print-report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR)."\n");
$readReport=json_decode((string)file_get_contents(__DIR__.'/print-report.json'),true,512,JSON_THROW_ON_ERROR);
$view=pdScreen($read);$mask=pdFlagMap($report['ink']['flagged_positions']);$savedMask=pdSavePNG($mask,'workflow-mask.png',true);
$zoom=$mask->getImageRegion(40,32,10,14);$zoom->sampleImage(400,320);
$images=['readback'=>pdImage($view,'workflow-readback-view8.png'),'flag_crop'=>pdImage($zoom,'workflow-flag-map8.png')];
$rects=pdCompare([$view,$zoom],'print-workflow.png');
$errors=['empty_units'=>pdTry(fn()=>(new Imagick())->getImageUnits()),'empty_ink'=>pdTry(fn()=>(new Imagick())->getImageTotalInkDensity())];
$policies=['RGB'=>pdTry(fn()=>inspectForPrint($card,250.0)),'undefined_units'=>pdTry(fn()=>pdPhysical($card))];
pdDiagnostic('print-workflow',['executed_api'=>['getImageUnits','getImageTotalInkDensity'],'before_ink'=>$beforeInk,'delivery'=>pdState($delivery,'CMYK'),'readback'=>pdState($read,'CMYK'),'saved_tiff'=>['filename'=>'delivery-cmyk.tiff','sha256'=>hash_file('sha256',__DIR__.'/delivery-cmyk.tiff'),'bytes'=>filesize(__DIR__.'/delivery-cmyk.tiff')],'report'=>$report,'read_report'=>$readReport,'saved_mask'=>$savedMask,'errors'=>$errors,'policies'=>$policies,'crop'=>['x'=>10,'y'=>14,'width'=>40,'height'=>32,'scale'=>10],
'images'=>$images,'panel_keys'=>['readback','flag_crop'],'panel_geometry'=>$rects,'source_unchanged'=>pdState($card)===$beforeCard&&pdState($ink,'CMYK')===$beforeInk,
'checks'=>['TIFF_exact_CMYK'=>pdHash($delivery,'CMYK')===pdHash($read,'CMYK'),'TIFF_units'=>$read->getImageUnits()===Imagick::RESOLUTION_PIXELSPERINCH,'report_readback'=>$report===$readReport,'one_warning_pixel'=>$report['ink']['flagged_count']===1,'manual_review'=>$report['status']==='needs-review'&&count($report['warnings'])===2,'empty_units_caught'=>($errors['empty_units']['error']['class']??null)==='ImagickException','empty_ink_caught'=>($errors['empty_ink']['error']['class']??null)==='ImagickException','RGB_policy_caught'=>($policies['RGB']['error']['class']??null)==='InvalidArgumentException','unknown_units_caught'=>($policies['undefined_units']['error']['class']??null)==='InvalidArgumentException']]);
echo "print-workflow.png\nworkflow-readback-view8.png\nworkflow-flag-map8.png\ndelivery-cmyk.tiff\nworkflow-mask.png\nprint-report.json\nmetrics.json\nstatus=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2\n";

Настоящий TIFF сохранён и прочитан снова с теми же CMYK-значениями. Отчёт указывает один пиксель выше учебного порога 250% и отсутствие ICC-профиля. Статус требует рассмотрения: файл не объявляется пригодным для печати. JSON действительно открыт заново, ошибки и отказы проверки обработаны отдельно. Архив содержит example.php, два входа card.png.b64 и patches.tiff.b64 и print-fixture.php. Распакуйте всё рядом и выполните php example.php. Файлы автоматически восстановятся с проверкой SHA. Вспомогательный код рисует диаграммы, читает каналы и сохраняет отчёт; полная версия приведена ниже. Все изучаемые вызовы находятся в example.php. Проверенная среда: PHP 8.5.11, Imagick 3.8.1, ImageMagick 7.1.1-46 Q16, Windows.

Ожидаемый вывод после настоящего запуска
print-workflow.png
workflow-readback-view8.png
workflow-flag-map8.png
delivery-cmyk.tiff
workflow-mask.png
print-report.json
metrics.json
status=needs-review maximum=400.0000% flagged=1 ICC=missing errors=2 policy-rejections=2
Источники и обсуждения

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

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

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

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