refloat icon indicating copy to clipboard operation
refloat copied to clipboard

LEDs: add rainbow effects

Open acheronfail opened this issue 1 year ago • 19 comments

Setting the headlights to Solid with white, and then setting the taillights to Rainbow Cycle allows users to replicate the Mullet effect from the Float package.

Using Rainbow Cycle on the front and the back replicates the "Rave" effect from the Float package.

~~Rainbow Fade has also been added, which was inspired by the Float package's "RGB Fade" effect.~~ Renamed to RGB Fade after using color_wheel.

acheronfail avatar Aug 15 '24 02:08 acheronfail

Doesn't look that great with the camera I have, but this'll do for a preview.

https://github.com/user-attachments/assets/bb46bf5d-7f38-438b-a763-8458a99a2bb3

https://github.com/user-attachments/assets/18194cd2-e77a-43ca-84b6-c0d6e0d20c18

acheronfail avatar Aug 15 '24 02:08 acheronfail

Here's the RGB Fade using color_wheel instead. I think it still looks good, plus using color_wheel let me remove a almost all of the code for the previous fade implementation I had!

https://github.com/user-attachments/assets/8fe0c5ce-a87c-4f2f-b1fc-f5947f41671c

acheronfail avatar Aug 15 '24 08:08 acheronfail

Ok, it doesn't really go through all the colors very consistently though, I also noticed it tends to just return shades of red, green and blue. It's a deficiency of the color_wheel() function, maybe it could be improved... Definitely not necessary for this PR, but would you maybe like to give it a try? Ideally it should go through the rainbow in a consistent way.

lukash avatar Aug 15 '24 21:08 lukash

Definitely not necessary for this PR, but would you maybe like to give it a try? Ideally it should go through the rainbow in a consistent way.

yep, in that case I’ll rename it back to “Rainbow Fade” and I’ll have a crack at improving color_wheel :+1:

acheronfail avatar Aug 15 '24 22:08 acheronfail

After trying out a bunch of different ways to interpolate the colours (tried smoothing RGB, tried HSV values, etc) I found that this provides a much more "natural" looking fade between the colours of the rainbow.

Again, my camera isn't the best, it looks much better in person, but here's the demo of it:

https://github.com/user-attachments/assets/952ce47a-748a-4e9f-b386-98e158095f36

acheronfail avatar Aug 16 '24 03:08 acheronfail

Alrighty, I separated the commits, and had a crack at creating an optimised version which avoids the trig functions, and instead uses a bunch of multiplications with a few divisions. It almost perfectly matches the previous implementation.

I'm not really sure how I can profile these changes though? Aside from statically analysing it myself? I threw everything into https://godbolt.org/ but I couldn't really see any major improvements with my new version... :sweat_smile:

If you have any pointers I'm all ears! I've been using this tiny tool I created to test the LED colours without actually having to write the new package to my wheel: https://github.com/acheronfail/led_test since writing it over bluetooth each time was getting rather cumbersome...

acheronfail avatar Aug 16 '24 12:08 acheronfail

You've already done more instrumentation than I'd bother with :grinning:

