Fill gap between 2 point with <div> - javascript

I did an experiment today to see what I can do with <div>s. So I made a simple Paint-like program, which you can draw with <div>s.
$(window).mousemove(function(e){
if(!mousedown){
return false;
}
var x = e.clientX,
y = e.clientY;
drawDot(x,y,ele);
lastX = x;
lastY = y;
});
This is part of the code. It works, but there are gaps between dots. So I created a function called fillDot which will draw a line from point A (last point) to point B (current point).
drawDot(x,y,ele);
fillDot(lastX,lastY,x,y,ele);
function fillDot(lx,ly,x,y,canvas){
var rise = y - ly,
run = x - lx,
slope = rise / run,
yInt = y - (slope * x);
if( lx < x ){
for(var i = lx; i < x ; i+=.5){
var y = slope * i + yInt;
drawDot(i,y,canvas);
}
}else if( x < lx ){
for(var i = x; i < lx ; i++){
var y = slope * i + yInt;
drawDot(i,y,canvas);
}
}
}
It works fine only when I am drawing horizontal-ish lines. When I draw from top to bottom or bottom to top, there will still be gaps. I found something called Bresenham's line algorithm which can do the same thing, but I don't know how to use it...
Any idea how to fill all points in between?
ps: I know there is <canvas>, but I am testing what I can do with <div>.

Nevermind, I translated the Bresenham's line algorithm into JavaScript and it works perfectly now!
function fillDot(x0, y0, x1, y1){
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
drawDot(x0,y0);
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx){
err += dx;
y0 += sy;
}
}
}

Related

Fill in shape with lines at a specified angle

