Hacker News new | past | comments | ask | show | jobs | submit login
New alternatives to HSL and HSV that better match color perception (bottosson.github.io)
398 points by bjornornorn 12 days ago | hide | past | favorite | 78 comments

Good related reading: https://raphlinus.github.io/color/2021/01/18/oklab-critique...., with some analysis of various colour spaces of different types, focusing especially on their behaviour in gradients, to do with interpolation.

My biggest complaint about perceptual colour spaces has been that they make working with colours near gamut boundaries really hard. For example, with #ff0 (yellow) in LCH, you can’t tweak any of the three parameters in either direction without going out of gamut (play with it at https://css.land/lch/ to get a vague idea of what I mean, though it doesn’t handle going out of gamut well). Yet such colours have plenty of practical value, and it’s a shame that many things eschew them because they’ve drunk too much perceptual uniformity kool-aid. Colour palettes often just don’t have any decent yellows at all. (To be sure, you do need to use such colours with care, but to remove them altogether is distressing.) For colour pickers especially, perceptual colour spaces have just been no good, because of their weird shapes and because you’re more likely to want to pick interesting colours nearer gamut bounds.

So I’m really glad to see this, because it’s just what I’ve been grumbling about the absence of. Thank you Björn, I continue to enjoy your work!

My biggest complaint about perceptual colour spaces has been that they make working with colours near gamut boundaries really hard.

I agree, this can be awkward. I like using perceptual spaces because they are self-evidently a “neater” starting point for colour work than something artificially distorted like HSL or (horror!) RGB, but it’s important to recognise that they offer an incremental improvement and don’t magically solve all problems. In particular, producing a useful and/or aesthetically pleasing result still isn’t necessarily as simple as varying one axis in isolation, because you can quickly fall outside the available gamut that way.

As a practical example of this, I recently needed a scheme for a variation of syntax highlighting in a structured document. My requirements were:

1. around 10 clearly distinct hues

2. all colours maintaining good contrast with a white background

3. no colour appearing too bright relative to the near-black unhighlighted text

4. all colours at a similar level of perceived lightness to avoid unintentional bias or emphasis.

My starting point was to choose evenly spaced hues within a perceptually uniform colour space, pick a medium-to-low value and look for maximum chroma in each case. This wasn’t a bad start, but it still needed significant practical adjustments like increasing the value for colours around the yellow/green area and dropping a hue entirely so I could space others around the blue/purple area more widely. Otherwise, however perceptually uniform the scheme might theoretically have been, in practice it would have ended up with the yellow/green area colours looking washed out and the blue/purple ones not distinctive enough under some realistic viewing conditions.

I played around with a perceptual color space and made an interactive doodle that you can see here: https://hexpansion.io/ → click on "Colors".

The blobs attempt to spread out in a perceptual color space. You can add more blobs with + and remove them by clicking on their pie slices or on a blob directly.

Code is based on an older perceptual color space: https://github.com/neolefty/hexerals/tree/master/src/color

Very interesting, but I had a question about the background color on the page. but first some ... background. A long time ago, I worked at a digital printer manufacturer and I was building a equal perceptual gradient target for a backlit transparency material, Ilfochrome, at a customer site. One thing I found interesting was how much the perception of how 'linear' the gradient looked depended on how bright the background was.

A 'target' was a set of densities we would calibrate the machine to reach for a particular RGB value. 0,0,0 = 3.4D and 255,255,255 = .04D (for transparent material).

