How can I perform flood fill with HTML Canvas?

Asked
Active3 hr before
Viewed126 times

8 Answers

canvasperform
90%

To create a flood fill you need to be able to look at the pixels that are there already and check they aren't the color you started with so something like this.,As attached in the link below is a screenshot of my canvas (outer box is the canvas). The inner box is a grey box and the line is a line drawn in the canvas. How do I create a flood fill function that fills the entire canvas (except the inner grey box as well as the line) with a specific color?,There are other speedups like checking across a row at a time since rows are cache friendly and you can compute the offset to a row once and use that while checking the entire row where as now for every pixel we have to compute the offset multiple times. ,The solution to running out of stack space is to implement our own stack. For example instead of recursively calling fillPixel we could keep an array of positions we want to look at. We'd add the 4 positions to that array and then pop things off the array until it's empty

const ctx = document.querySelector("canvas").getContext("2d");

ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(250, 70);
ctx.lineTo(270, 120);
ctx.lineTo(170, 140);
ctx.lineTo(190, 80);
ctx.lineTo(100, 60);
ctx.lineTo(50, 130);
ctx.lineTo(20, 20);
ctx.stroke();

floodFill(ctx, 40, 50, [255, 0, 0, 255]);

function getPixel(imageData, x, y) {
   if (x < 0 || y < 0 || x >= imageData.width || y >= imageData.height) {
      return [-1, -1, -1, -1]; // impossible color
   } else {
      const offset = (y * imageData.width + x) * 4;
      return imageData.data.slice(offset, offset + 4);
   }
}

function setPixel(imageData, x, y, color) {
   const offset = (y * imageData.width + x) * 4;
   imageData.data[offset + 0] = color[0];
   imageData.data[offset + 1] = color[1];
   imageData.data[offset + 2] = color[2];
   imageData.data[offset + 3] = color[0];
}

function colorsMatch(a, b) {
   return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
}

function floodFill(ctx, x, y, fillColor) {
   // read the pixels in the canvas
   const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);

   // get the color we're filling
   const targetColor = getPixel(imageData, x, y);

   // check we are actually filling a different color
   if (!colorsMatch(targetColor, fillColor)) {

      fillPixel(imageData, x, y, targetColor, fillColor);

      // put the data back
      ctx.putImageData(imageData, 0, 0);
   }
}

function fillPixel(imageData, x, y, targetColor, fillColor) {
   const currentColor = getPixel(imageData, x, y);
   if (colorsMatch(currentColor, targetColor)) {
      setPixel(imageData, x, y, fillColor);
      fillPixel(imageData, x + 1, y, targetColor, fillColor);
      fillPixel(imageData, x - 1, y, targetColor, fillColor);
      fillPixel(imageData, x, y + 1, targetColor, fillColor);
      fillPixel(imageData, x, y - 1, targetColor, fillColor);
   }
}
<canvas></canvas>
load more v
88%

The next bit of code I adopted from William Malone’s code for a floodfill function:,Flood Fill is a common tool for drawing apps, but HTML Canvas doesn’t have a built-in flood fill function. And although there is a built-in function for drawing a straight line with lineTo, it’s a bit lacking for customization, so we’re going to go over how to make both a flood fill function as well as a line tool. I’ve built these functions to go along with an undo button infrastructure, so these functions can be used for more advanced drawing apps.,For a flood fill, we only need to store one point, the origin point, where the user clicked. So for flood fill, we only need to use the mousedown event to run the function, and the mouseup event to push the action to the undo stack.,Not only are we passing in the onscreen context now, but we have an additional argument, which is the scale difference between contexts. Let’s look at the line function:

For every mouse event, we use this code to first get the mouse coordinates as it relates to the offscreen canvas, since we’re clicking on the onscreen canvas:

let trueRatio = onScreenCVS.offsetWidth / offScreenCVS.width;
let mouseX = Math.floor(e.offsetX / trueRatio);
let mouseY = Math.floor(e.offsetY / trueRatio);
load more v
72%

