From 5be1bc0278d1a1f1ab37534b8bc755e93b399e8e Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 31 Jul 2025 07:23:04 +0200 Subject: [PATCH 1/7] proof of concept: use video scaling instead of NPB luminance --- wled00/bus_wrapper.h | 122 +++++++++---------------------------------- 1 file changed, 25 insertions(+), 97 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 5d8f306f5e..bf8ca5c58d 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -5,6 +5,8 @@ //#define NPB_CONF_4STEP_CADENCE #include "NeoPixelBusLg.h" +uint8_t globalBrightness = 255; // global brightness for digital busses, set by setBrightness() + //Hardware SPI Pins #define P_8266_HS_MOSI 13 #define P_8266_HS_CLK 14 @@ -775,11 +777,29 @@ class PolyBus { return true; } +#define R(c) (byte((c) >> 16)) +#define G(c) (byte((c) >> 8)) +#define B(c) (byte(c)) +#define W(c) (byte((c) >> 24)) + [[gnu::hot]] static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { - uint8_t r = c >> 16; - uint8_t g = c >> 8; - uint8_t b = c >> 0; - uint8_t w = c >> 24; + + // apply global brightness using video scaling: keep a minimum of 1 for each channel if non zero + uint32_t addRemains = 0; + addRemains = R(c) ? 0x00010000 : 0; + addRemains |= G(c) ? 0x00000100 : 0; + addRemains |= B(c) ? 0x00000001 : 0; + addRemains |= W(c) ? 0x01000000 : 0; + + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; + uint32_t rb = (((c & TWO_CHANNEL_MASK) * globalBrightness) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * globalBrightness) & ~TWO_CHANNEL_MASK; // scale white and green + c = (rb | wg) + addRemains; + + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + uint8_t w = W(c); RgbwColor col; uint8_t cctWW = wwcw & 0xFF, cctCW = (wwcw>>8) & 0xFF; @@ -897,99 +917,7 @@ class PolyBus { } static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { - switch (busType) { - case I_NONE: break; - #ifdef ESP8266 - case I_8266_U0_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U0_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_U1_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_DM_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_8266_BB_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; - #endif - #ifdef ARDUINO_ARCH_ESP32 - // RMT buses - case I_32_RN_NEO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_NEO_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_400_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_TM1_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_TM2_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_UCS_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_UCS_4: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_APA106_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_FW6_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_2805_5: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_TM1914_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_32_RN_SM16825_5: (static_cast(busPtr))->SetLuminance(b); break; - // I2S1 bus or paralell buses - #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_NEO_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_400_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_TM1_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_TM2_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_UCS_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_UCS_4: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_APA106_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_FW6_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_2805_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_TM1914_3: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - case I_32_I2_SM16825_5: if (_useParallelI2S) (static_cast(busPtr))->SetLuminance(b); else (static_cast(busPtr))->SetLuminance(b); break; - #endif - #endif - case I_HS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_SS_DOT_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_HS_LPD_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_SS_LPD_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_HS_LPO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_SS_LPO_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_HS_WS1_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_SS_WS1_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_HS_P98_3: (static_cast(busPtr))->SetLuminance(b); break; - case I_SS_P98_3: (static_cast(busPtr))->SetLuminance(b); break; - } + globalBrightness = b; } [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { From a3c6651a9b9063d361c7efacf5c45902e201bd68 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 31 Jul 2025 20:01:51 +0200 Subject: [PATCH 2/7] updated color scaling to preserve hue --- wled00/bus_wrapper.h | 58 +++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index bf8ca5c58d..c05a15b288 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -5,7 +5,7 @@ //#define NPB_CONF_4STEP_CADENCE #include "NeoPixelBusLg.h" -uint8_t globalBrightness = 255; // global brightness for digital busses, set by setBrightness() +uint32_t globalBrightness = 255; // global brightness for digital busses, set by setBrightness() //Hardware SPI Pins #define P_8266_HS_MOSI 13 @@ -784,27 +784,45 @@ class PolyBus { [[gnu::hot]] static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { - // apply global brightness using video scaling: keep a minimum of 1 for each channel if non zero - uint32_t addRemains = 0; - addRemains = R(c) ? 0x00010000 : 0; - addRemains |= G(c) ? 0x00000100 : 0; - addRemains |= B(c) ? 0x00000001 : 0; - addRemains |= W(c) ? 0x01000000 : 0; - - const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; - uint32_t rb = (((c & TWO_CHANNEL_MASK) * globalBrightness) >> 8) & TWO_CHANNEL_MASK; // scale red and blue - uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * globalBrightness) & ~TWO_CHANNEL_MASK; // scale white and green - c = (rb | wg) + addRemains; - - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - uint8_t w = W(c); RgbwColor col; uint8_t cctWW = wwcw & 0xFF, cctCW = (wwcw>>8) & 0xFF; + uint8_t r, g, b, w; + if(c > 0) { + if(globalBrightness < 255) { + // apply global brightness using video scaling: keep a minimum of 1 for each channel if non zero but preserve hue + uint8_t origR = R(c); + uint8_t origG = G(c); + uint8_t origB = B(c); - // reorder channels to selected order - switch (co & 0x0F) { + // determine dominant channel for hue preservation + uint8_t maxc = (origR > origG) ? ((origR > origB) ? origR : origB) : ((origG > origB) ? origG : origB); + uint8_t halfMax = maxc >> 1; + + // apply global brightness note: adding +128 for rounding makes virtually no visual difference + r = (origR * globalBrightness) >> 8; + g = (origG * globalBrightness) >> 8; + b = (origB * globalBrightness) >> 8; + w = (W(c) * globalBrightness) >> 8; + + // Hue-preserving rule: + r |= ((r == 0) ? 1 : 0) & ((origR > halfMax) ? 1 : 0); + g |= ((g == 0) ? 1 : 0) & ((origG > halfMax) ? 1 : 0); + b |= ((b == 0) ? 1 : 0) & ((origB > halfMax) ? 1 : 0); + } + else { + r = R(c); + g = G(c); + b = B(c); + w = W(c); + } + + } + else { + col = 0; + } + + // reorder channels to selected order + switch (co & 0x0F) { default: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default case 1: col.G = r; col.R = g; col.B = b; break; //1 = RGB, common for WS2811 case 2: col.G = b; col.R = r; col.B = g; break; //2 = BRG @@ -917,7 +935,7 @@ class PolyBus { } static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { - globalBrightness = b; + globalBrightness = b; // use bit shifts and add 1/2 for proper rounding } [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { From eb2de8a57fb95684b4469922300717bcd151bbcf Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 1 Aug 2025 12:17:59 +0200 Subject: [PATCH 3/7] Replace NPBlg with NPB, moved brightness scaling to show() - mproved gamma table calculation for low values - fixed mismatch in inverting gamma table calculation: inversion should now be as good as it gets --- wled00/FX_fcn.cpp | 35 ++++++- wled00/bus_wrapper.h | 244 ++++++++++++++++++------------------------- wled00/colors.cpp | 9 +- 3 files changed, 144 insertions(+), 144 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 32e34faf98..366cc89b89 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1654,7 +1654,40 @@ void WS2812FX::show() { if (_pixelCCT) { // cctFromRgb already exluded at allocation if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB); } - BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i])); + + uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32) + if(c > 0) { + if(!(realtimeMode && arlsDisableGammaCorrection)) + c = gamma32(c); // apply gamma correction if enabled + if(_brightness < 255) { + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + uint8_t w = W(c); + + // determine dominant channel for hue preservation + uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); + uint8_t halfMax = (maxc - 1) >> 1; // maxc is always > 0 + uint8_t scale = _brightness + 1; // add one to correct for bit shift + uint32_t addRemains = 0; + // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue + addRemains = r && r > halfMax ? 0x00010000 : 0; + addRemains |= g && g > halfMax ? 0x00000100 : 0; + addRemains |= b && b > halfMax ? 0x00000001 : 0; + + // apply global brightness + r = (r * _brightness) >> 8; + g = (g * _brightness) >> 8; + b = (b * _brightness) >> 8; + w = (w * _brightness) >> 8; + + const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; + uint32_t rb = (((c & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green + c = (rb | wg) + addRemains; + } + } + BusManager::setPixelColor(getMappedPixelIndex(i), c ); } Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index c05a15b288..bb123f52aa 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -3,7 +3,7 @@ #define BusWrapper_h //#define NPB_CONF_4STEP_CADENCE -#include "NeoPixelBusLg.h" +#include "NeoPixelBus.h" uint32_t globalBrightness = 255; // global brightness for digital busses, set by setBrightness() @@ -143,65 +143,65 @@ uint32_t globalBrightness = 255; // global brightness for digital busses, set by /*** ESP8266 Neopixel methods ***/ #ifdef ESP8266 //RGB -#define B_8266_U0_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio1 -#define B_8266_U1_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio2 -#define B_8266_DM_NEO_3 NeoPixelBusLg //3 chan, esp8266, gpio3 -#define B_8266_BB_NEO_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) +#define B_8266_U0_NEO_3 NeoPixelBus //3 chan, esp8266, gpio1 +#define B_8266_U1_NEO_3 NeoPixelBus //3 chan, esp8266, gpio2 +#define B_8266_DM_NEO_3 NeoPixelBus //3 chan, esp8266, gpio3 +#define B_8266_BB_NEO_3 NeoPixelBus //3 chan, esp8266, bb (any pin but 16) //RGBW -#define B_8266_U0_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio1 -#define B_8266_U1_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio2 -#define B_8266_DM_NEO_4 NeoPixelBusLg //4 chan, esp8266, gpio3 -#define B_8266_BB_NEO_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) +#define B_8266_U0_NEO_4 NeoPixelBus //4 chan, esp8266, gpio1 +#define B_8266_U1_NEO_4 NeoPixelBus //4 chan, esp8266, gpio2 +#define B_8266_DM_NEO_4 NeoPixelBus //4 chan, esp8266, gpio3 +#define B_8266_BB_NEO_4 NeoPixelBus //4 chan, esp8266, bb (any pin) //400Kbps -#define B_8266_U0_400_3 NeoPixelBusLg //3 chan, esp8266, gpio1 -#define B_8266_U1_400_3 NeoPixelBusLg //3 chan, esp8266, gpio2 -#define B_8266_DM_400_3 NeoPixelBusLg //3 chan, esp8266, gpio3 -#define B_8266_BB_400_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin) +#define B_8266_U0_400_3 NeoPixelBus //3 chan, esp8266, gpio1 +#define B_8266_U1_400_3 NeoPixelBus //3 chan, esp8266, gpio2 +#define B_8266_DM_400_3 NeoPixelBus //3 chan, esp8266, gpio3 +#define B_8266_BB_400_3 NeoPixelBus //3 chan, esp8266, bb (any pin) //TM1814 (RGBW) -#define B_8266_U0_TM1_4 NeoPixelBusLg -#define B_8266_U1_TM1_4 NeoPixelBusLg -#define B_8266_DM_TM1_4 NeoPixelBusLg -#define B_8266_BB_TM1_4 NeoPixelBusLg +#define B_8266_U0_TM1_4 NeoPixelBus +#define B_8266_U1_TM1_4 NeoPixelBus +#define B_8266_DM_TM1_4 NeoPixelBus +#define B_8266_BB_TM1_4 NeoPixelBus //TM1829 (RGB) -#define B_8266_U0_TM2_3 NeoPixelBusLg -#define B_8266_U1_TM2_3 NeoPixelBusLg -#define B_8266_DM_TM2_3 NeoPixelBusLg -#define B_8266_BB_TM2_3 NeoPixelBusLg +#define B_8266_U0_TM2_3 NeoPixelBus +#define B_8266_U1_TM2_3 NeoPixelBus +#define B_8266_DM_TM2_3 NeoPixelBus +#define B_8266_BB_TM2_3 NeoPixelBus //UCS8903 -#define B_8266_U0_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio1 -#define B_8266_U1_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio2 -#define B_8266_DM_UCS_3 NeoPixelBusLg //3 chan, esp8266, gpio3 -#define B_8266_BB_UCS_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) +#define B_8266_U0_UCS_3 NeoPixelBus //3 chan, esp8266, gpio1 +#define B_8266_U1_UCS_3 NeoPixelBus //3 chan, esp8266, gpio2 +#define B_8266_DM_UCS_3 NeoPixelBus //3 chan, esp8266, gpio3 +#define B_8266_BB_UCS_3 NeoPixelBus //3 chan, esp8266, bb (any pin but 16) //UCS8904 RGBW -#define B_8266_U0_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio1 -#define B_8266_U1_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio2 -#define B_8266_DM_UCS_4 NeoPixelBusLg //4 chan, esp8266, gpio3 -#define B_8266_BB_UCS_4 NeoPixelBusLg //4 chan, esp8266, bb (any pin) +#define B_8266_U0_UCS_4 NeoPixelBus //4 chan, esp8266, gpio1 +#define B_8266_U1_UCS_4 NeoPixelBus //4 chan, esp8266, gpio2 +#define B_8266_DM_UCS_4 NeoPixelBus //4 chan, esp8266, gpio3 +#define B_8266_BB_UCS_4 NeoPixelBus //4 chan, esp8266, bb (any pin) //APA106 -#define B_8266_U0_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio1 -#define B_8266_U1_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio2 -#define B_8266_DM_APA106_3 NeoPixelBusLg //3 chan, esp8266, gpio3 -#define B_8266_BB_APA106_3 NeoPixelBusLg //3 chan, esp8266, bb (any pin but 16) +#define B_8266_U0_APA106_3 NeoPixelBus //3 chan, esp8266, gpio1 +#define B_8266_U1_APA106_3 NeoPixelBus //3 chan, esp8266, gpio2 +#define B_8266_DM_APA106_3 NeoPixelBus //3 chan, esp8266, gpio3 +#define B_8266_BB_APA106_3 NeoPixelBus //3 chan, esp8266, bb (any pin but 16) //FW1906 GRBCW -#define B_8266_U0_FW6_5 NeoPixelBusLg //esp8266, gpio1 -#define B_8266_U1_FW6_5 NeoPixelBusLg //esp8266, gpio2 -#define B_8266_DM_FW6_5 NeoPixelBusLg //esp8266, gpio3 -#define B_8266_BB_FW6_5 NeoPixelBusLg //esp8266, bb +#define B_8266_U0_FW6_5 NeoPixelBus //esp8266, gpio1 +#define B_8266_U1_FW6_5 NeoPixelBus //esp8266, gpio2 +#define B_8266_DM_FW6_5 NeoPixelBus //esp8266, gpio3 +#define B_8266_BB_FW6_5 NeoPixelBus //esp8266, bb //WS2805 GRBCW -#define B_8266_U0_2805_5 NeoPixelBusLg //esp8266, gpio1 -#define B_8266_U1_2805_5 NeoPixelBusLg //esp8266, gpio2 -#define B_8266_DM_2805_5 NeoPixelBusLg //esp8266, gpio3 -#define B_8266_BB_2805_5 NeoPixelBusLg //esp8266, bb +#define B_8266_U0_2805_5 NeoPixelBus //esp8266, gpio1 +#define B_8266_U1_2805_5 NeoPixelBus //esp8266, gpio2 +#define B_8266_DM_2805_5 NeoPixelBus //esp8266, gpio3 +#define B_8266_BB_2805_5 NeoPixelBus //esp8266, bb //TM1914 (RGB) -#define B_8266_U0_TM1914_3 NeoPixelBusLg -#define B_8266_U1_TM1914_3 NeoPixelBusLg -#define B_8266_DM_TM1914_3 NeoPixelBusLg -#define B_8266_BB_TM1914_3 NeoPixelBusLg +#define B_8266_U0_TM1914_3 NeoPixelBus +#define B_8266_U1_TM1914_3 NeoPixelBus +#define B_8266_DM_TM1914_3 NeoPixelBus +#define B_8266_BB_TM1914_3 NeoPixelBus //Sm16825 (RGBWC) -#define B_8266_U0_SM16825_5 NeoPixelBusLg -#define B_8266_U1_SM16825_5 NeoPixelBusLg -#define B_8266_DM_SM16825_5 NeoPixelBusLg -#define B_8266_BB_SM16825_5 NeoPixelBusLg +#define B_8266_U0_SM16825_5 NeoPixelBus +#define B_8266_U1_SM16825_5 NeoPixelBus +#define B_8266_DM_SM16825_5 NeoPixelBus +#define B_8266_BB_SM16825_5 NeoPixelBus #endif /*** ESP32 Neopixel methods ***/ @@ -247,84 +247,84 @@ uint32_t globalBrightness = 255; // global brightness for digital busses, set by #endif //RGB -#define B_32_RN_NEO_3 NeoPixelBusLg // ESP32, S2, S3, C3 -//#define B_32_IN_NEO_3 NeoPixelBusLg // ESP32 (dynamic I2S selection) -#define B_32_I2_NEO_3 NeoPixelBusLg // ESP32, S2, S3 (automatic I2S selection, see typedef above) -#define B_32_IP_NEO_3 NeoPixelBusLg // parallel I2S (ESP32, S2, S3) +#define B_32_RN_NEO_3 NeoPixelBus // ESP32, S2, S3, C3 +//#define B_32_IN_NEO_3 NeoPixelBus // ESP32 (dynamic I2S selection) +#define B_32_I2_NEO_3 NeoPixelBus // ESP32, S2, S3 (automatic I2S selection, see typedef above) +#define B_32_IP_NEO_3 NeoPixelBus // parallel I2S (ESP32, S2, S3) //RGBW -#define B_32_RN_NEO_4 NeoPixelBusLg -#define B_32_I2_NEO_4 NeoPixelBusLg -#define B_32_IP_NEO_4 NeoPixelBusLg // parallel I2S +#define B_32_RN_NEO_4 NeoPixelBus +#define B_32_I2_NEO_4 NeoPixelBus +#define B_32_IP_NEO_4 NeoPixelBus // parallel I2S //400Kbps -#define B_32_RN_400_3 NeoPixelBusLg -#define B_32_I2_400_3 NeoPixelBusLg -#define B_32_IP_400_3 NeoPixelBusLg // parallel I2S +#define B_32_RN_400_3 NeoPixelBus +#define B_32_I2_400_3 NeoPixelBus +#define B_32_IP_400_3 NeoPixelBus // parallel I2S //TM1814 (RGBW) -#define B_32_RN_TM1_4 NeoPixelBusLg -#define B_32_I2_TM1_4 NeoPixelBusLg -#define B_32_IP_TM1_4 NeoPixelBusLg // parallel I2S +#define B_32_RN_TM1_4 NeoPixelBus +#define B_32_I2_TM1_4 NeoPixelBus +#define B_32_IP_TM1_4 NeoPixelBus // parallel I2S //TM1829 (RGB) -#define B_32_RN_TM2_3 NeoPixelBusLg -#define B_32_I2_TM2_3 NeoPixelBusLg -#define B_32_IP_TM2_3 NeoPixelBusLg // parallel I2S +#define B_32_RN_TM2_3 NeoPixelBus +#define B_32_I2_TM2_3 NeoPixelBus +#define B_32_IP_TM2_3 NeoPixelBus // parallel I2S //UCS8903 -#define B_32_RN_UCS_3 NeoPixelBusLg -#define B_32_I2_UCS_3 NeoPixelBusLg -#define B_32_IP_UCS_3 NeoPixelBusLg // parallel I2S +#define B_32_RN_UCS_3 NeoPixelBus +#define B_32_I2_UCS_3 NeoPixelBus +#define B_32_IP_UCS_3 NeoPixelBus // parallel I2S //UCS8904 -#define B_32_RN_UCS_4 NeoPixelBusLg -#define B_32_I2_UCS_4 NeoPixelBusLg -#define B_32_IP_UCS_4 NeoPixelBusLg// parallel I2S +#define B_32_RN_UCS_4 NeoPixelBus +#define B_32_I2_UCS_4 NeoPixelBus +#define B_32_IP_UCS_4 NeoPixelBus// parallel I2S //APA106 -#define B_32_RN_APA106_3 NeoPixelBusLg -#define B_32_I2_APA106_3 NeoPixelBusLg -#define B_32_IP_APA106_3 NeoPixelBusLg // parallel I2S +#define B_32_RN_APA106_3 NeoPixelBus +#define B_32_I2_APA106_3 NeoPixelBus +#define B_32_IP_APA106_3 NeoPixelBus // parallel I2S //FW1906 GRBCW -#define B_32_RN_FW6_5 NeoPixelBusLg -#define B_32_I2_FW6_5 NeoPixelBusLg -#define B_32_IP_FW6_5 NeoPixelBusLg // parallel I2S +#define B_32_RN_FW6_5 NeoPixelBus +#define B_32_I2_FW6_5 NeoPixelBus +#define B_32_IP_FW6_5 NeoPixelBus // parallel I2S //WS2805 RGBWC -#define B_32_RN_2805_5 NeoPixelBusLg -#define B_32_I2_2805_5 NeoPixelBusLg -#define B_32_IP_2805_5 NeoPixelBusLg // parallel I2S +#define B_32_RN_2805_5 NeoPixelBus +#define B_32_I2_2805_5 NeoPixelBus +#define B_32_IP_2805_5 NeoPixelBus // parallel I2S //TM1914 (RGB) -#define B_32_RN_TM1914_3 NeoPixelBusLg -#define B_32_I2_TM1914_3 NeoPixelBusLg -#define B_32_IP_TM1914_3 NeoPixelBusLg // parallel I2S +#define B_32_RN_TM1914_3 NeoPixelBus +#define B_32_I2_TM1914_3 NeoPixelBus +#define B_32_IP_TM1914_3 NeoPixelBus // parallel I2S //Sm16825 (RGBWC) -#define B_32_RN_SM16825_5 NeoPixelBusLg -#define B_32_I2_SM16825_5 NeoPixelBusLg -#define B_32_IP_SM16825_5 NeoPixelBusLg // parallel I2S +#define B_32_RN_SM16825_5 NeoPixelBus +#define B_32_I2_SM16825_5 NeoPixelBus +#define B_32_IP_SM16825_5 NeoPixelBus // parallel I2S #endif //APA102 #ifdef WLED_USE_ETHERNET // fix for #2542 (by @BlackBird77) -#define B_HS_DOT_3 NeoPixelBusLg //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9) +#define B_HS_DOT_3 NeoPixelBus //hardware HSPI (was DotStarEsp32DmaHspi5MhzMethod in NPB @ 2.6.9) #else -#define B_HS_DOT_3 NeoPixelBusLg //hardware VSPI +#define B_HS_DOT_3 NeoPixelBus //hardware VSPI #endif -#define B_SS_DOT_3 NeoPixelBusLg //soft SPI +#define B_SS_DOT_3 NeoPixelBus //soft SPI //LPD8806 -#define B_HS_LPD_3 NeoPixelBusLg -#define B_SS_LPD_3 NeoPixelBusLg +#define B_HS_LPD_3 NeoPixelBus +#define B_SS_LPD_3 NeoPixelBus //LPD6803 -#define B_HS_LPO_3 NeoPixelBusLg -#define B_SS_LPO_3 NeoPixelBusLg +#define B_HS_LPO_3 NeoPixelBus +#define B_SS_LPO_3 NeoPixelBus //WS2801 #ifdef WLED_USE_ETHERNET -#define B_HS_WS1_3 NeoPixelBusLg>, NeoGammaNullMethod> +#define B_HS_WS1_3 NeoPixelBus>> #else -#define B_HS_WS1_3 NeoPixelBusLg +#define B_HS_WS1_3 NeoPixelBus #endif -#define B_SS_WS1_3 NeoPixelBusLg +#define B_SS_WS1_3 NeoPixelBus //P9813 -#define B_HS_P98_3 NeoPixelBusLg -#define B_SS_P98_3 NeoPixelBusLg +#define B_HS_P98_3 NeoPixelBus +#define B_SS_P98_3 NeoPixelBus // 48bit & 64bit to 24bit & 32bit RGB(W) conversion #define toRGBW32(c) (RGBW32((c>>40)&0xFF, (c>>24)&0xFF, (c>>8)&0xFF, (c>>56)&0xFF)) @@ -777,52 +777,16 @@ class PolyBus { return true; } -#define R(c) (byte((c) >> 16)) -#define G(c) (byte((c) >> 8)) -#define B(c) (byte(c)) -#define W(c) (byte((c) >> 24)) - [[gnu::hot]] static void setPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint32_t c, uint8_t co, uint16_t wwcw = 0) { - + uint8_t r = c >> 16; + uint8_t g = c >> 8; + uint8_t b = c >> 0; + uint8_t w = c >> 24; RgbwColor col; uint8_t cctWW = wwcw & 0xFF, cctCW = (wwcw>>8) & 0xFF; - uint8_t r, g, b, w; - if(c > 0) { - if(globalBrightness < 255) { - // apply global brightness using video scaling: keep a minimum of 1 for each channel if non zero but preserve hue - uint8_t origR = R(c); - uint8_t origG = G(c); - uint8_t origB = B(c); - - // determine dominant channel for hue preservation - uint8_t maxc = (origR > origG) ? ((origR > origB) ? origR : origB) : ((origG > origB) ? origG : origB); - uint8_t halfMax = maxc >> 1; - // apply global brightness note: adding +128 for rounding makes virtually no visual difference - r = (origR * globalBrightness) >> 8; - g = (origG * globalBrightness) >> 8; - b = (origB * globalBrightness) >> 8; - w = (W(c) * globalBrightness) >> 8; - - // Hue-preserving rule: - r |= ((r == 0) ? 1 : 0) & ((origR > halfMax) ? 1 : 0); - g |= ((g == 0) ? 1 : 0) & ((origG > halfMax) ? 1 : 0); - b |= ((b == 0) ? 1 : 0) & ((origB > halfMax) ? 1 : 0); - } - else { - r = R(c); - g = G(c); - b = B(c); - w = W(c); - } - - } - else { - col = 0; - } - - // reorder channels to selected order - switch (co & 0x0F) { + // reorder channels to selected order + switch (co & 0x0F) { default: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default case 1: col.G = r; col.R = g; col.B = b; break; //1 = RGB, common for WS2811 case 2: col.G = b; col.R = r; col.B = g; break; //2 = BRG diff --git a/wled00/colors.cpp b/wled00/colors.cpp index da52bd4f75..1c1ec9a307 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -589,10 +589,13 @@ uint8_t NeoGammaWLEDMethod::gammaT_inv[256]; void NeoGammaWLEDMethod::calcGammaTable(float gamma) { float gamma_inv = 1.0f / gamma; // inverse gamma - for (size_t i = 0; i < 256; i++) { - gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); - gammaT_inv[i] = (int)(powf((float)i / 255.0f, gamma_inv) * 255.0f + 0.5f); + for (size_t i = 1; i < 256; i++) { + gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.75f); // +0.75 for more aggressive rounding at low values + gammaT_inv[i] = (int)(powf(((float)i - 0.25f) / 255.0f, gamma_inv) * 255.0f + 0.75f); + //DEBUG_PRINTF_P(PSTR("gammaT[%d] = %d gammaT_inv[%d] = %d\n"), i, gammaT[i], i, gammaT_inv[i]); } + gammaT[0] = 0; + gammaT_inv[0] = 0; } uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) From 6661db0be9181a77e04d06cd3671702fe633ca87 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 2 Aug 2025 10:04:22 +0200 Subject: [PATCH 4/7] Code cleanup, fixed gamma being applied in unnecessary places --- wled00/FX.cpp | 6 +++--- wled00/FX_fcn.cpp | 19 ++++--------------- wled00/image_loader.cpp | 2 +- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a680de64de..f3163dc18e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7528,9 +7528,9 @@ uint16_t mode_2Ddistortionwaves() { byte valueG = gdistort + ((a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 ))<<1); byte valueB = bdistort + ((a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 ))<<1); - valueR = gamma8(cos8_t(valueR)); - valueG = gamma8(cos8_t(valueG)); - valueB = gamma8(cos8_t(valueB)); + valueR = cos8_t(valueR); + valueG = cos8_t(valueG); + valueB = cos8_t(valueB); if(SEGMENT.palette == 0) { // use RGB values (original color mode) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 366cc89b89..24aa11b0ef 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1660,34 +1660,23 @@ void WS2812FX::show() { if(!(realtimeMode && arlsDisableGammaCorrection)) c = gamma32(c); // apply gamma correction if enabled if(_brightness < 255) { - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - uint8_t w = W(c); - + uint8_t r = R(c), g = G(c), b = B(c), w = W(c); // determine dominant channel for hue preservation uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); uint8_t halfMax = (maxc - 1) >> 1; // maxc is always > 0 - uint8_t scale = _brightness + 1; // add one to correct for bit shift uint32_t addRemains = 0; // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue addRemains = r && r > halfMax ? 0x00010000 : 0; addRemains |= g && g > halfMax ? 0x00000100 : 0; addRemains |= b && b > halfMax ? 0x00000001 : 0; - // apply global brightness - r = (r * _brightness) >> 8; - g = (g * _brightness) >> 8; - b = (b * _brightness) >> 8; - w = (w * _brightness) >> 8; - const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; - uint32_t rb = (((c & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue - uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green + uint32_t rb = (((c & TWO_CHANNEL_MASK) * _brightness) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * _brightness) & ~TWO_CHANNEL_MASK; // scale white and green c = (rb | wg) + addRemains; } } - BusManager::setPixelColor(getMappedPixelIndex(i), c ); + BusManager::setPixelColor(getMappedPixelIndex(i), c); } Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index aa4ae2e161..691ede1ac5 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -58,7 +58,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t // set multiple pixels if upscaling for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) { for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) { - activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue)); + activeSeg->setPixelColorXY(outX + i, outY + j, red, green, blue); } } } From 0d89cfd78ac27f7587c5d0f827ef9697eda453fb Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 7 Aug 2025 08:08:10 +0200 Subject: [PATCH 5/7] fixed too aggressive hue preservation, reverted gamma table calc --- wled00/FX_fcn.cpp | 27 ++++++++++++++++----------- wled00/bus_wrapper.h | 4 +--- wled00/colors.cpp | 4 ++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 24aa11b0ef..7fe720ac5e 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1658,21 +1658,26 @@ void WS2812FX::show() { uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32) if(c > 0) { if(!(realtimeMode && arlsDisableGammaCorrection)) - c = gamma32(c); // apply gamma correction if enabled - if(_brightness < 255) { + c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss + if(newBri < 255) { + // apply brightness note: could check if brightness is 255 and skip this step uint8_t r = R(c), g = G(c), b = B(c), w = W(c); - // determine dominant channel for hue preservation - uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); - uint8_t halfMax = (maxc - 1) >> 1; // maxc is always > 0 uint32_t addRemains = 0; // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue - addRemains = r && r > halfMax ? 0x00010000 : 0; - addRemains |= g && g > halfMax ? 0x00000100 : 0; - addRemains |= b && b > halfMax ? 0x00000001 : 0; - + // determine dominant channel for hue preservation + uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); + uint8_t quarterMax = maxc >> 2; + addRemains = r && r > quarterMax ? 0x00010000 : 0; + addRemains |= g && g > quarterMax ? 0x00000100 : 0; + addRemains |= b && b > quarterMax ? 0x00000001 : 0; + /* + addRemains = r ? 0x00010000 : 0; // rainbowbands looks MUCH better without the color preservation, so does normal rainbow + addRemains |= g ? 0x00000100 : 0; // but: PS fire looks pink without preservation... + addRemains |= b ? 0x00000001 : 0; +*/ const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; - uint32_t rb = (((c & TWO_CHANNEL_MASK) * _brightness) >> 8) & TWO_CHANNEL_MASK; // scale red and blue - uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * _brightness) & ~TWO_CHANNEL_MASK; // scale white and green + uint32_t rb = (((c & TWO_CHANNEL_MASK) * newBri) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * newBri) & ~TWO_CHANNEL_MASK; // scale white and green c = (rb | wg) + addRemains; } } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index bb123f52aa..5b47ed346c 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -5,8 +5,6 @@ //#define NPB_CONF_4STEP_CADENCE #include "NeoPixelBus.h" -uint32_t globalBrightness = 255; // global brightness for digital busses, set by setBrightness() - //Hardware SPI Pins #define P_8266_HS_MOSI 13 #define P_8266_HS_CLK 14 @@ -899,7 +897,7 @@ class PolyBus { } static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { - globalBrightness = b; // use bit shifts and add 1/2 for proper rounding + } [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 1c1ec9a307..04282184b0 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -590,8 +590,8 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) { float gamma_inv = 1.0f / gamma; // inverse gamma for (size_t i = 1; i < 256; i++) { - gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.75f); // +0.75 for more aggressive rounding at low values - gammaT_inv[i] = (int)(powf(((float)i - 0.25f) / 255.0f, gamma_inv) * 255.0f + 0.75f); + gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); + gammaT_inv[i] = (int)(powf(((float)i - 0.5f) / 255.0f, gamma_inv) * 255.0f + 0.5f); //DEBUG_PRINTF_P(PSTR("gammaT[%d] = %d gammaT_inv[%d] = %d\n"), i, gammaT[i], i, gammaT_inv[i]); } gammaT[0] = 0; From 2ca361a2bc8772506cee468576add599bb3d3ab3 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 7 Aug 2025 08:08:58 +0200 Subject: [PATCH 6/7] removed commented lines --- wled00/FX_fcn.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7fe720ac5e..a5286d8c9c 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1670,11 +1670,6 @@ void WS2812FX::show() { addRemains = r && r > quarterMax ? 0x00010000 : 0; addRemains |= g && g > quarterMax ? 0x00000100 : 0; addRemains |= b && b > quarterMax ? 0x00000001 : 0; - /* - addRemains = r ? 0x00010000 : 0; // rainbowbands looks MUCH better without the color preservation, so does normal rainbow - addRemains |= g ? 0x00000100 : 0; // but: PS fire looks pink without preservation... - addRemains |= b ? 0x00000001 : 0; -*/ const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; uint32_t rb = (((c & TWO_CHANNEL_MASK) * newBri) >> 8) & TWO_CHANNEL_MASK; // scale red and blue uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * newBri) & ~TWO_CHANNEL_MASK; // scale white and green From 00062b2460ae024e7f7bc89b6295ae076ca7b1a9 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 26 Aug 2025 13:03:21 +0200 Subject: [PATCH 7/7] big update to ABL, refactoring and some minor fixes Improvements to ABL handling: - removed strip level handling, ist now all done on bus level - limiter now respects pixel mapping - proper handling of white channel - improved current estimation - current is now always correctly reported to UI - minimal FPS impact if the ABL is not limiting but slighly higher impact for global ABL limit due to double-scaling - moved brightness scaling to BusDigital - created new header file colors.h to be able to access color functions in bus-manager. - updated colo_fade() with better video scaling to preserve hue's at low brightness - added IRAM_ATTR to color_fade (negligible speed impact when compared to inline and benefits other functions) - added IRAM_ATTR to color_blend as it is used a lot throughout the code, did not test speed impact but adding it to color_fade made it almost on-par with an inlined function Additional changes: - fixes for properly handling `scaledBri()` (by @blazoncek) - also use bit-shift instead of division in blending for ESP8266 - improvements for faster "softlight" calculation in blending - changed some variables to uint8_t to maybe let the compiler optimize better, uint8_t can be faster if read, store and set are all done in uint8_t, which is the case in the ones I changed - various minor code formatting changes --- wled00/FX_fcn.cpp | 100 ++---------------- wled00/bus_manager.cpp | 226 ++++++++++++++++++++++++++--------------- wled00/bus_manager.h | 17 ++-- wled00/bus_wrapper.h | 4 - wled00/colors.cpp | 64 +++++------- wled00/colors.h | 144 ++++++++++++++++++++++++++ wled00/e131.cpp | 2 +- wled00/fcn_declare.h | 127 ----------------------- wled00/json.cpp | 2 +- wled00/led.cpp | 4 +- wled00/udp.cpp | 6 +- wled00/wled.cpp | 4 +- wled00/wled.h | 1 + 13 files changed, 341 insertions(+), 360 deletions(-) create mode 100644 wled00/colors.h diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a5286d8c9c..ca3b247e41 100755 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1196,8 +1196,9 @@ void WS2812FX::finalizeInit() { if (busEnd > _length) _length = busEnd; // This must be done after all buses have been created, as some kinds (parallel I2S) interact bus->begin(); - bus->setBrightness(bri); + bus->setBrightness(scaledBri(bri)); } + BusManager::initializeABL(); // init brightness limiter DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap()); Segment::maxWidth = _length; @@ -1299,7 +1300,7 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } -#ifdef CONFIG_IDF_TARGET_ESP32C3 +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate #else static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1] @@ -1310,10 +1311,10 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; } static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255 static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } -#ifdef CONFIG_IDF_TARGET_ESP32C3 -static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab +#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) +static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a))) + ((2 * a * b + 256) << 8)) >> 16; } // Pegtop's formula (1 - 2a)b^2 #else -static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab +static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) + 255 * 2 * a * b) / (255 * 255); } // Pegtop's formula (1 - 2a)b^2 + 2ab #endif static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } @@ -1555,66 +1556,6 @@ void WS2812FX::blendSegment(const Segment &topSegment) const { Segment::setClippingRect(0, 0); // disable clipping for overlays } -// To disable brightness limiter we either set output max current to 0 or single LED current to 0 -static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) { - unsigned milliAmpsMax = BusManager::ablMilliampsMax(); - if (milliAmpsMax > 0) { - unsigned milliAmpsTotal = 0; - unsigned avgMilliAmpsPerLED = 0; - unsigned lengthDigital = 0; - bool useWackyWS2815PowerModel = false; - - for (size_t i = 0; i < BusManager::getNumBusses(); i++) { - const Bus *bus = BusManager::getBus(i); - if (!(bus && bus->isDigital() && bus->isOk())) continue; - unsigned maPL = bus->getLEDCurrent(); - if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL) - if (maPL == 255) { - useWackyWS2815PowerModel = true; - maPL = 12; // WS2815 uses 12mA per channel - } - avgMilliAmpsPerLED += maPL * bus->getLength(); - lengthDigital += bus->getLength(); - // sum up the usage of each LED on digital bus - uint32_t busPowerSum = 0; - for (unsigned j = 0; j < bus->getLength(); j++) { - uint32_t c = pixels[j + bus->getStart()]; - byte r = R(c), g = G(c), b = B(c), w = W(c); - if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation - busPowerSum += (max(max(r,g),b)) * 3; - } else { - busPowerSum += (r + g + b + w); - } - } - // RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less - if (bus->hasWhite()) { - busPowerSum *= 3; - busPowerSum >>= 2; //same as /= 4 - } - // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps - milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255); - } - if (lengthDigital > 0) { - avgMilliAmpsPerLED /= lengthDigital; - - if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation - unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power - if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget - powerBudget -= lengthDigital; - } else { - powerBudget = 0; - } - if (milliAmpsTotal > powerBudget) { - //scale brightness down to stay in current limit - unsigned scaleB = powerBudget * 255 / milliAmpsTotal; - brightness = ((brightness * scaleB) >> 8) + 1; - } - } - } - } - return brightness; -} - void WS2812FX::show() { unsigned long showNow = millis(); size_t diff = showNow - _lastShow; @@ -1640,10 +1581,6 @@ void WS2812FX::show() { show_callback callback = _callback; if (callback) callback(); // will call setPixelColor or setRealtimePixelColor - // determine ABL brightness - uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels); - if (newBri != _brightness) BusManager::setBrightness(newBri); - // paint actual pixels int oldCCT = Bus::getCCT(); // store original CCT value (since it is global) // when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1) @@ -1656,26 +1593,8 @@ void WS2812FX::show() { } uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32) - if(c > 0) { - if(!(realtimeMode && arlsDisableGammaCorrection)) + if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection)) c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss - if(newBri < 255) { - // apply brightness note: could check if brightness is 255 and skip this step - uint8_t r = R(c), g = G(c), b = B(c), w = W(c); - uint32_t addRemains = 0; - // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue - // determine dominant channel for hue preservation - uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); - uint8_t quarterMax = maxc >> 2; - addRemains = r && r > quarterMax ? 0x00010000 : 0; - addRemains |= g && g > quarterMax ? 0x00000100 : 0; - addRemains |= b && b > quarterMax ? 0x00000001 : 0; - const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; - uint32_t rb = (((c & TWO_CHANNEL_MASK) * newBri) >> 8) & TWO_CHANNEL_MASK; // scale red and blue - uint32_t wg = (((c >> 8) & TWO_CHANNEL_MASK) * newBri) & ~TWO_CHANNEL_MASK; // scale white and green - c = (rb | wg) + addRemains; - } - } BusManager::setPixelColor(getMappedPixelIndex(i), c); } Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments @@ -1688,9 +1607,6 @@ void WS2812FX::show() { // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods BusManager::show(); - // restore brightness for next frame - if (newBri != _brightness) BusManager::setBrightness(_brightness); - if (diff > 0) { // skip calculation if no time has passed size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding @@ -1755,7 +1671,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { if (_brightness == 0) { //unfreeze all segments on power off for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable } - BusManager::setBrightness(b); + BusManager::setBrightness(scaledBri(b)); if (!direct) { unsigned long t = millis(); if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index a83b29bdee..2e45ab71ba 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -20,6 +20,7 @@ #include "core_esp8266_waveform.h" #endif #include "const.h" +#include "colors.h" #include "pin_manager.h" #include "bus_manager.h" #include "bus_wrapper.h" @@ -141,6 +142,7 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } if (!PinManager::allocatePin(bc.pins[0], true, PinOwner::BusDigital)) { DEBUGBUS_PRINTLN(F("Pin 0 allocated!")); return; } _frequencykHz = 0U; + _colorSum = 0; _pins[0] = bc.pins[0]; if (is2Pin(bc.type)) { if (!PinManager::allocatePin(bc.pins[1], true, PinOwner::BusDigital)) { @@ -183,80 +185,62 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) //Stay safe with high amperage and have a reasonable safety margin! //I am NOT to be held liable for burned down garages or houses! -// To disable brightness limiter we either set output max current to 0 or single LED current to 0 -uint8_t BusDigital::estimateCurrentAndLimitBri() const { - bool useWackyWS2815PowerModel = false; - byte actualMilliampsPerLed = _milliAmpsPerLed; - - if (_milliAmpsMax < MA_FOR_ESP/BusManager::getNumBusses() || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation - return _bri; - } +// note on ABL implementation: +// ABL is set up in finalizeInit() +// scaled color channels are summed in BusDigital::setPixelColor() +// the used current is estimated and limited in BusManager::show() +// if limit is set too low, brightness is limited to 1 to at least show some light +// to disable brightness limiter for a bus, set LED current to 0 +void BusDigital::estimateCurrent() { + uint32_t actualMilliampsPerLed = _milliAmpsPerLed; if (_milliAmpsPerLed == 255) { - useWackyWS2815PowerModel = true; + // use wacky WS2815 power model, see WLED issue #549 + _colorSum *= 3; // sum is sum of max value for each color, need to multiply by three to account for clrUnitsPerChannel being 3*255 actualMilliampsPerLed = 12; // from testing an actual strip } + // _colorSum has all the values of color channels summed, max would be getLength()*(3*255 + (255 if hasWhite()): convert to milliAmps + uint32_t clrUnitsPerChannel = hasWhite() ? 4*255 : 3*255; + _milliAmpsTotal = ((uint64_t)_colorSum * actualMilliampsPerLed) / clrUnitsPerChannel + getLength(); // add 1mA standby current per LED to total (WS2812: ~0.7mA, WS2815: ~2mA) +} - unsigned powerBudget = (_milliAmpsMax - MA_FOR_ESP/BusManager::getNumBusses()); //80/120mA for ESP power - if (powerBudget > getLength()) { //each LED uses about 1mA in standby, exclude that from power budget - powerBudget -= getLength(); - } else { - powerBudget = 0; - } - - uint32_t busPowerSum = 0; - for (unsigned i = 0; i < getLength(); i++) { //sum up the usage of each LED - uint32_t c = getPixelColor(i); // always returns original or restored color without brightness scaling - byte r = R(c), g = G(c), b = B(c), w = W(c); - - if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation - busPowerSum += (max(max(r,g),b)) * 3; +void BusDigital::applyBriLimit(uint8_t newBri) { + // a newBri of 0 means calculate per-bus brightness limit + if (newBri == 0) { + if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus + newBri = 255; + + if (_milliAmpsLimit > getLength()) { // each LED uses about 1mA in standby + if (_milliAmpsTotal > _milliAmpsLimit) { + // scale brightness down to stay in current limit + newBri = ((uint32_t)_milliAmpsLimit * 255) / _milliAmpsTotal + 1; // +1 to avoid 0 brightness + _milliAmpsTotal = _milliAmpsLimit; + } } else { - busPowerSum += (r + g + b + w); + newBri = 1; // limit too low, set brightness to 1, this will dim down all colors to minimum since we use video scaling + _milliAmpsTotal = getLength(); // estimate bus current as minimum } } - if (hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less - busPowerSum *= 3; - busPowerSum >>= 2; //same as /= 4 + if (newBri < 255) { + uint8_t cctWW = 0, cctCW = 0; + unsigned hwLen = _len; + if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus + for (unsigned i = 0; i < hwLen; i++) { + uint8_t co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); // need to revert color order for correct color scaling and CCT calc in case white is swapped + uint32_t c = PolyBus::getPixelColor(_busPtr, _iType, i, co); + c = color_fade(c, newBri, true); // apply additional dimming note: using inline version is a bit faster but overhead of getPixelColor() dominates the speed impact by far + if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); + PolyBus::setPixelColor(_busPtr, _iType, i, c, co, (cctCW<<8) | cctWW); // repaint all pixels with new brightness + } } - // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps - BusDigital::_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * _bri) / (765*255); - - uint8_t newBri = _bri; - if (BusDigital::_milliAmpsTotal > powerBudget) { - //scale brightness down to stay in current limit - unsigned scaleB = powerBudget * 255 / BusDigital::_milliAmpsTotal; - newBri = (_bri * scaleB) / 256 + 1; - BusDigital::_milliAmpsTotal = powerBudget; - //_milliAmpsTotal = (busPowerSum * actualMilliampsPerLed * newBri) / (765*255); - } - return newBri; + _colorSum = 0; // reset for next frame } void BusDigital::show() { - BusDigital::_milliAmpsTotal = 0; if (!_valid) return; - - uint8_t cctWW = 0, cctCW = 0; - unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere()) - if (newBri < _bri) { - PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits - unsigned hwLen = _len; - if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus - for (unsigned i = 0; i < hwLen; i++) { - // use 0 as color order, actual order does not matter here as we just update the channel values as-is - uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, i, 0), _bri); - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); // this will unfortunately corrupt (segment) CCT data on every bus - PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness - } - } PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs) - // restore bus brightness to its original value - // this is done right after show, so this is only OK if LED updates are completed before show() returns - // or async show has a separate buffer (ESP32 RMT and I2S are ok) - if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, _bri); } bool BusDigital::canShow() const { @@ -264,12 +248,6 @@ bool BusDigital::canShow() const { return PolyBus::canShow(_busPtr, _iType); } -void BusDigital::setBrightness(uint8_t b) { - if (_bri == b) return; - Bus::setBrightness(b); - PolyBus::setBrightness(_busPtr, _iType, b); -} - //If LEDs are skipped, it is possible to use the first as a status LED. //TODO only show if no new show due in the next 50ms void BusDigital::setStatusPixel(uint32_t c) { @@ -283,13 +261,25 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; if (hasWhite()) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT + c = color_fade(c, _bri, true); // apply brightness + + if (BusManager::_useABL) { + // if using ABL, sum all color channels to estimate current and limit brightness in show() + uint8_t r = R(c), g = G(c), b = B(c); + if (_milliAmpsPerLed < 255) { // normal ABL + _colorSum += r + g + b + W(c); + } else { // wacky WS2815 power model, ignore white channel, use max of RGB (issue #549) + _colorSum += ((r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b)); + } + } + if (_reversed) pix = _len - pix -1; pix += _skip; - unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs unsigned pOld = pix; pix = IC_INDEX_WS2812_1CH_3X(pix); - uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri); + uint32_t cOld = PolyBus::getPixelColor(_busPtr, _iType, pix, co); switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set) case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break; case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break; @@ -306,17 +296,17 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } -// returns original color if global buffering is enabled, else returns lossly restored color from bus +// returns lossly restored color from bus uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; if (_reversed) pix = _len - pix -1; pix += _skip; - const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs - unsigned r = R(c); - unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? - unsigned b = _reversed ? G(c) : B(c); + uint8_t r = R(c); + uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? + uint8_t b = _reversed ? G(c) : B(c); switch (pix % 3) { // get only the single channel case 0: c = RGBW32(g, g, g, g); break; case 1: c = RGBW32(r, r, r, r); break; @@ -468,10 +458,7 @@ void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT } - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - uint8_t w = W(c); + uint8_t r = R(c), g = G(c), b = B(c), w = W(c); switch (_type) { case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation @@ -646,10 +633,7 @@ BusOnOff::BusOnOff(const BusConfig &bc) void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel 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 r = R(c), g = G(c), b = B(c), w = W(c); _data = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } @@ -937,13 +921,13 @@ void BusManager::off() { #ifdef ESP32_DATA_IDLE_HIGH esp32RMTInvertIdle(); #endif + _gMilliAmpsUsed = 0; // reset, assume no LED idle current if relay is off } void BusManager::show() { - _gMilliAmpsUsed = 0; + applyABL(); // apply brightness limit, updates _gMilliAmpsUsed for (auto &bus : busses) { bus->show(); - _gMilliAmpsUsed += bus->getUsedCurrent(); } } @@ -976,6 +960,85 @@ bool BusManager::canAllShow() { return true; } +void BusManager::initializeABL() { + _useABL = false; // reset + if (_gMilliAmpsMax > 0) { + // check global brightness limit + for (auto &bus : busses) { + if (bus->isDigital() && bus->getLEDCurrent() > 0) { + _useABL = true; // at least one bus has valid LED current + return; + } + } + } else { + // check per bus brightness limit + unsigned numABLbuses = 0; + for (auto &bus : busses) { + if (bus->isDigital() && bus->getLEDCurrent() > 0 && bus->getMaxCurrent() > 0) + numABLbuses++; // count ABL enabled buses + } + if (numABLbuses > 0) { + _useABL = true; // at least one bus has ABL set + uint32_t ESPshare = MA_FOR_ESP / numABLbuses; // share of ESP current per ABL bus + for (auto &bus : busses) { + if (bus->isDigital()) { + BusDigital &busd = static_cast(*bus); + uint32_t busLength = busd.getLength(); + uint32_t busDemand = busLength * busd.getLEDCurrent(); + uint32_t busMax = busd.getMaxCurrent(); + if (busMax > ESPshare) busMax -= ESPshare; + if (busMax < busLength) busMax = busLength; // give each LED 1mA, ABL will dim down to minimum + if (busDemand == 0) busMax = 0; // no LED current set, disable ABL for this bus + busd.setCurrentLimit(busMax); + } + } + } + } +} + +void BusManager::applyABL() { + if (_useABL) { + unsigned milliAmpsSum = 0; // use temporary variable to always return a valid _gMilliAmpsUsed to UI + unsigned totalLEDs = 0; + for (auto &bus : busses) { + if (bus->isDigital() && bus->isOk()) { + BusDigital &busd = static_cast(*bus); + busd.estimateCurrent(); // sets _milliAmpsTotal, current is estimated for all buses even if they have the limit set to 0 + if (_gMilliAmpsMax == 0) + busd.applyBriLimit(0); // apply per bus ABL limit, updates _milliAmpsTotal if limit reached + milliAmpsSum += busd.getUsedCurrent(); + totalLEDs += busd.getLength(); // sum total number of LEDs for global Limit + } + } + // check global current limit and apply global ABL limit, total current is summed above + if (_gMilliAmpsMax > 0) { + uint8_t newBri = 255; + uint32_t globalMax = _gMilliAmpsMax > MA_FOR_ESP ? _gMilliAmpsMax - MA_FOR_ESP : 1; // subtract ESP current consumption, fully limit if too low + if (globalMax > totalLEDs) { // check if budget is larger than standby current + if (milliAmpsSum > globalMax) { + newBri = globalMax * 255 / milliAmpsSum + 1; // scale brightness down to stay in current limit, +1 to avoid 0 brightness + milliAmpsSum = globalMax; // update total used current + } + } else { + newBri = 1; // limit too low, set brightness to minimum + milliAmpsSum = totalLEDs; // estimate total used current as minimum + } + + // apply brightness limit to each bus, if its 255 it will only reset _colorSum + for (auto &bus : busses) { + if (bus->isDigital() && bus->isOk()) { + BusDigital &busd = static_cast(*bus); + if (busd.getLEDCurrent() > 0) // skip buses with LED current set to 0 + busd.applyBriLimit(newBri); + } + } + } + _gMilliAmpsUsed = milliAmpsSum; + } + else + _gMilliAmpsUsed = 0; // reset, we have no current estimation without ABL +} + ColorOrderMap& BusManager::getColorOrderMap() { return _colorOrderMap; } @@ -991,3 +1054,4 @@ uint16_t BusDigital::_milliAmpsTotal = 0; std::vector> BusManager::busses; uint16_t BusManager::_gMilliAmpsUsed = 0; uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT; +bool BusManager::_useABL = false; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index f183e4b5bd..bc2313e569 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -237,7 +237,6 @@ class BusDigital : public Bus { void show() override; bool canShow() const override; - void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; @@ -249,6 +248,9 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } + void setCurrentLimit(uint16_t milliAmps) { _milliAmpsLimit = milliAmps; } + void estimateCurrent(); // estimate used current from summed colors + void applyBriLimit(uint8_t newBri); size_t getBusSize() const override; void begin() override; void cleanup(); @@ -261,8 +263,10 @@ class BusDigital : public Bus { uint8_t _pins[2]; uint8_t _iType; uint16_t _frequencykHz; - uint8_t _milliAmpsPerLed; uint16_t _milliAmpsMax; + uint8_t _milliAmpsPerLed; + uint16_t _milliAmpsLimit; + uint32_t _colorSum; // total color value for the bus, updated in setPixelColor(), used to estimate current void *_busPtr; static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() @@ -277,8 +281,6 @@ class BusDigital : public Bus { } return c; } - - uint8_t estimateCurrentAndLimitBri() const; }; @@ -412,8 +414,8 @@ struct BusConfig { }; -//fine tune power estimation constants for your setup -//you can set it to 0 if the ESP is powered by USB and the LEDs by external +// milliamps used by ESP (for power estimation) +// you can set it to 0 if the ESP is powered by USB and the LEDs by external #ifndef MA_FOR_ESP #ifdef ESP8266 #define MA_FOR_ESP 80 //how much mA does the ESP use (Wemos D1 about 80mA) @@ -428,6 +430,7 @@ namespace BusManager { //extern std::vector busses; extern uint16_t _gMilliAmpsUsed; extern uint16_t _gMilliAmpsMax; + extern bool _useABL; #ifdef ESP32_DATA_IDLE_HIGH void esp32RMTInvertIdle() ; @@ -443,6 +446,8 @@ namespace BusManager { //inline uint16_t ablMilliampsMax() { unsigned sum = 0; for (auto &bus : busses) sum += bus->getMaxCurrent(); return sum; } inline uint16_t ablMilliampsMax() { return _gMilliAmpsMax; } // used for compatibility reasons (and enabling virtual global ABL) inline void setMilliampsMax(uint16_t max) { _gMilliAmpsMax = max;} + void initializeABL(); // setup automatic brightness limiter parameters, call once after buses are initialized + void applyABL(); // apply automatic brightness limiter, global or per bus void useParallelOutput(); // workaround for inaccessible PolyBus bool hasParallelOutput(); // workaround for inaccessible PolyBus diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 5b47ed346c..2fe077182e 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -896,10 +896,6 @@ class PolyBus { } } - static void setBrightness(void* busPtr, uint8_t busType, uint8_t b) { - - } - [[gnu::hot]] static uint32_t getPixelColor(void* busPtr, uint8_t busType, uint16_t pix, uint8_t co) { RgbwColor col(0,0,0,0); switch (busType) { diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 04282184b0..bf2b69d73a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -8,7 +8,7 @@ * color blend function, based on FastLED blend function * the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB */ -uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { +uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221) uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1 @@ -64,26 +64,26 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) * fades color toward black * if using "video" method the resulting color will never become black unless it is already black */ - -uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) -{ +uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) { + if (c1 == 0 || amount == 0) return 0; // black or no change if (amount == 255) return c1; - if (c1 == BLACK || amount == 0) return BLACK; - uint32_t scaledcolor; // color order is: W R G B from MSB to LSB - uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; - if (!video) scale++; // add one for correct scaling using bitshifts - else { // video scaling: make sure colors do not dim to zero if they started non-zero - addRemains = R(c1) ? 0x00010000 : 0; - addRemains |= G(c1) ? 0x00000100 : 0; - addRemains |= B(c1) ? 0x00000001 : 0; - addRemains |= W(c1) ? 0x01000000 : 0; + + if (!video) amount++; // add one for correct scaling using bitshifts + else { + // video scaling: make sure colors do not dim to zero if they started non-zero unless they distort the hue + uint8_t r = byte(c1>>16), g = byte(c1>>8), b = byte(c1), w = byte(c1>>24); // extract r, g, b, w channels + uint8_t maxc = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // determine dominant channel for hue preservation + uint8_t quarterMax = maxc >> 2; // note: using half of max results in color artefacts + addRemains = r && r > quarterMax ? 0x00010000 : 0; + addRemains |= g && g > quarterMax ? 0x00000100 : 0; + addRemains |= b && b > quarterMax ? 0x00000001 : 0; + addRemains |= w ? 0x01000000 : 0; } const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; - uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue - uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green - scaledcolor = (rb | wg) + addRemains; - return scaledcolor; + uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * amount) >> 8) & TWO_CHANNEL_MASK; // scale red and blue + uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * amount) & ~TWO_CHANNEL_MASK; // scale white and green + return (rb | wg) + addRemains; } /* @@ -92,7 +92,7 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { - if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change + if (rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change CHSV32 hsv; rgb2hsv(rgb, hsv); //convert to HSV hsv.h += (hueShift << 8); // shift hue (hue is 16 bits) @@ -104,8 +104,7 @@ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_ } // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) -uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) -{ +uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { if (blendType == LINEARBLEND_NOWRAP) { index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping } @@ -120,16 +119,16 @@ uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t else ++entry; unsigned f2 = (lo4 << 4); unsigned f1 = 256 - f2; - red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is 20% slower + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is slower green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - // actually color_fade(c1, brightness) + // actually same as color_fade(), using color_fade() is slower uint32_t scale = brightness + 1; // adjust for rounding (bitshift) - red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower + red1 = (red1 * scale) >> 8; green1 = (green1 * scale) >> 8; - blue1 = (blue1 * scale) >> 8; + blue1 = (blue1 * scale) >> 8; } return RGBW32(red1,green1,blue1,0); } @@ -604,21 +603,6 @@ uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) return gammaT[value]; } -// used for color gamma correction -uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(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); -} - uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color) { if (!gammaCorrectCol) return color; diff --git a/wled00/colors.h b/wled00/colors.h new file mode 100644 index 0000000000..376959fd65 --- /dev/null +++ b/wled00/colors.h @@ -0,0 +1,144 @@ +#pragma once +#ifndef WLED_COLORS_H +#define WLED_COLORS_H + +/* + * Color structs and color utility functions + */ +#include +#include "FastLED.h" + +#define ColorFromPalette ColorFromPaletteWLED // override fastled version + +// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color +// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts +// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB +struct CRGBW { + union { + uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB) + struct { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t w; + }; + uint8_t raw[4]; // Access as an array in the order B, G, R, W + }; + + // Default constructor + inline CRGBW() __attribute__((always_inline)) = default; + + // Constructor from a 32-bit color (0xWWRRGGBB) + constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {} + + // Constructor with r, g, b, w values + constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {} + + // Constructor from CRGB + constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {} + + // Access as an array + inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; } + + // Assignment from 32-bit color + inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; } + + // Assignment from r, g, b, w + inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; } + + // Conversion operator to uint32_t + inline operator uint32_t() const __attribute__((always_inline)) { + return color32; + } + /* + // Conversion operator to CRGB + inline operator CRGB() const __attribute__((always_inline)) { + return CRGB(r, g, b); + } + + CRGBW& scale32 (uint8_t scaledown) // 32bit math + { + if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit + uint32_t scale = scaledown + 1; + uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue + uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green + color32 = rb | wg; + return *this; + }*/ + +}; + +struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions + union { + struct { + uint16_t h; // hue + uint8_t s; // saturation + uint8_t v; // value + }; + uint32_t raw; // 32bit access + }; + inline CHSV32() __attribute__((always_inline)) = default; // default constructor + + /// Allow construction from hue, saturation, and value + /// @param ih input hue + /// @param is input saturation + /// @param iv input value + inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v + : h(ih), s(is), v(iv) {} + inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v + : h((uint16_t)ih << 8), s(is), v(iv) {} + inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV + : h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {} + inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV +}; +extern bool gammaCorrectCol; +// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) +class NeoGammaWLEDMethod { + public: + [[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel + [[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color + static void calcGammaTable(float gamma); // re-calculates & fills gamma tables + static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB) + static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB) + static inline uint32_t Correct32(uint32_t color) { // apply Gamma to RGBW32 color (WLED specific, not used by NPB) + if (!gammaCorrectCol) return color; // no gamma correction + uint8_t w = byte(color>>24), r = byte(color>>16), g = byte(color>>8), b = byte(color); // extract r, g, b, w channels + w = gammaT[w]; r = gammaT[r]; g = gammaT[g]; b = gammaT[b]; + return (uint32_t(w) << 24) | (uint32_t(r) << 16) | (uint32_t(g) << 8) | uint32_t(b); + } + private: + static uint8_t gammaT[]; + static uint8_t gammaT_inv[]; +}; +#define gamma32(c) NeoGammaWLEDMethod::Correct32(c) +#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) +#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c) +#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c) +[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); +inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; +[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); +[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); +[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); +CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); +CRGBPalette16 generateRandomPalette(); +void loadCustomPalettes(); +extern std::vector customPalettes; +inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } +inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); +void rgb2hsv(const uint32_t rgb, CHSV32& hsv); +inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv +void colorKtoRGB(uint16_t kelvin, byte* rgb); +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO +void colorFromDecOrHexString(byte* rgb, const char* in); +bool colorFromHexString(byte* rgb, const char* in); +uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); +uint16_t approximateKelvinFromRGB(uint32_t rgb); +void setRandomColor(byte* rgb); + +[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false); + +#endif diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 98cfe28fb0..4d7c7b666c 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -191,7 +191,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 // only change brightness if value changed if (bri != e131_data[dataOffset]) { bri = e131_data[dataOffset]; - strip.setBrightness(scaledBri(bri), false); + strip.setBrightness(bri, false); stateUpdated(CALL_MODE_WS_SEND); } return; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d19f89b27d..3d6211864e 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -69,133 +69,6 @@ typedef struct WiFiConfig { } } wifi_config; -//colors.cpp -#define ColorFromPalette ColorFromPaletteWLED // override fastled version - -// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color -// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts -// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB -struct CRGBW { - union { - uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB) - struct { - uint8_t b; - uint8_t g; - uint8_t r; - uint8_t w; - }; - uint8_t raw[4]; // Access as an array in the order B, G, R, W - }; - - // Default constructor - inline CRGBW() __attribute__((always_inline)) = default; - - // Constructor from a 32-bit color (0xWWRRGGBB) - constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {} - - // Constructor with r, g, b, w values - constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {} - - // Constructor from CRGB - constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {} - - // Access as an array - inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; } - - // Assignment from 32-bit color - inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; } - - // Assignment from r, g, b, w - inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; } - - // Conversion operator to uint32_t - inline operator uint32_t() const __attribute__((always_inline)) { - return color32; - } - /* - // Conversion operator to CRGB - inline operator CRGB() const __attribute__((always_inline)) { - return CRGB(r, g, b); - } - - CRGBW& scale32 (uint8_t scaledown) // 32bit math - { - if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit - uint32_t scale = scaledown + 1; - uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue - uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green - color32 = rb | wg; - return *this; - }*/ - -}; - -struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions - union { - struct { - uint16_t h; // hue - uint8_t s; // saturation - uint8_t v; // value - }; - uint32_t raw; // 32bit access - }; - inline CHSV32() __attribute__((always_inline)) = default; // default constructor - - /// Allow construction from hue, saturation, and value - /// @param ih input hue - /// @param is input saturation - /// @param iv input value - inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v - : h(ih), s(is), v(iv) {} - inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v - : h((uint16_t)ih << 8), s(is), v(iv) {} - inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV - : h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {} - inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV -}; -// similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) -class NeoGammaWLEDMethod { - public: - [[gnu::hot]] static uint8_t Correct(uint8_t value); // apply Gamma to single channel - [[gnu::hot]] static uint32_t Correct32(uint32_t color); // apply Gamma to RGBW32 color (WLED specific, not used by NPB) - [[gnu::hot]] static uint32_t inverseGamma32(uint32_t color); // apply inverse Gamma to RGBW32 color - static void calcGammaTable(float gamma); // re-calculates & fills gamma tables - static inline uint8_t rawGamma8(uint8_t val) { return gammaT[val]; } // get value from Gamma table (WLED specific, not used by NPB) - static inline uint8_t rawInverseGamma8(uint8_t val) { return gammaT_inv[val]; } // get value from inverse Gamma table (WLED specific, not used by NPB) - private: - static uint8_t gammaT[]; - static uint8_t gammaT_inv[]; -}; -#define gamma32(c) NeoGammaWLEDMethod::Correct32(c) -#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) -#define gamma32inv(c) NeoGammaWLEDMethod::inverseGamma32(c) -#define gamma8inv(c) NeoGammaWLEDMethod::rawInverseGamma8(c) -[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); -inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; -[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); -[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); -[[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); -[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); -CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); -CRGBPalette16 generateRandomPalette(); -void loadCustomPalettes(); -extern std::vector customPalettes; -inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } -inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } -void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); -void rgb2hsv(const uint32_t rgb, CHSV32& hsv); -inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv -void colorKtoRGB(uint16_t kelvin, byte* rgb); -void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb -void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO -void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO -void colorFromDecOrHexString(byte* rgb, const char* in); -bool colorFromHexString(byte* rgb, const char* in); -uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); -uint16_t approximateKelvinFromRGB(uint32_t rgb); -void setRandomColor(byte* rgb); - //dmx_output.cpp void initDMXOutput(); void handleDMXOutput(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 4414681023..e8ebaaba29 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -312,7 +312,7 @@ static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0) jsonTransitionOnce = true; if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame strip.setTransition(0); - strip.setBrightness(scaledBri(bri), true); + strip.setBrightness(bri, true); // freeze and init to black if (!seg.freeze) { diff --git a/wled00/led.cpp b/wled00/led.cpp index 43771f9d53..35f5003679 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -57,7 +57,7 @@ void toggleOnOff() //scales the brightness with the briMultiplier factor byte scaledBri(byte in) { - unsigned val = ((uint16_t)in*briMultiplier)/100; + unsigned val = ((unsigned)in*briMultiplier)/100; if (val > 255) val = 255; return (byte)val; } @@ -68,7 +68,7 @@ void applyBri() { if (realtimeOverride || !(realtimeMode && arlsForceMaxBri)) { //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); - strip.setBrightness(scaledBri(briT)); + strip.setBrightness(briT); } } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index bdb60c363a..8e0f654a86 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -424,7 +424,7 @@ void realtimeLock(uint32_t timeoutMs, byte md) } // if strip is off (bri==0) and not already in RTM if (briT == 0) { - strip.setBrightness(scaledBri(briLast), true); + strip.setBrightness(briLast, true); } } @@ -434,14 +434,14 @@ void realtimeLock(uint32_t timeoutMs, byte md) realtimeMode = md; if (realtimeOverride) return; - if (arlsForceMaxBri) strip.setBrightness(scaledBri(255), true); + if (arlsForceMaxBri) strip.setBrightness(255, true); if (briT > 0 && md == REALTIME_MODE_GENERIC) strip.show(); } void exitRealtime() { if (!realtimeMode) return; if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE; - strip.setBrightness(scaledBri(bri), true); + strip.setBrightness(bri, true); realtimeTimeout = 0; // cancel realtime mode immediately realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately realtimeIP[0] = 0; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c372d22abd..fd0155d6af 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -187,12 +187,10 @@ void WLED::loop() doInitBusses = false; DEBUG_PRINTLN(F("Re-init busses.")); bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses) - BusManager::removeAll(); strip.finalizeInit(); // will create buses and also load default ledmap if present - BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005 if (aligned) strip.makeAutoSegments(); else strip.fixInvalidSegments(); - BusManager::setBrightness(bri); // fix re-initialised bus' brightness + BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824 configNeedsWrite = true; } if (loadLedmap >= 0) { diff --git a/wled00/wled.h b/wled00/wled.h index 52bb2f9366..50a5531bd9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -192,6 +192,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #include "fcn_declare.h" #include "NodeStruct.h" #include "pin_manager.h" +#include "colors.h" #include "bus_manager.h" #include "FX.h"