Vkontakte
+7 (499) 705-13-20
skype: kuzma.feskov

Играем с изображением в PHP

Автор: Кузьма Феськов, 20 сентября 2014

Оригинал на http://www.phpied.com/image-fun/
Перевод Феськов Кузьма

В данном материале мы рассмотрим некоторые примеры манипуляции с изображением при помощи библиотеки 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 />';
        
?> 

Результат:

Пример добавления шумов к изображению на PHP Gd lib

Вот еще примеры. Первое изображение получено при помощи фактора 20, а второе – 500:

Пример добавления шумов  на PHP Gd lib 2 Пример добавления шумов  на PHP Gd lib 3

Управление яркостью

Давайте теперь попробуем поиграть с яркостью изображения. Нижеприведенная функция добавляет целое число (одно и тоже) к каждому каналу пиксела. Если мы вызываем функцию с положительным значением – яркость увеличивается, если с отрицательным – уменьшается.

<?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 />';
?> 

Смотрим на результат:

Пример управления яркостью изображения  на PHP Gd lib Пример управления яркостью  на PHP Gd lib 2

Меняем местами цвета

Давайте теперь займемся сменой цветов. Это означает, что мы можем взять, скажем, количество красных цветов и заменить их, например, на количество синих цветов. Возможные варианты:

  • 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
Меняем местами цвета  на PHP Gd lib
RGB -> BGR
Меняем местами цвета  на PHP Gd lib
RGB -> BRG
Меняем местами цвета  на PHP Gd lib
RGB -> GBR
Меняем местами цвета  на PHP Gd lib
RGB -> GRB
Меняем местами цвета  на PHP Gd lib

Удаление или насыщение цветов

Далее рассматриваем еще 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 Gd lib
Удаляем зеленый
Удаление или насыщение цветов на PHP Gd lib
Удаляем синий
Удаление или насыщение цветов на PHP Gd lib
Удаляем красный и зеленый
Удаление или насыщение цветов на PHP Gd lib
Удаляем зеленый и синий
Удаление или насыщение цветов на PHP Gd lib
Удаляем красный и синий
Удаление или насыщение цветов на PHP Gd lib
Насыщаем красный
Удаление или насыщение цветов на PHP Gd lib
Насыщаем зеленый
Удаление или насыщение цветов на PHP Gd lib
Насыщаем синий
Удаление или насыщение цветов на PHP Gd lib
Насыщаем красный и зеленый
Удаление или насыщение цветов на PHP Gd lib
Насыщаем зеленый и синий
Удаление или насыщение цветов на PHP Gd lib
Насыщаем красный и синий
Удаление или насыщение цветов на PHP Gd lib

Делаем негатив

Эта функция очень проста – у вас много красного? Значит сделаем мало. И так далее.

<?php
function negative($pixel)
{
    return new Pixel(
    255 - $pixel->g,
    255 - $pixel->r,
    255 - $pixel->b
    );
}
?> 

Результат:

Делаем негатив изображения на PHP GD Lib

Оттенки серого (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
    );
}
?> 

Результат:

Делаем эффект оттенки серого (grayscale) на PHP GD Lib

Черно-белое

В отличие от оттенков серого, черно-белое изображение имеет только 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):

Делаем изображение черно-белым на PHP GD Lib

Отсечение

Не знаю, насколько эта функция может оказаться полезной. Она занимается удалением пограничных значений (переходов) цвета, заменяя их чистым цветом: если у вас было 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 GD Lib

Корректировка контраста

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

<?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 GD Lib Корректировка контраста изображений на PHP GD Lib

Соль и перец

Эта функция в целом случайным образом «разбрызгивает» по изображению белые и черные точки.

<?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 GD Lib

Гамма-коррекция

<?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):

Гамма-коррекция изображений на PHP GD Lib

Послесловие

Если вы, экспериментируя с этим классом, изобрели еще какой-либо интересный эффект - опубликуйте свою функцию в комментариях внизу этой страницы и я добавлю ее в эту статью!