В данном материале мы рассмотрим некоторые примеры манипуляции с изображением при помощи библиотеки GD, а именно – операции с пикселями. Манипуляции с пикселом означают, что действие будет относиться только к нему не затрагивая все окружающие пиксели.
Например, мы можем сделать негатив изображения. Для этого берем каждый пиксель в изображении и заменяем его противоположным цветом.
Хорошо, но как это работает? Очень просто. Я беру картинку PNG, прохожу по каждому пикселю этого изображения и передаю его функции, которая принимает пиксель в качестве параметра. Функция возвращает мне новый пиксель. Я складываю все новые пиксели и получаю новое изображение.
Класс для работы с пикселом
Для начала нам понадобится класс для работы с пикселом. Он очень прост – содержит в себе три значения: красного, зеленого и синего.
<?php
class Pixel {
function Pixel($r, $g, $b)
{
$this->r = ($r > 255) ? 255 : (($r < 0) ? 0 : (int)($r));
$this->g = ($g > 255) ? 255 : (($g < 0) ? 0 : (int)($g));
$this->b = ($b > 255) ? 255 : (($b < 0) ? 0 : (int)($b));
}
}
?>
Этот класс имеет только одну функцию – конструктор, которая сохраняет RGB значения пикселя.
Чтобы создать красный пиксел, вы просто делаете:
<?php
$red = new Pixel(255, 0, 0);
?>
Класс манипуляций с пикселами: главный метод
Далее мы создаем класс, который проделывает фактические действия с изображением. Назовем его Image_PixelOperations(). Я не стал писать удобного интерфейса для обработки различных форматов картинок, я думаю, что вы можете развить его самостоятельно. Все, что мне было нужно – это более простой метод для открытия PNG файлов, который бы проходил по каждому пикселу файла, вызывает функцию, получает новый пиксел и присваивает его новому изображению. Далее приведу текст метода:
<?php
class Image_PixelOperations {
function pixelOperation(
$input_image,
$output_image,
$operation_callback,
$factor = false
)
{
$image = imagecreatefrompng($input_image);
$x_dimension = imagesx($image);
$y_dimension = imagesy($image);
$new_image = imagecreatetruecolor($x_dimension, $y_dimension);
if ($operation_callback == 'contrast') {
$average_luminance = $this->getAverageLuminance($image);
} else {
$average_luminance = false;
}
for ($x = 0; $x < $x_dimension; $x++) {
for ($y = 0; $y < $y_dimension; $y++) {
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$pixel = new Pixel($r, $g, $b);
$pixel = call_user_func(
$operation_callback,
$pixel,
$factor,
$average_luminance
);
$color = imagecolorallocate(
$image,
$pixel->r,
$pixel->g,
$pixel->b
);
imagesetpixel($new_image, $x, $y, $color);
}
}
imagepng($new_image, $output_image);
}
}
?>
Метод принимает путь до файла. Никаких проверок не производит, предполагая, что это правильный PNGфайл. Второй параметр – это имя нового файла изображения. Третий – функция, которая будет вызываться для каждого пикселя. И последний параметр – это любой дополнительный параметр, который мы хотели бы передать в вызываемую для пиксела функцию.
Добавляем шумов
И так, пришло время, чтобы написать первую функцию обработки пиксела: addNoise(). Добавление шума к изображению означает добавление случайного значения к каждому каналу пикселя (если у вас возник вопрос, что такое канал, то я отвечу – уровень красного цвета в пикселе = канал, тоже самое с зеленым и синим). Далее привожу функцию:
<?php
function addNoise($pixel, $factor)
{
$random = mt_rand(-$factor, $factor);
return new Pixel(
$pixel->r + $random,
$pixel->g + $random,
$pixel->b + $random
);
}
?>
Что представляет из себя функция? Она получает случайное число в указанном пользователем диапазоне ($factor). И добавляет его к значению каналов пиксела. Пользователь указывает диапазон уровня шума: 0 – нет шума, 255 – очень много шума.
Давайте проверять! Создаем простую HTML форму:
<form method="get">
<input name="image" />
<input type="submit" />
</form>
Она принимает параметр – названия файла с изображением. После получения этого параметра я создаю новый объект класса для работы с пикселами:
<?php
if (!empty($_GET['image'])) {
$po =& new Image_PixelOperations();
}
?>
Далее я показываю оригинальное изображение, а затем результат обработки.
<?php
echo 'Оригинал: <br /><img src="'. $_GET['image'] .'" />';
echo '<hr />';
// Шумы
$noise = 100;
$po->pixelOperation($_GET['image'], 'result_noise.png',
array($po, 'addNoise'), $noise);
echo '<br />Добаляем шумы (factor '. $noise .'):
<br /><img src="result_noise.png" />';
echo '<hr />';
?>
Результат:
Вот еще примеры. Первое изображение получено при помощи фактора 20, а второе – 500:
Управление яркостью
Давайте теперь попробуем поиграть с яркостью изображения. Нижеприведенная функция добавляет целое число (одно и тоже) к каждому каналу пиксела. Если мы вызываем функцию с положительным значением – яркость увеличивается, если с отрицательным – уменьшается.
<?php
function adjustBrightness($pixel, $factor)
{
return new Pixel(
$pixel->r + $factor,
$pixel->g + $factor,
$pixel->b + $factor
);
}
?>
Чтобы протестировать эту функцию выполните следующий код:
<?php
$brightness = 50;
$po->pixelOperation($_GET['image'], 'result_bright.png',
array($po, 'adjustBrightness'), $brightness);
echo '<br />Ярче: <br /><img src="result_bright.png" />';
$brightness = -50;
$po->pixelOperation($_GET['image'], 'result_dark.png',
array($po, 'adjustBrightness'), $brightness);
echo '<br />Темнее: <br /><img src="result_dark.png" />';
echo '<hr />';
?>
Смотрим на результат:
Меняем местами цвета
Давайте теперь займемся сменой цветов. Это означает, что мы можем взять, скажем, количество красных цветов и заменить их, например, на количество синих цветов. Возможные варианты:
- RGB to RBG
- RGB to BGR
- RGB to BRG
- RGB to GBR
- RGB to GRB
Давайте посмотрим, как выглядит функция:
<?php
function swapColors($pixel, $factor)
{
switch ($factor) {
case 'rbg':
return new Pixel(
$pixel->r,
$pixel->b,
$pixel->g
);
break;
case 'bgr':
return new Pixel(
$pixel->b,
$pixel->g,
$pixel->r
);
break;
case 'brg':
return new Pixel(
$pixel->b,
$pixel->r,
$pixel->g
);
break;
case 'gbr':
return new Pixel(
$pixel->g,
$pixel->b,
$pixel->r
);
break;
case 'grb':
return new Pixel(
$pixel->g,
$pixel->r,
$pixel->b
);
break;
default:
return $pixel;
}
}
?>
Тестируем:
RGB -> RBG |
RGB -> BGR |
RGB -> BRG |
RGB -> GBR |
RGB -> GRB |
Удаление или насыщение цветов
Далее рассматриваем еще 2 функции. Первая – устанавливает значение цвета в 0 (например, нет красного). Вторая – наоборот увеличивает колличество цвета до максимального значения, или сразу 2канала. Таким образом, мы имеем 6 вариантов значений для каждого метода.
- Удаление (или насыщение) красный
- Удаление (или насыщение) зеленый
- Удаление (или насыщение) синий
- Удаление (или насыщение) красный и зеленый в то же самое время
- Удаление (или насыщение) красный и синий
- Удаление (или насыщение) зеленый и синий
Код:
<?php
function removeColor($pixel, $factor)
{
if ($factor == 'r' ) {
$pixel->r = 0;
}
if ($factor == 'g' ) {
$pixel->g = 0;
}
if ($factor == 'b' ) {
$pixel->b = 0;
}
if ($factor == 'rb' || $factor == 'br') {
$pixel->r = 0;
$pixel->b = 0;
}
if ($factor == 'rg' || $factor == 'gr') {
$pixel->r = 0;
$pixel->g = 0;
}
if ($factor == 'bg' || $factor == 'gb') {
$pixel->b = 0;
$pixel->g = 0;
}
return $pixel;
}
function maxColor($pixel, $factor)
{
if ($factor == 'r' ) {
$pixel->r = 255;
}
if ($factor == 'g' ) {
$pixel->g = 255;
}
if ($factor == 'b' ) {
$pixel->b = 255;
}
if ($factor == 'rb' || $factor == 'br') {
$pixel->r = 255;
$pixel->b = 255;
}
if ($factor == 'rg' || $factor == 'gr') {
$pixel->r = 255;
$pixel->g = 255;
}
if ($factor == 'bg' || $factor == 'gb') {
$pixel->b = 255;
$pixel->g = 255;
}
return $pixel;
}
?>
Результаты:
Делаем негатив
Эта функция очень проста – у вас много красного? Значит сделаем мало. И так далее.
<?php
function negative($pixel)
{
return new Pixel(
255 - $pixel->g,
255 - $pixel->r,
255 - $pixel->b
);
}
?>
Результат:
Оттенки серого (Grayscale)
Не знаю, в курсе вы или нет, но оттенок серого получается уравниванием R, G, B каналов. Более темные участки имеют больше насыщения, светлые – меньше.
Чтобы привести изображение к оттенкам серого мы должны взять среднее число насыщения каналов и установить их на среднее число.
<?php
function greyscale($pixel)
{
$pixel_average = ($pixel->r + $pixel->g + $pixel->b) / 3;
return new Pixel(
$pixel_average,
$pixel_average,
$pixel_average
);
}
?>
Результат:
Черно-белое
В отличие от оттенков серого, черно-белое изображение имеет только 2 цвета: черный (0,0,0) и белый (255,255,255). $factor мы будем использовать для определения границы того, что считать черным, а что – белым. Простота логики в том, что мы суммируем R+G+B и смотрим, к чему значение ближе – к 255 или к 0. Использование $factor позволит нам внести некоторую гибкость в алгоритм (внести некоторую субъективность):
<?php
function blackAndWhite($pixel, $factor)
{
$pixel_total = ($pixel->r + $pixel->g + $pixel->b);
if ($pixel_total > (((255 + $factor) / 2) * 3)) {
// белый
$pixel->r = 255;
$pixel->g = 255;
$pixel->b = 255;
} else {
$pixel->r = 0;
$pixel->g = 0;
$pixel->b = 0;
}
return $pixel;
}
?>
Результат ($factor = 20):
Отсечение
Не знаю, насколько эта функция может оказаться полезной. Она занимается удалением пограничных значений (переходов) цвета, заменяя их чистым цветом: если у вас было 5, 155, 250 станет – 0, 155, 255. $factor дает нам гибкость в рисунке. Пока я вижу нужность этой функции для уменьшения размера изображения.
<?php
function clip($pixel, $factor)
{
if ($pixel->r > 255 - $factor) {
$pixel->r = 255;
}
if ($pixel->r < $factor) {
$pixel->r = 0;
}
if ($pixel->g > 255 - $factor) {
$pixel->g = 255;
}
if ($pixel->g < $factor) {
$pixel->g = 0;
}
if ($pixel->b > 255 - $factor) {
$pixel->b = 255;
}
if ($pixel->b < $factor) {
$pixel->b = 0;
}
return $pixel;
}
?>
Результат ($factor = 100):
Корректировка контраста
Эта операция не является операцией над пикселом в чистом виде, поскольку принимает во внимание информацию обо всех пикселях для принятия решения о том, как поступить с данным конкретным. Настройка контраста нуждается в так называемой средней яркости. Чтобы высчитать среднюю яркость вам необходима формула, приведенная в функции ниже.
<?php
function getAverageLuminance($image)
{
$luminance_running_sum = 0;
$x_dimension = imagesx($image);
$y_dimension = imagesy($image);
for ($x = 0; $x < $x_dimension; $x++) {
for ($y = 0; $y < $y_dimension; $y++) {
$rgb = imagecolorat($image, $x, $y);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$luminance_running_sum += (0.30 * $r) + (0.59 * $g) + (0.11 * $b);
}
}
$total_pixels = $x_dimension * $y_dimension;
return $luminance_running_sum / $total_pixels;
}
?>
Конечное же преобразование контраста очень просто:
<?php
function contrast($pixel, $factor, $average_luminance)
{
return new Pixel(
$pixel->r * $factor + (1 - $factor) * $average_luminance,
$pixel->g * $factor + (1 - $factor) * $average_luminance,
$pixel->b * $factor + (1 - $factor) * $average_luminance
);
}
?>
Результаты (0.5 и 1.5 соответственно):
Соль и перец
Эта функция в целом случайным образом «разбрызгивает» по изображению белые и черные точки.
<?php
function saltAndPepper($pixel, $factor)
{
$black = (int)($factor/2 + 1);
$white = (int)($factor/2 - 1);
$random = mt_rand(0, $factor);
$new_channel = false;
if ($random == $black) {
$new_channel = 0;
}
if ($random == $white) {
$new_channel = 255;
}
if (is_int($new_channel)) {
return new Pixel($new_channel, $new_channel, $new_channel);
} else {
return $pixel;
}
}
?>
Пример ($factor = 20):
Гамма-коррекция
<?php
function gamma($pixel, $factor)
{
return new Pixel(
pow($pixel->r / 255, $factor) * 255,
pow($pixel->g / 255, $factor) * 255,
pow($pixel->b / 255, $factor) * 255
);
}
?>
Пример ($factor = 2.2):
Послесловие
Если вы, экспериментируя с этим классом, изобрели еще какой-либо интересный эффект — опубликуйте свою функцию в комментариях внизу этой страницы и я добавлю ее в эту статью!