Just before Christmas I got an urge to add a dither filter to my JS canvas library. The past few weeks have certainly been a very intense adventure through a very deep rabbit hole involving color spaces, color distances and discovering how to generate a best-fit reduced palette of colors and then dither it (hello, blue noise!)
I finally managed to complete the work last week and I'm quite happy with the results. Using the library people can set the colors of graphical objects or gradients using any CSS color space string (RGB, HSL, HWB, LAB, LCH) and the color should render as expected. The reduce-noise filter uses LAB internally to calculate color distances, but in the end I compromised and used euler distances for the algorithm rather than one of the more modern algorithms.
Work is still on a branch in the library[1] but I'm hoping to merge it into the main code in the next week or so (after testing across devices/screens/etc).
Aside: Safari has gone ahead and introduced support for HWB, LAB and LCH color strings in CSS. Sadly, they're not recognised by Safari's canvas engine (yet). Also, their HSL and HWB gradients seem to be the same as the RGB gradients; their LAB/LCH gradients seem to dodge the dreaded Gray Zone.
I highly recommend reading Human color vision / Peter K. Kaiser and Robert M. Boynton. It's a great read if you've gone down the color vision rabbit hole as it has nearly every single topic in regards to human color perception. The biggest surprise for me was that there's still a bit of debate about how exactly neurons in the eye are wired together to produce the color signals that go to the brain.
I read an amazing article once that described the representations of color at different processing levels in the human brain. For example, the 3 types of rods in the retina sense R/B/Y intensity, but at some point it is transformed into a different 3d representation with a R-G axis, a B-Y axis, and a greyscale intensity axis. There was some implication that this is information-theoretically optimal in some sense for representing images sampled from the natural world. Anyone know what I'm talking about?
One of latest papers I've read recommended using a matrix to transform color spaces, which i've also done a codepen for.
Interestingly the opponent process mirrors the LAB color space, which is soon going to be available in Safari. This is pretty cool and can enable developers to color coordinate easier.
I'm going to give the webpage you linked a good read, looks very interesting.
Following up on my comment (for posterity). I've now released the new code to my library. Includes a demo of using the full range of CSS color strings to set color in the canvas[1], and also a demo of my reducePalette (with dithering) filter[2] - I've no idea if the filter's output is 'Good Enough' for Image Engineering work, but I'm happy with the results.
I am very interested in the "best-fit reduced palette of colors" approach, but then to create mosaics. Say you have lots tiles/beads/bricks/marbles whatever in a handful of colors (and not covering the entire color space, but say three shades of blue, brown and bright pink) how can you represent a full color image as accurately as possible using only that very limited palette (and in a fairly limited resolution, as placing 10k tiles is not fun).
I have had some success manually using Photoshop to generate a reduced color space image to only 5 distinct colors (probably optimally chosen by the algorithm) and then remapping those 5 colors to 5 the 5 colors I actually have. There must be better ways
The approach I finally settled on was: check each pixel in the image to build up a list of (RGB) most popular colors and to memoize new colors into LAB values; sort the color data into commonest first; then build up the required palette from commonest downwards, excluding colors that were too close to each other based on a distance measure. Then you can go back to the original image and measure its color against the palette colors, from which you can determine the 3 palette colors closest to it. From that you can give each pixel a propensity range of the likelihood of it taking on one of those 3 colors and run it against a randomly generated value, or a set of preset values for blue noise.
This article seems to give RGB a bad rap but I think it makes the most sense for gradients. I wouldn't want a yellow to blue gradient to cycle through a (partial) rainbow of colors. But I also wouldn't really ever choose to make a pure yellow to pure blue gradient. Most pleasing gradients you see in the wild use colors that are more similar to begin with, so it's a bit of a strawman. It's really just about carefully choosing your start and end (and optionally intermediate) colors.
I agree that hue rotation makes for some weird gradients. Linear RGB is an acceptable color space for gradients, because gamma correction is accounted for. But performing a linear interpolation in sRGB or the like leads to a linear gradient which appears to ramp steeply from one color stop to the next. However, if you compare Linear RGB with CIELAB then you'll notice the same thing, the CIELAB gradients look smoother as linear movement through the color space more closely mirrors linear movement through our human perceptual system.
It's actually also about the interpolation path as well. Creating gradients in 3 component colorspaces is totally analogous to creating interpolation splines in 3d euclidean coordinates.
The fixed points define only the end points of the splines. The space between the end points corresponds to the color value along the interpolation curve.
If we are in an rgb cube, we notice the "gray zone" is along the diagonal from (0,0,0 to 1,1,1).
If we take two colors opposite of this diagonal (say color_a={r=0.5, g=0.5, b=0.0} and color_b = {0.5,0.5,1
0} we notice if we linearly interpolate we go through gray (0.5,0.5,0.5) in the middle.
If in stead we move along a curved path that avoids the diagonal, trying to maintain the saturation we get a much nicer result.
This "curved path of roughly equal saturation" is easier to define in a colorspace such as HSV where the saturation is one of the spatial coordinates.
But as I always say, there is no theoretically "correct" way to define a gradient, only techniques optimized for some specific use case.
My personal opinion is that generally linear interpolation in RGB results in the least satisfying gradients but that is just my _taste_.
I know nothing about gradients, but wouldn't dithering provide the best results? That way you're truly mixing those two colours and not going through completely different colours based on some arbitrary numerical representation of the colours.
I guess it depends on the size of your pixels. But as long as your pixels are small enough, I think this is the only way to have a gradient go straight from one colour to an opposite colour without going through either grey or a multitude of other colours.
I think this is for those cases where you'd like to create a striking gradient but RBG falls kind of flat. For a lot of us it wouldn't be apparent why that is, just disappointing. This tool remedies that by giving you a vibrant and striking gradient regardless of what you feed it.
I do agree about the rainbow of colours though. As someone who understands how RGB works and has worked with colour a lot for decades, this tool is kind of un-intuitive at a glance. That's probably alright though. I can continue hand-crafting gradients because I know how to. Most people using a generator likely don't know how to.
Otherwise it's also a great exercise in learning about and then teaching these things, which Josh is great at.
Ultimately it’s about perceptual color versus actual light. A red/blue gradient should be almost entirely transparent (ahem, grey if that’s what you’re getting from lack of data in RGB), but it too will “cycle through” and produce colors that don’t exist between red and blue. You’re expressing preference for a particular substitution approach that makes sense to you, which is perfectly fine as a preference but that’s all it is.
Edit: or it will produce the full spectrum between red and blue but that’s obviously not your preference.
This comment sheds a little bit of insight here: https://news.ycombinator.com/item?id=29895421. The original article chose to always go "clockwise" around the color wheel, picking up lots of different colors and giving the appearance of a rainbow, instead of taking the shorter, more naturalistic route of blending through teal & green.
Subtractive colour mixing in pigments is way more complex than the light on a monitor. Each pigment has its own distribution of absorbed and reflected wavelengths so mixing any two colours is mostly try and see in practice.
I was expecting the article to eventually explain how to make a gradient between two colors, without passing neither anything looking like "third hue" nor gray.
This is perhaps not possible in theory, but just adjusting the gradient curve so that the effect is minimized should be possible.
There is a related video by minute physics about how the RGB color space stores the square root of the brightness of each color channel, because human color perception is roughly logarithmic. Bad color blending is therefore pretty commonplace because it uses the average of square roots instead of the square root of the average of the original brightness values. See: https://www.youtube.com/watch?v=LKnqECcg6Gw
This is still an issue on iPhones today. When you have a notification icon and start to pull your Home Screen to make it blurry, the border of the red circle gets inexplicably dark. Whatever happened to Apple’s famed attention to detail. This has been the case since the iPhone 6 was new, if not earlier.
If you blend values in linear space, you'll linearly interpolate from the starting physical intensity to the ending physical intensity. This is a very reasonable default, corresponding to an objective physical measure. Also it makes your gradient appear roughly the same to people with reduced colour perception.
Other modes would be useful too, maybe something that goes through HSB colour space, but I suspect designers would find it easier to design their desired gradient in a program of their choosing that then outputs a piecewise linear approximation.
Because Hue is a degree, you can walk either clockwise or counter-clockwise. That is why in the Javascript widget (the one to select midpoints and color spaces), HSL v.s. Lab v.s. HCL acted quite differently. You probably also need a tweak for that in the "Gradient Generator" widget too.
Shouldn't it pick the way with the shortest distance? Otherwise you'd get an entire rainbow between green and yellow! (Or is it already doing that but which distance is shorter depends on the color space used?)
> It is widely assumed that the word "football" (or the phrase "foot ball") refers to the action of the foot kicking a ball. There is an alternative explanation, which is that football originally referred to a variety of games in medieval Europe, which were played on foot. There is no conclusive evidence for either explanation.
> In some cases, the word has been applied to games which involved carrying a ball and specifically banned kicking. For example, the English writer William Hone, writing in 1825 or 1826, quotes the social commentator Sir Frederick Morton Eden, regarding a game – which Hone refers to as "Foot-Ball" – played in the parish of Scone, Perthshire [in which kicking the ball was prohibited]
> Conversely, in 1363, King Edward III of England issued a proclamation banning "...handball, football, or hockey; coursing and cock-fighting, or other such idle games", suggesting that "football" was in fact being differentiated from games that involved other parts of the body.
It seems unlikely that King Edward III meant to ban games that involved walking on your hands, or perhaps brachiating.
> It seems unlikely that King Edward III meant to ban games that involved walking on your hands, or perhaps brachiating.
But this argument assumes that the etymology of 'handball' must be the same as 'football'. Football could be called that because you play it on foot, and handball could simultaneously be called that because you use your hand to strike the ball. There's no contradiction there.
No, all the argument assumes is that handball (and hockey) are played on foot. That would be enough for them to be called "football" under the theory that football is a class of games that aren't played on horseback.
> That would be enough for them to be called "football" under the theory that football is a class of games that aren't played on horseback.
If you followed this argument you'd be confused why we have basketball when netball already covers games that involve a net and a ball. Not all games are named by the same person with the same naming ideas.
What is the LAB color space that is provided in the tool but not discussed in the article? For all the hubbub about HSL and HCL, LAB looks the best to me.
LAB is one of the better ways to to start with perceptually uniform spacing of the colors. Also if the path through the color space avoids A and B being near 0, then the gray dead zone problem that this talks about goes away.
There are multiple ways to look at differences in LAB space unfortunately. Each one more mathematically complex, yet more perceptually accurate:
http://zschuessler.github.io/DeltaE/learn/
It’s a color space designed to be perceptually uniform. So as you blend through colors the saturation appears to stay the same. Check some of the reds and greens in the other color spaces and see how they get really saturated.
You can compare monochromatic scales between various color space interpolations and order them by contrast (great for accessibility work). Lab is from the 70s, the newest one there is CIECAM2 from the 2000s, also looking most natural to my eyes.
I find the article extremely dishonest as it goes from using a "yellow to blue gradient" (let's call that the initial requirement) and turns it into a supposedly better "yellow to orange to red to pink to purple to blue" gradient.
I think anybody that has to choose between the "gray dead zone" and the "rainbow" will ALWAYS settle for the "gray dead zone" if the initial requirement is to make a "yellow to blue gradient".
Does it contain valuable information? Yes. Does it avoid the "gray dead zone" problem? Not at all, unless you claim that bringing another problem magnitudes larger is "avoiding the problem".
It's also dishonest in the sense that gradients from bright yellow to bright blue or other combos that produce a grey deadzone are probably pretty hard to find in the wild.
I've tried the gradient generator, and I found it actually a bit difficult to pick colors that produced a significant grey zone.
They aren't exactly novel, it's mostly an attempt to connect the modern color science with actual software development. Image processing in software (even that has the dominating market share like Photoshop) is still largely built around computing convenience, not around human perception or the mapping of physical world to it. Which is sad to see as the models are known for a long time but still mostly hidden in academic obscurity. I hope people like Björn Ottosson will help to change that.
Love the article. Small UX gripe: it took me a while to figure out how to use the gradient sliders. The box at the top looks like a handle, and I was trying to interact with that. But it looks like I have to click the bar to move the box, but there is no visual indicator that the bar itself is a point of interaction other than a cursor change (which is really subtle).
Yeah, I would have considered adding ball with the same styling as the other drag handles at the tip, and remove the ball at the center pivot point. Mimicking the affordance elsewhere, behavior can stay the same as is.
The gradient generator Josh introduces here is pretty nice [0]! And the HSL vs RGB theory is also pretty interesting. I'll give this generator a try next time I want one.
But my favorite gradient generator at the moment (most pleasant gradients IMO) is still [1].
As the article says, the core problem is CSS interpolates in RGB colorspace. CSS Color 4 seems to have adapted a color space parameter for interpolation in its drafts. See discussion here: https://github.com/w3c/csswg-drafts/issues/5833
HTTP203 has two episodes on this which I found really helpful in understanding what color space related changes are being made in CSS and why they matter.
> Now, HSL isn't necessarily the best color mode to use in every situation; it tends to produce gradients that can be overly bright and vivid, because it doesn't take into account human perception.
Sure, but that is often not as useful as it sounds.
For instance, on your page, try to pick the most intense green using one of the Ok pickers. The edges of the physically representable colour space can be pretty important for many tasks.
As a (web/app) developer, I'd really wish I had any sort of talent do beautiful things, this person clearly has a sense of aesthetics that I don't, while I know that practice would make it better, I just don't feel that I'd get there.
Red pill: truely accept that design is very important, make the hard effort to improve your aesthetic work over time to improve your personal taste, listen to graphic designers and learn their taste from them. Become the master of both aesthetics and engineering, reap the significant rewards as one of the few that can navigate the compromise between the disciplines. The cost is that the world slowly becomes terrifyingly ugly, because you have to opened your eyes to hideous monstrous graph everywhere; even a simple sign on a road can make you physically ill. You are surrounded by evil, currently your mind is just closed to it. One friend of mine struggles to go to shopping, because the screeching noise of barbaric design hurts their psyche. You already struggle against the cesspit of bad engineering that surrounds you everyday: take the red pill and more than double your suffering.
Blue pill: keep ignorant of graphic design, enjoy mocking the trivial and meaningless exploits of designers, laugh at their talk of the energy or feelings of a font (idolising even just one minute part of a single character), remain happy in your ignorance. The cost is that you will always feel something is missing in your life and work. There is a worthwhile struggle for meaning in the dark arts, you will miss out on the pleasure of creating form from the void.
There are profound joys of creating something beautiful. Engineering is easier but more restricted because the definition of engineering is compromising to meet limitations: design has fewer limits and they are mostly created by your society within your own mind. A aesthetic sense will get you widespread respect, because people can appreciate it. Great engineering is a lonely vice that is usually not even appreciated by your fellow engineers. As an engineer you can subversively affect graphic design and you often have massive control over aesthetics. Comparatively, graphic designers are chained artists that often bleed away their soul slowly until they become corporate zombies with minds of mush.
> As an engineer you can subversively affect graphic design and you often have massive control over aesthetics.
For most of my career I was a hybrid designer/developer. There's definitely a dialog between them, and engineering enables and constrains some elements of the esthetics, but in general I would say it works the other direction more often than not.
That's why when I read about von Tiesenhausen’s Law of Engineering Design, it really rang true for me: "If you want to have a maximum effect on the design of a new engineering system, learn to draw. Engineers always wind up designing the vehicle to look like the initial artist’s concept."
We're just talking about esthetics, rather than functionality or purpose or process, which are much more of a give-and-take.
This is why I eventually just moved straight over to design: so much of product development is path-dependent, and designers exert a ton of leverage just by creating documents upstream of engineers.
There is also a third place to go where you respect good design, designers, and their work. You learn enough about it to enjoy it, but maybe not enough to do it well yourself. You accept that because you invest time in other things.
You can enjoy music without being a professional musician.
My day job is "artist" and the "everything looks terrible forever" scenario depicted here is rather exaggerated, IMHO. I can enjoy something pretty when I see it but I can also let ugly things slide by without worrying about it.
Some pieces are great, but others are infuriating, like the fact that I can't disable pointer or scroll wheel acceleration without installing 3rd party software.
At least they let me invert the scroll wheel so that its behavior matches everything else in the world.
I can recommend The Non-Designer's Design Book, by Robin Williams, as a great way to dip a toe into the world of design. It won't make you a designer, but it will help you fake it a bit when you don't have one. I found the suggestions surprisingly effective in my own work.
I used to do gradient interpolations all the time in dataviz using D3, and using HSL/HCL/LAB always created better gradients. I wish we had color mode options in CSS gradients, but this is a good workaround for now.
As part of my logo generator I've spent quite some time to create a color algorithm that takes one color and create a gradient based on an intensity slider and a "mode". It proved quite useful, so I've implemented it as a free stand alone tool for web developers to quickly create aesthetically pleasing CSS gradients:
This is one of those things that has been at the edge of my attention for years, and when someone articulates and demonstrates it, I had an "aha! of course" reaction. :)
The darkish gray dip is mostly avoided by using a linear colorspace. Interpolating in HSV can introduce weird artifacts. Mostly due to H being circular, if you naively interpolate between two close shade of red, you might go through all of the colors of the rainbow.
Interpolaring in a perceptual colorspace might make sense in certain applications, but if arithmetic on color values is involved then a linear colorspace is a good default (and premultiplied alpha, but that's an other story).
This needs to be higher up. It's not just about color interpolation - this is a problem with nonlinear color spaces. If you interpolate in a proper linear colorspace, you still get a desaturated color in the middle, but it's the proper brightness and it looks a lot better.
Gamma correction is the bane of computer graphics, and in 2022 there is absolutely no excuse for not using linear colorspaces for all image processing. We have the RAM. 8-bit non-linear colorspaces are a compression technique and should be only used for file formats, not image processing, ever. So many things would look much better and correct if we stopped trying to incorrectly process images in a compressed representation without uncompressing them first.
For comparison: nobody has used A-law or mu-law audio compression ever since digital POTS lines, and we certainly don't process audio in that form, ever. It's the same exact concept as storing pixel values in 8-bits with a gamma function. But somehow we think we can get away with processing images in that domain. I guess it's because if you try to do that with audio, the result is obviously wrong to any human ear, while if you try to do it with images it only looks somewhat wrong, not completely wrong...
Really insightful deep dives into a very misunderstood technology, presented with a fun, lighthearted writing style that I find myself trying to emulate in my own technical writing.
This discussion thread already has an unusual number of posts specifically praising the author by name. It's very weird and unusual on HN. But he's just a really popular guy?
There are more colors spaces than just HSV and RGB.
The HSV gradients look wonky; the HSV space was designed for easy color picking UI with efficient calculations on 1980's hardware, not for interpolation.
I found this site where you can play with interpolation via various color spaces:
LAB looks good; but interestingly, going from yellow to blue, it doesn't interpolate through green at all. Which makes sense, since yellow-blue is one axis in LAB, whereas red-green is another.
Yeah... Is it really a 2-point gradient if it contains 5 different colors? The grey bugs me, but surely turning a gradient into a rainbow is not a solution. More like, an alternative.
Not really, it's just a gradient along the hue axis, instead of fading colors into each other. It's not more or less wrong than doing it the other way.
If we're going to think about gradients at that level, we should also think about perceptual nonlinearities that create all kinds of distracting visual artifacts... See e.g. https://news.ycombinator.com/item?id=20754364
Since this is posted by you, Josh.. is the date in the article inaccurate (should the year be 2022?) or did you just decided to push to HN on the one year anniversary? (Title might benefit from the year)
Personally, when it's so recent (like within last year or three) I don't really care if the title is missing the year on HN. It becomes more useful when it's clearly a dated (i.e. maybe incorrect or no longer relevant) article.
Well, I reckon it's a typo rather than an old article.. I can't believe that a joshwcomeau.com article would only have 2000 visits in a year, and most of us type/write/speak the wrong year in early January :D
The problem with HSL gradients is that you now have a completely different color, and the whole thing looks like nothing found in nature. Unless the rainbow is an intentional design element, it just doesn't quite look right.
It's useful, but not a substitute for really carefully picking your colors so it all works.
One of the harder things in creative projects is when you have two individual elements you want to use, but you don't like what happens if you add both.
Nice. Things ending up being gray is a common problem on the web.
I took a deep dive into gradients some time ago. Not CSS but custom calculations that evened out things like brightness, luminescence, contrast and so on. My key takeaway was that my preferred way of doing gradients was to use similar hues and also play around with the saturation of the colors. One does not need complimentary colors to get that gradient feel.
You can create multi-stop gradients easily with this tool, but doing so with pure blue and yellow looks nothing like the blend of those two colors would look. I think you should just pick colors that fit better together, unless the rainbow effect of walking along the hue angle is the effect you’re going for.
This also addresses an issue when you transition from black to transparent to emulate a shadow, and end up with a "cliff" where the gradient abruptly changes. You can see this in the tool when you transition from black to white with LRGB. Any of the other color spaces avoid it. Very cool!
Personally I've used SVGs and linear gradients to achieve a smooth gradient and blurs. I made a CodePen about blurs several year ago: https://codepen.io/toastal/pen/NAVybq
So, the "solution" for having a gradient go from yellow to a blue (via a gray region) is to have yellow go through green and then blue? Color me unimpressed.
I remember years ago using non-linear functions to avoid a similar problem in a different field. I bet the same solution would work here and wouldn't look so "wrong". You could pick some non-linear fun to interpolate between values. And you could interpolate different RGB channels at an offset. The reult would be less gray in the middle.
What you’re describing would result in a yellow to blue gradient having green in the middle - at least that’s what would be interpreted by the human eye.
The solution is to do linear interpolation in HSL instead of in RGB. It's a simple and useful insight and the article does a fantastic job explaining and motivating it. And then goes so far as to supply an interactive tool to try it out!
lab looks best IMO. Safari supports lab in CSS, but I don't have a way to test: do linear-gradients in Safari where lab is the colour space used for the two colours, does it do the gradient in lab colour space?
No, it's still bad. For example, it hijacks the space bar. You're spacing/pgdwning like normal when it pops out, then it hijacks the space to open up a nag modal. It was terrible when it was Clippy, it's terrible when it's a Bob the Builder avatar. Comeau's website never sparks joy in me because I'm always wondering what the next bullshit will be (was this the one which for a while was doing sparkles after the mouse?).
I seriously wonder if a person exists that likes being bothered when they're trying to read something? If that's not the case, and in fact nobody likes this, why anyone would think that this was a good idea?
I personally don't find this particular popup bothering. I can continue to read the page without interruption. I also understand that they want me to subscribe or whatever so I know when their next article is up.
Can you give examples of sites that do spark joy for you? Whenever I encounter hate for certain design decisions online I like to know the alternative I should strive to follow instead.
The person who complained, Gwern, has their own site at https://www.gwern.net/. It’s another article-focused site, so the designs should be mostly comparable. However, Gwern’s articles are much longer, so their design choices might reflect that.
For me Page Down does not trigger the click on the "Sure" button of the small popup. That would be a really weird thing. Space does, because the element is focused automatically (which is kinda bad imo).
Heh, I didn't like it, I never like when something pops up and animates on a website after a timer while you're reading, I never like when something overlays any part left or right of the text you're trying to read, and I never like when you have to close something to continue distraction free. This thing is exactly an aggressive modal popup.
It honestly scared me too when I saw it pop out of the side of the screen. I've viewed this blog before and had the same reaction. It's cute, but I'm sure you and I aren't alone.
I finally managed to complete the work last week and I'm quite happy with the results. Using the library people can set the colors of graphical objects or gradients using any CSS color space string (RGB, HSL, HWB, LAB, LCH) and the color should render as expected. The reduce-noise filter uses LAB internally to calculate color distances, but in the end I compromised and used euler distances for the algorithm rather than one of the more modern algorithms.
Work is still on a branch in the library[1] but I'm hoping to merge it into the main code in the next week or so (after testing across devices/screens/etc).
Aside: Safari has gone ahead and introduced support for HWB, LAB and LCH color strings in CSS. Sadly, they're not recognised by Safari's canvas engine (yet). Also, their HSL and HWB gradients seem to be the same as the RGB gradients; their LAB/LCH gradients seem to dodge the dreaded Gray Zone.
[1] - https://github.com/KaliedaRik/Scrawl-canvas/tree/dec-21-dith...