I'm not sure how representative the profiling can be on x86_64, given all the differences, and assuming you're running and measuring a lot of iterations. What you could do is running the code in a tight loop on the VESC and monitor CPU usage, which you can see either in LispBM scripting or possibly in Terminal when you list threads (where it's time-based). There may be other ways too, these are the simple ones I know of.

If you could provide performance measurements for the three implementations that'd be great.

IMO if you set up the loop so that you get CPU load between 20-80% for all three versions and just record the percentage from LispBM it'll be a very representative result.

As for the Taylor Series, they should be good enough. I still think it could possibly be quite a bit simpler (my single quadratic function looked very close) but I haven't done the hard work so I'll leave that up to you unless the performance is somehow bad.

Thanks for the work you're putting into this!

lukash avatar Aug 16 '24 13:08 lukash

IMO if you set up the loop so that you get CPU load between 20-80% for all three versions and just record the percentage from LispBM it'll be a very representative result.

Thinking about this, it's not correct, or rather not trivial to do right. But the old "run in a loop and measure time" can be done quite easily on the LED thread:

diff --git a/src/leds.c b/src/leds.c
index 465082e..cd20ee1 100644
--- a/src/leds.c
+++ b/src/leds.c
@@ -762,6 +762,14 @@ void leds_update(Leds *leds, const State *state, FootpadSensorState fs_state) {
         return;
     }

+    if (fs_state == FS_LEFT) {
+        log_msg("WHEEL STARTT");
+        for (uint32_t i = 0; i < 10000000; ++i) {
+            volatile uint32_t a = color_wheel(42);
+        }
+        log_msg("WHEEL FINISH");
+    }
+
     if (leds->cfg->on) {
         if (leds->on_off_fade == 0.0f) {
             full_animation_reset(leds, current_time);

I measured the following: Old implementation with shifts: 0.573s Implementation with Taylor series: 0.570s Implementation with sinf: 130s

So the sinf is about 230x slower :grin: (I also tried to remove the acosf() from the TAU definition and it was the same, so the compiler did manage to optimize that out it seems).

The Taylor series implementation is faster than I thought :) Having another look at it, it seems the color_wheel() input of 0..255 is mapped onto input range 0..TAU for the taylor series, this could potentially be simplified if the Taylor series took the 0..255 directly. The way it is, there are some unnecessary arithmetic operations. Not a real performance concern, just code simplicity. I'll leave it up to you if you want to go to that length.

Otherwise, no issue with the code. Just tested it and to me it seems the color_wheel() now mainly cycles through the combination colors and doesn't really show red, green or blue though. I think maybe the curve should be a sine in the end? At least that's worked best for me in anim_fade().

Last note, on speed. I think we should set some consistent speed baseline (as makes sense for each effect). E.g. the Strobe effect I added switches after 1 second and the user is expected t oset the speed faster if they want. I did merge the Felony at fast speed, but now I wonder if we should make these slow by default (1s period) and have the user set the faster speed? I'm mainly after consistency here, I don't want one effect to be at 1s period and another at 0.05s...

lukash avatar Aug 17 '24 12:08 lukash

Oh, awesome results!

Thinking about this, it's not correct, or rather not trivial to do right. But the old "run in a loop and measure time" can be done quite easily on the LED thread:

Question, where do I see these logs? Is there somewhere in VESC Tool?

The way it is, there are some unnecessary arithmetic operations. Not a real performance concern, just code simplicity. I'll leave it up to you if you want to go to that length.

Have a look at this commit and tell me what you think: https://github.com/acheronfail/refloat/commit/fae10786a56c8ce62171f9d053447a8e2192617d - I changed color_wheel to take a float, and also simplified a bunch of the operations as much as I could.

Otherwise, no issue with the code. Just tested it and to me it seems the color_wheel() now mainly cycles through the combination colors and doesn't really show red, green or blue though. I think maybe the curve should be a sine in the end? At least that's worked best for me in anim_fade().

The Taylor Series logic is approximating sine, I think the reason we don't see the red/green/blue is because of the offsets between the colours. For example, to get red we'd need only the red calculation to be at the top of the curve, while the other two are lower. But in its current state, I've been offsetting each of the colors about 1/3 away from each other on the curve, so there won't be a time we see the primary colours...

Last note, on speed. I think we should set some consistent speed baseline (as makes sense for each effect). E.g. the Strobe effect I added switches after 1 second and the user is expected t oset the speed faster if they want. I did merge the Felony at fast speed, but now I wonder if we should make these slow by default (1s period) and have the user set the faster speed? I'm mainly after consistency here, I don't want one effect to be at 1s period and another at 0.05s...

I agree with that, I also changed that in https://github.com/acheronfail/refloat/commit/fae10786a56c8ce62171f9d053447a8e2192617d, so if you think that looks good I'll bring that into this branch and split things into their own commits.

acheronfail avatar Aug 17 '24 23:08 acheronfail

Question, where do I see these logs? Is there somewhere in VESC Tool?

Yes, in the LispBM Scripting tab under Dev Tools, which also allows to restart the package, watch CPU and RAM usage and run lisp.

Have a look at this commit and tell me what you think: https://github.com/acheronfail/refloat/commit/fae10786a56c8ce62171f9d053447a8e2192617d - I changed color_wheel to take a float, and also simplified a bunch of the operations as much as I could.

Well that's one thing, another thing is you're converting input to the 0-tau range, but if the Taylor series was made to work with the input verbatim this wouldn't be needed. It's just a small thing though.

The Taylor Series logic is approximating sine, I think the reason we don't see the red/green/blue is because of the offsets between the colours. For example, to get red we'd need only the red calculation to be at the top of the curve, while the other two are lower. But in its current state, I've been offsetting each of the colors about 1/3 away from each other on the curve, so there won't be a time we see the primary colours...

Your Taylor Series is not approximating sine, it's approximating a sqrt(sine(...)), quite a different function. I kinda assumed if you would just use sine, that would render the base colors better. But not sure how that is with the three channels offset. Maybe it needs to be done a bit differently still.

I agree with that, I also changed that in https://github.com/acheronfail/refloat/commit/fae10786a56c8ce62171f9d053447a8e2192617d, so if you think that looks good I'll bring that into this branch and split things into their own commits.

I'm still a bit undecisive on the timing thing - what should be the baseline? I mean making everything have 1s period makes some sense, but for some effects it'll be too fast (e.g. your rainbow fade going through all colors in 1s will likely not be very good). Other effects, e.g. the Knight Rider have a defined "standard" speed. Maybe we should use 1s period for the blinking effects and just something that works well for the other ones.

If you don't mind, update the timing of the effects in this PR to what you think is best and we can later fix up Felony to have consistent timing (other effects might need updating too, I can handle that).

I'm not at home this week, my responses might be slow. I'd like to play with the color_wheel() colors and see how it is myself, since we're at it, I'd like it to have a nice and consistent rainbow :slightly_smiling_face:.

lukash avatar Aug 18 '24 13:08 lukash

Your Taylor Series is not approximating sine, it's approximating a sqrt(sine(...)), quite a different function. I kinda assumed if you would just use sine, that would render the base colors better. But not sure how that is with the three channels offset. Maybe it needs to be done a bit differently still.

My apologies, I didn't write that clearly enough. I was referring to my latest commit, which I linked from the previous post. If you look here: https://github.com/acheronfail/refloat/blob/fae10786a56c8ce62171f9d053447a8e2192617d/src/leds.c#L103-L123 the Taylor Series does indeed approximate sine, and I also readjusted the calculations so TAU is no longer needed, etc. So that version is indeed using sine, but you still don't get the primary colours due to the offsets being used.

I'm still a bit undecisive on the timing thing - what should be the baseline? I mean making everything have 1s period makes some sense, but for some effects it'll be too fast (e.g. your rainbow fade going through all colors in 1s will likely not be very good). Other effects, e.g. the Knight Rider have a defined "standard" speed. Maybe we should use 1s period for the blinking effects and just something that works well for the other ones.

I'm not 100% sure either, but we can decide that later I think.

I'm not at home this week, my responses might be slow. I'd like to play with the color_wheel() colors and see how it is myself, since we're at it, I'd like it to have a nice and consistent rainbow 🙂.

I'd also like some improvements to color_wheel too, so no stress. I'm in no rush to get this out, just had a bit of spare time and enjoyed tackling the problem. If I get any more time this week I'll have another go!

acheronfail avatar Aug 18 '24 21:08 acheronfail

If we assume the LEDs are linear and have the same primaries as sRGB, we could use Björn Ottosson's oklab for some nice gradients? Could look something like this:

static uint32_t color_wheel(uint8_t pos) {
    float lightness = 0.75f;
    float chroma = 0.1275f;
    float hue_rad = TAU * pos / 256.0f;

    float a_ = chroma * cosf(hue_rad);
    float b_ = chroma * sinf(hue_rad);

    float l_ = lightness + 0.3963377774f * a_ + 0.2158037573f * b_;
    float m_ = lightness - 0.1055613458f * a_ - 0.0638541728f * b_;
    float s_ = lightness - 0.0894841775f * a_ - 1.2914855480f * b_;

    float l = l_ * l_ * l_;
    float m = m_ * m_ * m_;
    float s = s_ * s_ * s_;

    float r = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
    float b = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
    float g = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;

    return ((uint8_t) (r * 255) << 16) | ((uint8_t) (g * 255) << 8) | (uint8_t) (b * 255);
}

I haven't properly tested the code (I have an ADV), but after trying it in Blender I got this gradient:

image

The values for lightness and chroma were eyeballed to be as large as possible without any of the output channels clipping. Trig functions could use that Bhaskara approximation or something.

It doesn't go through 100% red, green and blue, because if it did it couldn't be perceptually uniform. Here's a cool website that demonstrates how it works as a color picker.

aeraglyx avatar Aug 28 '24 02:08 aeraglyx

Oh nice, I'll have to learn how to do that with Blender, being able to visualise that is really cool!

Tonight I'll try out this code on the VESC and report back how it goes.

acheronfail avatar Aug 28 '24 05:08 acheronfail

@aeraglyx I had a crack at implementing it, this was my code:

// Generate colors using the OKLAB colorspace, see: https://bottosson.github.io/posts/oklab/
static uint32_t color_wheel(uint8_t pos) {
    // See: https://oklch.com/#75,0.13,360,100
    float lightness = 0.75f;
    float chroma = 0.1275f;

    float cos_pos = 2 * (float) pos / 256.0f;
    float sin_pos = fabsf(cos_pos - 0.65f);
    float a = chroma * (2 * cosine_progress(cos_pos) - 1);
    float b = chroma * (2 * cosine_progress(sin_pos) - 1);

    float l_ = lightness + 0.3963377774f * a + 0.2158037573f * b;
    float m_ = lightness - 0.1055613458f * a - 0.0638541728f * b;
    float s_ = lightness - 0.0894841775f * a - 1.2914855480f * b;

    float l = l_ * l_ * l_;
    float m = m_ * m_ * m_;
    float s = s_ * s_ * s_;

    // convert to lRGB colorspace
    float lr = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
    float lg = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
    float lb = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;

    // and finally to RGB
    float rgb_r = lr > 0.0031308f ? 1.055f * powf(lr, 1.0f / 2.4f) - 0.055f : lr * 12.92f;
    float rgb_g = lg > 0.0031308f ? 1.055f * powf(lg, 1.0f / 2.4f) - 0.055f : lg * 12.92f;
    float rgb_b = lb > 0.0031308f ? 1.055f * powf(lb, 1.0f / 2.4f) - 0.055f : lb * 12.92f;

    return ((uint8_t) (rgb_r * 255) << 16) | ((uint8_t) (rgb_g * 255) << 8) |
        (uint8_t) (rgb_b * 255);
}

And here's what it looks like:

https://github.com/user-attachments/assets/b92ae4da-43c8-4ffb-a612-5481a22c19fd

It does seem to cycle nicely, but I find the greens too bright compared to the rest (the glare in the video shows that a little bit). I had a decent crack at trying to tweak it, but with OKLAB it appears that those greens are quite bright.

acheronfail avatar Aug 28 '24 10:08 acheronfail

Hey, I don't have time to share now, but I had a go at tweaking @acheronfail's original attempt a couple days ago to reasonably satisfactory results. I also did a very quick test of the OKLCH, I'll post a comparison.

lukash avatar Aug 28 '24 11:08 lukash

@acheronfail Thanks for checking it out!

Why is the sRGB transform at the end needed? I sort of expected the LEDs use something like PWM for brightness, in which case it should just stay linear I think. The green channel peak in the oklab sweep is noticeably darker to compensate for green being perceived too strongly, but the ~1/2.2 gamma would make them more equal at the peaks if that makes sense (so green would seem relatively brighter again). Could be wrong though..

And just for fun, here are the individual channels plotted against hue:

oklab_rgb_sweep

So maybe the simplest solution would really be just scaled sine waves. If oklab ends up working, I tried to very roughly match it with cosines on Desmos.

aeraglyx avatar Aug 28 '24 19:08 aeraglyx

So, first on my tweak of @acheronfail's original approach, to me it seemed that due to nonlinearities of the LEDs (and/or possible other factors, I don't really know) the sine blending wasn't working well and I perceived the sine waves need to be kind of squished. So instead of feeding them a linear function f(x) = x, I used a "to the power of n" transition (not sure I'm calling things the right names here).

