React Canvas,在鼠标点击位置检测图像中的区域

React Canvas, detect region in image at mouse click position

我有一张 canvas 这张图片

我想要当我点击进入时 canvas 我会在我点击的位置

例如,当我点击红点时,我将得到区域 1-2-3-4-5-6

// input: mousePosition: {x, y}
// output: listPoint: [
//  {x1, y1},
//  {x2, y2},
//  ...
//  {x6, y6}
// ]

有没有人有任何想法或任何工具可以帮助我。谢谢

这可以通过 HTML map areas 实现。我冒昧地根据你的图像在 500px x 450px 区域绘制坐标。

创建不同的区域后,您可以使每个区域包含一个 href 属性,该属性可以转到该区域的页面或使用 URL 执行 JavaScript 代码格式:javascript:insert_your_code_here.

如果你想得到哪个区域在坐标 x, y 上,你可以在所述坐标上模拟点击事件。

结合所有这些,这是我想出的代码:

document.querySelector("#submitCoords").addEventListener("click", () => {
  const x = document.querySelector("#xInput").value;
  const y = document.querySelector("#yInput").value;

  document.elementFromPoint(x, y).click();
});

const clicked = (area) => {
  document.querySelector(
    "#clickResults"
  ).innerText = `Clicked ${area} area.`;
};
<img src="https://i.imgur.com/myv6wss.jpeg" id="image" alt="Image" usemap="#workmap" width="500" height="450" />

<map name="workmap" id="workmap">
      <area
        shape="poly"
        coords="46,46,223,53,213,183,101,203,46,199"
        alt="A area"
        href="javascript:clicked('A')"
      />
      <area
        shape="poly"
        coords="409,57,446,57,446,348,390,262"
        alt="B area"
        href="javascript:clicked('B')"
      />
      <area
        shape="poly"
        coords="46,199,101,203,134,298,148,340,46,340"
        alt="C area"
        href="javascript:clicked('C')"
      />
      <area
        shape="poly"
        coords="148,340,134,298,390,262,446,348"
        alt="D area"
        href="javascript:clicked('D')"
      />
      <area
        shape="poly"
        coords="223,53,409,57,390,262,134,298,101,203,213,183"
        alt="Center area"
        href="javascript:clicked('Center')"
      />
    </map>

<br />
<input type="number" placeholder="Input x coordinate here" id="xInput" />
<input type="number" placeholder="Input y coordinate here" id="yInput" />
<br />
<br />
<button id="submitCoords">Submit coordinates</button>

<p id="clickResults"></p>

代码笔:codepen.io/pen/wvdEYeZ

如果您有点和形状的列表,您可以使用 click 事件找出您所在的位置。

'Alternative approach' 来自 this 在 math.stackexchange.com

上的回答

为了确定我们是否在一个形状中,我们从鼠标点击的地方到我们 canvas 的外部取一条线段。然后我们计算它与我们形状的边缘相交的次数。如果交叉点的数量是奇数,那么我们就是这个形状。然后我们对每个形状都这样做,直到找到鼠标所在的位置。

要计算两条线是否相交,我们可以使用 this GeeksforGeeks 文章中的数学。它通过比较线中点的方向来工作。

我在下面有一个例子。

let globalPoints = [
    {"x":155,"y":395,"n":"A"}/*0*/,
    {"x":235,"y":630,"n":"B"}/*1*/,
    {"x":0,"y":630,"n":"C"}/*2*/,
    {"x":0,"y":380,"n":"D"}/*3*/,
    {"x":0,"y":80,"n":"E"}/*4*/,
    {"x":405,"y":80,"n":"F"}/*5*/,
    {"x":385,"y":355,"n":"G"}/*6*/,
    {"x":224,"y":598,"n":"H"}/*7*/,
    {"x":755,"y":520,"n":"I"}/*8*/,
    {"x":795,"y":80,"n":"J"}/*9*/,
    {"x":890,"y":85,"n":"K"}/*10*/,
    {"x":890,"y":630,"n":"L"}/*11*/,
    {"x":855,"y":630,"n":"M"}/*12*/,
    {"x":0,"y":0,"n":"N"}/*13*/,
    {"x":890,"y":0,"n":"O"}/*14*/
];

let shapePoints = [
    [0,1,2,3],
    [3,4,5,6,0],
    [0,7,8,9,5,6],
    [9,10,11,12,8],
    [9,10,11,12,8],
    [7,1,12,8],
    [4,13,14,10,9,5]
];

let lastClick = null;

class Shape {

    constructor(points)
    {
        this.points = points;
        this.filledIn = false;
    }

    draw(ctx) {

        ctx.beginPath();
        ctx.strokeStyle = 'black';
        ctx.lineWidth = 3;

        ctx.fillStyle = 'red';

        ctx.moveTo(globalPoints[this.points[0]].x,globalPoints[this.points[0]].y);

        for (let i =1;i < this.points.length;i++) {
            ctx.lineTo(globalPoints[this.points[i]].x,globalPoints[this.points[i]].y);
        }

        ctx.closePath();

        if (this.filledIn) {
            ctx.fill();
        }

        ctx.stroke();
    }

    toString()
    {
        let name = "";
        for (let i=0;i<this.points.length;i++) {
            name += globalPoints[this.points[i]].n;
        }


        return name;
    }

