I'd like to draw a red rectangle and an image on a HTML canvas. My goal is to completely cover the rectangle with the image, in combination with scaling all content using setTransform so the image fits the canvas. The issue I'm having is that the rectangle is still visible on the edges of the image.
I tried experimenting with the scale and it seems this is caused by rounding, as I'm scaling all content using a single setTransform call.
I can fix the issue by applying adjusted dimensions to both the rectangle and the image, so the resulting dimensions the rectangle and image are integer values. That works in Safari and Chrome, but it doesn't in Firefox.
Here's the reproduction code:
const DPR = window.devicePixelRatio || 1;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const checkbox = document.querySelector('input');
// Logical canvas size (matches physical pixels)
const container = document.getElementById('container');
const width = container.clientWidth * DPR;
const height = container.clientHeight * DPR;
// Desired content size
const contentWidth = 3508;
const contentHeight = 2480;
// Compute scale to fit content inside canvas
const scale = Math.min(width / contentWidth, height / contentHeight);
console.log(scale);
// Compute adjusted input size so output aligns to integers
const targetOutputWidth = Math.round(contentWidth * scale);
const targetOutputHeight = Math.round(contentHeight * scale);
const adjustedWidth = targetOutputWidth / scale;
const adjustedHeight = targetOutputHeight / scale;
function draw() {
canvas.width = width;
canvas.height = height;
// Set transform and draw
ctx.setTransform(scale, 0, 0, scale, 0, 0);
// Determine to fix issue
const fixIssue = checkbox.checked;
// Draw background rect
ctx.fillStyle = 'red';
if (!fixIssue) {
ctx.fillRect(0, 0, contentWidth, contentHeight);
} else {
ctx.fillRect(0, 0, adjustedWidth, adjustedHeight);
}
// Load and draw image
const img = new Image();
img.onload = () => {
// ctx.imageSmoothingEnabled = false; // Uncommenting this doesn't help
if (!fixIssue) {
ctx.drawImage(img, 0, 0, contentWidth, contentHeight);
} else {
ctx.drawImage(img, 0, 0, adjustedWidth, adjustedHeight);
}
};
img.src = `https://placehold.co/${contentWidth}x${contentHeight}`;
}
checkbox.oninput = draw;
draw();
<label>Fix issue: <input type="checkbox"></label>
<div id="container" style="width: 838px; height: 446px;">
<canvas id="canvas" style="width: 100%; height: 100%; background: white; display: block;"></canvas>
</div>
When fixIssue is set to false, this is what browsers display (make sure to view these screenshots at 100% to see the red rectangle at the edge of the image):
| Chrome | Safari | Firefox |
|---|---|---|
![]() |
![]() |
![]() |
When setting fixIssue to true, the issue is fixed in Chrome and Safari but not in Firefox:
| Chrome | Safari | Firefox |
|---|---|---|
![]() |
![]() |
![]() |
You can see that the rectangle is still visible at the top of the image in Firefox.
How can I prevent this from happening? I tried disabling image smoothing which doesn't help. I'd like to keep the single setTransform call as it's convenient for scaling all content.







