Skip to content

Align on log2 versus linear encoding for CSS, 2D canvas, WebGL, and WebGPU APIs #129

Open

Description

@ccameron-chromium

This is discussed here:
https://github.com/w3c/ColorWeb-CG/blob/main/hdr-big-picture.md

There exist 4 APIs that need to specify HDR headrooms:

  • CSS hdr-color
  • 2D Canvas globalHDRHeadroom
  • WebGL unpackHDRHeadroom
  • WebGPU hdrHeadroom

These must align on using linear or log2 value. If they are to use linear values, then I would suggest using the terms globalLinearHDRHeadroom, unpackLinearHDRHeadroom, and linearHDRHeadroom to ensure no ambiguity.

Moving the cross-specification discussion here. Once there is a resolution, we can update each specification to match it.

Activity

  1. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor
    1. A log2 scale is perceptually uniform, equal increases give equal effects. A linear scale causes an increment to do a lot at the start of the scale and little at the higher ranges. This is why Hybrid Log-Gamma HDR uses a log scale for the headroom portion, and why photographic stops are defined the way they are
    2. A log2 scale makes interpolating colors by headroom simpler to explain and simpler to show in pseudocode; a linear scale requires all the headroom values to be first converted to log2 before they can be used
    3. log2 gives an easy to understand, straightforward definition of headroom which places SDR headroom at 0 and values greater than zero represent

    The amount by which peak white is greater than HDR reference white is termed the HDR headroom, and depends on the level of HDR reference white, user preference, and viewing conditions.
    It is typically expressed in photographic stops. Thus, standard dynamic range (SDR) by definition has an HDR headroom of 0 stops, because the brightest white is HDR reference white.)

    A linear scale has caused confusion because it starts at 1 rather than zero
    4. ISO_21496-1, Gain map metadata for image conversion: Part 1: Dynamic Range Conversion uses a log2 scale for headroom
    5. SMPTE-ST-2094-50 Dynamic Metadata for Color Volume Transform - Application #5 (Broadcast) uses a log2 scale for headroom

  2. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    I added non-normative text next to the definition, emphasizing the expected value range (that the value will be 0 for SDR screens, and may be 0 or >0 for HDR-capable screens, but will vary a lot over time).

    Originally posted by @ccameron-chromium in #149

  3. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    ISO definition of headroom:

    Image
  4. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    From an Adobe blog explaining HDR:

    At last, I can edit the file in HDR and see the highlights as they ought to be: lively and luminous! The HDR rendition gives a much greater impression of sunlight shining through the fox's fur. HDR displays offer an extra two to four stops of highlight headroom compared to a normal display.

  5. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    From Greg Benz Photography, describing HDR screens:

    1,000+ nit monitors (to provide 3+ stops of HDR headroom):

  6. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    From the Committee Draft of SMPTE ST 2094-50:

    high dynamic range headroom
    HDR headroom
    ratio of maximum content light level of an image to the HDR reference white luminance of the image, expressed as a log base 2 value
    Note 1 to entry: For example, for an image with the reference white luminance at 203 cd/m2, and maximum content light level equal to 1624, the HDR headroom would be log2(1624/203) , or 3. This means 3 stops of HDR headroom beyond HDR reference white.
    Note 2 to entry: This is analogous to the HDR headroom from Clause 3.6 of ISO/DIS 21496-1:2025.

  7. ladyhavoc commented on Feb 4, 2026

    @ladyhavoc
    1. A log2 scale is perceptually uniform, equal increases give equal effects. A linear scale causes an increment to do a lot at the start of the scale and little at the higher ranges. This is why Hybrid Log-Gamma HDR uses a log scale for the headroom portion, and why photographic stops are defined the way they are

    Personal opinion - I'm not sure I agree with the claim that log2 appears linear in typical office and home viewing conditions (which is a large portion of web usage), the baseline ambient light is generally fairly significant (in fact it can be distractingly bright in a room) so iris adaptation is not purely based on the brightness of the colors being compared in absolute terms, nor even in relative terms.

    2. A log2 scale makes [interpolating colors by headroom](https://drafts.csswg.org/css-color-hdr/#headroom-interpolation) simpler to explain and simpler to [show in pseudocode](https://drafts.csswg.org/css-color-hdr/#headroom-interpolation); a linear scale requires all the headroom values to be first converted to log2 before they can be used
    

    Position - Our position is that we should not be interpolating hdr colors based on target display headroom, so this hdr-color syntax is problematic by itself from our perspective, using it to justify the headroom being log2 is out of scope from that perspective as we do not want this feature to be implemented this way. Tonemapping using Reinhard equation 4 with the maximum RGB component as brightness metric offers a better solution to apply to web content in general, where each element can be tonemapped independently based on the target display headroom and the calculated brightness of the element (whether a CSS color or image), a CSS property can be added to hint that this innate brightness be ignored in lieu of a higher value to make an entire portion of a document be artistically consistent with itself (tonemapping effect being applied as if the content brightness is at least that value on all elements that inherit the value). We believe that as HDR displays become more ubiquitous and more featureful (higher headroom) it will become obvious that specifying a single value that is (typically) below the target display maximum will be the norm on the web (on the assumption that displays will have 3 stops of headroom above reference white in future - see note 1 below) so there is no reason to state a headroom value or define how color interpolation behaves, because values will quite typically be within bounds of the display and the interpolation of hdr colors based on target display headroom will eventually become a footnote in history, with tonemapping as the solution for displays of lesser capability (or explicit user intent - some users experience eye strain with bright content and would wish to limit this), which provides a well-defined solution to the problem. Web content authors having to specify multiple hdr colors to interpolate between seems inelegant when the purpose would be consistent display of neighboring elements, and images do not have a corresponding interpolation criteria (in so far as static JPEG gainmaps as proposed by Adobe do not seem equivalent, and dynamic gainmaps as defined in the two cited standards are inherently very similar to Reinhard tonemapping) so tonemapping has to be applied to them which is inconsistent with the proposed hdr-color syntax; we can have one solution of tonemapping for all colors out of range for the target display and not focus on interpolating hdr-color syntax differently than images.

    Note 1: Assumption of 3 stops of usable headroom is based on Steve Yedlin's excellent talk on Debunking HDR https://www.yedlin.net/DebunkingHDR/ the dynamic range between focused content in the image and background highlights is inherently limited by eye strain, so we don't expect headroom exceeding 3 stops to be useful, per his argument you don't want to go much above 2 stops for 'sizzling highlights' as he terms it or characters focused in the middle of the screen become shadowy silhouettes in practice. 3 stops gets you the dynamic range for breathtaking vistas and terrain photos with a reasonable sky brightness, so I think display technology will plateau around there as standard.

    3. log2 gives an easy to understand, [straightforward definition of headroom](https://drafts.csswg.org/css-color-hdr/#introducing-headroom) which places SDR headroom at 0 and values greater than zero represent
    

    Personal opinion - This statement makes sense on its merits, however I generally don't want to be specifying headroom values wherever we can possibly avoid it, for the reason stated above about display capabilities expected to typically exceed the content on the web.

  8. svgeesus commented on Feb 4, 2026

    @svgeesus
    Contributor

    Personal opinion - I'm not sure I agree with the claim that log2 appears linear in typical office and home viewing conditions (which is a large portion of web usage), the baseline ambient light is generally fairly significant (in fact it can be distractingly bright in a room) so iris adaptation is not purely based on the brightness of the colors being compared in absolute terms, nor even in relative terms.

    A fair point and of course the movie and TV standards assume adaptation to a dim (TV in the evening) or dark (movie theatre) environment. Is there a better transfer function that you had in mind? Do we have data on adaptation state in typicl office and home daytime environments? (Off topic here but I also wonder about the adapted white point in such environments).

    My understanding of Hybrid Log Gamma is that it uses the peak range primarily to account for viewing in non-dark environments (turn up the brightness) as opposed to PQ which primarily uses it for super-bright highlights and assumes a dim to dark viewing environment. And of course HLG uses a log scale to encode everything above HDR reference white (IRE 0.75).

  9. ladyhavoc commented on Feb 5, 2026

    @ladyhavoc

    Personal opinion - I'm not sure I agree with the claim that log2 appears linear in typical office and home viewing conditions (which is a large portion of web usage), the baseline ambient light is generally fairly significant (in fact it can be distractingly bright in a room) so iris adaptation is not purely based on the brightness of the colors being compared in absolute terms, nor even in relative terms.

    A fair point and of course the movie and TV standards assume adaptation to a dim (TV in the evening) or dark (movie theatre) environment. Is there a better transfer function that you had in mind? Do we have data on adaptation state in typicl office and home daytime environments? (Off topic here but I also wonder about the adapted white point in such environmvieents).

    My understanding of Hybrid Log Gamma is that it uses the peak range primarily to account for viewing in non-dark environments (turn up the brightness) as opposed to PQ which primarily uses it for super-bright highlights and assumes a dim to dark viewing environment. And of course HLG uses a log scale to encode everything above HDR reference white (IRE 0.75).

    Studying human vision is definitely outside my expertise, and I'm not sure how this curve would be applied in practice unless we're talking about another kind of perceptually uniform gradient in office viewing conditions or something of that nature, I was merely taking minor issue with the assumption that log2 is perceptually linear in typical office viewing conditions.

    Very much personal opinion here but HLG is the more human-aligned transfer function than PQ, it has less range because as a practical matter we don't need 10000 nits scene reference color in all normal usage scenarios and the PQ function needs 10 bits just to match SDR quality for the SDR dynamic range - Yedlin's lecture touched on the lack of need for more than 2-3 stops of contrast between scene elements and background and HLG seems tailored to this same assumption very well.

    This is largely off topic to the concerns of this issue though.

  10. ccameron-chromium commented on Feb 11, 2026

    @ccameron-chromium
    ContributorAuthor

    From the discussion last week, there are multiple parties that take the position that "whatever the name is, it must specify whether it is log2 or linear in the name".

    That gives us a choice of:

    • globalLinearHDRHeadroom, unpackLinearHDRHeadroom, and linearHDRHeadroom
      • or globalLinearHeadroom, unpackLinearHeadroom, and linearHeadroom
      • or globalHeadroomLinear, unpackHeadroomLinear, and headroomLinear
    • globalLog2HDRHeadroom, unpackLog2HDRHeadroom, and log2HDRHeadroom
    • globalHDRHeadroomStops, unpackHDRHeadroomStops, and hdrHeadroomStops (EDIT: added based on comments below)

    My thoughts:

    • I find the "log2" version a bit uglier than the "linear one".
    • I prefer to maintain the position that "headroom is log2 unless specified otherwise". If we use the "linear" names, then I can still labor under the illusion that this is the case.

    My vote:

    • I vote for the "linear" variation, based on the above.
    • I will still insist that "if not specified as linear, it's log2", but nobody needs to care about that.

    Future:

    • The one place where I may insist to use log2 is in the SMPTE ST 2094-50 metadata, were, e.g, there exists a BaselineHdrHeadroom metadata item, which is defined as log2, and I wouldn't want to change it.
  11. jonsneyers commented on Feb 14, 2026

    @jonsneyers

    What about "HDRHeadroomStops"? It's not an explicit "log2" but it's clear enough that this is in logscale units, and "stops" is a pretty intuitive and well-known way to think about doublings of light.

    I have no strong opinion — it's trivial to convert from one to the other, the main thing is it needs to be clear what is expected. But I have a slight preference for stops/log2, since it makes to me more sense to have a scale that starts at 0 for something that is called "headroom".

  12. svgeesus commented on Feb 18, 2026

    @svgeesus
    Contributor

    I agree, and suggested a name with "stops" on the call last week

  13. gregbenz commented on Feb 23, 2026

    @gregbenz

    There is value in both stops and linear:

    • stops reflect human perception and are the basis of photo editing for HDR
    • linear is of course what developers will use and know best

    If I had to pick one, I'd go with stops because anyone needing linear should be able to easily do the conversion.

    I agree with @ccameron-chromium and @svgeesus's suggestion that the name should make it clear which units are used. It's worth being verbose to avoid confusion / accidental misuse.

    I like names starting with hdrHeadroom as it puts the key info first (and maybe helps code hinting in some cases as your search will start with "h" most likely). It's not clear to me that "global" or "unpack" add value here, so I like:

    • hdrHeadroomStops
    • hdrHeadroomLinear
      (ideally offering both, but "hdrHeadroomStops" if only supporting one)
  14. louderspace commented on Feb 23, 2026

    @louderspace

    My recommendation is to use hdrHeadroomLinear. Since we are often processing pixel values and histograms using linear values, any log or stop conversion is easily handled by the display. Within Adobe, this Lightroom Web example shows a histogram that is based on stops within the HDR range. The histograms values are computed as linear values and the display is transforming them to stops. Therefore, I prefer the native data to be linear and any formulation to convert to log is up to the application. -jacquave@adobe.com

    Image
  15. mwyrzykowski commented on Feb 25, 2026

    @mwyrzykowski

    Apple discussed internally and prefers linearHeadroom as the HDR part seems superfluous given the context which this value is used.

  16. svgeesus commented on Mar 3, 2026

    @svgeesus
    Contributor

    Apple discussed internally and prefers linearHeadroom as the HDR part seems superfluous given the context which this value is used.

    And for the small amount of headroom in narrow-range SDR content?

  17. ccameron-chromium commented on May 5, 2026

    @ccameron-chromium
    ContributorAuthor

    Apple discussed internally and prefers linearHeadroom as the HDR part seems superfluous given the context which this value is used.

    There is a sense that this makes. But I'm against that level of simplification. We have similar values for "colorSpace" (in 2D, WebGL, and WebGPU), and "pixelFormat" (WebGPU and ImageData). There is no other space or format at play, so those should be "format" and "space" (by that -- reasonable -- logic). But that's not the direction followed, and I think they're right to give the extra context. And I think for headroom, having the "this is about HDR" in the name immensely improves clarity.

  18. ccameron-chromium commented on May 6, 2026

    @ccameron-chromium
    ContributorAuthor

    At this point, I think I'd like to limit the menu of options down to:

    Option A:

    • globalHdrHeadroomLinear (2D canvas), unpackHdrHeadroomLinear (WebGL), and hdrHeadroomLinear (WebGPU)

    Option B:

    • globalLinearHdrHeadroom (2D canvas), unpackLinearHdrHeadroom (WebGL), and linearHdrHeadroom (WebGPU)

    My preference is Option A (putting linear up-front is more confusing IMO).

    (Now that the Tag review is done, we are ready to start landing spec patches).

  19. mwyrzykowski commented on May 12, 2026

    @mwyrzykowski

    And I think for headroom, having the "this is about HDR" in the name immensely improves clarity.

    What could image headroom refer to other than HDR? It seems the two terms are overlapping in context.

    Option (A) does not read well as linear being an adjective should precede headroom, a noun.

  20. ccameron-chromium commented on May 19, 2026

    @ccameron-chromium
    ContributorAuthor

    I suspect W3C probably has guidelines that prefer (B) over (A), much as I'd like to have "linear" come after.

    One other comment is should we consider "linearized HDR headroom" instead of "linear HDR headroom"? The idea being that you have linearized a normally log feature.

  21. ladyhavoc commented on May 19, 2026

    @ladyhavoc

    I can't say I like the idea of linearized, it's also semantically an issue because whether that is spelled with a z or s depends on regional spellings.

    I do not have a lot to directly contribute on the topic of Linear vs Linearized as I don't like the term HdrHeadroom in this to begin with - in my mind it is peak content brightness (i.e. the magnitude of the brightest pixel or 99th percentile of pixel magnitudes if one wishes to be a little more flexible), so part of my issue with linearized is that the thing I would want this to describe has never been associated with log2 to begin with, you are linearizing headroom but you are not linearizing peak content brightness as that is already a linear concept. In terms of the Reinhard tonemapping paper the equation 4 parameter Lwhite (which I read as luminance of white) has to be equal to the ratio of content/display peak content brightness values, this value is implicitly linear and its name comes from rendering terminology, whereas HdrHeadroom comes from photography terminology and has very little prior relevance to rendering.

  22. ladyhavoc commented on May 19, 2026

    @ladyhavoc

    I should call out something unobvious to anyone who might be casually reading this issue, the semantic use of HdrHeadroom here is describing the virtualized display peak brightness, in regards to the content brightness - i.e. you have a piece of content, and you are saying "and this was authored to be shown on a display of this peak brightness", a lower value leads to a brighter picture because you are saying it was meant to saturate at this level of white.

    I find it more useful to think about this in terms of a piece of code I wrote a while back for testing:

    // This transforms linear RGB colors to fit within a display limit using
    // equation 4 from the Reinhard (2002) paper, given a peak content luminance and
    // peak output luminance.
    //
    // Paper:
    // https://www-old.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf
    //
    // Equation 4:
    //   Ld(x,y) = L(x,y) * (1 + L(x,y) / (Lw * Lw)) / (1 + L(x,y))
    // Where:
    //   L(x,y) is the luminance of an input pixel in linear RGB 0..Lwhite
    //   Ld(x,y) is the luminance of the output pixel in linear RGB 0..1
    //   Lw is the log-average luminance of the entire image (all pixels)
    //     Lw = (1 / N) * exp(sum(log(epsilon + Lw(x,y))))
    //   An exact definition of luminance is not in the paper.
    //
    // Adding a parameter Lo for maximum output luminance rather than assuming 1 is
    // very useful, which gives us this variant of Equation 4:
    //   Ld(x,y) = L(x,y) * (1 + L(x,y) * Lo / (Lw * Lw)) / (1 + L(x,y) / Lo)
    //
    // Separating out the uniform values for a given combination of Lw and Lo:
    //   a = Lo / (Lw * Lw)
    //   b = 1 / Lo
    //   Ld(x,y) = L(x,y) * (1 + L(x,y) * a) / (1 + L(x,y) * b)
    //
    // Extending it to handle RGB colors by rescaling them:
    //   L(x,y) = max(R(x,y), G(x,y), B(x,y))
    //   Rd(x,y) = R(x,y) * (1 + L(x,y) * Lo / (Lw * Lw)) / (1 + L(x,y) / Lo)
    //   Gd(x,y) = G(x,y) * (1 + L(x,y) * Lo / (Lw * Lw)) / (1 + L(x,y) / Lo)
    //   Bd(x,y) = B(x,y) * (1 + L(x,y) * Lo / (Lw * Lw)) / (1 + L(x,y) / Lo)
    //
    // HDR content (e.g. 1000 nits) on an SDR display (<= 203 nits) is scaled by a
    // curve such that the darker colors are nearly unmodified but the brighter
    // colors are greatly compressed, so that detail is not completely lost in bright
    // areas by clamping (which is the default behavior without deliberate
    // tonemapping).
    //
    // By default this is dst=src until an init method such as Reinhard() is called.
    struct TonemapReinhardDesc {
      // These two coefficients are extracted from Reinhard equation 4 as they are
      // uniform for any unique pair of luminanceContent and luminanceDisplay
      float a = 0.0f;
      float b = 0.0f;
    
      // Calculate curve coefficients for Reinhard tonemapping.
      //
      // Luminance is typically in nits, but is used as a ratio so any units work.
      //
      // If luminanceContent <= luminanceDisplay this makes a no-op transform.
      TonemapReinhardDesc(float luminanceContent, float luminanceDisplay) {
        if (luminanceContent <= luminanceDisplay) {
          a = 0.0f;
          b = 0.0f;
        } else {
          a = luminanceDisplay / (luminanceContent * luminanceContent);
          b = 1.0f / luminanceDisplay;
        }
      }
    
      vec3 DstFromSrc(const vec3 src) const {
        // Scale the overall RGB brightness as a whole, not individually, this is
        // important to avoid saturation and hue changes.
        auto maximum = std::max(src.x(), std::max(src.y(), src.z()));
        auto scale = (1.0f + a * maximum) / (1.0f + b * maximum);
        return vec3{{src.x() * scale, src.y() * scale, src.z() * scale}};
      }
    };

    In the above code, luminanceDisplay and luminanceContent have very deliberate naming that makes their purpose clear in the context in which they are used.

    What we're talking about here is slightly different because the headroom values are often used both as a luminanceDisplay value and a luminanceContent value in some cases:

    • hdr-color is taking two values that luminanceDisplay could be and interpolating between those colors based on where the actual luminanceDisplay lands in that range during this render (this interpolation is clamped to either end of the range), with the expectation of not applying a tonemapping curve to the result, this strange behavior is the reason I oppose hdr-color as a concept and believe regular CSS color specification is quite adequate already with values > 1 which is already allowed but that's a separate discussion, either way we have to calculate the luminanceContent value from the result when compositing into the stacking context.
      • Note that luminanceDisplay can change in realtime on mobile devices with a light sensor, so imagine re-rasterizing the entire visible portion of the document at vsync rate and the battery impact that would have - I prefer making stacking contexts have a luminanceContent value for composing into their parent stacking context, as this is friendly to tile compositors where only the root needs to be re-rendered each frame and the contents of all child stacking contexts can be cached across frames, but this approach is very much incompatible with the way hdr-color is specified (unless we ignore the SDR color provided and use only the HDR end of the range, much like a derived luminanceContent on an image file, but then we're not honoring the way hdr-color is specified to work).
    • globalLinearHdrHeadroom in Canvas2D is simultaneously both the luminanceDisplay value used in rendering into the canvas framebuffer (and implicitly the luminanceContent is derived from the primitive fill color or images being drawn in each operation), and the luminanceContent used in compositing that into the parent stacking context in terms of CSS image composition, I'm not sure if it makes sense for these to be the exact same value but it hand-waves that aspect.
    • unpackLinearHdrHeadroom in WebGL is describing the luminanceDisplay value for uploading textures from CSS image sources, this can be changed for each texture created (and there are reasons to do that, but most of them boil down to "just set it to a bigger number than you will ever encounter" in my thought experiments). The luminanceContent value for compositing the canvas into the stacking context does not seem to be specified (please correct me if I missed it, or if it is implicitly the globalLinearHdrHeadroom from Canvas2D?).
    • linearHdrHeadroom in WebGPU is handled the same as unpackLinearHdrHeadroom in WebGL.
  23. ccameron-chromium commented on May 22, 2026

    @ccameron-chromium
    ContributorAuthor

    globalLinearHdrHeadroom in Canvas2D is simultaneously both the luminanceDisplay value used in rendering into the canvas framebuffer [...] and the luminanceContent used in compositing [...] I'm not sure if it makes sense for these to be the exact same value but it hand-waves that aspect.

    You're right, that wouldn't make sense, but the proposal is careful about that.

    globalLinearHdrHeadroom is the value used when rendering into the canvas framebuffer. And it can be changed at every draw call (e.g, if an application wants to draw a bunch of stuff in SDR and then one main thing in HDR).

    But the value used in compositing is the one that is specified in the context's toneMapping parameter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions