I want to use the Canvas API to create a square grid where each cell has a 1px border on its bottom and a 1px border on its right. The entire grid would then have a 1px border drawn around it, so it'd look like this:
Once the grid exists, I want to write a function that will highlight a given cell. So for example calling highlightCell(1, 3, 'green') would result in this:
I thought this would be simple enough, but I'm having a devil of a time working it out. My problem is that when I account for having to straddle pixel lines -- drawing from eg 3.5 to 7.5, rather than 3 to 7, to not get blurry lines -- the math to figure out coordinates doesn't seem to work the way I expect at the edges, and I get results like this, where the highlight isn't placed correctly.
My math is:
I want a 700x700 px grid divided into 35 cells
Canvas itself is 702x702 px to allow for a 1px border on each side
Cells have a 1px east border and 1px south border so the highlight rectangles are 19x19px
Where w is the width of the canvas in px and h is its height, draw the outside border of the whole grid by going from (0.5, 0.5) --> (w + 0.5, 0.5) --> (w + 0.5, h + 0.5) --> (0.5, h + 0.5) --> (0.5, 0.5), creating a solid line for the first and last pixel row and first and last pixel column. The inside space is then 700x700.
Highlight cell 2,4 by drawing a 19x19 px rectangle at ???. I cannot figure out a value here that consistently works.
I'd appreciate someone explaining what I've done wrong because I'm sure it's something dumb but I just can't see it.
Here's the JS Fiddle of my attempt.
I would do it by separating the logic and the drawing. For example by having a state object and a drawFrame function; like this:
// setup state
const state = {
__arr: [],
__width: 20,
__height: 20,
__cell: {
width: 20,
height: 20,
},
__onUpdate: () => {},
__calcIndex(x, y) {
const index = x + y * this.__width;
if (index >= this.__arr.length || index < 0) {
throw new Error('Invalid index!');
}
return index;
},
init(onUpdate) {
this.__arr = Array(this.__width * this.__height).fill(0);
this.__onUpdate = onUpdate;
},
get(x, y) {
const index = this.__calcIndex(x, y);
return this.__arr[index];
},
set(x, y, value) {
const index = this.__calcIndex(x, y);
this.__arr[index] = value;
this.__onUpdate();
},
};
// setup drawing logic
const canvas = document.createElement('canvas');
document.body.append(canvas);
const ctx = canvas.getContext('2d');
const drawFrame = () => {
const cell = state.__cell;
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.fillStyle = 'orangered';
for (let x = 0; x < state.__width; x++) {
for (let y = 0; y < state.__width; y++) {
ctx.strokeRect(cell.width * x, cell.height * y, cell.width, cell.height);
if (state.get(x, y) !== 0) {
ctx.fillRect(1 + cell.width * x, 1 + cell.height * y, cell.width-1, cell.height-1);
}
}
}
}
state.init(drawFrame);
canvas.width = state.__width * state.__cell.width;
canvas.height = state.__height * state.__cell.height;
drawFrame();
state.set(2, 4, 1);
state.set(3, 5, 1);
state.set(2, 6, 1);
state.set(7, 4, 1);
Related
const coords = [
{
name: "Rijnstraat vervolg",
points: [
[695, 500],
[680, 480],
[580, 475],
[520, 460],
],
width: 10,
types: [types.car, types.truck, types.pedestrian, types.bike],
oneway: true,
},
...
]
I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads)
And the function can be described as someone who is trying to get to somewhere.
I already tried a recursive function that looks like this:
function calculatePathToShop(startPoint, shopPoint) {
const targetShopPoint = findClosestPointOnPath(shopPoint);
const targetPathIndex = findPathByPoint(targetShopPoint);
const connectedPaths = calculateConnectedPaths(targetPathIndex);
let startPathIndex = -1;
connectedPaths.forEach(path => {
const pathPoints = coords[path].points;
pathPoints.forEach(pathPoint => {
if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path;
});
});
if (startPathIndex == -1) return false;
let startPathPoints = coords[startPathIndex].points;
let targetPathPoints = coords[targetPathIndex].points;
if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse();
ctx.strokeStyle = "rgba(255, 0, 0, .05)";
}
This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail
So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image)
Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js
(The rest of the code is in the github repo itself)
Fixed step interpolation
To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps.
EG
steps = 100;
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps};
Then loop that number of steps adding the vector to a position initialized to the start point.
points = []; // array of interpolated points
point = {...start} // set start position.
while (steps--) {
points.push({...point});
point.x += vec.x;
point.y += vec.y;
}
points.push({...end}); // last point at end
This will create different spacing for different line lengths.
Fixed distance interpolation
To get a constant spacing between points you will need to use the lines' length to get the number of steps.
pixelsPerStep = 2; // distance between points.
start = {x: 50, y: 100}
end = {x: 150, y: 300}
step = {x: end.x - start.x, y: end.y - start.y};
lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep;
points = []; // array of interpolated points
for (i = 0; i < lineSteps ; i += 1) {
u = i / lineSteps;
points.push({x: start.x + step.x * u, y: start.y + step.y * u});
}
// check to add end point
Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it.
eg from code above
// add last point if within (0.01 * pixelsPerStep) pixels of correct spacing
if (Math.abs(lineSteps - i) < 0.01) {
points.push({...end});
}
Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment.
Example
The code below is an example of a constant spaced set of points interpolated from another set of points.
The example draws the new points in black dots. The original points are rendered in red.
Note that the distance between new points is constant and thus may not fall on the original (red) points.
Note that there is a check at the end to test if a last point should be added.
const ctx = canvas.getContext("2d");
const P2 = (x, y) => ({x, y});
const points = [
P2(100,90),
P2(300,210),
P2(350,110),
P2(50,10),
P2(6,219),
];
const interpolatedPoints = interpolatePath(points, 35);
drawPoints(interpolatedPoints, 2);
ctx.fillStyle = "RED";
drawPoints(points);
function drawPoints(points, size = 1) {
ctx.beginPath();
for (const p of points) {
ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1);
}
ctx.fill();
}
function interpolatePath(path, pixelStep) {
const res = [];
var p2, i = 1, overflow = 0;
while (i < path.length) {
const p1 = path[i - 1];
p2 = path[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const len = Math.hypot(dx, dy) / pixelStep;
let j = overflow;
while (j < len) {
const u = j / len;
res.push(P2(p1.x + dx * u, p1.y + dy * u));
j++;
}
overflow = j - len;
i++;
}
// add last point if close to correct distance
if (Math.abs(overflow) < 0.01) {
res.push(P2(p2.x, p2.y));
}
return res;
}
<canvas id="canvas" width="400" height="400"></canvas>
Recently, I have been trying to create code to fill a polygon of any shape with color. I have gotten as far as being able to fill a shape that has lines of only one border size correctly, though I have found myself unable to do anything more than that. The problem is that the code does not know when to consider a line of pixels greater than that which it expects as a vertical or horizontal border of the shape. I am going through each pixel of the shape from left to right and checking if any of the pixels have any form of color by checking if the alpha value is 0 or not. Once it finds a pixel that does have an alpha value of anything other than 0, it moves forward a single pixel and then uses the even/odd technique to determine whether the point is inside part of the polygon or not (it makes an infinite line to the right and determines if the number of collisions with colored lines is odd, and if it is, the point is inside the polygon). In general, we consider a single, lone pixel to count as a single line, and we consider a horizontal line of more than one pixel to be two lines because of how often horizontal lines will be part of a border or not. Take the following scenario:
Here, the red dot is the point (pixel) we begin testing from. If we did not consider that horizontal line in the middle to be two points (as is shown by the red lines and x's), we would only have two points of intersection and therefore would not fill the pixel despite the fact that we most definitely do want to fill that pixel. As stated earlier, however, this brings up another problem with a different scenario:
In this case, if we do count a horizontal line of more than one pixel to be two separate lines, we end up not filling any areas with borders that are thicker than the expected thickness. For your reference, the function to handle this is as follows:
//imgData is essentially a WebImage object (explained more below) and r, g, and b are the color values for the fill color
function fillWithColor(imgData, r, g, b) {
//Boolean determining whether we should color the given pixel(s) or not
var doColor = false;
//Booleans determining whether the last pixel found in the entire image was colored
var blackLast = false;
//Booleans determining whether the last 1 or 2 pixels found after a given pixel were colored
var foundBlackPrev, foundBlackPrev2 = false;
//The number of colored pixels found
var blackCount = 0;
//Loop through the entire canvas
for(var y = 0; y < imgData.height; y += IMG_SCALE) {
for(var x = 0; x < imgData.width; x += IMG_SCALE) {
//Test if given pixel is colored
if(getAlpha(imgData, x, y) != 0) {
//If the last pixel was black, begin coloring
if(!blackLast) {
blackLast = true;
doColor = true;
}
} else {
//If the current pixel is not colored, but the last one was, find all colored lines to the right
if(blackLast){
for(var i = x; i < imgData.width; i += IMG_SCALE) {
//If the pixel is colored...
if(getAlpha(imgData, i, y) != 0) {
//If no colored pixel was found before, add to the count
if(!foundBlackPrev){
blackCount++;
foundBlackPrev = true;
} else {
//Otherwise, at least 2 colored pixels have been found in a row
foundBlackPrev2 = true;
}
} else {
//If two or more colored pixels were found in a row, add to the count
if(foundBlackPrev2) {
blackCount++;
}
//Reset the booleans
foundBlackPrev2 = foundBlackPrev = false;
}
}
}
//If the count is odd, we start coloring
if(blackCount & 1) {
blackCount = 0;
doColor = true;
} else {
//If the last pixel in the entire image was black, we stop coloring
if(blackLast) {
doColor = false;
}
}
//Reset the boolean
blackLast = false;
//If we are to be coloring the pixel, color it
if(doColor) {
//Color the pixel
for(var j = 0; j < IMG_SCALE; j++) {
for(var k = 0; k < IMG_SCALE; k++) {
//This is the same as calling setRed, setGreen, setBlue and setAlpha functions from the WebImage API all at once (parameters in order are WebImage object equivalent, x position of pixel, y position of pixel, red value, green value, blue value, and alpha value)
setRGB(imgData, x + j, y + k, r, g, b, 255);
}
}
}
}
}
}
//Update the image (essentially the same as removing all elements from the given area and calling add on the image)
clearCanvas();
putImageData(imgData, 0, 0, imgData.width, imgData.height);
//Return the modified data
return imgData;
}
Where...
imgData is the collection of all of the pixels in the given area (essentially a WebImage object)
IMG_SCALE is the integer value by which the image has been scaled up (which gives us the scale of the pixels as well). In this example, it is equal to 4 because the image is scaled up to 192x256 (from 48x64). This means that every "pixel" you see in the image is actually comprised of a 4x4 block of identically-colored pixels.
So, what I'm really looking for here is a way to determine whether a given colored pixel that comes after another is part of a horizontal border or if it is just another piece comprising the thickness of a vertical border. In addition, if I have the wrong approach to this problem in general, I would greatly appreciate any suggestions as to how to do this more efficiently. Thank you.
I understand the problem and I think you would do better if you would switch your strategy here. We know the following:
the point of start is inside the shape
the color should be filled for every pixel inside the shape
So, we could always push the neighbors of the current point into a queue to be processed and be careful to avoid processing the same points twice, this way traversing all the useful pixels and including them into the coloring plan. The function below is untested.
function fillColor(pattern, startingPoint, color, boundaryColor) {
let visitQueue = [];
let output = {};
if (startingPoint.x - 1 >= 0) visitQueue.push({startingPoint.x - 1, startingPoint.y});
if (startingPoint.x + 1 < pattern.width) visitQueue.push({startingPoint.x + 1, startingPoint.y});
if (startingPoint.y + 1 < pattern.height) visitQueue.push({startingPoint.x, startingPoint.y + 1});
if (startingPoint.y - 1 >= 0) visitQueue.push({startingPoint.x, startingPoint.y - 1});
let visited = {};
while (visitQueue.length > 0) {
let point = visitQueue[0];
visitQueue.shift();
if ((!visited[point.x]) || (visited[point.x].indexOf(point.y) < 0)) {
if (!visited[point.x]) visited[point.x] = [];
visited[point.x].push(point.y);
if (isBlank(pattern, point)) { //you need to implement isBlank
if (!output[point.x]) output[point.x] = [];
output[point.x].push(point.y);
if (point.x + 1 < pattern.width) visitQueue.push({point.x + 1, point.y});
if (point.x - 1 >= 0) visitQueue.push({point.x - 1, point.y});
if (point.y + 1 < pattern.height) visitQueue.push({point.x, point.y + 1});
if (point.y - 1 >= 0) visitQueue.push({point.x, point.y - 1})
}
}
}
return output;
}
As far as I understood you cannot "consider a horizontal line of more than one pixel to be two lines". I don't think you need to count black pixels the way you do, rather count groups of 1 or more pixels.
I would also tidy the code by avoiding using the "doColor" boolean variable. You could rather move the coloring code to a new function color(x,y) and call it straight away.
const ctx = document.querySelector("canvas").getContext("2d");
//ctx.lineWidth(10);//-as you asked we are setting greater border or line width,BUT "LINEWIDTH" IS NOT WORKING IN INBUILT STACKOVERFLOW SNIPPET USE IT IN A FILE I THINK STACKOVERFLOW IS NOT UP-TO-DATE,IN ANY IDE UNCOMENT THIS
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();
function getMousePosition(canvas, event) {
let rect = canvas.getBoundingClientRect();
let mx = event.clientX - rect.left;
let my = event.clientY - rect.top;
console.log("Coordinate x: " + mx, "Coordinate y: " + my);
floodFill(ctx, mx, my, [155, 0, 255, 255], 128);
}
let canvasElem = document.querySelector("canvas");
canvasElem.addEventListener("mousedown", function(e) {
getMousePosition(canvasElem, e);
});
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, rangeSq) {
const dr = a[0] - b[0];
const dg = a[1] - b[1];
const db = a[2] - b[2];
const da = a[3] - b[3];
return dr * dr + dg * dg + db * db + da * da < rangeSq;
}
function floodFill(ctx, x, y, fillColor, range = 1) {
// read the pixels in the canvas
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
// flags for if we visited a pixel already
const visited = new Uint8Array(imageData.width, imageData.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)) {
const rangeSq = range * range;
const pixelsToCheck = [x, y];
while (pixelsToCheck.length > 0) {
const y = pixelsToCheck.pop();
const x = pixelsToCheck.pop();
const currentColor = getPixel(imageData, x, y);
if (!visited[y * imageData.width + x] &&
colorsMatch(currentColor, targetColor, rangeSq)) {
setPixel(imageData, x, y, fillColor);
visited[y * imageData.width + x] = 1; // mark we were here already
pixelsToCheck.push(x + 1, y);
pixelsToCheck.push(x - 1, y);
pixelsToCheck.push(x, y + 1);
pixelsToCheck.push(x, y - 1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
<canvas></canvas>
This is based on other answers
note:"LINEWIDTH" IS NOT WORKING IN INBUILT STACKOVERFLOW SNIPPET USE IT IN A FILE I THINK STACKOVERFLOW IS NOT UP-TO-DATE,
But it works well in simple HTML,JS website
Hitbox Overlay IIFE Code
//CSS Hitbox Solution 08-26-2015
//StackOverflow - https://stackoverflow.com/questions/32233084/show-an-element-without-hitbox-does-not-take-mouse-touch-input
//Detect MouseOver https://stackoverflow.com/questions/1273566/how-do-i-check-if-the-mouse-is-over-an-element-in-jquery
//Source: https://stackoverflow.com/questions/3942776/using-jquery-to-find-an-element-at-a-particular-position
//https://css-tricks.com/snippets/jquery/get-x-y-mouse-coordinates/
(function($) {
$.mlp = {
x: 0,
y: 0
}; // Mouse Last Position
function documentHandler() {
var $current = this === document ? $(this) : $(this).contents();
$current.mousemove(function(e) {
jQuery.mlp = {
x: e.pageX,
y: e.pageY
};
});
$current.find("iframe").load(documentHandler);
}
$(documentHandler);
$.fn.ismouseover = function(overThis) {
var result = false;
this.eq(0).each(function() {
var $current = $(this).is("iframe") ? $(this).contents().find("body") : $(this);
var offset = $current.offset();
result = offset.left <= $.mlp.x && offset.left + $current.outerWidth() > $.mlp.x && offset.top <= $.mlp.y && offset.top + $current.outerHeight() > $.mlp.y;
});
return result;
};
})(jQuery);
$('.notification-box').on("click", function() {
$("button").each(function(i) {
var iteratedButton = $('button:eq(' + i + ')');
var buttonID = iteratedButton.attr("id");
if (iteratedButton.ismouseover()) {
iteratedButton.toggleClass(buttonID);
}
});
});
Example 01: Overlay Example for context
Example 02: Concept for auto generating content - Derived from this stackoverflow question.
There is a way by which one can have multiple objects underneath an overlay that masks them. Then, there is a way to have the pointer interact with the elements underneath said overlay if the user clicks at the predetermined point. My question is, may someone please write the code that would, marry the concept of the <map> tag with the IIFE that detects if the point of reference the user clicked is that image and then, act as though it was clicked.
If that did not make sense, simply, I am looking for a process that deviates away from manually setting up coordinates for <area> or having to use tool (which are profound) such as http://www.image-maps.com/. Rather, we would let the pointer do all the work.
We have the following high utility + highly compatible methods: .offset(), .position(), elementFromPoint() and the ability to put elements behind a mask utilizing basic CSS.
So we could combine the IIFE Overlay hitbox method above + ???? = Profit (good bye mapping coordinates via <map>).
I just do not know what the ???? is. I do know that whatever the solution is, I would prefer that it works in all browsers (including IE 5).
Lastly, the process should be fairly automatic in design, setup and implementation.
Whoever creates it, please dub it autoMapperJs (as it would not be limited to images).
Update:
A core feature component of the ???? has been realized as noted by #Alex in the comments. CreateJs notices when the pointer is hovered over a non-transparent area of a image. That is powerful and should be standard in the tool created. It also seems to utilize .mousemove() and z-index. Please keep commenting, as collectively, I feel a solution can be found.
Here's a start. Put images into an array of layers and placements on canvas then run through them on mouse over for hit. Also put over images in layers array to draw that image when hit.
var can = document.getElementById('image-map');
var W = can.width;
var H = can.height;
var ctx = can.getContext('2d');
var layers = [];
var mouse = {x:0,y:0};
can.addEventListener('mousemove', function(evt) {
mouse = getMousePos(can, evt);
drawCanvas();
}, false);
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
main();
function main() {
initLayers();
drawCanvas();
}
function drawCanvas() {
ctx.clearRect(0, 0, W, H);
var hit = -1;
for (var i =layers.length; i--;) {
var c = layers[i];
if(maskHit(c.img, c.x, c.y)) {
hit = i;
break;
}
}
for (var i =0; i < layers.length; i++) {
var c = layers[i];
var img = hit === i ? c.hov : c.img;
ctx.drawImage(img, c.x, c.y);
}
ctx.drawImage(circ(10,"rgba(255,200,0,.75)"), mouse.x-10/2,mouse.y-10/2);
}
// UTILITY TO DRAW SAMPLE IMAGES
function circ(size, color) {
var can = document.createElement('canvas');
can.width = can.height = size;
var to_rad = Math.PI / 180;
var ctx = can.getContext('2d');
ctx.beginPath();
ctx.moveTo(size, size / 2);
ctx.arc(size / 2, size / 2, size / 2, 0, 360 * to_rad);
ctx.fillStyle = color;
ctx.fill();
return can;
}
function initLayers() {
var s = 75; // size
// PUT YOUR IMAGES IN A LAYERS ARRAY WITH X,Y COORDS FOR CANVAS
// PLACEMENT. X AND Y ARE TOP LEFT CORNDER OF IMAGE. STORE HOVER
// IMAGE FOR MOUSE HIT.
layers = [{
img: circ(s, "#090"),
hov: circ(s, "#C0C"),
x: 123,
y: 12
}, {
img: circ(s, "#F00"),
hov: circ(s, "#C0C"),
x: 63,
y: 12
}, {
img: circ(s, "#00F"),
hov: circ(s, "#C0C"),
x: 3,
y: 12
}];
}
var maskCan = document.createElement("canvas");
maskCan.width=maskCan.height=1;
var maskCtx = maskCan.getContext('2d');
function maskHit(img, x, y) {
// get relative coords to image upper left corner
x = mouse.x - x;
y = mouse.y - y;
if (x < 0 || y < 0 || x > img.width || y > img.height) return false;
//return 1; // square hit, no alpha check
// ALPHA CHECK - draw one pixel, get and check alpha.
// sx sy sw sh dx dy dw dh
maskCtx.clearRect(0,0,1,1);
maskCtx.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
var imageData = maskCtx.getImageData(0,0,1,1);
//console.log(imageData.data[3])
return imageData.data[3] === 255;
}
#image-map {
border: 1px solid #ACE;
}
<canvas id="image-map" width="200" height="100"></canvas>
I have 2 functions, the first function draws a lined empty grid (28x16) and the second function draws a square box that fills a selected grid location with a colour, each box is 25X25. the 2nd function allows me to draw a box anywhere in the canvas using the grid location. hence, to draw a box I created another function that calls the grid locations and draw it there, for example to draw a box in the grid location where x=5 and y=4 this line must be entered drawWalls(5,4); this line can be used again and again to draw different boxes, so I can draw the boxes and the function works perfect, now I want to create more than 1 square box at the same time (for example a line of boxes), the problem is, I have never created a loop like this or a loop that has more than 1 variable, please help, also I am getting a little confused with arrays, I want to know if these boxes are stored in an array and how can I call them again, say for example I click on a box and it gets deleted, this is not important but if you have any ideas on the first problem then please help, thank you for your time.
draw grid
function drawGrid() {
ctxBg.beginPath();
for (var i = 0; i <= canvasWidth-25; i+= 25) {
ctxBg.moveTo(-25 + i,55);
ctxBg.lineTo(-25 + i, canvasHeight - 55);
}
for (var i = 25; i <= canvasHeight -75; i+= 25) {
ctxBg.moveTo(55,25 + i);
ctxBg.lineTo(canvasWidth-55, 25 + i);
}
ctxBg.stroke();
}
function ClearBg() {
ctxBg.clearRect(0,0,canvasWidth,canvasHeight);
}
Draw the wall
function Wall(row, col) {
this.row = row;
this.col = col;
this.color = "#000";
this.width = 25
this.height = 25
this.leftX = this.row;
this.rightX = this.row + this.width;
this.topY = this.col;
this.bottomY = this.col + this.height;
}
function drawWalls(x,y) {
walls.push( new Wall(x, y));
for (var b = 0; b < walls.length; b++) {
ctxWall.fillStyle = walls[b].color;
ctxWall.fillRect(walls[b].row * gridSize, walls[b].col * gridSize, walls[b].width, walls[b].height);
}
}
function createWalls() {
drawWalls(9,9);
drawWalls(8,8);
drawWalls(7,7);
}
I am trying to make the function createWalls in a loop to draw them all together, unfortunately I still haven't created the mouse click event, but I will soon
This was a fun thing to do: http://jsfiddle.net/techsin/rcjouqkf/
To get any 'box/cell' coordinates all you have to do is this:
get x position of the click divide it by size of a box so you know
which box you are at the round it to next whole number, which
represent the box you are at. then times that number with the box
size. this gives you box's right edge
repeat for y to get bottom edge.
to get top and left just subtract the size from edges.
var can = getById('can'),
boxes = 20,
size = 20,
ctx = can.getContext('2d'),
clearBtn = getById('clearBtn');
drawGrid();
function drawGrid() {
var len = can.height = can.width = boxes * size;
for (var i = 0; i < boxes; i++) {
ctx.beginPath();
ctx.moveTo(size + size * i - .5, 0);
ctx.lineTo(size + size * i - .5, len);
ctx.moveTo(0, size + size * i - .5);
ctx.lineTo(len, size + size * i - .5);
ctx.stroke();
}
}
can.addEventListener('mousemove', function (evt) {
var mousePos = getMousePos(can, evt);
var sx = (Math.ceil(mousePos.x/size)-1)*size,
sy = (Math.ceil(mousePos.y/size)-1)*size;
console.log(sx,sy,sx+size,sy+size);
ctx.fillRect(sx,sy,size,size);
}, false);
clearBtn.addEventListener('click', function (evt) {
ctx.clearRect(0,0,can.width,can.height);
drawGrid();
});
function getMousePos(canvas, evt) {
var rect = can.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function getById(x) {
return document.getElementById(x);
}
Put the definitions of your walls in javascript objects and store those wall-objects in an array. Then use the array to draw your walls as necessary.
Each wall-object holds the information required to draw one wall:
var walls=[
{direction:'horizontal',startX:4,endX:6,Y:3,fill:'skyblue'},
{direction:'vertical',startY:2,endY:6,X:1,fill:'lightgreen'}
];
Then you can loop through the array and draw each wall based on each wall-object:
var cellSize=25;
function drawAllWalls(walls){
for(var i=0;i<walls.length;i++){
var w=walls[i];
ctxWall.fillStyle=w.fill;
if(w.direction=='horizontal'){
for(var x=w.startX;x<=w.endX;x++){
ctxWall.fillRect(x*cellSize,w.Y*cellSize,cellSize,cellSize)
}
}else{
for(var y=w.startY;y<=w.endY;y++){
ctxWall.fillRect(w.X*cellSize,y*cellSize,cellSize,cellSize)
}
}
}
}
If you later want to delete a particular wall you can:
Delete that wall from the walls-array.
Clear the canvas,
Redraw all remaining walls in the array (the deleted wall will not be redrawn).
If you later want to delete just 1 box from a wall, you can:
Delete that wall from the walls-array.
Add back the part(s) of the wall that still exist to the walls array,
Clear the canvas,
Redraw all walls in the array (the deleted box will not be redrawn).
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctxBg=canvas.getContext("2d");
var ctxWall=canvas.getContext("2d");
var canvasWidth=canvas.width;
var canvasHeight=canvas.height;
var colCount=8;
var rowCount=8;
var cellSize=25;
var walls=[
{direction:'horizontal',startX:4,endX:6,Y:3,fill:'skyblue'},
{direction:'vertical',startY:2,endY:6,X:1,fill:'lightgreen'}
];
drawGrid();
drawAllWalls(walls);
function drawAllWalls(walls){
for(var i=0;i<walls.length;i++){
var w=walls[i];
ctxWall.fillStyle=w.fill;
if(w.direction=='horizontal'){
for(var x=w.startX;x<=w.endX;x++){
ctxWall.fillRect(x*cellSize,w.Y*cellSize,cellSize,cellSize)
}
}else{
for(var y=w.startY;y<=w.endY;y++){
ctxWall.fillRect(w.X*cellSize,y*cellSize,cellSize,cellSize)
}
}
}
}
function drawGrid(){
ctxBg.beginPath();
for(var x=0;x<colCount+1;x++){
ctxBg.moveTo(x*cellSize,0);
ctxBg.lineTo(x*cellSize,rowCount*cellSize);
}
for(var y=0;y<rowCount+1;y++){
ctxBg.moveTo(0,y*cellSize,0);
ctxBg.lineTo(colCount*cellSize,y*cellSize);
}
ctxBg.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
Say I have this image:
I'd like to recognize the position of the red ball in the image, I could measure the size of the ball(in pixel) in ahead.
I know that I could draw the image to a canvas, then I could get the pixel color data with context.getImageData, but then what should I do? which algorithm sould I use? I'm new to image processing, thanks a lot.
Here's code dedicated to getting that ball position. The output position will logged to the console so have your JS console open! This code has some values in it that you can play with. I chose some that work for your image such as the rough diameter of the ball being 14 pixels and the threshold for each colour component.
I saved the image as "test.jpg" but you can change the code to the correct image path on line 11.
<!DOCTYPE html>
<html>
<body>
<canvas width="800" height="600" id="testCanvas"></canvas>
<script type="text/javascript">
var img = document.createElement('img');
img.onload = function () {
console.log(getBallPosition(this));
};
img.src = 'test.jpg';
function getBallPosition(img) {
var canvas = document.getElementById('testCanvas'),
ctx = canvas.getContext('2d'),
imageData,
width = img.width,
height = img.height,
pixelData,
pixelRedValue,
pixelGreenValue,
pixelBlueValue,
pixelAlphaValue,
pixelIndex,
redThreshold = 128,
greenThreshold = 40,
blueThreshold = 40,
alphaThreshold = 180,
circleDiameter = 14,
x, y,
count,
ballPosition,
closestBallCount = 0,
closestBallPosition;
// Draw the image to the canvas
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0);
// Get the image data
imageData = ctx.getImageData(0, 0, width, height);
pixelData = imageData.data;
// Find the ball!
for (y = 0; y < height; y++) {
// Reset the pixel count
count = 0;
// Loop through the pixels on this line
for (x = 0; x < width; x++) {
// Set the pixel data starting point
pixelIndex = (y * width * 4) + (x * 4);
// Grab the red pixel value
pixelRedValue = pixelData[pixelIndex];
pixelGreenValue = pixelData[pixelIndex + 1];
pixelBlueValue = pixelData[pixelIndex + 2];
pixelAlphaValue = pixelData[pixelIndex + 3];
// Check if the value is within out red colour threshold
if (pixelRedValue >= redThreshold && pixelGreenValue <= greenThreshold && pixelBlueValue <= blueThreshold && pixelAlphaValue >= alphaThreshold) {
count++;
} else {
// We've found a pixel that isn't part of the red ball
// so now check if we found any red data
if (count === circleDiameter) {
// We've found our ball
return {
x: x - Math.floor(circleDiameter / 2),
y: y
};
} else {
// Any data we found was not our ball
if (count < circleDiameter && count > closestBallCount) {
closestBallCount = count;
closestBallPosition = {
x: x - Math.floor(circleDiameter / 2),
y: y
};
}
count = 0;
}
}
}
}
return closestBallPosition;
}
</script>
</body>
</html>
Well i would go and cluster pixels of that color. For example, you could have a look up table where you store red (or in the range of a treshold) pixels (coordinates being the look up key) and an integer value being the cluster id whenever you encounter a pixel without any known red neighbours it starts a new cluster, all other red pixels get the cluster id of a red pixel they are the neighbour of. Depending of you algorithms kernel:
A) XXX B) X
XOX XOX
XXX X
you might need to deal (case B) with a pixel connecting two prior not connected clusters. You would have to replace the cluster id of one of that clusters.
After that you have clusters of pixels. These you can analyse. In case of a round shape i would look for the median in x and y for each cluster and check if all the pixels of that cluster are in the radius.
This will fail if the red ball (or part of it) is in front of another red object. You would than need more complex algorithms.