Closed davidscotson closed 3 years ago
I noticed there's a c function in neovim for this, but not sure how accessabile that is for lua code:
https://github.com/neovim/neovim/blob/master/src/nvim/highlight.c#L608
Cool idea and the algo is pretty simple. I wonder now it behaves without converting to rbg. At any rate we can go hsl->rgb->hsl if it's weird.
Do you imagine the syntax being something:
Normal { fg = xxx, bg = yyy },
Comment { Normal, fg = Normal.fg.mix(Normal.bg, 40) }
-- maybe mix_to which implies the "one direction" of ours.
-- Shortcut to ... mi? mix? Maybe just mix is fine with no shortcut.
Sass takes two colours and mixes over -100 (all c1) to 100 (all c2) but I think we would just have 0 to 100, where0 == fg
and 100 == bg
in the above example?
I think the extra weirdness in the Sass implementation is to handle the two colors possibly having alpha components and some oddities that might arise from that. The neovim implementation only does rgb and is a bit more straightforward and is probably enough for lush purposes (does winblend, pumblend affect this? I'm not really familiar with them).
I had kind of assumed that RGB wouldn't be great for this kind of thing and some fancy colorspace would be part of the answer, but apparently RGB is the best choice (or at least one of the best of the standard options) for doing this kind of additive color mixing. The visual gradients on the bootstrap doc page look good to me anyway.
The basic use would be just to take two colors and a mix-ratio and generate a new color that could be used like any other so the Normal.fg.mix(Normal.bg, 40)
example you give makes sense, with the 40 being the percentage of the color you're passing in, e.g 0 would be a no-op and return the original color, passing in 100 would just use the passed in color. This is kind of the main use that Bootstrap has for it, building a palette of tones based on selected base colors.
However, I'm trying to order syntax components by "readability" and then semi-automatically assign properties based on that, so possibly an alternate or extra syntax might be Normal.fg.fade_into(Normal.bg, 80%), where 100% fades it to invisibility and 0 doesn't fade it at all. And potentialy there would just be a way to say Normal.faded(50)
which would let you set the fg and bg (and everything else?) from the other Highlight group, but just change the fg to be closer to the bg which is left unchanged. I'm not sure how this would work with items that don't have specific backgrounds set, do we have access to what their fall-back inherited bg would be or do we need to set both?
I can't immediately think of a case where you'd want to fade a background into it's own fg, so it's probably alright for that to be only one direction.
Normal.faded(50) which would let you set the fg and bg (and everything else?) from the other Highlight group, but just change the fg to be closer to the bg which is left unchanged.
At first glance this is a bit too ambiguous for me
Maybe there is something like
Comment { fg = hsl.faded(Normal.fg, 50) }
but that's quite similar to just going Normal.fg.fade(bg, 50)
, probably not worth the extra syntax burden.
Normal.fg.fade_into(Normal.bg, 80%), where 100% fades it to invisibility and 0 doesn't fade it at all.
Wouldn't that just be the same as mix?
fg = Normal.fg.fade_into(bg, 80) == "80% invisible on bg
be the same as
fg = Normal.fg.mix(bg, 80) == "20% strength of fg"
It does make me think that you might have mix_into/mix_toward
and mix_away
, what you're really operating on is the distance between two points and a vector between them.
I pushed a basic vector mix to https://github.com/rktjmp/lush.nvim/tree/feat-mix.
The results do not map 1 to 1 to sass's mix (sass mix 80 == hsl mix 70), but I am undecided whether that should be treated as a reference implementation or not since we are mixing in different colorspaces.
I think as long as mix behaves predictably, it's probably ok since, well, I can't really say what 80% of black into blue is exactly, just that it should look "more black" than 50%.
I've had good results using hsluv for mixing in my own experiments. There's a lua implementation that's pretty tiny. If you want to check out my functions here's a gist.
In addition to more consistent blending, rotating/(de)saturating/lightening/darkening in the hsluv space should also be more consistent but I haven't played around with that yet. Sorry to butt in with this only slightly relevant comment but I'd been working on mixing colors for lush and thought I'd share.
@davidscotson Are you using lush's mix fn or your own? If Lush, do you think it behaves how you'd expect? (I am assuming it's lush's cause you use hsl()
but maybe you wrapped your own).
@harrygallagher4 if you use the branch listed above, does the included mix
function match the output of your blend/fade
?
No, but I don't think it's supposed to. I'm just calculating a midpoint of the h/s/l(uv) values in the hsluv space. I can't find any function aside from this one to test but I can't see how that one would do the same thing as transforming in the hsluv space. Here are some comparisons I did.
local lush = require 'lush'
local util = require 'harryg.colorutilsfnl'
local palette = require'melange.colors'.Melange.lush -- this is how melange exposes the palette
local function color(c) return palette[c] end
local function pr(c) print(c.hex) end
print('')
pr(color('red'))
pr(color('green'))
-- lush: mix two `lush.hsl` colors
-- mine: convert two `lush.hsl` colors to hex, hex to hsluv, transform, hsluv to hex, hex to `lush.hsl` color
print('')
pr(palette.red.mix(palette.green, 50))
pr(util['blend-lush'](palette.red, palette.green, 0.5))
-- lush: mix two `lush.hsluv` colors
-- mine: convert two hex colors to hsluv, transform, convert back
print('')
pr(lush.hsluv(palette.red.hex).mix(lush.hsluv(palette.green.hex), 50))
print(util['blend-hex'](palette.red.hex, palette.green.hex, 0.5))
p.s. If I wasn't clear enough earlier, I'm not defining my colorscheme with hsluv colors. I'm just doing the blend
transformation in hsluv space so the result is more perceptually consistent. By "perceptually consistent" I really mean that the result is more like what people imagine when they think of mixing colors, the gradient screenshot in my gist is a good example of that.
Merged mainline in d3071c7a05930e51913bb880a517fe0aef1de4c3.
Rather than lighten or darken colors by a fixed percentage or percentage-point amount, I'd like to move one color towards another (or another way to describe that is to mix them together by some proportion).
The web framework Bootstrap mention why they do this and have an interactive example of the differencee:
https://codepen.io/emdeoh/pen/zYOQOPB
And have static examples of their palette and go into some of the details here: https://getbootstrap.com/docs/5.0/customize/color/#notes-on-sass
They use the standard Sass mix function which is implemented here for the latest Dart version of the library:
https://github.com/sass/dart-sass/blob/master/lib/src/functions/color.dart#L785
They mostly just use it to generate tones by mixing white and black, but in particular I'm thinking of the ability to say "make this color contrast less with the background color, where you have no idea what that background color is, it could be pure white, pure black, a deep green or a bright pink. But if you have code comments for example, you probably want them to blend in a bit with the background and can state what you want relative to that destination color e.g. comments might be 50% of the Normal fg mixed towards bg but listchars might be 80% and that might work the same (well, within limits) even as the background changes wildly.