Extended Dynamic Range support

My app currently supports display and editing of RAW files in HDR mode (Extended Dynamic Range).

I came across 2 issues:

  1. In HDR mode, if I am using the default boostAmount = 1.0, then some of the highlight colors will shift. Like a clear blue sky becomes a light gray / light purple sky. I've to set boostAmount = 0.0 to avoid this problem. Is this a bug or is there a way to keep the Apple colors and not having this issue?

  2. The Shadow and highlight filter does not appear to work correctly in HDR mode, I've it hooked up in the linearSpaceFilter. Would be nice if you guys can introduce a spatial aware shadow & highlight filter.

Answered by DTS Engineer in 897049022

Thanks for the detailed write-up. To look into this I built a small tool around CIRAWFilter and ran it on several clear-sky ProRAW captures on shipping macOS 26.5.1. It decodes the sensor RAW, not the embedded preview, and samples the sky under a range of settings. I wanted to see the highlight color as measured channel values rather than by eye. Here is what I found, and where the evidence points.

On my captures I could not reproduce a blue-to-gray/purple shift from boostAmount in the decoded RAW itself. I sampled the sky in Core Image's extended-range linear working space. At boostAmount = 1.0 the sky stayed blue-dominant across the whole brightness range, including bright areas near the sun. When I forced the sky to blow out, it desaturated toward white, with red the last channel to reach the ceiling. So it approached white or a faint cyan, never magenta or purple. I did not see red exceed green in any configuration, and a purple cast needs that. In my testing boostAmount = 0.0 did not preserve blue better. It slightly desaturated the sky and reached white sooner.

A note on what boostAmount is, from the CIRAWFilter header: it controls the amount of global tone curve, where 0 is a linear response and 1 is the full curve. So boostAmount = 0.0 is not a targeted highlight control. It disables the whole default rendering curve and changes the overall look, and boostShadowAmount then has no effect. That is worth weighing before adopting it as a fix.

The pattern you describe, bright blue sky turning gray or purple, is the signature of highlights being clamped somewhere downstream of the RAW decode, not in the decode itself. A few things from the Core Image and EDR sessions line up with that. Each is worth checking in your pipeline:

  • Core Image's working space is unclamped and linear. That is why the decoded RAW keeps values above 1.0 and preserves the sky's color. If your linearSpaceFilter clamps to the 0-to-1 range, it would cap the highlights before the rest of the pipeline runs. The "Display EDR content with Core Image, Metal, and SwiftUI" session (WWDC22) suggests reviewing custom kernels for clamp, min, or max that limit RGB to 0-to-1. Those limits can often be removed.
  • Color management clips EDR values above 1.0 when an extended-range color space is not specified. "Explore HDR rendering with EDR" (WWDC21) notes this can be intermittent or content dependent. It is worth confirming your working and destination color spaces are extended-range.
  • The display path matters. "Support HDR images in your app" (WWDC23) contrasts CALayer, which tone-maps content above the display's headroom, with CAMetalLayer, where values above headroom are clamped and produce a sharp discontinuity. If your HDR mode renders through a Metal layer, highlights above the current headroom are clamped unless you tone-map them yourself.
  • On the RAW side, extendedDynamicRangeAmount lets the output use the headroom present in the file. In my tests it kept the sky blue while giving highlights real room above 1.0, instead of compressing them toward the ceiling as reducing the curve does.

For tone mapping specifically, "Use HDR for dynamic image experiences in your app" (WWDC24) describes the system path. It combines a content headroom on the image with the display headroom, fed to CIToneMapHeadroom / toneMapHeadroomFilter, and it is automatic for UIImageView and SwiftUI views. That session notes the newer Reference White tone-mapping operator reduces highlight clipping and better maintains color reproduction. That is the class of symptom you are describing. It also notes that some edits leave an image's content headroom unknown, which can defeat correct tone mapping. So it is worth checking the contentHeadroom on the image you hand to display.

I would not want to call the behavior a bug or not from here. I could not reproduce the shift with the stock filter, and I do not have your linearSpaceFilter or your exact settings. If you can share the CIRAWFilter configuration you use, the linearSpaceFilter you attach, and how you render for display (color space and layer type), that would help. A RAW that shows the shift would be even better. A capture that reproduces it on your side is the most useful thing to compare against.

