Peace
Peace

Reputation: 35

How to get coordinates of html canvas tag

I want to get coordinates of the painted area of an image placed in an HTML canvas tag and send it to the database. And populate it on the same area of that image in another page. And how can I reset or clear the painted area by clicking the reset button?

JSfiddle example

var canvas = document.getElementById("canvas");
var img = document.getElementById("imagearea"),
    ctx = canvas.getContext("2d"),
    painting = false,
    lastX = 0,
    lastY = 0,
    lineThickness = 1;

canvas.width = canvas.height = 600;
ctx.fillRect(0, 0, 600, 600);
ctx.drawImage(img, 10, 10);

canvas.onmousedown = function(e) {
    painting = true;
    ctx.fillStyle = "#ff0000";
    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;

    }
}
<canvas id="canvas">
</canvas>
<img id="imagearea" src="https://media.istockphoto.com/photos/green-apple-with-leaf-and-cut-isolated-on-white-picture-id1141004606?k=6&m=1141004606&s=170667a&w=0&h=zwbN4lLc7MFb6f_aZ4npNL3i4Tgde-yINlYTztlI1QQ=" style="display: none;" />

<button> Reset </button>

Upvotes: 3

Views: 2469

Answers (4)

Helder Sepulveda
Helder Sepulveda

Reputation: 17574

The 'onmousemove' is doing all the drawing, I would save all you need to an array then on some event send to the database or draw to another canvas...

Here is a very simple example. I'm keeping the code minimal to convey my point that you already have all you need, all that is needed is to store it in a variable, You can bring back the lineThickness, the rest of the calculations and logic later, that should not be a problem.

var button = document.getElementById("btn");
var canvas = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");

var ctx = canvas.getContext("2d");
var ctx2 = canvas2.getContext("2d");

var painting = false;
var coordinates = [];

canvas.onmousedown = function(e) {painting = true;}
canvas.onmouseup = function(e) {painting = false;}

canvas.onmousemove = function(e) {
  if (painting) {
    x = e.pageX - this.offsetLeft;
    y = e.pageY - this.offsetTop;
    coordinates.push({x, y})
    ctx.fillRect(x, y, 5, 5);
  }
}

button.onmousedown = function(e) {
  ctx.clearRect(0, 0, 300, 150);
  coordinates.forEach(coord => {
    ctx2.fillRect(coord.x, coord.y, 5, 5);
  });
};
<canvas id="canvas1" width=300 height=150></canvas>
<button id="btn"> Reset </button>
<canvas id="canvas2" width=300 height=150></canvas>

Here we have a new event on the button click, I'm clearing the initial canvas and drawing all the coordinates to a second canvas, we could also be sending that data to server to be stored that should not be a problem.

Upvotes: 1

Fennec
Fennec

Reputation: 1872

To achieve what you're looking for, you need to store the min(x, y) coords for the (top, left) position and the max(x, y) coords for the (bottom, right) position, and you can't get the image on the same canvas if you are looking to remove the drawing on the area. use an HTML element with absolute position relative to the canvas for the area frame, attach an event to crop, and display the target area another one to remove it.

Here is a working example with a lot of comments, it should be clear. Click on the area to display the preview on the "x" to remove it with the drawing, it can handle multiple areas.

const source = "https://media.istockphoto.com/photos/green-apple-with-leaf-and-cut-isolated-on-white-picture-id1141004606?k=6&m=1141004606&s=170667a&w=0&h=zwbN4lLc7MFb6f_aZ4npNL3i4Tgde-yINlYTztlI1QQ=";
const container = document.querySelector("#container");
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const resetButton = document.querySelector("button");

let lastDrawnArea = [[Infinity, Infinity], [0, 0]];
let image;
let painting = false;
let lastX = 0;
let lastY = 0;
let lineThickness = 1;

init();

async function init() {

  await loadDrawImage();
  
  // Start Event Listening
  canvas.onmousedown = function(e) {
    painting = true;
    ctx.fillStyle = "#ff0000";
    lastX = e.pageX - this.offsetLeft;
    lastY = e.pageY - this.offsetTop;
  };

  canvas.onmouseup = function(e){
      painting = false;
      // Set the drawing area  
      setDrawingArea();
  }

  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;
          
          // Set The min, max coordinate of the current drawing 
          // to define the current drawing area
          lastDrawnArea = [
            [// Top left min([x, y]) coords
              Math.min(lastDrawnArea[0][0], mouseX),
              Math.min(lastDrawnArea[0][1], mouseY)
            ],
            [// Bottom right max([x, y]) coords
              Math.max(lastDrawnArea[1][0], mouseX),
              Math.max(lastDrawnArea[1][1], mouseY)
            ]
          ]
      }
  }
}

async function loadDrawImage() {
  image = new Image();
  
  // Load the image
  await new Promise(resolve => {
    image.onload = resolve;
    image.src = source;
  });

  const [width, height] = [image.naturalWidth, image.naturalHeight];

  // Set the container and canvas size
  container.style.width = `${width}px`;
  container.style.height = `${height}px`;
  canvas.width = width;
  canvas.height = height;
  
  // Set the container in the background
  container.style.background = `url(${image.src})`;
}

