Skip to content
2024-09-10

OKLAB Colorspace Gamut Clipping

Intro

This project starts off by browsing hacker noes and stumbling on a fantastic article by Björn Ottosson on sRGB gamut clipping. In summary, it details a solution to image color editing where modifications to an RGB color can cause it to fall outside the RBG color range of 0.0 to 1.0 range (or 0 to 255 for 8-bit colors). The initial solution is to clamp the values to the acceptable range. As the erticle shows, this causes loss of detail though. The solution proposed is to map the color to to OKLAB colorspace and then perform the gamut clipping there, and then convert it back.

I have never heard about the OKLAB, nor the LAB color space, but I got really excited about it and found a neat tool to visualize it at https://oklch.com/

Implementation

Ideally, I would write this in GPU code, but because I want to experiment and debug easier, I did the calculations on the CPU. The language I used is V which transpiles to C so the operations should still be pretty fast. All of my code can be found on https://github.com/phcreery/oklabgamutclipping but I am going to show a small snippet of it.

modify_image_rgbf64(mut image_color_image_gamut_clip_preserve_chroma, image, fn (pixel_rgbf64 RGBf64) RGBf64 {
    temp_pixel_rgbf64 := adjust_temp(pixel_rgbf64, 4500, 1.0)
    mut pixel_oklab := oklab.linear_srgb_to_oklab(oklab.RGB{
        r: temp_pixel_rgbf64.r
        g: temp_pixel_rgbf64.g
        b: temp_pixel_rgbf64.b
    })
    mut pixel_lch := oklab.oklab_to_lch(pixel_oklab)
    pixel_lch.l = pixel_lch.l * 1.2
    pixel_lch.c = pixel_lch.c * 2.5
    colorized_pixel_oklab := oklab.lch_to_oklab(pixel_lch)
    mut new_pixel_rgbf64 := oklab.oklab_to_linear_srgb(colorized_pixel_oklab)
    new_pixel_rgbf64 = oklab.gamut_clip_project_to_0_5(new_pixel_rgbf64)
    return RGBf64{
        r: new_pixel_rgbf64.r
        g: new_pixel_rgbf64.g
        b: new_pixel_rgbf64.b
    }
})

In other words, this function iterates over every pixel and performs these steps in this order:

  • [RGB] Adjust temp
    • Create 4500K RGB color using Tanner Helland's method
    • Multiply temp color by pixel
    • Blend temp color with pixel color by amount
  • [OKLAB] Convert RGB to OKLAB
  • [LCH] Convert OKLAB to LCH
  • [LCH] Adjust luminance by a factor of 1.2
  • [LCH] Adjust chroma by a factor of 2.5
  • [OKLAB] Convert LCH to OKLAB
  • [RGB] Clip colors to region valid for RGB

Results

I used an image that had a high dynamic range and would be really challenging to keep the details in the dar AND light regions

Before: BeforeRAW

Minor Corrections AfterIncreased Luminance and Chroma

Notice how the light in the trees is removing details.

After: AfterIncreased Luminance and Chroma with OKLAB Gamut Clipping

All thats needed now is a slight tweak to the red hue and some chromatic abberation compensation.

Lets take a more dramatic image to see the affects.

BeforeWithout OKLAB Gamut ClippingWith L0=0.5L_0=0.5
BeforeBeforeBefore

Next Steps

I want to experiment with the other gamut clipping algorithms. I might make this into an entire image processing application. Yea. That way I can stop using Adobe Lightroom, I wont hand over my image rights to them, and I wouldn't have to wait for startup times anymore.

This page is delivered to you from my garage.