One group in the company insisted on a simple cube root density drop off, based on a reading of the L* formula. However, when the customer printed a large gradient with it, they rejected it. It looked like the 'linear' gradient on your site. However, when we put the test pattern (a small 4x8") pattern on the light board, it looked perfect.

So we tried an experiment, we printed 3 gradients on a full sized sheet, (50 inchesx50 inches), one with a 'clear' background, one with a 50% grey and one with a black background.

I was in the field, so I found a gradient that looked good, but did a bunch of study when I got back, I felt that some of the newer CIE models at the time did take into account background.

Tldr; I was curious is your page and those models included into its gradient calculation what the surround is? Also, it would be interesting to display these with an adjustable background. (dark, light, grey), or with colors.

FYI the + button doesn't work for me on Android Fennec (Firefox) 91.2.0 (Build #912020), though the rest seems to

I created a tool that could help you on creating this type of palettes. It's based on CIELAB and you can see where all the colors are placed in the lightness, chroma and hue space. You can also see they boundaries for any family color so you know how much you can stretch chroma without shifting lightness or hue.


IMO perceptual uniformity isn't even necessarily desirable in all (or even most) cases.

The only time I think it's really necessary is making color palettes for data visualizations.

Otherwise, if you're e.g. designing a color palette for a webpage, you probably do want some variation in perceived brightness. Heck, I could think of plenty of valid data visualization scenarios where you'd want this.

I want to be able to have variation in perceived brightness, but not as a side effect while browsing for a hue. I doubt most designers would argue otherwise.

Compare it to waiting for a compiler: Sure, you can use the time off to stretch and take a sip of tea but there would be no serious opposition to compilers working at double speed tomorrow. It's simply better to not have to wait (all else being equal). But it's something you accepted and you deal with it, so wait you will.

The current color pickers are an annoyance that we have lived with since, well, always, but an annoyance nonetheless. Happy to adopt these pickers if they gain adoption. OKHSL and OKHSV look spiffy.

Another application that might not be as rare as one would like to think is color-interpolation. Interpolating colors in a way that doesn't make them perceptually brighter or darker is hard.

As someone who deals with color pickers a lot, it is just nice to have multiple options. Sometimes I just wanna punch in a hex number, sometimes I want a SV+H square, sometimes HS+V works better, etc.

I see value in having a color picker that allows you to pick colors without changing brightness. E.g. imagine you let users pick colors that should work on a background that you designed. giving them a color wheel that stays perceptually at the same brightness has value here, because no matter which color they choose the brightness-contrast on your background will work.

I worked on software that had the ability to change the hue of an object. If you had a bright blue car and wanted it to be yellow, changing the hue to yellow worked perfectly. Then the perceptually correct zealots took over and mandated that all color manipulations would take place in the Lab color space. Now the intense blue 0,0,255 didn't turn into an intense yellow 255,255,0, it turned into what was considered a yellow of the same lightness - 31,31,0. This was too dark to be useful to anybody, and there was no way to compensate. Worse yet it wasn't even correct, because the lightness isn't accurate for intense saturated colors - if you look at 0,0,255 and 31,31,0 side by side you'll see what I mean, they're not even close to the same perceptual lightness.

Wow, you’re not kidding about the LCH picker working poorly near the gamut boundary. Changing hue is especially poorly behaved.

Hint to people trying to reproduce this: start by clicking the input button at the top and typing #ff0

> you can’t tweak any of the three parameters in either direction without going out of gamut

This is a good thing: it teaches you the shape of the gamut, and gets you working with colors consistently.

#FFFF0O does not have “plenty of practical value”. It is a very niche tool with an extremely small range of uses, and should be avoided almost all of the time.

> For colour pickers especially, perceptual colour spaces have just been no good,

Just the opposite: for color pickers especially, using anything else makes it much much harder to get good results.

Perceptual colour spaces are basically great if you want to pick an exact colour for the mud you are drawing.

They really, really aren't good for picking useful colours. People don't keep using HSV because they are idiots, and have remained idiots for the last thirty years, they are using HSV because it lets you pick usable colours.

The article goes through a lot of deep, thoughtful work only to arrive at an end result of this: https://bottosson.github.io/img/colorpicker/okhsl_circle.png

A circle of different muds and a little bit of hot pink.

I think the real story is subjective, not quantitative. Color pickers:


OKHSL works a lot better than the alternatives. It's really nice!

OKHSV doesn't seem as good, and neither do the other new ones.

And speaking subjectively, I have always preferred HSV because I came to color from the background of mixing pigments (painting). Adding white to a pigment to desaturate it (make it more pastel) is intuitive with paints ... as is adding black to darken a pigment.

HSV (and Okhsv) more closely match my painterly intuition.

HSV and not HSL for that mental model? That's surprising to me—I thought the tint/shade system would lead to wanting to put max-chroma in the middle.

Oh that really helped me understand this whole thing.

My personal complaint with color pickers is that they make it hard to find many normal environmental colors. Where is brown? I've learned to find it around red, a little toward orange, and a dark saturation. It's not intuitive, and I don't feel like I just pick _some_ brown because I can't easily explore the space of browns through beige and tan. Flesh tones are similarly difficult.

I'm not sure what the solution is. A purely algorithmic color picker probably isn't the answer because it's not just our color perception that matters, but also the way colors are formed in our environment (which is what gives us so many browns).

Brown is weird [1]. It's basically dark orange. Flesh tones fall into that same category - people are orange-hued [2]. Oranges/reds are mostly produced by various unsaturated molecules, such as carotenes, lignins, phenols, and tars, and browns are just dark collections of many kinds red-orange pigments. It does not map well to any of the common color spaces because its inherently heterogeneous.

The solution would probably be pallettes that let you pick arbitrary axes to move through color space.

1 - https://youtu.be/wh4aWZRtTwU

2 - According to Neil Harbisson, the guy with the color-sensing antenna, in-person conversation, but you can also verify with photoshop.

I don’t understand that video. Can’t all colours be described differentially to another colour? And don’t we have lots of names for different shades? Why does this make brown unusual?

I think what makes brown unique in this regard is its ubiquity in nature, partial desaturation, and lack of obvious mapping to a saturated color.

Emergence of names of colors in languages usually goes black, white, red, yellow, grue, then blue forks from blue, brown, orange, then "specialty" colors like pink, purple, indigo, teal. It's the only partially saturated shade that gets a name before its saturated variant (orange). Being able to name things brown is more important than orange, so that mental link is weaker. Hence why we have "dark blue" but brown is not "dark orange." (though I think some languages call it "dark red"? I'm a bit hazy on that detail)


