← Writing

Bright footage kills white captions, and shadow cannot save them

Why we stopped adding drop shadow to captions over blown-out frames and started flipping the ink instead — with the thresholds we settled on.

LumaCaption · · 6 min read

A drop shadow cannot rescue white captions on bright footage because a shadow can only darken what is already behind the glyph, and on a blown-out frame there is nothing left to darken — the fix is to change the colour of the text rather than to add more treatment behind it.

Almost every captioning tool solves legibility the same way: put a shadow behind the text, and if that is not enough, put a thicker outline around it. It works most of the time, which is why the failure is so easy to miss until someone films outdoors.

Shoot against a white wall, a bright sky, a window, or snow, and white captions dissolve. The instinctive response is to turn the shadow up, and it does not help — it produces an ugly grey smear around the letters and the text is still hard to read. We spent a while assuming we had the shadow parameters wrong before realising the approach was wrong.

Why more shadow makes it worse, not better

A drop shadow is a darkened, blurred copy of the glyph drawn behind it. It creates contrast by making the immediate surroundings of the letterform darker than the letterform. That works when the footage behind has headroom to be darkened into.

On a high-key frame there is no headroom. The pixels behind the caption are already near the top of the range, so darkening them by the amount a tasteful shadow applies moves them a few percent — nowhere near enough to separate white type from a white background. Push the shadow far enough to actually work and you are no longer drawing a shadow, you are drawing a grey box, at which point you have made the decision to obscure the picture but done it in the least attractive way available.

An outline has the same problem from the other direction. A heavy stroke does restore legibility, but it converts every caption style you own into outlined text. The face, the weight and the personality you chose stop mattering, because at a stroke thick enough to rescue a blown-out frame the outline is the dominant visual feature.

What a careful editor does instead

Watch someone caption footage by hand and they do not reach for the shadow slider on a bright shot. They change the text colour to something dark, because the problem is not that the text lacks a treatment — it is that the text is the wrong colour for what is behind it.

So that is what the renderer does. It reads how bright the footage is behind the caption and, when the frame is genuinely at one end of the range, flips the ink: bright frame, dark text; dark frame, light text. The preset keeps its typeface, size, motion and accent hue. Only the ink changes.

The result is that a style chosen for its personality does not quietly become unreadable over particular footage, and it does not have to become outlined text to survive either.

The two rules that make it safe

Adapting to the picture is easy to get wrong in ways that are worse than not doing it. Two constraints did most of the work of making it reliable.

The first is that the renderer never reads pixels. It is a pure function of its inputs, and the background luminance is passed in by the caller — because only the caller knows where the footage actually is. In the live preview the captions are drawn on a transparent canvas over a video element, and in the exporter they are composited onto the decoded frame. A renderer that tried to sample for itself would be right in one of those cases and wrong in the other.

The second is quantisation. The luminance is not used as a continuous value; it is bucketed into three bands with a deliberately wide dead zone in the middle. A continuous value would let a caption change colour halfway through a word during a fade, which looks like a bug even when it is arithmetically correct. The dead zone means adaptation only happens when the footage is genuinely at one end rather than whenever it drifts past the midpoint.

There is a second, less obvious benefit to bucketing. The layout cache compares style identity to decide whether it can reuse the previous frame’s measurements. A style object that changed on every frame would bust that cache continuously; a style that only changes when the band changes keeps it stable.

Luminance measure
Rec.709 relative luminance, 0 to 1 — the standard weighting that accounts for the eye being far more sensitive to green than to blue. Averaging raw RGB instead is a common shortcut and it misjudges saturated footage badly.
Light threshold — 0.62
At or above this, the frame counts as bright and the ink goes dark. Set well above the midpoint on purpose: adapting is a visible change and should be reserved for footage that is genuinely high-key.
Dark threshold — 0.34
At or below this, the frame counts as dark and the ink goes light. Symmetrically below the midpoint.
The dead zone — 0.34 to 0.62
Most footage lives here and nothing happens: the preset is used exactly as designed. This band is wide because a caption that adapts on ordinary mid-tone footage is more distracting than one that never adapts at all.
What is sampled
Only the sub-rectangle of the source that a cover-fitted frame actually shows. Sampling the whole source was a real bug — it made the preview and the export disagree about the ink on any reframed project.

What this does not fix

Flipping the ink handles a frame that is uniformly bright or uniformly dark. It does not handle a frame that is both, which is the genuinely hard case: a subject lit against a dark background, with the caption crossing the boundary. There the correct answer really is a treatment behind the text rather than a colour change, and the tools available are the ones this post has been sceptical about.

It also does not fix a caption placed somewhere silly. The most reliable legibility improvement available to anyone reading this is not a rendering technique — it is moving the caption to a part of the frame that is not busy, which is a decision no automatic system should be making on your behalf.

If you are building something similar

The generalisable findings, separated from our specific implementation.

Treat legibility as a colour problem before treating it as a treatment problem. Shadows and strokes are additive fixes that work in the middle of the range and fail at the ends, and the ends are exactly where you need them.

Quantise anything you derive from the picture. Continuous adaptation reads as instability, and stability is worth more than precision for something a viewer is reading rather than looking at.

Keep the thing that measures separate from the thing that draws. The bug that cost us the most was not in the adaptation logic at all — it was sampling the wrong region, and it only appeared on reframed projects, which is the kind of thing a pure renderer with an explicit input makes obvious instead of mysterious.

Getyourvideoswatched.

Every style unlocked. No credit card.