I need to create line segments within a shape and not just a visual pattern - I need to know start and end coordinates for those lines that are within a given boundary (shape). I'll go through what I have and explain the issues I'm facing
I have a closed irregular shape (can have dozens of sides) defined by [x, y] coordinates
shape = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
];
I calculate and draw the bounding box of this shape
I then draw my shape on the canvas
Next, I cast rays within the bounding box with a set spacing between each ray. The ray goes from left to right incrementing by 1 pixel.
Whenever a cast ray gets to a pixel with RGB values of 100, 255, 100 I then know it has entered the shape. I know when it exits the shape if the pixel value is not 100, 255, 100. Thus I know start and end coordinates for each line within my shape and if one ray enters and exits the shape multiple times - this will generate all line segments within that one ray cast.
For the most part it works but there are issues:
It's very slow. Perhaps there is a better way than casting rays? Or perhaps there is a way to optimize the ray logic? Perhaps something more intelligent than just checking for RGB color values?
How do I cast rays at a different angle within the bounding box? Now it's going left to right, but how would I fill my bounding box with rays cast at any specified angle? i.e.:
I don't care about holes or curves. The shapes will all be made of straight line segments and won't have any holes inside them.
Edit: made changes to the pixel RGB sampling that improve performance.
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 15;
shape = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
];
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
// display bounding box
ctx.fillStyle = 'rgba(255,0,0,.2)';
ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
// loop through the shape in vertical slices
for(var i = boundingBox[0][1]+lineSpacing; i <= boundingBox[1][1]; i += lineSpacing) {
// send ray from left to right
for(var j = boundingBox[0][0], start = false; j <= boundingBox[1][0]; j++) {
x = j, y = i;
pixel = y * (canvas.width * 4) + x * 4;
// if pixel is within boundary (shape)
if(canvasData[pixel] == 100 && canvasData[pixel+1] == 255 && canvasData[pixel+2] == 100) {
// arrived at start of boundary
if(start === false) {
start = [x,y]
}
} else {
// arrived at end of boundary
if(start !== false) {
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.beginPath();
ctx.moveTo(start[0], start[1]);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
start = false;
}
}
}
// show entire cast ray for debugging purposes
ctx.strokeStyle = 'rgba(0,0,0,.2)';
ctx.beginPath();
ctx.moveTo(boundingBox[0][0], i);
ctx.lineTo(boundingBox[1][0], i);
ctx.closePath();
ctx.stroke();
}
<canvas id="canvas" width="350" height="350"></canvas>
This is a pretty complex problem that I am trying to simplify as much as possible. Using the line intersection formula we can determin where the ray intersects with the shape at every edge. What we can do is loop through each side of the shape while check every rays intersection. If they intersect we push those coordinates to an array.
I have tried to make this as dynamic as possible. You can pass the shape and change the number of rays and the angle. As for the angle it doesn't take a specific degree (i.e. 45) but rather you change the start and stop y axis. I'm sure if you must have the ability to put in a degree we can do that.
It currently console logs the array of intersecting coordinates but you can output them however you see fit.
The mouse function is just to verify that the number match up. Also be aware I am using toFixed() to get rid of lots of decimals but it does convert to a string. If you need an integer you'll have to convert back.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d")
canvas.width = 300;
canvas.height = 300;
ctx.fillStyle = "violet";
ctx.fillRect(0,0,canvas.width,canvas.height)
//Shapes
let triangleish = [
[150,10], // x, y
[10,300],
[150,200],
[300,300]
]
let star = [ [ 0, 85 ], [ 75, 75 ], [ 100, 10 ], [ 125, 75 ],
[ 200, 85 ], [ 150, 125 ], [ 160, 190 ], [ 100, 150 ],
[ 40, 190 ], [ 50, 125 ], [ 0, 85 ] ];
let coords = [];
//Class that draws the shape on canvas
function drawShape(arr) {
ctx.beginPath();
ctx.fillStyle = "rgb(0,255,0)";
ctx.moveTo(arr[0][0], arr[0][1]);
for (let i=1;i<arr.length;i++) {
ctx.lineTo(arr[i][0], arr[i][1]);
}
ctx.fill();
ctx.closePath();
}
//pass the shape in here to draw it
drawShape(star)
//Class to creat the rays.
class Rays {
constructor(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.w = canvas.width;
this.h = 1;
}
draw() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(this.x1, this.y1)
ctx.lineTo(this.x2, this.y2)
ctx.stroke();
ctx.closePath();
}
}
let rays = [];
function createRays(angle) {
let degrees = angle * (Math.PI/180)
//I am currently creating an array every 10px on the Y axis
for (let i=0; i < angle + 45; i++) {
//The i will be your start and stop Y axis. This is where you can change the angle
let cx = canvas.width/2 + (angle*2);
let cy = i * 10;
let x1 = (cx - 1000 * Math.cos(degrees));
let y1 = (cy - 1000 * Math.sin(degrees));
let x2 = (cx + 1000 * Math.cos(degrees));
let y2 = (cy + 1000 * Math.sin(degrees));
rays.push(new Rays(x1, y1, x2, y2))
}
}
//enter angle here
createRays(40);
//function to draw the rays after crating them
function drawRays() {
for (let i=0;i<rays.length; i++) {
rays[i].draw();
}
}
drawRays();
//This is where the magic happens. Using the line intersect formula we can determine if the rays intersect with the objects sides
function intersectLines(coord1, coord2, rays) {
let x1 = coord1[0];
let x2 = coord2[0];
let y1 = coord1[1];
let y2 = coord2[1];
let x3 = rays.x1;
let x4 = rays.x2;
let y3 = rays.y1;
let y4 = rays.y2;
//All of this comes from Wikipedia on line intersect formulas
let d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) {
return
}
let t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
let u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
//if this statement is true then the lines intersect
if (t > 0 && t < 1 && u > 0) {
//I have currently set it to fixed but if a string does not work for you you can change it however you want.
//the first formula is the X coord of the interect the second is the Y
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return
}
//function to call the intersect function by passing in the shapes sides and each ray
function callIntersect(shape) {
for (let i=0;i<shape.length;i++) {
for (let j=0;j<rays.length;j++) {
if (i < shape.length - 1) {
intersectLines(shape[i], shape[i+1], rays[j]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rays[j]);
}
}
}
}
callIntersect(star);
//just to sort them by the Y axis so they they show up as in-and-out
function sortCoords() {
coords.sort((a, b) => {
return a[1] - b[1];
});
}
sortCoords()
console.log(coords)
//This part is not needed only added to verify number matched the mouse posit
let mouse = {
x: undefined,
y: undefined
}
let canvasBounds = canvas.getBoundingClientRect();
addEventListener('mousemove', e => {
mouse.x = e.x - canvasBounds.left;
mouse.y = e.y - canvasBounds.top;
ctx.clearRect(0, 0, canvas.width, canvas.height)
drawCoordinates();
})
function drawCoordinates() {
ctx.font = '15px Arial';
ctx.fillStyle = 'black';
ctx.fillText('x: '+mouse.x+' y: '+mouse.y, mouse.x, mouse.y)
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "violet";
ctx.fillRect(0,0,canvas.width,canvas.height)
for (let i=0;i<rays.length; i++) {
rays[i].draw();
}
drawShape(star)
drawCoordinates();
requestAnimationFrame(animate)
}
animate()
<canvas id="canvas"></canvas>
I'm not an expert, but maybe you could do something like this:
Generate the points that constitute the borders.
Organize them in a convenient structure, e.g. an object with the y as key, and an array of x as values.
2.1. i.e. each item in the object would constitute all points of all borders in a single y.
Iterate over the object and generate the segments for each y.
3.1. e.g. if the array of y=12 contains [ 10, 20, 60, 80 ] then you would generate two segments: [ 10, 12 ] --> [ 20, 12 ] and [ 60, 12 ] --> [ 80, 12 ].
To generate the borders' points (and to answer your second question), you can use the line function y = a*x + b.
For example, to draw a line between [ 10, 30 ] and [ 60, 40 ], you would:
Solve a and b by substituting x and y for both points and combining these two formulas (with standard algebra):
For point #1: 30 = a*10 + b
For point #2: 40 = a*60 + b
b = 30 - a*10
40 = a*60 + (30 - a*10)
a*60 - a*10 = 40 - 30
50*a = 10
a = 0.2
30 = a*10 + b
30 = 0.2*10 + b
b = 30 - 2
b = 28
With a and b at hand, you get the function for your specific line:
y = 0.2*x + 28
With that, you can calculate the point of the line for any y. So, for example, the x of the point right under the first point ([ 10, 30 ]) would have a y of 31, and so: 31 = 0.2*x + 28, and so: x = 15. So you get: [ 15, 31 ].
You may need a bit of special handling for:
Vertical lines, because the slope is "infinite" and calculating it would cause division by zero.
Rounding issues. For some (probably most) pixels you will get real x values (i.e. non-integer). You can Math.round() them, but it can cause issues, like:
8.1. Diagonal rays may not actually hit a border point even when they go through a border. This will probably require additional handling (like checking points around and not just exactly the pixels the ray lies on).
8.2. The points your algorithm generate may (slightly) differ from the points that appear on the screen when you use libraries or built-in browser functionality to draw the shape (depending on the implementation of their drawing algorithms).
This is a mashup of Justin's answer and code from my proposed question.
One issue was generating rays at a set angle and a set distance from each other. To have rays be equal distances apart at any angle we can use a vector at a 90 degree angle and then place a new center point for the next line.
We can start at the exact midpoint of our boundary and then spread out on either side.
Red line is the center line, green dots are the vector offset points for the next line.
Next I modified Justin's intersect algorithm to iterate by ray and not side, that way I get interlaced coordinates where array[index] is the start point of a segment and array[index+1] is the end point.
And by connecting the lines we get a shape that is filled with lines inside its boundaries at set distances apart
Issues:
I had to inflate the boundary by 1 pixel otherwise certain shapes would fail to generate paths
I'd like rays to be some what aligned. It's hard to explain, but here's an example of 6 triangles rotated at 60 degree increments that form a hexagon with their inner lines also offset by 60 degree increments. The top and bottom triangle inner lines do not join those of the outside triangles. This is an issue with the cast rays. Ideally I'd like them to join and be aligned with the outer most edge if that makes sense. Surely there is a better way to cast rays than this...
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 12;
angle = 45;
shapes = [
[[143.7,134.2], [210.4,18.7], [77.1,18.7]],
[[143.7,134.2], [77.1,18.7], [10.4,134.2]],
[[143.7,134.2], [10.4,134.2], [77.1,249.7]],
[[143.7,134.2], [77.1,249.7], [210.4,249.7]],
[[143.7,134.2], [210.4,249.7], [277.1,134.2]],
[[143.7,134.2], [277.1,134.2], [210.4,18.7]]
];
for(var i in shapes) {
lines = getLineSegments(shapes[i], 90+(-60*i), lineSpacing);
for(var i = 0; i < lines.length; i += 2) {
start = lines[i];
end = lines[i+1];
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.closePath();
ctx.stroke();
}
}
function getLineSegments(shape, angle, lineSpacing) {
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
boundingBox[1][0] += 1, boundingBox[1][1] += 1;
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
rayPaths = [];
path = getPathCoords(boundingBox, 0, 0, angle);
rayPaths.push(path);
/*ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
getPaths:
for(var i = 0, lastPaths = [path, path]; true; i++) {
for(var j = 0; j < 2; j++) {
pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;
pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];
pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);
pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);
offsetX = pathOffsetPointX-boundingMidX;
offsetY = pathOffsetPointY-boundingMidY;
path = getPathCoords(boundingBox, offsetX, offsetY, angle);
if(
path[0][0] < boundingBox[0][0] ||
path[1][0] > boundingBox[1][0] ||
path[0][0] > boundingBox[1][0] ||
path[1][0] < boundingBox[0][0]
) break getPaths;
/*ctx.fillStyle = 'green';
ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
rayPaths.push(path);
lastPaths[j] = path;
}
}
coords = [];
function intersectLines(coord1, coord2, rays) {
x1 = coord1[0], x2 = coord2[0];
y1 = coord1[1], y2 = coord2[1];
x3 = rays[0][0], x4 = rays[1][0];
y3 = rays[0][1], y4 = rays[1][1];
d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) return;
t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return;
}
function callIntersect(shape) {
for (var i = 0; i < rayPaths.length; i++) {
for (var j = 0; j< shape.length; j++) {
if (j < shape.length - 1) {
intersectLines(shape[j], shape[j+1], rayPaths[i]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
}
}
}
}
callIntersect(shape);
return coords;
}
function getPathCoords(boundingBox, offsetX, offsetY, angle) {
coords = [];
// add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
angle = angle + 0.0000000000001 - 90;
boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
x = boundingMidX + offsetX, y = boundingMidY + offsetY;
dx = Math.cos(Math.PI * angle / 180);
dy = Math.sin(Math.PI * angle / 180);
for(var i = 0; i < 2; i++) {
bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];
if(dx == 0) ix = x, iy = by;
if(dy == 0) iy = y, ix = bx;
tx = (bx - x) / dx;
ty = (by - y) / dy;
if(tx <= ty) {
ix = bx, iy = y + tx * dy;
} else {
iy = by, ix = x + ty * dx;
}
coords.push([ix, iy]);
dx = -dx;
dy = -dy;
}
return coords;
}
<canvas id="canvas" width="500" height="500"></canvas>
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
lineSpacing = 10;
angle = 45;
shape = [
[200,10], // x, y
[10,300],
[200,200],
[400,300]
];
lines = getLineSegments(shape, angle, lineSpacing);
for(var i = 0; i < lines.length; i += 2) {
start = lines[i];
end = lines[i+1];
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.closePath();
ctx.stroke();
}
function getLineSegments(shape, angle, lineSpacing) {
boundingBox = [
[Infinity,Infinity],
[-Infinity,-Infinity]
]
// get bounding box coords
for(var i in shape) {
if(shape[i][0] < boundingBox[0][0]) boundingBox[0][0] = shape[i][0];
if(shape[i][1] < boundingBox[0][1]) boundingBox[0][1] = shape[i][1];
if(shape[i][0] > boundingBox[1][0]) boundingBox[1][0] = shape[i][0];
if(shape[i][1] > boundingBox[1][1]) boundingBox[1][1] = shape[i][1];
}
boundingBox[0][0] -= 1, boundingBox[0][1] -= 1;
boundingBox[1][0] += 1, boundingBox[1][1] += 1;
// display bounding box
ctx.fillStyle = 'rgba(255,0,0,.2)';
ctx.fillRect(boundingBox[0][0], boundingBox[0][1], boundingBox[1][0]-boundingBox[0][0], boundingBox[1][1]-boundingBox[0][1]);
// display shape (boundary)
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
for(var i = 1; i < shape.length; i++) {
ctx.lineTo(shape[i][0], shape[i][1]);
}
ctx.closePath();
ctx.fillStyle = 'rgba(100,255,100,1)';
ctx.fill();
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
rayPaths = [];
path = getPathCoords(boundingBox, 0, 0, angle);
rayPaths.push(path);
/*ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
getPaths:
for(var i = 0, lastPaths = [path, path]; true; i++) {
for(var j = 0; j < 2; j++) {
pathMidX = (lastPaths[j][0][0] + lastPaths[j][1][0]) / 2;
pathMidY = (lastPaths[j][0][1] + lastPaths[j][1][1]) / 2;
pathVectorX = lastPaths[j][1][1] - lastPaths[j][0][1];
pathVectorY = lastPaths[j][1][0] - lastPaths[j][0][0];
pathLength = Math.sqrt(pathVectorX * pathVectorX + pathVectorY * pathVectorY);
pathOffsetPointX = pathMidX + ((j % 2 === 0 ? pathVectorX : -pathVectorX) / pathLength * lineSpacing);
pathOffsetPointY = pathMidY + ((j % 2 === 0 ? -pathVectorY : pathVectorY) / pathLength * lineSpacing);
offsetX = pathOffsetPointX-boundingMidX;
offsetY = pathOffsetPointY-boundingMidY;
path = getPathCoords(boundingBox, offsetX, offsetY, angle);
if(
path[0][0] < boundingBox[0][0] ||
path[1][0] > boundingBox[1][0] ||
path[0][0] > boundingBox[1][0] ||
path[1][0] < boundingBox[0][0]
) break getPaths;
/*ctx.fillStyle = 'green';
ctx.fillRect(pathOffsetPointX-2.5, pathOffsetPointY-2.5, 5, 5);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
ctx.moveTo(path[0][0], path[0][1]);
ctx.lineTo(path[1][0], path[1][1]);
ctx.closePath();
ctx.stroke();*/
rayPaths.push(path);
lastPaths[j] = path;
}
}
coords = [];
function intersectLines(coord1, coord2, rays) {
x1 = coord1[0], x2 = coord2[0];
y1 = coord1[1], y2 = coord2[1];
x3 = rays[0][0], x4 = rays[1][0];
y3 = rays[0][1], y4 = rays[1][1];
d = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4);
if (d == 0) return;
t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3 - x4)) / d;
u = ((x2 - x1)*(y1 - y3) - (y2 - y1)*(x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
coords.push([(x1 + t*(x2 - x1)).toFixed(2),(y1 + t*(y2 - y1)).toFixed(2)])
}
return;
}
function callIntersect(shape) {
for (var i = 0; i < rayPaths.length; i++) {
for (var j = 0; j< shape.length; j++) {
if (j < shape.length - 1) {
intersectLines(shape[j], shape[j+1], rayPaths[i]);
} else {
intersectLines(shape[0], shape[shape.length - 1], rayPaths[i]);
}
}
}
}
callIntersect(shape);
return coords;
}
function getPathCoords(boundingBox, offsetX, offsetY, angle) {
coords = [];
// add decimal places otherwise can lead to Infinity, subtract 90 so 0 degrees is at the top
angle = angle + 0.0000000000001 - 90;
boundingBoxWidth = boundingBox[1][0] - boundingBox[0][0];
boundingBoxHeight = boundingBox[1][1] - boundingBox[0][1];
boundingMidX = ((boundingBox[0][0]+boundingBox[1][0]) / 2);
boundingMidY = ((boundingBox[0][1]+boundingBox[1][1]) / 2);
x = boundingMidX + offsetX, y = boundingMidY + offsetY;
dx = Math.cos(Math.PI * angle / 180);
dy = Math.sin(Math.PI * angle / 180);
for(var i = 0; i < 2; i++) {
bx = (dx > 0) ? boundingBoxWidth+boundingBox[0][0] : boundingBox[0][0];
by = (dy > 0) ? boundingBoxHeight+boundingBox[0][1] : boundingBox[0][1];
if(dx == 0) ix = x, iy = by;
if(dy == 0) iy = y, ix = bx;
tx = (bx - x) / dx;
ty = (by - y) / dy;
if(tx <= ty) {
ix = bx, iy = y + tx * dy;
} else {
iy = by, ix = x + ty * dx;
}
coords.push([ix, iy]);
dx = -dx;
dy = -dy;
}
return coords;
}
<canvas id="canvas" width="500" height="500"></canvas>

