One dot on, one dot off
Squares filled with a one-dot black-and-white pattern, each as many pixels wide as its density says. Two things can be read off them: whether your screen renders the pattern intact, and which of the densities is the real one, if you hold a ruler up to it.
sizes are given in device pixels
An empty field means «do not draw this one»: use as many of the three as suit you. The values come from your ruler calibration, and you can type any others you like.
The pattern: one row of dots black, the next one white. It is the most demanding signal a screen can be given, because there is no room for any error to hide. If the stripes look the same everywhere and clean, the image reaches the glass intact. If some lines look thicker than others, or you see grey zones or wide bands that move as you move your head, something is resampling the image along the way.
The ruler: a square N device pixels wide comes out exactly one inch (2.54 cm) if and only if the real density is N ppi. Measure the squares: whichever one reads 2.54 cm names your true density, and the others show how far off that assumption was.
Preconditions: zoom at 100%, and do not take a screenshot to show it elsewhere. A screenshot goes through a second round of sampling and will show errors that are not your screen's.
What to expect
Square 1 side — device pixels
Square 2 side — device pixels
Square 3 side — device pixels
Enter at least one density for a square to be drawn.
It detects resampling, that is, something touching the image between the browser and the glass. It does not detect density: a broken pattern tells you «something is going on here», not how dense your panel is. That question is answered by the ruler, or by the calibration.
And the converse, which matters just as much: a clean pattern does not mean «all is well», it means «nothing is being scaled down on this path». At 100% zoom with no fractional scaling it will come out perfect and you will learn nothing further. An empty result is a result too.
For two independent reasons. First, subpixel rendering: each dot of an LCD is three RGB stripes side by side, horizontally. Vertical lines land on them and can come out with a purple or green tint, horizontal ones do not. If one set is coloured and the other neutral, that is information about how the screen is built.
Second, the scaling is not always the same on both axes, nor are pixels always square: the classic case is a 1280×1024 resolution on a 4:3 screen. If only one direction breaks, you find out only by having both in front of you.
They do not, and that is deliberate: the line is one device pixel in all three, so the stripes come out identical everywhere. Only the size of the square changes, because each density asks for a different number of them. The comparison here is made with a ruler, not by eye.
The pattern is written with putImageData straight into device pixels: the canvas backing store is N×N and its CSS size is N/DPR. That bypasses antialiasing entirely and puts no scaling of ours in between, so that whatever breaks was broken by the system and not by the page.
There is, though, a trap that yields a false positive: if the canvas itself sits on half a device pixel (all too easy with centring and fractional padding), the stripes break even on a perfect screen. So the page measures each canvas's position after layout and «latches» it onto a whole device pixel with a corrective margin, remeasuring on every resize or zoom.