diff --git a/wled00/FX.h b/wled00/FX.h index 8a969181..3b6bfab6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -166,7 +166,7 @@ #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 #define FX_MODE_POLICE 48 -#define FX_MODE_POLICE_ALL 49 +#define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_TWO_DOTS 50 #define FX_MODE_TWO_AREAS 51 #define FX_MODE_RUNNING_DUAL 52 @@ -231,7 +231,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -#define FX_MODE_CANDY_CANE 114 +#define FX_MODE_CANDY_CANE 114 // candidate for removal #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 @@ -259,7 +259,7 @@ class WS2812FX { uint8_t grouping, spacing; uint8_t opacity; uint32_t colors[NUM_COLORS]; - uint8_t cct; + uint8_t cct; //0==2000K, 255==10160K 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; @@ -658,7 +658,6 @@ class WS2812FX { uint8_t mainSegment = 0, - rgbwMode = RGBW_MODE_DUAL, paletteFade = 0, paletteBlend = 0, milliampsPerLed = 55, diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 05914e38..d2282939 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -192,21 +192,25 @@ uint16_t WS2812FX::realPixelIndex(uint16_t i) { void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) { - //auto calculate white channel value if enabled - if (isRgbw) { - if (rgbwMode == RGBW_MODE_AUTO_BRIGHTER || (w == 0 && (rgbwMode == RGBW_MODE_DUAL || rgbwMode == RGBW_MODE_LEGACY))) - { - //white value is set to lowest RGB channel - //thank you to @Def3nder! - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - } else if (rgbwMode == RGBW_MODE_AUTO_ACCURATE && w == 0) - { - w = r < g ? (r < b ? r : b) : (g < b ? g : b); - r -= w; g -= w; b -= w; - } - } - if (SEGLEN) {//from segment + 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); @@ -217,20 +221,6 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b)); /* Set all the pixels in the group */ - uint16_t realIndex = realPixelIndex(i); - uint16_t len = SEGMENT.length(); - - // determine if we can do white balance - 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 (allowCCT || bus->getType() == TYPE_ANALOG_2CH || bus->getType() == TYPE_ANALOG_5CH) { - cct = SEGMENT.cct; - break; - } - } - for (uint16_t j = 0; j < SEGMENT.grouping; j++) { uint16_t indexSet = realIndex + (IS_REVERSE ? -j : j); if (indexSet >= SEGMENT.start && indexSet < SEGMENT.stop) { diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index ffe6a04f..172bb55e 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -10,8 +10,9 @@ #include "bus_wrapper.h" #include -//color.cpp +//colors.cpp uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); +void colorRGBtoRGBW(byte* rgb); // enable additional debug output #ifdef WLED_DEBUG @@ -34,17 +35,18 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); //temporary struct for passing bus configuration to bus struct BusConfig { uint8_t type = TYPE_WS2812_RGB; - uint16_t count = 1; - uint16_t start = 0; - uint8_t colorOrder = COL_ORDER_GRB; - bool reversed = false; + uint16_t count; + uint16_t start; + uint8_t colorOrder; + bool reversed; uint8_t skipAmount; bool refreshReq; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0) { + uint8_t autoWhite; + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, uint8_t aw = 0) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; + count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; uint8_t nPins = 1; if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address else if (type > 47) nPins = 2; @@ -68,9 +70,10 @@ struct BusConfig { //parent class of BusDigital and BusPwm class Bus { public: - Bus(uint8_t type, uint16_t start) { + Bus(uint8_t type, uint16_t start, uint8_t aw) { _type = type; _start = start; + _autoWhiteMode = isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY; }; virtual ~Bus() {} //throw the bus under the bus @@ -83,19 +86,19 @@ class Bus { virtual void setBrightness(uint8_t b) {}; virtual void cleanup() {}; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return 1; } + inline uint16_t getLength() { return _len; } virtual void setColorOrder() {} virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } + inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } + inline uint16_t getStart() { return _start; } + inline void setStart(uint16_t start) { _start = start; } + inline uint8_t getType() { return _type; } + inline bool isOk() { return _valid; } + inline bool isOffRefreshRequired() { return _needsRefresh; } + bool containsPixel(uint16_t pix) { return pix >= _start && pix < _start+_len; } - inline uint16_t getStart() { return _start; } - inline void setStart(uint16_t start) { _start = start; } - inline uint8_t getType() { return _type; } - inline bool isOk() { return _valid; } - inline bool isOffRefreshRequired() { return _needsRefresh; } - inline bool containsPixel(uint16_t pix) { return pix >= _start; } - - virtual bool isRgbw() { return false; } + virtual bool isRgbw() { return Bus::isRgbw(_type); } static bool isRgbw(uint8_t type) { if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true; if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; @@ -108,14 +111,43 @@ class Bus { uint8_t _type = TYPE_NONE; uint8_t _bri = 255; uint16_t _start = 0; + uint16_t _len = 1; bool _valid = false; bool _needsRefresh = false; + uint8_t _autoWhiteMode = 0; + + uint32_t autoWhiteCalc(uint32_t c) { + switch (_autoWhiteMode) { + case RGBW_MODE_MANUAL_ONLY: + break; + case RGBW_MODE_LEGACY: + byte rgb[4]; + rgb[0] = c >> 16; + rgb[1] = c >> 8; + rgb[2] = c ; + rgb[3] = c >> 24; + colorRGBtoRGBW(rgb); + c = ((rgb[3] << 24) | (rgb[0] << 16) | (rgb[1] << 8) | (rgb[2])); + break; + default: + //white value is set to lowest RGB channel, thank you to @Def3nder! + uint8_t r = c >> 16; + uint8_t g = c >> 8; + uint8_t b = c ; + uint8_t w = c >> 24; + if (_autoWhiteMode == RGBW_MODE_AUTO_BRIGHTER || w == 0) w = r < g ? (r < b ? r : b) : (g < b ? g : b); + if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } + c = ((w << 24) | (r << 16) | (g << 8) | (b)); + break; + } + return c; + } }; class BusDigital : public Bus { public: - BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start) { + BusDigital(BusConfig &bc, uint8_t nr) : Bus(bc.type, bc.start, bc.autoWhite) { if (!IS_DIGITAL(bc.type) || !bc.count) return; if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _pins[0] = bc.pins[0]; @@ -157,6 +189,7 @@ class BusDigital : public Bus { } void setPixelColor(uint16_t pix, uint32_t c) { + if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); if (reversed) pix = _len - pix -1; else pix += _skip; PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); @@ -192,10 +225,6 @@ class BusDigital : public Bus { _colorOrder = colorOrder; } - inline bool isRgbw() { - return Bus::isRgbw(_type); - } - inline uint8_t skippedLeds() { return _skip; } @@ -222,7 +251,6 @@ class BusDigital : public Bus { uint8_t _colorOrder = COL_ORDER_GRB; uint8_t _pins[2] = {255, 255}; uint8_t _iType = I_NONE; - uint16_t _len = 0; uint8_t _skip = 0; void * _busPtr = nullptr; }; @@ -230,7 +258,7 @@ class BusDigital : public Bus { class BusPwm : public Bus { public: - BusPwm(BusConfig &bc) : Bus(bc.type, bc.start) { + BusPwm(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _valid = false; if (!IS_PWM(bc.type)) return; uint8_t numPins = NUM_PWM_PINS(bc.type); @@ -265,23 +293,26 @@ class BusPwm : public Bus { void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) { if (pix != 0 || !_valid) return; //only react to first pixel c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT (w remains unchanged) + if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); uint8_t r = c >> 16; uint8_t g = c >> 8; uint8_t b = c ; uint8_t w = c >> 24; switch (_type) { - case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value - _data[0] = max(r, max(g, max(b, w))); + case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation + _data[0] = w; //max(r, max(g, max(b, 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] = 255 - _data[1]; // or (w * (255-cct)) / 255; + _data[0] = (w * (255-cct)) / 255; break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white // perhaps a non-linear adjustment would be in order. need to test - _data[4] = (w * cct) / 255; w = 255 - w; // or (w * (255-cct)) / 255; + _data[4] = (w * cct) / 255; + w = (w * (255-cct)) / 255; case TYPE_ANALOG_4CH: //RGBW _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB @@ -292,6 +323,7 @@ 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 = c >> 16; uint8_t g = c >> 8; uint8_t b = c ; @@ -304,7 +336,7 @@ class BusPwm : public Bus { 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] = 0; break; + _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = w; break; } } @@ -335,14 +367,12 @@ class BusPwm : public Bus { uint8_t getPins(uint8_t* pinArray) { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); - for (uint8_t i = 0; i < numPins; i++) pinArray[i] = _pins[i]; + for (uint8_t i = 0; i < numPins; i++) { + pinArray[i] = _pins[i]; + } return numPins; } - bool isRgbw() { - return Bus::isRgbw(_type); - } - inline void cleanup() { deallocatePins(); } @@ -378,7 +408,7 @@ class BusPwm : public Bus { class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start) { + BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _valid = false; // switch (bc.type) { // case TYPE_NET_ARTNET_RGB: @@ -399,12 +429,10 @@ class BusNetwork : public Bus { // break; // } _UDPchannels = _rgbw ? 4 : 3; - //_rgbw |= bc.rgbwOverride; // RGBW override in bit 7 or can have a special type _data = (byte *)malloc(bc.count * _UDPchannels); if (_data == nullptr) return; memset(_data, 0, bc.count * _UDPchannels); _len = bc.count; - //_colorOrder = bc.colorOrder; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); _broadcastLock = false; _valid = true; @@ -412,6 +440,7 @@ 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); uint16_t offset = pix * _UDPchannels; _data[offset] = 0xFF & (c >> 16); _data[offset+1] = 0xFF & (c >> 8); @@ -479,8 +508,6 @@ class BusNetwork : public Bus { private: IPAddress _client; - uint16_t _len = 0; - //uint8_t _colorOrder; uint8_t _bri = 255; uint8_t _UDPtype; uint8_t _UDPchannels; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index d0bbc6f8..4aaa6ecb 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -61,7 +61,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(apBehavior, ap[F("behav")]); - /* JsonArray ap_ip = ap["ip"]; for (byte i = 0; i < 4; i++) { @@ -83,7 +82,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); - CJSON(strip.rgbwMode, hw_led[F("rgbwm")]); + uint8_t rgbwMode = hw_led[F("rgbwm")] | RGBW_MODE_DUAL; // use global setting (legacy) CJSON(allowCCT, hw_led["cct"]); JsonArray ins = hw_led["ins"]; @@ -112,13 +111,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { uint16_t start = elm["start"] | 0; if (length==0 || start + length > MAX_LEDS) continue; // zero length or we reached max. number of LEDs, just stop uint8_t ledType = elm["type"] | TYPE_WS2812_RGB; + uint8_t awMode = elm[F("rgbwm")] | rgbwMode; bool reversed = elm["rev"]; bool refresh = elm["ref"] | false; ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh s++; uint16_t busEnd = start + length; if (busEnd > lC) lC = busEnd; - BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst); + BusConfig bc = BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, awMode); mem += BusManager::memUsage(bc); if (mem <= MAX_LED_MEMORY && busses.getNumBusses() <= WLED_MAX_BUSSES) busses.add(bc); // finalization will be done in WLED::beginStrip() } @@ -530,7 +530,6 @@ void serializeConfig() { hw_led[F("total")] = ledCount; hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; - hw_led[F("rgbwm")] = strip.rgbwMode; hw_led["cct"] = allowCCT; JsonArray hw_led_ins = hw_led.createNestedArray("ins"); @@ -551,6 +550,7 @@ void serializeConfig() { ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); ins[F("rgbw")] = bus->isRgbw(); + ins[F("rgbwm")] = bus->getAutoWhiteMode(); } // button(s) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index dfdd53e0..a1a9523d 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -64,7 +64,6 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break; case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q; } - if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc @@ -111,7 +110,6 @@ void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins } else { rgb[0]=237;rgb[1]=255;rgb[2]=239;//150 } - if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } #ifndef WLED_DISABLE_HUESYNC @@ -169,7 +167,6 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www rgb[0] = 255.0*r; rgb[1] = 255.0*g; rgb[2] = 255.0*b; - if (strip.isRgbw && strip.rgbwMode == RGBW_MODE_LEGACY) colorRGBtoRGBW(col); } void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) @@ -244,3 +241,24 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M float sat = 100.0f * ((high - low) / high);; // maximum saturation is 100 (corrected from 255) rgb[3] = (byte)((255.0f - sat) / 255.0f * (rgb[0] + rgb[1] + rgb[2]) / 3); } + +// 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) +{ + byte rgbw[4] = {0,0,0,0}; + colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB + rgb[0] = ((uint16_t) rgbw[0] * rgb[0]) / 255; // correct R + rgb[1] = ((uint16_t) rgbw[1] * rgb[1]) / 255; // correct G + rgb[2] = ((uint16_t) rgbw[2] * rgb[2]) / 255; // correct B +} + +uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) +{ + byte rgbw[4] = {0,0,0,0}; + colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB + rgbw[0] = ((uint16_t) rgbw[0] * ((rgb>>16) & 0xFF)) / 255; // correct R + rgbw[1] = ((uint16_t) rgbw[1] * ((rgb>> 8) & 0xFF)) / 255; // correct G + rgbw[2] = ((uint16_t) rgbw[2] * ((rgb ) & 0xFF)) / 255; // correct B + rgbw[3] = ((rgb>>24) & 0xFF); + return colorFromRgbw(rgbw); +} diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 650d0d7c..5dc99d29 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -170,6 +170,7 @@ gId("dig"+n+"r").style.display = (t>=80 && t<96) ? "none":"inline"; // hide reversed for virtual gId("dig"+n+"s").style.display = ((t>=80 && t<96) || (t > 40 && t < 48)) ? "none":"inline"; // hide skip 1st for virtual & analog gId("dig"+n+"f").style.display = (t>=16 && t<32 || t>=50 && t<64) ? "inline":"none"; // hide refresh + gId("dig"+n+"a").style.display = (isRGBW) ? "inline":"none"; // auto calculate white gId("rev"+n).innerHTML = (t > 40 && t < 48) ? "Inverted output":"Reversed (rotated 180°)"; // change reverse text for analog gId("psd"+n).innerHTML = (t > 40 && t < 48) ? "Index:":"Start:"; // change analog start description } @@ -334,6 +335,7 @@ ${i+1}:

Reversed:

Skip 1st LED:

Off Refresh:  
+

Auto-calculate white channel from RGB:
 
`; f.insertAdjacentHTML("beforeend", cn); } @@ -558,16 +560,6 @@ ${i+1}:
- - Auto-calculate white channel from RGB:
- -

Config template:

diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 23c21fc0..fd1c5fb8 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