NeoPixelBus icon indicating copy to clipboard operation
NeoPixelBus copied to clipboard

Hue-preserving conversion between RGB and RGBW

Open jackjansen opened this issue 4 years ago • 14 comments

Describe the solution you'd like

I'm looking for a converter between RgbColor and RgbwColor (both ways ideally, but RGB to RGBW is really the most interesting) that preserves the hue, i.e. you would somehow pass in the color temperature of your W pixels and it would extract the right amount of white light of that hue from the RGB channels to the W channel.

Describe alternatives you've considered

I'm hoping something like this has already been done by someone. Otherwise I will give it a go myself. If I implement something myself, would you be interested in incorporating it into NeoPixelBus?

I'm also working on a TemperatureColor (which would have a temperature and intensity parameter), would you be interested in incorporating that?

Additional context

This is mainly in the context of using RGBW strips for home lighting.

jackjansen avatar Dec 27 '19 11:12 jackjansen

The issue are:

  • if I have an RGBW (250, 110, 50, 255) and convert this to an RGB, what would you expect the results to be? The W channel would already push all the other channels to max and converting back would not be the same results.
  • The W channel can come in three variants, Cool, Neutral, and Warm. Each of these would require a different conversion factor (color temperature) for the W channel.
  • The W channel can also come in different brightness than the color channels, such that RGBW (255,255,255,0) would be dimmer than RGBW (0,0,0,128).

So far I have left it to consumer to come up with conversions due to the variance and no real standard way to achieve it (what one person expects won't match all).

But I am willing to see what you come up with.

Makuna avatar Jan 06 '20 01:01 Makuna

What I'm doing right now is only converting from RGB to RGBW by extracting the shared whiteness. This is done based on the white led color temperature, computing what its R G and B components are in D65 (which I am assuming is what the RGB values are in) and subtracting the relevant numbers from the R G and B.

But: I've only really tested this yet with converting from TemperatureColor->RGB->RGBW (where it works fine, at least for visual inspection and for the very lousy color temperature meter I have). I still need to test with RGB->RGBW.

And: you're right about my assumption that (255,255,255,0) has the same intensity as (0,0,0,255). I will add a parameter to my converter so you can modify the relative intensities (the datasheet for whatever RGBW LED people use should have that information, I guess).

(and I still need to fix up my API to sort-of match your API, I'll add a note here when I have something)

jackjansen avatar Jan 06 '20 13:01 jackjansen

I've created a patch. Actually, I've created a library NPBColorLib but with the specific intention of having it disappear shortly, to be integrated into NeoPixelBus.

Can you have a look at https://github.com/jackjansen/NPBColorLib and tell me what you think?

If you like it I'll fork NeoPixelBus, integrate it and send you a merge request.

jackjansen avatar Feb 01 '20 00:02 jackjansen

@Makuna could you have a look at my NPBColorLib, please, and let me know whether you would consider it for inclusion?

My preference is to include this into NeoPixelBus, but if you feel the functionality is too esoteric then I'll distribute it as a separate library.

But I need to decide shortly: I have other projects that depend on this that I want to create a release for.

Thanks!

jackjansen avatar Mar 22 '20 00:03 jackjansen

This repo can do the job for you: https://github.com/iamh2o/rgbw_colorspace_converter/

I wrote this module with a friend in such a way that 'color' objects could be instantiated via several color systems, and the object could spit out translations to all the other colorsystems it supports- which after a LOT of research (a key piece being https://www.neltnerlabs.com/saikoled/how-to-convert-from-hsi-to-rgb-white ), we finally nailed the [HSI/HSL/HSV/RGB/HEX] -> RGBW conversion.

There are a ton of packages that have the general colorspace problem solved, but it seems the RGBW case is pretty specific to physical lighting/leds, and not applicable to digital displays, RGBW was not included in any modules I'd looked at.

And the killer feature of this module is that the color objects you instantiate can be manipulated in several color systems depending on your needs (different ones that you created it in), and it will keep all of the translations to the other spaces up to date- and it's super fast, we've not yet had it be a frame rate limiting component.

So something like this would be a loop through the fully bright, fully saturated rainbow (note how the RGB vs the HSV codes are far less amenable to programatic manipulation):

from rgbw_colorspace_converter.colors.converters import RGB

color = RGB(255,0,0)

ctr = 0

while ctr < 10:
     color.hsv_h += .1
     print(f"HSV:{color.hsv}  RGB:{color.rgb}  HSI:{color.hsi} HEX:{color.hex}")
     ctr += 1

# "H" in hsv is actually expressed in 360 degrees, and it is cylindrical. We've normalized it to being between 
# 0-1 (so H=0=H=1 - both are red)
HSV:(0.0, 1.0, 1.0)  RGB:(255, 0, 0)  HSI:(0.0, 1.0, 0.33333) HEX:#ff0000
HSV:(0.1, 1.0, 1.0)  RGB:(255, 153, 0)  HSI:(36.0, 1.0, 0.533328) HEX:#ff9900
HSV:(0.2, 1.0, 1.0)  RGB:(203, 255, 0)  HSI:(72.23529411764707, 1.0, 0.5986868235294117) HEX:#cbff00
HSV:(0.3, 1.0, 1.0)  RGB:(51, 255, 0)  HSI:(108.0, 1.0, 0.399996) HEX:#33ff00
HSV:(0.4, 1.0, 1.0)  RGB:(0, 255, 102)  HSI:(144.0, 1.0, 0.46666199999999997) HEX:#00ff66
HSV:(0.5, 1.0, 1.0)  RGB:(0, 255, 255)  HSI:(180.0, 1.0, 0.66666) HEX:#00ffff
HSV:(0.6, 1.0, 1.0)  RGB:(0, 102, 255)  HSI:(216.0, 1.0, 0.46666199999999997) HEX:#0066ff
HSV:(0.7, 1.0, 1.0)  RGB:(50, 0, 255)  HSI:(251.76470588235296, 1.0, 0.39868882352941176) HEX:#3200ff
HSV:(0.8, 1.0, 1.0)  RGB:(204, 0, 255)  HSI:(288.0, 1.0, 0.599994) HEX:#cc00ff
HSV:(0.9, 1.0, 1.0)  RGB:(255, 0, 152)  HSI:(324.2352941176471, 1.0, 0.5320208235294118) HEX:#ff0098
HSV:(1.0, 1.0, 1.0)  RGB:(255, 0, 0)  HSI:(0.0, 1.0, 0.33333) HEX:#ff0000

# Note-  you can access the color.rgbw and color.hsl values with this nomenclature.

iamh2o avatar Aug 18 '21 08:08 iamh2o

@iamh2o I will have a look at your repo, thanks for the link! My solution https://github.com/jackjansen/NPBColorLib works pretty well for the mid intensities, but breaks down at low levels, due to the individual color LEDs having different cutoff points below which they simply turn off. That has the effect that "warm white" from an RGBW strip where the W LED is cool will below some level show as 100% red (because apparently the red LED continues working at the lowest levels).

jackjansen avatar Aug 18 '21 09:08 jackjansen

@jackjansen Sorry, for some reason I was not receiving notifications of change until iamh2o commented! I will take add this to my list to review and comment.

Makuna avatar Aug 18 '21 16:08 Makuna

AH! Yes, https://github.com/iamh2o/rgbw_colorspace_converter/ presumes a 'natural' white LED strip. Correcting for the other cool/warm/etc strips would be a challenge. I believe those cool/warm white LEDs are for applications that mostly want to just use the white LEDs, or are only using the white LEDs. Because, you can create cool or warm white with the natural white + RGB (and save the headache for correcting the white). i think...

FWIW- all of the formulas/math used to translate between the color spaces are pretty clearly laid out in the converter module. ie: https://github.com/iamh2o/rgbw_colorspace_converter/blob/5dfbf9fd3d519939191d7e7d3213eb173dcce828/src/rgbw_colorspace_converter/colors/converters.py#L242, in case you have more of an interest in the innards.

iamh2o avatar Aug 18 '21 20:08 iamh2o

@iamh2o (unsure whether we should take this discussion to your repo, let me know if you want to do that) your code and the Neltner Labs you're referring to assumes there is a "natural white". This is true in case of a color space definition, such as D65, which define a specific white point (I think the black body radiation of a 6500K temperature object).

Unfortunately, RGBW LEDs have various colours for their W led. Often, these are something like 5000K for cool white, 3000K for warm white. So that means that if you want to reproduce a specific color with an RGBW led you have to cater for the color/temperature of your W led.

And, to make matters more difficult, the R/G/B leds in an RGBW led are usually tuned for something like D65, so if you program a value where R=G=B=value, W=0 you'll get a 6500K white. But if you program R=G=B=0, W=value you'll get a white light of the color temperature of your W LED.

And then to make matters even more difficult the W LEDs often have a different intensity from the R,G,B LEDs. So setting R=G=B=value, W=0 or R=G=B=0, W=value will result in different intensities (usually with the W LED being brighter, so the second case being brighter).

(please note that this whole analysis (if you can call it that:-) is based on my limited understanding of the subject as I'm hacking my way into it, so please correct me if I'm wrong....)

I've built a device based on a VEML6040 to sense that actual color temperature of what I'm creating, but unfortunately these sensors are themselves rather finicky and need individual calibration....

jackjansen avatar Aug 18 '21 22:08 jackjansen

@jackjansen - I have no strong opinion about where the thread lives, it seems potentially useful in context of the above, but also possibly a tangent. How about this, if we have more than 2 more msgs, I'll open one up and refer back to this thread?

The color sensing device sounds really interesting, I'd love to hear more about that. A friend of mine, who I'll point in this direction, has written an imaging system to record you leds, in any geometry, and map them all plus calibrate them for more even color balance. I imagine, that reading the LED white actual and calculating the delta from ideal could be used to generate a correction factor--- one that I would hazard a guess would be straight forward to apply in HSV/HSL/HSI space vs. RGB, which would likely just be 3x more complicated and all that much more book keeping. It's one of the main reasons we chose HSV as the internal 'true' color reference, and as one of the others change, it changes and the others will reflect this change if requested.

And I agree, the questions/challenges you raise are not at all clear to me how to resolve (I'l call in Nettler!).

iamh2o avatar Aug 19 '21 05:08 iamh2o

@iamh2o I'd love some help on this! The repo for the low-level color sensor (esp32 board that provides the RGBW values as a REST server on the WiFi) is in https://github.com/cwi-dis/iotsaRGBWSensor.

But the high-level code, which controls RGBW led output of the system-under-test, then reads the resulting color values from the sensor, and then plots a colourspace diagram with a curve of the system under test, is currently tied rather tightly to my use case. It lives in https://github.com/cwi-dis/lissabon subdirectory python/lissabonCalibrate.

That code would have to be separated from the Lissabon use case and moved the iotsaRGBWSensor repo.

And then it has also turned out the the VEML6040 boards really need individual calibration (as the application note from Vishnay states) so that has to be implemented.

jackjansen avatar Aug 19 '21 23:08 jackjansen

Wow-- pretty awesome. I'm actually not 100% clear on the final application beyond calibrating- or is that the final application? I'll prob see my friend this w/e and we generally talk LEDs :-) Will see if he has anything to add.

iamh2o avatar Aug 23 '21 08:08 iamh2o

Oh- well, here is the repo that controls the lighting for the project - it's a lot :::: https://github.com/baaahs/sparklemotion

iamh2o avatar Aug 23 '21 08:08 iamh2o

Hi! I came across this very issue, and I've been trying to figure out a conversion method for a personal project in the past week. I created a very simple Arduino library that takes white color temperature into account, which also has a color correction feature for RGBW NeoPixels that mix in a little bit of white into the blue color.

I wouldn't say it's completely perfect, but I hope it helps :) Here's the link to my repo: https://github.com/BertanT/Arduino-RGBWConverter

BertanT avatar Nov 20 '21 11:11 BertanT