> lack of obvious mapping to a saturated color

30 degrees hue, 50% value, 100% saturated, looks exactly brown to me?

> brown is not "dark orange."

I think it is. In fact, take my 30 degrees hue, 50% value, 100% saturated, then gradually increase the value and see where you get to!

I don't think the other commenter is disagreeing with you on those things -- they're talking about human perceptions, and saying that although brown is in fact dark orange, that's not how we name it or how we tend to see it.

Just out of curiosity, what if you go to a significantly darker brown, say 30/100/20 h/s/v. Unfortunately these subjective tests will vary from monitor to monitor, but assuming that's still clearly brown rather than black to you (to me it's a more archetypal brown than the brighter version you initially named, which I might call orange-brown) -- what do you perceive when you keep the saturation and value fixed, and vary the hue? Would you agree that many or most of the other hues give you a colour more obviously 'dark X', where X is the 100%-value counterpart, than hue 30 does? For me it's much more obvious at the green-blue-purple segment of the spectrum than the red-orange-yellow segment.

This isn't meant as a trick question or anything -- if I'm getting at anything specific, it's that maybe part of the reason the orange-brown link seems more obvious to you than to me is that when you think of a central example of brown, you think of something brighter (and thus more obviously orange) than I do.

> Would you agree that many or most of the other hues give you a colour more obviously 'dark X'

Hmm not really. But we're not going to resolve what seems like a subjective question!

Oh don't worry, I wasn't planning on trying to argue the point! Just genuinely curious about the extent to which our difference is perceptual/conceptual/other. If we disagree on the 'dark X' thing, it makes me think there's a real perceptual difference and/or a pretty deep conceptual one (in terms of how we divide up the colour spectrum), though of course it doesn't prove anything.

In English, pink and brown are very specific colors which the brains of native speakers might not associate with their true hue values.

That's why the original commenter was having difficulty finding brown, because we do not necessarily associate it with a dark orange or flesh color with de-saturated red or orange.

But if we were trying to paint something like dark teal water. The brain would immediately go straight to blues/greens.

These "color categories" that we form in our brains can be different in every culture or language. That is issue with what was suggested.

