Related
I created the below image in html and JavaScript where the alphabets curve around the arc of the image. I am trying to convert the code I wrote and properly inject the functions inside of a QT project. However, my current implementation does not work due TypeError: Type errors. Can someone help with this?
Also if possible: I would like to rotate the alphabets to be vertical to the arc instead of horizontal.
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Canvas {
anchors.fill: parent
Image {
id: abcBar
source: "alphabetBar.png"
visible: false
}
onPaint: {
var ctx = getContext('2d');
ctx.save();
ctx.canvas.width = 160;
ctx.canvas.height = 432;
ctx.font = "18px Roboto";
ctx.textAlign = "center";
ctx.fillStyle = "#000000"
const centerX = 10;
const centerY = ctx.canvas.height / 2;
const angle = Math.PI;
const radius = 130
ctx.fillStyle = "#000000"
ctx.restore();
ctx.save();
ctx.drawImage(abcBar, 0, 0, ctx.canvas.width, ctx.canvas.height);
console.log('loaded')
ctx.restore();
const args = {
ctx,
text: "A • D • G • J • M • P • S • V • Z",
offset: 0,
G1: {
x: 20,
y: 80,
},
G2: {
x: 190,
y: 230,
},
G3: {
x: 0,
y: 372
},
}
textOnCurve(args);
}
}
function textOnCurve({ ctx, text, offset, G1, G2, G3, G4}){
const x1 = G1.x;
const y1 = G1.y;
const x2 = G2.x;
const y2 = G2.y;
const x3 = G3.x;
const y3 = G3.y;
const x4 = G3.x;
const y4 = G3.y;
ctx.save();
ctx.textAlign = "center";
var widths = [];
for (var i = 0; i < text.length; i++)
{
widths[widths.length] = ctx.measureText(text[i]).width;
}
ctx.beginPath();
var ch = curveHelper(x1, y1, x2, y2, x3, y3, x4, y4);
ctx.stroke();
var pos = offset;
var cpos = 0;
for (var j = 0; j < text.length; j++)
{
pos += widths[j] / 2;
cpos = ch.forward(pos);
ch.tangent(cpos);
ctx.setTransform(ch.vect.x, ch.vect.y, -ch.vect.y, ch.vect.x, ch.vec.x, ch.vec.y);
ctx.fillText(text[j], 0, 0);
pos += widths[j] / 2;
}
ctx.restore();
}
function curveHelper(x1, y1, x2, y2, x3, y3, x4, y4)
{
var tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4;
var a, b, c, u;
var vec, currentPos, vec1, vect, quad, currentDist;
vec = { x: 0, y: 0 } ;
vec1 = { x: 0, y: 0 } ;
vect = { x: 0, y: 0 } ;
quad = false;
currentPos = 0;
currentDist = 0;
if (x4 === undefined || x4 === null)
{
quad = true;
x4 = x3;
y4 = y3;
}
var estLen = Math.sqrt((x4 - x1)* (x4 - x1)+ (y4 - y1)* (y4 - y1));
var onePix = 1 / estLen;
function posAtC(c)
{
tx1 = x1; ty1 = y1;
tx2 = x2; ty2 = y2;
tx3 = x3; ty3 = y3;
tx1 += (tx2 - tx1)* c;
ty1 += (ty2 - ty1)* c;
tx2 += (tx3 - tx2)* c;
ty2 += (ty3 - ty2)* c;
tx3 += (x4 - tx3)* c;
ty3 += (y4 - ty3)* c;
tx1 += (tx2 - tx1)* c;
ty1 += (ty2 - ty1)* c;
tx2 += (tx3 - tx2)* c;
ty2 += (ty3 - ty2)* c;
vec.x = tx1 + (tx2 - tx1)* c;
vec.y = ty1 + (ty2 - ty1)* c;
return vec;
}
function posAtQ(c)
{
tx1 = x1; ty1 = y1;
tx2 = x2; ty2 = y2;
tx1 += (tx2 - tx1)* c;
ty1 += (ty2 - ty1)* c;
tx2 += (x3 - tx2)* c;
ty2 += (y3 - ty2)* c;
vec.x = tx1 + (tx2 - tx1)* c;
vec.y = ty1 + (ty2 - ty1)* c;
return vec;
}
function forward(dist)
{
var step;
helper.posAt(currentPos);
while (currentDist < dist)
{
vec1.x = vec.x;
vec1.y = vec.y;
currentPos += onePix;
helper.posAt(currentPos);
currentDist += step = Math.sqrt((vec.x - vec1.x)* (vec.x - vec1.x)+ (vec.y - vec1.y)* (vec.y - vec1.y));
}
currentPos -= ((currentDist - dist)/ step)* onePix
currentDist -= step;
helper.posAt(currentPos);
currentDist += Math.sqrt((vec.x - vec1.x)* (vec.x - vec1.x)+ (vec.y - vec1.y)* (vec.y - vec1.y));
return currentPos;
}
function tangentQ(pos)
{
a = (1 - pos)* 2;
b = pos * 2;
vect.x = a * (x2 - x1)+ b * (x3 - x2);
vect.y = a * (y2 - y1)+ b * (y3 - y2);
u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
vect.x /= u;
vect.y /= u;
}
function tangentC(pos)
{
a = (1 - pos)
b = 6 * a * pos;
a *= 3 * a;
c = 3 * pos * pos;
vect.x = -x1 * a + x2 * (a - b)+ x3 * (b - c)+ x4 * c;
vect.y = -y1 * a + y2 * (a - b)+ y3 * (b - c)+ y4 * c;
u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
vect.x /= u;
vect.y /= u;
}
var helper = {
vec: vec,
vect: vect,
forward: forward,
}
if (quad)
{
helper.posAt = posAtQ;
helper.tangent = tangentQ;
} else
{
helper.posAt = posAtC;
helper.tangent = tangentC;
}
return helper
}
}
The reference to Image on line 35 is not going to work because it is a browser-specific JavaScript class which is not available in the QML JavaScript runtime. Even though it seems like the QML Image class you have commented out should be referencable in the runtime, no QML components are directly referencable in JavaScript as classes like this.
You will need to port your code to use Canvas::loadImage() as documented here:
https://doc.qt.io/qt-5/qml-qtquick-canvas.html#loadImage-method
Or you can uncomment your QML Image object and call Context2d::drawImage() with its id as the first argument as documented here:
https://doc.qt.io/qt-5/qml-qtquick-context2d.html#drawImage-method-2
Note, if you do that, the Image will appear twice in the window unless you set visible: false on the QML one.
I am using the following code to draw on HTML5 canvas:
const context = canvas.getContext('2d');
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.closePath();
However, if I print unique values:
console.log(new Set(context.getImageData(0, 0, canvas.width, canvas.height).data))
I can see that the color that I use in fillStyle gets interpolated.
I tried to disable interpolation/smoothing by adding the following flags:
context.imageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.mozImageSmoothingEnabled = false;
However, it does not help. I would highly appreciate if you could advise me how to fix the issue.
The is no native way to draw circles that are pixelated. To do that you must render each pixel manually.
There are several methods you can use to do this. The most common have some additional artifacts (like inconsistent line width) that are hard to avoid.
The following function draw a circle using a modification of the Berzingham line algorithm (also good for rendering pixelated lines) called the Midpoint circle algorithm
Unfortunately most of the methods that can draw arbitrary lines and circle are slow. The two mentioned above are the fastest standard methods I know about.
Example
The example defines 3 functions to draw pixelated circles
pixelPixelatedCircle (Red outer circles and single blue in example) draws a single pixel wide circle using the current fill style
fillPixelatedCircle (Red inner circle in example) draws a a solid circle using the current fill style
strokePixelatedCircle (Black circles in example) draws a circle line with a width. Not the width only works when it is >= 2. If you want a single pixel width use the first function. Also not that this function uses a second canvas to render the circle
The example draws all three types
The outer red circle drawn using pixelPixelatedCircle are to demonstrate that the quality of the circles are consistent. There should be alternating 1 pixel width circles, red and dark red. and an outer blue just touching the canvas edge circles.
For circles less than radius of 2 use ctx.rect as the outcome will be the same.
Note the circle radius is an integer thus a circle radius 1000 will be identical to circle radius 1000.9 The sample applies to the circle center. To be able to have sub pixel positioning and radius will need another algorithm which is slower and has lower quality lines.
Note I added a simple zoom canvas so I could see the results better, I was going to remove it but left it in just for interested people. It is not crucial to the answer.
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const size = Math.min(w, h);
const circleWorkCanvas = document.createElement("canvas");
const cCtx = circleWorkCanvas.getContext("2d");
function resizeCircleCanvas(ctx) {
if (circleWorkCanvas.width !== ctx.canvas.width || circleWorkCanvas.height !== ctx.canvas.height) {
circleWorkCanvas.width = ctx.canvas.width;
circleWorkCanvas.height = ctx.canvas.height;
}
}
strokePixelatedCircle(ctx, w / 2 | 0, h / 2 | 0, size * 0.35, 5);
strokePixelatedCircle(ctx, w / 2 | 0, h / 2 | 0, size * 0.3, 4);
strokePixelatedCircle(ctx, w / 2 | 0, h / 2 | 0, size * 0.25, 3);
strokePixelatedCircle(ctx, w / 2 | 0, h / 2 | 0, size * 0.2, 2);
ctx.fillStyle = "red";
fillPixelatedCircle(ctx, w / 2, h / 2, size * 0.15);
ctx.fillStyle = "blue";
pixelPixelatedCircle(ctx, w / 2, h / 2, size * 0.38);
ctx.fillStyle = "blue";
pixelPixelatedCircle(ctx, w / 2, h / 2, size * 0.5);
ctx.fillStyle = "red";
for(let v = 0.40; v < 0.49; v += 1 / size) {
ctx.fillStyle = "#600"
pixelPixelatedCircle(ctx, w / 2, h / 2, size * v);
ctx.fillStyle = "#F00"
v += 1 / size;
pixelPixelatedCircle(ctx, w / 2, h / 2, size * v );
}
function strokePixelatedCircle(ctx, cx, cy, r, lineWidth) {
resizeCircleCanvas(ctx);
cCtx.clearRect(0, 0, cCtx.canvas.width, cCtx.canvas.height);
cCtx.globalCompositeOperation = "source-over";
cCtx.fillStyle = ctx.strokeStyle;
fillPixelatedCircle(cCtx, cx, cy, r + lineWidth / 2);
cCtx.globalCompositeOperation = "destination-out";
fillPixelatedCircle(cCtx, cx, cy, r - lineWidth / 2);
cCtx.globalCompositeOperation = "source-over";
ctx.drawImage(cCtx.canvas, 0, 0);
}
function fillPixelatedCircle(ctx, cx, cy, r){
r |= 0; // floor radius
ctx.setTransform(1,0,0,1,0,0); // ensure default transform
var x = r, y = 0, dx = 1, dy = 1;
var err = dx - (r << 1);
var x0 = cx - 1| 0, y0 = cy | 0;
var lx = x,ly = y;
ctx.beginPath();
while (x >= y) {
ctx.rect(x0 - x, y0 + y, x * 2 + 2, 1);
ctx.rect(x0 - x, y0 - y, x * 2 + 2, 1);
if (x !== lx){
ctx.rect(x0 - ly, y0 - lx, ly * 2 + 2, 1);
ctx.rect(x0 - ly, y0 + lx, ly * 2 + 2, 1);
}
lx = x;
ly = y;
y++;
err += dy;
dy += 2;
if (err > 0) {
x--;
dx += 2;
err += (-r << 1) + dx;
}
}
if (x !== lx) {
ctx.rect(x0 - ly, y0 - lx, ly * 2 + 1, 1);
ctx.rect(x0 - ly, y0 + lx, ly * 2 + 1, 1);
}
ctx.fill();
}
function pixelPixelatedCircle(ctx, cx, cy, r){
r |= 0;
ctx.setTransform(1,0,0,1,0,0); // ensure default transform
var x = r, y = 0, dx = 1, dy = 1;
var err = dx - (r << 1);
var x0 = cx | 0, y0 = cy | 0;
var lx = x,ly = y;
var w = 1, px = x0;
ctx.beginPath();
var rendering = 2;
while (rendering) {
const yy = y0 - y;
const yy1 = y0 + y - 1;
const xx = x0 - x;
const xx1 = x0 + x - 1;
ctx.rect(xx, yy1, 1, 1);
ctx.rect(xx, yy, 1, 1);
ctx.rect(xx1, yy1, 1, 1);
ctx.rect(xx1, yy, 1, 1);
if (x !== lx){
const yy = y0 - lx;
const yy1 = y0 + lx - 1;
const xx = x0 - ly;
w = px - xx;
const xx1 = x0 + ly - w;
ctx.rect(xx, yy, w, 1);
ctx.rect(xx, yy1, w, 1);
ctx.rect(xx1, yy, w, 1);
ctx.rect(xx1, yy1, w, 1);
px = xx;
}
lx = x;
ly = y;
y++;
err += dy;
dy += 2;
if (err > 0) {
x--;
dx += 2;
err += (-r << 1) + dx;
}
if (x < y) { rendering -- }
}
ctx.fill();
}
const ctxZ = canvasZoom.getContext("2d");
canvas.addEventListener("mousemove",(event) => {
ctxZ.clearRect(0,0,30,30);
ctxZ.drawImage(canvas, -(event.pageX-10), -(event.pageY-10));
});
canvas {border: 1px solid black}
#canvasZoom {
width: 300px;
height: 300px;
image-rendering: pixelated;
}
<canvas id="canvas" width="300" height="300"></canvas>
<canvas id="canvasZoom" width="30" height="30"></canvas>
There doesn't appear to be a built-in setting that I can find, but you can loop through the image data and set the individual pixels if they are within some threshold of what you want.
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.beginPath();
context.arc(250, 250, 250, 0, 2 * Math.PI, false);
context.fillStyle = 'rgb(255, 0, 0)';
context.fill();
context.closePath();
console.log(getDistinctColors(context).length + " distinct colors before filter");
solidifyColor(context, 255, 0, 0);
console.log(getDistinctColors(context).length + " distinct colors aftrer filter");
function solidifyColor(context, r, g, b, threshold = 3) {
const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
var rDif = Math.abs(imageData.data[i + 0] - r);
var bDif = Math.abs(imageData.data[i + 1] - b);
var gDif = Math.abs(imageData.data[i + 2] - g);
if (rDif <= threshold && bDif <= threshold && gDif <= threshold) {
imageData.data[i + 0] = r;
imageData.data[i + 1] = g;
imageData.data[i + 2] = b;
imageData.data[i + 3] = 255; // remove alpha
}
}
context.putImageData(imageData, 0, 0);
}
function getDistinctColors(context) {
var colors = [];
const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
colors.push([
imageData.data[i + 0], // R value
imageData.data[i + 1], // G value
imageData.data[i + 2], // B value
imageData.data[i + 3] // A value
]);
}
return [...new Set(colors.map(a => JSON.stringify(a)))].map(a => JSON.parse(a));
}
<canvas id=canvas width=500 height=500></canvas>
If you see run the code snippet that i have, you'll this html canvas drawing thing that is segmented to color the arcs in a gradient.
I'm trying to figure out how to gradually clear the context of old 'arcs' from the tail end of the drawing similar to a 'mouse trail' effect, perhaps similar to this: https://imgur.com/a/2Lr9IYq
I'm thinking, perhaps that somehow, once the total counter goes above the segment count, it clears itself but im not quite sure how to do that.
Grateful if anyone can point me in the right direction!
var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
colors = [
{r: 0, g: 0, b: 0, a:1},
{r: 255, g: 255, b: 255, a:1},
],
cIndex = 0, maxColors = colors.length,
total = 0, segment = 1000,
isDown = false, px, py;
setSize();
c.onmousedown = c.ontouchstart = function(e) {
isDown = true;
var pos = getPos(e);
px = pos.x;
py = pos.y;
};
c.onmousemove = c.ontouchmove = function(e) {if (isDown) plot(e)};
c.onmouseup = c.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 plot(e) {
var pos = getPos(e);
plotLine(ctx, px, py, pos.x, pos.y);
px = pos.x;
py = pos.y;
}
function plotLine(ctx, x1, y1, x2, y2) {
var diffX = Math.abs(x2 - x1),
diffY = Math.abs(y2 - y1),
dist = Math.sqrt(diffX * diffX + diffY * diffY),
step = dist / 50,
i = 0,
t, b, x, y;
while (i < dist) {
t = Math.min(1, i / dist);
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
// ctx.shadowBlur = 500;
// ctx.shadowOffsetX = 0;
// ctx.shadowOffsetY = 0;
// ctx.shadowColor = getColor();
// console.log(getColor())
ctx.fillStyle = getColor();
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI*2);
ctx.fill();
i += step;
}
function getColor() {
var r, g, b, a, t, c1, c2;
c1 = colors[cIndex];
c2 = colors[(cIndex + 1) % maxColors];
t = Math.min(1, total / segment);
if (++total > segment) {
total = 0;
if (++cIndex >= maxColors) cIndex = 0;
}
r = c1.r + (c2.r - c1.r) * t;
g = c1.g + (c2.g - c1.g) * t;
b = c1.b + (c2.b - c1.b) * t;
a = c1.a + (c2.a - c1.a) * t;
return "rgb(" + (r|0) + "," + (g|0) + "," + (b|0) + "," + (a) + ")";
}
}
window.onresize = setSize;
function setSize() {
c.width = window.innerWidth;
c.height = window.innerHeight;
}
html, body {
margin:0;
padding:0;
}
body {
padding: 0px;
}
<canvas id="myCanvas" ></canvas>
I need to draw a circle and i have only two points.Now i need to find center point and radius of the circle? You can form the circle in clock wise direction.
Thanks in advance
Here is a Brute Force approach to the problem.
EDIT
Added a max iterations limit to cut off calculations if the line between the two points is almost straight along x (meaning a radius would be nearing Infinity)
Also animations, because that makes everything better :)
var canvas = document.body.appendChild(document.createElement("canvas"));
var ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 1000;
var points = [
{ x: parseInt(prompt("x1", "110")), y: parseInt(prompt("y1", "120")), r: 5 },
{ x: parseInt(prompt("x2", "110")), y: parseInt(prompt("y2", "60")), r: 5 },
];
function calculateRemainingPoint(points, x, precision, maxIteration) {
if (x === void 0) { x = 0; }
if (precision === void 0) { precision = 0.001; }
if (maxIteration === void 0) { maxIteration = 100000; }
var newPoint = {
x: x,
y: (points[0].y + points[1].y) / 2,
r: 50
};
var d0 = distance(points[0].x, points[0].y, x, newPoint.y);
var d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var iteration = 0;
//Bruteforce approach
while (Math.abs(d0 - d1) > precision && iteration < maxIteration) {
var oldDiff = Math.abs(d0 - d1);
var oldY = newPoint.y;
iteration++;
newPoint.y += oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var diff_1 = Math.abs(d0 - d1);
if (diff_1 > oldDiff) {
newPoint.y = oldY - oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
}
}
var diff = (points[0].x + points[1].x) / points[0].x;
newPoint.r = d0;
return newPoint;
}
points.push(calculateRemainingPoint(points));
function distance(x1, y1, x2, y2) {
var a = x1 - x2;
var b = y1 - y2;
return Math.sqrt(a * a + b * b);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(-canvas.width, canvas.height / 2);
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(canvas.width / 2, -canvas.height);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.stroke();
ctx.closePath();
for (var pointIndex = 0; pointIndex < points.length; pointIndex++) {
var point = points[pointIndex];
ctx.beginPath();
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, point.r, 0, Math.PI * 2);
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, 2, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
}
setInterval(function () {
points = points.slice(0, 2);
points[Math.floor(Math.random() * points.length) % points.length][Math.random() > 0.5 ? 'x' : 'y'] = Math.random() * canvas.width - canvas.width / 2;
setTimeout(function () {
points.push(calculateRemainingPoint(points));
requestAnimationFrame(draw);
}, 1000 / 60);
}, 1000);
draw();
No that is impossible.
Create two circles with the same radius at centerpoints A + B. At the intersection of these two circles create an circle with the same radius....
Then make the same with an other radius....
I'm drawing lines on an HTML canvas, and use a less precise 2d-array (representing blocks of 10x10 pixels) in which I 'draw' lines with Bresenham's algorithm to store line-ids, so I can use that array to see which line is selected.
This works, but I would like it to be more accurate - not in the 10x10 size that I use (I like that I don't exactly have to click on the line), but when I draw a representation of that array over my actual canvas, I see that there are a lot of the 10x10 blocks not filled, even though the line is crossing them:
Is there a better solution to this? What I want is to catch ALL grid blocks that the actual line passes through.
Without seeing your code, I think you made a rounding error while filling the lookup table using the Bresenham algorithm or you scaled the coordinates before running the algorithm.
This jsFiddle shows what I came up with and the squares are perfectly aligned.
HTML
<canvas id="myCanvas"></canvas>
CSS
#myCanvas {
width: 250px;
height: 250px;
}
JavaScript
var $canvas = $("#myCanvas"),
ctx = $canvas[0].getContext("2d");
ctx.canvas.width = $canvas.width();
ctx.canvas.height = $canvas.height();
function Grid(ctx) {
this._ctx = ctx;
this._lines = [];
this._table = [];
this._tableScale = 10;
this._createLookupTable();
}
Grid.prototype._createLookupTable = function() {
this._table = [];
for (var y = 0; y < Math.ceil(ctx.canvas.height / this._tableScale); y++) {
this._table[y] = [];
for (var x = 0; x < Math.ceil(ctx.canvas.width / this._tableScale); x++)
this._table[y][x] = null;
}
};
Grid.prototype._updateLookupTable = function(line) {
var x0 = line.from[0],
y0 = line.from[1],
x1 = line.to[0],
y1 = line.to[1],
dx = Math.abs(x1 - x0),
dy = Math.abs(y1 - y0),
sx = (x0 < x1) ? 1 : -1,
sy = (y0 < y1) ? 1 : -1,
err = dx - dy;
while(true) {
this._table[Math.floor(y0 / 10)][Math.floor(x0 / 10)] = line;
if ((x0 == x1) && (y0 == y1)) break;
var e2 = 2 * err;
if (e2 >- dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
};
Grid.prototype.hitTest = function(x, y) {
var ctx = this._ctx,
hoverLine = this._table[Math.floor(y / 10)][Math.floor(x / 10)];
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
this._lines.forEach(function(line) {
line.draw(ctx, line === hoverLine ? "red" : "black");
});
};
Grid.prototype.drawLookupTable = function() {
ctx.beginPath();
for (var y = 0; y < this._table.length; y++)
for (var x = 0; x < this._table[y].length; x++) {
if (this._table[y][x])
ctx.rect(x * 10, y * 10, 10, 10);
}
ctx.strokeStyle = "rgba(0, 0, 0, 0.2)";
ctx.stroke();
};
Grid.prototype.addLine = function(line) {
this._lines.push(line);
this._updateLookupTable(line);
};
Grid.prototype.draw = function() {
var ctx = this._ctx;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
this._lines.forEach(function(line) {
line.draw(ctx);
});
};
function Line(x0, y0, x1, y1) {
this.from = [ x0, y0 ];
this.to = [ x1, y1];
}
Line.prototype.draw = function(ctx, style) {
ctx.beginPath();
ctx.moveTo(this.from[0], this.from[1]);
ctx.lineTo(this.to[0], this.to[1]);
ctx.strokeStyle = style || "black";
ctx.stroke();
};
var grid = new Grid(ctx);
grid.addLine(new Line(80, 10, 240, 75));
grid.addLine(new Line(150, 200, 50, 45));
grid.addLine(new Line(240, 10, 20, 150));
grid.draw();
grid.drawLookupTable();
$canvas.on("mousemove", function(e) {
grid.hitTest(e.offsetX, e.offsetY);
grid.drawLookupTable();
});
Your best option is to treat the mouse-cursor-position as a small circle (f.e. with a 5px radius) and check if the line intersects with the circle.
Use the math as explained in this Q&A
JavaScript
A simple function to detect intersection would be:
function lineCircleIntersects(x1, y1, x2, y2, cx, cy, cr) {
var dx = x2 - x1,
dy = y2 - y1,
a = dx * dx + dy * dy,
b = 2 * (dx * (x1 - cx) + dy * (y1 - cy)),
c = cx * cx + cy * cy,
bb4ac;
c += x1 * x1 + y1 * y1;
c -= 2 * (cx * x1 + cy * y1);
c -= cr * cr;
bb4ac = b * b - 4 * a * c;
return bb4ac >= 0; // true: collision, false: no collision
}
See it working in this jsFiddle, but note that this function will also return true if the cursor is on the slope of the line outside [x1, y1], [x2, y2]. I'll leave this up to you :)
You can also try line-circle-collision library on github which should give you what you want.