Попытка разобраться с интересной возможностью canvas, которая называется “манипуляция с пикселями” (raw pixel). Основная суть этой возможности заключается в том, что можно получить информацию о цвете и альфа-канале любого пикселя, расположенного в произвольном месте canvas.
Образно выражаясь, можно сделать снимок (снять цветовой отпечаток) с любого участка canvas. Причем, этот отпечаток может быть любого размера (20х20 пикселей, 100х100 пикселей, 1х1 пиксель) - какой потребуется.
Техника Raw Pixel возможна благодаря объекту ImageData, у которого есть три свойства:
- ImageData.width - ширина объекта в пикселях
- ImageData.height - высота объекта в пикселях
- ImageData.data - массив данных
Первые два свойства примитивно просты - это геометрические размеры объекта ImageData.
Самым интересным свойством объекта ImageData является последнее -
.1
ImageData.data
Данное свойство в свою очередь является объектом, а если быть точнее - одномерным массивом. В этом массиве на каждый пиксель из “отпечатка” отводится 4 байта:
- imageData.data[0] — значение красного цвета (число от 0 до 255);
- imageData.data[1] — значение зеленого цвета (число от 0 до 255);
- imageData.data[2] — значение синего цвета (число от 0 до 255);
- imageData.data[3] — значение прозрачности (число от 0 до 255);
В результате получается значение цвета в формате RGBA.
У Canvas есть несколько методов для работы с объектом ImageData:
- getImageData()
- putImageData()
- toDataURL()
- createIamgeData()
Наиболее интересные и полезные два первых метода -
и 1
getImageData
.1
putImageData
Метод getImageData
Метод
позволяет создать экземпляр объекта ImageData на основе существующего canvas. Другими словами, этот метод “делает снимок” существующего canvas и преобразует этот “снимок” в объект ImageData.1
getImageData
Создадим простой пример для наглядного отображения работы метода
:1
getImageData
Что происходит в выше приведенном коде? Все просто - создаются два блока с синим и зеленым цветом, причем блок с зеленым цветом намеренно накладывается на блок с синим цветом.
А затем с помощью метода
делаем снимок (снимаем отпечаток - если хотите) размером 1х1 пиксель с уже готового рисунка в canvas.1
getImageData
В первом случае левый верхний угол “отпечатка” будет находиться в точке (40, 40) координатной сетки canvas; во-втором случае левый верхний угол “отпечатка” будет находиться в точке (20, 20). В обоих случая ширина и высота “отпечатка” (снимка) равна 1x1 пиксель - то есть, будет делаться “снимок” размером (площадью) в 1 пиксель.
Результат метода
помещается в переменную 1
getImageData
. Так как эта переменная не что иное, как ссылка на конкретный экземпляр объекта ImageData, то мы можем воспользоваться свойством 1
rawPixel
этого объекта, а точнее - массивом данных. Обращаясь по индексу к каждому из элементов массива, в итоге мы получаем значение цвета данного пикселя в формате RGBA.1
data
Площадь “снимка” можно произвольно увеличить и тогда массив данных также увеличиться. К примеру, такой код:
… создаст массив вида:
Как видим, к любому элементу этого массива можно обратиться по его индексу, чтобы получить значение цвета конкретного пикселя.
Сделаем приведенный выше пример более интересным (и наглядным для понимания) - добавим в него динамики. Преобразуем его так, чтобы в любой момент времени в отдельном информационном блоке выводился цвет (в формате RGBA) того участка canvas, над которым в данный момент находится курсор мыши. Будем считать, что курсор мыши имеет размер 1х1 пиксель:
В этом коде функция
при движении курсора мыши (событие 1
getColor
) над областью canvas (1
mousemove
) считывает координаты этого курсора в переменные 1
picker.canvas
и 1
cx
. Значения этих переменных передаются в качестве параметров методу 1
cy
, результат работы которого помещается в переменную 1
getImageData
.1
currentColor
Из переменной
с помощью свойства 1
currentColor
достаются значения (как элементы массива, по индексу) для каждого из RGBA-каналов. Все четыре значения конкатенируются и передаются в виде строкового значения - как фоновый RGBA-цвет для блока 1
data
.1
colorBox
Для пущей наглядности с помощью свойства
в блок 1
textContent
передается текущее значение цвета.1
colorBox
Представленный выше функционал - не что иное, как обычный Color Picker в любом графическом редакторе. Просто в данном примере достаточно изменить событие
на событие 1
mousemove
, чтобы все заработало как надо.1
click
Метод putImageData
Возможности метода
значительно шире, так как этот метод позволяет редактировать canvas. Другими словами, с помощью метода 1
putImageData
получается конкретный экземпляр объекта ImageData из текущего canvas.1
getImageData
Затем при помощи произвольной функции производится преобразование данных этого объекта.
Отредактировванные данные возвращаются обратно в canvas с помощью метода
.1
putImageData
Давайте на конкретном примере рассмотрим описанный выше пример:
В приведенном выше коде динамически (с помощью конструктора) создается экземпляр изображения и задается значение для его атрибута
. Затем на это изображение “вешается” функция, задача которой - отрисовать это изображение в canvas.1
src
Изображение отрисовывается в canvas:
… и тут же с него снимается отпечаток - создается объект ImageData:
Полученный объект
обрабатывается двумя произвольными функциями - 1
imageData
и 1
imageInvert()
при событии 1
grayScaleImage()
на кнопках:1
click
Функция
инвертирует цвета - “пробегается” по массиву 1
imageInvert()
и производит простое вычитание текущего значения цвета из 255:1
imageData.data
Функция
также “пробегается” по массиву 1
grayScaleImage()
, но при этом производит усреднение значения цвета для каждого из пикселов:1
imageData.data
Приведем еще один пример инвертации цвета. В произвольной функции будет производится перемена цвета местами - значение красного канала будет помешаться в зеленый канал; значение зеленого цвета будет помещаться в красный канал:
В этом коде canvas задаются размеры оригинального изображения:
Затем в цикле производится взаимозамещение красного и зеленого каналов:
Метод toDataURL()
Еще одним интересным методом при работе с замещением пикселей является метод
. Суть его проста - также как и метод 1
toDataURL()
, этот метод получается “снимок” текущего canvas и сохраняет результат в виде изображения в двух форматах на выбор - 1
getImageData()
или 1
jpg
.1
png
Синтаксис этого метода таков:
Стоит обратить внимание на явное указание (с помощью MIME) формата, в котором производится сохранение изображения. Помимо этого, при сохранении в формате
возможно указание второго параметра, который служит для задания качества сохраняемого изображения (от 0 до 1).1
jpg
Кроме этого, стоит обратить внимание, что изображение кодируется в base64 формате и именно в этом виде может быть использовано; но никак не в форматах
или 1
jpg
.1
png
Для внесения большей ясности давайте рассмотрим еще один интересный пример, в котором будет показана работа метода
:1
toDataURL()
Что мы имеем в приведенном выше коде? Ну, во-первых, это конечно же canvas. На этом canvas’е при помощи мыши мы можем рисовать - за это отвечает функция
.1
drawCanvas()
У этой функции работа проста, но есть одна фишка - это флаг
. Когда курсор мыши попадает в область canvas и начинает двигаться в пределах области этого canvas (событие 1
mouseDown
), то запускается функция 1
mousemove
.1
drawCanvas (event)
Но результата работы этой функции нет, так условие внутри этой функции не срабатывает из-за значения флага
.1
mouseDown == false
С помощью событий
и 1
mousedown
в коде производится переключение состояний флага 1
mouseup
из 1
mouseDown
в 1
false
и обратно.1
true
Также при событии
производится “снимок” текущего canvas и помещение его в атрибут 1
mouseup
ссылки 1
href
:1
a
Обратите внимание на редкий HTML5-атрибут
, в котором задается имя скачиваемого изображения.1
download
Если для ссылки указан атрибут
, то при клике по этой ссылке перехода никуда не происходит, а выполняется скачивание изображения с именем по-умолчанию (заданном в атрибуте 1
download
).1
download
Заключение
Вот в принципе и все о манипуляцих с пикселями (raw pixel) в canvas. На самом деле это конечно же не все, что можно рассказать и сделать с помощью этой техники.
Здесь я просто сам познакомился с нею и вкратце описал моменты, которые мне показались наиболее интересными.
Этой статьи бы не было, если бы не существовали источники, которые и помогли ей родиться:
- Справочник HTML5 Canvas
- Pixel manipulation with canvas
- Canvas, images and pixels - статья с большим набором отличных примеров, некоторые из которых я использовал в своем скромном обзоре
P.S.
“Живых” примеров кода из этого обзора я не привожу, ибо лень ) Адекватная критика и замечания приветствуются )
На этом все.