A rounded viewport has two focal lengths
Worth reading first: A texture does not interpolate on the page · Recovering the camera from the picture it drew.
Halving a picture moves its centre ended by noticing a smaller thing beside the thing it measured. A picture of 1,000 by 562 reduced by three-quarters goes to 750 by 422, because the row count had to round, and a picture with one focal length has acquired two that disagree by about a tenth of a per cent. It is not a resize that causes this. Every window a renderer ever draws is in the same state, and the state is built into how windows and buffers are sized. On a screen at 1.25 device pixels a layout unit, a window 1,001 units wide has a buffer 1,251 pixels wide, which is 1.24975 of them to a unit, and a window 563 tall has one of 704 pixels, or 1.25044 to a unit.
A renderer then does a natural thing. It takes the window’s aspect ratio from the numbers it was given, , builds a projection with that aspect, and draws into the buffer. The two axes now have slightly different scales from world to pixels. The pixel that is not square measured what a stretched camera costs, at a stretch of a third or a half. This one is much smaller, and the question is whether small is the end of it.
Two focal lengths from a whole-number buffer
Take a vertical field of view with half-angle , a window of by layout units at a display scale , and a buffer of by pixels. A projection built with aspect maps a camera-space direction to a horizontal position of and a vertical one to . So the two focal lengths in pixels are
Their ratio is one when the rounding preserved the shape, and otherwise it is off. On whole-number display scales, rounding does nothing and the ratio is exactly one. On a fractional one, which covers most laptops and very many desktop monitors, it is almost never exactly one.
The scatter has the shape a reader would expect. The difference falls as one over the size, since half a pixel of rounding is a smaller share of a larger buffer. At a width of 320 the two focal lengths can differ by 3.3 parts in a thousand, and across a modern 2,000-pixel window by a few parts in ten thousand. It has no preferred sign: a window can be stretched either way, and resizing a window by one layout unit can flip it.
That scatter makes the small windows look like the problem: a phone-sized canvas with a third of a per cent of anisotropy. A reader who stops here concludes that the error is small on large screens and worth a check on small ones. That turns out to be the wrong picture of it, and the correct one is both simpler and more reassuring.
The error is a displacement, and it is bounded
An anisotropy matters to a picture only through where it moves things. Fix the vertical scale. A mark at the frame’s left or right edge then stands away from where equal focal lengths would have put it by
pixels. Now write the rounding explicitly: and , each no larger than a half. Then to first order the ratio minus one is , so
The width has cancelled. A relative error falling as one over the size and a distance to the frame’s edge growing with it have exactly offset each other. The displacement at the edge depends only on the window’s shape, and for 16:9 it cannot exceed px, whether the window is a small embed or a whole 4K monitor.
The sweep confirms it over 15,408 windows, from 320 to 2,560 layout units, six shapes and eight display scales. No window breaks the bound. The largest displacement anywhere is 0.708 px, on a 21:9 window for which the bound is 0.833, and most sit a long way inside it.
So the mental model should be turned over. The picture’s edge is out by less than one pixel, at any size, and the small window has no special problem. It only looks as if it does when the error is written as a proportion. In the same way, a pixel is not a point keeps its half pixel of convention at any resolution: it is a smaller share of a larger picture, and it is still half a pixel.
Of the windows swept, 54% displace the edge by under 0.02 px, a figure dominated by the whole-number display scales, and 8% by more than 0.4 px. Across the fractional scales the typical window is a good fraction of a pixel out and does not improve as it grows.
The bound depends on the shape, and on which axis is held
The width disappeared from the bound, and the shape did not. A square window has a bound of half a pixel at its edge. A 16:9 window has 0.69, a 21:9 window 0.83, and a window with the 32:9 proportions of a very wide monitor reaches 1.14. Those are small numbers, but they do show which arrangement is worst: the wide window, whose height’s rounding is multiplied up by how much longer its width is.
A portrait window goes the other way, as long as the vertical is held and the displacement is taken along the width: a 9:16 window has a bound of about 0.39. That is less impressive than it sounds, because the same difference is still there, and at the top and bottom of the frame relative to a horizontal axis held fixed it would come to more. Holding the vertical is a convention, and the bound moves with the convention. The whole of the displacement is the difference between two scales across a frame, and a renderer can place the zero of that difference anywhere it likes, at the cost of more error at the other edge.
So a fairer statement of the limit is about both rounding errors, each up to a half, each scaled by the share of the frame it acts over. Nothing in the geometry of the projection can make it larger. It is one of those rare results where a defect is worst-cased by simple counting and a sweep of fifteen thousand windows cannot find anything that exceeds the count.
A window being dragged
The rounding also explains a small thing that a careful viewer may have seen and not been able to name.
As the window is dragged wider, the displacement cycles with a period of four layout units at this scale, varying by a good third of a pixel. A static scene being rendered in a resizing window therefore has edge detail shifting back and forth by that amount as it grows, in a repeating pattern unrelated to anything in the scene. It would be masked by the far larger reflow of the content itself, but that is what makes the whole mistake so easy to live with: there has never been a moment when it was the most visible thing on the screen.
Cumulative effects are a different matter. This displacement is a scale across the frame, and so it has no effect at the frame’s centre and its full effect at the edge. Viewed from a tile’s coordinates, it has a part of the picture off by a little over a half pixel in a place where a neighbouring tile may not be. That is where the bookkeeping in a tile is an off-centre frustum becomes exact and not merely tidy. A renderer that sets each tile’s window by rounding its share of the frame and computes each tile’s aspect from its own rounded size is not rendering one picture. Seams between tiles at one of these scales can then show the displacement, which the unbroken frame would hide.
No picture can see it
How large must a displacement be before anything in a single picture could tell? Reading a pixel’s aspect ratio from one picture requires an instrument, and the instrument available is a box with a square face, which reads a pixel’s width from vanishing points, as a square closes the line of cameras develops. So try that instrument, with a window of 1,000 by 562 rounded as badly as it can be.
Six edges of 160 px read to half a pixel give a scatter of about 13 parts in a thousand. Ninety-six long edges give 3.7. The window’s rounding is at most 1.39. So this particular fault is below the sharpest test a picture can supply, by more than a factor of two even with a target far better than any real scene. The conclusion is limited to reading it through the picture’s geometry. A calibrated test, one that renders a known circle with hundreds of pixels on its rim and reads it back as a whole, could detect it easily. It is simply that nothing about a normal picture ever has.
There is a useful negative conclusion there. A recovered camera cannot be used to diagnose this. A camera recovered from a rendered image will report one focal length if the method assumes one and a pair if it allows two, and in the second case the reported difference will be noise. Recovering the camera gives no warning, which is correct: the camera it reports reproduces the picture to better than any reading.
Centring has the same arithmetic
Rounding reaches a viewport in one other way. A picture of a fixed shape centred in a larger buffer leaves bars at the sides or above and below, and each bar must be a whole number of pixels.
At 1,920 by 1,080, a 16:9 picture fills the buffer, and all is well. At 1,921 by 1,080 one column is left over, half of it on each side, and half a column is not available. The viewport goes half a pixel off centre, and the picture’s principal point is half a pixel from the buffer’s middle. It is exactly the half pixel of the sampling convention, and it arrives through a route nobody thinks of as sampling. Whether it matters depends on who is reading. A frame stored with its bars and read back with the assumption that the picture is centred gets a principal point half a pixel out. A frame read by its viewport does not, since the viewport has already been reported honestly.
The cure is one line, and that line is about units
All of the anisotropy goes away by taking the aspect from the buffer rather than the window:
The two focal lengths are then equal, exactly, on every window, by construction: . The field of view moves by a tiny amount, since the vertical half-angle is unchanged and the horizontal one is computed from the whole-number shape instead of the layout shape, and that is correct, since the buffer’s shape is what is on the screen. Over the same windows the ratio comes out at one, to the arithmetic floor.
So why does anybody do otherwise? Because a layout system hands a canvas its size in layout units, and hands it that number first. The aspect is computed where the size is known, and the buffer is sized a line or two later, by another function, perhaps through a device-scale helper and a different rounding rule. There is often no point in the program at which both numbers are in hand, and every test passes whichever is used, because nothing that reads a picture can detect the difference, as shown above. So the fix is very cheap and nothing demands it.
The analogy with the earlier resize is close. There, a multiplication looked correct because it composed with itself. Here, an aspect looks correct because it is what the window claims to be. In both, the true number is available in the pipeline, and the mistake comes from asking the wrong component.
Why it might matter anyway
A displacement of 0.7 px at the frame’s edge is below the threshold of almost anything viewed on its own. Three cases are worth naming where it is not.
Stereo. Two buffers rounded independently, for two eyes, carry their own displacements. If they differ, the difference is a horizontal disparity that varies across the frame, and a horizontal disparity of half a pixel at the edge of a head-mounted display’s panel is a stated depth at that position. In practice, two eyes usually have identical buffers, so the rounding is identical and cancels. The case to worry about is the one where each eye’s buffer has been given its own shape or scale.
Rendered training data. A pipeline that renders images for a model to learn camera poses from will often report the exact intrinsics it asked for. If the aspect came from the window, the intrinsics are off by exactly this much and the labels have a window-dependent bias. It sits at fractions of a pixel, comparable to the targets such models are scored on.
Reprojection and history. A temporal renderer, reprojecting last frame’s pixels to this frame’s positions, can keep picking up a fraction of a pixel when the window has changed, which makes a resize leave a brief smear at the frame’s edges and nowhere else. That is a small price, and one that does not recur once the size settles.
A second scale on the way to the screen is a different fault
It treats a display’s scale as one number per window. A browser can report a fractional scale that was itself rounded, or apply a further scale to the buffer on its way to the screen, and that second scale is a resample with all the consequences of the halved picture and none of the bound here. A mismatched canvas in a web page, whose buffer does not match the on-screen size, is a different and much larger fault of that kind.
The sweep’s windows also come in six shapes and a fixed step of seven layout units. A window between those steps is not tested directly, but the bound does not depend on the steps, since it comes from the rounding limits themselves.
And the displacement is taken at the frame’s edge in the horizontal direction with the vertical held fixed, which is one choice of what to hold still. A renderer that treats both axes alike, or holds the horizontal field, splits the same difference differently. Every such choice moves the error between the two edges without altering its size by more than the factor of the shape.
Still open: what a real display’s own scale does on top
Every number here is a rounding between a layout’s coordinates and a buffer. On a fractional display, a compositor then often scales that buffer to the panel’s real pixels, typically drawing at a whole-number scale and reducing, and that is a resample with a real filter. If it uses the true map, the earlier essay’s arithmetic applies to the composition. If it uses another, there is yet another centre in play.
The measurement that settles what that adds composes three steps — a layout’s rounding into a buffer, a compositor’s scale from buffer to panel, and a readback from panel to a screenshot — and tracks a mark at the frame’s edge through all three, at the common scales. It asks whether the composition can exceed the bound set here or whether the bound survives it, and whether a screenshot of a rendered scene, taken at the panel’s size, still shows the camera that the renderer thought it was using.
An error that falls with the size when written as a proportion, and stays still when written as a distance, is an error about pixels. It should be stated in pixels.
What links here
Computed from the collection, not written here: the essays that point at this one.
Shares its objects with
Essays that name at least two of the same things, and that neither author linked.
- A box leaves a line of cameras — both name anisotropy, aspect ratio, camera calibration, focal length, principal point
- A curved screen is eight flat ones — both name camera matrix, quantisation, sampling grid
- A known target sharpens the fit and does not separate it — both name camera calibration, focal length, principal point
- A wedge of glass turns the camera behind it — both name camera calibration, focal length, principal point
- Fitting a lens from straightness alone — both name camera calibration, focal length, principal point
- Focusing is a zoom — both name camera calibration, focal length, principal point
Named objects
A flat tag is an object no other essay names yet.
AnisotropyAspect ratioCamera calibrationCamera matrixFocal lengthPrincipal pointQuantisationSampling grid