canvas - get pixels on a line

I need a function that accept 5 arguments (ctx, startX, startY, endX, endY). It should return pixels on a canvas that lay on the line, that starts on (startX, startY) and ends on (endX, endY). How can I implement it?
You can use Brensenham line algorithm. It will get each pixel without needing to check if you already have that pixel which many other line methods would need.
function getPixelsOnLine(ctx, startX, startY, endX, endY){
const imageData = ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height);
const data = imageData.data;
const pixelCols = [];
const getPixel = (x,y) => {
if(x < 0 || x >= imageData.width || y < 0 || y >= imageData.height ){
return "rgba(0,0,0,0)";
}
const ind = (x + y * imageData.width) * 4;
return `rgba(${data[ind++]},${data[ind++]},${data[ind++]},${data[ind++]/255})`;
}
var x = Math.floor(startX);
var y = Math.floor(startY);
const xx = Math.floor(endX);
const yy = Math.floor(endY);
const dx = Math.abs(xx - x);
const sx = x < xx ? 1 : -1;
const dy = -Math.abs(yy - y);
const sy = y < yy ? 1 : -1;
var err = dx + dy;
var e2;
var end = false;
while (!end) {
pixelCols.push(getpixel(x,y));
if ((x === xx && y === yy)) {
end = true;
} else {
e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x += sx;
}
if (e2 <= dx) {
err += dx;
y += sy;
}
}
}
return pixelCols;
}
Function returns array of pixel as CSS color values rgba(red,green,blue,alpha) on line from start to end.

