Отправляет email-рассылки с помощью сервиса Sendsay

Построй свой сайт на PHP!

  Все выпуски  

Построй свой сайт на PHP!


Построй свой сайт на PHP!

искать в
Здравствуйте, уважаемые читатели!

Итак, вот и обещанный выпуск. Не буду устраивать особенных лирических отступлений. Если хотите пообщаться, просто заходите на форум webdeveloper.net.ru, мой блог, пишите письма или стучитесь в ICQ: #233661333.

С уважением, Олег Шимчик (The Wanderer)

Забавы с картинками

От переводчика

Эта статья является переводом заметки Image fun, опубликованной в блоге phpied.com. Естественно, это накладывает определенный отпечаток на манеру подачи материала. Однако, как мне кажется, заметка сама по себе достаточно интересна и самодастаточна и не требует каких-либо дополнительных изменений, поэтому все было оставлено в исходном виде. Приятного чтения!

Вот несколько примеров работы с изображениями с использованием библиотеки GD. Точнее, операций с пикселами. Операция с пикселом подразумевает какое-либо действие над отдельным пикселом в изобрежении без учета его соседей.

Примером операции с пикселами является создание негатива изображения. Вы берете каждый пиксел в изображении и заменяете его пикселом противоположного цвета.

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

Класс Pixel

Для начала я создал класс Pixel. Он просто включает в себя три целых значения: значения содержания красного, зеленого и синего в пикселе.

<?php
class Pixel {
    function 
Pixel($r$g$b)
    {
        
$this->= ($r 255) ? 255 : (($r 0) ? : (int)($r));
        
$this->= ($g 255) ? 255 : (($g 0) ? : (int)($g));
        
$this->= ($b 255) ? 255 : (($b 0) ? : (int)($b));
    }
}
?>

У этого класса всего один метод, конструктор класса, который занимается предварительной обработкой значений RGB.

Для создания красного пикселя вам просто нужно написать:

<?php
$red 
= new Pixel(25500);
?>

Класс для операций над пикселами и главный метод

Затем я создал класс для операций над пикселами, который я назвал Image_PixelOperations. Я не ставил своей задачей создать удобный интерфейс для чтения и записи разных файловых форматов, я думаю, что этот класс можно разрабатывать дальше и взять в качестве основы PEAR Image_Transform, который имеет инструменты для открытия, проверки, отображения и записи файлов изображений. Все, что мне было нужно, это простой метод, который открывает PNG, перебирает все пиксели, вызывает функцию, получает новый пиксел и записывает его в изображение. Итак, метод pixelOperation:

<?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-файл. Второй параметр - имя выходного файла. Третий - это имя функции, которая будет вызвана для каждого пиксела, последний параметр - это любой параметр, который вы хотите передать вызываемой функции.
Средняя яркость - это специфичный для операции "контраст" параметр, так что он не особо относится к тому, что делает pixelOrerations.

Добавляем шум

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

<?php
    
function addNoise($pixel$factor)
    {
        
$random mt_rand(-$factor$factor);
        return new 
Pixel(
                    
$pixel->$random,
                    
$pixel->$random,
                    
$pixel->$random
                
);
    }
?>

Здесь мы генерируем случайное значение в заданом пользователем диапазоне и добавляем его к пикселу. Заданный пользователем диапазон - это число в интервале от 0 до 255, где 0 означает меньший уровень шума, а 255 - больший. Хорошо, 0 означает, что шума нет, а 255 означает кучу шума, 255 - это не граница, вы можете задать большее значение, но чем больше значение, тем больше шума и практически ничего от исходного изображения.

Давайте проверим! Я сделал простейшую HTML-форму:

<form method="get">
    <input name="image" />

    <input type="submit" />
</form>

Я задаю в форме имя изображения и отправляю ее.

Затем, если что-то было отправлено, я создаю объект класса Image_PixelOperations:

<?php
if (!empty($_GET['image'])) {
    
    
$po =& new Image_PixelOperations();
    
}
?>

Затем я вывожу оригинальное изображение, вызываю метод pixelOperation и отображаю результат:

<?php
    
echo 'Original: <br /><img src="'$_GET['image'] .'" />';
    echo 
'<hr />';
    
    
// noise
    
$noise 100;
    
$po->pixelOperation($_GET['image'], 'result_noise.png', array($po'addNoise'), $noise);
    echo 
'<br />Add noise (factor '$noise .'): <br /><img src="result_noise.png" />';
    echo 
'<hr />';
    
?>

Результат:

Изменения уровня шума дают следующие результаты (первый имеет фактор шума 20, а второй 500):

