Skip to content

Color Range

currentMigrated from docs/colorimetry/range.mdx at 37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0

Range says which code values mean black and white. Limited range (also called tv, studio, broadcast or MPEG range) puts black at 16 and white at 235 in 8-bit video; full range (pc or JPEG range) uses 0–255. ffprobe reports it as color_range=tv or color_range=pc.

Code values8-bit limited10-bit limited8-bit full10-bit full
Luma black–white16–23564–9400–2550–1023
Chroma16–24064–9600–2550–1023
Neutral chroma128512128512

Delivered video — Blu-ray, UHD Blu-ray including HDR, broadcast, streaming — is limited range almost without exception. Full range turns up in screen recordings, some phone and webcam footage, JPEG-derived video and RGB sources. Limited range exists for historical analog reasons; it costs very little precision at 10 bits, and following the convention avoids playback problems, so encode delivery copies as limited range unless you have a reason not to.

Values below 16 or above 235 can legitimately appear in limited-range video (overshoot from cameras and filters). Players clip them on display, but encoders and filters normally keep them, so don’t clamp a source just because it has them.

Samples arePlayer assumesWhat you see
LimitedFullWashed out: black is gray, white is dull
FullLimitedToo contrasty: shadows and highlights crushed and clipped

Before changing the file, rule out the player and display: GPU drivers and TVs have their own range settings that produce the same symptoms.

Terminal window
ffprobe -v error -select_streams v:0 -show_entries stream=pix_fmt,color_range -of default=nw=1 input.mkv

yuvj420p (and the other yuvj formats) is FFmpeg’s legacy way of saying full-range 8-bit; current FFmpeg still decodes full-range 8-bit H.264 and HEVC to it.

To see what the samples actually use, measure a scene that should contain black and white, such as credits:

Terminal window
ffmpeg -ss 00:10:00 -i input.mkv -frames:v 100 -vf "signalstats,metadata=mode=print:file=-" -f null - | grep -E "YMIN|YMAX"

YMIN near 16 and YMAX near 235 means limited; values down to 0 and up to 255 mean full. A dim scene has neither, so a single frame proves nothing.

The samples are right but the tag is wrong (for example limited-range video tagged pc): retag with setparams, or fix the bitstream without re-encoding:

Terminal window
ffmpeg -i input.mkv -vf setparams=range=tv -c:v libx264 -crf 18 output.mkv
ffmpeg -i input.mkv -c copy -bsf:v h264_metadata=video_full_range_flag=0 -color_range tv output.mkv

hevc_metadata has the same video_full_range_flag option; av1_metadata uses color_range. The bitstream filter doesn’t change the container, and from a Matroska or MP4 input FFmpeg copies the old range value; with -c copy, -color_range sets only that container field, so the two agree again. For an existing Matroska file, mkvpropedit output.mkv --edit track:v1 --set color-range=1 fixes it in place; remuxing with mkvmerge keeps the old value. If the input was 8-bit full range and decodes as yuvj420p, put format=yuv420p before setparams.

The samples need to change (for example full-range phone footage for a limited-range delivery): convert with scale or zscale:

Terminal window
ffmpeg -i full.mkv -vf scale=out_range=limited,format=yuv420p -c:v libx264 -crf 18 output.mkv

On a full-range ramp this maps luma 0–255 to 16–235 and tags the output tv. The format=yuv420p is required for 8-bit full-range H.264/HEVC input, which decodes as yuvj420p: without it scale changes nothing, and zscale=range=limited converts the samples but leaves the output tagged pc. In VapourSynth, core.resize.Spline36(clip, range_s="limited") does the same, using the clip’s _Range property (0 limited, 1 full) as the input.

Range in standalone encoders and containers

Section titled “Range in standalone encoders and containers”

Y4M can carry a full-range flag (XCOLORRANGE=FULL), and FFmpeg writes it. x264 and aomenc read it; x265 4.3 and SvtAv1EncApp 4.2 ignore it and tag their output as limited. Pass the range to those two yourself: --range full for x265, --color-range full for SvtAv1EncApp.

Matroska stores range in its own Range element with different numbers from H.273: 1 is broadcast (limited) and 2 is full. With mkvmerge that is --color-range 0:1 for limited; the H.273 flag in the bitstream uses 0 for limited and 1 for full.