> That's why you were having difficulty finding brown

Not sure if you’re replying to the right person? I didn’t say anything about finding brown being difficult.

I don't even know what that would mean for it to be difficult to find a colour?

> because you do not necessarily associate it with a dark orange

That’s what brown is - a dark orange. The same way navy is a dark blue. But nobody makes videos claiming that navy is a weird colour because it’s actually dark blue.

It sounds like the brown-orange link is intuitively obvious to you. But I think the claim being made is that for most native English-speakers, the link between orange and brown is significantly less obvious than that between X and dark X, for most colours X. FWIW that is true in my case.

(Not that I deny the link exists; but when I look at e.g. an orange next to a dark brown tree branch, I don't see them as versions of the same colour in the way I do with, say, a lime and a dark green leaf. That's not a great example but hopefully you see what I'm getting at.)

This seems like a simple case of different people finding different things unintuitive.

My theory is that many people have a mental model of colors that maps roughly to the hue and value in HSV, so for example terms like "dark orange" is a value followed by a hue. Now suppose you understand brown as a "dark beige", that's suddenly confusing because beige introduces saturation and you cannot map beige back to orange without thinking about saturation.

> Now suppose you understand brown as a "dark beige"

I think my point is brown is dark beige, and beige is light brown. Neither is canonical. Is orange canonical? Maybe it's high frequency red? Or low frequency yellow? That's what Newton thought! It's all relative.

Some color pickers allow you to load a palette of names and they locate those names and values in the color picker interface.

You can load common name sets (e.g. X11 color names, CSS names, Munsell names, Behr) and they map their values into your color pickers.

This works well especially with paint pigments as every manufacturer has their exotic names that they hype for that year. "Oh, 'Ice Blue' is really just a slightly blueish white."

Looking at the author's explanation for HSV and HSL, it sounds like they are designed for hypothetical programmable wavelength lights and paints. Hue would be wavelength, Saturation is either output filter bandwidth or conversion element efficiency, Value/Lightness is either output current or amount of ink.

RGB is likewise for fixed wavelength three-color lights/filters, and CMYK is for prints. I think it's the same situation as units of measurements in aviation[1] being all over the places; the measurements are tied to specific devices, principles and usages of them.

1: Altitude in feet measured by pressure altimeters, distances measured in nautical miles defined as one minute of latitude, fuel in pounds and dimensions in inches, but never altitudes in miles or distances in feet, except for runway lengths which is always feet or meters.

> Hue would be wavelength

This doesn't work, because "https://en.wikipedia.org/wiki/Magenta is an extra-spectral color, meaning that it is not a hue associated with monochromatic visible light."

HSV/L are conceptually close to the way artists have been thinking about color for centuries, since the days when everyone ground their own pigments.

Hue is the angle on the color wheel. Which somewhat translates to wavelength if you ignore the fact that we see chords of high blue + low red as the colors we give names like purple, indigo, violet, and magenta.

Saturation is how intense the color is. Lower the saturation and it turns grey, or pastel. We have a few special names here; "pink" is a low-saturation red, for instance.

Value/lightness how dark/light a color is. Lower the value on that "pink" and you might get something you'd label "brick red": it's darker, it's still definitely reddish, but it's not the most intense red you can get while still being kinda dark.

Saturation collapses as you go to the extremes of value/lightness, it's just pitch black at one end and pure white light at the other.

The whole HSL and HSV business has never been about hypothetical light sources. HSL and HSV are created from monitor CRT RGB using piecewise functions for the hue. No colorimetric considerations are given at all: it’s all about how to mix an algebraic mean.

> The main drawback of using these models directly for color picking is that the sRGB gamut has a quite irregular shape in these color spaces. As a result, changing one parameter, such as hue, can easily create a color outside the target gamut, making them quite tedious to use.

Yes, and since we use color models in development primarily to model RGB light as displayed by machines, these are less than useful indeed.

For anyone looking for a neat color model that is novel and is a bit more intuitive (arguably), look up HCG.


HCG = Human Chorionic Gonadotropin, which will dominate your search results.

Thanks, seems they renamed it recently.

I'm fascinated by this field - ultimately all 2D color pickers show a slice of a 3D structure and we use one dimension (usually brightness) to navigate through the Z axis of this structure. A while ago I built this experiment to show how true 3D color pickers would work: https://wolframhempel.github.io/skeeem/

Why does it flood my navigation history? I couldn't use my back button to return to here after playing around a bit.

Apparently it saves what you do to the address bar. Which is good on its own, but messes up history when you use href.assign() instead of .replace().


This is ridiculously cool, thank you for sharing :)

