Предыдущий ролик Следующий ролик  

Видео урок: Доступ к "сырым" пикселям

HTML5: графика и анимация с Canvas

Возможно, одна из самых классных и наиболее мощных возможностей HTML5 canvas заключается в том, что теперь ваш скрипт может получить доступ к "сырым" пикселям canvas и напрямую манипулировать пикселями. Вы можете получить доступ к отдельным пиксельным данным как к массиву байтов canvas. Затем с этими данными можно что-то сделать и вставить их обратно в canvas. Каждое изображение в canvas, а по факту любой контекст canvas, состоит из пикселей в четыре байта шириной.

Давайте представим, что в canvas есть вот такое изображение, и мы смотрим на отдельную строку. Эта отдельная строка будет создана из отдельных пикселей, начиная слева и проходя вот сюда. Представьте, что каждый пиксель - это маленький кубик, составляющий частичку ряда. Итак, каждый из этих пикселей состоит из комбинации в четыре байта. У пикселя есть компоненты красный, зеленый и синий, а также настройка альфа. Получая прямой доступ к этим байтам, мы манипулируем тем, как изображение выглядит в canvas.

Если мы подсчитаем размер этого массива данных изображения, это будет высота изображения, верно, вот эта высота, также ширина, и 4 байта, поскольку вы ведь помните, что размер каждого пикселя составляет 4 байта. Чтобы получить число байтов, вам нужно ширину умножить на высоту и умножить на 4, чтобы подсчитать байты, входящие в пиксели. Также я хочу отметить здесь вопрос о безопасности. Если какой-то скрипт может получить доступ к данным canvas, он может тайком отправить их обратно на другой веб сайт или другой веб сервер.

То есть, по причинам безопасности скрипты должны происходить из одного источника. Если скрипт хочет получить доступ к контенту canvas на веб странице, он должен происходить из того же источника, что и сама веб страница; иначе доступа он не получит, а браузер выбросит исключение. Какие же есть функции для получения доступа к данным изображения? Ну, мы можем сделать несколько вещей. Мы можем получить высоту и ширину пиксельных данных canvas.

Также есть свойство data, которое является одномерным массивом всех "сырых" пиксельных данных. Это большой массив, содержащий все байты изображения. Мы можем создать новые данные изображения с нуля. Для это нужна функция createImageData. Есть две ее версии. Одна принимает ширину и высоту; вторая принимает существующую структуру данных изображения и создает из нее новую. Мы также можем получить данные изображения, и мы можем получить данные изображения в пределах заданных рамок. В canvas мы начинаем с исходной точки x и исходной точки y, а также заданной ширины и высоты, и мы можем получить часть данных изображения.

Затем мы можем сделать все, что угодно, с данными изображения и вставить их обратно при помощи putImageData. Функция putImageData вставляет измененные данные, основываясь на них, обратно в изображение в точках dx и dy. Вот эти аргументы - dirtyDx, dirtyDy, dirtyW и dirtyH - являются опциональными, и они указывают на то, что называется "грязным" прямоугольником (dirty rectangle). Если передается "грязный" прямоугольник, будут обновлены только байты внутри этого прямоугольника. Давайте посмотрим на реальный пример использования доступа к данным изображения.

Вот мой код, файл snippets и пример rawdata, а вот файл rawdata. Я хочу отметить, что что я запускаю пример с моего веб сервера, который расположен на моем компьютере. Вы видите, что в браузере у меня есть localhost, чтобы получить доступ к файлу. Таким образом, у нашего браузера есть домен, от которого он отталкивается, когда запускает мой скриптовый код и пытается получить доступ к данным изображения. Если вы хотите посмотреть, как работает этот пример, вам будет нужно запустить его с реального веб сайта. Не важно, будет это localhost или любой веб сайт, с которым вы работаете; вы можете просто его запустить, загрузив локальный файл в браузер.

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

