rktjmp / lush.nvim

Create Neovim themes with real-time feedback, export anywhere.
MIT License
1.51k stars 47 forks source link

Consider usung HSLUV rather than HSL #37

Closed davidscotson closed 3 years ago

davidscotson commented 3 years ago

As mentioned in other tickets, I'm looking to programmatically choose and/or adjust colors for themes. This is quite tricky in RGB and HSL because they're not really designed for it.

I was assuming I'd need to come up with some compensation factor to adjust colors for perceptual uniformity but ended up using a pre-existing library that does that based on a color system called HSLUV.

They have an official lua version: https://github.com/hsluv/hsluv-lua/blob/master/hsluv.lua

This works well for my purposes, (I might need to do further tweaks to account for ambient light, but this gives a much more solid foundation) I just do something like this to integrate with Lush via the common language of rgb hex:

background  = hsl(hsluv.hsluv_to_hex{hue, saturation, center-contrast})
foreground  = hsl(hsluv.hsluv_to_hex{hue, saturation, center+contrast})

Now that I've tried it out, and because the HSLUV interface is modelled on HSL, I'm thinking I might write a wrapper so I can just use the standard Lush DSL syntax, and the HSLUV stuff will all happen in the background. But it did occur to me that the goals you're aiming for with the DSL would possibly be better achieved if you switched out HSL for HSLUV but basically kept everything else the same. So this might be something you'd be interested in adopting as the core of lush.

The one big downside appears to be the speed compared with HSL/RGB, but I've not noticed any issues while using it interactively, so not sure if this just means "too computationally expensive to be widely adopted in the 1970s and become part of legacy computing", so possibly worth benchmarking to see if it's a material issue. I'm thinking if it gets adopted at the core of Lush, then all the calculations would be done within the HSLUV color space same as is done now in HSL and there would only be one transformation to hex at the end anyway.

The designer introduces it here: https://www.boronine.com/2012/03/26/Color-Spaces-for-Human-Beings/

And has a site here, with various demos, color pickers, examples etc. https://www.hsluv.org/comparison/

harrygallagher4 commented 3 years ago

Well, that was bizarre timing. This comment I just left happens to be extremely relevant to this issue.

Here's a gist with some of the work I've done on integrating lush.nvim + hsluv. I can add my colorscheme.lua to the gist if necessary but the screenshots pretty much cover the interesting bits so far

rktjmp commented 3 years ago

I have pushed an experimental branch that supports the HSLUV space (actually it's living in the same one as mix (https://github.com/rktjmp/lush.nvim/tree/feat-mix because I didn't want to manage two change sets.)

This is experimental and things might change. I went feet first without any tests so...

Usage:

hsluv = require('lush').hsluv -- or require('lush.vivid.hsluv')

-- doesn't this look familiar?
hsluv(100, 30, 20).ro(10).li(10).mix('black')

HSL won't ever be replaced with HSLUV because:

  1. It would break existing themes since the colorspace is obviously intentionally not a 1:1 match
  2. HSL is already "weird" for most people, probably 4th most popular behind #RRGGBB -> rgb -> hsb -> hsl, having a niche of a niche feels like a poor choice, even if it's technically superior. It also limits external tools since everything would have to be passed in as #RRGGBB from palette generators or image tools (you already have to do this half the time because HSL has poor support).

I hope that separating it out in its own color library lets people who know and understand it use it, but keeps backwards compatibility.


Note: Lush is entirely independent from the color system.

Lush takes whatever you stick in fg/bg, it just expects to be able to concat it with a string and get a good "vim value" (rgbhex or colour name) back when it compiles.

This means if you want to explore say, the L*a*b* colorspace, you just need to wrap whatever value you pass to lush's fg/bg keys in a meta table that responds to tostring() and concat().


The one big downside appears to be the speed compared with HSL/RGB, but I've not noticed any issues while using it interactively, so not sure if this just means "too computationally expensive to be widely adopted in the 1970s and become part of legacy computing",

Somewhat contrived test:

count = 100000
t_a = vim.loop.hrtime()
for i=0,count do
  local x = hsl(100, 50, 50).ro(10).li(20).mix('black', 50)
end
t_b = vim.loop.hrtime()
n = t_b - t_a
print("hsl n=count: " .. n .. "ns total " .. n / count .. " ns avg")

t_a = vim.loop.hrtime()
for i=0,count do
  local x = hsluv(100, 50, 50).ro(10).li(20).mix('black', 50)
end
t_b = vim.loop.hrtime()
n = t_b - t_a
print("hsluv n=count: " .. n .. "ns total " .. n / count .. " ns avg")
hsl   n=100000: 1242679500 ns total 12426.79500 ns avg
hsluv n=100000: 1222371229 ns total 12223.71229 ns avg

0.012223 ms for a "semi complex" chain, I wouldn't worry about it. (HSLUV is slightly faster because it get's a warm cache. Flipping the order flips the results.)

davidscotson commented 3 years ago

Hey, just wanted to say I've been using this on the feat-mix branch since that was created and it's all worked well for me, with no issues as I've been fiddling with my theme ideas on top of it. The creation of (semi-)auto-generated color schemes that are visually pleasing seem much simpler, and it works well with the manual tweak-it-and-see-what-happens loop too as the values are little more human friendly.

rktjmp commented 3 years ago

Great! Basically just pending me writing some tests that are more specific to the "vivid" refactor before I can put it mainline. I will ping this thread when I finally get to that.

rktjmp commented 3 years ago

Merged mainline d3071c7a05930e51913bb880a517fe0aef1de4c3.