I'm trying to create an interface (HTML canvas/Vanilla JS) where a user can:
- Upload an image
- Draw on the image
- Save the new image in the same resolution as the original from step (1)
I'm stuck on (2). The x and y coordinates are all wrong when I try to draw. How far off they are depends on the size/shape of the image uploaded. I have made a codepen to demonstrate, because the Stack code snippet below was behaving strangely when run.
I am a JS/HTML rookie but my hypothesis is that the incorrect coordinates are based on the image's original resolution (which is retained when I save the resultant image) and not the image's resolution as it is displayed in the canvas.
Any help much appreciated.
const fileInput = document.querySelector(".file-input"),
inputImg = document.querySelector(".input-img img"),
chooseImgBtn = document.querySelector(".choose-img"),
saveImgBtn = document.querySelector(".save-img");
drawOnImage();
fileInput.addEventListener("change", async(e) => {
const [file] = fileInput.files;
// displaying the uploaded image
const image = document.createElement("img");
image.src = await fileToDataUri(file);
// enbaling the brush after after the image
// has been uploaded
image.addEventListener("load", () => {
drawOnImage(image);
});
return false;
});
function fileToDataUri(field) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
resolve(reader.result);
});
reader.readAsDataURL(field);
});
}
const colorElement = document.getElementsByName("colorRadio");
let color;
colorElement.forEach((c) => {
if (c.checked) color = c.value;
});
colorElement.forEach((c) => {
c.onclick = () => {
color = c.value;
};
});
function drawOnImage(image = null) {
const canvasElement = document.getElementById("canvas");
const context = canvasElement.getContext("2d");
// if an image is present,
// the image passed as a parameter is drawn in the canvas
if (image) {
const imageWidth = image.width;
const imageHeight = image.height;
// rescaling the canvas element
canvasElement.width = imageWidth;
canvasElement.height = imageHeight;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
}
let isDrawing;
canvasElement.onmousedown = (e) => {
isDrawing = true;
context.beginPath();
context.lineWidth = size;
context.strokeStyle = "black";
context.lineJoin = "round";
context.lineCap = "round";
context.moveTo(e.clientX, e.clientY);
};
canvasElement.onmousemove = (e) => {
if (isDrawing) {
context.lineTo(e.clientX, e.clientY);
context.stroke();
}
};
canvasElement.onmouseup = function() {
isDrawing = false;
context.closePath();
};
}
chooseImgBtn.addEventListener("click", () => fileInput.click());
/* Import Google font - Poppins */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
display: flex;
padding: 10px;
min-height: 90vh;
align-items: center;
justify-content: center;
background: #9c9c9c;
}
.container {
width: 1200px;
padding: 30px 35px 35px;
background: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.container .editor-panel,
.container .controls .reset-filter,
.container .controls .save-img {
opacity: 0.5;
pointer-events: none;
}
.container h2 {
margin-top: -8px;
font-size: 22px;
font-weight: 500;
}
.container .wrapper {
display: flex;
margin: 20px 0;
min-height: 335px;
}
.editor-panel .options,
.controls {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.wrapper .canvas-wrapper {
flex-grow: 1;
display: flex;
overflow: hidden;
margin-left: 20px;
border-radius: 5px;
align-items: center;
justify-content: center;
}
#canvas {
max-width: 700px;
max-height: 650px;
width: 100%;
height: 100%;
border-radius: 5px;
border: 1px solid #ccc;
object-fit: contain;
}
.controls button {
padding: 11px 20px;
font-size: 14px;
border-radius: 3px;
outline: none;
color: #fff;
cursor: pointer;
background: none;
transition: all 0.3s ease;
text-transform: uppercase;
}
.controls .choose-img {
background: #6C757D;
border: 1px solid #6C757D;
}
.controls .save-img {
margin-left: 5px;
background: #5372F0;
border: 1px solid #5372F0;
}
<link rel="stylesheet" href="https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" />
<div class="container">
<h2>Drawing</h2>
<div class="wrapper">
<div class="canvas-wrapper">
<canvas id="canvas" style="border: 1px solid black;"></canvas>
</div>
</div>
<div class="controls">
<input type="file" class="file-input" accept="image/*" hidden>
<button class="choose-img">Choose Input Image</button>
<div class="row">
<button class="save-img">Save</button>
</div>
</div>
</div>