Plot demonstration on desmos

You can see a regular sine wave as blue, orange is the "transition" of x and purple is the resulting modified sine wave.

I applied this with different exponents to the three channels to create a uniform rainbow pattern. The exponents can be easily tweaked to shift the widths of the particular colors.

Here's a demonstration of the result with direct comparison to @aeraglyx's okchl implementation:

https://github.com/user-attachments/assets/7ed547d4-07b1-4e34-9773-ae48007bcadc

(first is my modification, which is switched to okchl and then back and forth again)

So my modification is for the most part quite uniform and shows all the hues with relatively uniform widths. It shows more saturated colors and the yellow in particular is quite a bit wider than in okchl. okchl colors are more muted (mentioned by @aeraglyx as intentional). I quite like it in this particular rainbow effect, but I was thinking about this and I thought we could use this hue generating function in the future to specify the colors in the config (instead of the fixed enum of colors that is there now). In effect we'd be expressing the colors using hue (stored as 1 byte), chroma and lightness. I'd like it to cover all the possible colors that can be achieved with the RGB LEDs as well as possible. Maybe okchl is the way to go, using higher chroma to get the pure primary colors (which the proposed color_wheel doesn't do)? Maybe the hack I came up with will work better in practice?

I mean ideally we'd somehow transform the LED input to make them close to linear, but I felt it's not possible with a simple gamma function (hence I settled with a simple quadratic function for gamma, which I found not being great).

Anyway, code for my modification as well as the okchl function used in the demo is here: https://github.com/lukash/refloat/tree/color-wheel

You can use the portion and offset variables to show just a section of the hue range, which I used to tune the exponents of the polynomial functions.

Hope the above makes sense, it's late, I'm tired and don't have too much time for this... :sweat_smile:

lukash avatar Aug 28 '24 20:08 lukash

Why is the sRGB transform at the end needed?

Perhaps I implemented incorrectly at the beginning, but I found that the original transformation caused very dim and unpleasant effects - I then looked up some libraries that supported okchl and saw that they converted to sRGB after a similar transformation, which provided slightly better results... That said, looking at @lukash's demo, I think it was a case of PEBKAC. 😅

I do quite like the effect you created there, @lukash, with the LEDs showing a rainbow one by one. Would you be happy to merge this in if I took that combined with the okchl, and then made it cycle left-to-right through the rainbow? I think that would at least be a good start.

I was thinking about this and I thought we could use this hue generating function in the future to specify the colors in the config (instead of the fixed enum of colors that is there now). In effect we'd be expressing the colors using hue (stored as 1 byte), chroma and lightness. I'd like it to cover all the possible colors that can be achieved with the RGB LEDs as well as possible. Maybe okchl is the way to go, using higher chroma to get the pure primary colors (which the proposed color_wheel doesn't do)? Maybe the hack I came up with will work better in practice?

I think this is a good idea, but probably a future improvement and not for the addition of these rainbow effects?

acheronfail avatar Aug 28 '24 23:08 acheronfail

I'd like it to cover all the possible colors that can be achieved with the RGB LEDs as well as possible. Maybe okchl is the way to go, using higher chroma to get the pure primary colors (which the proposed color_wheel doesn't do)?

Oklch can do all sRGB colors (including primaries), but not all LCH combinations yield valid colors, so the final RGB values need to be clipped in some way. There is Okhsv, which pretty much solves that, but it's more complex and compromises other things.

I definitely like the idea of specifying config colors in some LCH/HSV space instead of enums. Even if the typical HSV was used, rainbows could still leverage the improved method (looks pretty good!) or Oklch.

The reason I brought up Oklab in the first place was that I don't really like how RGB strips and stuff usually "pulse" with changing hue (blue is dark etc.), maybe the rainbow's output channels could be scaled a bit to compensate for that? But that's just a tiny nitpick, also who knows how much this will vary across different hardware...

aeraglyx avatar Aug 29 '24 04:08 aeraglyx

Sorry, I don't have the time right now to commit to finishing this at the moment (that, and the LEDs on my board aren't working very well either, so that makes it difficult to write LED code! 😅)