X-neighbors of a pixel JavaScript

I'm working on image processing using JavaScript and I would like to you know if there was any generic formula to determine the x-neighbors of a pixel.
I know that for a 3*3 square there is 8 neighbors that can be determine using a specific x and y pixel.
(x-1,y-1) , (x-1,y) , (x-1,y+1),
(x,y-1) , (x,y) , (x,y+1),
(x+1,y-1) , (x+1,y) , (x+1,y+1).
The problem is that I'm working with 5*5 squares,7*7 squares and 9*9 squares and I was wondering if there was any way to have all the x-neighbors of a pixel from those three squares without having to write the location manually in my program.
Thanks
var size = 5;
var d = Math.floor(size / 2);
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
// Do something with (x + dx, y + dy)
}
}
}
If you're doing this is a lot (i.e. for every pixel in an image), it might be worth first creating a flat array of value first:
var size = 5;
var d = Math.floor(size / 2);
var dPixels = [];
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
dPixels.push(dx, dy);
}
}
}
// Then for each pixel (x, y)
for (var i = 0; i < dPixels.length; i += 2){
// Do something with x + dPixels[i], y + dPixels[i + 1]
}

Smooth canvas line and on curser start point

I am attempting to create a canvas web application and having two main problems. I would love to make the pen tool draw more smoothly. Secondly, each time I clear the sketch and begin to draw again the line begins in a different point to the curser/mouse.
Here is javascript for my drawing tool:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var radius = 10;
var dragging = false;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context.lineWidth = radius*2;
var putPoint = function(e){
if(dragging){
context.lineTo(e.clientX, e.clientY);
context.stroke();
context.beginPath();
context.arc(e.clientX, e.clientY, radius, 0, Math.PI*2);
context.fill();
context.beginPath();
context.moveTo(e.clientX, e.clientY);
}}
var engage = function(){
dragging = true;
}
var disengage = function(){
dragging = false;
}
canvas.addEventListener('mousedown', engage);
canvas.addEventListener('mousemove', putPoint);
canvas.addEventListener('mouseup', disengage);
and this is how I am clearing the sketch:
// JavaScript Document
// bind event handler to clear button
document.getElementById('clear').addEventListener('click', function() {
context.clearRect(0, 0, canvas.width, canvas.height);
}, false);
A live preview can be seen at: http://www.sarahemily.net/canvas/
THANKS FOR YOUR HELP!
First the problem of the line starting in the wrong spot. You are forgetting to finish the path you create. You have beginPath, and moveTo but you leave it hanging. You need to call stroke once when the mouse button is up.
Smoothing.
Line smoothing is a very complicated thing to do with many professional drawing apps tackling the problem with a variety of solutions. There does not seem to be one agreed upon method. The big problem is.. How do you smooth a line but not destroy the desired line? and How do you do it quickly????
Here I present a two stage process.
Reduce the line complexity
Step one, reduce the line complexity. Sampling the mouse gives way to many points. So I need to reduce the number of points, but not lose any details.
I use the Ramer–Douglas–Peucker algorithm. It's quick and does a good job of reducing the complexity (number of points) of a line. Below you can find my implementation of the algorithm. It's not the best as it could do with some optimisation. You could most likely find it in some other language and port it to javascript.
It uses a recursive function to reduce complexity based on length and angle between line segments. At its core is the dot product of two line segments, it is a quick way of determining the angle between the two segments. See the supplied link above for more details.
// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points: are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length: is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
var simplify = function(start, end) { // recursive simplifies points from start to end
var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
p1 = points[start];
p2 = points[end];
xx = p1[0];
yy = p1[1];
ddx = p2[0] - xx;
ddy = p2[1] - yy;
dist1 = (ddx * ddx + ddy * ddy);
maxDist = length;
for (var i = start + 1; i < end; i++) {
p = points[i];
if (ddx !== 0 || ddy !== 0) {
// dot product
t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
if (t > 1) {
dx = p[0] - p2[0];
dy = p[1] - p2[1];
} else
if (t > 0) {
dx = p[0] - (xx + ddx * t);
dy = p[1] - (yy + ddy * t);
} else {
dx = p[0] - xx;
dy = p[1] - yy;
}
}else{
dx = p[0] - xx;
dy = p[1] - yy;
}
dist = dx * dx + dy * dy
if (dist > maxDist) {
index = i;
maxDist = dist;
}
}
if (maxDist > length) { // continue simplification while maxDist > length
if (index - start > 1){
simplify(start, index);
}
newLine.push(points[index]);
if (end - index > 1){
simplify(index, end);
}
}
}
var end = points.length - 1;
var newLine = [points[0]];
simplify(0, end);
newLine.push(points[end]);
return newLine;
}
Smoothing using bezier curves
Next the smoothing. As the line has been simplified it if reasonably quick to then compare the angles between the many lines and create a bezier if the angle is below a required threshold.
Below is a example of how I do it. Though this will not fit the original line it is just concerned with smoothing. It is again a bit of a hack on my part and not based on any tried and tested algorithm. I have another one that does a bezier fit but that is too slow for the example.
Basicly it steps through the line segments and calculates the angle between two segments, if the angle is below the threshold it then adds bezier control points along the tangent of the two line segments, making either 2nd order or 3rd order beziers depending on whether two consecutive points are smoothed. This is a stripped down version of a much more complicated algorithm so excuse the mess.
// This is my own smoothing method The blindman`s smoother
// It creates a set of bezier control points either 2nd order or third order
// bezier curves.
// points: list of points [[x,y],[x,y],...,[x,y]]
// cornerThres: when to smooth corners and represents the angle between to lines.
// When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points
// returns [[x,y],[x,y,bx,by],[x,y,b1x,b1y,b2x,b2y],.....] with x and y line points
// bx,by control points for 2nd order bezier and b1x,b1y,b2x,b2y the control
// points for 3rd order bezier. These are mixed as needed. Test the length of
// each point array to work out which bezier if any to use.
var smoothLine = function(points,cornerThres,match){ // adds bezier control points at points if lines have angle less than thres
var p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
function dot(x, y, xx, yy) { // get do product
// dist1,dist2,nx1,nx2,ny1,ny2 are the length and normals and used outside function
// normalise both vectors
dist1 = Math.sqrt(x * x + y * y); // get length
if (dist1 > 0) { // normalise
nx1 = x / dist1 ;
ny1 = y / dist1 ;
}else {
nx1 = 1; // need to have something so this will do as good as anything
ny1 = 0;
}
dist2 = Math.sqrt(xx * xx + yy * yy);
if (dist2 > 0) {
nx2 = xx / dist2;
ny2 = yy / dist2;
}else {
nx2 = 1;
ny2 = 0;
}
return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
}
newPoints = []; // array for new points
aLen = points.length;
if(aLen <= 2){ // nothing to if line too short
for(i = 0; i < aLen; i ++){ // ensure that the points are copied
newPoints.push([points[i][0],points[i][1]]);
}
return newPoints;
}
p1 = points[0];
endP =points[aLen-1];
i = 0; // start from second poitn if line not closed
closed = false;
len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
if(len < Math.SQRT2){ // end points are the same. Join them in coordinate space
endP = p1;
i = 0; // start from first point if line closed
p1 = points[aLen-2];
closed = true;
}
newPoints.push([points[i][0],points[i][1]])
for(; i < aLen-1; i++){
p2 = points[i];
p3 = points[i + 1];
angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
if(dist1 !== 0){ // dist1 and dist2 come from dot function
if( angle < cornerThres*3.14){ // bend it if angle between lines is small
if(match){
dist1 = Math.min(dist1,dist2);
dist2 = dist1;
}
// use the two normalized vectors along the lines to create the tangent vector
x = (nx1 + nx2) / 2;
y = (ny1 + ny2) / 2;
len = Math.sqrt(x * x + y * y); // normalise the tangent
if(len === 0){
newPoints.push([p2[0],p2[1]]);
}else{
x /= len;
y /= len;
if(newPoints.length > 0){
var np = newPoints[newPoints.length-1];
np.push(p2[0]-x*dist1*0.25);
np.push(p2[1]-y*dist1*0.25);
}
newPoints.push([ // create the new point with the new bezier control points.
p2[0],
p2[1],
p2[0]+x*dist2*0.25,
p2[1]+y*dist2*0.25
]);
}
}else{
newPoints.push([p2[0],p2[1]]);
}
}
p1 = p2;
}
if(closed){ // if closed then copy first point to last.
p1 = [];
for(i = 0; i < newPoints[0].length; i++){
p1.push(newPoints[0][i]);
}
newPoints.push(p1);
}else{
newPoints.push([points[points.length-1][0],points[points.length-1][1]]);
}
return newPoints;
}
As I did not put that much thought into ease of use you will have to use the following function to render the resulting line.
var drawSmoothedLine = function(line){
var i,p;
ctx.beginPath()
ctx.moveTo(line[0][0],line[0][1])
for(i = 0; i < line.length-1; i++){
p = line[i];
p1 = line[i+1]
if(p.length === 2){ // linear
ctx.lineTo(p[0],p[1])
}else
if(p.length === 4){ // bezier 2nd order
ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
}else{ // bezier 3rd order
ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
}
}
if(p.length === 2){
ctx.lineTo(p1[0],p1[1])
}
ctx.stroke();
}
So to use these to smooth a line. Simply capture the mouse points as you draw. When the done, then send the points to both functions in turn. Erase the drawn line and replace it with the new line. The is a bit of a lag between pen up and the smoothed result, but there is plenty of room for improvement in both functions.
To put it all together I have added a snippet below. The two bars at the top left control the smoothing and detail. The bottom bar controls the first function described above and the top controls the smoothing (bezier) the more red you see the smoother the lines and greater the detail reduction.
Middle mouse button clears or just restart.
Sorry, this was more work than I expected so the comments are a little sparse. I will improve the comments as time permits..
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
// mouse stuff
var mouse = {
x:0,
y:0,
buttonLastRaw:0, // user modified value
buttonRaw:0,
buttons:[1,2,4,6,5,3], // masks for setting and clearing button raw bits;
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
if(event.type === "mousedown"){ mouse.buttonRaw |= mouse.buttons[event.which-1];
}else if(event.type === "mouseup"){mouse.buttonRaw &= mouse.buttons[event.which+2];
}else if(event.type === "mouseout"){ mouse.buttonRaw = 0; mouse.over = false;
}else if(event.type === "mouseover"){ mouse.over = true; }
event.preventDefault();
}
canvas.addEventListener('mousemove',mouseMove);
canvas.addEventListener('mousedown',mouseMove);
canvas.addEventListener('mouseup' ,mouseMove);
canvas.addEventListener('mouseout' ,mouseMove);
canvas.addEventListener('mouseover' ,mouseMove);
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
var simplify = function(start, end) { // recursize simplifies points from start to end
var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
p1 = points[start];
p2 = points[end];
xx = p1[0];
yy = p1[1];
ddx = p2[0] - xx;
ddy = p2[1] - yy;
dist1 = (ddx * ddx + ddy * ddy);
maxDist = length;
for (var i = start + 1; i < end; i++) {
p = points[i];
if (ddx !== 0 || ddy !== 0) {
t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
if (t > 1) {
dx = p[0] - p2[0];
dy = p[1] - p2[1];
} else
if (t > 0) {
dx = p[0] - (xx + ddx * t);
dy = p[1] - (yy + ddy * t);
} else {
dx = p[0] - xx;
dy = p[1] - yy;
}
}else{
dx = p[0] - xx;
dy = p[1] - yy;
}
dist = dx * dx + dy * dy
if (dist > maxDist) {
index = i;
maxDist = dist;
}
}
if (maxDist > length) { // continue simplification while maxDist > length
if (index - start > 1){
simplify(start, index);
}
newLine.push(points[index]);
if (end - index > 1){
simplify(index, end);
}
}
}
var end = points.length - 1;
var newLine = [points[0]];
simplify(0, end);
newLine.push(points[end]);
return newLine;
}
// This is my own smoothing method
// It creates a set of bezier control points either 2nd order or third order
// bezier curves.
// points: list of points
// cornerThres: when to smooth corners and represents the angle between to lines.
// When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points
var smoothLine = function(points,cornerThres,match){ // adds bezier control points at points if lines have angle less than thres
var p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
function dot(x, y, xx, yy) { // get do product
// dist1,dist2,nx1,nx2,ny1,ny2 are the length and normals and used outside function
// normalise both vectors
dist1 = Math.sqrt(x * x + y * y); // get length
if (dist1 > 0) { // normalise
nx1 = x / dist1 ;
ny1 = y / dist1 ;
}else {
nx1 = 1; // need to have something so this will do as good as anything
ny1 = 0;
}
dist2 = Math.sqrt(xx * xx + yy * yy);
if (dist2 > 0) {
nx2 = xx / dist2;
ny2 = yy / dist2;
}else {
nx2 = 1;
ny2 = 0;
}
return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
}
newPoints = []; // array for new points
aLen = points.length;
if(aLen <= 2){ // nothing to if line too short
for(i = 0; i < aLen; i ++){ // ensure that the points are copied
newPoints.push([points[i][0],points[i][1]]);
}
return newPoints;
}
p1 = points[0];
endP =points[aLen-1];
i = 0; // start from second poitn if line not closed
closed = false;
len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
if(len < Math.SQRT2){ // end points are the same. Join them in coordinate space
endP = p1;
i = 0; // start from first point if line closed
p1 = points[aLen-2];
closed = true;
}
newPoints.push([points[i][0],points[i][1]])
for(; i < aLen-1; i++){
p2 = points[i];
p3 = points[i + 1];
angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
if(dist1 !== 0){ // dist1 and dist2 come from dot function
if( angle < cornerThres*3.14){ // bend it if angle between lines is small
if(match){
dist1 = Math.min(dist1,dist2);
dist2 = dist1;
}
// use the two normalized vectors along the lines to create the tangent vector
x = (nx1 + nx2) / 2;
y = (ny1 + ny2) / 2;
len = Math.sqrt(x * x + y * y); // normalise the tangent
if(len === 0){
newPoints.push([p2[0],p2[1]]);
}else{
x /= len;
y /= len;
if(newPoints.length > 0){
var np = newPoints[newPoints.length-1];
np.push(p2[0]-x*dist1*0.25);
np.push(p2[1]-y*dist1*0.25);
}
newPoints.push([ // create the new point with the new bezier control points.
p2[0],
p2[1],
p2[0]+x*dist2*0.25,
p2[1]+y*dist2*0.25
]);
}
}else{
newPoints.push([p2[0],p2[1]]);
}
}
p1 = p2;
}
if(closed){ // if closed then copy first point to last.
p1 = [];
for(i = 0; i < newPoints[0].length; i++){
p1.push(newPoints[0][i]);
}
newPoints.push(p1);
}else{
newPoints.push([points[points.length-1][0],points[points.length-1][1]]);
}
return newPoints;
}
// creates a drawable image
var createImage = function(w,h){
var image = document.createElement("canvas");
image.width = w;
image.height =h;
image.ctx = image.getContext("2d");
return image;
}
// draws the smoothed line with bezier control points.
var drawSmoothedLine = function(line){
var i,p;
ctx.beginPath()
ctx.moveTo(line[0][0],line[0][1])
for(i = 0; i < line.length-1; i++){
p = line[i];
p1 = line[i+1]
if(p.length === 2){ // linear
ctx.lineTo(p[0],p[1])
}else
if(p.length === 4){ // bezier 2nd order
ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
}else{ // bezier 3rd order
ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
}
}
if(p.length === 2){
ctx.lineTo(p1[0],p1[1])
}
ctx.stroke();
}
// smoothing settings
var lineSmooth = {};
lineSmooth.lengthMin = 8; // square of the pixel length
lineSmooth.angle = 0.8; // angle threshold
lineSmooth.match = false; // not working.
// back buffer to save the canvas allowing the new line to be erased
var backBuffer = createImage(canvas.width,canvas.height);
var currentLine = [];
mouse.lastButtonRaw = 0; // add mouse last incase not there
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.clearRect(0,0,canvas.width,canvas.height);
var drawing = false; // if drawing
var input = false; // if menu input
var smoothIt = false; // flag to allow feedback that smoothing is happening as it takes some time.
function draw(){
// if not drawing test for menu interaction and draw the menus
if(!drawing){
if(mouse.x < 203 && mouse.y < 24){
if(mouse.y < 13){
if(mouse.buttonRaw === 1){
ctx.clearRect(3,3,200,10);
lineSmooth.angle = (mouse.x-3)/200;
input = true;
}
}else
if(mouse.buttonRaw === 1){
ctx.clearRect(3,14,200,10);
lineSmooth.lengthMin = (mouse.x-3)/10;
input = true;
}
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
if(mouse.buttonRaw === 0 && input){
input = false;
mouse.lastButtonRaw = 0;
}
ctx.lineWidth = 1;
ctx.fillStyle = "red";
ctx.fillRect(3,3,lineSmooth.angle*200,10);
ctx.fillRect(3,14,lineSmooth.lengthMin*10,10);
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillStyle = "#5F2"
ctx.strokeRect(3,3,200,10);
ctx.fillText("Smooth",5,2)
ctx.strokeRect(3,14,200,10);
ctx.fillText("Detail",5,13);
}else{
canvas.style.cursor = "crosshair";
}
if(!input){
ctx.lineWidth = 3;
if(mouse.buttonRaw === 1 && mouse.lastButtonRaw === 0){
currentLine = [];
drawing = true;
backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
backBuffer.ctx.drawImage(canvas,0,0);
currentLine.push([mouse.x,mouse.y])
}else
if(mouse.buttonRaw === 1){
var lp = currentLine[currentLine.length-1]; // get last point
// dont record point if no movement
if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
currentLine.push([mouse.x,mouse.y]);
ctx.beginPath();
ctx.moveTo(lp[0],lp[1])
ctx.lineTo(mouse.x,mouse.y);
ctx.stroke();
}
}else
if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 1){
ctx.textAlign = "center"
ctx.fillStyle = "red"
ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
smoothIt = true;
}else
if(smoothIt){
smoothIt = false;
var newLine = smoothLine(
simplifyLineRDP(
currentLine,
lineSmooth.lengthMin
),
lineSmooth.angle,
lineSmooth.match
);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(backBuffer,0,0);
drawSmoothedLine(newLine);
drawing = false;
}
}
// middle button clear
if(mouse.buttonRaw === 2){
ctx.clearRect(0,0,canvas.width,canvas.height);
}
mouse.lastButtonRaw = mouse.buttonRaw;
requestAnimationFrame(draw);
}
draw();
.canC { width:1000px; height:500px;}
<canvas class="canC" id="canV" width=1000 height=500></canvas>
I would love to make the pen tool draw more smoothly
Use can use quadratic curves instead of lines:
ctx.quadraticCurveTo(cpx, cpy, x, y);
Example: http://www.w3schools.com/tags/canvas_quadraticcurveto.asp

Javascript animation move diagonally and reverse

I'm having trouble trying to make an animation with JavaScript and the HTML5 canvas element.
I need to have an animation that starts in the bottom right corner of my canvas, which moves up diagonally to the top right hand corner and then reverses back the other direction to create a back and forth movement.
I can get my animation to move diagonally, but then I can't get it to change the direction.
I'm also having trouble getting the animation to start from the bottom no matter what I do.
The code below currently moves my animation top to bottom diagonally.
var context;
var x = 0;
var y = 0;
var stepx = 1.55;
var stepy = 1.55;
function setupAnimCanvas() {
var canvas = document.getElementById('assignmenttwoCanvas');
if (canvas.getContext) {
ctx = canvas.getContext('2d');
setInterval('draw();', 20);
img = new Image();
img.src = '../images/dragon.png';
//animation();
}
}
startPos = (500, 500);
endPos = (0, 0);
function draw() {
ctx.clearRect(0,0, 500,500);
drawBackground();
ctx.drawImage(img, y, x);
x += 3;
if(x < endPos){
x -= stepx;
y += stepy;
}
else if (y > endPos) {
x += stepx;
y -= stepy;
}
else {
endPos = startPos;startPos = y;
}
moveit();
setTimeout('mover()',25);
}
You can use a delta value for x and y. When the x or y position is at start or end you just reverse the delta by setting delta = -delta;
var dx = -3, dy = -3;
Then simply check for any criteria that would reverse the direction:
if (x < endPos[0] || x > startPos[0] || y < endPos[1] || y > startPos[1] ) {
dx = -dx;
dy = -dy;
}
See demo here:
http://jsfiddle.net/AbdiasSoftware/5dSSZ/
The essence of this (see comments and original source lines at fiddler-link)
var startPos = [500, 500];
var endPos = [0, 0];
var dx = -3, dy = -3;
var x = startPos[0], y = startPos[1];
function draw() {
ctx.clearRect(0, 0, 500, 500);
ctx.fillRect(x, y, 20, 20); //for demo as no image
x += dx;
y += dy;
if (x < endPos[0] || x > startPos[0] ||
y < endPos[1] || y > startPos[1] ) {
dx = -dx;
dy = -dy;
}
setTimeout(draw, 16); //requestAnimationFrame is a better choice
}

Categories