可以使用洪水填充到由用戶定義的線爲邊界的顏色點擊區域。
讓用戶在畫布上畫線。
當用戶單擊由線限定的區域時,使用顏色填充該區域。
注意:您必須繪製網格線在畫布下方,否則這些網格線將作爲邊界此時,floodFill算法,您將只需填寫一個網格單元。您可以使用CSS在您的畫布下對圖像進行分層或使用單獨的畫布繪製網格線。
這裏的起始示例代碼和一個演示:http://jsfiddle.net/m1erickson/aY4Xs/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas and mousedown related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
// save canvas size to vars b/ they're used often
var canvasWidth=canvas.width;
var canvasHeight=canvas.height;
// define the grid area
// lines can extend beyond grid but
// floodfill wont happen outside beyond the grid
var gridRect={x:50,y:50,width:200,height:200}
drawGridAndLines();
// draw some test gridlines
function drawGridAndLines(){
ctx.clearRect(0,0,canvas.width,canvas.height)
// Important: the lineWidth must be at least 5
// or the floodfill algorithm will "jump" over lines
ctx.lineWidth=5;
ctx.strokeRect(gridRect.x,gridRect.y,gridRect.width,gridRect.height);
ctx.beginPath();
ctx.moveTo(75,25);
ctx.lineTo(175,275);
ctx.moveTo(25,100);
ctx.lineTo(275,175);
ctx.stroke();
}
// save the original (unfilled) canvas
// so we can reference where the black bounding lines are
var strokeData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// fillData contains the floodfilled canvas data
var fillData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
// Thank you William Malone for this great floodFill algorithm!
// http://www.williammalone.com/articles/html5-canvas-javascript-paint-bucket-tool/
//////////////////////////////////////////////
function floodFill(startX, startY, startR, startG, startB) {
var newPos;
var x;
var y;
var pixelPos;
var neighborLeft;
var neighborRight;
var pixelStack = [[startX, startY]];
while (pixelStack.length) {
newPos = pixelStack.pop();
x = newPos[0];
y = newPos[1];
// Get current pixel position
pixelPos = (y * canvasWidth + x) * 4;
// Go up as long as the color matches and are inside the canvas
while (y >= 0 && matchStartColor(pixelPos, startR, startG, startB)) {
y -= 1;
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
y += 1;
neighborLeft = false;
neighborRight = false;
// Go down as long as the color matches and in inside the canvas
while (y <= (canvasHeight-1) && matchStartColor(pixelPos, startR, startG, startB)) {
y += 1;
fillData.data[pixelPos] = fillColor.r;
fillData.data[pixelPos + 1] = fillColor.g;
fillData.data[pixelPos + 2] = fillColor.b;
fillData.data[pixelPos + 3] = 255;
if (x > 0) {
if (matchStartColor(pixelPos - 4, startR, startG, startB)) {
if (!neighborLeft) {
// Add pixel to stack
pixelStack.push([x - 1, y]);
neighborLeft = true;
}
} else if (neighborLeft) {
neighborLeft = false;
}
}
if (x < (canvasWidth-1)) {
if (matchStartColor(pixelPos + 4, startR, startG, startB)) {
if (!neighborRight) {
// Add pixel to stack
pixelStack.push([x + 1, y]);
neighborRight = true;
}
} else if (neighborRight) {
neighborRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
}
function matchStartColor(pixelPos, startR, startG, startB) {
// get the color to be matched
var r = strokeData.data[pixelPos],
g = strokeData.data[pixelPos + 1],
b = strokeData.data[pixelPos + 2],
a = strokeData.data[pixelPos + 3];
// If current pixel of the outline image is black-ish
if (matchstrokeColor(r, g, b, a)) {
return false;
}
// get the potential replacement color
r = fillData.data[pixelPos];
g = fillData.data[pixelPos + 1];
b = fillData.data[pixelPos + 2];
// If the current pixel matches the clicked color
if (r === startR && g === startG && b === startB) {
return true;
}
// If current pixel matches the new color
if (r === fillColor.r && g === fillColor.g && b === fillColor.b) {
return false;
}
return true;
}
function matchstrokeColor(r, g, b, a) {
// never recolor the initial black divider strokes
// must check for near black because of anti-aliasing
return (r + g + b < 100 && a === 255);
}
// Start a floodfill
// 1. Get the color under the mouseclick
// 2. Replace all of that color with the new color
// 3. But respect bounding areas! Replace only contiguous color.
function paintAt(startX, startY) {
// get the clicked pixel's [r,g,b,a] color data
var pixelPos = (startY * canvasWidth + startX) * 4,
r = fillData.data[pixelPos],
g = fillData.data[pixelPos + 1],
b = fillData.data[pixelPos + 2],
a = fillData.data[pixelPos + 3];
// this pixel's already filled
if (r === fillColor.r && g === fillColor.g && b === fillColor.b) {
return;
}
// this pixel is part of the original black image--don't fill
if (matchstrokeColor(r, g, b, a)) {
return;
}
// execute the floodfill
floodFill(startX, startY, r, g, b);
// put the colorized data back on the canvas
ctx.putImageData(fillData, 0, 0);
}
// end floodFill algorithm
//////////////////////////////////////////////
// get the pixel colors under x,y
function getColors(x,y){
var data=ctx.getImageData(x,y,1,1).data;
return({r:data[0], g:data[1], b:data[2], a:data[3] });
}
// create a random color object {red,green,blue}
function randomColorRGB(){
var hex=Math.floor(Math.random()*16777215).toString(16);
var r=parseInt(hex.substring(0,2),16);
var g=parseInt(hex.substring(2,4),16);
var b=parseInt(hex.substring(4,6),16);
return({r:r,g:g,b:b});
}
function handleMouseDown(e){
e.preventDefault();
// get the mouse position
x=parseInt(e.clientX-offsetX);
y=parseInt(e.clientY-offsetY);
// don't floodfill outside the gridRect
if(
x<gridRect.x+5 ||
x>gridRect.x+gridRect.width ||
y<gridRect.y+5 ||
y>gridRect.y+gridRect.height
){return;}
// get the pixel color under the mouse
var px=getColors(x,y);
// get a random color to fill the region with
fillColor=randomColorRGB();
// floodfill the region bounded by black lines
paintAt(x,y,px.r,px.g,px.b);
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
}); // end $(function(){});
</script>
</head>
<body>
<h4>Click in a region within the grid square.</h4>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
[關於getImageData信息和像素陣列]
context.getImageData().data
獲取表示R,G,B的陣列&畫布指定區域的值(在本例中,我們選擇了整個畫布)。左上像素(0,0)是數組中的第一個元素。
每個像素由數組中的4個順序元素表示。
第一個數組元素包含紅色成分(0-255),下一個元素包含藍色,下一個包含藍色,下一個包含綠色,下一個包含alpha(不透明度)。通過獲取未來4
// pixelPos is the position in the array of the first of 4 elements for pixel (mouseX,mouseY)
var pixelPos = (mouseY * canvasWidth + mouseX) * 4
,你可以得到所有4個R,G,B,A值:
// pixel 0,0
red00=data[0];
green00=data[1];
blue00=data[2];
alpha00=data[3];
// pixel 1,0
red10=data[4];
green10=data[5];
blue10=data[6];
alpha10=data[7];
因此,你跳轉到任何像素的紅色元素的鼠標下這樣像素數組元素
var r = fillData.data[pixelPos];
var g = fillData.data[pixelPos + 1];
var b = fillData.data[pixelPos + 2];
var a = fillData.data[pixelPos + 3];
因此,您希望點的區域填充顏色而不是需要一組形成區域的線條?我問,因爲第一個是「簡單」的填充,第二個是數學冒險;-) – markE
@markE - 簡單的洪水:) - 但如何檢測多邊形的邊界,然後使用它們來創建填充? – mtyson
@markE我不認爲有畫布對象的內置填充。有一些庫和StackOverflow的答案。 –