In the meantime I've converted this PR to a draft to indicate it's not ready.

If anyone else wants to pick it up please do, but for the time being I'm putting this on the shelf!

acheronfail avatar Oct 21 '24 22:10 acheronfail

@acheronfail no worries. I've been slowly messing with the OKLCH implementation and wanna come up with a decent color_wheel() function for the next release.

lukash avatar Oct 26 '24 17:10 lukash

To give an update here, I was (long time ago) messing with the two methods of producing colors with a hue value and sadly the more hackish method (my approach) seemed to give somewhat better and more uniform color gradient based on my limited observations. I now have a benchtop controller and am planning to rig two strips next to each other for a side by side comparison, just gotta get around to do it.

After that I'll clean up and merge the two new animation effects here.

lukash avatar Apr 04 '25 17:04 lukash

@lukash I managed to obtain a Thor300 with blown mosfets, so I setup a nice little desktop rig I can use to easily test colours. I've tweaked the rainbow values and they look really good now!

I've also added another effect - Rainbow Roll - which cycles the leds horizontally across the strip: I recommend using this one to see the colour distribution. I think it's looking quite nice.

Hoping we can get this one across the line!

acheronfail avatar May 01 '25 09:05 acheronfail

Okay, I'm done updating it now 😅 found a couple things I could optimise and lift out of loops, etc. Let's try to get these effects in. 🌈

