Okhsv and Okhsl

#### Two new shade areas for shade selecting

This submit has an accompanying interactive comparability of shade pickers.

I’d suggest trying out the interactive demo first, then return in case you are within the background and technical particulars.

Choosing colours is a typical operation in lots of purposes and over time shade pickers have change into pretty standardized. Ubiquitous as we speak are shade pickers primarily based on HSL and HSV. They’re easy transformations of RGB values to various coordinates chosen to higher correlate with perceptual qualities.

Listed here are two widespread variants of shade pickers constructed on HSL and HSV:

A HSV shade picker.

A HSL shade picker.

Regardless of shade selecting taking part in a giant function in loads of purposes, the design of shade pickers isn’t a very properly researched subject. Whereas some variation exist within the widgets themselves, the selection of HSL or HSV is usually taken as a right, with only some exceptions.

Is their dominance properly deserved or would it not be attainable to create higher options? I no less than suppose that this query deserves to be explored and that shade picker design needs to be an lively analysis subject. With this submit I hope to contribute to the exploration of what a greater shade picker may and needs to be, and hopefully encourage others to do the identical!

The primary focus right here can be on the selection of shade area, quite than the design of the UI widget used for navigating the colour area.

This remainder of this submit is organized as observe:

- A short historical past of shade selecting
- What are significant properties of shade areas for shade selecting?
- What choices exist already and the way do they carry out?
- Introducing two new shade areas: Okhsl and Okhsv
- Concepts for future work

## Coloration selecting earlier than computer systems

Munsell Coloration System. Picture by Hannes Grobe, license CC BY-SA 4.0

Instance of NCS shade picker. Screenshot from NCS Navigator within the Colourpin app.

Categorizing, describing and selecting colours is an previous drawback and predates computer systems by many centuries. Through the years numerous artists and scientists have labored to know how people understand colours and used that information to try to create sensible techniques for describing colours. Many alternative shade ordering techniques have been created over time primarily based on mixing properties of paints, gentle or on perceptual qualities.

In the course of the twentieth century two essential shade techniques emerged. The Munsell Coloration System and the Pure Coloration System (NCS). Each of them are primarily based on human notion and had been derived utilizing experiments, however with totally different approaches. The 2 techniques are utilized in many sensible purposes as we speak nonetheless.

Within the Munsell shade system, colours are described with three parameters, designed to match the perceived look of colours: Hue, Chroma and Worth. The parameters are designed to be impartial and every have a uniform scale. This ends in a shade strong with an irregular form. Fashionable shade areas and fashions, equivalent to CIELAB, Cam16 and my very own Oklab, are very comparable of their building.

The Pure Coloration System takes a distinct strategy, and is designed to make it simple to explain colours, quite than to match perceptual qualities. It does this by describing colours by their similarity to 6 major colours: white, black, yellow, pink, inexperienced and blue. The yellow, pink, inexperienced and blue colours are used to find out the hue. The ultimate shade is described by a shade triangle with the corners white, black and probably the most saturated shade of the given hue. A place within the triangle is described with the parameters whiteness, blackness, chromaticness. Any two of these parameters are ample, since they sum to 1.

For extra details about historic shade techniques, it is a nice useful resource: colorsystem.com.

## What makes shade picker?

Earlier than diving into how shade pickers have advanced within the digital period, let’s look a bit additional at what issues might be related when designing a shade area for shade selecting. This half assumes familiarity with shade look ideas equivalent to lightness, chroma, saturation and hue.

On this submit the main focus can be on what’s as we speak the commonest case, selecting colours within the sRGB gamut. Broad gamut and HDR shows have gotten extra widespread and can be more and more essential, so vast gamut and HDR shade selecting is certainly a subject for additional analysis and growth, however it won’t be thought of right here.

Right here’s an try at capturing helpful properties for shade areas designed for choosing colours:

