BradyBrenot / huestacean

Philips Hue control app for desktop with screen syncing. C++ with Qt Quick GUI.
http://huestacean.com
Apache License 2.0
566 stars 54 forks source link

Use a better color space for interpolating between colors #72

Closed BradyBrenot closed 6 years ago

BradyBrenot commented 6 years ago

Using xyY for interpolating was a bad idea; desaturated colors are too often intermediaries, and because they use the LEDs more evenly they always look brighter.

https://en.wikipedia.org/wiki/Lab_color_space#Cylindrical_representation:_CIELCh_or_CIEHLC


Post-commit explanation:

Color spaces

I like this article's explanation of it interpolating in color spaces. It has pictures. https://web.archive.org/web/20150510140238/http://www.stuartdenman.com:80/improved-color-blending

Prior to this change, I was averaging colors and interpolating them over time in CIE xyY. If you take a look at the graph of what the color space looks like: image of CIE xy

Then imagine just drawing a straight line from e.g. green to magenta. It cuts right near "white", which is right: D65 location

Now, instead, I'm blending in CIE LCh°. This is a cylindrical color space, similar to HSV, but "perceptually uniform"; it's meant to map better to how humans actually see. Now the perceived "saturation", "lightness" and "hue" all appear to change at the same rate, in the same direction, with no weird intermediates.

This is a little weirder to visualize as it's three-dimensional and based on human perception instead of nice easy-to-use numbers. The diagram below shows the visible light spectrum mapped onto LCh. The important thing to note is that the hue (h) is a angle (see the circular axis on the bottom), separate from C (roughly the "saturation") and L (the lightness).

visible light in LChab

Color picking

This is sort of experimental. In d746d0a957226ce1c44ebd2629474c6584f6e3ee I settled on a "better" way of picking colors and brightnesses.

For luminance: I drop the 75% least-bright samples from the image immediately. I figure e.g. if there's a bright shape surrounded by darkness, the lights ought to take on the brightness of that shape rather than the surrounding darkness. It worked out pretty well in a couple darker videos I tested it on, where there was a bright spaceship or lasers or explosions surrounded by black.

For color: I drop 25%-75% of the least-saturated samples (it keeps going until it finds one that's not super unsaturated or super dim). I also drop the C (in LCh color space) contribution of any extremely dark samples, because leaving them tends to leave the image with an inappropriate color cast (usually reddish).

Limitations

There's still a rather large limitation on how good Huestaean's color/lightness picking can be: I resize the screen image on the GPU before processing it. This reduces the resolution of the image that I can work with on the CPU (down to 144 pixels right now). This has two negative effects:

  1. The GPU is doing a more simple RGB averaging instead of doing it in LCh space -- although this probably isn't really much of an issue until I have to crunch those 144 pixels down to 1-10 lights.
  2. Smaller details get lost, and some colors can get dulled.

But one big positive:

  1. Performance! It takes a lot less CPU time to process, and there's less data to move from the GPU to the CPU.

IIRC ScreenBloom is working with 1/4 size image, which is much larger than what I've got.

Collisionc commented 6 years ago

I thought this was a "issue" I was seeing, but didn't know how to explain it. Grey/browns are particularly noticeable offenders as a lot of video-games love to use grey/brown on everything.

(oh, you resolved this while I was typing :D)

Collisionc commented 6 years ago

Just tried to build on vs2017, errors out here:

28>c:\huestacean\src\huestacean.cpp(233): error C3493: 'CHROMA_L_CUTOFF' cannot be implicitly captured because no default capture mode has been specified 28>c:\huestacean\src\huestacean.cpp(296): error C3493: 'CHROMA_L_CUTOFF' cannot be implicitly captured because no default capture mode has been specified 28>c:\huestacean\src\huestacean.cpp(309): error C2064: term does not evaluate to a function taking 1 arguments

BradyBrenot commented 6 years ago

Thanks @Collisionc, will fix. Bit annoyed that my VS didn't error those for me :\

BradyBrenot commented 6 years ago

Or it did and I ignored it 💃

BradyBrenot commented 6 years ago

Fixed in 4d7292f16f523be84fbb9e7d671f9f8d356a5418

BradyBrenot commented 6 years ago

@Collisionc

I thought this was a "issue" I was seeing, but didn't know how to explain it. Grey/browns are particularly noticeable offenders as a lot of video-games love to use grey/brown on everything.

Just for more explanation / for fun, I updated the issue's first comment up above with more explanation as to what this is about. 😄

BradyBrenot commented 6 years ago

Argh, I went through that trouble of using LCh and I forgot to actually use it for the smoothing. Blah blah fixing it now.