Search code examples
web-standardshdrhdrimages

How does one create and display HDR images in web browsers?


Are there any image formats for the web with full HDR image support? 10/12-bit channels, DCI-P3/Rec.2020 colour space, etc.

It seems like none of the conventional formats support it, and no one is talking about it, even when YouTube accepts HDR uploads and HDR monitor adoption is increasing.


Solution

  • Current support for displaying HDR images on web pages is heterogeneous and even buggy in some browsers, mainly because of the lack of an accepted standard.

    Even though we have many web-friendly image formats that perfectly support HDR like JPEG-XL, AVIF and even JPEG-XT, there still isn't a consensus nor a single format that is well supported in a browser majority.

    Additionally, both Apple (with HEIF and their SDK) and Android (with UltraHDR) are making good efforts towards an HDR-enabled web and mobile apps, but at this point are still mostly ecosystem-centered proposals waiting to be adopted.

    The lack of conversion tools is also an important deterrent: Consistently converting from HDR images generated from Apple/Android devices or HDR formats like EXR and HDR into web-ready formats is currently a quite convoluted task involving a plethora of different tools.

    My own investigation during the last months led me to a solution that uses a combination of JPEG-XT, JPEG-XL and AVIF files to attain a 75% coverage of current browser versions (Firefox and Safari do not currently support HDR images, or are very buggy in their HDR implementations). The only solution I found to attain near-100% compatibility by supporting also Firefox and Safari is to use an HDR video file displaying the HDR still image for those browsers. I discuss this solution in a section below.

    In the current state of things (as of the end of 2024) JPEG-XT is the clear winner: It displays HDR images without any issues on all Chrome-based browsers, and falls back automatically to a non-HDR image for users that do not have an HDR display. And because JPEG-XT images are simply ".jpg" files, they can be embedded into HTML just like any other JPEG, with the <img> tag, or even in url statements in CSS.

    To attain the 75% compatibility (according to Statcounter), I use the <picture> HTML tag to provide the browser with the three alternatives to cover the widest possible compatibility.

    Note the media tricks used in JXL and AVIF formats, which prevents known compatibility bugs in Firefox and Safari targets causing them to render HDR images too dark or consume a lot of CPU.

    Also note the image-rendering style, which fixes an issue in certain Chrome versions when embedding large HDR images that caused the image to disappear just after being downloaded.

        <picture style="image-rendering: -webkit-optimize-contrast;">
          <source srcset="image.jxl" media="not all and (-webkit-touch-callout: none)" type="image/jxl" />
          <source srcset="image.avif" media="all and (min--moz-device-pixel-ratio:0) and (min-resolution: 3e1dpcm) {}" type="image/avif" />
          <img src="image.jpg">
        </picture>
    

    On this HTML snippet, please note that image.jpg refers to an HDR JPEG-XT (UltraHDR) image, not a regular JPG image.

    Current HDR images compatibility:*

    • Chrome an other chrome-based: JPG, AVIF
    • Brave: JPG, AVIF
    • Arc: JPG, AVIF
    • Firefox: none (presents bugs when trying to display HDR AVIF images)
    • Safari: none
    • Edge: AVIF

    Update january 2025

    Attaining Near-100% compatible HDR images on a website

    Because Firefox and Safari do not support any of the current HDR image format standards, they're the ones keeping the compatibility down to ~75%. However, both Firefox and Safari are capable of displaying HDR videos instead of images. This allows us to attain a near 100% compatibility by displaying an MP4 HDR video containing a still of the image for those browsers.

    This can be done in two ways:

    CSS-only

    • Use conditional CSS via @media and @supports to force Firefox and Safari to display an MP4 video instead of an image, and it can be done like this:

    Embed the image with the following HTML:

    <div class="hdrjpg">
        <img src="jpeg-xt.jpg" style="image-rendering: -webkit-optimize-contrast;">
        <video src="image.mp4" type="video/mp4" muted>
    </div>
    

    And use the following CSS to ensure the video is only used in Firefox and Safari:

    /* Use the MP4 video instead of the img on Firefox */
    @media screen and (min--moz-device-pixel-ratio:0) {
        .hdrjpg > img { display: none; }
        .hdrjpg > video { display: block; }
    }
    
    /* Use the MP4 video instead of the img on Safari */
    @supports (-webkit-backdrop-filter: blur(1px)) {
        .hdrjpg > img { display: none; }
        .hdrjpg > video { display: block; }
    }
    

    However, this method has a serious downside: The MP4 video is loaded by the browser even when it's not needed, causing a significant unnecessary bandwidth usage.

    Javascript

    To avoid the downside of using the CSS method mentioned above, a small Javascript snippet like this can be used instead:

    Embed the image with the following HTML:

    <div class="hdrjpg" data-mp4-src="image.mp4">
        <img src="jpeg-xt.jpg" style="image-rendering: -webkit-optimize-contrast;">
    </div>
    

    Run the following javascript:

    if (
        'MozAppearance' in document.documentElement.style /* Target Firefox */
        ||
        /^((?!chrome|android).)*safari/i.test(navigator.userAgent) /* Target Safari */
    ) {
        document.querySelectorAll('.hdrjpg[data-mp4-src]').forEach((element) => {
            element.querySelector('img').remove();
            let video = document.createElement('video');
            video.src = element.dataset.mp4Src;
            video.muted = true;
            element.appendChild(video);
        });
    }
    

    Converting an HDR source to web-ready formats

    Converting an HDR source image to JPEG-XT, JPEG-XL, MP4 and other web-ready formats while keeping the HDR effect can be really tricky, so I created HDRJPG, an API that allows you to do exactly that.

    Further documentation: