Skip to content
Open
9 changes: 3 additions & 6 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -1791,19 +1792,14 @@ 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
// correct/adjust RGB value according to desired CCT value, it will still affect actual WW/CW ratio
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
Expand Down Expand Up @@ -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
Expand Down
76 changes: 56 additions & 20 deletions wled00/bus_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment thread
DedeHai marked this conversation as resolved.

// 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
Comment thread
DedeHai marked this conversation as resolved.
Comment thread
DedeHai marked this conversation as resolved.
}

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;
Expand Down Expand Up @@ -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)
Comment thread
DedeHai marked this conversation as resolved.
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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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) {
Expand Down
14 changes: 3 additions & 11 deletions wled00/bus_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<LEDType> getLEDTypes();

Expand All @@ -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;
}
};


Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions wled00/colors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions wled00/colors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion wled00/udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading