From ea0f37f5b9c52a38f72c06c3db055c1832a5bc60 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Wed, 24 Nov 2021 11:02:25 +0100 Subject: [PATCH] CCT bus manager logic simplification CCT from RGB if none set (-1) --- wled00/FX.h | 10 +- wled00/FX_fcn.cpp | 28 +- wled00/bus_manager.h | 84 +- wled00/cfg.cpp | 4 +- wled00/colors.cpp | 52 +- wled00/data/settings_leds.htm | 4 +- wled00/fcn_declare.h | 2 +- wled00/html_settings.h | 7 +- wled00/html_ui.h | 1576 ++++++++++++++++----------------- wled00/json.cpp | 2 +- wled00/set.cpp | 2 +- wled00/wled.h | 4 +- wled00/xml.cpp | 2 +- 13 files changed, 897 insertions(+), 880 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 2195f6e5..669d0366 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -165,12 +165,12 @@ #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 -#define FX_MODE_POLICE 48 +#define FX_MODE_POLICE 48 // candidate for removal (after below three) #define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_TWO_DOTS 50 -#define FX_MODE_TWO_AREAS 51 +#define FX_MODE_TWO_AREAS 51 // candidate for removal #define FX_MODE_RUNNING_DUAL 52 -#define FX_MODE_HALLOWEEN 53 +#define FX_MODE_HALLOWEEN 53 // candidate for removal #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_FADE 56 @@ -247,7 +247,7 @@ class WS2812FX { // segment parameters public: - typedef struct Segment { // 30 (32 in memory) bytes + typedef struct Segment { // 31 (32 in memory) bytes uint16_t start; uint16_t stop; //segment invalid if stop == 0 uint16_t offset; @@ -259,7 +259,7 @@ class WS2812FX { uint8_t grouping, spacing; uint8_t opacity; uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==2000K, 255==10160K + int16_t cct; //-1==auto (no RGB balance correction), 0==1900K, 255==10091K char *name; bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d5163915..eb7f7b5d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -138,6 +138,7 @@ void WS2812FX::service() { if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen _virtualSegmentLength = SEGMENT.virtualLength(); + busses.setSegmentCCT(SEGMENT.cct, correctWB); _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; if (!IS_SEGMENT_ON) _bri_t = 0; for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { @@ -156,6 +157,7 @@ void WS2812FX::service() { } } _virtualSegmentLength = 0; + busses.setSegmentCCT(-1); if(doShow) { yield(); show(); @@ -191,21 +193,6 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) uint16_t realIndex = realPixelIndex(i); uint16_t len = SEGMENT.length(); - // determine if we can do white balance and accurate W calc - // NOTE & TODO: does not work correctly with custom mapping if map spans different strips - int16_t cct = -1; - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); - if (bus == nullptr || !bus->containsPixel(realIndex)) continue; - //if (bus == nullptr || bus->getStart()getStart()+bus->getLength()>realIndex) continue; - uint8_t busType = bus->getType(); - if (allowCCT - || busType == TYPE_ANALOG_2CH - || busType == TYPE_ANALOG_5CH) { - if (cct<0) cct = SEGMENT.cct; - } - } - //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) if (_bri_t < 255) { r = scale8(r, _bri_t); @@ -226,14 +213,14 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) if (indexMir >= SEGMENT.stop) indexMir -= len; if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; - busses.setPixelColor(indexMir, col, cct); + busses.setPixelColor(indexMir, col); } /* offset/phase */ indexSet += SEGMENT.offset; if (indexSet >= SEGMENT.stop) indexSet -= len; if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; - busses.setPixelColor(indexSet, col, cct); + busses.setPixelColor(indexSet, col); } } } else { //live data, etc. @@ -618,7 +605,7 @@ void WS2812FX::resetSegments() { _segments[0].setOption(SEG_OPTION_SELECTED, 1); _segments[0].setOption(SEG_OPTION_ON, 1); _segments[0].opacity = 255; - _segments[0].cct = 128; + _segments[0].cct = -1; for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { @@ -626,7 +613,7 @@ void WS2812FX::resetSegments() { _segments[i].grouping = 1; _segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].opacity = 255; - _segments[i].cct = 128; + _segments[i].cct = -1; _segments[i].speed = DEFAULT_SPEED; _segments[i].intensity = DEFAULT_INTENSITY; _segment_runtimes[i].reset(); @@ -1162,4 +1149,5 @@ uint32_t WS2812FX::gamma32(uint32_t color) return RGBW32(r, g, b, w); } -WS2812FX* WS2812FX::instance = nullptr; \ No newline at end of file +WS2812FX* WS2812FX::instance = nullptr; +int16_t Bus::_cct = -1; \ No newline at end of file diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index cae4ac86..af15d695 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -74,7 +74,7 @@ struct BusConfig { } }; -//parent class of BusDigital and BusPwm +//parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: Bus(uint8_t type, uint16_t start, uint8_t aw) { @@ -88,7 +88,6 @@ class Bus { virtual void show() {} virtual bool canShow() { return true; } virtual void setPixelColor(uint16_t pix, uint32_t c) {}; - virtual void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {}; virtual uint32_t getPixelColor(uint16_t pix) { return 0; }; virtual void setBrightness(uint8_t b) {}; virtual void cleanup() {}; @@ -111,6 +110,9 @@ class Bus { if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; return false; } + static void setCCT(uint16_t cct) { + _cct = cct; + } bool reversed = false; @@ -122,16 +124,18 @@ class Bus { bool _valid = false; bool _needsRefresh = false; uint8_t _autoWhiteMode = 0; + static int16_t _cct; uint32_t autoWhiteCalc(uint32_t c) { if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c; + uint8_t w = W(c); + //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) + if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c; uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); - uint8_t w = W(c); - // ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) - if (!(w > 0 && _autoWhiteMode == RGBW_MODE_DUAL)) w = r < g ? (r < b ? r : b) : (g < b ? g : b); - if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } // subtract w in ACCURATE mode + w = r < g ? (r < b ? r : b) : (g < b ? g : b); + if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode return RGBW32(r, g, b, w); } }; @@ -181,17 +185,13 @@ class BusDigital : public Bus { } void setPixelColor(uint16_t pix, uint32_t c) { - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); + c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT if (reversed) pix = _len - pix -1; else pix += _skip; PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); } - void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) { - c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT - setPixelColor(pix, c); - } - uint32_t getPixelColor(uint16_t pix) { if (reversed) pix = _len - pix -1; else pix += _skip; @@ -282,22 +282,30 @@ class BusPwm : public Bus { _valid = true; }; - void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) { + void setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); - c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT (w remains unchanged) + if (_type == TYPE_ANALOG_3CH && _cct >= 1900) { + c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + } + c = autoWhiteCalc(c); uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); + uint8_t cct = 0; //0 - full warm white, 255 - full cold white + if (_cct > -1) { + if (_cct >= 1900) cct = (_cct - 1900) >> 5; + else if (_cct < 256) cct = _cct; + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; + } switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation - _data[0] = w; //max(r, max(g, max(b, w))); + _data[0] = w; break; case TYPE_ANALOG_2CH: //warm white + cold white // perhaps a non-linear adjustment would be in order. need to test - //w = max(r, max(g, max(b, w))); _data[1] = (w * cct) / 255; _data[0] = (w * (255-cct)) / 255; break; @@ -313,25 +321,6 @@ class BusPwm : public Bus { } } - void setPixelColor(uint16_t pix, uint32_t c) { - if (pix != 0 || !_valid) return; //only react to first pixel - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - uint8_t w = W(c); - - switch (_type) { - case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value - _data[0] = max(r, max(g, max(b, w))); break; - case TYPE_ANALOG_2CH: //warm white + cold white - case TYPE_ANALOG_3CH: //standard dumb RGB - case TYPE_ANALOG_4CH: //standard dumb RGBW - case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB - _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = w; break; - } - } - //does no index check uint32_t getPixelColor(uint16_t pix) { if (!_valid) return 0; @@ -432,7 +421,8 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + c = autoWhiteCalc(c); uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); @@ -440,11 +430,6 @@ class BusNetwork : public Bus { if (_rgbw) _data[offset+3] = W(c); } - void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) { - c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT - setPixelColor(pix, c); - } - uint32_t getPixelColor(uint16_t pix) { if (!_valid || pix >= _len) return 0; uint16_t offset = pix * _UDPchannels; @@ -564,8 +549,7 @@ class BusManager { Bus* b = busses[i]; uint16_t bstart = b->getStart(); if (pix < bstart || pix >= bstart + b->getLength()) continue; - if (cct<0) busses[i]->setPixelColor(pix - bstart, c); // no white balance - else busses[i]->setPixelColor(pix - bstart, c, cct); // do white balance + busses[i]->setPixelColor(pix - bstart, c); } } @@ -575,6 +559,18 @@ class BusManager { } } + void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) { + if (cct > 255) { //kelvin value, convert to 0-255 + if (cct < 1900) cct = 1900; + if (cct > 10091) cct = 10091; + if (!allowWBCorrection) cct = (cct - 1900) >> 5; + } else if (cct > -1) { + //if white balance correction allowed, save as kelvin value instead of 0-255 + if (allowWBCorrection) cct = 1900 + (cct << 5); + } else cct = -1; + Bus::setCCT(cct); + } + uint32_t getPixelColor(uint16_t pix) { for (uint8_t i = 0; i < numBusses; i++) { Bus* b = busses[i]; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index a641d417..8539a486 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -80,7 +80,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); uint8_t rgbwMode = hw_led[F("rgbwm")] | RGBW_MODE_DUAL; // use global setting (legacy) - CJSON(allowCCT, hw_led["cct"]); + CJSON(correctWB, hw_led["cct"]); JsonArray ins = hw_led["ins"]; @@ -520,7 +520,7 @@ void serializeConfig() { hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; - hw_led["cct"] = allowCCT; + hw_led["cct"] = correctWB; JsonArray hw_led_ins = hw_led.createNestedArray("ins"); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index e7e2b7d1..2677f61a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -245,19 +245,10 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M } */ -// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) -void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb) -{ - uint32_t col = RGBW32(rgb[0], rgb[1], rgb[2], 0); - col = colorBalanceFromKelvin(kelvin, col); - rgb[0] = R(col); - rgb[1] = G(col); - rgb[2] = B(col); -} - byte correctionRGB[4] = {0,0,0,0}; uint16_t lastKelvin = 0; +// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) { //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor() @@ -267,6 +258,45 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) 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); + rgbw[3] = W(rgb); return colorFromRgbw(rgbw); +} + +//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; + } } \ No newline at end of file diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 9e6d9277..b141b4d0 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -510,7 +510,9 @@ ${i+1}:
Make a segment for each output:
Custom bus start indices:
- Allow WB correction:
+ White Balance correction:
+ Calculate CCT from RGB: TODO
+ CCT blending mode: TODO

Touch threshold:
diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index eda6bf34..1ba20b33 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -68,8 +68,8 @@ void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TOD void colorFromDecOrHexString(byte* rgb, char* in); bool colorFromHexString(byte* rgb, const char* in); -void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb); uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); +uint16_t approximateKelvinFromRGB(uint32_t rgb); //dmx.cpp void initDMX(); diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 83bc10f7..9cd514e8 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -77,7 +77,7 @@ onclick="B()">Back // Autogenerated from wled00/data/settings_leds.htm, do not edit!! const char PAGE_settings_leds[] PROGMEM = R"=====(LED Settings