It's true that perceptual color spaces can be hard to work with, but they can help generate fairly good UI themes on-the-fly when used correctly. For UI, the ability to keep lightness constant regardless of chroma and hue is essential to preserve contrast. Likewise, hue is quite important to keep constant because it's the most prominent part of a dynamic theme. This leaves chroma as the remaining component that can be reduced to bring colors in gamut.

UI examples: https://twitter.com/kdrag0n/status/1427822417313898498 (using ZCAM, not Oklab or the new Oklab/Okhsv) / https://www.figma.com/file/ZeqPgO1RgA6eg8TMm7r8Ae/kdrag0n's-...

Full generated palettes: https://twitter.com/kdrag0n/status/1431100523894083589 / https://www.figma.com/file/mZ6VxvsC2yN4zD9rDR4S2I/kdrag0n's-...

Yes, it's very easy to create colors outside the sRGB gamut boundaries, and you have to compromise something in order to bring them back in gamut. However, when working with colors programmatically, I'd still recommend considering perceptually-uniform color spaces instead of HSL/HSV-style models that are made to simplify interactive color picking.

My phone and laptop, and browsers support DCI-P3. Is there a color picker for wider gamuts? Because the article keeps talking about sRGB and picking in that color space, but I feel like is limiting the capabilities of current technology.

I had the same question looking into HSLuv recently. Came across this https://github.com/hsluv/hsluv/issues/73 but it didn't really gain much traction. Maybe it'll be more popular once Chrome finishes CSS Color Module Level 4 color space support.

Well, I gave it a +1 and a subscribe. Hopefully we can break the chains of sRGB soon.

There's value in having a lowest common denominator. sRGB isn't going anywhere for quite some time.

I had a project where I had to convert absorption spectrum of a liquid sample into a colour. It was one of those "Just do this quick, it doesn't have to be perfect" type of things where I didn't get a chance to really understand it. I was baffled how many standards and technicalities there is behind a seemingly simple thing.

Take the dot product between 1 - your spectrum and the CIE XYZ color matching functions, then convert it to sRGB or any other color space of your choice.

I love this work - the OKLab model is really wonderful.

But I think for color pickers, there are cases where "constant lightness" is not the most important constraint. For instance, you might really mean "the most saturated primary in each hue" and then - you may want the most saturated colors to appear on a line (saturated red, saturated blue, etc.). A constant lightness constraint makes these appear in a really unpredictable pattern.

In less saturated colors, this constraint is very useful and feels right, but I think at the extremes it may reduce usability.

This is great work. I love how the result is a representation of maximally saturated colors that correctly depict perceptual lightness of what those maximum saturations are. You can get a little palette out of that which contains 'all colors' but which makes intuitive sense in a way that traditional representations don't.

It's like fitting maximum saturation into a palette that's more linear. From canary yellow you don't HAVE a lot of room to go to pure white: there's very little difference. And it translates to very little linear space between the points. So nice.

These days I would want a color picker that considers the lighting and surrounding colors.

I have an office which has huge windows facing (mostly) away from the sun so I have bright light from either clouds or blue sky. Reproductions of renaissance paintings and photographs on ‘bright white’ paper look great, but at home the color temps are in the high 2000s usually and I need coated papers for the images to be compelling.

So I am always thinking about what environment a print is in and would love to have more tools to tune up the appearance.

Then get a colorimeter that can measure that ambient light and recalibrate your screen. The highest end monitors have hoods on them to prevent external light from having too much influence on your screen.

