Turning a 1080p Screen Recording Into a Usable 4K Asset

Screen recordings are the one case where 4K is genuinely the right target, because UI text is the highest-frequency content video can carry. But text also fails differently — measured: 4× upscaling loses 12% of the ink pixels, and a default 4:2:0 export silently destroys up to 46% of the colour in small text without moving the ink count at all.

14 November 2026 · ink-coverage measurement on rendered text, plus chroma-retention measurement across 4:2:0 / 4:2:2 / 4:4:4 encodes

Everything in the previous article about 4K being a bit-budget trap has an exception, and this is it. A screen recording is not a photograph. It is a rendering of vector content at a fixed pixel grid, and when you scale it, you are re-rasterising that vector content badly instead of resampling natural texture.

Why text is the hardest content to upscale

Natural images have a smooth frequency spectrum: most energy is low, tailing off gradually. Text does not. A glyph edge is a near-instant transition from background to ink, which means its energy is concentrated at the highest frequencies the grid can represent. When you downscale text, you do not lose "some detail" — you lose the specific frequencies that make the letterform legible.

Measured on rendered text, comparing ink coverage (pixels below mid-grey) after downscale-then-upscale at each factor:

FactorNearest inkBilinear BicubicLanczos
Ground truth8,682 px8,682 8,6828,682
2×8,2448,237 8,3488,392
3×8,1457,855 7,9407,986
4×8,2767,649 7,8697,877

At 2×, the best method retains 96.7% of the ink pixels. At 4×, the best retains 90.7%, and the worst (bilinear) retains 88.1%. That 12% loss is not distributed evenly — it comes off the thin strokes first, which is exactly the eyelash equivalent for typography: the thin parts of letters, the serifs, the counters inside an "e" or "a".

Compare with the same measurement on a line drawing. Line art showed a 7% loss at 2× and up to 21% overshoot after sharpening. Text is more robust than hand-drawn line art at 2× and degrades faster beyond it. Both fail at thin strokes first. That consistency across two very different content types is the reason to believe the mechanism.

The mistake almost everyone makes

Upscaling the recording. If you control the source, do not do this at all — re-record at 4K, or better, re-render from the app at 4K. Upscaling a 1080p capture to 4K spends real compute to produce letters that are 12% thinner than the original and have soft edges.

The exception: you have a finished recording you cannot re-shoot. Then upscaling is the only path, and 2× is the right target. From 1080p, 2× is 4K exactly — which is convenient, and means the "go to 4K" instinct happens to be right for once.

What to do about the areas around the text

A screen recording is mostly flat UI: solid panels, thin dividers, icon strokes, and text. Flat panels upscale perfectly under any method (a constant stays constant). The dividers and icons behave like line art — they lose weight. The text loses ink. So the visible degradation is concentrated in a small fraction of the pixels, which is why it is easy to miss in a thumbnail and obvious full-screen.

  1. Record at native resolution of the target if at all possible. This single step removes the entire problem.
  2. If you must upscale, use a method that preserves edges — Lanczos or bicubic, not bilinear. The table above shows bilinear is consistently worst on ink coverage.
  3. Do not sharpen the result. Sharpening increases ink coverage 18–21% (measured on line art, same mechanism), which thickens strokes and produces a halo on flat UI panels where there is nothing to hide it.
  4. Check the smallest text, not the largest. Title text survives everything. Caption text, menu labels and code are where the loss lands.

Why the bit budget changes for this content

In the bits-per-pixel measurement, 4K at the same bitrate as 2K got 0.2237 bpp versus 0.4206. For photographic content that is a real quality cut. For UI content it is less painful, because flat regions compress almost for free — a solid panel costs nearly nothing at any resolution. The bit budget gets spent on the text and edges, which are a small fraction of the frame.

So the usual "4K at the same bitrate is worse" rule softens for screen recordings. Not because the arithmetic changed but because the content's entropy is concentrated in a small area. This is the one content class where a 4K export at a modest bitrate is a reasonable trade.

The second loss channel nobody measures: chroma subsampling

Everything above measured ink at a mid-grey threshold, which is a luma measurement. That is the metric every sharpness article uses, and it is the metric most people check when they look at an exported frame. It is also incomplete, because the moment your recording leaves the capture app it goes through a colour transform — RGB to YCbCr — and an export preset that almost certainly throws away chroma resolution before a single pixel is scaled.

H.264 at 4:2:0 stores one chroma sample for every 2×2 block of luma samples. That is a 50% cut in horizontal chroma detail and a 50% cut vertically, applied unconditionally, before rate control even gets a say. For a photograph this is invisible: human vision resolves colour far more coarsely than brightness, which is the entire reason the format exists. For coloured UI text it is not invisible, because the thing you are trying to preserve is a two-pixel wide coloured stroke.

I rendered a 1920×1080 test plate with four sizes of text (24, 16, 13, 11 px) in four colours, on both a white and a dark background, encoded each through libx264 and decoded a frame back, then measured mean chroma magnitude inside the glyph ink mask.

Text size4:2:0 crf184:2:2 crf18 4:4:4 crf184:2:0 crf28
24 px84.0%91.1% 94.6%71.5%
16 px66.0%81.4% 87.1%48.6%
13 px60.2%76.3% 85.8%42.4%
11 px54.1%73.1% 85.3%38.7%

Chroma retained, as a percentage of the ground truth's chroma magnitude inside the ink mask. Coloured text only (25 groups above the measurement's noise floor — achromatic rows sit at a chroma magnitude near zero, where any ratio is meaningless, so they are excluded rather than reported as a spectacular 215% gain).

The result that makes this worth writing down: luma ink coverage after the round trip was 100.7% on average under 4:2:0 (range 98.6–104.4%) and 100.2% under 4:4:4. Your existing ink-coverage check, the one in the table further up this page, reports the export as essentially perfect — while 46% of the colour in 11 px text is gone.

That is the whole trap. The metric that is easy to run says the file is fine. The damage is entirely in the channel that metric does not look at. Glyph edges survive as luminance steps — the letters remain legible, the document still reads — but saturated UI colours desaturate toward grey, and any colour used to carry meaning (a red error label, an amber warning, a green "paid" badge) loses the property that made it distinguishable in the first place. In the worst single case, dark green at 11 px, 4:2:0 kept 48.6% of the original chroma while 4:4:4 kept 72.3%.

Two details rule out the obvious explanations.

It is not rate control. Stepping 4:2:0 from crf18 to crf28 halved the file (44,022 → 22,243 bytes) and dropped 11 px chroma from 54.1% to 38.7%. So bitrate matters — but even the generous crf18 encode, at 8.80 Mbit/s, cannot climb above the ceiling that the subsampling sets. The ceiling is structural, not budgetary.

It is not bleeding, mostly. Mean chroma appearing in the background pixels inside the text bounding box — pixels where ground truth has none — rose from 5.10 under 4:4:4 to 6.42 under 4:2:0 at 11 px. Real, measurable, and an order of magnitude too small to explain a 31-point drop in retained chroma. The colour is not smeared outward so much as averaged away in place.

What this costs, and what to change

This is the part that should end the debate. I re-encoded the same plate at 4:4:4:

EncodeBytesBitrate Chroma @11pxvs 4:2:0
4:2:0 crf1844,0228.80 Mb/s 54.1%—
4:4:4 crf1847,5089.50 Mb/s 85.3%+7.9%
4:2:2 crf1851,19110.24 Mb/s 73.1%+16.3%

A 7.9% larger file buys 31 percentage points of retained colour in the smallest text. This is a far better exchange rate than anything in the upscaling chain, where doubling compute typically buys single-digit gains. And note 4:2:2 is worse than 4:4:4 on both size and chroma — it is a compromise format for camera capture, not something to pick for a screen recording you control end to end.

  1. Export screen recordings at 4:4:4. The cost is under 8% on this content and it removes the loss channel entirely rather than reducing it. If your tool offers "high profile with 4:4:4" or a "lossless-ish" toggle, that is the one to take.
  2. Do not fix it by raising bitrate within 4:2:0. The measurements cap out around 54% retention at 11 px no matter how generous the rate control gets.
  3. Do not trust a luma-based check on its own. Ink coverage said 100.7% while nearly half the colour was gone. If you only have one metric, measure chroma magnitude inside the glyph mask, not pixel count.
  4. Be careful with colour-as-meaning. If red-versus-amber is the semantic content of your UI, 4:2:0 at small sizes makes those two states converge. Legibility survives the damage; distinguishability does not.

One honest limit on all of this: 4:4:4 is better, not free of loss. Even at 11 px it retained 85.3%, not 100%, because the lossy quantisation stage still runs. The only setting that retains everything is a lossless codec, which is why capture at the final resolution remains the highest-value move on this page. Subsampling control is what you reach for when you cannot re-record.

Reproducing the chroma measurement

ffmpeg -y -i gt.png -c:v libx264 -preset slow -pix_fmt yuv444p -crf 18 out.mp4
ffmpeg -y -i out.mp4 -frames:v 1 back.png

y, cb, cr = rgb_to_ycbcr(np.asarray(Image.open("back.png").convert("RGB"), float))
chroma = np.hypot(cb - 128, cr - 128)          # saturation magnitude
# sample inside the glyph ink mask taken from the ground truth, not the decoded frame
print(chroma[ink_mask].mean() / gt_chroma[ink_mask].mean() * 100)

Sample the mask from ground truth, always. Deriving the mask from the decoded frame lets the codec define its own score — a frame with the colour averaged away produces a smaller ink mask and therefore grades itself higher.

Reproducing it

# render text at high res, downscale, upscale, count ink pixels
img.save("gt.png")
small = img.resize((w//factor, h//factor), Image.LANCZOS)
back  = small.resize((w, h), Image.LANCZOS)

ink = (np.asarray(back.convert("L"), float) < 128).mean() * 100
# compare to ink of the ground truth; delta is what the method lost or added

Try one frame of your recording

Extract a single frame with the smallest text in it, upscale that, and look at 100%. If the thin strokes hold, the recording will hold. If they go soft, no amount of bitrate saves them — the pixels are gone. UpgoLAB runs the upscale in the browser for photo and short video, which makes a one-frame test take seconds instead of a render queue.

One frame tells you the answer. That is the useful part about text: it fails visibly and early, not gradually.