Давайте сначала убедимся, что это работает. Обновляем страницу. Ок, все работает. Итак, мы берем исходное изображение, рисуем его в canvas, и все отлично. Это не проблема; мы это уже видели. Давайте снова вернемся к коду. И теперь мы сделаем с этим изображением что-то интересное. Давайте получим данные изображения и немного с ними поработаем. Вставляем. Теперь смотрим, что делает код, который я только что вставил. Мы получаем данные изображения и каждый 15ый ряд изображения мы конвертируем в полосу инвертированных пикселей высотой в пять рядов.

Мы получаем пиксельное значение и инвертируем его. Здесь у меня есть пара переменных. У меня есть переменная, которая отслеживает текущий ряд, и я начинаю с ряда 0. Также у меня есть максимальный ряд, который является тем же самым, что и высота canvas. Затем я получаю данные изображения, вызывая функцию ctx.getImageData, и мне нужны все данные изображения. Мне нужны данные, начиная с верхнего левого угла и заканчивая нижним правым. Таким образом я получаю все байты изображения. А затем из данных изображения, которые мне возвращаются, я получаю свойство data, потому что это массив байтов, и я сохраняю его в локальной переменной, которая называется pixels.

Теперь мне нужно пройти циклом по каждому ряду в изображении. Начинаю я с ряда 0. И пока текущий ряд будет меньше максимального ряда, я получаю полосу высотой в пять рядов. Вот у меня есть локальный счетчик i, который будет просчитывать каждые пять рядов. Помните, чтобы получить доступ к байтам каждого ряда, и именно это будет отслеживать данная переменная, мне нужно взять текущий ряд и добавить локальный счетчик рядов. Мы начинаем с 0 и идем 0, 1, 2, 3, 4, 5, а затем мы переходим к следующему ряду и так далее.

Чтобы получить RowBytes, мне нужен текущий ряд, умноженный на ширину canvas и умноженный на четыре, поскольку в каждом пикселе есть 4 байта. Таким способом я перемещаюсь к началу каждого ряда в изображении по массиву байтов. Как только я это сделаю, мне нужно пройти циклом по всем байтам в конкретном ряду. Мы начинаем с 0, затем мы переходим к ширине canvas, умноженной на 4, вот все нужные нам байты. И каждый раз мы инкрементируем j на 4, потому что нам нужно пропустить 4 байта каждого пикселя. Чтобы пропустить пиксель, нужно просто пропустить 4 байта.

При проходе по циклу мы говорим, что контент пикселя в thisRowBytes плюс мой индекс равен числу 255, потому что это максимальное значение любого байта, минус то, что сейчас здесь находится. Мы делаем это для красного, зеленого и синего компонентов каждого из байтов. С альфа каналом мы ничего не делаем, хотя мы и могли бы. Просто на данный момент мы этого делать не будем. Обычно мы будем инвертировать все значения пикселей.

После того как мы это сделаем, мы инкрементируем текущий ряд на 15, то есть, мы пропускаем следующие 15 рядов, и мы их полностью удаляем. После прохождения цикла у каждого 15ого ряда будет полоса инвертированных пикселей в пять рядов в высоту. Когда это будет сделано, мы просто вызываем putImageData, чтобы вставить данные изображения обратно в canvas. Давайте сохраним. Обновляем страницу. Вы видите, что здесь у вас есть пять рядов, где пиксели инвертированы от их изначальных цветов.

Вот исходное изображение, вот canvas, и наш цикл прошел по всем рядам и инвертировал каждый 15ый ряд по полоске в пять пикселей высотой. Мы могли бы здесь остановиться, но давайте еще кое-что рассмотрим. Давайте используем "грязный" прямоугольник, и вы же помните, что наши действия повлияют только на байты, которые находят внутри прямоугольника. Я пишу 50, 50, и давайте сделаем прямоугольник шириной в 400 пикселей.

Также он будет 200 пикселей высотой. Сохраняем, обновляем страничку. Вы видите, что только пиксели, находящиеся внутри прямоугольника, подверглись воздействию. В этом уроке мы рассмотрели, как получить доступ к данным изображения а canvas, манипулировать пикселями, вставлять их обратно, и опционально влиять на определенный диапазон пикселей в прямоугольнике.