Letterbox & Pillarbox: The Exact Math of Black Bars
Bar size = (box − scaled) ÷ 2 — letterbox/pillarbox math, which side the bars land on, windowboxing, and how to compute or avoid them.
Black bars are not a style choice the player made — they’re the only honest output of fitting one aspect ratio inside another. Once the math is visible, every bar you’ve ever seen is predictable to the pixel.
The one formula
Fitting a source sw × sh into a box bw × bh uses the smaller scale factor:
s = min(bw ÷ sw, bh ÷ sh)
W' = sw × s
H' = sh × s
bars on each side = (box − scaled) ÷ 2 — on whichever axis fell short
The smaller factor is always bound by exactly one axis. The other axis comes up short, and the shortfall — split evenly — is the bar.
Which side gets the bars?
The aspect comparison decides it, no memorizing needed:
- Source wider than box (
src aspect > box aspect): width touches, height falls short → bars top and bottom = letterboxing. Cinema 2.39:1 in a 16:9 player is the canonical case. - Source taller than box (
src aspect < box aspect): height touches, width falls short → bars left and right = pillarboxing. 4:3 or 9:16 content on a widescreen display. - Same aspect: no bars at all — fit fills the box exactly.
A quick orientation check: the content is the shape it keeps; the bars fill the rest. Tall content → bars stand beside it (pillars). Wide content → bars lie above and below it (a letter slot).
Worked examples (verify them on the scaler)
| Source | Box | Scaled | Bars |
|---|---|---|---|
| 4:3 TV (1440×1080) | 1920×1080 | 1440×1080 | pillarbox 240 px each side |
| Cinema 2.39:1 (1920×803) | 1920×1080 | 1920×803 | letterbox ~138 px each |
| 9:16 phone video (1080×1920) | 1920×1080 | 607×1080 | pillarbox ~656 px each |
| 16:9 clip (1920×1080) | 1080×1920 story | 1080×607 | letterbox ~656 px each |
| Ultrawide 21:9 (2560×1080) | 1920×1080 | 1920×810 | letterbox 135 px each |
That symmetric pair — vertical video in a widescreen player vs widescreen video in a vertical story — is the same ×0.5625 fit flipped, which is why the bars come out identical.
The half-pixel problem
(box − scaled) ÷ 2 isn’t always an integer: (1080 − 803) ÷ 2 = 138.5. Renderers split the remainder unevenly — 138 px one side, 139 the other — or round the scaled size to even first so the remainder lands whole. Video encoders do the second: an even-aligned output can’t have a fractional bar, which is one more reason codec-facing sizes round to even.
Windowboxing: bars on all four sides
Windowboxing is bars baked into the content meeting more bars at playback. Classic chain: a widescreen film letterboxed into a 4:3 broadcast master → that 4:3 master pillarboxed into a 16:9 stream. The image floats in a black frame with real pixels storing the black.
No scaling mode removes baked bars — they’re part of the image. The fix is detection + crop: find the bar region (FFmpeg’s cropdetect does it automatically: cropdetect=limit=24:round=2), crop it out, then fit normally. The tell is a 16:9 file whose “content” is actually a different aspect — compute the aspect of the non-black region, not the file.
Removing bars vs choosing them
Bars exist because fit refuses to crop or distort. The alternatives are the other two honest modes from the fit/fill/stretch guide: fill (crop the overflow — YouTube’s “zoom to fill” button) or stretch (distort — the mode that made a generation of 4:3 TVs play squashed widescreen). A fourth option exists when you control the pipeline: export the content’s native aspect and let the player letterbox — which is exactly what happens when a 2.39:1 file plays in a 16:9 window.
For deliberate letterboxing — say, burning cinema bars into a 16:9 deliverable — FFmpeg’s pad filter takes the same formula: pad=W:H:(ow-iw)/2:(oh-ih)/2:black centers the scaled image on a black canvas. CSS does it visually for free: object-fit: contain inside a fixed box is the bar formula, rendered live.
Frequently asked questions
Letterbox vs pillarbox — which is which?
Letterbox = horizontal bars, top and bottom: a wider source inside a taller/narrower box (2.39:1 cinema on a 16:9 TV). Pillarbox = vertical bars, left and right: a taller/narrower source inside a wider box (4:3 archive footage on a 16:9 screen). The names describe where the bars stand — letterbox bars lie flat like a mail slot, pillarbox bars stand like columns.
How do I compute the bar size exactly?
Scale the source by s = min(boxW÷srcW, boxH÷srcH), then the bar on the non-touching axis is (box − scaled) ÷ 2 on each side. 2.39:1 film into a 1920×1080 frame: scaled 1920×803 → (1080 − 803) ÷ 2 ≈ 138.5 px bars top and bottom. If the result is a half-pixel, one side gets one extra row — encoders and players handle this by rounding differently, so expect 138/139.
What is windowboxing?
Bars on all four sides — it happens when content was already letterboxed and then gets pillarboxed into an even wider frame (a 4:3 broadcast of a widescreen movie, played on a 16:9 TV). The picture floats in a black rectangle. There's no scaling fix because the bars are baked into the pixels — the fix is cropping the baked bars out first.
How do I add letterbox bars deliberately in FFmpeg?
Two filters: scale with aspect preservation, then pad to the container: scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black. The (ow-iw)/2 expression is the bar formula itself — output minus input, halved, centered.
Why does my 'fit' result show bars of 0 px but still leaves a gap?
Half-pixel remainder or a rounding mismatch — the scaled height rounded down to an integer leaves a 1-px (or sub-pixel) seam some renderers show as a hairline. Rounding the scaled side up instead, or padding by one pixel and cropping after, both eliminate it. For video it never arises — even-rounded dims make the remainder whole.