I am building a web app for my ML model in which i need to mark the area with the mouse pointer where there is occlusion in the image. The ML model then takes the marked image and reconstructs the missing regions.
In order to draw over the image, first i made a Canvas and on to that canvas we need to upload the selected image. Then i made the canvas to take the size of the resized image.
The problem here is: The mouse points to the one location but draws on the other location.
Further more I figured out that, when i vary the Webpage window size then at some window shape the mouse pointer exactly draws where I want to.
I am not understanding what's happened? Is it due to the error in window resizing or what ?
Resizing the Canvas
function overlayImage() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var rect = canvas.getBoundingClientRect();
var img = this;
var scaleX = canvas.width / img.width;
var scaleY = canvas.height / img.height;
var scale = Math.min(scaleX, scaleY);
var newWidth = img.width * scale;
var newHeight = img.height * scale;
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = newWidth;
canvas.height = newHeight;
ctx.drawImage(img, 0,0, newWidth, newHeight);
}
Now the Mouse Events
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
var rect = canvas.getBoundingClientRect();
let coord = {
x: 0,
y: 0
};
document.addEventListener("mousedown", start);
document.addEventListener("mouseup", stop);
window.addEventListener("resize", resize);
function resize() {
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
}
resize();
function reposition(event) {
coord.x = (event.clientX - rect.left) / (rect.right - rect.left) * canvas.width;
coord.y = (event.clientY - rect.top) / (rect.bottom - rect.top) * canvas.height;
}
function start(event) {
document.addEventListener("mousemove", draw);
console.log("To check if mouse event is occurring")
reposition(event);
}
function stop() {
document.removeEventListener("mousemove", draw);
}
function draw(event) {
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.moveTo(coord.x, coord.y);
reposition(event);
ctx.lineTo(coord.x, coord.y);
ctx.stroke();
}
I tried to correct the offset values of x,y but still it did not worked.
I then checked if I don't resize the canvas size then may be it would help but still not worked.
There are solutions present in stack overflow for the static canvas size but my Canvas size changes according to the image dimension with the aspect ratio balanced. That's why I can draw properly over some image while the drawing starts far away from where i pointed.
You'll need to update the x
and y
coordinate (get the correct position by taking the scale factor of the canvas itself) before using moveTo()
and lineTo()
. Have a look at the DrawingBoard
class' startDrawing
, addPoint
and scale
functions in the example below.
drawing.js
class DrawingBoard {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.isDrawing = false;
}
scale(x, y) {
const pageWidth = document.body.clientWidth;
const scale = Math.min(pageWidth / this.canvas.width, 1);
return [x/scale, y/scale];
}
startDrawing(x, y, color) {
this.isDrawing = true;
this.ctx.strokeStyle = color;
this.ctx.beginPath();
this.ctx.moveTo(... this.scale(x, y));
}
addPoint(x, y) {
if (this.isDrawing) {
this.ctx.lineTo(... this.scale(x, y));
this.ctx.stroke();
}
}
stopDrawing() {
this.isDrawing = false;
this.ctx.closePath();
}
}
const imageCanvas = document.getElementById('imageCanvas');
const overlayCanvas = document.getElementById('overlayCanvas');
const board = new DrawingBoard(overlayCanvas);
overlayCanvas.addEventListener('mousedown', event => {
board.startDrawing(event.offsetX, event.offsetY, '#ff7b06');
});
overlayCanvas.addEventListener('mousemove', event => {
board.addPoint(event.offsetX, event.offsetY);
});
overlayCanvas.addEventListener('mouseup', () => board.stopDrawing());
overlayCanvas.addEventListener('mouseout', () => board.stopDrawing());
The following code is not really relevant for answering the question, however, it's provided for a full working example.
drawing.js
continuation
document
.getElementById('imageInput')
.addEventListener('change', ({target: {files}}) => {
const [file] = files;
imageCanvas.hidden = false;
overlayCanvas.hidden = false;
if (file && file.type.match('image/jpeg')) {
const reader = new FileReader();
reader.onload = ({target: {result}}) => {
const image = new Image();
image.onload = () => {
const ctx = imageCanvas.getContext('2d');
const maxWidth = document.body.clientWidth;
let { width, height } = image;
imageCanvas.width = width;
imageCanvas.height = height;
copyProperties(overlayCanvas, imageCanvas, 'width', 'height');
ctx.clearRect(0, 0, width, height);
ctx.drawImage(image, 0, 0, width, height);
}
image.src = result;
}
reader.readAsDataURL(file);
}
});
function copyProperties(target, source, ...properties) {
for (const property of properties) {
target[property] = source[property];
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Draw on Image</title>
<link rel="stylesheet" href="drawing.css">
</head>
<body>
<header>
<label for="imageInput">Select an JPEG Image</label>
<input type="file" id="imageInput" accept=".jpg,.jpeg">
</header>
<main>
<div class="canvas-container">
<canvas id="imageCanvas" hidden></canvas>
<canvas id="overlayCanvas" hidden></canvas>
</div>
</main>
<script src="drawing.js"></script>
</body>
</html>
drawing.css
body {
margin: 0;
padding: 0;
}
header {
padding: 1rem;
border-bottom: 1px solid black;
}
.canvas-container {
position: relative;
}
canvas {
max-width: 100%;
height: auto;
}
#overlayCanvas {
position: absolute;
top: 0;
left: 0;
pointer-events: auto;
cursor: crosshair;
}