function setDrawingArea(){
  const [TOP_LEFT, BOTTOM_RIGHT, X, Y] = [0, 1, 0, 1];
  const container = document.querySelector("#container");
  const template = document.querySelector("#areaTemplate");
  const area = template.content.firstElementChild.cloneNode(true);
  
  // You should replace this with the lineThickness 
  const offset = 10;
  
  // Get the area size
  const width = lastDrawnArea[BOTTOM_RIGHT][X] - lastDrawnArea[TOP_LEFT][X];
  const height = lastDrawnArea[BOTTOM_RIGHT][Y] - lastDrawnArea[TOP_LEFT][Y];
  
  area.style.left = `${lastDrawnArea[TOP_LEFT][X] - offset}px`;
  area.style.top = `${lastDrawnArea[TOP_LEFT][Y] - offset}px`;
  area.style.width = `${width + (offset * 2)}px`;
  area.style.height = `${height + (offset * 2)}px`;

  // Draw the template
  container.append(area);
  
  // Add the events
  area.onclick = previewArea; // Preveiw event
  area.querySelector("b").onclick = removeArea; // Remove event
  
  // Reset "lastDrawnArea" value
  lastDrawnArea = [[Infinity, Infinity], [0, 0]];  
  
}

function previewArea(e) {
  const preview = document.querySelector("#preview");
  const previewCanvas = preview.querySelector("canvas");
  const previewCtx = previewCanvas.getContext("2d");
  
  // Get the drawing area coords
  const area = e.target;
  const [x, y, width, height] = [
    parseFloat(area.style.left),
    parseFloat(area.style.top),
    parseFloat(area.style.width),
    parseFloat(area.style.height)
  ];
  
  // Draw the preview area
  previewCanvas.width = width;
  previewCanvas.height = height;
  previewCtx.drawImage(image,
    x, y, width, height,
    0, 0, width, height);
}

function removeArea(e) {
  const area = e.target.parentElement;
  const [x, y, width, height] = [
    parseFloat(area.style.left),
    parseFloat(area.style.top),
    parseFloat(area.style.width),
    parseFloat(area.style.height)
  ];
  
  ctx.clearRect(x, y, width, height);
  
  area.remove();
}

resetButton.onclick = () => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  document.querySelectorAll('.area').forEach(el => el.remove());
}
body {
  display: flex;
}

#container {
  position: relative;
}

.area {
  position: absolute;
  border: 2px solid #333;
  color: #333;
  cursor: pointer;
}

.area b {
  position: absolute;
  right: 0;
  top: 0;
  transform: translate(100%, -100%);
  color: red;
  cursor: pointer;
}
<div id="container">
  <canvas></canvas>
</div>

<div id="preview">
  <canvas></canvas>
</div>

<template id="areaTemplate">
  <div class="area">
    <b>x</b>
  </div>
</template>

<button> Reset All </button>

There is one problem with this example if two areas overlap the drawing will be removed at the intersection of both areas, to overcome this issue you need to keep each drawing on its own canvas (... a lot of work).

Last but not least, you can achieve the same result with ease and have better control over the canvas and its elements if you use a library like fabric js, take a look at this example of freedrawing, you will get the drawing coordinates for free, and you can still have everything on the same canvas (the image or whatever you need to add to canvas and all the drawing with no overlap ...), the initial learning curve may take some time but by the end of the day, you'll get a better understanding of HTML canvas overall.

Side note: the image you're using has a cross-origin limitation, you should use images from the same domain or from domains that allow cross-origin.

Upvotes: 3

Davedude
Davedude

Reputation: 170

I don’t know if this is quite what you were thinking, but if you simply want the same image to appear on another webpage, you could use ctx.getImageData() to copy the drawing from the canvas as an object, convert it to json, then send it to the database. Then on the other end, turn it back into an object and use ctx.putImageData() to place it back on the new canvas.

Upvotes: 0

Guimby
Guimby

Reputation: 31

EDIT : Update with a working demo based on your fiddle

You may have to adapt this function to also include the thikness of the drawed lines (they may appear outside of the registered area). But like this, you have te position and the size of your drawed area.

You can now do a ROI on it if you want.


You can track the area drawed with a function like this one:

var drawedArea = [0,0,0,0];
function drawedAreaTrack(x, y) {
  // top left x
  if (drawedArea[0] === 0) {
  drawedArea[0] = x;
  } else {
  drawedArea[0] = Math.min(drawedArea[0], x);
  }
  

  // top left y
   if (drawedArea[1] === 0) {
  drawedArea[1] = y;
  } else {
  drawedArea[1] = Math.min(drawedArea[1], y);
  }

  // bottom right x
  drawedArea[2] = Math.max(drawedArea[2], x);

  // bottom right y
  drawedArea[3] = Math.max(drawedArea[3], y);
  
  console.log(drawedArea);
}

You could use those two point to get the total area drawed.

Here is a working example-> Fiddle :

http://jsfiddle.net/b90h6gaq/8/

Upvotes: 1

Related Questions