Kuzma's PHP Look

Home page: http://kuzma.russofile.ru
Оригинал на http://www.phpied.com/image-fun/
Перевод Феськов Кузьма (kuzma@russofile.ru, http://php.russofile.ru)

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

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

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

Результат:

Рис. 1

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

Рис. 2 Рис. 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 />';
?>

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

Рис. 4 Рис. 5

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

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

  • 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
Рис. 6
RGB -> BGR
Рис. 7
RGB -> BRG
Рис. 8
RGB -> GBR
Рис. 9
RGB -> GRB
Рис. 10

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

Далее рассматриваем еще 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;
}
?>

Результаты:

Удаляем красный
Рис. 11
Удаляем зеленый
Рис. 12
Удаляем синий
Рис. 13
Удаляем красный и зеленый
Рис. 14
Удаляем зеленый и синий
Рис. 15
Удаляем красный и синий
Рис. 16
Насыщаем красный
Рис. 17
Насыщаем зеленый
Рис. 18
Насыщаем синий
Рис. 19
Насыщаем красный и зеленый
Рис. 20
Насыщаем зеленый и синий
Рис. 21
Насыщаем красный и синий
Рис. 22

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

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

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

Результат:

Рис. 23

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

Результат:

Рис. 24

Черно-белое

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

Рис. 25

Отсечение

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

Рис. 26

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

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

<?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 соответственно):

Рис. 27 Рис. 28

Соль и перец

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

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

Рис. 29

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

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

Рис. 30

Послесловие

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

Яндекс цитирования