fabric.js: Flood Fill
This Fiddle implements a flood fill for fabric.js. The flood filled area is added as a fabric-js object to the canvas, which can then be moved around.
by Anand Rathod
HTML
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<p class="intro">
Click <i>Flood fill</i>, then click anywhere in the image to flood fill the area under the cursor.<br>
Click <i>Default</i> to get default fabric.js behavior: you can select the flood-filled area as an (image) object now.<br>
You can (optionally) change fill color and fill tolerance.
</p>
<table cellpadding="6">
<tr>
<td>Mode</td>
<td>
<input type="radio" name="mode" value="fabric" id="mode1" checked> <label for="mode1">Default</label>
<input type="radio" name="mode" value="floodFill" id="mode2"> <label for="mode2">Flood fill</label>
</td>
</tr>
<tr>
<td>Color</td>
<td>
<div class="color selected" data-color="#f00"></div>
<div class="color" data-color="#0f0"></div>
<div class="color" data-color="#00f"></div>
<div class="color" data-color="#000"></div>
<div class="color" data-color="#fff"></div>
<div class="color" data-color="#0ff"></div>
<div class="color" data-color="#f0f"></div>
<div class="color" data-color="#ff0"></div>
</td>
</tr>
<tr>
<td>Tolerance</td>
<td><input type="range" id="tolerance" min="0" max="50" value="2" step="1"></td>
</tr>
<tr>
<td></td>
<td>
<button type="button" id="removeObject">Remove object</button>
</td>
</tr>
</table>
<div id="canvasContainer">
<canvas id="c" width="600" height="400"></canvas>
</div>
CSS
body, input, button {
font: 12pt Arial, Helvetica;
}
#canvasContainer {
background-image: url(data:image/gif;base64,R0lGODlhCgAKAIAAAOLi4v///yH5BAAHAP8ALAAAAAAKAAoAAAIRhB2ZhxoM3GMSykqd1VltzxQAOw==);
border: 2px solid #ccc;
display: inline-block;
}
.intro {
background-color: #ddd;
border: 1px solid: #333;
padding: 5px;
}
input[type=range], input[type=text] {
width: 200px;
}
input[type=radio] {
transform: scale(1.25);
}
div.color {
display: inline-block;
width: 16px;
height: 16px;
padding: 1px;
border: 1px solid #000;
cursor: pointer;
}
div.color.selected {
padding: 0;
border: 2px solid #000;
}
JavaScript
/*
* FloodFill for fabric.js
* @author Arjan Haverkamp (av01d)
* @date October 2018
*/
var FloodFill = {
// Compare subsection of array1's values to array2's values, with an optional tolerance
withinTolerance: function(array1, offset, array2, tolerance)
{
var length = array2.length,
start = offset + length;
tolerance = tolerance || 0;
// Iterate (in reverse) the items being compared in each array, checking their values are
// within tolerance of each other
while(start-- && length--) {
if(Math.abs(array1[start] - array2[length]) > tolerance) {
return false;
}
}
return true;
},
// The actual flood fill implementation
fill: function(imageData, getPointOffsetFn, point, color, target, tolerance, width, height)
{
var directions = [[1, 0], [0, 1], [0, -1], [-1, 0]],
coords = [],
points = [point],
seen = {},
key,
x,
y,
offset,
i,
x2,
y2,
minX = -1,
maxX = -1,
minY = -1,
maxY = -1;
// Keep going while we have points to walk
while (!!(point = points.pop())) {
x = point.x;
y = point.y;
offset = getPointOffsetFn(x, y);
// Move to next point if this pixel isn't within tolerance of the color being filled
if (!FloodFill.withinTolerance(imageData, offset, target, tolerance)) {
continue;
}
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
if (x < minX || minX == -1) { minX = x; }
if (y < minY || minY == -1) { minY = y; }
// Update the pixel to the fill color and add neighbours onto stack to traverse
// the fill area
i = directions.length;
while (i--) {
// Use the same loop for setting RGBA as for checking the neighbouring pixels
if (i < 4) {
imageData[offset + i] = color[i];
coords[offset+i] = color[i];
}
// Get the new coordinate by adjusting x and y based on current step
x2 = x + directions[i][0];
y2 = y + directions[i][1];
key = x2 + ',' + y2;
// If new coordinate is out of bounds, or...