**Orthogonal Lightness**– Hue/Chroma/Saturation might be altered, whereas preserving perceived Lightness fixed**Orthogonal Chroma**– Lightness/Hue might be altered, whereas preserving perceived Chroma fixed**Orthogonal Saturation**– Lightness/Hue might be altered, whereas preserving perceived Saturation fixed**Orthogonal Hue**– Lightness/Chroma/Saturation might be altered, whereas preserving perceived Hue fixed**Easy Geometrical Form**– Match the goal gamut right into a cylinder or different easy form, in order that parameters might be altered independently with out leading to colours outdoors the goal gamut. May be a swept triangle like NCS, since it’s easy to map backwards and forwards to a cylinder.**Max Chroma at edge**– Make it simple to seek out the strongest shade of a given hue, by putting the strongest shade on fringe of the colour quantity.**Varies Easily**– Differ easily with every parameter. No discontinuous or abrupt modifications.**Varies Evenly**– The perceived magnitude of the change in shade brought on by altering a parameter needs to be uniform for all values of the parameter.

**Notice:** These properties are in battle, so designing a shade area for shade selecting is a about discovering which tradeoffs to make. Particularly, impartial management of hue, lightness and chroma can’t be achieved in a shade area that additionally maps sRGB to a easy geometrical form.

## Coloration areas for shade selecting

By far probably the most used shade areas as we speak for shade selecting are HSL and HSV, two representations launched within the traditional 1978 paper “Coloration Areas for Pc Graphics”. HSL and HSV designed to roughly correlate with perceptual shade properties whereas being quite simple and low-cost to compute.

Value noting is that HSL and HSV should not fairly shade areas on their very own, they’re transformations from a supply RGB shade area. For every set of RGB primaries and switch features, the transformation to HSL and HSV produces distinctive shade areas. Right now HSL and HSV are mostly used along with the sRGB shade area, so that’s what we’ll take a look at right here and we’ll right here use HSL and HSV to consult with HSL and HSV for the sRGB shade area.

Additionally helpful to notice is that HSL and HSV should not repeatedly differentiable, in order that limits their use with numerical optimization and machine studying.

### HSV

HSV describes colours with three parameters:

**“Hue”**– Roughly corresponds to perceived hue, however it has fairly extreme distortions.**“Saturation”**– Roughly corresponds to saturation relative to most attainable saturation in sRGB of the identical hue.**“Worth”**– A bit onerous to outline. Will be seen as how a lot to combine the colour with black, with 100% being no black and 0% utterly black. “Worth” is usually additionally known as Brightness.

HSV is kind of much like the Pure Coloration System in its construction and it’s attainable to remodel it to have parameters extra much like NCS, then known as hue, whiteness and blackness (HWB). After that transformation the biggest distinction in contrast with NCS are:

- NCS is derived primarily based on analysis into the looks of colours and does job at matching human notion
- HWB/HSV has a easy building, not taking analysis into shade look into consideration and isn’t matching notion intently. Hue is probably the most problematic.
- NCS has a gamut designed to comprise pigments realizable in paint/print
- HWB/HSV has a gamut primarily based on the RGB shade area it’s constructed from (mostly sRGB)

Instance of hue distortion for deep blue colours. Discover the purple shift as saturation decreases.

### HSL

HSV describes colours with three parameters:

**“Hue”**– Equivalent to “hue” in HSV, with the identical points.**“Saturation”**– Roughly the chroma of the colour relative to probably the most colourful shade with the identical “lightness” and “hue”. Confusingly known as saturation, which it isn’t corresponding to. Within the unique paper it was known as “relative chroma”, which is extra correct. Not the identical as “saturation” in HSV.**“Lightness”**– Some correlation with the notion of lightness, with 0% equivalent to black and 100% to white. Doesn’t match the notion of lightness properly in any respect for saturated colours. Known as “Depth” within the unique paper.

Instance of hue distortion for deep blue colours.

Instance of colours HSL considers to have the identical lightness.

### HSLuv

HSLuv is a current growth to deal with a number of the shortcomings of HSL. It’s primarily based on CIELChuv, a cylindrical type of 1976 CIE shade area CIELUV. CIELChuv is constructed in order that for a given hue, all colours of that hue might be constructed by additive mixing of white and a saturated shade of that hue (and usually, additive mixing of sunshine types straight strains in CIELuv).

HSLuv describes colours with three parameters:

