Мы почти у цели! Эти последние детали являются необязательными: ограничение области рисования прямоугольника на холсте и использование остальной части холста для нашего графического пользовательского интерфейса (т.е. кнопок).
Let's mask the canvas so all drawing is within a the drawing area. Мы будем использовать методы clip
, save
и restore
. Internet Explorer поддерживает эти методы начиная с 9й версии.
Последнее усилие для перемещения всех этих кнопок на наш холст. Нам потребуется загрузка изображений и их отображение на основе нашего взаимодействия с пользователем. Я использовал стандартные методы JavaScript, поэтому я не буду утомлять вас подробностями (они входят в исходный код, если вам интересно).
Финальный результат
Теперь, когда мы закончили работать с HTML5 Canvas, можно немножко отдохнуть и пораскрашивать!
Вы можете скачать исходный код чтобы не набивать его с нуля.
Теперь я покажу вам как создать простой инструмент заливки, использовуя JavaScript HTML5 Canvas. Мы реализовуем алгоритм закрашивания с помощью объекта HTML5 ImageData пиксельных манипуляции.
Мы узнаем как определить цвета пикселя на холсте HTML5, после чего шаг за шагом пойдем к реализации алгоритма заливки в JavaScript. Давайте начнем.
The HTML5 Canvas Pixel
Прежде чем мы начнем закрашивание фона, мы должны знать цвета нашего изображения. Для этого мы можем использовать метод ImageData, чтобы получить данные, которые в себя цвет точки в RGB формате (каждый цвет представлен целым значением в диапазоне 0 и 255) и прозрачность (значение альфа канала).
Метод getImageData
принимает четыре параметра: местоположение (X, Y) и размер (ширина, высота). Код JavaScript выглядит следующим образом:
var imgData = context.getImageData(x, y, width, height);
Так, для изображения с красными пикселями будут следующие данные [255, 0, 0, 255]
.
var imgData = context.getImageData(0, 0, 1, 1);
// imageData.data[0] = [255,0,0,255]
imageData для четырехпиксельного изображения из красного, черного, белого и фиолетового цветов будет такой [255,0,0,255, 0,0,0,255, 255,255,255,255, 203,53,148,255]
.
var imgDataArray = context.getImageData(0, 0, 2, 2);
// imageData.data[0] = [255,0,0,255] /* красный */
// imageData.data[1] = [0,0,0,255] /* черный */
// imageData.data[2] = [255,255,255,255] /* белый */
// imageData.data[3] = [203,53,148,255] /* фиолетовый */
Изображение, которое мы будем использовать в этом уроке, занимает 3 пикселя в ширину и 5 пикселей в высоту. Оно состоит из 15 точек, - 13 белых [255,255,255,255]
и 2 черные [0,0,0,255]
. Черные пиксели находятся в позиции 3 и 7 (см. рисунок ниже).
var imageData = context.getImageData(0,0,canvasWidth,canvasHeight);
// imageData.data = [
// 255,255,255,255, 255,255,255,255, 0,0,0,255,
// 255,255,255,255, 255,255,255,255, 255,255,255,255,
// 0,0,0,255, 255,255,255,255, 255,255,255,255,
// 255,255,255,255, 255,255,255,255, 255,255,255,255,
// 255,255,255,255, 255,255,255,255, 255,255,255,255]
Preview Flood Fill Animation
Теперь, когда мы показали, как мы получим пиксельные данные из HTML5 Canvas с помощью метода ImageData мы реализуем алгоритм заливки с помощью JavaScript.
The following animation shows the steps of the flood fill algorithm on our example image:
Заливка шаг за шагом
Our objective is to fill of all similarly colored pixels connected to the starting pixel with a new fill color. В нашем примере начальная пиксель белый и цвет заливки фиолетовый
First push the starting pixel's x and y coordinate into an array called pixelStack
. Давайте начнем с пикселя в (1,3).
var pixelStack = ;
Далее мы создаем цикл while, который будет сохранять последнее местоположение пикселя в pixelStack
. In the case of our example, we pop the only coordinate (1,3) and assign those values to the variables x
и y
respectively.
while(pixelStack.length) {
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
...
}
Мы путешествуем вверх пиксель за пикселем, пока мы не найдем границы изображения или пиксель, который не соответствует цвету заливки. Мы используем цикл while и функцию matchStartColor
которая возвращает true
, если цвет соответствует начальному цвету.
pixelPos = (y*canvasWidth + x) * 4;
while(y-- >= 0 && matchStartColor(pixelPos)) {
pixelPos -= canvasWidth * 4;
}
...
}
function matchStartColor(pixelPos) {
var r = colorLayer.data[pixelPos];
var g = colorLayer.data[pixelPos+1];
var b = colorLayer.data[pixelPos+2];
return (r == startR && g == startG && b == startB);
}
Пройдя 3 пикселя мы доходим до края изображения. Мы знаем это, потому y
становится равным -1 и условие y-- >= 0
не выполняется. Так как мы зашли слишком далеко, мы увеличиваем y
и обновляем переменную pixelPos
. Вместо того, чтобы увеличивать pixelPos по одному, мы увеличиваем ее на ширину холста, которая равна 4. Мы инициализировать несколько новых переменных reachLeft
и reachRight
равных false
. Они нам пригодятся для управления добавлением в наш стек пикселей. Скоро вы это увидите.
Мы создаем другой цикл while, на этот раз для путешествия вниз. Unlike how we went up blindly, the way down will involve adding new pixels to the stack and coloring pixels. Let's start by coloring our first pixel at (1,0) with the fill color purple.
pixelPos += canvasWidth * 4;
++y;
reachLeft = false;
reachRight = false;
while(y++ < canvasHeight-1 && matchStartColor(pixelPos)) {
colorPixel(pixelPos);
...
}
After filling the pixel at (1,0) we must now determine if its neighbors to the left and right need filling too. Прежде всего, давайте исследуем пиксель слева.
Игнорируя любые пикселей с координатой х
меньше нуля, мы проверяем, если соответствует ли цвет пикселя начальному цвету. В нашем примере пиксель слева белый, как наш стартовый пиксель, поэтому мы добавляем его в стек для последующей обработки. Мы также устанавливаем значение переменной reachLeft
равной true
This will prevent us adding pixels that will eventually handled by the downward march of the pixel we just added.
if(x > 0) {
if(matchStartColor(pixelPos - 4)) {
if(!reachLeft){
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if(reachLeft) {
reachLeft = false;
}
}
Теперь переходим к пикселю справа. Его цвет черный, а черный не соответствует начальному цвету пикселя, поэтому мы не добавляем его в стек. Продолжаем двигаться вниз.
if(x < canvasWidth-1) {
if(matchStartColor(pixelPos + 4)) {
if(!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if(reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
Пиксель ниже имеет такой же цвет, как наш начальный пиксель, поэтому мы покрасим его в фиолетовый цвет.
We look to the left of our new purple pixel and find it matches, but reachLeft
is true so we don't add it to the stack. The right pixel also matches but its variable reachRight
is false so we do add it to the stack.
Color the next pixel. Look to the left, no match this time so we change reachLeft
to false. Looking to the right matches but reachRight
is true so we do nothing.
Color the next pixel. It happens to be the starting pixel but other than for reasons of nostalgia it's not significant.
We look to the left and the color matches. Since we changed reachLeft
to false last time, we add it to the stack. The right side matches too, but its boolean reachRight
is true so we don't add it.
Color the pixel below. The booleans reachLeft
and reachRight
to the right and left are true so we don't add them to the stack.
We have reached the bottom of the image thus are done with the column of our starting pixel.
Очистка стека
The first column has been filled however they are still pixels in the stack which means there are more columns to fill. Pop the next pixel from the stack which is the pixel (0,3).
Мы продолжаем процесс до тех пор стек с пикселями, не станет пустым, а значит нечего будет закрашивать.
Eight pixels later we have filled all the matching pixels in our example image. Хотя наше тестовое изображение и было небольшым - процесс закрашивания области одинаков и для больших изображений.
HTML5 JS Paint Bucket Code
Несколько замечаний о коде:
- Internet Explorer 8 не поддерживает HTML5-манипуляции c пикселями даже при использовании ExplorerCanvas
- Firefox не поддерживают пиксельные манипуляции локально. Код будет работать на сервере, но, при отладке страницы локально, Firebug выдаст сообщение "Ошибка безопасности Код: 1000" (Security error code: 1000).
pixelStack = ;
while(pixelStack.length) {
var newPos, x, y, pixelPos, reachLeft, reachRight;
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
pixelPos = (y*canvasWidth + x) * 4;
while(y-- >= drawingBoundTop && matchStartColor(pixelPos)) {
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
++y;
reachLeft = false;
reachRight = false;
while(y++ < canvasHeight-1 && matchStartColor(pixelPos)) {
colorPixel(pixelPos);
if(x > 0) {
if(matchStartColor(pixelPos - 4)) {
if(!reachLeft) {
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if(reachLeft) {
reachLeft = false;
}
}
if(x < canvasWidth-1) {
if(matchStartColor(pixelPos + 4)) {
if(!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if(reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
context.putImageData(colorLayer, 0, 0);
function matchStartColor(pixelPos) {
var r = colorLayer.data[pixelPos];
var g = colorLayer.data[pixelPos+1];
var b = colorLayer.data[pixelPos+2];
return (r == startR && g == startG && b == startB);
}
function colorPixel(pixelPos) {
colorLayer.data[pixelPos] = fillColorR;
colorLayer.data[pixelPos+1] = fillColorG;
colorLayer.data[pixelPos+2] = fillColorB;
colorLayer.data[pixelPos+3] = 255;
}
Вот мы и сделали все, что хотели. Попробуйте теперь порисовать в html5 Canvas. Как видите, это было не сложно. Скачайте код для раскрашивания картинки в Canvas и порадуйте своих посетителей необычным контентом.