Extend Line based on slope to the end of canvas/drawing area - javascript

I am trying to extend a line (from to points(X,Y)) to the end of the drawing area.
so far i found a couple of instructions on how to calculate the extension end point.
however i don't really get it done it works in one direction and breaks as soon as you reach over the middle point.
see attached code sample (the real product i am working on is in swift, but as it is not a programming language related issue, i ported it to javascript)
on the right side it seems to work, black line is the one the user selects, red one is the extension to the edge of canvas, going to the left side produces garbage.
var canvas = document.getElementById("myCanvas");
var endPoint = {
x: 200,
y: 200
};
function draw() {
//Demo only in final product user also can select the startpoint
startPoint = {
x: 150,
y: 150
}
screenMax = {
x: canvas.height,
y: canvas.width
}
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(startPoint.x, startPoint.y);
ctx.lineTo(endPoint.x, endPoint.y);
ctx.strokeStyle = "#000000";
ctx.stroke();
//Extend line to end of canvas according to slope
var slope = 1.0
var extendedPoint = {
x: 0,
y: 0
}
if (endPoint.x != startPoint.x) {
slope = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x);
extendedPoint = {
x: screenMax.x,
y: slope * (screenMax.x - endPoint.x) + endPoint.y
}
} else {
slope = 0
extendedPoint.x = endPoint.x;
extendedPoint.y = screenMax.y;
}
console.log(endPoint);
//Draw the Extension
ctx.beginPath();
ctx.moveTo(endPoint.x, endPoint.y);
ctx.lineTo(extendedPoint.x, extendedPoint.y);
ctx.strokeStyle = "#FF0000";
ctx.stroke();
}
//initial draw
draw();
//handle Mouse dOwn
canvas.onmousedown = function(e) {
handleMouseDown(e);
}
// handle the mousedown event
//Set new endpoint
function handleMouseDown(e) {
mouseX = parseInt(e.clientX);
mouseY = parseInt(e.clientY);
endPoint = {
x: mouseX,
y: mouseY
}
draw();
}
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</body>
</html>

This function may help, takes the line x1,y1 to x2,y2 and extends it to the border defined by left,top,right,bottom returning the intercept point as {x:?,y:?}
function toBorder(x1, y1, x2, y2, left, top, right, bottom){
var dx, dy, py, vx, vy;
vx = x2 - x1;
vy = y2 - y1;
dx = vx < 0 ? left : right;
dy = py = vy < 0 ? top : bottom;
if(vx === 0){
dx = x1;
}else if(vy === 0){
dy = y1;
}else{
dy = y1 + (vy / vx) * (dx - x1);
if(dy < top || dy > bottom){
dx = x1 + (vx / vy) * (py - y1);
dy = py;
}
}
return {x : dx, y : dy}
}

Slope approach is not universal - it cannot work with vertical lines (x0=x1).
I'd use parametric representation of ray (line)
x0 = startPoint.x
x1 = endPoint.x
y0 = startPoint.y
y1 = endPoint.y
dx = x1 - x0
dy = y1 - y0
x = x0 + dx * t
y = y0 + dy * t
Now check what border will be intersected first (with smaller t value)
//prerequisites: potential border positions
if dx > 0 then
bx = width
else
bx = 0
if dy > 0 then
by = height
else
bx = 0
//first check for horizontal/vertical lines
if dx = 0 then
return ix = x0, iy = by
if dy = 0 then
return iy = y0, ix = bx
//in general case find parameters of intersection with horizontal and vertical edge
tx = (bx - x0) / dx
ty = (by - y0) / dy
//and get intersection for smaller parameter value
if tx <= ty then
ix = bx
iy = y0 + tx * dy
else
iy = by
ix = x0 + ty * dx
return ix, iy

Related

ClearRect is removing everything on the Canvas

