Skip to content

Commit 9a76beb

Browse files
committed
Per-output brightness scaling (AKA brightness factor)
1 parent 34c566b commit 9a76beb

6 files changed

Lines changed: 34 additions & 14 deletions

File tree

wled00/bus_manager.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr)
157157
_consistent = nr < WLED_MAX_RMT_CHANNELS || _skip > 0; // TODO: no longer needed once neopixelbus#905 (or similar) is merged or use https://github.com/blazoncek/NeoPixelBus.git#clearto-buffers
158158
_iType = PolyBus::getI(bc.type, _pins, nr);
159159
if (_iType == I_NONE) { DEBUGBUS_PRINTLN(F("Incorrect iType!")); return; }
160+
_scale = bc.scale;
160161
_hasRgb = hasRGB(bc.type);
161162
_hasWhite = hasWhite(bc.type);
162163
_hasCCT = hasCCT(bc.type);
@@ -247,7 +248,8 @@ void BusDigital::setPixelColor(unsigned pix, uint32_t c) {
247248
if (!_valid || pix >= _len) return;
248249
if (hasWhite()) c = autoWhiteCalc(c);
249250
if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT
250-
c = gamma32Func(color_fade(c, _bri, true));
251+
uint8_t adjBri = _scale != 100 ? scaleBri(_bri, _scale) : _bri;
252+
c = gamma32Func(color_fade(c, adjBri, true));
251253

252254
// pre-calcualte power usage for per-output ABL (a single bus should never have over 2000 LEDs so uint32_t is enough for _busPowerSum)
253255
// WARNING: assumes pixel is not modified agin until show() is called
@@ -407,6 +409,7 @@ BusPwm::BusPwm(const BusConfig &bc)
407409
ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift)
408410
#endif
409411
}
412+
_scale = bc.scale;
410413
_hasRgb = hasRGB(bc.type);
411414
_hasWhite = hasWhite(bc.type);
412415
_hasCCT = hasCCT(bc.type);
@@ -463,7 +466,7 @@ void BusPwm::show() {
463466

464467
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
465468
// see: https://en.wikipedia.org/wiki/Lightness
466-
unsigned pwmBri = _bri;
469+
unsigned pwmBri = _scale != 100 ? scaleBri(_bri, _scale) : _bri;
467470
if (pwmBri < 21) { // linear response for values [0-20]
468471
pwmBri = (pwmBri * maxBri + 2300 / 2) / 2300 ; // adding '0.5' before division for correct rounding, 2300 gives a good match to CIE curve
469472
} else { // cubic response for values [21-255]
@@ -609,6 +612,7 @@ BusNetwork::BusNetwork(const BusConfig &bc)
609612
_UDPtype = 0;
610613
break;
611614
}
615+
_scale = bc.scale;
612616
_hasRgb = hasRGB(bc.type);
613617
_hasWhite = hasWhite(bc.type);
614618
_hasCCT = false;
@@ -638,7 +642,8 @@ void BusNetwork::setPixelColor(unsigned pix, uint32_t c) {
638642
void BusNetwork::show() {
639643
if (!_valid || !canShow()) return;
640644
_broadcastLock = true;
641-
realtimeBroadcast(_UDPtype, _client, _len, _data, _bri, hasWhite());
645+
uint8_t adjBri = _scale != 100 ? scaleBri(_bri, _scale) : _bri;
646+
realtimeBroadcast(_UDPtype, _client, _len, _data, adjBri, hasWhite());
642647
_broadcastLock = false;
643648
}
644649

@@ -782,7 +787,7 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc)
782787
#ifdef WLED_DEBUG_BUS
783788
size_t lastHeap = getFreeHeapSize();
784789
#endif
785-
//_valid = false; // not needed as Bus constructor already set it to false
790+
_scale = bc.scale;
786791
_hasRgb = true;
787792
_hasWhite = false;
788793
_hasCCT = false;
@@ -1006,9 +1011,10 @@ void BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c) {
10061011
}
10071012

10081013
void BusHub75Matrix::setBrightness(uint8_t b) {
1009-
_bri = scaleBri(b);
1014+
Bus::setBrightness(b);
10101015
if (!_valid) return;
1011-
display->setBrightness(_bri);
1016+
uint8_t adjBri = _scale != 100 ? scaleBri(_bri, _scale) : _bri;
1017+
display->setBrightness(adjBri);
10121018
}
10131019

10141020
void BusHub75Matrix::show(void) {

wled00/bus_manager.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb);
6161
#define WS2812_2CH_3X_SPANS_2_ICS(i) ((i)&0x01) // every other LED zone is on two different ICs
6262

6363
extern byte briMultiplier;
64-
static inline uint8_t scaleBri(uint8_t bri) { unsigned b = ((unsigned)bri*briMultiplier)/100; return b > 255 ? 255 : b; }
6564

