Spot the bug - #129: Retina Pixel Sampler

Why is my pixel snapshot reading from the wrong position?

const ctx = canvas.getContext('2d');
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillRect(20, 20, 50, 50);
const pixel = ctx.getImageData(20, 20, 1, 1).data;

Reply with what is broken and how you would fix it.

The scaling by the devicePixelRatio changes the size of the area you are drawing, including the coordinates.

Interesting observation, @kirupa. We’ll see if that’s the whole story when the solution drops later today.

The retina pixel sampling detail is a subtle one. I’ve seen similar issues cause headaches in data visualization exports.

The issue is that ctx.scale() changes the canvas coordinate system, but getImageData() works with the canvas’s actual pixel coordinates. With a device pixel ratio of 2, for example, the rectangle drawn at logical (20, 20) ends up around device pixel (40, 40).

So reading (20, 20) samples a different pixel than the one you expect.

One approach is to keep drawing in CSS/logical coordinates but convert the sampling coordinates to device pixels:

const dpr = window.devicePixelRatio;
const pixel = ctx.getImageData(20 * dpr, 20 * dpr, 1, 1).data;

I’d also make sure the canvas backing dimensions are scaled appropriately for the DPR. Otherwise you can still run into blurry rendering or coordinate mismatches.

Ah, you’ve spotted an interesting detail there. The scaling does indeed shift things in a way that can be deceptive. We’ll post the full solution later today.