diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index c53bfe0d8e..fffa162987 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1763,6 +1763,7 @@ void WS2812FX::show() { return; // no pixels allocated, nothing to show } + BusManager::updateGammaUse(); // update gamma use (and bus brightness correction) in case something changed unsigned long showNow = millis(); size_t diff = showNow - _lastShow; @@ -1791,8 +1792,6 @@ void WS2812FX::show() { 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) if (cctFromRgb) BusManager::setSegmentCCT(-1); - // use color gamma correction if enabled, not in realtime mode with gamma disabled or currently overriding RT mode - bool useGammaCorrection = gammaCorrectCol && !(realtimeMode && arlsDisableGammaCorrection && !realtimeOverride); for (size_t i = 0; i < totalLen; i++) { // when correctWB is true setSegmentCCT() will convert CCT into K with which we can then @@ -1800,10 +1799,7 @@ void WS2812FX::show() { if (_pixelCCT) { // cctFromRgb already exluded at allocation if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB); } - uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32) - if (c > 0 && useGammaCorrection) - c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss BusManager::setPixelColor(getMappedPixelIndex(i), c); } Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments @@ -1879,7 +1875,8 @@ void WS2812FX::setCCT(uint16_t k) { // direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply // direct=false immediately triggers an effect redraw void WS2812FX::setBrightness(uint8_t b, bool direct) { - if (gammaCorrectBri) b = gamma8(b); + if (b > 0 && gammaCorrectBri) + b = gamma8(map(b, 1, 255, gamma8inv(1), 255)); // map from 1 to 255 in gamma space or strip will turn off at low brightness as gamma(b) goes to 0 if (_brightness == b) return; _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 01cfa93a4d..f115fc4886 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -266,33 +266,44 @@ void BusDigital::setStatusPixel(uint32_t c) { } } +void BusDigital::setBrightness(uint8_t b) { + _bri = b; + if (_bri > 0 && _bri < 255 && applyGamma) + _bri = gamma8inv(_bri + 1); // limit min brightness so gamma does not dim to black +} + // note: using WLED_O2_ATTR makes this function ~7% faster at the expense of 600 bytes of flash void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT + if (_reversed) pix = _len - pix - 1; + pix += _skip; uint8_t cctWW = 0, cctCW = 0; uint16_t wwcw = 0; - if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW); - c = color_fade(c, _bri, true); // apply brightness - - if (hasCCT()) { - wwcw = ((cctCW + 1) * _bri) & 0xFF00; // apply brightness to CCT (store CW in upper byte) - wwcw |= ((cctWW + 1) * _bri) >> 8; - if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w - } + // apply brightness, color correction and white calculation, if black, we can skip all of it as they are no-ops + if (c > 0) { + c = color_fade(c, _bri, false); // apply brightness + c = gamma32(c); // apply gamma correction if (currently) used + if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT + + if (hasWhite()) c = autoWhiteCalc(c, cctWW, cctCW); + + if (hasCCT()) { + wwcw = uint16_t(cctCW) << 8; // store CW in upper byte + wwcw |= cctWW; + if (_type == TYPE_WS2812_WWA) c = RGBW32(wwcw, wwcw >> 8, 0, W(c)); // ww,cw, 0, w + } - 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 (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; 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; @@ -342,6 +353,18 @@ size_t BusDigital::getBusSize() const { return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer } +uint32_t BusDigital::restoreColorLossy(uint32_t c, uint8_t restoreBri) const { + c = gamma32inv(c); // note: if ABL is used, this can skew colors (chain is scale->gamma->scaleABL) + if (restoreBri < 255) { + uint8_t* chan = (uint8_t*) &c; + for (uint_fast8_t i = 0; i < 4; i++) { + uint_fast16_t val = chan[i]; + chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); + } + } + return c; +} + void BusDigital::setColorOrder(uint8_t colorOrder) { // upper nibble contains W swap information if ((colorOrder & 0x0F) > 5) return; @@ -565,8 +588,15 @@ void BusPwm::show() { // for all other cases it will just try to "spread" the load on PSU // Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is // also mandatory that both channels use the same timer (pinManager takes care of that). + unsigned duty; for (unsigned i = 0; i < numPins; i++) { - unsigned duty = (_data[i] * pwmBri) / 255; + unsigned duty; + bool isLinearChannel = cctICused && ((_type == TYPE_ANALOG_2CH && i == 1) || (_type == TYPE_ANALOG_5CH && i == 4)); + if (applyGamma && !isLinearChannel) { + duty = (unsigned)(powf((float)_data[i] / 255.0f, gammaCorrectVal) * pwmBri); // apply full resolution gamma correction: way more accurate than using gamma8(_data[i]) + } else { + duty = (_data[i] * pwmBri) / 255; + } unsigned deadTime = 0; if (_type == TYPE_ANALOG_2CH && Bus::_cctBlend <= 0) { @@ -736,6 +766,7 @@ BusNetwork::BusNetwork(const BusConfig &bc) void BusNetwork::setPixelColor(unsigned pix, uint32_t c) { if (!_valid || pix >= _len) return; uint8_t ww, cw; // dummy, unused + // note: gamma correction is done by the receiver if (_hasWhite) c = autoWhiteCalc(c, ww, cw); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT unsigned offset = pix * _UDPchannels; @@ -1144,8 +1175,8 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc. void IRAM_ATTR BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; // note: no need to check pix >= _len as that is checked in containsPixel() + c = gamma32(c); // apply gamma correction if (currently) used // if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT - if (_ledBuffer) { CRGB fastled_col = CRGB(c); if (_ledBuffer[pix] != fastled_col) { @@ -1448,6 +1479,11 @@ void BusManager::off() { _gMilliAmpsUsed = 0; // reset, assume no LED idle current if relay is off } +void BusManager::updateGammaUse() { + applyGamma = gammaCorrectCol && !(realtimeMode && arlsDisableGammaCorrection && !realtimeOverride); + BusManager::setBrightness(scaledBri(bri)); // update brightness for all (digital) buses in case gamma usage changed +} + void BusManager::show() { applyABL(); // apply brightness limit, updates _gMilliAmpsUsed for (auto &bus : busses) { diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index eeb10cff24..00c412d64a 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -253,6 +253,7 @@ class BusDigital : public Bus { void show() override; bool canShow() const override; void setStatusPixel(uint32_t c) override; + void setBrightness(uint8_t b) override; [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; @@ -271,6 +272,7 @@ class BusDigital : public Bus { bool isI2S(); // true if this bus uses I2S driver void begin() override; void cleanup(); + uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) const; static std::vector getLEDTypes(); @@ -288,17 +290,6 @@ class BusDigital : public Bus { void *_busPtr; static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() - - inline uint32_t restoreColorLossy(uint32_t c, uint8_t restoreBri) const { - if (restoreBri < 255) { - uint8_t* chan = (uint8_t*) &c; - for (uint_fast8_t i=0; i<4; i++) { - uint_fast16_t val = chan[i]; - chan[i] = ((val << 8) + restoreBri) / (restoreBri + 1); //adding _bri slightly improves recovery / stops degradation on re-scale - } - } - return c; - } }; @@ -559,6 +550,7 @@ namespace BusManager { void on(); void off(); + void updateGammaUse(); [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c); [[gnu::hot]] uint32_t getPixelColor(unsigned pix); void show(); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 767b8cf0b2..638fda5358 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -667,13 +667,13 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) uint8_t NeoGammaWLEDMethod::Correct(uint8_t value) { - if (!gammaCorrectCol) return value; + if (!applyGamma) return value; // gamma is (currently) disabled return gammaT[value]; } uint32_t NeoGammaWLEDMethod::inverseGamma32(uint32_t color) { - if (!gammaCorrectCol) return color; + if (!applyGamma) return color; uint8_t w = W(color); uint8_t r = R(color); uint8_t g = G(color); diff --git a/wled00/colors.h b/wled00/colors.h index 00fe4fb498..a5fadcdcdc 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -29,7 +29,8 @@ struct CRGBW; // forward declations struct CHSV32; -extern bool gammaCorrectCol; +extern bool applyGamma; +extern float gammaCorrectVal; // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { public: @@ -39,7 +40,7 @@ class NeoGammaWLEDMethod { 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 + if (!applyGamma) return color; // gamma is (currently) disabled 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); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index ac5554df40..1489cb0d19 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -439,9 +439,9 @@ void realtimeLock(uint32_t timeoutMs, byte md) void exitRealtime() { if (!realtimeMode) return; if (realtimeOverride == REALTIME_OVERRIDE_ONCE) realtimeOverride = REALTIME_OVERRIDE_NONE; - strip.setBrightness(bri, true); realtimeTimeout = 0; // cancel realtime mode immediately realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately + strip.setBrightness(bri, true); realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again strip.getMainSegment().freeze = false; diff --git a/wled00/wled.h b/wled00/wled.h index a948f87521..d2c909d885 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -392,6 +392,7 @@ WLED_GLOBAL bool cctICused _INIT(true); // CCT IC used (Athom 15W bulb WLED_GLOBAL bool cctICused _INIT(false); // CCT IC used (Athom 15W bulbs) #endif WLED_GLOBAL bool gammaCorrectCol _INIT(true); // use gamma correction on colors +WLED_GLOBAL bool applyGamma _INIT(true); // apply gamma correction to colors if not (temporarily) disabled WLED_GLOBAL bool gammaCorrectBri _INIT(false); // use gamma correction on brightness WLED_GLOBAL float gammaCorrectVal _INIT(2.2f); // gamma correction value