If your desk doesn't move, you could always have a cron job that loads different color profiles based on time of day and possibly weather if you really want to make a project of it.

I think screen color space and print color space are just two entirely different considerations. They meet up in the middle somewhere, but you really can't look at both at the same time

Either the screen or the print depends on the surrounding environment. The print also depends on how it is lit.

To some extent one learns that something that looks like this in one environment will look like that in that environment.

FTA: OKLab et al specifically excludes consideration of illumination environment for simplicity: "Should assume normal well lit viewing conditions"


“Technically correct” way would be to control the surroundings than adapt representations. High CRI lightbulbs, display hoods, wallpapers, etc.

What I don't like in standard HSV color picker [1] is that the colors are distributed non-uniformly. For example, there is a wide area of same looking green color, but just a thin line of yellow and orange. I didn't understand if new models solve this issue.

[1] https://bottosson.github.io/img/colorpicker/hsv-picker.png

You can try it out in the interactive color picker comparison page linked from the article.


Thank you for the link, very interesting. Now I see there's another problem: the colors on the hue bar (especially with OKLrCH model) look too blurred and mixed with each other. And there's almost no green, but mostly reddish and blueish tones.

How is color distance computed for these? Still Euclidean or a different measure? Euclidean on RGB leads to "grayification" of colors.

On the original OKlab Euclidean will do. Perceptually-uniform color spaces are designed for that (at least for close colors).

On these slightly squished and stretched ones… I think converting back to OKlab is the better idea. You can just turn the polar stuff into Cartesian directly too, but it will be distorted.

I’ve used HSLuv a fair bit, and while it’s great for e.g. programmatically ensuring good contrast… it produces some unpleasant colors especially in the yellowish range. I’m going to try out Okhsl as a replacement because it sure looks nicer to my eyes, at least on my phone.

It turned out… not good. That said the entire JS interface was unclear what numeric types it expected and I have no idea if I was using it correctly. I’ll take the yucky mustard yellow browns I have for now :shrug:.

When I did eventually produce colors I expected to be generally perceptually uniform, everything bluish was much much brighter than I expected. Drastically worse than HSL.

I don’t think this is a problem with the color space, I think the JS source wasn’t designed to be used by humans.

I think what you are seeing is the effect of optical brighteners that are used in paper. They absorb UV light to and emit blue light to make the paper look whiter. The amount of UV light in the environment changes the color of the paper.

What's interesting is that the HSL/HSV wheel in Adobe programs has a slightly different disribution compared to the rest of the industry, they've basically tweaked it perceptually. This yields different results in their color grading tools within camera raw and premiere.

It gets even worse when you go from human eye to screen (eg sRGB) to print (eg Pantone).

For what it's worth, as someone who has deuteranopia (red-green color blindness) the resulting OKHSL color picker seems to be harder to use than the HSL one. The OKHSV one is fine since yellow separates green and red.

Great conference talk with a brief recap of color perception https://www.youtube.com/watch?v=xAoljeRJ3lU

Craig Blackwell on YouTube has a great series of videos about the fundamentals of color vision. https://youtu.be/iDsrzKDB_tA

Any good explanation why HSV has the color distortion problem mentioned in the article? I thought hue was the main thing it got right. Is it due to a problem with sRGB itself?

I really like the de-saturated color wheel / hue slider in this. It makes it very noticeably easier to pick good de-saturated colors.

There’s some really cool features about lab-like color spaces. Particularly, the ability to quickly find, localize and transmit data (from with lower error rates):


In almost every computer vision class I took, switching to the LAB color space and rerunning algorithms also still always led to improvement.

The real trick in my mind is trying to do this via sensors and / or hardware.

The OKlab definition is probably a bit easier to implement in hardware than CIELAB since there’s no funny “if” branch. Still, the number representation will be more complicated than YUV and such… fp16 maybe?

IIRC author of the TFA has retweeted some shader implementation of OKlab for color interpolation.

Damn, the way OKLrCH wiggles around as you drag the hue slider is kind of really unpleasant.

Did Björn do this work for the benefit of any particular app or project?

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact