.... actually we undo gamma adjustments, because screens (laptop, pad, etc) will apply their own gamma corrections. Activate in LED settings: "Use Gamma correction for preview" Colors in WLED are sometimes gamma corrected, sometimes not. This change tries to make the best out of the color mess, but its still not working properly in all configurations.
404 lines
14 KiB
C++
404 lines
14 KiB
C++
#include "wled.h"
|
|
|
|
/*
|
|
* Color conversion & utility methods
|
|
*/
|
|
|
|
/*
|
|
* color blend function
|
|
*/
|
|
IRAM_ATTR_YN uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) {
|
|
if(blend == 0) return color1;
|
|
uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF;
|
|
if(blend == blendmax) return color2;
|
|
const uint_fast8_t shift = b16 ? 16 : 8;
|
|
|
|
uint32_t w1 = W(color1);
|
|
uint32_t r1 = R(color1);
|
|
uint32_t g1 = G(color1);
|
|
uint32_t b1 = B(color1);
|
|
|
|
uint32_t w2 = W(color2);
|
|
uint32_t r2 = R(color2);
|
|
uint32_t g2 = G(color2);
|
|
uint32_t b2 = B(color2);
|
|
|
|
uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift;
|
|
uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift;
|
|
uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift;
|
|
uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift;
|
|
|
|
return RGBW32(r3, g3, b3, w3);
|
|
}
|
|
|
|
/*
|
|
* color add function that preserves ratio
|
|
* idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule
|
|
*/
|
|
IRAM_ATTR_YN uint32_t color_add(uint32_t c1, uint32_t c2) // WLEDMM added IRAM_ATTR_YN
|
|
{
|
|
uint32_t r = R(c1) + R(c2);
|
|
uint32_t g = G(c1) + G(c2);
|
|
uint32_t b = B(c1) + B(c2);
|
|
uint32_t w = W(c1) + W(c2);
|
|
uint_fast16_t max = r;
|
|
if (g > max) max = g;
|
|
if (b > max) max = b;
|
|
if (w > max) max = w;
|
|
if (max < 256) return RGBW32(r, g, b, w);
|
|
else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max);
|
|
}
|
|
|
|
void setRandomColor(byte* rgb)
|
|
{
|
|
lastRandomIndex = strip.getMainSegment().get_random_wheel_index(lastRandomIndex);
|
|
colorHStoRGB(lastRandomIndex*256,255,rgb);
|
|
}
|
|
|
|
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
|
|
{
|
|
float h = ((float)hue)/65535.0f;
|
|
float s = ((float)sat)/255.0f;
|
|
int i = floorf(h*6);
|
|
float f = h * 6.0f - i;
|
|
int p = int(255.0f * (1.0f-s));
|
|
int q = int(255.0f * (1.0f-f*s));
|
|
int t = int(255.0f * (1.0f-(1.0f-f)*s));
|
|
p = constrain(p, 0, 255);
|
|
q = constrain(q, 0, 255);
|
|
t = constrain(t, 0, 255);
|
|
switch (i%6) {
|
|
case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break;
|
|
case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break;
|
|
case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break;
|
|
case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break;
|
|
case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break;
|
|
case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break;
|
|
}
|
|
}
|
|
|
|
//get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html)
|
|
void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
|
|
{
|
|
int r = 0, g = 0, b = 0;
|
|
float temp = kelvin / 100.0f;
|
|
if (temp <= 66.0f) {
|
|
r = 255;
|
|
g = roundf(99.4708025861f * logf(temp) - 161.1195681661f);
|
|
if (temp <= 19.0f) {
|
|
b = 0;
|
|
} else {
|
|
b = roundf(138.5177312231f * logf((temp - 10.0f)) - 305.0447927307f);
|
|
}
|
|
} else {
|
|
r = roundf(329.698727446f * powf((temp - 60.0f), -0.1332047592f));
|
|
g = roundf(288.1221695283f * powf((temp - 60.0f), -0.0755148492f));
|
|
b = 255;
|
|
}
|
|
//g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish
|
|
rgb[0] = (uint8_t) constrain(r, 0, 255);
|
|
rgb[1] = (uint8_t) constrain(g, 0, 255);
|
|
rgb[2] = (uint8_t) constrain(b, 0, 255);
|
|
rgb[3] = 0;
|
|
}
|
|
|
|
void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins
|
|
{
|
|
//this is only an approximation using WS2812B with gamma correction enabled
|
|
if (mired > 475) {
|
|
rgb[0]=255;rgb[1]=199;rgb[2]=92;//500
|
|
} else if (mired > 425) {
|
|
rgb[0]=255;rgb[1]=213;rgb[2]=118;//450
|
|
} else if (mired > 375) {
|
|
rgb[0]=255;rgb[1]=216;rgb[2]=118;//400
|
|
} else if (mired > 325) {
|
|
rgb[0]=255;rgb[1]=234;rgb[2]=140;//350
|
|
} else if (mired > 275) {
|
|
rgb[0]=255;rgb[1]=243;rgb[2]=160;//300
|
|
} else if (mired > 225) {
|
|
rgb[0]=250;rgb[1]=255;rgb[2]=188;//250
|
|
} else if (mired > 175) {
|
|
rgb[0]=247;rgb[1]=255;rgb[2]=215;//200
|
|
} else {
|
|
rgb[0]=237;rgb[1]=255;rgb[2]=239;//150
|
|
}
|
|
}
|
|
|
|
#ifndef WLED_DISABLE_HUESYNC
|
|
void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
|
|
{
|
|
float z = 1.0f - x - y;
|
|
float X = (1.0f / y) * x;
|
|
float Z = (1.0f / y) * z;
|
|
float r = (int)255*(X * 1.656492f - 0.354851f - Z * 0.255038f);
|
|
float g = (int)255*(-X * 0.707196f + 1.655397f + Z * 0.036152f);
|
|
float b = (int)255*(X * 0.051713f - 0.121364f + Z * 1.011530f);
|
|
if (r > b && r > g && r > 1.0f) {
|
|
// red is too big
|
|
g = g / r;
|
|
b = b / r;
|
|
r = 1.0f;
|
|
} else if (g > b && g > r && g > 1.0f) {
|
|
// green is too big
|
|
r = r / g;
|
|
b = b / g;
|
|
g = 1.0f;
|
|
} else if (b > r && b > g && b > 1.0f) {
|
|
// blue is too big
|
|
r = r / b;
|
|
g = g / b;
|
|
b = 1.0f;
|
|
}
|
|
// Apply gamma correction
|
|
r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * powf(r, (1.0f / 2.4f)) - 0.055f;
|
|
g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * powf(g, (1.0f / 2.4f)) - 0.055f;
|
|
b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * powf(b, (1.0f / 2.4f)) - 0.055f;
|
|
|
|
if (r > b && r > g) {
|
|
// red is biggest
|
|
if (r > 1.0f) {
|
|
g = g / r;
|
|
b = b / r;
|
|
r = 1.0f;
|
|
}
|
|
} else if (g > b && g > r) {
|
|
// green is biggest
|
|
if (g > 1.0f) {
|
|
r = r / g;
|
|
b = b / g;
|
|
g = 1.0f;
|
|
}
|
|
} else if (b > r && b > g) {
|
|
// blue is biggest
|
|
if (b > 1.0f) {
|
|
r = r / b;
|
|
g = g / b;
|
|
b = 1.0f;
|
|
}
|
|
}
|
|
rgb[0] = byte(255.0f*r);
|
|
rgb[1] = byte(255.0f*g);
|
|
rgb[2] = byte(255.0f*b);
|
|
}
|
|
|
|
void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
|
|
{
|
|
float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f;
|
|
float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f;
|
|
float Z = rgb[0] * 0.000088f + rgb[1] * 0.072310f + rgb[2] * 0.986039f;
|
|
xy[0] = X / (X + Y + Z);
|
|
xy[1] = Y / (X + Y + Z);
|
|
}
|
|
#endif // WLED_DISABLE_HUESYNC
|
|
|
|
//RRGGBB / WWRRGGBB order for hex
|
|
void colorFromDecOrHexString(byte* rgb, char* in)
|
|
{
|
|
if (in[0] == 0) return;
|
|
char first = in[0];
|
|
uint32_t c = 0;
|
|
|
|
if (first == '#' || first == 'h' || first == 'H') //is HEX encoded
|
|
{
|
|
c = strtoul(in +1, NULL, 16);
|
|
} else
|
|
{
|
|
c = strtoul(in, NULL, 10);
|
|
}
|
|
|
|
rgb[0] = R(c);
|
|
rgb[1] = G(c);
|
|
rgb[2] = B(c);
|
|
rgb[3] = W(c);
|
|
}
|
|
|
|
//contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order
|
|
bool colorFromHexString(byte* rgb, const char* in) {
|
|
if (in == nullptr) return false;
|
|
size_t inputSize = strnlen(in, 9);
|
|
if (inputSize != 6 && inputSize != 8) return false;
|
|
|
|
uint32_t c = strtoul(in, NULL, 16);
|
|
|
|
if (inputSize == 6) {
|
|
rgb[0] = (c >> 16);
|
|
rgb[1] = (c >> 8);
|
|
rgb[2] = c ;
|
|
} else {
|
|
rgb[0] = (c >> 24);
|
|
rgb[1] = (c >> 16);
|
|
rgb[2] = (c >> 8);
|
|
rgb[3] = c ;
|
|
}
|
|
return true;
|
|
}
|
|
#if 0 // WLEDMM minf/maxf are defined in libm already
|
|
static float minf (float v, float w) // WLEDMM better use standard library fminf()
|
|
{
|
|
if (w > v) return v;
|
|
return w;
|
|
}
|
|
|
|
static float maxf (float v, float w) // WLEDMM better use standard library fmaxf()
|
|
{
|
|
if (w > v) return w;
|
|
return v;
|
|
}
|
|
#endif
|
|
|
|
// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance)
|
|
// called from bus manager when color correction is enabled!
|
|
uint32_t IRAM_ATTR_YN colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) // WLEDMM: IRAM_ATTR_YN
|
|
{
|
|
//remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor()
|
|
static byte correctionRGB[4] = {0,0,0,0};
|
|
static uint16_t lastKelvin = 0;
|
|
if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB
|
|
lastKelvin = kelvin;
|
|
byte rgbw[4];
|
|
rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R
|
|
rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G
|
|
rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B
|
|
rgbw[3] = W(rgb);
|
|
return RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]);
|
|
}
|
|
|
|
//approximates a Kelvin color temperature from an RGB color.
|
|
//this does no check for the "whiteness" of the color,
|
|
//so should be used combined with a saturation check (as done by auto-white)
|
|
//values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg)
|
|
//equation spreadsheet at https://bit.ly/30RkHaN
|
|
//accuracy +-50K from 1900K up to 8000K
|
|
//minimum returned: 1900K, maximum returned: 10091K (range of 8192)
|
|
uint16_t approximateKelvinFromRGB(uint32_t rgb) {
|
|
//if not either red or blue is 255, color is dimmed. Scale up
|
|
uint8_t r = R(rgb), b = B(rgb);
|
|
if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0)
|
|
|
|
if (r > b) {
|
|
//scale blue up as if red was at 255
|
|
uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535)
|
|
b = ((uint16_t)b * scale) >> 8;
|
|
//For all temps K<6600 R is bigger than B (for full bri colors R=255)
|
|
//-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K)
|
|
if (b < 33) return 1900 + b *6;
|
|
if (b < 72) return 2100 + (b-33) *10;
|
|
if (b < 101) return 2492 + (b-72) *14;
|
|
if (b < 132) return 2900 + (b-101) *16;
|
|
if (b < 159) return 3398 + (b-132) *19;
|
|
if (b < 186) return 3906 + (b-159) *22;
|
|
if (b < 210) return 4500 + (b-186) *25;
|
|
if (b < 230) return 5100 + (b-210) *30;
|
|
return 5700 + (b-230) *34;
|
|
} else {
|
|
//scale red up as if blue was at 255
|
|
uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535)
|
|
r = ((uint16_t)r * scale) >> 8;
|
|
//For all temps K>6600 B is bigger than R (for full bri colors B=255)
|
|
//-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K)
|
|
if (r > 225) return 6600 + (254-r) *50;
|
|
uint16_t k = 8080 + (225-r) *86;
|
|
return (k > 10091) ? 10091 : k;
|
|
}
|
|
}
|
|
|
|
#if !defined(WLED_USE_CIE_BRIGHTNESS_TABLE)
|
|
//gamma 2.8 lookup table used for color correction
|
|
static byte gammaT[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
|
|
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
|
|
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
|
|
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
|
|
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
|
|
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
|
|
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
|
|
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
|
|
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
|
|
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
|
|
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
|
|
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
|
|
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
|
|
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
|
|
#else
|
|
// experimental
|
|
// CIE 1931 lookup table (8bit->8bit) that was proposed during discussion of issue #2767
|
|
// https://github.com/Aircoookie/WLED/issues/2767#issuecomment-1310961308
|
|
// unfortunately NepixelsBu has its own internal table, that kills low brightness values similar to the original WLED table.
|
|
// see https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoGamma.h
|
|
static const byte gammaT[256] = {
|
|
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4,
|
|
4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
|
|
7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 10, 11,
|
|
11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17,
|
|
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24,
|
|
25, 25, 26, 27, 27, 28, 28, 29, 30, 30, 31, 31, 32, 33, 33, 34,
|
|
35, 35, 36, 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46,
|
|
47, 48, 49, 50, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
|
|
62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78,
|
|
79, 81, 82, 83, 84, 85, 87, 88, 89, 91, 92, 93, 94, 96, 97, 99,
|
|
100, 101, 103, 104, 106, 107, 109, 110, 111, 113, 115, 116, 118, 119, 121,
|
|
122, 124, 126, 127, 129, 130, 132, 134, 135, 137, 139, 141, 142, 144, 146,
|
|
148, 150, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 170, 172, 174,
|
|
177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 198, 200, 202, 204, 207,
|
|
209, 211, 213, 216, 218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242,
|
|
245, 247, 250, 252, 255 };
|
|
#endif
|
|
|
|
// WLEDMM begin
|
|
static uint8_t gammaTinv[256] = { 0 };
|
|
static void calcInvGammaTable(float gamma)
|
|
{
|
|
float gammaInv = 1.0f / 2.4f; // surprise surprise: WLED palettes use a fixed gamma of 2.4 !!!
|
|
//float gammaInv = 1.0f / gamma; // if we go by the book, 1.0/gamma will revert gamma corrections
|
|
for (size_t i = 0; i < 256; i++) {
|
|
gammaTinv[i] = (int)(powf((float)i / 255.0f, gammaInv) * 255.0f + 0.5f);
|
|
}
|
|
}
|
|
uint8_t unGamma8(uint8_t value) {
|
|
if (!gammaCorrectCol || (value == 0) || (value == 255)) return value;
|
|
if ((gammaCorrectVal <= 1.0f) || (gammaCorrectVal > 3.0f)) return value;
|
|
if (gammaTinv[255] == 0) calcInvGammaTable(gammaCorrectVal);
|
|
return gammaTinv[value];
|
|
}
|
|
// wleDMM end
|
|
|
|
uint8_t gamma8_cal(uint8_t b, float gamma)
|
|
{
|
|
return (int)(powf((float)b / 255.0f, gamma) * 255.0f + 0.5f);
|
|
}
|
|
|
|
// re-calculates & fills gamma table
|
|
void calcGammaTable(float gamma)
|
|
{
|
|
#if !defined(WLED_USE_CIE_BRIGHTNESS_TABLE) // WLEDMM not possible when using the CIE table
|
|
for (uint16_t i = 0; i < 256; i++) {
|
|
gammaT[i] = gamma8_cal(i, gamma);
|
|
}
|
|
#endif
|
|
calcInvGammaTable(gamma); // WLEDMM
|
|
}
|
|
|
|
// used for individual channel or brightness gamma correction
|
|
IRAM_ATTR_YN uint8_t gamma8(uint8_t b) // WLEDMM added IRAM_ATTR_YN
|
|
{
|
|
return gammaT[b];
|
|
}
|
|
|
|
// used for color gamma correction
|
|
uint32_t gamma32(uint32_t color)
|
|
{
|
|
if (!gammaCorrectCol) return color;
|
|
uint8_t w = W(color);
|
|
uint8_t r = R(color);
|
|
uint8_t g = G(color);
|
|
uint8_t b = B(color);
|
|
w = gammaT[w];
|
|
r = gammaT[r];
|
|
g = gammaT[g];
|
|
b = gammaT[b];
|
|
return RGBW32(r, g, b, w);
|
|
}
|