acheronfail avatar May 01 '25 09:05 acheronfail

So you finally made me to make that LED testing jig to compare the colors (which is a good thing, now I can finally test the LEDs properly :grin: ).

I really don't think OKLCH is doing a good job with the LEDs, the colors are a bit bland, not very vibrant. In your tweaked version (not sure what you tweaked exactly and what was the goal), it seems to be even a bit worse, here's a comparison with my implementation of OKLCH (mostly as aeraglyx posted it), bottom is yours: PXL_20250509_103910184

And here's my custom implementation with tweaks to the shape of the sine wave to make the result visually better (top) compared to yours (bottom): PXL_20250509_113146632

It's not the same on the pictures as in reality, but the difference is visible. We could also argue about OKLCH being more uniform at the expense of pure saturated colors, but with LEDs one kind of expects the latter. Your version in particular lacks pure red (it's more like orange) and the blue and green are also not very pure...

Here's my code: https://github.com/lukash/refloat/tree/color-wheel

It's just the new color_wheel() implementation which I renamed to hue_to_color() (note the "hue" is shifted a bit compared to oklch, mine is aligned within the hue range, OKLCH seems to be a bit shifted). I can provide the code I used to test it too, if you want to mess with it more. The branch is meant to be merged to main and then you can use it in the PR if you agree.