    //use the 'alternate method' from this answer
    //https://math.stackexchange.com/questions/926559/when-is-a-point-in-the-plane-inside-a-simple-closed-path
    calculateMouseClick(mouseX,mouseY)
    {
        //generate a ray that extends to outside of the shape,
        //we know -1,-1 will be outside as all points are positive
        let ray = {"p1":{"x":mouseX,"y":mouseY},"p2":{"x":-1,"y":-1}};

        let numIntersections = 0;

        //calculate all but last lines
        for (let i=0;i<this.points.length-1;i++) {
            let shapeEdge = {
                "p1":{
                    "x":globalPoints[this.points[i]].x,
                    "y":globalPoints[this.points[i]].y
                },
                "p2":{
                    "x":globalPoints[this.points[i+1]].x,
                    "y":globalPoints[this.points[i+1]].y
                }
            };

            if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
                numIntersections++;
            }
        }

        //last line wraps around to start of array
        let shapeEdge = {
            "p1":{
                "x":globalPoints[this.points[0]].x,
                "y":globalPoints[this.points[0]].y
            },
            "p2":{
                "x":globalPoints[this.points[this.points.length-1]].x,
                "y":globalPoints[this.points[this.points.length-1]].y
            }
        };

        if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
            numIntersections++;
        }

        if (numIntersections == 0 || numIntersections%2 == 0) {
            return false;
        }

        return true;
    }

}

let shapes = [];

function init()
{
    "use strict";
    for (let i = 0;i< globalPoints.length;i++) {
        //add 50 to x and y so we don't draw at the very edge of the canvas
        globalPoints[i].x += 50;
        globalPoints[i].y += 80;
    }

    for (let i = 0;i < shapePoints.length;i++) {
        shapes.push(new Shape(shapePoints[i]));
    }

    draw();

    let canvas = document.getElementById("drawingArea");
    canvas.addEventListener("click",canvasClicked);
}

function draw()
{
    let canvas = document.getElementById("drawingArea");
    let ctx = canvas.getContext('2d');

    ctx.clearRect(0,0,canvas.width,canvas.height);
    drawShapes(ctx);
    drawPoints(ctx);
    drawClick(ctx);
}

function drawShapes(ctx)
{
    "use strict";

    for (let i = 0;i<shapes.length;i++) {
        shapes[i].draw(ctx);
    }
}

function drawPoints(ctx)
{
    "use strict";

    ctx.font = "48px sans";
    for (let i = 0;i < globalPoints.length;i++) {
        ctx.fillStyle = "black";
        ctx.beginPath();
        ctx.arc(globalPoints[i].x,globalPoints[i].y,5,0,2*Math.PI);
        ctx.fill();
        ctx.fillStyle = "#444444";
        ctx.fillText(globalPoints[i].n,globalPoints[i].x-40,globalPoints[i].y-25);
    }
}

function drawClick(ctx)
{
    "use strict";
    if (lastClick == null) {
        return;
    }

    ctx.strokeStyle = "#33dd33";
    ctx.arc(lastClick.x,lastClick.y,10,0,2*Math.PI);
    ctx.fill();
}

function canvasClicked(event)
{
    "use strict";

    let mouseX = event.offsetX;
    let mouseY = event.offsetY;
    lastClick = {"x":mouseX,"y":mouseY};

    //clear out previous click
    for (let i=0;i<shapes.length;i++) {
        shapes[i].filledIn = false;
    }

    let foundShape = false;
    let shapeNameElem = document.getElementById("shapeName");
    for (let i=0;i<shapes.length;i++) {
        let isIn = shapes[i].calculateMouseClick(mouseX,mouseY);

        if (isIn) {
            shapes[i].filledIn = true;
            foundShape = true;
            shapeNameElem.innerHTML = "Clicked shape: "+shapes[i].toString();
            break;
        }
    }

    if (!foundShape) {
        shapeNameElem.innerHTML="";
    }


    draw();
}

/********************************
onSegment(), orientation(), doIntersect()
https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/

license CC BY-SA
https://www.geeksforgeeks.org/copyright-information/
********************************/

// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
function onSegment(p, q, r)
{
    if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) &&
        q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y))
    return true;

    return false;
}
 
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
function orientation(p, q, r)
{
 
    // See https://www.geeksforgeeks.org/orientation-3-ordered-points/
    // for details of below formula.
    let val = (q.y - p.y) * (r.x - q.x) -
            (q.x - p.x) * (r.y - q.y);

    if (val == 0) return 0; // colinear

    return (val > 0)? 1: 2; // clock or counterclock wise
}

// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
function doIntersect(p1, q1, p2, q2)
{
 
    // Find the four orientations needed for general and
    // special cases
    let o1 = orientation(p1, q1, p2);
    let o2 = orientation(p1, q1, q2);
    let o3 = orientation(p2, q2, p1);
    let o4 = orientation(p2, q2, q1);

    // General case
    if (o1 != o2 && o3 != o4)
        return true;

    // Special Cases
    // p1, q1 and p2 are colinear and p2 lies on segment p1q1
    if (o1 == 0 && onSegment(p1, p2, q1)) return true;

    // p1, q1 and q2 are colinear and q2 lies on segment p1q1
    if (o2 == 0 && onSegment(p1, q2, q1)) return true;

    // p2, q2 and p1 are colinear and p1 lies on segment p2q2
    if (o3 == 0 && onSegment(p2, p1, q2)) return true;

    // p2, q2 and q1 are colinear and q1 lies on segment p2q2
    if (o4 == 0 && onSegment(p2, q1, q2)) return true;

    return false; // Doesn't fall in any of the above cases
} 



window.addEventListener("load",init);
<html>
    <head>
        <meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
        <script src="test.js"></script>
        <link rel="stylesheet" href="test.css"/>
    </head>
    <body>
  <p id="shapeName"></p>
        <canvas id="drawingArea" width="1000" height="800"></canvas>
        
    </body>
</html>

没有图像中的点列表似乎是不可能的,我找到了另一个解决方案:(