Transfer-Function Conversion in Digital Imaging and Video

Definitions, conversion mathematics, failure modes, historical causes, browser behavior, and diagnostic methods

Research cut-off: 7 August 2026
Audience: color scientists, codec and video-pipeline engineers, browser/graphics implementers, finishing and mastering engineers
Companion files: the Simplified-Chinese edition and a reproducible NumPy script are supplied separately.


Evidence policy and notation

Every substantive historical or technical claim is marked by source class:

A citation such as [A-04] refers to the classified bibliography in §11.4. When an exact historical claim could not be supported by a primary source, the text says so explicitly.

Unless otherwise stated:

The numerical tables were computed in double precision. The exact formulas and assumptions are restated beneath each table and are implemented in gamma-analysis-repro.py.


1. Introduction: why the same media does not look the same

A file does not contain “brightness” in a self-sufficient sense. It contains code values plus, sometimes, declarations about what those values mean. The final image is produced by a chain:

scene light
  -> camera/scene encoding
  -> RGB or Y'CbCr representation
  -> quantization and legal range
  -> codec bitstream
  -> container metadata
  -> decoder and color conversion
  -> application working space
  -> effects, scaling, compositing, tone mapping
  -> OS compositor and display profile
  -> display EOTF and viewing environment
  -> perceived appearance

A difference anywhere in that chain can be reported colloquially as a “gamma shift,” even when the actual error is an affine range remap, a 601/709 matrix mismatch, a display-profile conversion, an HDR tone mapper, a chroma-resampling phase error, or arithmetic performed on nonlinear samples. That loose vocabulary is the principal reason the subject remains confusing.

The most useful engineering rule is therefore:

Do not diagnose a gamma error from appearance alone. First identify the signal domain, metadata, range, matrix, transfer function, color primaries, working space, output transform, and display path.

A genuine transfer-function mismatch has a characteristic nonlinear relationship between corresponding samples. Full/limited-range confusion has a characteristic affine relationship. Matrix confusion changes colors in a direction-dependent way. Tone mapping is generally content- and luminance-dependent and cannot be represented by one scalar exponent. These distinctions are measurable.

1.1 Four different questions that are often collapsed into one

  1. How was scene light encoded? This is the OETF or another scene-to-code transform.
  2. What light should a reference display emit for a code? This is the EOTF.
  3. What rendering relationship was intended between scene light and display light? This is the OOTF.
  4. What arithmetic did software actually perform on stored samples? This may have no colorimetric validity at all.

The rest of this article keeps these questions separate.

1.2 A layer model for failures

Layer Correct question Typical false diagnosis
Encoding Which OETF/encoding curve produced the samples? “The file is gamma 2.2.”
Signal representation RGB, Y′CbCr, constant-luminance, ICtCp? “Y is luminance.”
Quantization Full or narrow range; bit depth; headroom/footroom? “The contrast/gamma changed.”
Metadata What do CICP/VUI, colr, ICC, PNG chunks say? “The codec changed the pixels.”
Processing Were operations done in code space or linear light? “The scaler is darker.”
Rendering Which EOTF, OOTF, tone map, display profile, surround? “The monitor has the wrong gamma.”

2. Conceptual foundation: OETF, EOTF, OOTF, and system gamma

2.1 Strict definitions

ITU-R BT.2100-3 defines the terms as follows [A-07, Annex 1]:

For a reference chain in which one OETF feeds one EOTF:

OOTF=EOTFOETF. \operatorname{OOTF} = \operatorname{EOTF}\circ\operatorname{OETF}.

Conversely, an OETF designed to realize a desired OOTF on a specified EOTF can be written:

OETF=EOTF1OOTF. \operatorname{OETF} = \operatorname{EOTF}^{-1}\circ\operatorname{OOTF}.

This is why an EOTF is not, in general, the inverse of the camera OETF. The inverse OETF reconstructs scene-referred linear values. The EOTF produces display-referred linear light. Those are different endpoints.

2.2 Why “gamma = 2.2” is usually incomplete

The phrase can mean at least six different things:

  1. a pure decoding law L=V2.2L=V^{2.2};
  2. an encoding law V=L1/2.2V=L^{1/2.2};
  3. a display calibration target;
  4. a rough description of the sRGB transfer function;
  5. an effective end-to-end exponent;
  6. a software control whose parameter is applied as 1/γ1/\gamma, as in AviSynth/VapourSynth Levels.

Only the first two unambiguously specify a mathematical function, and even then the black and white normalization, clipping behavior, sign handling, and domain must be stated. sRGB is not a pure 2.2 law. BT.1886 is not a pure 2.4 law when LB>0L_B>0. BT.709 is an OETF with a linear toe and an offset power branch, not “gamma 0.45.” PQ is not meaningfully described by a single gamma. HLG has an OETF plus an explicit luminance-dependent OOTF.

2.3 Encoding gamma, display gamma, and system gamma

For the special case of pure powers,

E=Egc,FD=(E)gd, E'=E^{g_c}, \qquad F_D=(E')^{g_d},

where gc<1g_c<1 is the camera/encoding exponent and gd>1g_d>1 is the display exponent. Then

FD=Egcgd, F_D=E^{g_cg_d},

and the system gamma is gs=gcgdg_s=g_cg_d.

An alternate notation calls the encoding “gamma” γc=1/gc\gamma_c=1/g_c. In that notation the same system exponent is gs=gd/γcg_s=g_d/\gamma_c. Mixing these two conventions is a common source of reversed corrections.

For real television curves, the composite is not exactly a power law, so “system gamma” is a rendering-intent characterization or a local/fitted slope, not an identity valid at every level.

2.4 BT.709 OETF plus BT.1886 EOTF: why the intended result is about 1.2, not a defect

BT.709-6 specifies the source OETF [A-03, Part 1, item 1.2]:

E={4.5E,0E<0.018,1.099E0.450.099,0.018E1. E' = \begin{cases} 4.5E, & 0\le E<0.018,\\ 1.099E^{0.45}-0.099, & 0.018\le E\le1. \end{cases}

With an ideal-black BT.1886 display, the normalized EOTF is FD=(E)2.4F_D=(E')^{2.4}. Therefore the exact composite is

FD={(4.5E)2.4,E<0.018,(1.099E0.450.099)2.4,E0.018. F_D= \begin{cases} (4.5E)^{2.4}, & E<0.018,\\ (1.099E^{0.45}-0.099)^{2.4}, & E\ge0.018. \end{cases}

It is not E0.45×2.4=E1.08E^{0.45\times2.4}=E^{1.08}, because the BT.709 power branch has an offset. Its local log-log slope is

glocal(E)=dlnFDdlnE=2.41.0990.45E0.451.099E0.450.099(E0.018). g_{\mathrm{local}}(E)=\frac{d\ln F_D}{d\ln E} =2.4\frac{1.099\cdot0.45\,E^{0.45}} {1.099E^{0.45}-0.099} \quad(E\ge0.018).

Scene-linear EE BT.709 EE' BT.1886 relative output Local log slope
0.018 0.081248 0.002419 2.3960
0.020 0.090000 0.003092 2.2680
0.050 0.186453 0.017757 1.6534
0.100 0.290940 0.051657 1.4475
0.180 0.409008 0.116992 1.3414
0.250 0.489940 0.180444 1.2982
0.500 0.705515 0.432927 1.2315
0.750 0.866551 0.709098 1.2034
1.000 1.000000 1.000000 1.1869

Calculation: direct composition of the published BT.709 OETF and ideal-black BT.1886, followed by analytic differentiation. No fit was used.

At the top end, the effective OETF slope is about 0.495, not 0.45; multiplied by 2.4 it gives 1.187. Over the upper half of the signal, a constrained log-domain fit is about 1.202. This is the engineering basis for the familiar “about 1.2” description.

More importantly, ITU-R Report BT.2390-12 explicitly states that conventional SDR television has an OOTF/system gamma of 1.2 and explains it as rendering intent that compensates for viewing an emissive screen in a dark or dim surround [A-08, §6.2]. BBC engineering literature likewise describes an effective camera exponent near 0.5 and a display exponent near 2.4, giving approximately 1.2 [L-03]. It is therefore wrong to call the non-inverse pairing a standards mistake. The non-unity OOTF is deliberate.

The toe is not well represented by any single system exponent. It reflects camera-noise and legacy design constraints and should not be used to infer that the entire system has a 2.4 OOTF.

2.5 Relative colorimetry versus absolute luminance

Most SDR RGB encodings are relative. A normalized value of 1 means reference white, but the number alone does not state whether white is 80, 100, 120, or 200 cd/m². The rendered appearance depends on the display white, black, surround, flare, adaptation, and OOTF.

PQ is different. SMPTE ST 2084 and BT.2100 define an absolute EOTF whose output is cd/m² up to 10,000 cd/m² [A-06, A-07]. A PQ code does not merely mean “a fraction of white.” HLG remains scene-relative at the OETF stage and obtains display behavior through an explicit system gamma and peak-luminance parameter. Consequently:


3. Transfer-function atlas

3.1 sRGB: IEC 61966-2-1

IEC 61966-2-1:1999 defines the following component encoding [A-01; B-01]. For linear C[0,1]C\in[0,1]:

C={12.92C,C0.0031308,1.055C1/2.40.055,C>0.0031308. C'= \begin{cases} 12.92C, & C\le0.0031308,\\ 1.055C^{1/2.4}-0.055, & C>0.0031308. \end{cases}

The decoding function is

C={C/12.92,C0.04045,(C+0.0551.055)2.4,C>0.04045. C= \begin{cases} C'/12.92, & C'\le0.04045,\\ \left(\frac{C'+0.055}{1.055}\right)^{2.4}, & C'>0.04045. \end{cases}

The standard was published in 1999; the pre-standard HP/Microsoft/W3C proposal dates to 1996, and the final IEC constants corrected a small rounding issue in the proposal [A-01; A-15]. sRGB uses BT.709 primaries and D65 but not the BT.709 OETF.

What “sRGB is approximately gamma 2.2” actually means

Define a pointwise effective decoding exponent

geff(C)=lnClnC. g_{\mathrm{eff}}(C')=\frac{\ln C}{\ln C'}.

It is 1.739 at code 8, 1.901 at code 16, 2.042 at code 32, 2.149 at code 64, 2.224 at code 128, and 2.257 at code 192. Thus no constant 2.2 describes the toe and shadows.

A least-squares pure-power fit minimizing unweighted linear-light error over the entire normalized code interval gives approximately 2.2333. A log-domain fit restricted to C[0.25,1]C'\in[0.25,1] gives approximately 2.1959. Therefore “2.2” is a useful mid/high-tone shorthand under a specified fitting criterion, not the sRGB formula.

3.2 Pure power 2.2 and its dark-region error as an sRGB substitute

For a pure 2.2 decoding law:

C2.2=(C)2.2. C_{2.2}=(C')^{2.2}.

8-bit code sRGB linear Pure 2.2 linear Pure 2.2 − sRGB Relative error Difference at 100-nit white
0 0 0 0 0 cd/m²
1 0.000303527 0.000005077 −0.000298450 −98.33% −0.029845 cd/m²
2 0.000607054 0.000023328 −0.000583726 −96.16% −0.058373 cd/m²
4 0.001214108 0.000107187 −0.001106921 −91.17% −0.110692 cd/m²
8 0.002428216 0.000492504 −0.001935712 −79.72% −0.193571 cd/m²
12 0.003676507 0.001201740 −0.002474767 −67.31% −0.247477 cd/m²
16 0.005181517 0.002262953 −0.002918564 −56.33% −0.291856 cd/m²
20 0.006995410 0.003697240 −0.003298170 −47.15% −0.329817 cd/m²
32 0.014443844 0.010397802 −0.004046042 −28.01% −0.404604 cd/m²
64 0.051269458 0.047775754 −0.003493704 −6.81% −0.349370 cd/m²
128 0.215860500 0.219519718 +0.003659218 +1.70% +0.365922 cd/m²
192 0.527115126 0.535641609 +0.008526483 +1.62% +0.852648 cd/m²
255 1 1 0 0% 0 cd/m²

Calculation: decode each integer code divided by 255 using the exact sRGB function and V2.2V^{2.2}. Relative error is measured against exact sRGB linear light. The large shadow percentages coexist with small absolute luminance differences; both facts matter.

Across continuous [0,1][0,1], the maximum absolute linear-light difference is 0.00852769 near code value 0.7496. Across integer 8-bit samples, the practical consequence depends on later quantization, display black, flare, and spatial context.

3.3 BT.1886

ITU-R BT.1886-0 was approved in March 2011 to provide a reproducible reference EOTF for flat-panel HDTV production displays as CRT reference monitors disappeared [A-04, Scope and Annex 1]. Its full form is

L=a(max(V+b,0))γ,γ=2.4, L=a\left(\max(V+b,0)\right)^\gamma, \qquad \gamma=2.4,

with

a=(LW1/γLB1/γ)γ, a=\left(L_W^{1/\gamma}-L_B^{1/\gamma}\right)^\gamma,

b=LB1/γLW1/γLB1/γ. b=\frac{L_B^{1/\gamma}} {L_W^{1/\gamma}-L_B^{1/\gamma}}.

For LB=0L_B=0, this reduces to L=LWV2.4L=L_WV^{2.4}. For LB>0L_B>0, it does not. For example, at LW=100L_W=100 cd/m²:

The max defines behavior below the effective black offset. BT.1886 also gives a 10-bit narrow-range normalization V=(D64)/876V=(D-64)/876 [A-04, Annex 1].

BT.1886 was not intended to assert that every CRT was exactly 2.4. Its motivation was to replace an undocumented, device-dependent appeal to “whatever a good CRT does” with a portable target that is reasonably equivalent to legacy reference behavior.

3.4 CRT “gamma 2.5”

A CRT’s dominant nonlinearity is associated with electron-gun/cathode-grid drive and space-charge behavior, not with a nonlinear phosphor light output [L-01, L-04]. Over the useful range, beam current is well approximated by a power of drive voltage. Engineering literature commonly reports properly adjusted CRT exponents in approximately the 2.35–2.55 range and uses 2.5 as a representative value [L-01].

Three qualifications are essential:

  1. actual exponent depends on tube, cutoff/black setup, drive circuitry, measurement interval, and fitting method;
  2. older broadcast systems did not universally standardize every receiver at exactly 2.5—historical nominal values varied by system and context;
  3. a measured CRT response can deviate from a single power near black and at high drive.

Thus “CRT gamma 2.5” is a historically useful physical approximation, not a universal metadata value that should be attached to all legacy video.

3.5 The traditional Macintosh 1.8 system

The early Macintosh convention was an effective code-to-display-light response near 1.8, achieved through the video lookup table feeding an ordinary CRT; it did not mean that Macintosh CRT phosphors had a special physical exponent [L-01]. If the CRT itself was near 2.5, a LUT with an exponent near 1.8/2.50.721.8/2.5\approx0.72 produced an overall response near 1.8.

Poynton documents a strong relationship to graphic arts and prepress: QuickDraw codes sent to an imagesetter gave a tolerable match to the approximately 1.75-power behavior of common halftone/offset-print workflows [L-01]. This supports a printing/prepress compatibility explanation. However, this research did not find a first-party Apple design memorandum proving the narrower popular story that “1.8 was chosen specifically to compensate a particular LaserWriter.” That narrower claim should be treated as unverified.

Mac OS X 10.6 Snow Leopard, released on 28 August 2009, changed the default display gamma from 1.8 to 2.2. Apple’s contemporaneous system-refinements text said the change better served digital-content producers and consumers; the original page is no longer live, but the wording survives in contemporaneous archives and quotations [B-21, C-06]. Apple’s release date is independently documented in its newsroom [B-20].

3.6 BT.601, BT.709, and BT.2020 OETFs

BT.601-7 and BT.709-6 use the same nominal piecewise OETF [A-02, §2.6.4; A-03, item 1.2]:

E={4.5E,0E<0.018,1.099E0.450.099,0.018E1. E'=\begin{cases} 4.5E,&0\le E<0.018,\\ 1.099E^{0.45}-0.099,&0.018\le E\le1. \end{cases}

BT.2020-2 expresses the same shape as [A-05, Table 4]:

E={4.5E,0E<β,αE0.45(α1),βE1. E'=\begin{cases} 4.5E,&0\le E<\beta,\\ \alpha E^{0.45}-(\alpha-1),&\beta\le E\le1. \end{cases}

The continuous coefficients are

α=1.09929682680944,β=0.018053968510807. \alpha=1.09929682680944\ldots, \qquad \beta=0.018053968510807\ldots.

BT.2020’s practical tabulated values are α=1.099,β=0.018\alpha=1.099,\beta=0.018 for 10-bit systems and α=1.0993,β=0.0181\alpha=1.0993,\beta=0.0181 for 12-bit systems. These differences preserve continuity and code-level behavior at the stated precision. The three standards therefore have nearly identical transfer shapes, but their primaries, matrices, resolutions, and digital representations are not interchangeable.

3.7 PQ: SMPTE ST 2084 and BT.2100

PQ is an absolute display-referred EOTF. For normalized PQ code EE', BT.2100-3 gives [A-07, Table 4]:

FD=10000Y, F_D=10000Y,

Y=(max(E1/m2c1,0)c2c3E1/m2)1/m1, Y=\left( \frac{\max\left(E'^{1/m_2}-c_1,0\right)} {c_2-c_3E'^{1/m_2}} \right)^{1/m_1},

where

m1=2610/16384=0.1593017578125,m2=(2523/4096)×128=78.84375,c1=3424/4096=0.8359375,c2=(2413/4096)×32=18.8515625,c3=(2392/4096)×32=18.6875. \begin{aligned} m_1&=2610/16384=0.1593017578125,\\ m_2&=(2523/4096)\times128=78.84375,\\ c_1&=3424/4096=0.8359375,\\ c_2&=(2413/4096)\times32=18.8515625,\\ c_3&=(2392/4096)\times32=18.6875. \end{aligned}

The inverse is

E=(c1+c2Ym11+c3Ym1)m2,Y=FD/10000. E'=\left(\frac{c_1+c_2Y^{m_1}}{1+c_3Y^{m_1}}\right)^{m_2}, \qquad Y=F_D/10000.

Representative codes are approximately 0.50808 for 100 cd/m², 0.58069 for 203 cd/m², 0.75183 for 1000 cd/m², and 0.90257 for 4000 cd/m². These are not fractions of a content-defined white; they encode absolute luminance according to the reference EOTF.

BT.2100 additionally defines a reference PQ OOTF and constructs its reference PQ OETF as EOTF1OOTF\operatorname{EOTF}^{-1}\circ\operatorname{OOTF}, making explicit that an HDR production OETF is not merely the inverse of PQ’s EOTF [A-07, Table 4 and Annex 1].

3.8 HLG

BT.2100-3 defines the HLG OETF [A-07, Table 5]:

E={3E,0E1/12,aln(12Eb)+c,1/12<E1, E'=\begin{cases} \sqrt{3E},&0\le E\le1/12,\\ a\ln(12E-b)+c,&1/12<E\le1, \end{cases}

with

a=0.17883277,b=0.28466892,c=0.55991073. a=0.17883277,\quad b=0.28466892,\quad c=0.55991073.

The inverse is

E={E2/3,0E1/2,[exp((Ec)/a)+b]/12,1/2<E1. E=\begin{cases} E'^2/3,&0\le E'\le1/2,\\ \left[\exp((E'-c)/a)+b\right]/12,&1/2<E'\le1. \end{cases}

HLG’s reference OOTF applies the system exponent to scene luminance rather than independently to each color component:

FD=αYSγ1E, F_D=\alpha Y_S^{\gamma-1}E,

YS=0.2627RS+0.6780GS+0.0593BS, Y_S=0.2627R_S+0.6780G_S+0.0593B_S,

where α=LW\alpha=L_W and γ=1.2\gamma=1.2 at LW=1000L_W=1000 cd/m². For nominal peaks from 400 to 2000 cd/m² [A-07, note 5f]:

γ=1.2+0.42log10(LW/1000). \gamma=1.2+0.42\log_{10}(L_W/1000).

Nominal peak LWL_W System gamma HLG HDR reference white in BT.2408-9
400 cd/m² 1.03 101 cd/m²
600 cd/m² 1.11 138 cd/m²
800 cd/m² 1.16 172 cd/m²
1000 cd/m² 1.20 203 cd/m²
1500 cd/m² 1.27 276 cd/m²
2000 cd/m² 1.33 343 cd/m²

The gamma values are the BT.2100 formula rounded to two decimals; reference-white values are from ITU-R BT.2408-9, Table 4 [A-09]. HLG therefore has no single display gamma independent of peak brightness.

3.9 scRGB, Display P3, and Adobe RGB (1998)

3.10 Compact atlas

Encoding/display function Direction Exact character Reference
sRGB linear ↔︎ code piecewise linear + offset 2.4 power IEC 61966-2-1:1999
Adobe RGB (1998) linear ↔︎ code pure 563/256 decoding power Adobe/ICC
BT.601/709 OETF scene linear → code 4.5 toe + offset 0.45 power BT.601-7 / BT.709-6
BT.2020 OETF scene linear → code same shape, precision-specific α/β BT.2020-2
BT.1886 code → display light 2.4 with black/white parameters BT.1886-0:2011
PQ code → absolute display light perceptual rational-power function ST 2084 / BT.2100
HLG OETF + OOTF scene linear → code → display hybrid log/sqrt plus peak-dependent system gamma BT.2100
scRGB scene/display linear storage linear extended-range RGB IEC 61966-2-2:2003
Display P3 linear ↔︎ code sRGB transfer, P3-D65 primaries CSS Color 4 / Apple ecosystem

4. Conversion mathematics

4.1 The general conversion, not the shortcut

Let D1D_1 decode source code V1V_1 into a common linear-light domain and E2E_2 encode that linear value into the destination representation. The correct conversion is

V2=E2(D1(V1)). V_2=E_2(D_1(V_1)).

If primaries or white differ, insert a colorimetric transform in linear light:

𝐕2=E2(MXYZ2AM1XYZD1(𝐕1)), \mathbf V_2=E_2\left(M_{XYZ\rightarrow2}\,A\,M_{1\rightarrow XYZ}\,D_1(\mathbf V_1)\right),

where AA is a chromatic-adaptation transform when required. Range and matrix decoding occur at their own layers before this RGB-light conversion.

4.2 Derivation of the exponent-ratio shortcut

Assume all of the following:

  1. source and destination use exact pure-power decoding functions;
  2. source and destination represent the same linear-light quantity;
  3. values are normalized to [0,1][0,1];
  4. black is zero and white is one, with no flare or black offset;
  5. no clipping or super-white/negative values;
  6. same RGB primaries and white point;
  7. no Y′CbCr matrix or full/limited-range conversion is being folded into the operation;
  8. no OOTF, tone mapping, exposure scaling, or viewing-environment compensation is intended.

Let

L=V1γ1,V2=L1/γ2. L=V_1^{\gamma_1}, \qquad V_2=L^{1/\gamma_2}.

Then

V2=V1γ1/γ2. \boxed{V_2=V_1^{\gamma_1/\gamma_2}}.

The direction is determined by the decoding exponents. To convert code values that currently decode with 2.2 into code values that should decode with 2.4, use exponent 2.2/2.4=0.91672.2/2.4=0.9167. Reversing the conversion uses 2.4/2.2=1.09092.4/2.2=1.0909.

4.3 Common ratios and directions

Source decoding exponent Destination decoding exponent Code-domain exponent Effect on mid-code values
2.2 2.4 2.2/2.4 = 0.9166667 raises destination codes so 2.4 decoding preserves light
2.4 2.2 2.4/2.2 = 1.0909091 lowers destination codes
2.2 2.5 2.2/2.5 = 0.88 raises destination codes
2.5 2.2 2.5/2.2 = 1.1363636 lowers destination codes
1.8 2.2 1.8/2.2 = 0.8181818 raises destination codes
2.2 1.8 2.2/1.8 = 1.2222222 lowers destination codes

“Raises codes” does not mean “raises final luminance”: the destination’s steeper EOTF cancels the code change under the stated assumptions.

4.4 Error when sRGB is replaced by a pure 2.2 source

Consider exact conversion from sRGB code VsV_s to a destination encoded for a pure 2.4 EOTF:

Vexact=DsRGB(Vs)1/2.4. V_{\mathrm{exact}}=D_{sRGB}(V_s)^{1/2.4}.

The shortcut is

Vapprox=Vs2.2/2.4. V_{\mathrm{approx}}=V_s^{2.2/2.4}.

Input sRGB code Exact destination code 2.2/2.4 approximation Approx − exact
0 0.000 0.000 0.000
8 20.753 10.675 −10.078
16 28.460 20.152 −8.308
32 43.626 38.042 −5.583
64 73.957 71.814 −2.143
128 134.621 135.567 +0.946
192 195.284 196.594 +1.310
255 255.000 255.000 0.000

All 256 integer inputs: maximum absolute error 10.2078 code values at input code 6; mean absolute error 2.1610; RMSE 3.3063; 95th-percentile absolute error 8.5509. The destination was not requantized before measurement.

The large low-code difference is exactly where the sRGB linear segment matters. A power ratio is not a faithful sRGB converter even though its midtones may look close in many images.

The reverse conversion, pure-2.4 code to sRGB, has maximum absolute error 8.5471 codes and mean absolute error 2.1684 when approximated by V2.4/2.2V^{2.4/2.2}.

4.5 Error caused by nonzero BT.1886 black

Assume source code xx represents relative light x2.2x^{2.2}. On a target display with white LW=100L_W=100 and black LBL_B, the desired absolute luminance is

Ldesired=LB+(LWLB)x2.2. L_{\mathrm{desired}}=L_B+(L_W-L_B)x^{2.2}.

The exact destination drive is V=BT18861(Ldesired)V=\operatorname{BT1886}^{-1}(L_{\mathrm{desired}}). Compare that with the black-ignorant shortcut x2.2/2.4x^{2.2/2.4}:

LBL_B Contrast Max abs error Input at max Mean abs error RMSE
0 cd/m² 0.000 code 0.000 0.000
0.001 cd/m² 100000:1 1.945 9 0.992 1.152
0.01 cd/m² 10000:1 4.800 17 2.546 2.940
0.1 cd/m² 1000:1 11.373 31 6.366 7.284
1.0 cd/m² 100:1 25.125 54 15.001 16.924

Calculation: all 256 8-bit input codes, normalized source power 2.2, absolute black-to-white interpolation as written above, exact inversion of BT.1886, no final quantization. The table is a sensitivity study, not a recommendation to master SDR at every listed black level.

A power-ratio shortcut is exact only in the ideal-black row. Once black is nonzero, the affine placement of relative image light between LBL_B and LWL_W, plus BT.1886’s a,ba,b terms, changes the required codes.

4.6 Perceptual size of the sRGB/power-2.2 approximation

The following compares the exact sRGB decoding with pure 2.2 decoding, using BT.709/sRGB primaries, D65, CIE Lab, and CIEDE2000. No chromatic adaptation or post-conversion quantization was applied.

Sample set Mean ΔE00 95th percentile Maximum Fraction ≥ 1
256 neutral codes 0.554 1.786 2.033 at code 27 18.36%
17×17×17 RGB code cube 0.523 1.273 4.799 at (0, 0.0625, 0) 8.06%

A ΔE00 near 1 is often used as a rough controlled-condition visibility scale, but it is not a universal threshold: adaptation, luminance, surround, spatial frequency, display black, and observer variability matter. For HDR, ITU’s ΔEITP is designed so that approximately 1 corresponds to one just-noticeable difference under its intended conditions [A-20]; that statement must not be transferred mechanically to ΔE00.

The table also demonstrates why “the average error is small” can coexist with visible seams, dark UI elements, or differences in low-level saturated colors.

4.7 Reversible implementation pattern

For any named pair of transfer functions:

1. Parse/override source metadata explicitly.
2. Undo quantization range and Y'CbCr matrix to obtain encoded RGB.
3. Apply the exact source inverse transfer function.
4. Transform primaries/white in linear light, if needed.
5. Apply the intended OOTF or tone map, if and only if the task requires it.
6. Apply the exact destination encoding function.
7. Convert to destination matrix/range.
8. Quantize with adequate precision and, where appropriate, dither.
9. Write matching bitstream and container metadata.
10. Verify with decoded pixel values and a controlled reference display path.

A one-line pow() is correct only when the assumptions in §4.2 have been proved, not merely guessed from visual similarity.


5. A taxonomy of apparent “gamma shifts”

This chapter is the core diagnostic classification. Each class lists mechanism, characteristic symptom, reproducible condition, and discriminating test.

5.1 Metadata failures: transfer, primaries, matrix, range, and conflicts

Modern video elementary streams can signal CICP-style values for:

H.264/AVC and HEVC carry such declarations in SPS VUI; AV1 has corresponding sequence-header fields. Containers may also carry color information: ISO BMFF/QuickTime uses a colr box/atom, commonly with nclc or nclx payloads. Matroska has its own Colour elements. A transcoder can preserve pixels while deleting, inventing, or changing these declarations.

Mechanisms

  1. Missing tags: the decoder/application applies a heuristic—often 601 for SD, 709 for HD, narrow range for Y′CbCr, or sRGB for still images.
  2. Wrong tags: mathematically valid conversion, wrong interpretation.
  3. Lost tags during transcode/remux: pixels survive, semantics do not.
  4. Container/bitstream disagreement: for example, MP4 colr says BT.709 while SPS VUI says BT.2020/PQ.
  5. Partial declarations: primaries and matrix are present but transfer is unspecified, or vice versa.
  6. Application override: an NLE’s “interpret footage” setting replaces file metadata.

There is no universal cross-application rule that “container always wins” or “bitstream always wins.” Demuxers expose side data differently; decoders may report VUI; applications may merge, override, or ignore one source. FFmpeg issue reports demonstrate real mov colr versus SPS-VUI disagreements [B-24]. Therefore precedence is an empirical property of a particular demuxer/decoder/application version, not a safe assumption.

Symptoms

Reproduction and inspection

ffprobe -v error -select_streams v:0 \
  -show_entries stream=color_range,color_space,color_transfer,color_primaries \
  -of default=nw=1 input.mp4

# Inspect codec headers rather than only container-level side data.
ffmpeg -i input.mp4 -map 0:v:0 -c copy -bsf:v trace_headers -f null - 2>&1 \
  | rg 'colour_primaries|transfer_characteristics|matrix_coefficients|video_full_range'

Inspect colr separately with an ISO-BMFF parser such as Bento4 mp4dump. Then compare: (a) the file, (b) a raw elementary stream extracted without re-encoding, and (c) a remux with explicit tags. If appearance changes while decoded numeric RGB does not, the difference is downstream color management, not codec reconstruction.

5.2 Full versus narrow range: an affine error, not a gamma error

For 8-bit luma/R′G′B′ in conventional narrow range:

xnarrow=D1623516=D16219. x_{\mathrm{narrow}}=\frac{D-16}{235-16}=\frac{D-16}{219}.

For full range:

xfull=D255. x_{\mathrm{full}}=\frac{D}{255}.

Chroma conventionally uses codes 16–240 with neutral 128. At 10-bit, nominal luma black/white are 64/940 and chroma extrema are 64/960.

Code DD Interpreted full D/255D/255 Interpreted narrow (D16)/219(D-16)/219, unclamped
0 0.000000 −0.073059
16 0.062745 0.000000
32 0.125490 0.073059
64 0.250980 0.219178
128 0.501961 0.511416
192 0.752941 0.803653
235 0.921569 1.000000
255 1.000000 1.091324

If narrow data are treated as full, code 16 becomes gray 0.0627 and code 235 becomes 0.9216: blacks rise and whites dim. If full data are treated as narrow, sub-16 and over-235 values clip, crushing both ends.

A range mapping has form

y=ax+b. y=ax+b.

A pure gamma mismatch has form

y=xp. y=x^p.

On a plot of corresponding samples, the first is a straight line in ordinary coordinates; the second is curved. In log-log coordinates a pure power is a straight line through the origin after normalization, whereas the affine offset bends strongly. This is the fastest mathematical distinction between the two.

Caveat: clipping, quantization, and subsequent tone curves can make a range error look less perfectly affine. Fit the central unsaturated samples first.

5.3 BT.601 versus BT.709 matrix confusion

For nonlinear RGB, BT.601 and BT.709 form different luma-like signals:

Y601=0.299R+0.587G+0.114B, Y'_{601}=0.299R'+0.587G'+0.114B',

Y709=0.2126R+0.7152G+0.0722B. Y'_{709}=0.2126R'+0.7152G'+0.0722B'.

A neutral R=G=BR'=G'=B' survives either matrix unchanged, so a matrix mismatch does not normally produce a uniform neutral gamma curve. It changes saturated colors and color edges.

Example: encode (R,G,B)=(0.8,0.4,0.2)(R',G',B')=(0.8,0.4,0.2) as BT.601 full-range Y′CbCr, then decode with BT.709 coefficients. The result is approximately

(0.83737,0.42694,0.18600). (0.83737,\ 0.42694,\ 0.18600).

Encoding the same RGB as BT.709 and decoding as BT.601 gives

(0.76386,0.37141,0.21219). (0.76386,\ 0.37141,\ 0.21219).

Pure gray remains gray. Highly saturated red encoded as 601 and decoded as 709 can reconstruct to approximately (1.0864,0.0965,0.0141)(1.0864,0.0965,-0.0141) before clipping. Clipping then makes the error more nonlinear and can create an apparent local brightness change, but the root cause is the matrix.

5.4 QuickTime and Apple ColorSync: several historical mechanisms, not one immortal bug

Apple’s 1999 Technical Note TN2162 documents a colr image-description extension with nclc values for primaries, transfer function, and Y′CbCr matrix [B-04]. It also states:

Those statements already show why “QuickTime gamma bug” is too coarse. Possible causes included a legacy gamma-level atom, an nclc interpretation, a codec-specific Y′CbCr-to-RGB path, an application preview transform, ColorSync display conversion, range expansion, and old assumptions about the Macintosh 1.8 graphics system.

The legacy gama image-description extension stores a gamma-level indication. The newer colr/nclc mechanism separates primaries, transfer, and matrix. Current ISO-BMFF ecosystems also use nclx, which can carry a full-range flag. H.264/HEVC can simultaneously carry VUI values. Consequently, a ProRes or H.264 file can traverse Apple and non-Apple software with identical compressed samples but different color interpretation.

Historically observed pattern

The reliable diagnostic method is to export a gray ramp, decode it to raw numeric samples using multiple independent paths, inspect all metadata layers, and compare application screenshots only after display color management is controlled.

5.5 NLE working spaces and linear-light options

After Effects

Current After Effects color management distinguishes the input interpretation, project working space, working-space linearization, display transform, and output profile [B-06]. Two controls that are often confused are:

Adobe recommends adequate precision, especially 16- or 32-bpc, when linearizing. At 8-bpc, repeated decoding, processing, and re-encoding can expose banding.

With no project working space, After Effects can behave more like a pass-through/legacy numeric pipeline, but imported footage interpretation, viewer display color management, codec conversion, and export tags can still differ. “Color management off” does not prove “no transform anywhere.”

Premiere Pro

Current Premiere documents an explicit source/input color space → working color space → output color space architecture, with optional input and output tone mapping and gamut compression [B-07]. Tone mapping before effects and tone mapping after effects are not equivalent. A source misidentified as Rec.709 rather than HLG/PQ can be transformed incorrectly before the user applies a single grade.

For reproducibility, record:

A screenshot of the timeline and a screenshot of an external player are not a valid test unless both display paths are colorimetrically characterized.

5.6 Processing nonlinear code values: scaling, blur, alpha, sharpening, antialiasing

A spatial average should average light when the operation models light integration. Averaging encoded values instead averages a perceptual/code representation.

For a 50% black/white checkerboard:

Thus a gamma-space 50% downscale produces only 42.8% of the correct light, or a −57.2% relative luminance error in this constructed case.

The same principle affects:

The qualifier is semantic: some authored UI/graphic operations are intentionally defined in sRGB-like code space to preserve familiar appearance rather than simulate radiometry. The engineering error is not “ever using nonlinear arithmetic”; it is using it unknowingly or inconsistently.

5.7 Chroma subsampling and chroma siting

4:2:0 does not merely reduce chroma resolution. It also requires a geometric convention for where each chroma sample lies relative to luma samples. Common conventions include:

Bitstreams can signal chroma sample location, but metadata may be absent or ignored. Upsampling with the wrong phase shifts chroma by a fraction of a luma sample. On colored edges this produces fringes, color bleeding, and RGB overshoot/undershoot. Because reconstructed RGB contributes to perceived brightness and may clip, the artifact can be described as a brightness halo even though the Y′ plane was unchanged.

A diagnostic edge pattern should contain saturated red/blue transitions at one-pixel and two-pixel positions. Compare planes before RGB conversion, and test alternative siting explicitly. A true gamma error affects broad neutral ramps; a siting error is localized around color edges and reverses direction with edge polarity.

5.8 Non-constant luminance: Y′ is not luminance

Conventional Y′CbCr is formed from nonlinear components. In BT.709:

Y=0.2126R+0.7152G+0.0722B. Y'=0.2126R'+0.7152G'+0.0722B'.

Physical relative luminance is formed from linear components:

Y=0.2126R+0.7152G+0.0722B. Y=0.2126R+0.7152G+0.0722B.

In general,

OETF1(Y)Y. \operatorname{OETF}^{-1}(Y')\ne Y.

For linear pure red (R,G,B)=(1,0,0)(R,G,B)=(1,0,0), Y=0.2126Y=0.2126. The nonlinear components are (1,0,0)(1,0,0), so Y=0.2126Y'=0.2126; applying the BT.709 inverse OETF to that scalar gives only about 0.06075, not 0.2126. Equality holds for neutrals, not arbitrary colors.

This non-constant-luminance property means that chroma filtering, quantization, clipping, gamut conversion, or nonlinear RGB operations can change reconstructed physical luminance even if stored Y′ appears unchanged. The error becomes more consequential with BT.2020’s wider gamut and HDR’s larger luminance range [A-08].

BT.2020 defines both:

CL better isolates luminance from chroma but is more complex and less widely deployed. BT.2100 introduces ICtCp/constant-intensity options for HDR; NCL Y′CbCr remains common and must not be treated as physical luminance.

5.9 Static-image metadata: PNG and JPEG

PNG

PNG Third Edition establishes this color-space precedence [A-12, §4.3]:

  1. cICP;
  2. iCCP;
  3. sRGB;
  4. cHRM plus gAMA.

A decoder should use the highest-priority color-space information it understands. iCCP may coexist with approximate gAMA/cHRM for older readers; a compliant modern reader uses the ICC profile. iCCP and sRGB should not both be present. The gAMA integer stores image gamma multiplied by 100000; for a nominal 1/2.2 encoding it is about 45455. Interpreting that field as a display exponent of 2.2 reverses its meaning.

The alpha channel is linear coverage/full-range and is not gamma-corrected by gAMA. Treating alpha as if it had the RGB transfer function creates edge errors.

Historical libraries do not all implement the current precedence identically. Conflicting chunks are therefore a portability hazard even when one interpretation is formally correct.

JPEG

JPEG commonly embeds ICC profiles in APP2 markers. The ICC convention uses an ICC_PROFILE\0 identifier, a sequence number, and total-chunk count because one marker cannot hold a large profile; the profile can span multiple APP2 segments [A-17]. Missing or misordered segments invalidate the profile. JPEG may also contain EXIF color-space hints, but an ICC profile is the stronger colorimetric description when valid.

Screenshots and untagged files

Whether a screenshot embeds the source display profile, converts pixels to sRGB, embeds Display P3, or writes no profile depends on OS, API, application, and format. The only reliable answer for a specific screenshot is to inspect the file. Outside a defined ecosystem there is no universal rule for an untagged image. On the modern web, browsers generally treat untagged image content as sRGB; legacy or non-color-managed applications may instead send raw numbers directly to the display.

5.10 Display and browser color management

On a wide-gamut Display-P3-like monitor, an untagged sRGB image must not be interpreted as native display RGB. Correct rendering conceptually does:

assumed/embedded source RGB
 -> source linear/PCS
 -> display profile
 -> display device values

Sending sRGB numbers directly to a wider-gamut native framebuffer oversaturates colors. Assigning a Display P3 profile to unchanged sRGB numbers also changes their meaning; converting sRGB to P3 preserves color while changing numbers.

Operating systems differ in where color conversion occurs—application, graphics API, window compositor, or display pipeline—and in whether calibration curves are applied separately from ICC characterization. Double color management occurs when both the application and another layer apply a transform intended to be singular. Bypassing management produces the opposite failure.

WebKit documents color matching for wide-gamut web content and assumes untagged images are sRGB [B-18]. Firefox 89 changed macOS behavior so untagged images were treated as sRGB consistently with CSS colors [B-19]. Browser, OS, GPU, and display-profile versions remain relevant; a valid test page should include tagged sRGB, tagged Display P3, untagged copies, CSS colors, and canvas readbacks.

5.11 HDR↔︎SDR and SDR↔︎HDR: tone mapping is not “gamma correction”

HDR-to-SDR must decide how an absolute or high-relative-range scene is rendered onto a lower peak and narrower gamut. Variables include:

Current ITU guidance uses 203 cd/m² as HDR reference white in key production/interchange contexts [A-09]. This does not redefine the conventional BT.1886 SDR reference display as 203 cd/m²; it specifies how reference/diffuse white is placed in an HDR system. BT.2446 documents multiple HDR/SDR conversion methods rather than one exponent [A-10].

Two implementations can both correctly decode PQ yet look different because one uses a hard clip, another uses a global knee, and a third uses dynamic/local tone mapping. Their pixel relationship is content-dependent and often spatially varying. Fitting one gamma to those results is a descriptive approximation, not an explanation.

SDR-to-HDR likewise requires an inverse tone-mapping/rendering decision: what remains diffuse white, how much highlight expansion is synthesized, whether colors expand, and what peak is targeted. Merely decoding SDR and re-encoding as PQ produces an HDR container, not an HDR rendering.


6. Historical deep dive: CRT 2.5, Macintosh 1.8, QuickTime, and the “0.88 fix”

6.1 From a physical CRT response to a standardized EOTF

Early video systems could rely on the useful accident that a CRT’s electron-gun drive was nonlinear in a reasonably repeatable way. Camera-side pre-correction both improved the allocation of signal steps and approximately compensated the receiver. But television was never a perfectly inverse camera/display chain: viewing in a dim room called for a contrast-enhancing OOTF.

The shorthand history is:

physical CRT family: roughly 2.35–2.55 in useful operating ranges
camera/source pre-correction: roughly a 0.45–0.5 exponent plus toe/offset
end-to-end rendering: deliberately greater than unity
flat-panel era: no common natural CRT law
2011: BT.1886 defines a reproducible reference EOTF, gamma 2.4 with Lw/Lb

The value 2.5 came from measurement and electron-gun physics, not an instruction that every file be decoded with a 2.5 LUT. The later 2.4 reference is a standardization choice that reproduces accepted reference viewing behavior while accounting for display black.

6.2 What can and cannot be proved about Macintosh 1.8

The well-supported facts are:

  1. early Macintosh graphics used a LUT plus an ordinary CRT to produce an effective response near 1.8 [L-01];
  2. the CRT itself was not physically “a 1.8 monitor”;
  3. 1.8 aligned usefully with graphic-arts/prepress tone reproduction, and raw QuickDraw codes could produce acceptable imagesetter results [L-01];
  4. the convention became entrenched in desktop publishing;
  5. Snow Leopard changed the default to 2.2 in 2009 [B-20, B-21].

The weaker claim is that Apple selected exactly 1.8 as a documented mathematical compensation for one named LaserWriter model. This research searched Apple support/developer archives, QuickDraw/ColorSync material, LaserWriter documentation indexes, Poynton’s publications, and contemporary discussions. It found no first-party design record establishing that narrow story. The defensible wording is print/prepress-chain compatibility, not proven LaserWriter-specific origin.

This distinction matters because a historical rationale does not create a valid modern file conversion. A 1.8 desktop LUT, a printing dot-gain curve, an ICC perceptual rendering, and a video OOTF are different transforms.

6.3 QuickTime gamma-shift timeline and why reports conflict

A compact evidence-weighted timeline:

Date Evidence What it establishes
1999-12-14 Apple TN2162 [B-04] nclc/colr semantics, colr over gama, and a legacy Mac Y′CbCr transfer adjustment that colr cannot describe
2001-era QTFF Apple file-format docs [B-05] gamma and color image-description extensions existed in the container architecture
2006 Apple/community reports [C-07] users observed cross-player gamma/black differences; mechanism not uniquely proved
2007 After Effects CS3 material [C-02] QuickTime 7.2-era behavior changed enough for Adobe to expose a legacy-matching option
2008 Video-production guidance [C-03] gamma-shift workarounds were already common, but often mixed player, codec, and range effects
2009-08-28 Snow Leopard [B-20, B-21] default desktop gamma changed 1.8→2.2; QuickTime X changed the application stack
2010-12-31 controlled blog test [C-01] one AE CS5/Windows/QuickTime H.264 path was empirically fit as x0.88x^{0.88}; QuickTime Player also showed a separate range/contrast remap
2013 onward encoding forums/guides [C-04, C-05] Levels(..., gamma=0.88) became a portable community recipe, often detached from its original conditions

The reports conflict because they did not all test the same pipeline. Some compared decoded bytes; some compared screenshots. Some used H.264, some ProRes, some Animation. Some ran on Windows through Apple’s QuickTime components; others ran through ColorSync on macOS. Some errors were nonlinear; others were full/narrow range. A single folklore label absorbed all of them.

6.4 Provenance and evidential status of 0.88

The earliest independently retrievable, date-stamped, exact 0.88 measurement found in this research is the 31 December 2010 article “The QuickTime Gamma Bug” [C-01]. It reports that After Effects CS5 on Windows, when encoding H.264 through the tested QuickTime path, transformed gray samples approximately as

y=x0.88, y=x^{0.88},

with a maximum 8-bit increase of about 12 codes around input 88. It also reports a different QuickTime Player transformation that raised black and compressed contrast—an affine/range-like error, not the same gamma curve.

This is C-class empirical evidence, not an Apple or Adobe specification. It establishes that the tested path behaved that way. It does not establish that all QuickTime H.264 encoders did, that ProRes did, or that the implementation’s internal rationale was 2.2/2.52.2/2.5.

Numerically,

0.88=2.2/2.5. 0.88=2.2/2.5.

That makes a CRT/PC-gamma story tempting. However, no first-party source found here says that the implementation deliberately converted a 2.2-coded signal to a 2.5-coded signal. The 2010 author explicitly treated the cause as unknown. Therefore the theoretical-origin claim remains unproven; 0.88 should be described as an empirical fit/recipe whose numerical equality to 2.2/2.52.2/2.5 may explain later folklore.

Search limitations: several old Doom9 links and mailing-list references are dead, blocked, or unindexed. Earlier exact uses may exist. The claim here is “earliest retrievable in this search,” not “first ever posted.”

6.5 The direction trap: Levels(gamma=0.88) does not apply x0.88x^{0.88}

AviSynth documents Levels as [B-10]:

y=(xxin,minxin,maxxin,min)1/γ(xout,maxxout,min)+xout,min, y=\left(\frac{x-x_{in,min}}{x_{in,max}-x_{in,min}}\right)^{1/\gamma} (x_{out,max}-x_{out,min})+x_{out,min},

with input clamping and the selected range parameters. VapourSynth std.Levels exposes matching semantics: values greater than 1 brighten, values less than 1 darken, and planes selects which planes are processed [B-09]. In normalized full-range form:

y=x1/γ. y=x^{1/\gamma}.

Therefore:

This is different from saying “convert a correct pure-2.2 encoding to a correct pure-2.5 encoding.” That forward conversion is x2.2/2.5=x0.88x^{2.2/2.5}=x^{0.88}, which would require Levels(gamma=1.13636), not 0.88. Much community discussion fails to distinguish the correction parameter from the mathematical exponent.

6.6 When 0.88 works and when it creates a new error

gamma=0.88 is defensible only when measurements show that the source has been brightened by approximately x0.88x^{0.88} in the same normalized domain and that the desired correction is its inverse. Conditions include:

It is harmful when:

The correct order is to fit the observed mapping first, then choose an inverse. Never begin with 0.88 because two images “look Apple-different.”

6.7 Y-only versus RGB-channel gamma: a numerical example

Take full-range BT.709 nonlinear RGB

(R,G,B)=(0.8,0.4,0.2). (R',G',B')=(0.8,0.4,0.2).

Its Y′CbCr values are approximately

(Y,CB,CR)=(0.470600,0.145829,0.209169). (Y',C_B,C_R)=(0.470600,-0.145829,0.209169).

Apply Levels(gamma=0.88), i.e. exponent 1.13636:

Method Resulting (R,G,B)(R',G',B') HSV hue HSV saturation sRGB-decoded relative luminance
Original (0.800000, 0.400000, 0.200000) 20.000° 0.7500 0.22579
Gamma on Y′ only; Cb/Cr copied (0.754033, 0.354033, 0.154033) 20.000° 0.7957 0.18751
Same gamma per R′/G′/B′ channel (0.776024, 0.353017, 0.160589) 18.760° 0.7931 0.19466

Calculation: full-range BT.709 NCL matrix; no clipping; HSV is computed on nonlinear RGB; the last column decodes each channel with sRGB solely as a reproducible display-light proxy.

Changing Y′ with Cb/Cr fixed adds the same code-domain delta to R′, G′, and B′ before clipping. It preserves channel differences and this example’s HSV hue, but it changes saturation and does not preserve physical luminance or chromaticity. Per-channel gamma changes channel ratios and therefore hue and saturation. With clipping, Y′-only processing can also change hue abruptly.

For YUV clips, planes=0 means “process the Y plane,” not “perform a colorimetrically correct transfer conversion.” For RGB clips, selecting all three color planes applies the scalar law to each encoded component. Neither operation automatically linearizes, changes primaries, handles BT.1886 black, or updates metadata.

6.8 Comparison of common tools

Tool/filter Domain and purpose Linear-light/color-managed? Main risk
AviSynth Levels / VS std.Levels sample/range remap with 1/γ1/\gamma power; selected planes No automatic color management parameter direction, Y-only default/workflow, range assumptions
FFmpeg eq simple brightness/contrast/gamma adjustment, commonly per native plane Not a standards conversion applying “gamma” to YUV samples and mistaking it for TRC conversion
FFmpeg lut, lutrgb, lutyuv arbitrary sample-domain LUT Only if user builds exact pipeline wrong domain, clipping, low precision
FFmpeg colorspace transfer/primaries/matrix/range conversion Standards-aware; uses defined conversions wrong/missing input declarations or unsupported edge case
FFmpeg zscale zimg scaling and color conversion with dither/precision options Can perform exact transfer/primaries/matrix conversions metadata defaults and option direction still matter
mpv/libplacebo playback rendering, ICC/output TRC, linear scaling, tone/gamut mapping Designed around explicit color pipeline; options select behavior using --gamma-factor as a metadata fix, wrong target assumptions
madVR proprietary playback renderer with calibration/tone controls Exact internals not fully public inferring mathematical order from UI labels or forum descriptions

FFmpeg’s colorspace/zscale are the appropriate families for declared color-space conversion; eq and LUT filters are appropriate for deliberate sample transforms. mpv’s --linear-downscaling, --target-trc, ICC, --target-peak, tone-mapping, and --treat-srgb-as-power22 options demonstrate that “gamma” interacts with the full rendering pipeline [B-11, B-12]. --gamma-factor is an appearance adjustment, not a substitute for correct source metadata.

For madVR, this research found extensive community descriptions but insufficient public primary documentation to state a single exact internal operation order for every mode. Any exact claim should be verified against a particular build with ramps and readback/capture measurements.


7. Web and browser implementation traps

7.1 SVG filter primitives versus CSS filter functions

Filter Effects Level 1 inherits a historically important split [A-13, §10]:

Thus these are not guaranteed to match:

.element-a { filter: brightness(0.5); }     /* CSS function: sRGB */
.element-b { filter: url(#svgBrightness); } /* SVG primitive: linearRGB by default */

An SVG equivalent must explicitly choose the intended domain:

<filter id="svgBrightness" color-interpolation-filters="sRGB">
  <feComponentTransfer>
    <feFuncR type="linear" slope="0.5"/>
    <feFuncG type="linear" slope="0.5"/>
    <feFuncB type="linear" slope="0.5"/>
  </feComponentTransfer>
</filter>

Without sRGB, halving a mid-gray in linear light and halving its encoded sRGB code are different operations. The split exists because SVG filter primitives were specified with linear-light color processing as the default, while CSS filter-function behavior was standardized around established sRGB-like compositing and web compatibility. It is a normative inconsistency in default domains, not necessarily a browser bug.

Actual browser history

WebKit bug 185343 reported that filter:url(#id) incorrectly forced sRGB instead of respecting SVG’s linearRGB default; it was reported on 4 May 2018 and resolved fixed on 7 May 2018 [B-13]. This is concrete evidence that a browser could implement the two paths inconsistently even where the specification was clear. Older Safari/WebKit Gaussian-blur reports likewise involved linearRGB/sRGB differences and backend behavior [B-14].

Do not infer current cross-browser equality from one historical fix. Use a test page with explicit sRGB and linearRGB, capture numeric output where possible, and record browser build, OS, GPU, and color profile.

7.2 feComponentTransfer exact semantics

Filter Effects Level 1 defines per-component functions on unpremultiplied color values [A-13, §9.12]:

Defaults are amplitude=1, exponent=1, offset=0, slope=1, and intercept=0. Separate feFuncR, feFuncG, feFuncB, and feFuncA elements control the channels. A gamma primitive’s parameter is the actual exponent—not the reciprocal convention used by Levels—so parameter names alone do not establish direction across APIs.

The transfer operates in the filter primitive’s selected interpolation color space. A mathematically identical component function gives different visual light output in sRGB and linearRGB.

7.3 feComposite operator="arithmetic", clamping, and signed intermediates

The arithmetic composite is [A-13, §9.8]:

result=k1i1i2+k2i1+k3i2+k4. \mathrm{result}=k_1i_1i_2+k_2i_1+k_3i_2+k_4.

Each output component is clamped to [0,1][0,1]. Filter primitive results are likewise clipped to the allowed premultiplied range before being passed onward. Therefore a nominal subtraction

<feComposite in="A" in2="B" operator="arithmetic"
             k1="0" k2="1" k3="-1" k4="0"/>

cannot preserve negative values: they become zero immediately.

A common engineering workaround is to bias and scale a signed difference into the legal interval:

Dpacked=0.5+0.5(AB), D_{packed}=0.5+0.5(A-B),

implemented with k2=0.5,k3=0.5,k4=0.5k_2=0.5,k_3=-0.5,k_4=0.5. Later processing can interpret 0.5 as zero. The workaround only succeeds if every intermediate remains in range; a later “unpack” that generates negative values will again clamp. If exact signed arithmetic is required, WebGL/WebGPU, a float canvas, or an off-browser processing stage is usually more appropriate than chaining normative clamped SVG primitives.

7.4 Intermediate precision and banding

The specification defines functional results and clamping but does not promise that every implementation uses the same intermediate storage precision. Engines may use 8-bit unorm surfaces, 10/16-bit formats, half-float textures, CPU float buffers, or mixed paths depending on primitive, GPU, color space, tiling, and compositing mode.

An 8-bit intermediate can expose banding when:

Higher bit depth reduces quantization but does not defeat normative clipping. Dither can decorrelate banding at the final quantization stage; it cannot recover values already clipped or rounded away in an earlier surface.

A reproducible browser test should use a 16-bit or generated float ramp as source, apply one primitive at a time, and compare screenshots/readbacks at 400% magnification. A visible smooth result is not proof of high precision because dithering can mask low bit depth; a histogram/readback is more informative.

7.5 Premultiplied alpha

Filter Effects specifies that filter images are generally represented as premultiplied RGBA. feColorMatrix and feComponentTransfer are exceptions that conceptually operate on temporarily unpremultiplied color values and then return to premultiplied form [A-13, §8].

If a pipeline mistakenly applies a nonlinear function directly to premultiplied channels, then for color CC and alpha α\alpha:

(αC)pαCp (\alpha C)^p\ne\alpha C^p

unless p=1p=1 or special values apply. Low-alpha edges change color and darkness. Correct handling is conceptually:

if alpha > 0: unpremultiply RGB
apply color operation in declared color space
premultiply RGB by alpha
if alpha == 0: define RGB safely, normally zero

Repeated unpremultiply/premultiply at low alpha can amplify quantization and hidden-color differences. Test transparent edges over both black and white backgrounds; an error invisible over one background may be obvious over the other.

7.6 GPU versus CPU paths and public browser issues

Browser engines can route filters through Blink/WebCore/Gecko display lists, Skia/CoreGraphics/Moz2D, GPU shaders, or CPU fallbacks. Differences can arise from:

Public examples include:

These issue numbers are evidence that path-dependent differences have existed. They are not permission to claim that current Chrome, Safari, or Firefox always renders filters incorrectly. A current compatibility statement requires a build-specific test.

7.7 Canvas color spaces and getImageData

The Canvas 2D specification supports requesting a context color space, and current WHATWG text includes srgb, srgb-linear, display-p3, and display-p3-linear concepts; deployed browser support is narrower and should be feature-tested [A-14]. Common shipping syntax includes:

const ctx = canvas.getContext("2d", { colorSpace: "display-p3" });

getImageData() can request a color space and pixel format in implementations that support the options:

const image = ctx.getImageData(0, 0, w, h, {
  colorSpace: "display-p3",
  pixelFormat: "rgba-float16"
});

The important semantic point is that readback can involve a color-space conversion into the requested ImageData representation. It is not necessarily a dump of raw GPU framebuffer bytes. rgba-unorm8 quantizes to 8-bit normalized channels; rgba-float16 permits HDR/wide-range values where implemented. Drawing an ImageData into a context of another color space can trigger another conversion.

Canvas has at least four distinct spaces to keep separate:

  1. the source image’s embedded/assumed color space;
  2. the canvas backing/context color space;
  3. the ImageData readback/requested color space and format;
  4. the page/compositor/display output space.

For conformance testing, fill patches using CSS color values, decoded images, and direct ImageData; read them back in both sRGB and P3; and compare against independently computed colorimetric values. A screenshot alone conflates canvas conversion with display color management.


8. Diagnostic and measurement methodology

8.1 First establish that pixels are comparable

A transfer fit is meaningful only after the images are registered and represent the same stage of the pipeline. Before fitting:

  1. match frame/timecode and field order;
  2. undo crop, rotation, pixel-aspect, and scaling differences;
  3. align to subpixel accuracy if one path resized;
  4. exclude subtitles, UI overlays, burned-in metadata, and borders;
  5. identify codec blocking, denoising, sharpening, and chroma-resampling differences;
  6. compare in a common decoded RGB format with explicit matrix/range/transfer assumptions;
  7. avoid using screenshots until OS/display color management has been isolated.

For same-resolution sources, phase correlation can estimate translation. For rescaled or slightly warped images, use robust feature matching followed by an affine/homography fit, but sample smooth interior regions for tone analysis. Edges are contaminated by kernel and chroma-siting differences.

8.2 Decision tree for a still image or matched pair

START: Two images of the same frame look different
 |
 |-- Are decoded numeric pixels identical before display?
 |      |-- YES -> display/application color management, output profile,
 |      |          HDR mode, OS compositor, monitor profile, or screenshot path.
 |      `-- NO  -> continue.
 |
 |-- Do black and white endpoints map approximately by y = a*x + b?
 |      |-- YES -> full/narrow range, level scaling, pedestal, or contrast/offset.
 |      |          Check 16/235 (or 64/940) and clipping.
 |      `-- NO  -> continue.
 |
 |-- Are neutral corresponding pixels related by y ≈ c*x^p, with small residuals?
 |      |-- YES -> transfer-function/power-like mismatch. Inspect tags and fit p.
 |      `-- NO  -> continue.
 |
 |-- Do errors depend strongly on hue while neutrals remain close?
 |      |-- YES -> 601/709/2020 matrix, primaries/profile, gamut mapping,
 |      |          per-channel curve, or chroma processing.
 |      `-- NO  -> continue.
 |
 |-- Are differences concentrated at colored edges or fine detail?
 |      |-- YES -> chroma siting/upsampling, scaling kernel, nonlinear filtering,
 |      |          sharpening, alpha, or GPU precision.
 |      `-- NO  -> continue.
 |
 |-- Are highlight differences content-dependent, spatially local, or capped in nits?
 |      |-- YES -> HDR/SDR tone mapping, local adaptation, dynamic metadata,
 |      |          target peak/reference-white mismatch.
 |      `-- NO  -> continue.
 |
 `-- Fit a combined model: per-channel 3x3 matrix + offset + monotonic curves;
        inspect residuals and pipeline metadata. Do not label it “gamma” yet.

This tree deliberately checks numeric identity before appearance. If two applications display the same decoded RGB differently, changing the file’s pixels may “fix” one application while corrupting every correctly managed path.

8.3 Fitting a power mismatch

For normalized, positive, unclipped corresponding neutral samples (xi,yi)(x_i,y_i), fit

y=cxp. y=cx^p.

Taking logarithms:

lny=plnx+lnc. \ln y=p\ln x+\ln c.

A linear regression gives exponent pp and gain cc. The assumptions are strict:

Robust procedure

  1. normalize using measured black/white or known code ranges;
  2. remove samples within a guard band of black and clipping, for example below 2% and above 98%;
  3. select low-chroma pixels or a neutral wedge;
  4. fit an affine model y=ax+by=ax+b and a power model y=cxpy=cx^p;
  5. use Huber loss, Theil–Sen, or RANSAC to reject edges and codec artifacts;
  6. inspect residual versus input, position, and hue;
  7. bootstrap confidence intervals over blocks, not individual highly correlated pixels;
  8. validate the inverse on held-out patches.

A more flexible model is

y=b+cmax(xx0,0)p, y=b+c\max(x-x_0,0)^p,

but parameter coupling makes it easy to overfit. Prefer known range endpoints and standard transfer functions over an unconstrained curve when metadata suggests a standard.

Model signatures

8.4 Quantile and histogram methods when registration is unavailable

For exactly the same frame but unknown spatial alignment, compare channel or luminance quantiles:

Qy(q)f(Qx(q)). Q_y(q)\approx f(Q_x(q)).

A monotonic transfer can be estimated from many quantile pairs. This is useful for exposure/range/gamma hypotheses but is not uniquely identifying: different spatial images can share a histogram, and matrix/gamut changes alter channel distributions. Use it to propose a curve, then verify with registered patches or a test chart.

Histogram clues:

A. Code-value staircase and ramp

Include exact values 0, 1, 2, 4, 8, 12, 16, 20, 32, 64, 128, 192, 235, 240, and 255 for 8-bit tests, plus equivalent 10-bit values. Use both:

This separates transfer interpretation from quantization spacing.

B. PLUGE and range chart

Include below-black, black, near-black, white, and above-white values. For 8-bit narrow video, explicitly mark 0, 16, 235, and 255; for chroma include 16, 128, and 240. A range error becomes immediately visible and measurable.

C. 50% checkerboard and frequency sweep

Use equal-area black/white checks at multiple spatial frequencies, then downscale or blur. Correct linear-light averaging tends toward 50% light, encoded as sRGB ≈0.735357. A nonlinear-domain average tends toward code 0.5. Include a zone plate to expose kernel and mipmap changes.

D. Saturated matrix patches

Use red, green, blue, cyan, magenta, yellow, skin-like colors, and neutrals. A 601/709 matrix mismatch leaves neutral gray close while moving saturated patches. Add near-gamut-boundary values to reveal clipping.

E. Chroma-siting edges

Use alternating red/blue, red/gray, and blue/gray one-pixel vertical and horizontal edges. Inspect Cb/Cr planes and reconstructed RGB separately. Shift the assumed chroma location by half a sample to reproduce the fringe.

F. Alpha-compositing target

Use colored shapes with alpha values 1, 0.75, 0.5, 0.25, 0.01, and 0 over black, white, and saturated backgrounds. Compare straight and premultiplied representations. Nonlinear and premultiplication errors appear at different edge positions.

G. HDR luminance patches

For PQ, include patches encoded for known absolute values such as 0.005, 0.1, 1, 10, 100, 203, 1000, and 4000 cd/m². For HLG, include reference-white and peak-related levels under a stated LWL_W and system gamma. Measure display output with an instrument if the question concerns actual nits rather than decoded values.

8.6 End-to-end validation protocol

  1. Generate the chart in float linear light.
  2. Produce exact sRGB, BT.709, pure-gamma, PQ, and HLG variants with independent reference code.
  3. Produce full- and narrow-range variants and explicit 601/709/2020 matrices.
  4. Write matching metadata at both bitstream and container layers.
  5. Produce deliberate conflict files for precedence testing.
  6. Decode to planar float RGB through each application/tool.
  7. Hash or numerically compare pre-display frames.
  8. Display through a characterized path; disable dynamic display features.
  9. Measure patches with a colorimeter/spectroradiometer when evaluating EOTF.
  10. Archive application versions, command lines, display profile, OS build, GPU, and screenshots.

A test is incomplete if it records only what the viewer looked like.

8.7 Common misdiagnoses


9. Engineering practice: where to fix the problem

9.1 Correct semantics before changing samples

If pixels are correct and tags are wrong, repair tags. If tags are correct and one application ignores them, configure or replace that application. Alter pixels only when the intended color representation truly changes or when a measured historical transform must be inverted for archival restoration.

A metadata-only repair is not harmless if the original pixels were actually created under different assumptions; verify against a known reference before relabeling.

9.2 Keep operations in the domain they require

Do not linearize merely because “linear is always correct.” A transfer function is part of a representation; the operation’s physical or perceptual meaning determines the correct domain.

9.3 Use adequate precision and dither

Decode and transform in float or at least 16-bit integer where possible. Avoid repeated 8-bit round trips. Delay clipping until a defined gamut/range boundary. Dither at the final reduction to a lower integer bit depth; do not use dither to hide an incorrect curve.

9.4 Make metadata redundant and consistent

Where the format permits both bitstream and container declarations, write consistent values. Preserve:

Automated CI should reject contradictory tags and compare a decode against golden patch values.

9.5 Separate grading monitor, GUI viewer, and consumer preview

A reference output should have a known display EOTF, peak, black, surround, and signal range. GUI viewers are useful but are mediated by OS color management. Consumer previews are deliberately variable. Do not force all three to match by baking an undocumented correction into the master.

9.6 Treat 203 cd/m² carefully

Use 203 cd/m² where the relevant HDR standard/workflow defines HDR reference white. Do not assume that every SDR master, desktop white, or display calibration should be 203 nits. Record whether a value is SDR reference-display white, HDR diffuse/reference white, graphics white, or display peak.

9.7 Version-pin empirical workarounds

If a legacy application truly requires a corrective LUT, name it after the measured path, for example:

AE_CS5_WIN_QT_H264_measured_inverse_x_pow_0p88.cube

Document the source test, range, plane, precision, and expected hash. Do not call it simply quicktime_gamma_fix.cube.


10. Common claims versus the evidence

Popular claim Actual situation Evidence strength
“sRGB is gamma 2.2.” sRGB is a piecewise linear/offset-2.4 function; 2.2 is a fitting shorthand whose accuracy depends on range and metric. A
“BT.709 gamma is 2.4.” BT.709 specifies an OETF; BT.1886 specifies the reference display EOTF. A
“0.45 × 2.4 = 1.08, so 1.2 is wrong.” BT.709 has an offset and varying local slope; upper-tone effective camera exponent is near 0.5. ITU explicitly defines conventional SDR rendering intent around system gamma 1.2. A/L
“The camera OETF and display EOTF should be inverses.” Their composition includes the intended OOTF; unity is not required. A
“BT.1886 is exactly V2.4V^{2.4}.” Only when LB=0L_B=0; otherwise a,ba,b change the curve. A
“CRT gamma was exactly 2.5.” 2.5 is a representative value; measured useful-range values vary, commonly about 2.35–2.55. L
“Mac monitors physically had gamma 1.8.” The effective system used LUT compensation plus an ordinary CRT. L
“Apple chose 1.8 specifically for the LaserWriter.” Broad print/prepress compatibility is supported; a first-party LaserWriter-specific design proof was not found. L/C, limited
“Snow Leopard changed Mac gamma in 2010.” Mac OS X 10.6 shipped 28 August 2009 and changed the default 1.8→2.2. B
“QuickTime always adds 0.88 gamma.” One tested 2010 AE CS5/Windows/QuickTime H.264 path was fit as x0.88x^{0.88}; other paths and versions differ. C, path-specific
Levels(gamma=0.88) applies x0.88x^{0.88}.” It applies x1/0.88x^{1/0.88} in normalized form and therefore darkens. B
“A lifted black is a gamma error.” Full/narrow mismatch is affine and should be tested first. A/mathematical
“Y′ is luminance.” Y′ is a weighted sum of nonlinear components; equality with physical luminance holds only for neutrals/special cases. A
“A 601/709 mismatch changes only brightness.” It primarily produces color-dependent RGB errors; neutrals remain neutral. A/mathematical
“Linear-light processing is always visually correct.” It is radiometrically appropriate for light integration; some standards deliberately define perceptual/code-space operations. A/engineering
“PNG gAMA beats an ICC profile.” Current PNG precedence places iCCP above sRGB and cHRM+gAMA; cICP is highest. A
“CSS and SVG brightness filters use the same space.” CSS filter functions use sRGB; SVG primitives default to linearRGB. A
“Canvas readback is raw device bytes.” getImageData can convert to a requested color space/pixel format. A
“HDR-to-SDR is a gamma conversion.” It is a rendering/tone/gamut mapping problem with absolute/reference-white decisions. A
“If two screenshots match, the pipeline is correct.” Two wrong display paths can match; validate decoded values, metadata, and measured output. Methodological

11. Appendices

11.1 Terminology quick reference

Term Domain mapping Common confusion
OETF scene-linear light → nonlinear signal called “display gamma”
inverse OETF nonlinear signal → scene-linear representation confused with EOTF
EOTF nonlinear signal → display-linear light assumed to invert camera OETF
OOTF scene-linear light → display-linear light omitted from simplistic gamma diagrams
TRC generic tone-response curve, often ICC channel curve treated as necessarily a pure gamma
system gamma effective/OOTF exponent under stated conditions treated as one exact exponent over a piecewise curve
Y′ luma-like weighted sum of nonlinear RGB called physical luminance Y
luminance Y weighted sum of linear RGB in a colorimetric space computed from primed components
narrow range nominal legal video range with head/foot room called “gamma-compressed range”
reference white specified white level in a rendering system confused with display peak
PQ absolute display EOTF treated as relative gamma
HLG scene-relative OETF plus display OOTF treated as one fixed display curve

11.2 Parameter quick reference

Function Key parameters
sRGB encode threshold 0.0031308; slope 12.92; exponent 1/2.4; scale 1.055; offset 0.055
sRGB decode threshold 0.04045; reciprocal slope 1/12.92; exponent 2.4
BT.601/709 OETF threshold 0.018; slope 4.5; exponent 0.45; scale 1.099; offset 0.099
BT.2020 OETF exact α=1.09929682680944…; β=0.018053968510807…; exponent 0.45
BT.2020 10-bit practical α=1.099; β=0.018
BT.2020 12-bit practical α=1.0993; β=0.0181
BT.1886 γ=2.4; a,ba,b from LW,LBL_W,L_B
Adobe RGB (1998) decode exponent 563/256=2.19921875
PQ m1=2610/16384m_1=2610/16384; m2=2523/4096×128m_2=2523/4096\times128; c1=3424/4096c_1=3424/4096; c2=2413/4096×32c_2=2413/4096\times32; c3=2392/4096×32c_3=2392/4096\times32
HLG a=0.17883277; b=0.28466892; c=0.55991073; γ=1.2 at 1000 nits

11.3 Minimal exact-conversion pseudocode

# Encoded source RGB -> encoded destination RGB.
# Source/destination functions must be exact named functions, not labels like "2.2-ish".

def convert(rgb_code, decode_source, encode_destination,
            linear_rgb_matrix=None, rendering_transform=None):
    linear = decode_source(rgb_code)

    if linear_rgb_matrix is not None:
        linear = linear_rgb_matrix @ linear

    if rendering_transform is not None:
        # OOTF, tone map, gamut map, exposure, or adaptation explicitly chosen.
        linear = rendering_transform(linear)

    return encode_destination(linear)

For Y′CbCr input, range normalization and inverse matrix precede this function. For Y′CbCr output, destination encoding precedes the destination matrix and range quantization. For PQ, the decoded value is absolute luminance unless a specified production transform changes the domain.

11.4 Classified bibliography

A — standards and specifications

[A-01] IEC 61966-2-1:1999, Multimedia systems and equipment — Colour measurement and management — Part 2-1: Colour management — Default RGB colour space — sRGB, including Amendment 1. IEC publication page: https://webstore.iec.ch/publication/6169. Public parameter registry: https://registry.color.org/rgb-registry/srgb.

[A-02] ITU-R BT.601-7 (03/2011), Studio encoding parameters of digital television for standard 4:3 and wide-screen 16:9 aspect ratios, §2.6.4 for OETF and digital-representation clauses. https://www.itu.int/dms_pubrec/itu-r/rec/bt/r-rec-bt.601-7-201103-i!!pdf-e.pdf.

[A-03] ITU-R BT.709-6 (06/2015), Parameter values for the HDTV standards for production and international programme exchange, Part 1, item 1.2 for OETF; Part 2 for current production parameters. https://www.itu.int/rec/R-REC-BT.709-6-201506-I/en.

[A-04] ITU-R BT.1886-0 (03/2011), Reference electro-optical transfer function for flat panel displays used in HDTV studio production, Scope and Annex 1, Eq. (1)–(4). https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1886-0-201103-I!!PDF-E.pdf.

[A-05] ITU-R BT.2020-2 (10/2015), Parameter values for ultra-high definition television systems for production and international programme exchange, Table 4 for OETF and tables defining NCL/CL. https://www.itu.int/rec/R-REC-BT.2020-2-201510-I/en.

[A-06] SMPTE ST 2084:2014, High Dynamic Range Electro-Optical Transfer Function of Mastering Reference Displays. DOI: https://doi.org/10.5594/SMPTE.ST2084.2014.

[A-07] ITU-R BT.2100-3 (02/2025), Image parameter values for high dynamic range television for use in production and international programme exchange, Tables 4–5, notes 5e–5f, Annexes 1–2. https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-3-202502-I!!PDF-E.pdf.

[A-08] ITU-R Report BT.2390-12 (03/2025), High dynamic range television for production and international programme exchange, especially §6.2 on OOTF/system gamma. https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2390-12-2025-PDF-E.pdf.

[A-09] ITU-R Report BT.2408-9 (03/2026), Operational practices in HDR television production, Table 4 for monitor luminance/reference white. https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2408-9-2026-PDF-E.pdf.

[A-10] ITU-R Report BT.2446-1 (2021), Methods for conversion of high dynamic range content to standard dynamic range content and vice-versa. https://www.itu.int/pub/R-REP-BT.2446.

[A-11] ITU-T H.273 (03/2024), Coding-independent code points for video signal type identification, tables for colour primaries, transfer characteristics, matrix coefficients, range, and chroma location. https://www.itu.int/rec/T-REC-H.273.

[A-12] W3C, Portable Network Graphics (PNG) Specification, Third Edition, Recommendation 24 June 2025, §4.3 and color-space chunk chapters. https://www.w3.org/TR/png-3/.

[A-13] W3C, Filter Effects Module Level 1, 18 December 2018, §§8–10 (feComposite, feComponentTransfer, color-interpolation-filters). https://www.w3.org/TR/filter-effects-1/.

[A-14] WHATWG, HTML Living Standard, Canvas 2D context, color spaces, ImageData, and getImageData settings. https://html.spec.whatwg.org/multipage/canvas.html.

[A-15] W3C, CSS Color Module Level 4, predefined sRGB and Display-P3 spaces and transfer functions. https://www.w3.org/TR/css-color-4/.

[A-16] IEC 61966-2-2:2003, Extended RGB colour space — scRGB. Public ICC registry entry: https://registry.color.org/rgb-registry/scrgb.

[A-17] ICC, Embedding ICC Profiles in JPEG Files, technical note, APP2 chunk structure. https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf.

[A-18] ISO/CIE 11664-6:2014, Colorimetry — Part 6: CIEDE2000 colour-difference formula. https://www.iso.org/standard/63731.html.

[A-19] ITU-R BT.2035, A reference viewing environment for evaluation of HDTV programme material or completed programmes. https://www.itu.int/rec/R-REC-BT.2035.

[A-20] ITU-R BT.2124-0 (01/2019), Objective metric for the assessment of the potential visibility of colour differences in television. https://www.itu.int/rec/R-REC-BT.2124.

[A-21] ISO/IEC 14496-12, ISO base media file format, colr/colour-information box family; current edition should be consulted for nclx syntax. ISO catalogue: https://www.iso.org/standard/83102.html.

[A-22] ITU-T H Supplement 15 (2017), Conversion and coding practices for HDR/WCG Y′CbCr 4:2:0 video with PQ transfer characteristics, and H Supplement 19 for HDR/WCG signalling and chroma location. https://www.itu.int/rec/T-REC-H.Sup15.

B — vendor/project primary sources

[B-01] ICC Three Component Color Encoding Registry, sRGB, exact public encoding characteristics. https://registry.color.org/rgb-registry/srgb.

[B-02] Adobe, Adobe RGB (1998) Color Image Encoding, May 2005; ICC registry entry gives gamma 2.19921875. https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf; https://registry.color.org/rgb-registry/adobe-rgb-1998.

[B-03] ICC registry, scRGB and scRGB-nl. https://registry.color.org/rgb-registry/scrgb.

[B-04] Apple Technical Note TN2162, Uncompressed Y′CbCr Video in QuickTime Files, published 14 December 1999; sections on colr, nclc, gama, and legacy Macintosh graphics conversion. https://developer.apple.com/library/archive/technotes/tn2162/_index.html.

[B-05] Apple, QuickTime File Format Specification (archived), image-description extensions including gamma/color information. https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/.

[B-06] Adobe, Color management — After Effects, updated 27 March 2026; working space, linearization, and “Blend Colors Using 1.0 Gamma.” https://helpx.adobe.com/after-effects/desktop/adjust-colors/color-management/color-management.html.

[B-07] Adobe, How Color Management works — Premiere, updated 7 January 2026; input, working, output, tone mapping, and gamut compression. https://helpx.adobe.com/premiere/desktop/correct-color/set-up-color-management/how-color-management-works.html.

[B-08] FFmpeg project, FFmpeg Filters Documentation, eq, lut, colorspace, and zscale; consult the source corresponding to the deployed release. https://ffmpeg.org/ffmpeg-filters.html.

[B-09] VapourSynth R76 documentation, std.Levels, gamma direction and planes. https://www.vapoursynth.com/doc/functions/video/levels.html.

[B-10] AviSynth+ documentation, Levels, exact 1/γ1/\gamma formula and YUV behavior. https://avisynthplus.readthedocs.io/en/latest/avisynthdoc/corefilters/levels.html.

[B-11] mpv stable manual, color management, linear downscaling, target TRC/peak, ICC, gamma factor, and tone mapping. https://mpv.io/manual/stable/.

[B-12] libplacebo documentation/source, GPU color-management and tone-mapping pipeline. https://libplacebo.org/; https://code.videolan.org/videolan/libplacebo.

[B-13] WebKit Bug 185343, “CSS filters which reference SVG filters fail to respect the color-interpolation-filters of the filter,” reported/fixed May 2018. https://bugs.webkit.org/show_bug.cgi?id=185343.

[B-14] WebKit Bug 136418 and related Bug 212649, historical linearRGB Gaussian-blur/filter behavior. https://bugs.webkit.org/show_bug.cgi?id=136418.

[B-15] Mozilla Bug 948265, “Render CSS Filters using Moz2D,” verified fixed, milestone Firefox 34. https://bugzilla.mozilla.org/show_bug.cgi?id=948265.

[B-16] Chromium Issue 41007781, public-indexed feGaussianBlur banding report; current tracker status unavailable without sign-in during this research. https://issues.chromium.org/issues/41007781.

[B-17] Skia Issue 362116527, public-indexed sRGB/Display-P3 SVG feColorMatrix difference; current status unavailable without sign-in during this research. https://issues.skia.org/issues/362116527.

[B-18] WebKit, Wide Gamut Color in CSS with Display-P3, browser color matching and untagged-image assumptions. https://webkit.org/blog/10042/wide-gamut-color-in-css-with-display-p3/.

[B-19] Mozilla, Firefox 89 release notes for developers / color management changes on macOS, untagged images treated as sRGB. https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/89.

[B-20] Apple Newsroom, “Apple to Ship Mac OS X Snow Leopard on August 28,” 24 August 2009. https://www.apple.com/newsroom/2009/08/24Apple-to-Ship-Mac-OS-X-Snow-Leopard-on-August-28/.

[B-21] Apple, archived Mac OS X 10.6 refinements text: “The default gamma has been changed from 1.8 to 2.2…” The original page is retired; contemporaneous quotation is preserved in [C-06]. Treat the wording as an archived vendor statement, not a live support page.

[B-22] Microsoft, Use DirectX with Advanced Color on high/standard dynamic range displays, scRGB linear values and SDR white convention. https://learn.microsoft.com/windows/win32/direct3darticles/high-dynamic-range.

[B-23] Adobe, Color basics — After Effects, linear-light compositing guidance. https://helpx.adobe.com/after-effects/desktop/adjust-colors/color-basics/color-basics.html.

[B-24] FFmpeg Trac #9637, container colr versus SPS VUI color metadata behavior. https://trac.ffmpeg.org/ticket/9637.

C — community and empirical sources

[C-01] Vitrolite, “The QuickTime Gamma Bug,” 31 December 2010. Empirical AE CS5/Windows/QuickTime tests, including x0.88x^{0.88} and a separate QuickTime Player contrast/range result. https://vitrolite.wordpress.com/2010/12/31/quicktime_gamma_bug/.

[C-02] ProVideo Coalition, After Effects CS3/QuickTime gamma-adjustment discussion, 31 October 2007. Used only as evidence of an Adobe-exposed legacy compatibility option and contemporary behavior reports.

[C-03] Video Copilot, QuickTime gamma-shift workflow discussion, 17 June 2008. Used as evidence that the workaround class was circulating, not as a normative mechanism.

[C-04] AnimeMusicVideos forum, 2013 discussion using Levels(0,0.88,255,0,255) and linking the 2010 empirical article. Community propagation evidence.

[C-05] SilentAperture, Advanced Encoding Guide — The 0.88 gamma bug. https://silentaperture.gitlab.io/mdbook-guide/filtering/detinting.html. Contemporary recipe; mechanism claims remain C-class.

[C-06] Creative COW, “Great news: default gamma in Snow Leopard will be 2.2,” 9 June 2009, quoting Apple’s then-live refinements page. https://creativecow.net/forums/thread/great-news-default-gamma-in-snow-leopard-will-be-2/.

[C-07] Apple Support Communities and production forums, 2006–2009 QuickTime/H.264/ProRes gamma-shift reports. Used only to establish chronology and variability; individual posts do not prove a universal transform.

L — independent technical literature

[L-01] Charles Poynton, Frequently Asked Questions about Gamma, 1998 revision; CRT exponent range, Macintosh effective 1.8 behavior, and printing/imagesetter discussion. https://www.poynton.ca/pdf/GammaFAQ.pdf.

[L-02] Charles Poynton, “The rehabilitation of gamma,” SPIE/IS&T Electronic Imaging era technical paper; terminology and system-rendering discussion. https://www.poynton.ca/PDFs/Rehabilitation_of_gamma.pdf.

[L-03] BBC Research & Development, White Paper WHP 283, High Dynamic Range Television and Hybrid Log-Gamma / transfer-function engineering discussion; effective SDR camera exponent near 0.5, display 2.4, system about 1.2. https://downloads.bbc.co.uk/rd/pubs/whp/whp-pdf-files/WHP283.pdf.

[L-04] GTE Laboratories, US Patent 3,934,171 (1976), electron-gun drive/beam-current power-law behavior. https://patents.google.com/patent/US3934171A/en.


Reproducibility note

The companion gamma-analysis-repro.py implements sRGB, BT.709, BT.1886, PQ, HLG, CIE Lab/CIEDE2000, matrix examples, and every numerical error table in this article. It writes machine-readable gamma-analysis-results.json. Recompute before adapting the figures to a different bit depth, black level, white level, code range, transfer pair, or perceptual metric.