**“Hue”**– Identical as hue in CIELChuv. Doesn’t match the notion of hue totally because of the Abney impact: the notion of hue doesn’t correspond to additive mixing.**“Saturation”**– Based mostly on chroma as outlined in CIELChuv, however rescaled to be relative to probably the most saturated sRGB shade of the identical “lightness” and “hue”.**“Lightness”**– Identical as lightness in CIELChuv. Does job at matching perceived lightness.

Two downside with HSLuv are:

- Doesn’t match notion of hue. That is notably apparent for deep blue and purple colours.
- The way in which “Saturation” is outlined, it doesn’t fluctuate easily because of the uneven form of the sRGB gamut. E.g. by preserving “Saturation” fixed and altering hue, the perceived chroma can change drastically and abruptly.

Instance of hue distortion for blue colours. The distortion in HSLuv is totally different from that in sRGB and is brought on by the Abney impact.

Instance of fixed lightness in HSLuv, with low “saturation” near the middle of the circle and most “saturation” on the edge. Discover how the blue and pink hues are far more saturated than surrounding colours.

Slice of colours with fixed “saturation” in HSLuv. The scaling to match the uneven form of the sRGB gamut makes the perceived chroma fluctuate erratically.

### Coloration areas modelling shade look

Whereas there’s a restricted quantity of analysis achieved concerning shade selecting, loads of work has been achieved to create shade fashions which might be capable of predict shade look. These proceed within the custom of the Munsell shade mannequin mentioned above, however use extra fashionable shade science and mathematical fashions to higher mannequin the looks of shade. Some of the well-known of those is CIELab, however there are as we speak a number of new fashions that carry out higher.

Evaluating all the colour fashions is past the scope of this submit, the essential conclusion right here is that these fashions can mannequin the notion of Lightness, Hue and Chroma significantly better than all of the beforehand mentioned choices. For a short overview of a number of the newer fashions, see my earlier submit “A perceptual shade area for picture processing”. Since then one other shade mannequin has additionally appeared: ZCAM. For a a lot deeper overview of contemporary shade science and totally different makes an attempt at modeling shade look, I like to recommend the e book “Coloration Look Fashions” by Mark D. Fairchild.

The primary downside of utilizing these fashions immediately for shade selecting is that the sRGB gamut has a fairly irregular form in these shade areas. Consequently, altering one parameter, equivalent to hue, can simply create a shade outdoors the goal gamut, making them fairly tedious to make use of. A number of shade pickers have been made utilizing both CIELab or extra fashionable lab-like shade areas. From what I can inform they’ve solely seen restricted use in contrast with the extra widespread HSV and HSL shade pickers nonetheless.

I’d suppose that the explanation that they haven’t caught on is that their drawbacks outweigh their advantages: utilizing an area with parameters that don’t match the our notion of hue, lightness and chroma is less complicated than utilizing one with an irregular form. That’s actually my private expertise.

For the extra superior fashions an extra complication is that they’ve a number of parameters meant to be adjusted primarily based on the viewing circumstances. When used for shade selecting they appear to principally be set to match some type of common viewing circumstances although.

Listed here are a few examples of the irregular form of the sRGB gamut in a perceptual shade area:

A slice of the gamut with a relentless blue hue.

A slice of the gamut with a relentless yellow hue.

It’s sadly additionally widespread to see CIELab primarily based shade pickers exhibiting colours outdoors the goal gamut and infrequently they’re mapped again by merely clamping particular person RGB parts. This creates extreme distortions in hue, lightness and chroma, in would would in any other case be a reasonably uniform shade area.

### Abstract

These are the colour areas I’m conscious of which might be related, however please attain out in case you are conscious of any extra shade areas helpful for shade selecting.

To summarize, here’s a desk of the totally different shade areas mentioned and the way they match the totally different fascinating properties. That is positively a bit subjective, however will hopefully give a good overview.

HSV | HSL | HSLuv | Lab-like* | NCS | |
---|---|---|---|---|---|

Orthogonal Lightness | no | no | sure | sure | no |

Orthogonal Chroma | no | no | no | sure | partial |

Orthogonal Saturation | partial | no | no | no** | no |

Orthogonal Hue | partial | partial | partial | sure | sure |

Easy Geometrical Form | sure | sure | sure | no | no*** |

Max Chroma at Edge | sure | no | no | sure | no |

Varies Easily | sure | sure | no | sure | sure |

Varies Evenly | no | no | no | sure | partial |

*) This in fact is dependent upon which Lab-like shade area. That is the very best an look modelling shade area may obtain.**) If fascinating, saturation can be utilized as an alternative of Chroma, after which this is able to be a sure and “Orthogonal Chroma” a no.***) NCS has a easy geometrical form, however it doesn’t match the sRGB gamut.

## Discovering a greater tradeoff

One of many principal benefits of HSL and HSV over the totally different Lab shade areas is that they map the sRGB gamut to a cylinder. This makes them simple to make use of since all parameters might be modified independently, with out the chance of making colours outdoors of the goal gamut.

The primary downside alternatively is that their properties don’t match human notion notably properly. Reconciling these conflicting objectives completely isn’t attainable, however on condition that HSV and HSL don’t use something derived from experiments referring to human notion, creating one thing that makes a greater tradeoff doesn’t appear unreasonable.

We’ll try to do exactly that by creating new shade areas much like HSL and HSV however that higher match notion. This can be achieved by leveraging the Oklab shade area. Utilizing Oklab right here over extra superior fashions equivalent to CAM16 is beneficial as a result of understanding the mathematics turns into quite a bit less complicated. It additionally signifies that it gained’t be a full shade mannequin capable of adapt to totally different viewing circumstances, however that’s in all probability additionally fascinating right here since it’s extra sensible.

For consistency with the naming of Oklab, these new shade areas can be referred to as Okhsl and Okhsv. The parameters may also be known as

$h$$s$

$l$

$h$

$s$

$v$

## Intermission – a brand new lightness estimate for Oklab

One design resolution with Oklab is to make use of a design that’s scale impartial. That’s, Oklab has no idea of reference white, not like CIELab for instance. In lots of circumstances this is a bonus, because it makes coping with bigger dynamic ranges simpler.

Nonetheless, within the context of a shade picker with properly outlined dynamic vary and a transparent reference white luminance it reduces Oklab’s potential to foretell lightness. Subsequently, an extra lightness estimate is required to higher deal with these circumstances. With a reference white luminance of

$Y=1$$L_r$

$k_1 = 0.206 ,qquad k_2 = 0.03 ,qquad k_3 = frac{1+k_1}{1+k_2}$

$L_r = frac{k_3 L – k_1 + sqrt{(k_3 L – k_1)^2 + 4 k_2 k_3 L}} 2$

With the inversion:

$L = frac{L_r (L_r + k_1)}{k_3 (L_r + k_2)}$

