Skip to content

Commit d5d7fde

Browse files
authored
use video scaling instead of NPB luminance & new ABL (#4798)
* updated color scaling to preserve hue at low brightness resulting in much better colors * replace NPBlg with NPB, moved brightness scaling to bus manager * improved gamma table calculation: fixed mismatch in inverting gamma table calculation: inversion should now be as good as it gets * code cleanup, fixed gamma being applied in unnecessary places 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
1 parent 8aeb9e1 commit d5d7fde

File tree

15 files changed

+452
-538
lines changed

15 files changed

+452
-538
lines changed

wled00/FX.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7528,9 +7528,9 @@ uint16_t mode_2Ddistortionwaves() {
75287528
byte valueG = gdistort + ((a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 ))<<1);
75297529
byte valueB = bdistort + ((a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 ))<<1);
75307530

7531-
valueR = gamma8(cos8_t(valueR));
7532-
valueG = gamma8(cos8_t(valueG));
7533-
valueB = gamma8(cos8_t(valueB));
7531+
valueR = cos8_t(valueR);
7532+
valueG = cos8_t(valueG);
7533+
valueB = cos8_t(valueB);
75347534

75357535
if(SEGMENT.palette == 0) {
75367536
// use RGB values (original color mode)

wled00/FX_fcn.cpp

Lines changed: 12 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,8 +1194,9 @@ void WS2812FX::finalizeInit() {
11941194
if (busEnd > _length) _length = busEnd;
11951195
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
11961196
bus->begin();
1197-
bus->setBrightness(bri);
1197+
bus->setBrightness(scaledBri(bri));
11981198
}
1199+
BusManager::initializeABL(); // init brightness limiter
11991200
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
12001201

12011202
Segment::maxWidth = _length;
@@ -1297,7 +1298,7 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t
12971298
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
12981299
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
12991300
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
1300-
#ifdef CONFIG_IDF_TARGET_ESP32C3
1301+
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
13011302
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
13021303
#else
13031304
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
@@ -1308,10 +1309,10 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; }
13081309
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
13091310
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
13101311
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
1311-
#ifdef CONFIG_IDF_TARGET_ESP32C3
1312-
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
1312+
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
1313+
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
13131314
#else
1314-
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
1315+
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
13151316
#endif
13161317
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
13171318
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
@@ -1550,66 +1551,6 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
15501551
Segment::setClippingRect(0, 0); // disable clipping for overlays
15511552
}
15521553

1553-
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
1554-
static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) {
1555-
unsigned milliAmpsMax = BusManager::ablMilliampsMax();
1556-
if (milliAmpsMax > 0) {
1557-
unsigned milliAmpsTotal = 0;
1558-
unsigned avgMilliAmpsPerLED = 0;
1559-
unsigned lengthDigital = 0;
1560-
bool useWackyWS2815PowerModel = false;
1561-
1562-
for (size_t i = 0; i < BusManager::getNumBusses(); i++) {
1563-
const Bus *bus = BusManager::getBus(i);
1564-
if (!(bus && bus->isDigital() && bus->isOk())) continue;
1565-
unsigned maPL = bus->getLEDCurrent();
1566-
if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL)
1567-
if (maPL == 255) {
1568-
useWackyWS2815PowerModel = true;
1569-
maPL = 12; // WS2815 uses 12mA per channel
1570-
}
1571-
avgMilliAmpsPerLED += maPL * bus->getLength();
1572-
lengthDigital += bus->getLength();
1573-
// sum up the usage of each LED on digital bus
1574-
uint32_t busPowerSum = 0;
1575-
for (unsigned j = 0; j < bus->getLength(); j++) {
1576-
uint32_t c = pixels[j + bus->getStart()];
1577-
byte r = R(c), g = G(c), b = B(c), w = W(c);
1578-
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
1579-
busPowerSum += (max(max(r,g),b)) * 3;
1580-
} else {
1581-
busPowerSum += (r + g + b + w);
1582-
}
1583-
}
1584-
// RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
1585-
if (bus->hasWhite()) {
1586-
busPowerSum *= 3;
1587-
busPowerSum >>= 2; //same as /= 4
1588-
}
1589-
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
1590-
milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255);
1591-
}
1592-
if (lengthDigital > 0) {
1593-
avgMilliAmpsPerLED /= lengthDigital;
1594-
1595-
if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation
1596-
unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power
1597-
if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget
1598-
powerBudget -= lengthDigital;
1599-
} else {
1600-
powerBudget = 0;
1601-
}
1602-
if (milliAmpsTotal > powerBudget) {
1603-
//scale brightness down to stay in current limit
1604-
unsigned scaleB = powerBudget * 255 / milliAmpsTotal;
1605-
brightness = ((brightness * scaleB) >> 8) + 1;
1606-
}
1607-
}
1608-
}
1609-
}
1610-
return brightness;
1611-
}
1612-
16131554
void WS2812FX::show() {
16141555
if (!_pixels) return; // no pixels allocated, nothing to show
16151556

@@ -1637,10 +1578,6 @@ void WS2812FX::show() {
16371578
show_callback callback = _callback;
16381579
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
16391580

1640-
// determine ABL brightness
1641-
uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels);
1642-
if (newBri != _brightness) BusManager::setBrightness(newBri);
1643-
16441581
// paint actual pixels
16451582
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
16461583
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
@@ -1651,7 +1588,11 @@ void WS2812FX::show() {
16511588
if (_pixelCCT) { // cctFromRgb already exluded at allocation
16521589
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
16531590
}
1654-
BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i]));
1591+
1592+
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
1593+
if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
1594+
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
1595+
BusManager::setPixelColor(getMappedPixelIndex(i), c);
16551596
}
16561597
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
16571598

@@ -1663,9 +1604,6 @@ void WS2812FX::show() {
16631604
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
16641605
BusManager::show();
16651606

1666-
// restore brightness for next frame
1667-
if (newBri != _brightness) BusManager::setBrightness(_brightness);
1668-
16691607
if (diff > 0) { // skip calculation if no time has passed
16701608
size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
16711609
_cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
@@ -1730,7 +1668,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
17301668
if (_brightness == 0) { //unfreeze all segments on power off
17311669
for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable
17321670
}
1733-
BusManager::setBrightness(b);
1671+
BusManager::setBrightness(scaledBri(b));
17341672
if (!direct) {
17351673
unsigned long t = millis();
17361674
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon

0 commit comments

Comments
 (0)