My requirements are simple: flood with a single color starting from a single point, where the boundary color is any color greater than a certain delta of the color at the specified point. ,To create a flood fill you need to be able to look at the pixels that are there already and check they aren't the color you started with so something like this.,Here's an implementation that I've been working on. It can get really slow if the replacement color is too close to the original color. It's quite a bit faster in Chrome than Firefox (I haven't tested it in any other browsers).,Those will complicate the algorithm so they are best left for you to figure out.

My requirements are simple: flood with a single color starting from a single point, where the boundary color is any color greater than a certain delta of the color at the specified point.

var r1, r2; // red values
var g1, g2; // green values
var b1, b2; // blue values
var actualColorDelta = Math.sqrt((r1 - r2) * (r1 - r2) + (g1 - g2) * (g1 - g2) + (b1 - b2) * (b1 - b2))

function floodFill(canvas, x, y, fillColor, borderColorDelta) {
   ...
}

BTW, when I call this, I use a custom stopFunction:

  stopFill: function(fillColor, seedColor, pixelColor) {
     // Ignore alpha difference for now.
     return Math.abs(pixelColor.r - seedColor.r) > this.colorTolerance || Math.abs(pixelColor.g - seedColor.g) > this.colorTolerance || Math.abs(pixelColor.b - seedColor.b) > this.colorTolerance;
  },
load more v
65%

Live code collaboration

/echo simulates Async calls:
JSON: /echo/json/
JSONP: //jsfiddle.net/echo/jsonp/
HTML: /echo/html/
XML: /echo/xml/

/echo
load more v
75%

Canvasfill is a demo for a friend who wants to flood fill a clicked area in an HTML 5 canvas.,JavaScript loads a PNG image into the canvas when the page loads. ,The code uses a requestAnimationFrame polyfill from Paul Irish for efficient animations. ,This is not the most efficient approach to using the canvas, so if you need speed you’ll want to look at grabbing the entire array of pixels. With the current approach it is easier to “see” how the flood fill algorithm works since you can watch as pixels change colors in specific directions.

var img = new Image();
img.onload = function() {
   canvas.width = img.width;
   canvas.height = img.height;
   context.drawImage(this, 0, 0);
};
img.src = "thermometer_01.png";
load more v
40%

I have an HTML canvas object on which I am drawing with JavaScript. I’ve created a rough sketch of Kentucky like so:,Powered by Discourse, best viewed with JavaScript enabled,Canvas paints over the top of the existing content so the order is important.,You can fill that path before drawing the stroke.

I have an HTML canvas object on which I am drawing with JavaScript. I’ve created a rough sketch of Kentucky like so:

<html>

<head>
</head>

<body>
   <canvas id="mainCanvas" width="640" height="480" style="border:1px solid #000000;">
   </canvas>
   <script>
      var canvas = document.getElementById("mainCanvas");
      var context = canvas.getContext("2d");
      /* Draw Kentucky */
      context.fillStyle = "#00A800";
      context.fillRect(1, 16, 527, 440);
      context.fillStyle = "#00A800";
      context.strokeStyle = "#54FC54"; // Light Green, COLOR 10 in QB.
      context.fillStyle = "#00A800"; // Darker Green, COLOR 2 in QB.
      context.fill();
      context.stroke();
      context.moveTo(105, 190);
      context.lineTo(150, 190);
      context.lineTo(150, 185);
      context.lineTo(276, 185); // 290, 185
      context.moveTo(276, 185);
      context.lineTo(301, 175);
      context.lineTo(305, 160);
      context.lineTo(310, 155);
      context.lineTo(305, 145);
      context.lineTo(300, 125);
      context.lineTo(290, 110);
      context.lineTo(275, 95); // 1
      context.lineTo(270, 95); // 1
      context.lineTo(260, 100); // 1
      context.lineTo(250, 100); // 1
      context.lineTo(240, 90); // 1
      context.lineTo(235, 85); // 1
      context.lineTo(230, 85); // 1
      context.lineTo(220, 90); // 1
      context.lineTo(220, 100); // 1
      context.lineTo(210, 105); // 1
      context.lineTo(205, 115); // 1
      context.lineTo(195, 125); // 1
      context.lineTo(170, 130); // 1
      context.lineTo(165, 135); // 1
      context.lineTo(130, 140); // 1
      context.lineTo(120, 150); // 1
      context.lineTo(120, 160); // 1
      context.lineTo(115, 165); // 1
      context.lineTo(105, 170); // 1
      context.lineTo(105, 190); // 1
      context.strokeStyle = "#54FC54"; // Light Green, COLOR 10 in QB.
      context.stroke();
      /* End Kentucky */
   </script>
</body>

</html>
load more v
22%

In code, this might look like this:,Have a value close enough to the old value we would like to replace,In code, it might look like this:,Now that we have the ability to find all neighboring elements, we can proceed to traverse through those nodes in the most straightforward way: recursion.

function find_neighbors(canvas, loc::CartesianIndex, old_val, new_val)

# Finding north, south, east, west neighbors
possible_neighbors = [loc + CartesianIndex(0, 1),
   loc + CartesianIndex(1, 0),
   loc + CartesianIndex(0, -1),
   loc + CartesianIndex(-1, 0)
]

# Exclusing neighbors that should not be colored
neighbors = []
for possible_neighbor in possible_neighbors
if inbounds(size(canvas), possible_neighbor) &&
   canvas[possible_neighbor] == old_val
push!(neighbors, possible_neighbor)
end
end

return neighbors
end
load more v
60%

Given a 2D screen, location of a pixel in the screen and a color, replace color of the given pixel and all adjacent same colored pixels with the given color.,The following is the implementation of the above algorithm.  ,In MS-Paint, when we take the brush to a pixel and click, the color of the region of that pixel is replaced with a new selected color. Following is the problem statement to do this task. ,Below is the implementation of the above approach:  

Example: 

Input:
   screen[M][N] = {
      {
         1,
         1,
         1,
         1,
         1,
         1,
         1,
         1
      },
      {
         1,
         1,
         1,
         1,
         1,
         1,
         0,
         0
      },
      {
         1,
         0,
         0,
         1,
         1,
         0,
         1,
         1
      },
      {
         1,
         2,
         2,
         2,
         2,
         0,
         1,
         0
      },
      {
         1,
         1,
         1,
         2,
         2,
         0,
         1,
         0
      },
      {
         1,
         1,
         1,
         2,
         2,
         2,
         2,
         0
      },
      {
         1,
         1,
         1,
         1,
         1,
         2,
         1,
         1
      },
      {
         1,
         1,
         1,
         1,
         1,
         2,
         2,
         1
      },
   };
x = 4, y = 4, newColor = 3
The values in the given 2 D screen
indicate colors of the pixels.
x and y are coordinates of the brush,
   newColor is the color that
should replace the previous color on
screen[x][y] and all surrounding
pixels with same color.

Output:
   Screen should be changed to following.
screen[M][N] = {
   {
      1,
      1,
      1,
      1,
      1,
      1,
      1,
      1
   },
   {
      1,
      1,
      1,
      1,
      1,
      1,
      0,
      0
   },
   {
      1,
      0,
      0,
      1,
      1,
      0,
      1,
      1
   },
   {
      1,
      3,
      3,
      3,
      3,
      0,
      1,
      0
   },
   {
      1,
      1,
      1,
      3,
      3,
      0,
      1,
      0
   },
   {
      1,
      1,
      1,
      3,
      3,
      3,
      3,
      0
   },
   {
      1,
      1,
      1,
      1,
      1,
      3,
      1,
      1
   },
   {
      1,
      1,
      1,
      1,
      1,
      3,
      3,
      1
   },
};

// A recursive function to replace 
// previous color 'prevC' at  '(x, y)' 
// and all surrounding pixels of (x, y)
// with new color 'newC' and
floodFil(screen[M][N], x, y, prevC, newC)
1) If x or y is outside the screen, then
return.
2) If color of screen[x][y] is not same as prevC, then
return
3) Recur
for north, south, east and west.
floodFillUtil(screen, x + 1, y, prevC, newC);
floodFillUtil(screen, x - 1, y, prevC, newC);
floodFillUtil(screen, x, y + 1, prevC, newC);
floodFillUtil(screen, x, y - 1, prevC, newC);
Output
Updated screen after call to floodFill:
   1 1 1 1 1 1 1 1
1 1 1 1 1 1 0 0
1 0 0 1 1 0 1 1
1 3 3 3 3 0 1 0
1 1 1 3 3 0 1 0
1 1 1 3 3 3 3 0
1 1 1 1 1 3 1 1
1 1 1 1 1 3 3 1
Output
1 1 1 1 1 1 1 1
1 1 1 1 1 1 0 0
1 0 0 1 1 0 1 1
1 3 3 3 3 0 1 0
1 1 1 3 3 0 1 0
1 1 1 3 3 3 3 0
1 1 1 1 1 3 1 1
1 1 1 1 1 3 3 1
load more v

Other "canvas-perform" queries related to "How can I perform flood fill with HTML Canvas?"