//Editable Vars
let cols = 35;
let rows = 35;
let fps = 5;
//Declarations
let canvas;
let ctx;
let background;
let grid = new Array(cols);
let w;
let h;
let pathfinder;
let target;
let timer;
let renderQueue = [];
let canPathfind = true;
//Space Class
class Space{
constructor(x,y,c='lightgrey'){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
}
draw(){
ctx.fillStyle = this.c;
ctx.strokeStyle = 'black';
ctx.fillRect(this.x * w, this.y * h, this.w, this.h);
ctx.rect(this.x * w, this.y * h, this.w, this.h);
ctx.stroke();
}
move(x,y){
if(x < 0 || x > cols-1 || y < 0|| y > rows-1){
return;
}
grid[this.x][this.y] = new Space(this.x,this.y);
renderQueue.push(grid[this.x][this.y]);
this.x = x;
this.y = y;
grid[this.x][this.y] = this;
renderQueue.push(grid[this.x][this.y]);
}
}
//Game-Run code
function gameStart(){
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
w = canvas.width / cols;
h = canvas.height / rows;
createGrid();
pathfinder = new Space(randomInt(cols-1),randomInt(rows-1),'green');
grid[pathfinder.x][pathfinder.y] = pathfinder;
target = new Space(randomInt(cols-1),randomInt(rows-1),'red');
grid[target.x][target.y] = target;
drawGrid();
timer = setInterval(updateScreen, 1000/fps);
}
function restartGame(){
clearInterval(timer);
gameStart();
}
//Starts loading process on windows load
window.onload = gameStart();
//Checks all 8 possible move directions and calls pathfinder.move for best move
function pathfind(pathfinder, target){
if(!canPathfind) return;
let p = {x: pathfinder.x, y: pathfinder.y};
let t = {x: target.x, y: target.y};
let move = {x : 0, y : 0};
// 0,0 9,9
//if(p.x == t.x && p.y == t.y){
//restartGame();
//}
if(t.x - p.x >= 1){
move.x = 1;
}else if(t.x - p.x <= -1){
move.x = -1;
}else{
move.x = 0;
}
if(t.y - p.y >= 1){
move.y = 1;
}else if(t.y - p.y <= -1){
move.y = -1;
}else{
move.y = 0;
}
pathfinder.move(pathfinder.x + move.x, pathfinder.y + move.y);
}
function updateScreen(){
pathfind(pathfinder,target);
drawUpdatedSpaces();
}
function drawUpdatedSpaces(){
for(let i = 0; i < renderQueue.length; i++){
renderQueue[i].draw();
}
renderQueue = [];
}
function drawGrid(){
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j].draw();
}
}
}
//Creates grid and instantiates Space in every cell
function createGrid(){
for(let i = 0; i < grid.length; i++){
grid[i] = new Array(rows);
}
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j] = new Space(i,j);
}
}
}
// Returns distance to target from specified coords
function distanceFromTarget(x, y) {
return (Math.sqrt(Math.pow(Math.abs(x - target.x), 2) + (Math.pow(Math.abs(y - target.y), 2))));
}
// Returns random Integer between 0 and Max
function randomInt(max) {
return Math.floor(Math.random() * max);
}
It runs as expected which is great, but performance is super slow. That may be because I'm using jsfiddle to work on this while away from my personal PC setup, but is there a way to make this perform better? As of right now I can't move the grid size to >50 without it running extremely slow. I would love to eventually move to a 4k x 4k grid and create an auto-generating maze for the 'pathfinder' to pathfind through.
Thoughts/things I'm considering for performance:
Using HTML grid and updating via CSS instead of HTML5 Canvas
Only re-drawing cells that have changed (Implemented in the Space.move() function with array renderQueue)
literally re-doing everything in python :D
I have made a grid and need to check whether a cell has a or several bombs around it, but I'm a bit confused on how to do it now. I have tried this code;
function placeNumbers() {
for (let x = -ColumnRow; x < ColumnRow * 3; x += ColumnRow) {
for (let y = -ColumnRow; y < ColumnRow * 3; y += ColumnRow) {
if (cells[x].bomb == false) {
//do something
}
}
}
}
but then it says that I can't use .bomb. What can I do? I have made a class Cell which has a bomb feature and if a bomb is on the cell then the cell should have bomb = true. So then i have to check wether the neighbour of a cell does has the bomb true or false?
Does anyone have any tips or know what to do here?
Here's a sample of the code:
const canvas = document.getElementById("myCanvas")
const ctx = canvas.getContext("2d")
class Cell {
constructor(x, y, w) {
this.x = x
this.y = y
this.w = w
this.bomb = false
this.revealed = false
}
show() {
const cell = new Path2D();
cell.rect(this.x, this.y, this.w, this.w);
ctx.stroke(cell);
this.cell = cell;
}
}
const w = canvas.width
const h = canvas.height
const ColumnRow = w / 15
const cells = []
const bombs = 10
let checked = true
let bombPosition = []
function setup() {
for (let x = 0; x < w - 1; x += ColumnRow) {
for (let y = 0; y < h - 1; y += ColumnRow) {
cells.push(new Cell(x, y, ColumnRow))
}
}
}
function drawCells() {
for (let c of cells) {
c.show()
}
}
function numOfBombs() {
for (let i = 0; i < bombs; i++) {
randomX = Math.floor(Math.random() * w / ColumnRow) * ColumnRow
randomY = Math.floor(Math.random() * h / ColumnRow) * ColumnRow
bombPosition.push({ x: randomX, y: randomY });
}
}
function drawBomb() {
let img = new Image();
img.onload = function () {
for (let i = 0; i < bombPosition.length; i++) {
ctx.drawImage(img, bombPosition[i].x, bombPosition[i].y, ColumnRow, ColumnRow)
}
};
img.src = "https://raw.githubusercontent.com/americosp/Minesweeper/master/Minesweeper/images/mine.png";
}
function bombCollision(cell) {
for (let i = 0; i < bombPosition.length; i++) {
if (cell.x == bombPosition[i].x && cell.y == bombPosition[i].y) {
console.log("same position");
}
}
}
canvas.addEventListener('click', function (e) {
for (const cell of cells) {
if (ctx.isPointInPath(cell.cell, e.offsetX, e.offsetY)) {
ctx.clearRect(cell.x, cell.y, cell.w, cell.w);
checked = true
cell.revealed = true
bombCollision(cell)
} else {
/* ctx.clearRect(cell.x, cell.y, cell.w, cell.w); */
}
}
});
function update() {
drawCells()
if (checked) {
drawBomb()
// coverCell()
checked = false
}
requestAnimationFrame(update)
}
function update2() {
numOfBombs()
setup()
}
update2()
update()
<canvas id="myCanvas" width="600" height="600"></canvas>
I am trying to find an algorithm of how to draw a simple (no lines are allowed to intersect), irregular polygon.
The number of sides should be defined by the user, n>3.
Here is an intial code which only draws a complex polygon (lines intersect):
var ctx = document.getElementById('drawpolygon').getContext('2d');
var sides = 10;
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(0, 0);
for(var i=0;i<sides;i++)
{
var x = getRandomInt(0, 100);
var y = getRandomInt(0, 100);
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
// https://stackoverflow.com/a/1527820/1066234
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
JSFiddle: https://jsfiddle.net/kai_noack/op2La1jy/6/
I do not have any idea how to determine the next point for the connecting line, so that it does not cut any other line.
Further, the last point must close the polygon.
Here is an example of how one of the resulting polygons could look like:
Edit: I thought today that one possible algorithm would be to arrange the polygon points regular (for instance as an rectangle) and then reposition them in x-y-directions to a random amount, while checking that the generated lines are not cut.
I ported this solution to Javascript 1 to 1. Code doesn't look optimal but produces random convex(but still irregular) polygon.
//shuffle array in place
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
/** Based on Sander Verdonschot java implementation **/
class Point {
constructor(x, y) {
this.x = x;
this.y = y
}
}
function generateRandomNumbersArray(len) {
const result = new Array(len);
for (let i = 0; i < len; ++i) {
result[i] = Math.random();
}
return result;
}
function generateRandomConvexPolygon(vertexNumber) {
const xPool = generateRandomNumbersArray(vertexNumber);
const yPool = generateRandomNumbersArray(vertexNumber);
// debugger;
xPool.sort();
yPool.sort();
const minX = xPool[0];
const maxX = xPool[xPool.length - 1];
const minY = yPool[0];
const maxY = yPool[yPool.length - 1];
const xVec = []
const yVec = [];
let lastTop = minX;
let lastBot = minX;
xPool.forEach(x => {
if (Math.random() >= 0.5) {
xVec.push(x - lastTop);
lastTop = x;
} else {
xVec.push(lastBot - x);
lastBot = x;
}
});
xVec.push(maxX - lastTop);
xVec.push(lastBot - maxX);
let lastLeft = minY;
let lastRight = minY;
yPool.forEach(y => {
if (Math.random() >= 0.5) {
yVec.push(y - lastLeft);
lastLeft = y;
} else {
yVec.push(lastRight - y);
lastRight = y;
}
});
yVec.push(maxY - lastLeft);
yVec.push(lastRight - maxY);
shuffle(yVec);
vectors = [];
for (let i = 0; i < vertexNumber; ++i) {
vectors.push(new Point(xVec[i], yVec[i]));
}
vectors.sort((v1, v2) => {
if (Math.atan2(v1.y, v1.x) > Math.atan2(v2.y, v2.x)) {
return 1;
} else {
return -1;
}
});
let x = 0, y = 0;
let minPolygonX = 0;
let minPolygonY = 0;
let points = [];
for (let i = 0; i < vertexNumber; ++i) {
points.push(new Point(x, y));
x += vectors[i].x;
y += vectors[i].y;
minPolygonX = Math.min(minPolygonX, x);
minPolygonY = Math.min(minPolygonY, y);
}
// Move the polygon to the original min and max coordinates
let xShift = minX - minPolygonX;
let yShift = minY - minPolygonY;
for (let i = 0; i < vertexNumber; i++) {
const p = points[i];
points[i] = new Point(p.x + xShift, p.y + yShift);
}
return points;
}
function draw() {
const vertices = 10;
const _points = generateRandomConvexPolygon(vertices);
//apply scale
const points = _points.map(p => new Point(p.x * 300, p.y * 300));
const ctx = document.getElementById('drawpolygon').getContext('2d');
ctx.fillStyle = '#f00';
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i = 1; i < vertices ; ++i)
{
let x = points[i].x;
let y = points[i].y;
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
}
draw();
<canvas id="drawpolygon"></canvas>
You could generate random points and then connect them with an approximate traveling salesman tour. Any tour that cannot be improved by 2-opt moves will not have edge crossings.
If it doesn't need to be random, here's a fast irregular n-point polygon:
Points are:
(0,0)
((i mod 2)+1, i) for 0 <= i <= n-2
Lines are between (0,0) and the two extreme points from the second row, as well as points generated by adjacent values of i.
I am trying to make Conway's Game of Life in JavaScript, but cannot find what is wrong with a specific function after hours of debugging.
The program works by making a 2d array based of global "row" and "column" variables, and then pushes "Square" objects to each space in the array. The program then sets an interval for the "draw" function for a specific interval, (the global variable "rate").
I apologize if this is hard to understand, but basically, every specific time interval, like every 1000 milliseconds, the program checks every "Square" object in the array, updates the amount of neighbors it has, and then draws it on the screen.
This is where I am stuck; the update function is supposed to check all 8 neighbors that a square has, (or 3-5 if it is an edge square) but it will only check 4 neighbors. No matter what I do, if I click a square to populate it, only the top, top left, top right, and left neighbors of the now populated square will register that their neighbor has become populated.
Other than this bug the code is working fine, and i'm 99% sure the problem is in this one function, because the code will still function as a cellular automata currently, just not as Conway's Game of Life.
var canvas = document.getElementById("life");
var ctx = canvas.getContext('2d');
var timer;
var rate = 1000;
var rows = 20;
var columns = 20;
var width = 20;
var clickX;
var clickY;
var board;
var running = false;
var checkArray = [
[-1, 0, 1, 1, 1, 0, -1, -1],
[-1, -1, -1, 0, 1, 1, 1, 0]
];
var visuals = true;
var gridColor = "#000000";
/*
live cell with < 2 neighbors = death
live cell with 2 or 3 neighbors = live for 1 generation
live cell with > 4 neighbors = death
dead cell with 3 neighbors = live for 1 generation
0 = death
1 = death
2 = continues life if alive
3 = continues life if alive OR brings to life if dead
4 = death
5 = death
6 = death
7 = death
8 = death
*/
window.onload = function() {
makeBoard();
var timer = setInterval(draw, rate);
window.addEventListener("mousedown", clickHandler);
// for(var i = 0; i < 8; i++){
// var checkIndexX = checkArray[0][i];
// var checkIndexY = checkArray[1][i];
// console.log(checkIndexX, checkIndexY);
// }
}
function makeBoard() {
board = new Array(columns);
for (var i = 0; i < rows; i++) {
var intRow = new Array(rows);
for (var j = 0; j < columns; j++) {
intRow[j] = new Square(false, j, i);
}
board[i] = intRow;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var y = 0; y < rows; y++) {
for (var x = 0; x < columns; x++) {
if (running) {
board[y][x].update();
}
board[y][x].draw();
if (visuals) {
board[y][x].visuals();
}
}
}
drawRunButton();
}
function Square(alive, PARX, PARY) {
this.alive = false;
this.X = PARX;
this.Y = PARY;
this.neighbors = 0;
this.update = function() {
this.neighbors = 0;
for (var i = 0; i < 8; i++) {
var checkIndexX = checkArray[0][i];
var checkIndexY = checkArray[1][i];
if ((this.X + checkIndexX) >= 0 && (this.X + checkIndexX) < columns &&
(this.Y + checkIndexY) >= 0 && (this.Y + checkIndexY) < rows) {
var check = board[this.Y + checkIndexY][this.X + checkIndexX];
// console.log(this.X, this.Y, check.X, check.Y, checkIndexX, checkIndexY);
if (check.alive) {
this.neighbors++;
}
}
}
if (this.alive) {
if (this.neighbors < 2 || this.neighbors > 3) {
this.alive = false;
}
} else {
if (this.neighbors == 3) {
this.alive = true;
}
}
};
this.visuals = function() {
drawVisuals(this.neighbors, this.X * width, this.Y * width);
};
this.draw = function(alive) {
drawSquare(this.alive, this.X * width, this.Y * width, width);
}
}
function clickHandler(e) {
var clickX = e.screenX - 68;
var clickY = e.screenY - 112;
mapClick(clickX, clickY);
manageRun(clickX, clickY);
}
function mapClick(x, y) {
var indexX = Math.floor(x / width);
var indexY = Math.floor(y / width);
if (indexX >= 0 && indexX < columns && indexY >= 0 && indexY < rows) {
board[indexY][indexX].alive = true;
}
}
function manageRun(x, y) {
if (x >= (columns * width) + 5 && x <= (columns * width) + 45 && y >= 5 && y <= 45) {
if (running) {
running = false;
} else {
running = true;
}
console.log(running);
}
}
function drawRunButton() {
drawSquare(false, (columns * width) + 5, 5, 40)
}
function drawSquare(fill, x, y, width) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + width, y);
ctx.lineTo(x + width, y + width);
ctx.lineTo(x, y + width);
ctx.lineTo(x, y);
if (fill) {
ctx.fillStyle = gridColor;
ctx.fill();
} else {
ctx.strokeStyle = gridColor;
ctx.stroke();
}
}
function drawVisuals(neighbors, x, y) {
ctx.beginPath();
ctx.fillStyle = "#ff0000";
ctx.font = '20px serif';
ctx.fillText(neighbors, x + (width / 3), y + (width / 1.25));
}
<!DOCTYPE html>
<html>
<head>
<title>template.com</title>
<link href="style.css" type="text/css" rel="stylesheet">
</head>
<body>
<canvas id="life" width="1400" height="700" style="border: 1px solid black"></canvas>
</body>
</html>
I am using a flood fill algorithm to fill in circles drawn on the canvas. The issue I am having is that the algorithm isn't filling right up to the edge of the circle.
Here is the algorithm based on this blog post:
function paintLocation(startX, startY, r, g, b) {
var colorLayer = context1.getImageData(0, 0, canvasWidth, canvasHeight);
pixelPos = (startY * canvasWidth + startX) * 4;
startR = colorLayer.data[pixelPos];
startG = colorLayer.data[pixelPos + 1];
startB = colorLayer.data[pixelPos + 2];
var pixelStack = [
[startX, startY]
];
var drawingBoundTop = 0;
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(colorLayer, pixelPos, startR, startG, startB)) {
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
++y;
reachLeft = false;
reachRight = false;
while (y++ < canvasHeight - 1 && matchStartColor(colorLayer, pixelPos, startR, startG, startB)) {
colorPixel(colorLayer, pixelPos, r, g, b);
if (x > 0) {
if (matchStartColor(colorLayer, pixelPos - 4, startR, startG, startB)) {
if (!reachLeft) {
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < canvasWidth - 1) {
if (matchStartColor(colorLayer, pixelPos + 4, startR, startG, startB)) {
if (!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
context1.putImageData(colorLayer, 0, 0);
}
Please see the JSFiddle or the below image to see what I mean. Clicking inside any circles will change the colour between yellow and black (the issue is far more visible with black).
I've read that the issue could be something to do with the anti-aliasing and I have tried turning it off with context1.imageSmoothingEnabled = true; but it didn't make a difference.
I have also tried changing my matchStartColour function as per this question but that doesn't help.
function matchStartColor(colorLayer, pixelPos, startR, startG, startB) {
var r = colorLayer.data[pixelPos];
var g = colorLayer.data[pixelPos + 1];
var b = colorLayer.data[pixelPos + 2];
return (r == startR && g == startG && b == startB);
}
I think it might have something to do with the fact that the circles have no fill colour and the background of the canvas isn't white but it is transparent black. I have tried changing the canvas background to white but that also didn't help.
Use flood fill to create a Mask
I just happened to do a floodFill the other day that addresses the problem of antialiased edges.
Rather than paint to the canvas directly, I paint to a byte array that is then used to create a mask. The mask allows for the alpha values to be set.
The fill can have a tolerance and a toleranceFade that control how it deals with colours that approch the tolerance value.
When pixel's difference between the start colour and tolerance are greater than (tolerance - toleranceFade) I set the alpha for that pixel to 255 - ((differance - (tolerance - toleranceFade)) / toleranceFade) * 255 which creates a nice smooth blend at the edges of lines. Though it does not work for all situations for high contrast situations it is an effective solution.
The example below shows the results of with and without the toleranceFade. The blue is without the toleranceFade, the red is with the tolerance set at 190 and the toleranceFade of 90.
You will have to play around with the setting to get the best results for your needs.
function showExample(){
var canvas = document.createElement("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
ctx.fillStyle = "white"
ctx.fillRect(0,0,canvas.width,canvas.height)
ctx.lineWidth = 4;
ctx.strokeStyle = "black"
ctx.beginPath();
ctx.arc(100,100,90,0,Math.PI * 2);
ctx.arc(120,100,60,0,Math.PI * 2);
ctx.stroke();
ctx.fillStyle = "blue";
floodFill.fill(100,100,1,ctx)
ctx.fillStyle = "red";
floodFill.fill(40,100,190,ctx,null,null,90)
}
// FloodFill2D from https://github.com/blindman67/FloodFill2D
var floodFill = (function(){
"use strict";
const extent = {
top : 0,
left : 0,
bottom : 0,
right : 0,
}
var keepMask = false; // if true then a mask of the filled area is returned as a canvas image
var extentOnly = false; // if true then the extent of the fill is returned
var copyPixels = false; // if true then creating a copy of filled pixels
var cutPixels = false; // if true and copyPixels true then filled pixels are removed
var useBoundingColor = false; // Set the colour to fill up to. Will not fill over this colour
var useCompareColor = false; // Rather than get the pixel at posX,posY use the compareColours
var red, green, blue, alpha; // compare colours if
var canvas,ctx;
function floodFill (posX, posY, tolerance, context2D, diagonal, area, toleranceFade) {
var w, h, painted, x, y, ind, sr, sg, sb, sa,imgData, data, data32, RGBA32, stack, stackPos, lookLeft, lookRight, i, colImgDat, differance, checkColour;
toleranceFade = toleranceFade !== undefined && toleranceFade !== null ? toleranceFade : 0;
diagonal = diagonal !== undefined && diagonal !== null ? diagonal : false;
area = area !== undefined && area !== null ? area : {};
area.x = area.x !== undefined ? area.x : 0;
area.y = area.y !== undefined ? area.y : 0;
area.w = area.w !== undefined ? area.w : context2D.canvas.width - area.x;
area.h = area.h !== undefined ? area.h : context2D.canvas.height - area.y;
// vet area is on the canvas.
if(area.x < 0){
area.w = area.x + area.w;
area.x = 0;
}
if(area.y < 0){
area.h = area.y + area.h;
area.y = 0;
}
if(area.x >= context2D.canvas.width || area.y >= context2D.canvas.height){
return false;
}
if(area.x + area.w > context2D.canvas.width){
area.w = context2D.canvas.width - area.x;
}
if(area.y + area.h > context2D.canvas.height){
area.h = context2D.canvas.height - area.y;
}
if(area.w <= 0 || area.h <= 0){
return false;
}
w = area.w; // width and height
h = area.h;
x = posX - area.x;
y = posY - area.y;
if(extentOnly){
extent.left = x; // set up extent
extent.right = x;
extent.top = y;
extent.bottom = y;
}
if(x < 0 || y < 0 || x >= w || y >= h){
return false; // fill start outside area. Don't do anything
}
if(tolerance === 255 && toleranceFade === 0 && ! keepMask){ // fill all
if(extentOnly){
extent.left = area.x; // set up extent
extent.right = area.x + w;
extent.top = area.y;
extent.bottom = area.y + h;
}
context2D.fillRect(area.x,area.y,w,h);
return true;
}
if(toleranceFade > 0){ // add one if on to get correct number of steps
toleranceFade += 1;
}
imgData = context2D.getImageData(area.x,area.y,area.w,area.h);
data = imgData.data; // image data to fill;
data32 = new Uint32Array(data.buffer);
painted = new Uint8ClampedArray(w*h); // byte array to mark painted area;
function checkColourAll(ind){
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
if((differance = Math.max( // get the max channel difference;
Math.abs(sr - data[ind4++]),
Math.abs(sg - data[ind4++]),
Math.abs(sb - data[ind4++]),
Math.abs(sa - data[ind4++])
)) > tolerance){
return false;
}
return true
}
// check to bounding colour
function checkColourBound(ind){
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
differance = 0;
if(sr === data[ind4] && sg === data[ind4 + 1] && sb === data[ind4 + 2] && sa === data[ind4 + 3]){
return false
}
return true
}
// this function checks the colour of only selected channels
function checkColourLimited(ind){ // check only colour channels that are not null
var dr,dg,db,da;
if( ind < 0 || painted[ind] > 0){ // test bounds
return false;
}
var ind4 = ind << 2; // get index of pixel
dr = dg = db = da = 0;
if(sr !== null && (dr = Math.abs(sr - data[ind4])) > tolerance){
return false;
}
if(sg !== null && (dg = Math.abs(sg - data[ind4 + 1])) > tolerance){
return false;
}
if(sb !== null && (db = Math.abs(sb - data[ind4 + 2])) > tolerance){
return false;
}
if(sa !== null && (da = Math.abs(sa - data[ind4 + 3])) > tolerance){
return false;
}
diferance = Math.max(dr, dg, db, da);
return true
}
// set which function to check colour with
checkColour = checkColourAll;
if(useBoundingColor){
sr = red;
sg = green;
sb = blue;
if(alpha === null){
ind = (y * w + x) << 2; // get the starting pixel index
sa = data[ind + 3];
}else{
sa = alpha;
}
checkColour = checkColourBound;
useBoundingColor = false;
}else if(useCompareColor){
sr = red;
sg = green;
sb = blue;
sa = alpha;
if(red === null || blue === null || green === null || alpha === null){
checkColour = checkColourLimited;
}
useCompareColor = false;
}else{
ind = (y * w + x) << 2; // get the starting pixel index
sr = data[ind]; // get the start colour that we will use tolerance against.
sg = data[ind + 1];
sb = data[ind + 2];
sa = data[ind + 3];
}
stack = []; // paint stack to find new pixels to paint
lookLeft = false; // test directions
lookRight = false;
stackPos = 0;
stack[stackPos++] = x;
stack[stackPos++] = y;
while (stackPos > 0) { // do while pixels on the stack
y = stack[--stackPos]; // get the pixel y
x = stack[--stackPos]; // get the pixel x
ind = x + y * w;
while (checkColour(ind - w)) { // find the top most pixel within tollerance;
y -= 1;
ind -= w;
}
//checkTop left and right if allowing diagonal painting
if(diagonal && y > 0){
if(x > 0 && !checkColour(ind - 1) && checkColour(ind - w - 1)){
stack[stackPos++] = x - 1;
stack[stackPos++] = y - 1;
}
if(x < w - 1 && !checkColour(ind + 1) && checkColour(ind - w + 1)){
stack[stackPos++] = x + 1;
stack[stackPos++] = y - 1;
}
}
lookLeft = false; // set look directions
lookRight = false; // only look is a pixel left or right was blocked
while (checkColour(ind) && y < h) { // move down till no more room
if(toleranceFade > 0 && differance >= tolerance-toleranceFade){
painted[ind] = 255 - (((differance - (tolerance - toleranceFade)) / toleranceFade) * 255);
painted[ind] = painted[ind] === 0 ? 1 : painted[ind]; // min value must be 1
}else{
painted[ind] = 255;
}
if(extentOnly){
extent.left = x < extent.left ? x : extent.left; // Faster than using Math.min
extent.right = x > extent.right ? x : extent.right; // Faster than using Math.min
extent.top = y < extent.top ? y : extent.top; // Faster than using Math.max
extent.bottom = y > extent.bottom ? y : extent.bottom; // Faster than using Math.max
}
if (checkColour(ind - 1) && x > 0) { // check left is blocked
if (!lookLeft) {
stack[stackPos++] = x - 1;
stack[stackPos++] = y;
lookLeft = true;
}
} else if (lookLeft) {
lookLeft = false;
}
if (checkColour(ind + 1) && x < w -1) { // check right is blocked
if (!lookRight) {
stack[stackPos++] = x + 1;
stack[stackPos++] = y;
lookRight = true;
}
} else if (lookRight) {
lookRight = false;
}
y += 1; // move down one pixel
ind += w;
}
if(diagonal && y < h){ // check for diagonal areas and push them to be painted
if(checkColour(ind - 1) && !lookLeft && x > 0){
stack[stackPos++] = x - 1;
stack[stackPos++] = y;
}
if(checkColour(ind + 1) && !lookRight && x < w - 1){
stack[stackPos++] = x + 1;
stack[stackPos++] = y;
}
}
}
if(extentOnly){
extent.top += area.y;
extent.bottom += area.y;
extent.left += area.x;
extent.right += area.x;
return true;
}
canvas = document.createElement("canvas");
canvas.width = w;
canvas.height = h;
ctx = canvas.getContext("2d");
ctx.fillStyle = context2D.fillStyle;
ctx.fillRect(0, 0, w, h);
colImgDat = ctx.getImageData(0, 0, w, h);
if(copyPixels){
i = 0;
ind = 0;
if(cutPixels){
while(i < painted.length){
if(painted[i] > 0){
colImgDat.data[ind] = data[ind];
colImgDat.data[ind + 1] = data[ind + 1];
colImgDat.data[ind + 2] = data[ind + 2];
colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
data[ind + 3] = 255 - painted[i];
}else{
colImgDat.data[ind + 3] = 0;
}
i ++;
ind += 4;
}
context2D.putImageData(imgData, area.x, area.y);
}else{
while(i < painted.length){
if(painted[i] > 0){
colImgDat.data[ind] = data[ind];
colImgDat.data[ind + 1] = data[ind + 1];
colImgDat.data[ind + 2] = data[ind + 2];
colImgDat.data[ind + 3] = data[ind + 3] * (painted[i] / 255);
}else{
colImgDat.data[ind + 3] = 0;
}
i ++;
ind += 4;
}
}
ctx.putImageData(colImgDat,0,0);
return true;
}else{
i = 0;
ind = 3;
while(i < painted.length){
colImgDat.data[ind] = painted[i];
i ++;
ind += 4;
}
ctx.putImageData(colImgDat,0,0);
}
if(! keepMask){
context2D.drawImage(canvas,area.x,area.y,w,h);
}
return true;
}
return {
fill : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
ctx = undefined;
canvas = undefined;
},
getMask : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
keepMask = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
ctx = undefined;
keepMask = false;
return canvas;
},
getExtent : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
extentOnly = true;
if(floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade)){
extentOnly = false;
return {
top : extent.top,
left : extent.left,
right : extent.right,
bottom : extent.bottom,
width : extent.right - extent.left,
height : extent.bottom - extent.top,
}
}
extentOnly = false;
return null;
},
cut : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
cutPixels = true;
copyPixels = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
cutPixels = false;
copyPixels = false;
ctx = undefined;
return canvas;
},
copy : function(posX, posY, tolerance, context2D, diagonal, area, toleranceFade){
cutPixels = false;
copyPixels = true;
floodFill(posX, posY, tolerance, context2D, diagonal, area, toleranceFade);
copyPixels = false;
ctx = undefined;
return canvas;
},
setCompareValues : function(R,G,B,A){
if(R === null && G === null && B === null && A === null){
return;
}
red = R;
green = G;
blue = B;
alpha = A;
useBoundingColor = false;
useCompareColor = true;
},
setBoundingColor : function(R,G,B,A){
red = R;
green = G;
blue = B;
alpha = A;
useCompareColor = false;
useBoundingColor = true;
}
}
}());
showExample();
Red floodFill.fill(40,100,190,ctx,null,null,90) tolerance 190, tolerance fade 90<br>Blue floodFill.fill(100,100,1,ctx) tolerance 1.<br>
For more info see readme at Github FloodFill2D