и

Яркость

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

<?php
    
function adjustBrightness($pixel$factor)
    {
    
        return new 
Pixel(
                    
$pixel->$factor,
                    
$pixel->$factor,
                    
$pixel->$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 в RBG
  • RGB в BGR
  • RGB в BRG
  • RGB в GBR
  • RGB в 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:

Удаление и стимуляция цветов

Далее у нас еще два метода. Один устанавливает канал в ноль (к примеру, "нет красного!") другой увеличивает канал до максимума. Или два канала. Таким образом, у нас есть шесть вариантов для каждого метода:

  • Удаление (или увеличение до максимума) красного
  • Удаление (или увеличение до максимума) зеленого
  • Удаление (или увеличение до максимума) синего
  • Удаление (или увеличение до максимума) красного и зеленого одновременно
  • Удаление (или увеличение до максимума) красного и синего
  • Удаление (или увеличение до максимума) зеленого и синего

(Не имеет особого смысла удаление или увеличение до максимума всех трех каналов. Почему?)

Реализация:

<?php
    
function removeColor($pixel$factor)
    {
    
        if (
$factor == 'r' ) {
            
$pixel->0;
        }
        if (
$factor == 'g' ) {
            
$pixel->0;
        }
        if (
$factor == 'b' ) {
            
$pixel->0;
        }
        if (
$factor == 'rb' || $factor == 'br') {
            
$pixel->0;
            
$pixel->0;
        }
        if (
$factor == 'rg' || $factor == 'gr') {
            
$pixel->0;
            
$pixel->0;
        }
        if (
$factor == 'bg' || $factor == 'gb') {
            
$pixel->0;
            
$pixel->0;
        }
    
        return 
$pixel;
    }
    
    function 
maxColor($pixel$factor)
    {
    
        if (
$factor == 'r' ) {
            
$pixel->255;
        }
        if (
$factor == 'g' ) {
            
$pixel->255;
        }
        if (
$factor == 'b' ) {
            
$pixel->255;
        }
        if (
$factor == 'rb' || $factor == 'br') {
            
$pixel->255;
            
$pixel->255;
        }
        if (
$factor == 'rg' || $factor == 'gr') {
            
$pixel->255;
            
$pixel->255;
        }
        if (
$factor == 'bg' || $factor == 'gb') {
            
$pixel->255;
            
$pixel->255;
        }
    
        return 
$pixel;
    }
    
?>

И тесты:
Удаляем красный:

Удаляем зеленый:

Удаляем синий:

Удаляем красный и зеленый:

Удаляем зеленый и синий:

Удаляем синий и красный:

Увеличиваем до максимума красный:

Увеличиваем до максимума зеленый:

Увеличиваем до максимума синий:

Увеличиваем до максимума красный и зеленый:

Увеличиваем до максимума зеленый и синий:

Увеличиваем до максимума красный и синий:

Негатив

Довольно просто превратить канал в негатив. Логика следующая: у вас много красного? Будет мало.

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

Тест выдает нам:

Шкала серого

Я не знаю, знаете ли вы, но серый - это цвет, в котором одинаковое количество красного зеленого и синего. Более темные оттенки серого имеют большее красного, зеленого и синего, более светлые - меньше. Чтобы превратить изображение в сверое мы берем среднее значение красного, зеленого и синего и присваиваем всем трем каналам это среднее значение.

<?php
    
function greyscale($pixel)
    {
    
        
$pixel_average = ($pixel->$pixel->$pixel->b) / 3;
    
        return new 
Pixel(
                    
$pixel_average,
                    
$pixel_average,
                    
$pixel_average
                
);
    }
?>

Тест:

Черный и белый

В отличие от серого, имеющего оттенки, у черно-белого изображения всего два цвета: черный (0, 0, 0) и белый (255, 255, 255). Мы используем коэффициент для того, чтобы определить какая граница подходит больше. Я имею в виду, что мы будем считать белым, а что черным. Самая простая логика - это просуммировать R+G+B и если сумма ближе к 255+255+255, чем к 0 (0+0+0), то мы считаем цвет белым, в противном случае - черным. Использование коэффициента дает нам большую гибкость при разграничении черного и белого. (Это отражает субъективность реального мира ;) )

<?php
    
function blackAndWhite($pixel$factor)
    {
        
$pixel_total = ($pixel->$pixel->$pixel->b);
    
        if (
$pixel_total > (((255 $factor) / 2) * 3)) {
            
// white
            
$pixel->255;
            
$pixel->255;
            
$pixel->255;
        } else {
            
$pixel->0;
            
$pixel->0;
            
$pixel->0;
        }
    
        return 
$pixel;
    }
    
?>

Проверка с коэффициентом 20:

Обрезание

С этого момента я начал розыск в интернете на предмет охоты за новыми пикселными операциями. И где-то нашел описание этого метода обрезания. Я не знаю, насколько все это полезно (может быть, я что-то не так понял). Вкратце смысл заключается в удалении близких к граничным значений и замене их на 0 или 255. Так что, если у вас есть цвет (5, 155, 250), то он станет (0, 155, 255). Опять же есть коэффициент, который дает гибкость в разграничении. Я не уверен, насколько все это полезно, единственное, что мне приходит в голову, это уменьшение размера файла, так как новое изображение использует меньше цветов. В любом случае, вот реализация и тест.

<?php
    
function clip($pixel$factor)
    {
        if (
$pixel->255 $factor) {
            
$pixel->255;
        }
        if (
$pixel->$factor) {
            
$pixel->0;
        }
        if (
$pixel->255 $factor) {
            
$pixel->255;
        }
        if (
$pixel->$factor) {
            
$pixel->0;
        }
        if (
$pixel->255 $factor) {
            
$pixel->255;
        }
        if (
$pixel->$factor) {
            
$pixel->0;
        }
    
        return 
$pixel;
    }
?>

Обрезание с коэффициентом 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->$factor + ($factor) * $average_luminance,
            
$pixel->$factor + ($factor) * $average_luminance,
            
$pixel->$factor + ($factor) * $average_luminance
            
);
    }