lukash avatar May 09 '25 13:05 lukash

It’s so hard to take pictures of these effects, whenever I do it looks totally different from reality!

Your implementation also looks good on my setup, so I’m happy for that to get merged in. 👍

So that and I’ll update this PR on top and we can get these effects in. 😊

acheronfail avatar May 09 '25 21:05 acheronfail

@acheronfail alright, the branch is merged, you can rebase the PR.

Note I realized my version of the OKLCH in the pictures was actually modified and had the chroma raised a bit, making the colors brighter. Sorry I kind of forgot about that :sweat_smile:

lukash avatar May 10 '25 12:05 lukash

alright, the branch is merged, you can rebase the PR.

Done

Note I realized my version of the OKLCH in the pictures was actually modified and had the chroma raised a bit, making the colors brighter. Sorry I kind of forgot about that 😅

Yeah, I did another final test after rebasing and compared again. I think the OKLCH implementation (that I tweaked the values a bit) generates more asthetically pleasing colours, but your new hue_to_color method covers more of the actual range, so all colours are better represented with it.

Certainly the LED bar sitting on my desk is extremely vibrant with your new changes - to the point where I had to decrease the brightness of my LED bar so it wasn't so blinding to my eyes - my implementation seemed softer. Perhaps your brighter one would look better at a distance on a onewheel.

For future reference this was the final version I had:

static uint32_t hue_to_color(uint8_t pos) {
    float lightness = 0.75f;
    float chroma = 0.1275f;
    float hue_rad = TAU * pos / 256.0f;
    float a_ = chroma * cosf(hue_rad);
    float b_ = chroma * sinf(hue_rad);
    float l_ = lightness + 0.3963377774f * a_ + 0.2158037573f * b_;
    float m_ = lightness - 0.1055613458f * a_ - 0.0638541728f * b_;
    float s_ = lightness - 0.0894841775f * a_ - 1.2914855480f * b_;
    float l = l_ * l_ * l_;
    float m = m_ * m_ * m_;
    float s = s_ * s_ * s_;
    float r = +4.0767416621f * l - 3.2077115913f * m + 0.2309699292f * s;
    float g = -1.1684380046f * l + 2.8097574011f * m - 0.2413193965f * s;
    float b = -0.0041960863f * l - 0.7034186147f * m + 1.5796147010f * s;
    return ((uint8_t) (r * 255) << 16) | ((uint8_t) (g * 255) << 8) | (uint8_t) (b * 255);
}

Still unsure which is better to be honest 😅 But I'd rather these effects go in sooner rather than bikeshedding on this too long, etc. Some effects are better than none! 🤣

acheronfail avatar May 10 '25 22:05 acheronfail

It can certainly be changed later as well. But for the most part I wanted the hue_to_color() function to return the full colors in the first place. For some of the effects softer colors can be nicer, certainly for some people... But I think the saturated ones are kind of the default.

lukash avatar May 11 '25 23:05 lukash

Not sure if the "re-request review" button notifies or not, but I applied your comments. 👍

acheronfail avatar May 12 '25 01:05 acheronfail