Trying to get this hover function working, but when I hover it clears the entire canvas. Can't figure out what the clearRect is clearing, and how I can focus it on only clearing the rectangle I've drawn in the statement. Any guidance - some of the canvas rendering simply depends on order in code.
function handleMouseMove(e) {
e.preventDefault();
e.stopPropagation();
let mouseX = e.clientX - offsetX;
let mouseY = e.clientY - offsetY;
for (var i = 0; i < t.length; i++) {
let cx = t[i].Criticality * 100 + 50;
let cy = t[i].Score + 50;
var dx = mouseX - cx;
var dy = mouseY - cy;
ctx.clearRect(0, 0, dx, dy);
if (dx * dx + dy * dy < 15 * 15) {
draw();
ctx.beginPath();
ctx.fillStyle = "white";
ctx.fillRect(mouseX, mouseY, 130, 70);
ctx.stroke();
}
}
}

given 2 center coordinates, how to find all Rectangle axes?

for a game I'm building, I need to draw a rectangle on two sides of a line made from two coordinates.
I have an image illustrating this "hard to ask" question.
given coordinates (-4,3) and (3, -4)
given that the width of the rectangle will be 4 (for example)
I need to find all (x1, y1), (x2, y2), (x3, y3), (x4, y4)
** I need to write this in Javascript eventually.
your help is much appreciated.
I've tried to solve this using javascript & canvas. The problem is that the coordinates in canvas are upside down, I suppose you already know this. Also since your rect would be extremely small, I've multiplied your numbers by 10.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 300,
cx = cw / 2;
let ch = canvas.height = 300,
cy = ch / 2;
const rad = Math.PI / 180;
ctx.translate(cx,cy)
//axis
ctx.strokeStyle = "#d9d9d9";
ctx.beginPath();
ctx.moveTo(-cx,0);
ctx.lineTo(cx,0);
ctx.moveTo(0,-cy);
ctx.lineTo(0,cy);
ctx.stroke();
// your data
let p1={x:-40,y:30};
let p2={x:30,y:-40};
// the angle of the initial line
let angle = Math.atan2(p2.y-p1.y, p2.x-p1.x);
// the center of the line
let c =
{ x: p1.x + (p2.x - p1.x)/2,
y: p1.y + (p2.y - p1.y)/2
}
let w = dist(p1, p2);//the width of the rect
let h = 60;//the height of the rect
// draw the initial line
line(p1,p2);
// draw the center as a red point
marker(c);
// calculate the opoints of the rect
function rectPoints(w,h){
let p1 = {
x : c.x -w/2,
y : c.y -h/2
}
let p2 = {
x : c.x + w/2,
y : c.y -h/2
}
let p3 = {
x : c.x + w/2,
y : c.y +h/2
}
let p4 = {
x : c.x -w/2,
y : c.y +h/2
}
// this rotate all the points relative to the center c
return [
rotate(p1,c, angle),
rotate(p2,c, angle),
rotate(p3,c, angle),
rotate(p4,c, angle)
]
}
// draw the rect
ctx.strokeStyle = "blue";
drawRect(rectPoints(w,h));
// some helpful functions
function line(p1,p2){
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function marker(p,color){
ctx.beginPath();
ctx.fillStyle = color || "red";
ctx.arc(p.x,p.y,4,0,2*Math.PI);
ctx.fill();
}
function rotate(p,c, angle){
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {
x: c.x + (p.x - c.x) * cos - (p.y - c.y) * sin,
y: c.y + (p.x - c.x) * sin + (p.y - c.y) * cos
}
}
function drawRect(points){
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
ctx.lineTo(points[1].x,points[1].y);
ctx.lineTo(points[2].x,points[2].y);
ctx.lineTo(points[3].x,points[3].y);
ctx.lineTo(points[0].x,points[0].y);
ctx.closePath();
ctx.stroke();
}
canvas{border:1px solid #d9d9d9}
<canvas></canvas>
Points A, B form vector
M.X = B.X - A.X
M.Y = B.Y - A.Y
Perpendicular vector
P.X = -M.Y
P.Y = M.X
Length of P:
Len = Math.sqrt(P.X*P.X + P.Y*P.Y)
Normalized (unit) perpendicular:
uP.X = P.X / Len
uP.Y = P.Y / Len
Points
X1 = A.X + uP.X * HalfWidth
Y1 = A.Y + uP.Y * HalfWidth
(X4, Y4) = (A.X - uP.X * HalfWidth, A.Y - uP.Y * HalfWidth)
and similar for points 2 and 3 around B

Drawing soft brush

I'm trying to create a smooth brush in HTML5, an example is below.
This is what I tried, it's something. But it's not as smooth as the image above.
Editor.Drawing.Context.globalAlpha = 0.3;
var amount = 3;
for(var t = -amount; t <= amount; t += 3) {
for(var n = -amount; n <= amount; n += 3) {
Editor.Drawing.Context.drawImage(Editor.Drawing.ClipCanvas, -(n-1), -(t-1));
}
}
And it looks like this.
Using brushes
Choose a brush, this can be an image with predefined brushes or you can make one using an off-screen canvas and draw a radial gradient into it. For simplicity I made a simple image brush such as these:
Then for each new point drawn to the canvas:
Calculate the diff between the previous and current point
Calculate the length of the line so we can use an absolute step value independent of length
Iterate over the length using a normalized value and the previously calculated step value
The step value can be anything that looks good as a result - it largely depends on the smoothness of the brush as well as its general size (smoother brushes will require smaller steps to blend into each other).
For this demo I used brush-width, the smaller values that are used, the more brushes will be drawn along the line, nicer result, but can also slow down the program, so find a value that compromises quality and speed.
For example:
This will be called every time a new point is registered when drawing:
function brushLine(ctx, x1, y1, x2, y2) {
var diffX = Math.abs(x2 - x1), // calc diffs
diffY = Math.abs(y2 - y1),
dist = Math.sqrt(diffX * diffX + diffY * diffY), // find length
step = 20 / (dist ? dist : 1), // "resolution"
i = 0, // iterator for length
t = 0, // t [0, 1]
b, x, y;
while (i <= dist) {
t = Math.max(0, Math.min(1, i / dist));
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
b = (Math.random() * 3) | 0;
ctx.drawImage(brush, x - bw * 0.5, y - bh * 0.5); // draw brush
i += step;
}
}
Demo
var brush = new Image();
brush.onload = ready;
brush.src = "//i.stack.imgur.com/HsbVA.png";
function ready() {
var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
isDown = false, px, py,
bw = this.width, bh = this.height;
c.onmousedown = c.ontouchstart = function(e) {
isDown = true;
var pos = getPos(e);
px = pos.x;
py = pos.y;
};
window.onmousemove = window.ontouchmove = function(e) {
if (isDown) draw(e);
};
window.onmouseup = window.ontouchend = function(e) {
e.preventDefault();
isDown = false
};
function getPos(e) {
e.preventDefault();
if (e.touches) e = e.touches[0];
var r = c.getBoundingClientRect();
return {
x: e.clientX - r.left,
y: e.clientY - r.top
}
}
function draw(e) {
var pos = getPos(e);
brushLine(ctx, px, py, pos.x, pos.y);
px = pos.x;
py = pos.y;
}
function brushLine(ctx, x1, y1, x2, y2) {
var diffX = Math.abs(x2 - x1),
diffY = Math.abs(y2 - y1),
dist = Math.sqrt(diffX * diffX + diffY * diffY),
step = bw / (dist ? dist : 1),
i = 0,
t = 0,
b, x, y;
while (i <= dist) {
t = Math.max(0, Math.min(1, i / dist));
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
b = (Math.random() * 3) | 0;
ctx.drawImage(brush, x - bw * 0.5, y - bh * 0.5);
i += step
}
}
}
body {background: #777}
canvas {background: #fff;cursor:crosshair}
<canvas width=630 height=500></canvas>
You can use this technique to simulate a variety of brushes.
Tip: with a small modification you can also variate the width depending on velocity to increase realism (not shown).

Canvas: How would you properly interpolate between two points using Bresenham's line algorithm?

I am making a simple HTML5 Canvas drawing app where a circle is placed at a x and y position each time the mouse moves. The (quite common but unsolved) problem is: when the mouse is moved very fast (as in faster than the mouse move events are triggered), you end up with space in between the circles.
I have used Bresenham's line algorithm to somewhat successfully draw circles between the gaps. However, I have encountered another problem: when the color is one of translucency I get an unintentional fade-to-darker effect.
Here's an example:
I don't understand why this is happening. How would you properly interpolate between two points using Bresenham's line algorithm? Or some other algorithm?
Here's my code: http://jsfiddle.net/E5NBs/
var x = null;
var y = null;
var prevX = null;
var prevY = null;
var spacing = 3;
var drawing = false;
var size = 5;
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
function createFlow(x1, y1, x2, y2, callback) {
var dx = x2 - x1;
var sx = 1;
var dy = y2 - y1;
var sy = 1;
var space = 0;
if (dx < 0) {
sx = -1;
dx = -dx;
}
if (dy < 0) {
sy = -1;
dy = -dy;
}
dx = dx << 1;
dy = dy << 1;
if (dy < dx) {
var fraction = dy - (dx >> 1);
while (x1 != x2) {
if (fraction >= 0) {
y1 += sy;
fraction -= dx;
}
fraction += dy;
x1 += sx;
if (space == spacing) {
callback(x1, y1);
space = 0;
} else {
space += 1;
}
}
} else {
var fraction = dx - (dy >> 1);
while (y1 != y2) {
if (fraction >= 0) {
x1 += sx;
fraction -= dy;
}
fraction += dx;
y1 += sy;
if (space == spacing) {
callback(x1, y1);
space = 0;
} else {
space += 1;
}
}
}
callback(x1, y1);
}
context.fillStyle = '#FFFFFF';
context.fillRect(0, 0, 500, 400);
canvas.onmousemove = function(event) {
x = parseInt(this.offsetLeft);
y = parseInt(this.offsetTop);
if (this.offsetParent != null) {
x += parseInt(this.offsetParent.offsetLeft);
y += parseInt(this.offsetParent.offsetTop);
}
if (navigator.appVersion.indexOf('MSIE') != -1) {
x = (event.clientX + document.body.scrollLeft) - x;
y = (event.clientY + document.body.scrollTop) - y;
} else {
x = event.pageX - x;
y = event.pageY - y;
}
context.beginPath();
if (drawing == true) {
if (((x - prevX) >= spacing || (y - prevY) >= spacing) || (prevX - x) >= spacing || (prevY - y) >= spacing) {
createFlow(x, y, prevX, prevY, function(x, y) {
context.fillStyle = 'rgba(0, 0, 0, 0.1)';
context.arc(x, y, size, 0, 2 * Math.PI, false);
context.fill();
});
prevX = x, prevY = y;
}
} else {
prevX = x, prevY = y;
}
};
canvas.onmousedown = function() {
drawing = true;
};
canvas.onmouseup = function() {
drawing = false;
};
HTML Canvas supports fractional / floating point coordinates, so using an algorithm for integer coordinate based pixel canvas is not necessary and could be considered even counter-productive.
A simple, generic solution would be something along these lines:
when mouse_down:
x = mouse_x
y = mouse_y
draw_circle(x, y)
while mouse_down:
when mouse_moved:
xp = mouse_x
yp = mouse_y
if (x != xp or y != yp):
dir = atan2(yp - y, xp - x)
dist = sqrt(pow(xp - x, 2) + pow(yp - y, 2))
while (dist > 0):
x = x + cos(dir)
y = y + sin(dir)
draw_circle(x, y)
dist = dist - 1
That is, whenever the mouse is moved to a location different from the location of the last circle drawn, walk towards the new location with steps having distance one.
If I understand well, you want to have rgba(0, 0, 0, 0.1) on every point. If so, then you can clear the point before drawing new one.
// this is bad way to clear the point, just I don't know canvas so well
context.fillStyle = 'rgba(255, 255, 255, 1)';
context.arc(x, y, size, 0, 2 * Math.PI, false);
context.fill();
context.fillStyle = 'rgba(0, 0, 0, 0.1)';
context.arc(x, y, size, 0, 2 * Math.PI, false);
context.fill();
I've figured it out.
"context.beginPath();" needed to be in the createFlow callback function like so:
createFlow(x, y, prevX, prevY, function(x, y) {
context.beginPath();
context.fillStyle = 'rgba(0, 0, 0, 0.1)';
context.arc(x, y, size, 0, 2 * Math.PI, false);
context.fill();
});

Create a realistic pencil tool for a painting app with HTML5 Canvas

First I want to say that I made a lot of research and tries myself without any success.
I am working on a MSPaint-like application using Canvas, and I would like to create a pencil tool which looks realistic like handmade drawings... Here is an example in the link below with the default tool :
http://www.onemotion.com/flash/sketch-paint/
I tried to play with mousespeed and linewidth properties but it is not working well (the entire line enlarge and shrink as I move the mouse). I have no idea of an algorithm acting on pixel raw data.
Do you know something existing or a suitable algorithm to apply ? Thank you very much for your help
EDIT
I decided to add the solution I've chosen because it seems to interest lot of people.
So, the best thing I found so far is to draw an image onto the canvas, using the technique explained here : http://css.dzone.com/articles/sketching-html5-canvas-and.
It works like a charm, the result is really convincing and this is quite easy to implement. Try it out here : http://tricedesigns.com/portfolio/sketch/brush.html#
You could try something like the following demo
Live Demo
Your most likely using moveTo and lineTo to create the paths, if you do it that way the properties will be shared for the path until you close the path. So everytime you change the thickness youd need to call closePath and then beginPath again.
In my example I use Bresenham's line algorithm to plot the points. Basically onmousedown it starts painting. Then onmousemove it compares the current coordinates with the last coordinates and plots all of the points between. Its also using fillRect to paint. Based on how fast your moving the line will be thicker or thinner.
Heres the code for the drawing function
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0,
lineThickness = 1;
canvas.width = canvas.height = 600;
ctx.fillRect(0, 0, 600, 600);
canvas.onmousedown = function(e) {
painting = true;
ctx.fillStyle = "#ffffff";
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmouseup = function(e){
painting = false;
}
canvas.onmousemove = function(e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
// find all points between
var x1 = mouseX,
x2 = lastX,
y1 = mouseY,
y2 = lastY;
var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
if (steep){
var x = x1;
x1 = y1;
y1 = x;
var y = y2;
y2 = x2;
x2 = y;
}
if (x1 > x2) {
var x = x1;
x1 = x2;
x2 = x;
var y = y1;
y1 = y2;
y2 = y;
}
var dx = x2 - x1,
dy = Math.abs(y2 - y1),
error = 0,
de = dy / dx,
yStep = -1,
y = y1;
if (y1 < y2) {
yStep = 1;
}
lineThickness = 5 - Math.sqrt((x2 - x1) *(x2-x1) + (y2 - y1) * (y2-y1))/10;
if(lineThickness < 1){
lineThickness = 1;
}
for (var x = x1; x < x2; x++) {
if (steep) {
ctx.fillRect(y, x, lineThickness , lineThickness );
} else {
ctx.fillRect(x, y, lineThickness , lineThickness );
}
error += de;
if (error >= 0.5) {
y += yStep;
error -= 1.0;
}
}
lastX = mouseX;
lastY = mouseY;
}
}
​

Categories

Resources