?>

Тесты с уменьшенным и увеличенным значениями контраста (коэффициенты 0.5 и 1.5):

Соль и перец

Я взял идею с этой страницы. Основа идем заключается в рассеивании случайных белых (соль) и черных (перец) пикселов. Реализация:

<?php
    
function saltAndPepper($pixel$factor)
    {
    
        
$black = (int)($factor/1);
        
$white = (int)($factor/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;
        }
    }
?>

Проверка с фактором 20:

Коррекция гаммы

<?php
    
function gamma($pixel$factor)
    {
    
        return new 
Pixel(
                
pow($pixel->255$factor) * 255,
                
pow($pixel->255$factor) * 255,
                
pow($pixel->255$factor) * 255
            
);
    }
?>

Проверка с коэффициентом 2.2:

Уравниваем вероятности

Смысл заключается в выборе случайной функции из описанных выше и вызове ее со случайным коэффициентом. Результат? Ну, научный способ создания самого худшего (или лучшего, в зависимости от того, как вы на это смотрите) цветного шума ;)

Дальше?

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

Другая функция будет заменять один цвет другим, тоже используя схожесть. К примеру, возьмем все очень-очень яркие оттеник серого и сделаем их белыми.

Затем, есть еще фильтры, набор операций, который учитывает не только данным пиксель, но и соседей. Примеры - размытие, определение краев и т.д.

Кстати, полный исходный текст к этой заметке находится здесь. Расширять его просто, вам нужно всего лишь реализовать функцию обратного вызова для эффекта и передать ее при вызове pixelOperation.

—————–

Обновление:

Спасибо Laurens Holst, который опубликовал этот комментарий о восприимчивости различных цветов человеческим глазом! Я создал новую функцию превращения цветов в оттенки серого, используя рекомендованную им формулу. В моей исходной функции я просто брал среднее значение R, G и B, теперь все эти цвета идут с коэффициентом. Затем я делю на сумму коэффициентов.

Результат лучше!

Вот эксперимент с тем же самым изображением Wiki (второе черно-белое изображени использует новую формулу)

Новое изобажение выглядит чуть светлее, ничего особенного. Но я повторил эксперимент с изображением, найденным на SitePoint и, похоже, что изображение не только светлее, но и содержит больше информации, видимой человеческим глазом. Посмотрите внимательнее на то, что напоминает дисплей на кассовом аппарате. Второе черно-белое изображение выглядит более "информативным".

Вот исходный код новой функции:

<?php
function greyscale2($pixel)
{
    
    
$pixel_average =
        ( 
0.3  $pixel->r
        
0.59 $pixel->g
        
0.11 $pixel->b) / (0.3 0.59 0.11);
    
    return new 
Pixel(
                
$pixel_average,
                
$pixel_average,
                
$pixel_average
            
);
    
}
?>
 
Copyright © 2004-2006 Построй свой сайт на PHP!
Перепечатка возможна только с сохранением авторства.
Выпуск #14: 2006-02-26

В избранное