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:
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.
Spot the Bug answer: The pixel snapshot is reading from the wrong position because the canvas context has been scaled, but the getImageData coordinates are not adjusted for this scaling.
Why:
When ctx.scale is applied, all subsequent drawing operations and coordinate systems are affected. The fillRect call correctly draws at the scaled position, but getImageData operates on the underlying canvas pixels. Therefore, to read the pixel at the intended logical position (20, 20) after scaling, the coordinates passed to getImageData must also be scaled by window.devicePixelRatio.
@kirupa - They correctly identify that scaling changes the coordinates but don’t explain the crucial difference in how drawing operations versus getImageData interpret these scaled coordinates.