Create A Paint Bucket Tool in HTML5 and JavaScript
Create A Paint Bucket Tool in HTML5 and JavaScript
by William Malone
This tutorial will show how to create a simple paint bucket tool on an HTML5 Canvas using JavaScript. We will be implementing a flood fill algorithm using the HTML5 imageData object for pixel manipulation. First we will learn how to determine the colors of the pixel on an HTML5 canvas. Then we will go step by step through an implementation of a flood fill algorithm in JavaScript. Let's get started.
var imgDataArray = context.getImageData(0, 0, 2, 2); // /* // /* // /* // /* imageData.data[0] red */ imageData.data[1] black */ imageData.data[2] white */ imageData.data[3] purple */ = [255,0,0,255] = [0,0,0,255] = [255,255,255,255] = [203,53,148,255]
The example image we will use throughout this article is 3 pixels wide and 5 pixels high. It consists of 15 pixels, 13 white [255,255,255,255] and 2 black[0,0,0,255]. The black pixels are in positions 3 and 7.
The getImageData method has four parameters: location (x,y) and dimension (width, height). The JavaScript code looks like this:
var imageData = context.getImageData(0,0,canvasWidth,can vasHeight); // imageData.data = [ // 255,255,255,255, 0,0,0,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, 255,255,255,255, 255,255,255,255,
Now that we have demonstrated how we will get pixel data from the HTML5 Canvas via the imageData object we will go step by step through the process of a flood fill algorithm implemented with JavaScript. The following animation shows the steps of the flood fill algorithm on our example image:
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); }
After travelling up 3 pixels we encounter the edge of the image. We know this because ybecomes -1 the condition y-- >= 0 fails. Since we have gone too far up, we increment yand update the variable pixelPos. Instead of just incrementing the pixelPos by one, we increment by the canvas width times 4. We initalize a couple of new variables reachLeft and reachRight to false. These will be used to manage additions to our pixel stack. We will see how soon. We create another while loop, this time travelling downward. 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.
while(pixelStack.length) {
Now we look to the right. The pixel on the right is black which does not match the starting pixel so we do not add it to the stack. We continue the march downward.
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. First let's investigate the pixel to the left. Ignoring any pixels with x position less than zero we check if the color matches the start color. In our example the pixel to the left is white just like our start pixel, so we add it to the stack to handle later. We also set the boolean reachLeft to true. This will prevent us adding pixels that will eventually handled by the downward march of the pixel we just added.
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;
The pixel below matches our starting pixel so we color it purple.
if(x > 0) { if(matchStartColor(pixelPos - 4)) { if(!reachLeft){ pixelStack.push([x - 1, y]); reachLeft = true; } } else if(reachLeft) { reachLeft = false; } }
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 pixel below. The booleans reachLeft and reachRight to the right and left are true so we don't add them 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. We have reached the bottom of the image thus are done with the column of our starting pixel.
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.
We continue the process until the pixel stack is empty and there are no more columns to fill.
Eight pixels later we have filled all the matching pixels in our example image. Although our example image was small the process is the same for larger images.
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];
Internet Explorer does not support HTML5 pixel manipulation even with use ofExplorerCanvas Firefox does not support pixel manipulation locally. It will work when hosted on a site, but if you try to test locally you will what Firebug describes as 'Security error" code: "1000'. The best explanation for this error was described by skarabaeus here.
pixelStack = [[startX, startY]]; 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) {
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; }
Source: http://www.williammalone.com/articles/html5canvas-javascript-paint-bucket-tool/