Back to Articles

2025-12-31

Vibrancy vs Saturation

By Anthony Dito

Learn how the saturation and vibrancy tools are built in BrushCue.

Vibrancy AdjustmentSaturation Adjustment
Vibrancy AnimationSaturation animation

First, Understanding OkLab

On BrushCue, we do our saturation and vibrancy adjustment using OkLab color. This is a perceptual color space — meaning it is designed to store colors similarly to how humans visualize them. This is opposed to RGB colors which stores color as how they will be used on the display. The R, G and B control how much red, green and blue light comes out from each pixel on the display.

Mathematical distances between R, G, B do not correspond to distance in how far a color is perceived to be from another. This is what OkLab solves for and why we use it for many color adjustments.

OkLab has two parts. There is an L component which specifies the lightness of the color. There are A and B components that specifies the hue and chroma of the color. You can think of the A and B as plotting a point on a graph and the angle of that point being the hue of the color.

The A and B components of the OkLab color can be visualized with the interactive component below. The key point — colors toward the center are more muted than colors toward the edge.

OkLab a/b color wheel

Hover to sample A and B values at a fixed L = 0.72.

A
B
Hover the wheel

How saturation works

OkLab cleanly separates the color (A and B) from the lightness (L). To modify the saturation, we increase or decrease the distance of the A and B components are from the origin. This is straightforward, multiply the A and B components by a scale. The code for how we do this is below.

fn perform_saturation_adjustment(input: vec4<f32>, scale: f32) -> vec4<f32> {
    let l = input.x;
    let a = input.y;
    let b = input.z;
    let alpha = input.w;
    return vec4<f32>(l, a * scale, b * scale, input.w);
}

How vibrancy works

Instead of uniformly increasing the A and B of a color like we do for saturation, vibrancy changes are subtler. Vibrancy changes selectively increase more muted colors (color closer to the origin) at a higher rate than more vibrant colors (colors further from the origin). Doing this allows for more natural-looking color enhancements. The code for how we do this is shared below. We calculate the saturation of the image, flip it so we have a value closer to 1 for muted colors and a value closer to 0 for saturated colors, apply the boost to the chroma and then recompute the A and B components.

fn perform_vibrancy_adjustment(input: vec4<f32>, vibrance_boost: f32) -> vec4<f32> {
    let l = input.x;
    let a = input.y;
    let b = input.z;
    let alpha = input.w;

    // measure the current saturation
    let chroma = sqrt(a * a + b * b); 

    // calculates a factor to scale by. chromas close to 0 get 1, chromas close to the max get 0.
    let saturation_factor = 1.0 - pow(chroma, 0.5);

    // apply the boost by multiplying the chroma by the boost scaled boost amount.
    let adaptive_boost = mix(vibrance_boost, 1.0, saturation_factor);
    let boosted_chroma = chroma * adaptive_boost;

    // recalculate the A and B
    let hue = atan2(b, a);
    let new_a = cos(hue) * boosted_chroma;
    let new_b = sin(hue) * boosted_chroma;

    return vec4<f32>(l, new_a, new_b, alpha);
}

Compared

As you can see in the above images, the vibrancy is a subtler, more natural effect. We boost the more muted colors while leaving the vibrant colors mostly unchanged. When you want realistic enhancements to the colors of your images, vibrance modifications are typically better.

This doesn't mean that saturation adjustments are always inferior. In practice, it rarely makes sense to reduce the vibrancy of an image. Doing so makes muted colors even more muted. A better way to make an image more muted would be to perform a saturation adjustment with a value less than 1. Additionally, saturation adjustments are uniform across the color range, which is an advantage in some cases—particularly when correcting an error in the original image.