{"course":{"slug":"motion-and-audio","title":"Motion, Sound and Music","url":"https://addaly.com/learn/motion-and-audio","level":"intermediate","track":"create"},"lesson":{"slug":"the-gamma-shift","title":"Why your export looks washed out","url":"https://addaly.com/learn/motion-and-audio/the-gamma-shift","position":40,"of":72,"minutes":10,"answer":"Limited range puts black at 16 and white at 235 while full range uses the whole 0 to 255, so a milky, low-contrast export is limited data read as full.","keyIdea":"Limited range puts black at 16 and white at 235 while full range uses the whole 0 to 255, so a milky, low-contrast export is limited data read as full, a crushed and over-contrasty one is full data read as limited, and untagged files leave every player to guess differently."},"body":{"format":"markdown","text":"## Your export looks washed out and nothing is broken\n\nThe most common complaint in video delivery: the file looked correct in the editor, and in QuickTime or on the web the blacks are milky and the contrast is gone. Or the opposite: the shadows are crushed and the highlights are blown.\n\nNothing was damaged. The pixel values in the file are almost certainly correct. What is wrong is the *description* of what those values mean, and the player is interpreting them under a different convention from the one they were written with.\n\n## The two conventions\n\n**Full range** (also called PC range, or `pc` in ffmpeg). Black is 0, white is 255. All 256 levels carry picture.\n\n**Limited range** (video range, studio range, `tv` in ffmpeg). Black is 16, white is 235. The headroom below 16 and above 235 exists for historical reasons (analogue signal excursions, overshoot from filtering) and is preserved in digital video because broadcast equipment expects it.\n\nAlmost all delivered video is limited range. Almost all computer graphics, screenshots and rendered output are full range.\n\nNow the arithmetic. A limited-range file interpreted as full range: the player takes 16 to be a real 16 rather than black, so blacks sit above zero: **washed out, milky, low contrast**. A full-range file interpreted as limited: the player expands 16–235 to 0–255, so everything below 16 clamps to black and everything above 235 clamps to white: **crushed shadows, blown highlights, over-contrasty**.\n\n> Figure: Milky or crushed, and which way round each one is. A two by two grid, rows Limited file and Full-range file, columns Player reads limited and Player reads full: Limited file and Player reads limited: Correct; Limited file and Player reads full: Milky; Full-range file and Player reads limited: Crushed; Full-range file and Player reads full: Correct. Limited range puts black at 16 and white at 235; full range uses all 256 levels. Nothing is damaged in either failure: the numbers in the file are right and the description of them is missing, so every player guesses and different players guess differently. Tag the four values on export and the whole category of problem disappears.\n\nThose two descriptions are the diagnosis. Milky means limited read as full. Crushed means full read as limited.\n\n## Why it happens, and why it happens more than it should\n\nThe container can carry the answer. MP4 and MOV have a `colr` atom; Matroska has colour elements; H.264 and HEVC bitstreams have video usability information. All of them can say \"limited range, BT.709 primaries, BT.709 transfer\".\n\nMany encoders do not write those tags. Faced with an untagged file, every player guesses, and different players guess differently: by resolution (assuming standard-definition means BT.601 and high-definition means BT.709), by codec, or by a fixed default. The same file then looks different in VLC, in Safari, in Chrome and in Premiere, and each of them is behaving reasonably.\n\n## Tag it explicitly\n\n```bash\nffmpeg -i in.mov -c:v libx264 -crf 20 \\\n  -pix_fmt yuv420p \\\n  -colorspace bt709 -color_primaries bt709 -color_trc bt709 \\\n  -color_range tv \\\n  -movflags +faststart out.mp4\n```\n\nFour tags, and they cost nothing. `-colorspace` is the matrix used to convert between RGB and the luma-chroma representation; `-color_primaries` says which red, green and blue; `-color_trc` is the transfer characteristic, the curve; `-color_range` is the one this lesson is about.\n\nCheck what a file claims:\n\n```bash\nffprobe -v error -select_streams v:0 \\\n  -show_entries stream=pix_fmt,color_range,color_space,color_primaries,color_transfer \\\n  -of default=nw=1 out.mp4\n```\n\nIf those come back `unknown`, you have found your bug before anybody reports it.\n\n## Converting rather than relabelling\n\nTagging says what the numbers mean. Sometimes you need to actually change them: a full-range source going into a limited-range delivery has to be scaled, not relabelled.\n\n```bash\nffmpeg -i graphics.mov -vf scale=in_range=full:out_range=limited \\\n  -color_range tv -c:v libx264 -crf 18 out.mp4\n```\n\nRelabelling without scaling produces exactly the crushed look described above. This is the usual reason a motion graphic cut into filmed footage has harder blacks than everything around it.\n\n## The macOS QuickTime gamma story\n\nFor years, files exported from one application and viewed in QuickTime on macOS appeared with shifted gamma, while the same file viewed in VLC looked right. The cause was a mixture of missing tags and a system colour-management path that applied a display transform some applications expected and others did not.\n\nModern macOS is far more consistent and the problem is much reduced, but two lessons survive. First, **a video player is a colour-managed application and the display profile is part of the chain**: the same file can look different on two calibrated monitors and both be correct. Second, **judge an export in more than one player**, on more than one machine, before deciding it is right.\n\n## Where this bites hardest\n\n- **Motion graphics and titles.** Authored full range in a design tool, dropped into a limited-range timeline. Blacks become crushed and pure white text clips.\n- **Screen recordings.** Full range by nature, delivered to platforms that assume limited.\n- **Green screen.** A range error changes every value in the frame, including the ones the keyer thresholds against, so a key that worked on the original fails on the re-tagged copy.\n- **Anything round-tripped through a tool that strips tags**, which includes several popular uploaders and converters.\n\n## The free path\n\n**ffmpeg** and **ffprobe** do all the tagging, converting and checking, free. **DaVinci Resolve**'s free edition has proper colour management with explicit input and output settings and broadcast-grade scopes: the parade and waveform will show you a milky black at 16 instead of 0 immediately, which is far more reliable than judging by eye on an uncalibrated laptop. **MediaInfo** is free and shows every colour tag in a file in a readable form.\n\n## The limitation\n\nThe tags are a claim, and nothing enforces them. A file can be tagged limited and contain full-range data, and no tool will object. Tagging tells a well-behaved player what to do; it does not make the data correct, and a mislabelled file is harder to diagnose than an untagged one because everything looks authoritative.\n\nThere is also no universal agreement on what to do with an untagged file, which means the only safe position is to tag everything you produce and to verify anything you receive. This is unglamorous and it removes an entire class of problem that otherwise reappears on every project."},"check":{"question":"A motion graphic exported from a design tool is cut into filmed footage. In the finished piece, the graphic's blacks are noticeably harder and its white text clips, while the surrounding footage looks correct. What has happened?","options":[{"text":"The graphic was authored full range and has been relabelled as limited without being scaled, so 0 to 255 is being expanded as though it were 16 to 235.","correct":true},{"text":"The graphic is 8-bit while the footage is 10-bit, so the graphic's values are being shifted two bits left during the conversion and the top of its range overflows.","correct":false,"tempting":"Bit-depth conversion scales rather than shifts, and it does not produce the asymmetric crush-and-clip described."},{"text":"The graphic is tagged with BT.601 primaries while the timeline is BT.709, so its colours are being converted through the wrong matrix.","correct":false,"tempting":"A primaries mismatch shifts hue and saturation rather than crushing black and clipping white."},{"text":"The design tool exported with a straight alpha channel that the editor is interpreting as premultiplied, so the graphic's values are being divided by their own alpha, which pushes opaque blacks below zero and opaque whites above the clipping point.","correct":false,"tempting":"Premultiplication errors affect partially transparent edge pixels only and cannot change values where alpha is one."}]},"prev":{"slug":"crf-and-bitrate","title":"Quality target or size target","url":"https://addaly.com/learn/motion-and-audio/crf-and-bitrate"},"next":{"slug":"bit-depth-and-banding","title":"Counting the steps in a gradient","url":"https://addaly.com/learn/motion-and-audio/bit-depth-and-banding"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Why your export looks washed out', in Motion, Sound and Music. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/motion-and-audio/the-gamma-shift (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Why your export looks washed out","courseTitle":"Motion, Sound and Music","module":{"index":5,"slug":"codecs-colour-and-delivery","title":"Codecs, colour and delivery"},"answer":null,"keyIdea":"Limited range puts black at 16 and white at 235 while full range uses the whole 0 to 255, so a milky, low-contrast export is limited data read as full, a crushed and over-contrasty one is full data read as limited, and untagged files leave every player to guess differently.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"cols":["Player reads limited","Player reads full"],"rows":["Limited file","Full-range file"],"type":"matrix","cells":[["Correct","Milky"],["Crushed","Correct"]],"title":"Milky or crushed, and which way round each one is","caption":"Limited range puts black at 16 and white at 235; full range uses all 256 levels. Nothing is damaged in either failure: the numbers in the file are right and the description of them is missing, so every player guesses and different players guess differently. Tag the four values on export and the whole category of problem disappears.","cellLabels":[["black at 16 is black","blacks sit above zero"],["16–235 stretched out","black at 0 is black"]]},"alt":"Figure: Milky or crushed, and which way round each one is. A two by two grid, rows Limited file and Full-range file, columns Player reads limited and Player reads full: Limited file and Player reads limited: Correct; Limited file and Player reads full: Milky; Full-range file and Player reads limited: Crushed; Full-range file and Player reads full: Correct. Limited range puts black at 16 and white at 235; full range uses all 256 levels. Nothing is damaged in either failure: the numbers in the file are right and the description of them is missing, so every player guesses and different players guess differently. Tag the four values on export and the whole category of problem disappears."}},"blocks":[{"t":"h2","id":"your-export-looks-washed-out-and-nothing-is-broken","runs":[{"text":"Your export looks washed out and nothing is broken"}]},{"t":"p","runs":[{"text":"The most common complaint in video delivery: the file looked correct in the editor, and in QuickTime or on the web the blacks are milky and the contrast is gone. Or the opposite: the shadows are crushed and the highlights are blown."}]},{"t":"p","runs":[{"text":"Nothing was damaged. The pixel values in the file are almost certainly correct. What is wrong is the "},{"text":"description","i":true},{"text":" of what those values mean, and the player is interpreting them under a different convention from the one they were written with."}]},{"t":"h2","id":"the-two-conventions","runs":[{"text":"The two conventions"}]},{"t":"p","runs":[{"text":"Full range","b":true},{"text":" (also called PC range, or "},{"text":"pc","code":true},{"text":" in ffmpeg). Black is 0, white is 255. All 256 levels carry picture."}]},{"t":"p","runs":[{"text":"Limited range","b":true},{"text":" (video range, studio range, "},{"text":"tv","code":true},{"text":" in ffmpeg). Black is 16, white is 235. The headroom below 16 and above 235 exists for historical reasons (analogue signal excursions, overshoot from filtering) and is preserved in digital video because broadcast equipment expects it."}]},{"t":"p","runs":[{"text":"Almost all delivered video is limited range. Almost all computer graphics, screenshots and rendered output are full range."}]},{"t":"p","runs":[{"text":"Now the arithmetic. A limited-range file interpreted as full range: the player takes 16 to be a real 16 rather than black, so blacks sit above zero: "},{"text":"washed out, milky, low contrast","b":true},{"text":". A full-range file interpreted as limited: the player expands 16–235 to 0–255, so everything below 16 clamps to black and everything above 235 clamps to white: "},{"text":"crushed shadows, blown highlights, over-contrasty","b":true},{"text":"."}]},{"t":"p","runs":[{"text":"Those two descriptions are the diagnosis. Milky means limited read as full. Crushed means full read as limited."}]},{"t":"h2","id":"why-it-happens-and-why-it-happens-more-than-it-should","runs":[{"text":"Why it happens, and why it happens more than it should"}]},{"t":"p","runs":[{"text":"The container can carry the answer. MP4 and MOV have a "},{"text":"colr","code":true},{"text":" atom; Matroska has colour elements; H.264 and HEVC bitstreams have video usability information. All of them can say “limited range, BT.709 primaries, BT.709 transfer”."}]},{"t":"p","runs":[{"text":"Many encoders do not write those tags. Faced with an untagged file, every player guesses, and different players guess differently: by resolution (assuming standard-definition means BT.601 and high-definition means BT.709), by codec, or by a fixed default. The same file then looks different in VLC, in Safari, in Chrome and in Premiere, and each of them is behaving reasonably."}]},{"t":"h2","id":"tag-it-explicitly","runs":[{"text":"Tag it explicitly"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -i in.mov -c:v libx264 -crf 20 \\\n  -pix_fmt yuv420p \\\n  -colorspace bt709 -color_primaries bt709 -color_trc bt709 \\\n  -color_range tv \\\n  -movflags +faststart out.mp4"},{"t":"p","runs":[{"text":"Four tags, and they cost nothing. "},{"text":"-colorspace","code":true},{"text":" is the matrix used to convert between RGB and the luma-chroma representation; "},{"text":"-color_primaries","code":true},{"text":" says which red, green and blue; "},{"text":"-color_trc","code":true},{"text":" is the transfer characteristic, the curve; "},{"text":"-color_range","code":true},{"text":" is the one this lesson is about."}]},{"t":"p","runs":[{"text":"Check what a file claims:"}]},{"t":"code","lang":"bash","label":"Bash","text":"ffprobe -v error -select_streams v:0 \\\n  -show_entries stream=pix_fmt,color_range,color_space,color_primaries,color_transfer \\\n  -of default=nw=1 out.mp4"},{"t":"p","runs":[{"text":"If those come back "},{"text":"unknown","code":true},{"text":", you have found your bug before anybody reports it."}]},{"t":"h2","id":"converting-rather-than-relabelling","runs":[{"text":"Converting rather than relabelling"}]},{"t":"p","runs":[{"text":"Tagging says what the numbers mean. Sometimes you need to actually change them: a full-range source going into a limited-range delivery has to be scaled, not relabelled."}]},{"t":"code","lang":"bash","label":"Bash","text":"ffmpeg -i graphics.mov -vf scale=in_range=full:out_range=limited \\\n  -color_range tv -c:v libx264 -crf 18 out.mp4"},{"t":"p","runs":[{"text":"Relabelling without scaling produces exactly the crushed look described above. This is the usual reason a motion graphic cut into filmed footage has harder blacks than everything around it."}]},{"t":"h2","id":"the-macos-quicktime-gamma-story","runs":[{"text":"The macOS QuickTime gamma story"}]},{"t":"p","runs":[{"text":"For years, files exported from one application and viewed in QuickTime on macOS appeared with shifted gamma, while the same file viewed in VLC looked right. The cause was a mixture of missing tags and a system colour-management path that applied a display transform some applications expected and others did not."}]},{"t":"p","runs":[{"text":"Modern macOS is far more consistent and the problem is much reduced, but two lessons survive. First, "},{"text":"a video player is a colour-managed application and the display profile is part of the chain","b":true},{"text":": the same file can look different on two calibrated monitors and both be correct. Second, "},{"text":"judge an export in more than one player","b":true},{"text":", on more than one machine, before deciding it is right."}]},{"t":"h2","id":"where-this-bites-hardest","runs":[{"text":"Where this bites hardest"}]},{"t":"ul","items":[[{"text":"Motion graphics and titles.","b":true},{"text":" Authored full range in a design tool, dropped into a limited-range timeline. Blacks become crushed and pure white text clips."}],[{"text":"Screen recordings.","b":true},{"text":" Full range by nature, delivered to platforms that assume limited."}],[{"text":"Green screen.","b":true},{"text":" A range error changes every value in the frame, including the ones the keyer thresholds against, so a key that worked on the original fails on the re-tagged copy."}],[{"text":"Anything round-tripped through a tool that strips tags","b":true},{"text":", which includes several popular uploaders and converters."}]]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"p","runs":[{"text":"ffmpeg","b":true},{"text":" and "},{"text":"ffprobe","b":true},{"text":" do all the tagging, converting and checking, free. "},{"text":"DaVinci Resolve","b":true},{"text":"’s free edition has proper colour management with explicit input and output settings and broadcast-grade scopes: the parade and waveform will show you a milky black at 16 instead of 0 immediately, which is far more reliable than judging by eye on an uncalibrated laptop. "},{"text":"MediaInfo","b":true},{"text":" is free and shows every colour tag in a file in a readable form."}]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"The tags are a claim, and nothing enforces them. A file can be tagged limited and contain full-range data, and no tool will object. Tagging tells a well-behaved player what to do; it does not make the data correct, and a mislabelled file is harder to diagnose than an untagged one because everything looks authoritative."}]},{"t":"p","runs":[{"text":"There is also no universal agreement on what to do with an untagged file, which means the only safe position is to tag everything you produce and to verify anything you receive. This is unglamorous and it removes an entire class of problem that otherwise reappears on every project."}]}],"check":{"question":[{"text":"A motion graphic exported from a design tool is cut into filmed footage. In the finished piece, the graphic’s blacks are noticeably harder and its white text clips, while the surrounding footage looks correct. What has happened?"}],"options":[{"text":[{"text":"The graphic was authored full range and has been relabelled as limited without being scaled, so 0 to 255 is being expanded as though it were 16 to 235."}],"correct":true,"tempting":null},{"text":[{"text":"The graphic is 8-bit while the footage is 10-bit, so the graphic’s values are being shifted two bits left during the conversion and the top of its range overflows."}],"correct":false,"tempting":[{"text":"Bit-depth conversion scales rather than shifts, and it does not produce the asymmetric crush-and-clip described."}]},{"text":[{"text":"The graphic is tagged with BT.601 primaries while the timeline is BT.709, so its colours are being converted through the wrong matrix."}],"correct":false,"tempting":[{"text":"A primaries mismatch shifts hue and saturation rather than crushing black and clipping white."}]},{"text":[{"text":"The design tool exported with a straight alpha channel that the editor is interpreting as premultiplied, so the graphic’s values are being divided by their own alpha, which pushes opaque blacks below zero and opaque whites above the clipping point."}],"correct":false,"tempting":[{"text":"Premultiplication errors affect partially transparent edge pixels only and cannot change values where alpha is one."}]}],"order":[1,0,2,3],"why":null},"prev":{"slug":"crf-and-bitrate","title":"Quality target or size target","minutes":10},"next":{"slug":"bit-depth-and-banding","title":"Counting the steps in a gradient","minutes":10}}}