This new lightness estimate intently matches the lightness estimate of CIELab general and is almost equal at 50% lightness (Y for CIELab L is 0.18406, and

$L_r$From prime to backside: CIELab

$L$$L_r$

$L$

## Introducing two new shade areas: Okhsv and Okhsl

With this new lightness estimate, we’re able to look into the development of Okhsv and Okhsl. Here’s a tough overview of the final thought behind Okhsv and Okhsl and their building. Some particulars are glossed over right here, for all the small print try the supply code beneath.

### Okhsv

To derive Okhsv, we’ll begin with OkLCh, use its estimate for hue,

$h$$s$

$v$

$L_r$

$C$

$L_r$

$C$

$L_r/L$

Right here is the sRGB gamut plotted for set of hues, with

$L_r$$C L_r/L$

To create a HSV-like shade area, we wish to discover a mapping in order that the cusp of the triangle is in

$s=1$$v=1$

To search out the cusp we are able to use the identical methodology as in my earlier submit about sRGB gamut clipping.

If we carry out this remapping we get the next end result:

Remaining now could be a small curve on the prime, that we additionally should take away. That is achieved by scaling

$v$As an extra step we alter saturation to be extra uniform for low saturation colours. This makes it simpler to match saturation values for various colours, when saturation is low. The impact of that is refined.

This offers us a brand new mannequin with a easy geometrical form and a hue parameter that intently matches notion. Total the area can be very acquainted to somebody who’s used to HSV, however with improved perceptual uniformity.

#### Okhwb

If desired, Okhsv will also be transformed to a HWB (hue, whiteness and blackness) kind.

$w = (1-s) v$

$b = 1-v$

With the inverse:

$s = 1-frac{w}{1-b}$

$v = 1-b$

### Okhsl

To derive Okhsl we additionally begin with OkLCh.

$L_r$$h$

$L_r$

$l$

For

$s$$C$

The only means to do that is to easily scale it by the utmost chroma contained in the sRGB gamut for a given worth of

$l$$h$

$C_{max}(h, l)$

$s$

As a substitute it could be good if we may discover a technique to maintain the unevenness native to colours near the sting of the gamut, leaving the inside much less affected. That is the important thing thought behind Okhsl.

One technique to remedy this is able to be to unravel it as a boundary worth drawback, discovering

$C = f(h, s, l)$$C = C_{max}(h, l)$

As a substitute Okhsl makes use of a reasonably ad-hoc strategy to create a easily various inside, since that makes it environment friendly to run and straightforward to invert.

As a substitute of scaling

$s$$C$

$s$

$C_0$

$s$

$C_{mid}$

$C_{max}$

- $C_0(l)$
- $C_{mid}(h, l)$
- $C_{max}(h, l)$

To get an understanding of

$C_0$$C_{mid}$

$C_{max}$

$C$

$C = s C_0$

$C = s C_{mid}$

$C = s C_{max}$

To create the total Okhsl mannequin, the values are interpolated in order that:

- At $s=0$
- At $s=0.8$
- At $s=1.0$

This offers the ultimate Okhsl mannequin:

Altogether this provides a mannequin with a easy geometrical form that has parameters for lightness and hue that intently match notion. The mannequin is kind of totally different from common HSL, as a way to obtain a greater lightness estimate. I consider Okhsl delivers a greater general compromise, and retains most of the advantages of Lab-like shade areas, with out the complexity of an irregular form.

Listed here are just a few examples of slices Okhsl, with fixed lightness and saturation:

Instance of fixed “lightness” in Okhsl.

Slice of colours with fixed “saturation” in Okhsl. Whereas not matching perceived chroma totally it’s easily various.

For 100% ‘saturation’ the variation in perceived chroma is bigger, because of the form of the sRGB gamut.

### Abstract

For completeness, here’s a desk of how Okhsv and Okhsl match the specified properties from earlier. Once more, that is positively a bit subjective. A greater technique to choose the efficiency is to only attempt the outcomes your self.

Okhsv | Okhsl | |
---|---|---|

Orthogonal Lightness | no | sure |

Orthogonal Chroma | no | no |

Orthogonal Saturation | partial | no |

Orthogonal Hue | sure | sure |

Easy Geometrical Form | sure | sure |

Max Chroma at Edge | sure | no |

Varies Easily | sure | sure |

Varies Evenly | no | no |

## Concepts for future work

Okhsv and Okhsl are my makes an attempt at making higher shade pickers for the sRGB gamut. I’d like to see extra experimentation general with shade picker design and within the subsequent few years, shade pickers for vast gamut and HDR can be increasingly more essential and want loads of analysis. They each supply their very own new challenges.

Broad gamut is difficult since we’re seeing an elevated number of totally different gamuts. Not less than for some time, goal shade areas can be far more assorted and purposes for authoring colours should both accept widespread subset or should take care of this complexity. This in fact can have a big effect on what shade pickers appear to be and the way they behave.

One fascinating avenue to pursue can be to extra routinely create shade areas like Okhsv and Okhsl for a given shade gamut. This could probably want to make use of a little bit of a distinct strategy, perhaps utilizing lookup tables and numerical options as a way to not want as a lot handcrafted logic.

HDR additionally has the problem of not being fairly standardized, however an added complexity is the elevated dynamic vary and variation is absolute brightness. Prior to now shade pickers have been capable of principally ignore how the attention adapts to totally different luminance ranges, however this doesn’t work as properly with HDR. Up to now the approaches I’ve seen are to make use of common SDR shade pickers, however with and added publicity/depth management. Is that this the perfect strategy or are there new methods we needs to be working with HDR shade pickers?

An extra factor to discover is what spacing of hues can be the perfect. Okhsl and Okhsv merely inherit their spacing from Oklab. A unique possibility could possibly be to do a remapping much like NCS, which might make the parameter fluctuate much less evenly, however may make it simpler to make use of by mapping the totally different axes to extra acquainted colours.

## Supply Code

Right here is the Supply Code for conversion between sRGB, HSL and HSV. This code is dependent upon the code from my earlier submit sRGB gamut clipping, which isn’t included right here. You will discover the supply for each posts mixed right here as a C++ header.

The interactive comparability of shade pickers additionally has an implementation of this in JavaScript. The supply is obtainable right here.

### License

All of the supply code on this web page is offered below the MIT license:

```
Copyright (c) 2021 Björn Ottosson
Permission is hereby granted, freed from cost, to any particular person acquiring a replica of
this software program and related documentation information (the "Software program"), to deal in
the Software program with out restriction, together with with out limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or promote copies
of the Software program, and to allow individuals to whom the Software program is furnished to do
so, topic to the next circumstances:
The above copyright discover and this permission discover shall be included in all
copies or substantial parts of the Software program.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

### Frequent code

`struct HSV { float h; float s; float v; };`

struct HSL { float h; float s; float l; };

struct LC { float L; float C; };

struct ST { float S; float T; };

float toe(float x)

{

constexpr float k_1 = 0.206f;

constexpr float k_2 = 0.03f;

constexpr float k_3 = (1.f + k_1) / (1.f + k_2);

return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));

}

float toe_inv(float x)

{

constexpr float k_1 = 0.206f;

constexpr float k_2 = 0.03f;

constexpr float k_3 = (1.f + k_1) / (1.f + k_2);

return (x * x + k_1 * x) / (k_3 * (x + k_2));

}

ST to_ST(LC cusp)

{

float L = cusp.L;

float C = cusp.C;

return { C / L, C / (1 - L) };

}

### HSV

This code converts between sRGB (not linear) and Okhsv.

`struct HSV { float h; float s; float v; };`

RGB okhsv_to_srgb(HSV hsv)

{

float h = hsv.h;

float s = hsv.s;

float v = hsv.v;float a_ = cosf(2.f * pi * h);

float b_ = sinf(2.f * pi * h);

LC cusp = find_cusp(a_, b_);

ST ST_max = to_ST(cusp);

float S_max = ST_max.S;

float T_max = ST_max.T;

float S_0 = 0.5f;

float ok = 1 - S_0 / S_max;

float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * ok * s);

float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * ok * s);

float L = v * L_v;

float C = v * C_v;

float L_vt = toe_inv(L_v);

float C_vt = C_v * L_vt / L_v;

float L_new = toe_inv(L);

C = C * L_new / L;

L = L_new;

RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });

float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));

L = L * scale_L;

C = C * scale_L;

RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });

return {

srgb_transfer_function(rgb.r),

srgb_transfer_function(rgb.g),

srgb_transfer_function(rgb.b),

};

}

HSV srgb_to_okhsv(RGB rgb)

{

Lab lab = linear_srgb_to_oklab({

srgb_transfer_function_inv(rgb.r),

srgb_transfer_function_inv(rgb.g),

srgb_transfer_function_inv(rgb.b)

});

float C = sqrtf(lab.a * lab.a + lab.b * lab.b);

float a_ = lab.a / C;

float b_ = lab.b / C;

float L = lab.L;

float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;

LC cusp = find_cusp(a_, b_);

ST ST_max = to_ST(cusp);

float S_max = ST_max.S;

float T_max = ST_max.T;

float S_0 = 0.5f;

float ok = 1 - S_0 / S_max;

float t = T_max / (C + L * T_max);

float L_v = t * L;

float C_v = t * C;

float L_vt = toe_inv(L_v);

float C_vt = C_v * L_vt / L_v;

RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });

float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));

L = L / scale_L;

C = C / scale_L;

C = C * toe(L) / L;

L = toe(L);

float v = L / L_v;

float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * ok * C_v);

return { h, s, v };

}

### HSL

This code converts between sRGB (not linear) and Okhsl.

`struct HSL { float h; float s; float l; };`

ST get_ST_mid(float a_, float b_)

{

float S = 0.11516993f + 1.f / (

+7.44778970f + 4.15901240f * b_

+ a_ * (-2.19557347f + 1.75198401f * b_

+ a_ * (-2.13704948f - 10.02301043f * b_

+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_

)))

);

float T = 0.11239642f + 1.f / (

+1.61320320f - 0.68124379f * b_

+ a_ * (+0.40370612f + 0.90148123f * b_

+ a_ * (-0.27087943f + 0.61223990f * b_

+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_

)))

);

return { S, T };

}

struct Cs { float C_0; float C_mid; float C_max; };

Cs get_Cs(float L, float a_, float b_)

{

LC cusp = find_cusp(a_, b_);

float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);

ST ST_max = to_ST(cusp);

float ok = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);

float C_mid;

{

ST ST_mid = get_ST_mid(a_, b_);

float C_a = L * ST_mid.S;

float C_b = (1.f - L) * ST_mid.T;

C_mid = 0.9f * ok * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));

}

float C_0;

{

float C_a = L * 0.4f;

float C_b = (1.f - L) * 0.8f;

C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));

}

return { C_0, C_mid, C_max };

}

RGB okhsl_to_srgb(HSL hsl)

{

float h = hsl.h;

float s = hsl.s;

float l = hsl.l;

if (l == 1.0f)

{

return { 1.f, 1.f, 1.f };

}

else if (l == 0.f)

{

return { 0.f, 0.f, 0.f };

}

float a_ = cosf(2.f * pi * h);

float b_ = sinf(2.f * pi * h);

float L = toe_inv(l);

Cs cs = get_Cs(L, a_, b_);

float C_0 = cs.C_0;

float C_mid = cs.C_mid;

float C_max = cs.C_max;

float mid = 0.8f;

float mid_inv = 1.25f;

float C, t, k_0, k_1, k_2;

if (s < mid)

{

t = mid_inv * s;

k_1 = mid * C_0;

k_2 = (1.f - k_1 / C_mid);

C = t * k_1 / (1.f - k_2 * t);

}

else

{

t = (s - mid)/ (1 - mid);

k_0 = C_mid;

k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;

k_2 = (1.f - (k_1) / (C_max - C_mid));

C = k_0 + t * k_1 / (1.f - k_2 * t);

}

RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });

return {

srgb_transfer_function(rgb.r),

srgb_transfer_function(rgb.g),

srgb_transfer_function(rgb.b),

};

}

HSL srgb_to_okhsl(RGB rgb)

{

Lab lab = linear_srgb_to_oklab({

srgb_transfer_function_inv(rgb.r),

srgb_transfer_function_inv(rgb.g),

srgb_transfer_function_inv(rgb.b)

});

float C = sqrtf(lab.a * lab.a + lab.b * lab.b);

float a_ = lab.a / C;

float b_ = lab.b / C;

float L = lab.L;

float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;

Cs cs = get_Cs(L, a_, b_);

float C_0 = cs.C_0;

float C_mid = cs.C_mid;

float C_max = cs.C_max;

float mid = 0.8f;

float mid_inv = 1.25f;

float s;

if (C < C_mid)

{

float k_1 = mid * C_0;

float k_2 = (1.f - k_1 / C_mid);

float t = C / (k_1 + k_2 * C);

s = t * mid;

}

else

{

float k_0 = C_mid;

float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;

float k_2 = (1.f - (k_1) / (C_max - C_mid));

float t = (C - k_0) / (k_1 + k_2 * (C - k_0));

s = mid + (1.f - mid) * t;

}

float l = toe(L);

return { h, s, l };

}

Should you preferred this text, it could be nice if you happen to thought of sharing it:

For discussions and suggestions, ping me on Twitter.

Printed

#shade #areas #shade #selecting