On the spatial-aware shadow and highlight filter: that is a feature request. A report through Feedback Assistant is the right way to put it in front of the team. If you file it, a sample image and the specific behavior you want would help.

If you post your settings and the linearSpaceFilter, I am glad to keep digging from there.

Thanks for the detailed write-up. To look into this I built a small tool around CIRAWFilter and ran it on several clear-sky ProRAW captures on shipping macOS 26.5.1. It decodes the sensor RAW, not the embedded preview, and samples the sky under a range of settings. I wanted to see the highlight color as measured channel values rather than by eye. Here is what I found, and where the evidence points.

On my captures I could not reproduce a blue-to-gray/purple shift from boostAmount in the decoded RAW itself. I sampled the sky in Core Image's extended-range linear working space. At boostAmount = 1.0 the sky stayed blue-dominant across the whole brightness range, including bright areas near the sun. When I forced the sky to blow out, it desaturated toward white, with red the last channel to reach the ceiling. So it approached white or a faint cyan, never magenta or purple. I did not see red exceed green in any configuration, and a purple cast needs that. In my testing boostAmount = 0.0 did not preserve blue better. It slightly desaturated the sky and reached white sooner.

A note on what boostAmount is, from the CIRAWFilter header: it controls the amount of global tone curve, where 0 is a linear response and 1 is the full curve. So boostAmount = 0.0 is not a targeted highlight control. It disables the whole default rendering curve and changes the overall look, and boostShadowAmount then has no effect. That is worth weighing before adopting it as a fix.

The pattern you describe, bright blue sky turning gray or purple, is the signature of highlights being clamped somewhere downstream of the RAW decode, not in the decode itself. A few things from the Core Image and EDR sessions line up with that. Each is worth checking in your pipeline:

  • Core Image's working space is unclamped and linear. That is why the decoded RAW keeps values above 1.0 and preserves the sky's color. If your linearSpaceFilter clamps to the 0-to-1 range, it would cap the highlights before the rest of the pipeline runs. The "Display EDR content with Core Image, Metal, and SwiftUI" session (WWDC22) suggests reviewing custom kernels for clamp, min, or max that limit RGB to 0-to-1. Those limits can often be removed.
  • Color management clips EDR values above 1.0 when an extended-range color space is not specified. "Explore HDR rendering with EDR" (WWDC21) notes this can be intermittent or content dependent. It is worth confirming your working and destination color spaces are extended-range.
  • The display path matters. "Support HDR images in your app" (WWDC23) contrasts CALayer, which tone-maps content above the display's headroom, with CAMetalLayer, where values above headroom are clamped and produce a sharp discontinuity. If your HDR mode renders through a Metal layer, highlights above the current headroom are clamped unless you tone-map them yourself.
  • On the RAW side, extendedDynamicRangeAmount lets the output use the headroom present in the file. In my tests it kept the sky blue while giving highlights real room above 1.0, instead of compressing them toward the ceiling as reducing the curve does.

For tone mapping specifically, "Use HDR for dynamic image experiences in your app" (WWDC24) describes the system path. It combines a content headroom on the image with the display headroom, fed to CIToneMapHeadroom / toneMapHeadroomFilter, and it is automatic for UIImageView and SwiftUI views. That session notes the newer Reference White tone-mapping operator reduces highlight clipping and better maintains color reproduction. That is the class of symptom you are describing. It also notes that some edits leave an image's content headroom unknown, which can defeat correct tone mapping. So it is worth checking the contentHeadroom on the image you hand to display.

I would not want to call the behavior a bug or not from here. I could not reproduce the shift with the stock filter, and I do not have your linearSpaceFilter or your exact settings. If you can share the CIRAWFilter configuration you use, the linearSpaceFilter you attach, and how you render for display (color space and layer type), that would help. A RAW that shows the shift would be even better. A capture that reproduces it on your side is the most useful thing to compare against.

On the spatial-aware shadow and highlight filter: that is a feature request. A report through Feedback Assistant is the right way to put it in front of the team. If you file it, a sample image and the specific behavior you want would help.

If you post your settings and the linearSpaceFilter, I am glad to keep digging from there.

Extended Dynamic Range support
 
 
Q