6665
struct BusConfig; // forward declaration
6766

@@ -114,6 +113,7 @@ class Bus {
114113
, _reversed(reversed)
115114
, _valid(false)
116115
, _needsRefresh(refresh)
116+
, _scale(100)
117117
{
118118
_autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY;
119119
};
@@ -125,7 +125,7 @@ class Bus {
125125
virtual bool canShow() const { return true; }
126126
virtual void setStatusPixel(uint32_t c) {}
127127
virtual void setPixelColor(unsigned pix, uint32_t c) = 0;
128-
virtual void setBrightness(uint8_t b) { _bri = scaleBri(b); };
128+
virtual void setBrightness(uint8_t b) { _bri = scaleBri(b, briMultiplier); }; // use global modifier
129129
virtual void setColorOrder(uint8_t co) {}
130130
virtual uint8_t getBrightness() const { return _bri; }
131131
virtual size_t getPins(uint8_t* pinArray = nullptr) const { return 0; }
@@ -162,6 +162,7 @@ class Bus {
162162
inline bool isReversed() const { return _reversed; }
163163
inline bool isOffRefreshRequired() const { return _needsRefresh; }
164164
inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; }
165+
inline uint8_t getBrightnessFactor() const { return _scale; }
165166

166167
static inline std::vector<LEDType> getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes
167168
static constexpr size_t getNumberOfPins(uint8_t type) { return isUsermod(type) || isHub75(type) ? 5 : isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK
@@ -225,6 +226,7 @@ class Bus {
225226
bool _hasWhite;// : 1;
226227
bool _hasCCT;// : 1;
227228
//} __attribute__ ((packed));
229+
uint8_t _scale; // used to scale output in % (i.e. <100 reduce brightness, >100 increase brightness)
228230
uint8_t _autoWhiteMode;
229231
// global Auto White Calculation override
230232
static uint8_t _gAWM;
@@ -240,6 +242,7 @@ class Bus {
240242
static uint8_t _cctBlend;
241243

242244
uint32_t autoWhiteCalc(uint32_t c) const;
245+
static inline uint8_t scaleBri(uint8_t bri, uint8_t multiplier) { unsigned b = ((unsigned)bri*multiplier)/100; return b > 255 ? 255 : b; }
243246
};
244247

245248

@@ -429,8 +432,9 @@ struct BusConfig {
429432
uint8_t milliAmpsPerLed;
430433
uint16_t milliAmpsMax;
431434
String text;
435+
uint8_t scale;
432436

433-
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart = 0, uint16_t len = DEFAULT_LED_COUNT, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "")
437+
BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart = 0, uint16_t len = DEFAULT_LED_COUNT, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT, String sometext = "", uint8_t pscale = 100)
434438
: count(std::max(len,(uint16_t)1))
435439
, start(pstart)
436440
, colorOrder(pcolorOrder)
@@ -441,6 +445,7 @@ struct BusConfig {
441445
, milliAmpsPerLed(maPerLed)
442446
, milliAmpsMax(maMax)
443447
, text(sometext)
448+
, scale(std::max(1,(int)pscale))
444449
{
445450
refreshReq = (bool) GET_BIT(busType,7);
446451
type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh)

wled00/cfg.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
243243
bool refresh = elm["ref"] | false;
244244
uint16_t freqkHz = elm[F("freq")] | 0; // will be in kHz for DotStar and Hz for PWM
245245
uint8_t AWmode = elm[F("rgbwm")] | (Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? RGBW_MODE_MANUAL_ONLY : Bus::getGlobalAWMode());
246+
uint8_t scale = elm[F("bf")] | 100;
246247
uint8_t maPerLed = elm[F("ledma")] | LED_MILLIAMPS_DEFAULT;
247248
uint16_t maMax = elm[F("maxpwr")] | 0; // maMax > 0 means per bus PP-ABL is enabled (bus has its own maximum allowable current)
248249
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
@@ -252,7 +253,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
252253
}
253254
ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh
254255
String host = elm[F("text")] | String();
255-
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host);
256+
busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax, host, scale);
256257
doInit |= INIT_BUS; // finalization done in beginStrip()
257258
if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want
258259
}
@@ -978,6 +979,7 @@ void serializeConfig() {
978979
ins["type"] = bus->getType() & 0x7F;
979980
ins["ref"] = bus->isOffRefreshRequired();
980981
ins[F("rgbwm")] = bus->getAutoWhiteMode();
982+
ins[F("bf")] = bus->getBrightnessFactor();
981983
ins[F("freq")] = bus->getFrequency();
982984
ins[F("maxpwr")] = bus->getMaxCurrent();
983985
ins[F("ledma")] = bus->getLEDCurrent();

wled00/data/settings_leds.htm

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
// these functions correspond to C macros found in const.h
1717
function gT(t) { for (let type of ledTypes) if (t == type.i) return type; } // getType from available ledTypes
1818
function isPWM(t) { return gT(t).t.charAt(0) === "A"; } // is PWM type
19-
function isAna(t) { return gT(t).t === "" || isPWM(t); } // is analog type
19+
function isOnOff(t){ return gT(t).t === ""; } // is On/Off type
20+
function isAna(t) { return isOnOff(t) || isPWM(t); } // is analog type
2021
function isD1P(t) { return gT(t).t === "D"; } // is digital 1 pin type
2122
function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type
2223
function isDig(t) { return isD1P(t) || isD2P(t); } // is digital type
@@ -321,6 +322,7 @@
321322
<div id="dig${s}s">Skip <input type="number" name="SL${s}" min="0" max="255" value="0" oninput="UI()"> LEDs</div>
322323
<div id="dig${s}f"><span id="off${s}"></span>: <input id="rf${s}" type="checkbox" name="RF${s}"></div>
323324
<div id="dig${s}a">Auto-calculate W channel from RGB:<br><select name="AW${s}"><option value=0>None</option><option value=1>Brighter</option><option value=2>Accurate</option><option value=3>Dual</option><option value=4>Max</option></select>&nbsp;</div>
325+
<div id="bf${s}">Brightness factor: <input type="number" min="1" max="255" name="BF${s}"> %</div>
324326
</div>`;
325327
f.insertAdjacentHTML("beforeend", cn);
326328
// fill led types (credit @netmindz)
@@ -883,6 +885,7 @@
883885
gId("dig"+n+"a").style.display = (hasW(t)) ? "block":"none"; // auto calculate white
884886
gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t) || isHub75(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off)
885887
gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266
888+
gId("bf"+n).style.display = isOnOff(t) ? "none" : "block"; // brightness multiplier has no effect on On/Off bus
886889
if (!isNet(t) || is8266()) gN("HS"+n).value = ""; // cleart host field if not network type or ESP8266
887890
});
888891
// rebuild GPIO dropdowns
@@ -1002,7 +1005,7 @@ <h3>Defaults</h3>
10021005
Use Gamma correction for color: <input type="checkbox" name="GC"> (recommended)<br>
10031006
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br>
10041007
Use Gamma value: <input name="GV" type="number" class="m" min="1" max="3" step="0.1" required><br><br>
1005-
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
1008+
Global brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
10061009
<h3>Transitions</h3>
10071010
Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
10081011
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>

wled00/set.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
146146
}
147147
}
148148

149-
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed;
149+
unsigned colorOrder, type, skip, awmode, channelSwap, maPerLed, scale;
150150
unsigned length, start, maMax;
151151
uint8_t pins[5] = {255, 255, 255, 255, 255};
152152
String text;
@@ -182,6 +182,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
182182
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED mA
183183
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max mA
184184
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
185+
char bf[4] = "BF"; bf[2] = offset+s; bf[3] = 0; //brightness factor
185186
if (!request->hasArg(lp)) {
186187
DEBUG_PRINTF_P(PSTR("# of buses: %d\n"), s);
187188
break;
@@ -201,6 +202,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
201202
break; // no parameter
202203
}
203204
awmode = request->arg(aw).toInt();
205+
scale = max(1, min(255, (int)request->arg(bf).toInt()));
204206
uint16_t freq = request->arg(sp).toInt();
205207
if (Bus::isPWM(type)) {
206208
switch (freq) {
@@ -245,7 +247,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
245247
text = request->arg(hs).substring(0,31);
246248
// actual finalization is done in WLED::loop() (removing old busses and adding new)
247249
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
248-
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text);
250+
busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax, text, scale);
249251
busesChanged = true;
250252
}
251253
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed

wled00/xml.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
336336
char la[4] = "LA"; la[2] = offset+s; la[3] = 0; //LED current
337337
char ma[4] = "MA"; ma[2] = offset+s; ma[3] = 0; //max per-port PSU current
338338
char hs[4] = "HS"; hs[2] = offset+s; hs[3] = 0; //hostname (for network types, custom text for others)
339+
char bf[4] = "BF"; bf[2] = offset+s; bf[3] = 0; //brightness factor
339340
settingsScript.print(F("addLEDs(1);"));
340341
uint8_t pins[5];
341342
int nPins = bus->getPins(pins);
@@ -383,6 +384,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
383384
printSetFormValue(settingsScript,la,bus->getLEDCurrent());
384385
printSetFormValue(settingsScript,ma,bus->getMaxCurrent());
385386
printSetFormValue(settingsScript,hs,bus->getCustomText().c_str());
387+
printSetFormValue(settingsScript,bf,bus->getBrightnessFactor());
386388
sumMa += bus->getMaxCurrent();
387389
}
388390
// strip.milliAmpsMax > 0 means per strip ABL is enabled

0 commit comments

Comments
 (0)