An 8-bit file carries chromaticity, not luminance. Its pixels are scattered on the diagram and measured against each triangle; nothing here is reported in nits.
Chromaticity plane with gamut triangles, spectral locus and line of purples. Areas are quoted as a share of the spectral locus, in the plane on screen. D65 white-point marker shown.
| Space | Primaries | White | Share of locus | Area |
|---|
Spectral locus uses CIE 1931 2° observer. ICtCp uses PQ (BT.2100). Jzazbz uses Safdar et al. 2017, including its own quantiser at p = 134.034375 rather than ST 2084’s.
Encoded signal (horizontal) vs absolute cd/m² (vertical, log scale). PQ maps 0–1 to 0–10 000 cd/m². HLG scales by peak nits and system gamma.
Comparative plot of all three transfer functions on a log-nits vertical axis. PQ covers the full 0–10 000 cd/m² range; HLG scales by system gamma at 1000-nit reference; sRGB peaks at ~100 nits.
Gamut boundaries in the chosen perceptual colour space at the signal level’s absolute luminance. Shows chroma extent for each enabled gamut.
Computes gamut boundary rings at multiple luminance levels (0.1, 1, 10, 100, 500, 1000, 4000, 10 000 nits) in the selected perceptual space (ICtCp or JzAzBz). Shows how chroma extent changes with luminance.
| Space | Area | Share of locus | White |
|---|
ITU-R BT.2020 primaries
BT.2020 (Rec.2020) specifies the colour primaries, white-point (D65), and transfer characteristics for UHDTV systems. Its primaries enclose 63.6% of the CIE 1931 xy locus as this page measures it, against 33.6% for sRGB and 45.6% for Display-P3. The 75.8 / 35.9 / 53.6 usually quoted are against a smaller locus area than the one drawn here.
Primaries: R (0.708, 0.292), G (0.170, 0.797), B (0.131, 0.046). White-point: D65 (0.3127, 0.3290).
Bit depth: 10-bit (narrow range) or 12-bit for HDR content. BT.2020 defines a non-constant-luminance YCbCr encoding; the HDR extension is in BT.2100.
ITU-R BT.2100 transfer
BT.2100 builds on BT.2020 primaries and adds two HDR transfer functions: Perceptual Quantizer (PQ) and Hybrid Log-Gamma (HLG). It also defines the ICtCp colour-difference space for HDR content evaluation.
PQ (ST 2084): Absolute luminance mapping 0–10 000 cd/m². Designed around the Barten CSF for optimal quantisation of human-visible luminance steps.
HLG (ARIB STD-B67): Relative, scene-referred transfer function that is backward-compatible with SDR displays. Uses system gamma (γsys) to adapt to display peak luminance.
SMPTE ST 2084 transfer
ST 2084 defines the Electro-Optical Transfer Function (EOTF) for mastering reference displays. PQ encodes absolute luminance from 0 to 10 000 cd/m² using a perceptual quantisation curve based on the Barten contrast sensitivity function.
PQ achieves near-invisible quantisation steps at all luminance levels with 10-bit or 12-bit encoding. It is the reference EOTF for Dolby Vision, HDR10, and HDR10+ workflows.
Key property: Display-referred — the encoded signal directly maps to absolute nits, making it independent of display peak luminance (requires tone mapping for non-reference displays).
ARIB STD-B67 transfer
HLG is a scene-referred HDR transfer function adopted by NHK and BBC. It combines a square-root segment (low light) with a logarithmic segment (highlights), providing backward-compatibility with SDR displays without metadata.
System gamma: γsys = 1.2 + 0.42 × log10(Lw/1000). For 1000-nit display: γsys = 1.2. Adjusted for dim (+0.95×) and bright (+0.90×) surround.
OOTF: The Opto-Optical Transfer Function applies system gamma to convert scene-linear to display-linear, producing the final rendered image.
ICtCp runs
ICtCp (Dolby, 2016) is a perceptually uniform colour space designed for HDR and wide-colour-gamut content. It decomposes colour into Intensity (I), Tritan chroma (Ct, blue-yellow), and Protan chroma (Cp, red-green).
Construction: BT.2020 linear RGB → LMS (BT.2100 matrix) → PQ encode each channel → 3×3 rotation to ICtCp.
Advantages: Better hue linearity than L*a*b* at HDR luminances. JND-calibrated by PQ non-linearity. Used for colour-difference evaluation (ΔEITP) in BT.2124.
Jzazbz corrected
JzAzBz (Safdar, Hardeberg & Ronnier Luo, 2017) is a perceptually uniform colour space that handles both SDR and HDR luminance ranges. It improves upon CIELAB by using PQ-based lightness and a non-linear XYZ pre-processing step.
Components: Jz (lightness), Az (redness-greenness), Bz (yellowness-blueness). Unlike ICtCp, JzAzBz operates from XYZ rather than BT.2020 RGB.
ITU-R BT.709 & sRGB primaries
BT.709 (Rec.709) defines the primaries for HDTV. sRGB (IEC 61966-2-1) shares the same primaries and D65 white-point but specifies its own transfer function (piecewise linear + gamma 2.4).
Primaries: R (0.640, 0.330), G (0.300, 0.600), B (0.150, 0.060). Covers 33.6% of the CIE 1931 xy locus as this page measures it; the ~35.9% usually quoted is against a smaller locus area.
Display-P3 primaries
Display-P3 (based on DCI-P3 cinema) uses wider primaries than sRGB, covering ~53.6% of CIE 1931. It is the default wide-gamut space on Apple devices and increasingly adopted in web content (CSS color Level 4).
Primaries: R (0.680, 0.320), G (0.265, 0.690), B (0.150, 0.060). White-point: D65 (same as sRGB).
ST 2084 PQ EOTF runs
Maps encoded signal V to absolute luminance Y (cd/m², max 10 000):
m₁ = 0.1593017578125
m₂ = 78.84375
c₁ = 0.8359375 c₂ = 18.8515625 c₃ = 18.6875
ARIB HLG OOTF & OETF runs
HLG OETF (scene linear → encoded E′):
E′ = a·ln(12E−b) + c if E > 1/12
a = 0.17883277, b = 0.28466892, c = 0.55991073
System gamma: γsys = 1.2 + 0.42·log10(Lw/1000)
ICtCp transform runs
1. Linear BT.2020 RGB → LMS via BT.2100 matrix M₁:
M₁ = [ 0.3593, 0.6976, −0.0359;
−0.1921, 1.1005, 0.0754;
0.0071, 0.0748, 0.8433 ]
2. Apply PQ EOTF independently to each L, M, S → L′, M′, S′
3. Rotation to ICtCp:
Ct = 1.6137·L′ − 3.3234·M′ + 1.7097·S′
Cp = 4.3780·L′ − 4.2455·M′ − 0.1325·S′
I is intensity (luminance); Ct is tritan (blue-yellow); Cp is protan (red-green).
Jzazbz transform corrected
Absolute XYZ (nits) → JzAzBz:
Y′ = g·Y − (g−1)·X (g = 0.66)
[L M S] = M·[X′ Y′ Z]
PQ-encode each L, M, S → L′, M′, S′
Iz = 0.5·L′ + 0.5·M′
Jz = (1+d)·Iz / (1+d·Iz) − d₀
Az = 3.524·L′ − 4.067·M′ + 0.543·S′
Bz = 0.199·L′ + 1.097·M′ − 1.296·S′
d = −0.56 d₀ ≈ 1.63×10−11
Gamut area — shoelace corrected
Locus share % = Aspace / Alocus × 100, both in the plane on screen. It was Aspace / ARec.2020, which made Rec.2020 read 100% and said nothing about how much of the visible spectrum any of the three holds.
Tone-mapping operators runs
Hable (Filmic):
f(x) = ((x(Ax+CB)+DE) / (x(Ax+B)+DF)) − E/F
A=0.15 B=0.50 C=0.10 D=0.20 E=0.02 F=0.30
Result = f(L) / f(11.2)
ACES Filmic:
Ld = (L(2.51L+0.03)) / (L(2.43L+0.59)+0.14)
Uchimura (Gran Turismo):
Piecewise toe + shoulder with max brightness P, contrast a, mid-level m, and hue preservation.
Chromaticity conversions runs
x = X / (X+Y+Z) y = Y / (X+Y+Z)
CIE 1976 u′v′:
u′ = 4x / (−2x + 12y + 3)
v′ = 9y / (−2x + 12y + 3)
Seventeen references, each with what this page takes from it. A ● marks one the rebuild corrected — a paper cited for a constant the code was not running, or a document credited with more than it defines.
No nits from a file absent
A PNG or JPEG holds eight bits per channel and no statement of what luminance those bits stood for. There is nothing in the file to read a nit off.
The readout used to print one anyway: the brightest luma in the image times the peak-luminance slider, taken from gamma-encoded values with BT.2020’s luma weights applied to sRGB pixels. Every part of that is wrong, and the number it produced moved when you moved a control that had nothing to do with the image.
What a file does carry is chromaticity. The pixels are decoded, plotted on the diagram, and measured for how much of each gamut they occupy — which is a real measurement, and one that varies.
Occupancy, not coverage computed
“What fraction of sRGB does this image cover?” sounds like the question, and for an untagged file it has a fixed answer: all of it. With no profile the file is read as sRGB, so every pixel is inside sRGB by construction, and inside P3 and Rec.2020 because those contain it. The old readout printed 100.0% for all three on every image, and that part of it was not even wrong — it was just answering a question with only one answer.
The useful question is how much of a gamut the image occupies. The chromaticities are binned at 0.002 in xy, and the occupied cells inside each triangle are compared with that triangle’s area. A grey ramp reads 0.0%; a dense sweep of the sRGB cube reads 99.9% of sRGB, 75.0% of P3 and 53.8% of Rec.2020, which is what the area ratios say it should.
One share, one denominator computed
Six places on this page quoted a gamut’s size: the metrics table, the comparison chart, the comparison table, the JSON, the CSV and the CSS custom properties. All six divided by Rec.2020’s area, so Rec.2020 read 100% and nothing said what it was 100% of. The CSV column was headed “Coverage%”.
All six divide by the spectral locus now — the 65-point one this page already tabulates, whose shoelace area is 0.33323 in xy against a published 0.334. That gives 33.6, 45.6 and 63.6 percent, and every file that carries those numbers names the locus that made them.
The 75.8 / 35.9 / 53.6 that circulate for these three are against a smaller locus area than the one drawn here. Both are defensible; quoting one while drawing the other is not.
Absolute and display-referred runs
PQ maps a signal to a luminance outright: 0.5 is 92 cd/m² on any display that can reach it. HLG and the two power laws reach a luminance only by way of a peak the display supplies. Those are different kinds of statement and the Curves view tags each row with which it is making.
The plot did not distinguish them. Its vertical axis topped out at
log₁₀(max(L_W, 100)) — the display peak — while PQ
reaches 10 000 cd/m² regardless, so at the default 1 000 nits about a
quarter of the PQ curve sat clamped flat against the top of the frame with the
nit stops ending at 1 000. Nothing on screen said a decade was missing. The
slider’s own caption read “controls PQ absolute scaling”, which
the code never did.
The surround factor stand-in
The HLG system gamma 1.2 + 0.42 log₁₀(L_W/1000) is BT.2100 Table
5’s, and runs as written. The dim and bright surrounds then multiply it by
0.95 and 0.90, and those two numbers appear in neither BT.2100 nor BT.2390.
They are kept, because a surround control is genuinely useful and the shape of the adjustment is right. What changed is that choosing one marks the reading adjusted instead of quoting it as a standard value, and the register files it as a stand-in.
Two quantisers corrected
ICtCp and Jzazbz are both built on a perceptual quantiser, and they are not the same one. ICtCp uses ST 2084 as written. Jzazbz raises the outer exponent by a factor of 1.7, to 134.034375.
The code had one encoder and called it from both. ICtCp was therefore correct and Jzazbz was not — every Jz, az and bz it produced ran through a curve 0.59× too shallow in the exponent. The plane it drew still looked like a perceptual plane, which is why it survived: the shape was right and only the scale was wrong.
What “ACES” means runs
The operator labelled ACES is Krzysztof Narkowicz’s 2016 curve fit:
(x(2.51x + 0.03)) / (x(2.43x + 0.59) + 0.14). It approximates the
look of the Academy’s RRT and ODT in five constants and is widely used for
exactly that reason.
It is not the Academy’s transform, which is a chain of lookup tables and matrices this page does not implement. The citation says so now, and was attributed to 2015.