GCC Code Coverage Report


Directory: ./
File: overlays/wled/FX_fcn.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 477 945 50.5%
Functions: 36 70 51.4%
Branches: 387 1156 33.5%

Line Branch Exec Source
1 /*
2 WS2812FX_fcn.cpp contains all utility functions
3 Harm Aldick - 2016
4 www.aldick.org
5
6 Copyright (c) 2016 Harm Aldick
7 Licensed under the EUPL v. 1.2 or later
8 Adapted from code originally licensed under the MIT license
9
10 Modified heavily for WLED
11 */
12 #include "wled.h"
13 #include "FX.h"
14 #include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
15 #include "palettes.h"
16
17 #include <climits>
18
19 /*
20 Custom per-LED mapping has moved!
21
22 Create a file "ledmap.json" using the edit page.
23
24 this is just an example (30 LEDs). It will first set all even, then all uneven LEDs.
25 {"map":[
26 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28,
27 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]}
28
29 another example. Switches direction every 5 LEDs.
30 {"map":[
31 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14,
32 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]}
33 */
34
35 #ifndef PIXEL_COUNTS
36 #define PIXEL_COUNTS DEFAULT_LED_COUNT
37 #endif
38
39 #ifndef DATA_PINS
40 #define DATA_PINS DEFAULT_LED_PIN
41 #endif
42
43 #ifndef LED_TYPES
44 #define LED_TYPES DEFAULT_LED_TYPE
45 #endif
46
47 #ifndef DEFAULT_LED_COLOR_ORDER
48 #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB
49 #endif
50
51
52 #if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES
53 #error "Max segments must be at least max number of busses!"
54 #endif
55
56 static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) {
57 return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0;
58 }
59
60 #ifndef WLED_DISABLE_FPP
61 static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) {
62 // Pins provided < pins required -> always invalid
63 // Pins provided = pins required -> always valid
64 // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated
65 return (sumPinsRequired(types, numTypes) > numPins) ? false :
66 (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0;
67 }
68 #else
69 static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) {
70 return true;
71 }
72 #endif
73
74 ///////////////////////////////////////////////////////////////////////////////
75 // Segment class implementation
76 ///////////////////////////////////////////////////////////////////////////////
77 unsigned Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[]
78 uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
79 uint16_t Segment::maxHeight = 1;
80 unsigned Segment::_vLength = 0;
81 unsigned Segment::_vWidth = 0;
82 unsigned Segment::_vHeight = 0;
83 uint8_t Segment::_segBri = 0;
84 uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0};
85 bool Segment::_colorScaled = false;
86 CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black);
87 CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
88 CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
89 uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
90 uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only)
91 uint16_t Segment::_transitionprogress = 0xFFFF;
92
93 #ifndef WLED_DISABLE_MODE_BLEND
94 bool Segment::_modeBlend = false;
95 uint16_t Segment::_clipStart = 0;
96 uint16_t Segment::_clipStop = 0;
97 uint8_t Segment::_clipStartY = 0;
98 uint8_t Segment::_clipStopY = 1;
99 #endif
100
101 // copy constructor
102 ✗ Segment::Segment(const Segment &orig) {
103 //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this);
104 ✗ memcpy((void*)this, (void*)&orig, sizeof(Segment));
105 ✗ _t = nullptr; // copied segment cannot be in transition
106 ✗ name = nullptr;
107 ✗ data = nullptr;
108 ✗ _dataLen = 0;
109 ✗ if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
110 ✗ if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
111 ✗ }
112
113 // move constructor
114 1196 Segment::Segment(Segment &&orig) noexcept {
115 //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this);
116 1196 memcpy((void*)this, (void*)&orig, sizeof(Segment));
117 1196 orig._t = nullptr; // old segment cannot be in transition any more
118 1196 orig.name = nullptr;
119 1196 orig.data = nullptr;
120 1196 orig._dataLen = 0;
121 1196 }
122
123 // copy assignment
124 ✗ Segment& Segment::operator= (const Segment &orig) {
125 //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
126 ✗ if (this != &orig) {
127 // clean destination
128 ✗ if (name) { free(name); name = nullptr; }
129 ✗ stopTransition();
130 ✗ deallocateData();
131 // copy source
132 ✗ memcpy((void*)this, (void*)&orig, sizeof(Segment));
133 // erase pointers to allocated data
134 ✗ data = nullptr;
135 ✗ _dataLen = 0;
136 // copy source data
137 ✗ if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
138 ✗ if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
139 }
140 ✗ return *this;
141 }
142
143 // move assignment
144 ✗ Segment& Segment::operator= (Segment &&orig) noexcept {
145 //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
146 ✗ if (this != &orig) {
147 ✗ if (name) { free(name); name = nullptr; } // free old name
148 ✗ stopTransition();
149 ✗ deallocateData(); // free old runtime data
150 ✗ memcpy((void*)this, (void*)&orig, sizeof(Segment));
151 ✗ orig.name = nullptr;
152 ✗ orig.data = nullptr;
153 ✗ orig._dataLen = 0;
154 ✗ orig._t = nullptr; // old segment cannot be in transition
155 }
156 ✗ return *this;
157 }
158
159 // allocates effect data buffer on heap and initialises (erases) it
160 1706 bool IRAM_ATTR_YN Segment::allocateData(size_t len) {
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1706 times.
1706 if (len == 0) return false; // nothing to do
162
3/4
✓ Branch 0 taken 1317 times.
✓ Branch 1 taken 389 times.
✓ Branch 2 taken 1317 times.
✗ Branch 3 not taken.
1706 if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation)
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1317 times.
1317 if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation
164 1317 return true;
165 }
166 //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this);
167 389 deallocateData(); // if the old buffer was smaller release it first
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 389 times.
389 if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) {
169 // not enough memory
170 DEBUG_PRINT(F("!!! Effect RAM depleted: "));
171 DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData());
172 ✗ errorFlag = ERR_NORAM;
173 ✗ return false;
174 }
175 // do not use SPI RAM on ESP32 since it is slow
176 389 data = (byte*)calloc(len, sizeof(byte));
177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 389 times.
389 if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed
178 389 Segment::addUsedSegmentData(len);
179 //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data);
180 389 _dataLen = len;
181 389 return true;
182 }
183
184 2755 void IRAM_ATTR_YN Segment::deallocateData() {
185
2/2
✓ Branch 0 taken 2369 times.
✓ Branch 1 taken 386 times.
2755 if (!data) { _dataLen = 0; return; }
186 //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data);
187
3/6
✓ Branch 0 taken 386 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 386 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 386 times.
✗ Branch 5 not taken.
386 if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer
188 386 free(data);
189 } else {
190 DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData());
191 }
192 386 data = nullptr;
193
1/2
✓ Branch 0 taken 386 times.
✗ Branch 1 not taken.
386 Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData());
194 386 _dataLen = 0;
195 }
196
197 /**
198 * If reset of this segment was requested, clears runtime
199 * settings of this segment.
200 * Must not be called while an effect mode function is running
201 * because it could access the data buffer and this method
202 * may free that data buffer.
203 */
204 9747 void Segment::resetIfRequired() {
205
2/2
✓ Branch 0 taken 8551 times.
✓ Branch 1 taken 1196 times.
9747 if (!reset) return;
206 //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this);
207
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1196 if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
208 1196 next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
209 1196 reset = false;
210 #ifdef WLED_ENABLE_GIF
211 endImagePlayback(this);
212 #endif
213 }
214
215 9744 CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
216
2/4
✓ Branch 0 taken 9744 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9744 times.
9744 if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0;
217
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 9744 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 9744 times.
9744 if (pal > 245 && (strip().customPalettes.size() == 0 || 255U-pal > strip().customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip
218 //default palette. Differs depending on effect
219
2/2
✓ Branch 0 taken 7121 times.
✓ Branch 1 taken 2623 times.
9744 if (pal == 0) pal = _default_palette; //load default palette set in FX _data, party colors as default
220
7/7
✓ Branch 0 taken 225 times.
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 1397 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 8064 times.
9744 switch (pal) {
221 225 case 0: //default palette. Exceptions for specific effects above
222 225 targetPalette = PartyColors_p; break;
223 26 case 1: //randomly generated palette
224 26 targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
225 26 break;
226 1397 case 2: {//primary color only
227
1/2
✓ Branch 0 taken 1397 times.
✗ Branch 1 not taken.
1397 CRGB prim = gamma32(colors[0]);
228
1/2
✓ Branch 0 taken 1397 times.
✗ Branch 1 not taken.
1397 targetPalette = CRGBPalette16(prim); break;}
229 26 case 3: {//primary + secondary
230
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 CRGB prim = gamma32(colors[0]);
231
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 CRGB sec = gamma32(colors[1]);
232
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 targetPalette = CRGBPalette16(prim,prim,sec,sec); break;}
233 3 case 4: {//primary + secondary + tertiary
234
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CRGB prim = gamma32(colors[0]);
235
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CRGB sec = gamma32(colors[1]);
236
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CRGB ter = gamma32(colors[2]);
237
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 targetPalette = CRGBPalette16(ter,sec,prim); break;}
238 3 case 5: {//primary + secondary (+tertiary if not off), more distinct
239
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CRGB prim = gamma32(colors[0]);
240
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 CRGB sec = gamma32(colors[1]);
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (colors[2]) {
242 ✗ CRGB ter = gamma32(colors[2]);
243 ✗ targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim);
244 } else {
245 3 targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec);
246 }
247 3 break;}
248 8064 default: //progmem palettes
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8064 times.
8064 if (pal>245) {
250 ✗ targetPalette = strip().customPalettes[255-pal]; // we checked bounds above
251
2/2
✓ Branch 0 taken 7273 times.
✓ Branch 1 taken 791 times.
8064 } else if (pal < 13) { // palette 6 - 12, fastled palettes
252 7273 targetPalette = *fastledPalettes[pal-6];
253 } else {
254 // Straight from the table: the gradients are ordinary memory here, and
255 // loadDynamicGradientPalette() stops at the entry indexed 255 that ends
256 // each one. WLED copied a fixed 72 bytes out of flash first, which on
257 // an ESP reads harmlessly past a short gradient; here it read past the
258 // end of the array (Fire, Fire 2012's default, among others).
259 791 targetPalette.loadDynamicGradientPalette((const byte*)gGradientPalettes[pal-13]);
260 }
261 8064 break;
262 }
263 9744 return targetPalette;
264 }
265
266 3189 void Segment::startTransition(uint16_t dur) {
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3189 times.
3189 if (dur == 0) {
268 ✗ if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition()
269 ✗ return;
270 }
271
2/2
✓ Branch 0 taken 1993 times.
✓ Branch 1 taken 1196 times.
3189 if (isInTransition()) return; // already in transition no need to store anything
272
273 // starting a transition has to occur before change so we get current values 1st
274
4/12
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1196 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1196 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1196 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
1196 _t = new(std::nothrow) Transition(dur); // no previous transition running
275
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
1196 if (!_t) return; // failed to allocate data
276
277 //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t);
278 1196 loadPalette(_t->_palT, palette);
279 1196 _t->_palTid = palette;
280
1/2
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
1196 _t->_briT = on ? opacity : 0;
281 1196 _t->_cctT = cct;
282 #ifndef WLED_DISABLE_MODE_BLEND
283 swapSegenv(_t->_segT); // copy runtime data to temporary
284 _t->_modeT = mode;
285 _t->_segT._dataLenT = 0;
286 _t->_segT._dataT = nullptr;
287 if (_dataLen > 0 && data) {
288 _t->_segT._dataT = (byte *)malloc(_dataLen);
289 if (_t->_segT._dataT) {
290 //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
291 memcpy(_t->_segT._dataT, data, _dataLen);
292 _t->_segT._dataLenT = _dataLen;
293 }
294 }
295 DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"),
296 (int)_t->_palTid,
297 (int)_t->_briT,
298 _t->_segT._colorT[0],
299 _t->_segT._colorT[1],
300 _t->_segT._colorT[2],
301 (int)_t->_modeT);
302 #else
303
2/2
✓ Branch 0 taken 3588 times.
✓ Branch 1 taken 1196 times.
4784 for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = colors[i];
304 #endif
305 }
306
307 5015 void Segment::stopTransition() {
308
2/2
✓ Branch 0 taken 1187 times.
✓ Branch 1 taken 3828 times.
5015 if (isInTransition()) {
309 //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this);
310 #ifndef WLED_DISABLE_MODE_BLEND
311 if (_t->_segT._dataT && _t->_segT._dataLenT > 0) {
312 //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT);
313 free(_t->_segT._dataT);
314 _t->_segT._dataT = nullptr;
315 _t->_segT._dataLenT = 0;
316 }
317 #endif
318
1/2
✓ Branch 0 taken 1187 times.
✗ Branch 1 not taken.
1187 delete _t;
319 1187 _t = nullptr;
320 }
321 5015 _transitionprogress = 0xFFFFU; // stop means stop - transition has ended
322 5015 }
323
324 // transition progression between 0-65535
325 8551 inline void Segment::updateTransitionProgress() {
326 8551 _transitionprogress = 0xFFFFU;
327
2/2
✓ Branch 0 taken 5952 times.
✓ Branch 1 taken 2599 times.
8551 if (isInTransition()) {
328 5952 unsigned diff = millis() - _t->_start;
329
3/4
✓ Branch 0 taken 5952 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5902 times.
✓ Branch 3 taken 50 times.
5952 if (_t->_dur > 0 && diff < _t->_dur) _transitionprogress = diff * 0xFFFFU / _t->_dur;
330 }
331 8551 }
332
333 #ifndef WLED_DISABLE_MODE_BLEND
334 void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
335 //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data);
336 tmpSeg._optionsT = options;
337 for (size_t i=0; i<NUM_COLORS; i++) tmpSeg._colorT[i] = colors[i];
338 tmpSeg._speedT = speed;
339 tmpSeg._intensityT = intensity;
340 tmpSeg._custom1T = custom1;
341 tmpSeg._custom2T = custom2;
342 tmpSeg._custom3T = custom3;
343 tmpSeg._check1T = check1;
344 tmpSeg._check2T = check2;
345 tmpSeg._check3T = check3;
346 tmpSeg._aux0T = aux0;
347 tmpSeg._aux1T = aux1;
348 tmpSeg._stepT = step;
349 tmpSeg._callT = call;
350 tmpSeg._dataT = data;
351 tmpSeg._dataLenT = _dataLen;
352 if (isInTransition() && &tmpSeg != &(_t->_segT)) {
353 // swap SEGENV with transitional data
354 options = _t->_segT._optionsT;
355 for (size_t i=0; i<NUM_COLORS; i++) colors[i] = _t->_segT._colorT[i];
356 speed = _t->_segT._speedT;
357 intensity = _t->_segT._intensityT;
358 custom1 = _t->_segT._custom1T;
359 custom2 = _t->_segT._custom2T;
360 custom3 = _t->_segT._custom3T;
361 check1 = _t->_segT._check1T;
362 check2 = _t->_segT._check2T;
363 check3 = _t->_segT._check3T;
364 aux0 = _t->_segT._aux0T;
365 aux1 = _t->_segT._aux1T;
366 step = _t->_segT._stepT;
367 call = _t->_segT._callT;
368 data = _t->_segT._dataT;
369 _dataLen = _t->_segT._dataLenT;
370 }
371 }
372
373 void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) {
374 //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data);
375 if (isInTransition() && &(_t->_segT) != &tmpSeg) {
376 // update possibly changed variables to keep old effect running correctly
377 _t->_segT._aux0T = aux0;
378 _t->_segT._aux1T = aux1;
379 _t->_segT._stepT = step;
380 _t->_segT._callT = call;
381 //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data);
382 _t->_segT._dataT = data;
383 _t->_segT._dataLenT = _dataLen;
384 }
385 options = tmpSeg._optionsT;
386 for (size_t i=0; i<NUM_COLORS; i++) colors[i] = tmpSeg._colorT[i];
387 speed = tmpSeg._speedT;
388 intensity = tmpSeg._intensityT;
389 custom1 = tmpSeg._custom1T;
390 custom2 = tmpSeg._custom2T;
391 custom3 = tmpSeg._custom3T;
392 check1 = tmpSeg._check1T;
393 check2 = tmpSeg._check2T;
394 check3 = tmpSeg._check3T;
395 aux0 = tmpSeg._aux0T;
396 aux1 = tmpSeg._aux1T;
397 step = tmpSeg._stepT;
398 call = tmpSeg._callT;
399 data = tmpSeg._dataT;
400 _dataLen = tmpSeg._dataLenT;
401 }
402 #endif
403
404 17060 uint8_t Segment::currentBri(bool useCct) const {
405
2/2
✓ Branch 0 taken 11804 times.
✓ Branch 1 taken 5256 times.
17060 unsigned prog = isInTransition() ? progress() : 0xFFFFU;
406
3/4
✓ Branch 0 taken 8530 times.
✓ Branch 1 taken 8530 times.
✓ Branch 2 taken 8530 times.
✗ Branch 3 not taken.
17060 uint32_t curBri = useCct ? cct : (on ? opacity : 0);
407
2/2
✓ Branch 0 taken 11804 times.
✓ Branch 1 taken 5256 times.
17060 if (prog < 0xFFFFU) {
408 #ifndef WLED_DISABLE_MODE_BLEND
409 uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0);
410 // _modeBlend==true -> old effect
411 if (blendingStyle != BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness
412 #else
413
2/2
✓ Branch 0 taken 5902 times.
✓ Branch 1 taken 5902 times.
11804 uint8_t tmpBri = useCct ? _t->_cctT : _t->_briT;
414 #endif
415 11804 curBri *= prog;
416 11804 curBri += tmpBri * (0xFFFFU - prog);
417 11804 return curBri / 0xFFFFU;
418 }
419 5256 return curBri;
420 }
421
422 ✗ uint8_t Segment::currentMode() const {
423 #ifndef WLED_DISABLE_MODE_BLEND
424 unsigned prog = isInTransition() ? progress() : 0xFFFFU;
425 if (prog == 0xFFFFU) return mode;
426 if (blendingStyle != BLEND_STYLE_FADE) {
427 // workaround for on/off transition to respect blending style
428 uint8_t modeT = (bri != briT) && bri ? FX_MODE_STATIC : _t->_modeT; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC
429 uint8_t modeS = (bri != briT) && !bri ? FX_MODE_STATIC : mode; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC
430 return _modeBlend ? modeT : modeS; // _modeBlend==true -> old effect
431 }
432 return _modeBlend ? _t->_modeT : mode; // _modeBlend==true -> old effect
433 #else
434 ✗ return mode;
435 #endif
436 }
437
438 ✗ uint32_t Segment::currentColor(uint8_t slot) const {
439 ✗ if (slot >= NUM_COLORS) slot = 0;
440 ✗ unsigned prog = progress();
441 ✗ if (prog == 0xFFFFU) return colors[slot];
442 #ifndef WLED_DISABLE_MODE_BLEND
443 if (blendingStyle != BLEND_STYLE_FADE) {
444 // workaround for on/off transition to respect blending style
445 uint32_t colT = (bri != briT) && bri ? BLACK : _t->_segT._colorT[slot]; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK
446 uint32_t colS = (bri != briT) && !bri ? BLACK : colors[slot]; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK
447 return _modeBlend ? colT : colS; // _modeBlend==true -> old effect
448 }
449 return color_blend16(_t->_segT._colorT[slot], colors[slot], prog);
450 #else
451 ✗ return color_blend16(_t->_colorT[slot], colors[slot], prog);
452 #endif
453 }
454
455 // pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork)
456 8530 void Segment::beginDraw() {
457 8530 _vWidth = virtualWidth();
458 8530 _vHeight = virtualHeight();
459 8530 _vLength = virtualLength();
460 8530 _segBri = currentBri();
461
2/2
✓ Branch 0 taken 5902 times.
✓ Branch 1 taken 2628 times.
8530 unsigned prog = isInTransition() ? progress() : 0xFFFFU; // transition progress; 0xFFFFU = no transition active
462 // adjust gamma for effects
463
2/2
✓ Branch 0 taken 25590 times.
✓ Branch 1 taken 8530 times.
34120 for (unsigned i = 0; i < NUM_COLORS; i++) {
464 #ifndef WLED_DISABLE_MODE_BLEND
465 uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], prog) : colors[i];
466 #else
467
2/2
✓ Branch 0 taken 17706 times.
✓ Branch 1 taken 7884 times.
25590 uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], prog) : colors[i];
468 #endif
469 25590 _currentColors[i] = gamma32(col);
470 }
471 // load palette into _currentPalette
472 8530 loadPalette(_currentPalette, palette);
473
2/2
✓ Branch 0 taken 5902 times.
✓ Branch 1 taken 2628 times.
8530 if (prog < 0xFFFFU) {
474 #ifndef WLED_DISABLE_MODE_BLEND
475 if (blendingStyle > BLEND_STYLE_FADE) {
476 //if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette
477 if (_modeBlend) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette
478 } else
479 #endif
480 {
481 // blend palettes
482 // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
483 // minimum blend time is 100ms maximum is 65535ms
484 5902 unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
485
2/2
✓ Branch 0 taken 89598 times.
✓ Branch 1 taken 5902 times.
95500 for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48);
486 5902 _currentPalette = _t->_palT; // copy transitioning/temporary palette
487 }
488 }
489 8530 }
490
491 // loads palette of the old FX during transitions (used by particle system)
492 ✗ void Segment::loadOldPalette(void) {
493 ✗ if(isInTransition())
494 ✗ loadPalette(_currentPalette, _t->_palTid);
495 ✗ }
496
497 // relies on WS2812FX::service() to call it for each frame
498 8530 void Segment::handleRandomPalette() {
499 // is it time to generate a new palette?
500
2/2
✓ Branch 0 taken 457 times.
✓ Branch 1 taken 8073 times.
8530 if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
501
2/6
✓ Branch 0 taken 457 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 457 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
457 _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
502 457 _lastPaletteChange = (uint16_t)(millis()/1000U);
503 457 _lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately
504 }
505
506 // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
507 // in reality there need to be 255 blends to fully blend two entirely different palettes
508
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 8348 times.
8530 if ((uint16_t)millis() - _lastPaletteBlend < strip().getTransition() >> 7) return; // not yet time to fade, delay the update
509 8348 _lastPaletteBlend = (uint16_t)millis();
510 8348 nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
511 }
512
513 // sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping)
514 // strip must be suspended (strip().suspend()) before calling this function
515 // this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw())
516 ✗ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) {
517 // return if neither bounds nor grouping have changed
518 ✗ bool boundsUnchanged = (start == i1 && stop == i2);
519 #ifndef WLED_DISABLE_2D
520 ✗ if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D
521 #endif
522
523 ✗ if (stop && (spc > 0 || m12 != map1D2D)) clear();
524 /*
525 if (boundsUnchanged
526 && (!grp || (grouping == grp && spacing == spc))
527 && (ofs == UINT16_MAX || ofs == offset)
528 && (m12 == map1D2D)
529 ) return;
530 */
531 ✗ stateChanged = true; // send UDP/WS broadcast
532
533 ✗ if (grp) { // prevent assignment of 0
534 ✗ grouping = grp;
535 ✗ spacing = spc;
536 } else {
537 ✗ grouping = 1;
538 ✗ spacing = 0;
539 }
540 ✗ if (ofs < UINT16_MAX) offset = ofs;
541 ✗ map1D2D = constrain(m12, 0, 7);
542
543 DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y);
544 ✗ markForReset();
545 ✗ if (boundsUnchanged) return;
546
547 // apply change immediately
548 ✗ if (i2 <= i1) { //disable segment
549 ✗ stop = 0;
550 ✗ return;
551 }
552 ✗ if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip().getLengthTotal())) start = i1; // Segment::maxWidth equals strip().getLengthTotal() for 1D
553 ✗ stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip().getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : MAX(1,i2));
554 ✗ startY = 0;
555 ✗ stopY = 1;
556 #ifndef WLED_DISABLE_2D
557 ✗ if (Segment::maxHeight>1) { // 2D
558 ✗ if (i1Y < Segment::maxHeight) startY = i1Y;
559 ✗ stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y);
560 }
561 #endif
562 // safety check
563 ✗ if (start >= stop || startY >= stopY) {
564 ✗ stop = 0;
565 ✗ return;
566 }
567 ✗ refreshLightCapabilities();
568 }
569
570
571 3588 Segment &Segment::setColor(uint8_t slot, uint32_t c) {
572
3/4
✓ Branch 0 taken 3588 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 624 times.
✓ Branch 3 taken 2964 times.
3588 if (slot >= NUM_COLORS || c == colors[slot]) return *this;
573
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2964 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2964 if (!_isRGB && !_hasW) {
574 ✗ if (slot == 0 && c == BLACK) return *this; // on/off segment cannot have primary color black
575 ✗ if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black
576 }
577 //DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c);
578 2964 startTransition(strip().getTransition()); // start transition prior to change
579 2964 colors[slot] = c;
580 2964 stateChanged = true; // send UDP/WS broadcast
581 2964 return *this;
582 }
583
584 ✗ Segment &Segment::setCCT(uint16_t k) {
585 ✗ if (k > 255) { //kelvin value, convert to 0-255
586 ✗ if (k < 1900) k = 1900;
587 ✗ if (k > 10091) k = 10091;
588 ✗ k = (k - 1900) >> 5;
589 }
590 ✗ if (cct != k) {
591 //DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k);
592 ✗ startTransition(strip().getTransition()); // start transition prior to change
593 ✗ cct = k;
594 ✗ stateChanged = true; // send UDP/WS broadcast
595 }
596 ✗ return *this;
597 }
598
599 ✗ Segment &Segment::setOpacity(uint8_t o) {
600 ✗ if (opacity != o) {
601 //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o);
602 ✗ startTransition(strip().getTransition()); // start transition prior to change
603 ✗ opacity = o;
604 ✗ stateChanged = true; // send UDP/WS broadcast
605 }
606 ✗ return *this;
607 }
608
609 ✗ Segment &Segment::setOption(uint8_t n, bool val) {
610 ✗ bool prevOn = on;
611 ✗ if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip().getTransition()); // start transition prior to change
612 ✗ if (val) options |= 0x01 << n;
613 ✗ else options &= ~(0x01 << n);
614 ✗ if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast
615 ✗ return *this;
616 }
617
618 1196 Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
619 // skip reserved
620
3/6
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1196 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1196 times.
1196 while (fx < strip().getModeCount() && strncmp_P("RSVD", strip().getModeData(fx), 4) == 0) fx++;
621
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
1196 if (fx >= strip().getModeCount()) fx = 0; // set solid mode
622 // if we have a valid mode & is not reserved
623
1/2
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
1196 if (fx != mode) {
624 #ifndef WLED_DISABLE_MODE_BLEND
625 //DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx);
626 startTransition(strip().getTransition()); // set effect transitions
627 #endif
628 1196 mode = fx;
629 int sOpt;
630 // load default values from effect string
631
1/2
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
1196 if (loadDefaults) {
632
2/2
✓ Branch 0 taken 178 times.
✓ Branch 1 taken 1018 times.
1196 sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED;
633
2/2
✓ Branch 0 taken 346 times.
✓ Branch 1 taken 850 times.
1196 sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY;
634
2/2
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 936 times.
1196 sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1;
635
2/2
✓ Branch 0 taken 192 times.
✓ Branch 1 taken 1004 times.
1196 sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2;
636
2/2
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 1032 times.
1196 sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3;
637
3/4
✓ Branch 0 taken 133 times.
✓ Branch 1 taken 1063 times.
✓ Branch 2 taken 133 times.
✗ Branch 3 not taken.
1196 sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false;
638
4/4
✓ Branch 0 taken 108 times.
✓ Branch 1 taken 1088 times.
✓ Branch 2 taken 25 times.
✓ Branch 3 taken 83 times.
1196 sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false;
639
3/4
✓ Branch 0 taken 107 times.
✓ Branch 1 taken 1089 times.
✓ Branch 2 taken 107 times.
✗ Branch 3 not taken.
1196 sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false;
640
2/2
✓ Branch 0 taken 239 times.
✓ Branch 1 taken 957 times.
1196 sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); else map1D2D = M12_Pixels; // reset mapping if not defined (2D FX may not work)
641
2/2
✓ Branch 0 taken 156 times.
✓ Branch 1 taken 1040 times.
1196 sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 3);
642
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1189 times.
1196 sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt;
643
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1189 times.
1196 sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business
644
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1189 times.
1196 sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt;
645
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1189 times.
1196 sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business
646
2/2
✓ Branch 0 taken 291 times.
✓ Branch 1 taken 905 times.
1196 sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0);
647 }
648 1196 sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette
649
2/2
✓ Branch 0 taken 971 times.
✓ Branch 1 taken 225 times.
1196 if(sOpt <= 0) sOpt = 6; // partycolors if zero or not set
650 1196 _default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected)
651 1196 markForReset();
652 1196 stateChanged = true; // send UDP/WS broadcast
653 }
654 1196 return *this;
655 }
656
657 291 Segment &Segment::setPalette(uint8_t pal) {
658
2/4
✓ Branch 0 taken 291 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 291 times.
291 if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
659
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 291 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 291 times.
291 if (pal > 245 && (strip().customPalettes.size() == 0 || 255U-pal > strip().customPalettes.size()-1)) pal = 0; // custom palettes
660
2/2
✓ Branch 0 taken 225 times.
✓ Branch 1 taken 66 times.
291 if (pal != palette) {
661 //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
662 225 startTransition(strip().getTransition());
663 225 palette = pal;
664 225 stateChanged = true; // send UDP/WS broadcast
665 }
666 291 return *this;
667 }
668
669 ✗ Segment &Segment::setName(const char *newName) {
670 ✗ if (newName) {
671 ✗ const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN);
672 ✗ if (newLen) {
673 ✗ if (name) name = static_cast<char*>(realloc(name, newLen+1));
674 ✗ else name = static_cast<char*>(malloc(newLen+1));
675 ✗ if (name) strncpy(name, newName, newLen+1);
676 ✗ name[newLen] = 0;
677 ✗ return *this;
678 }
679 }
680 ✗ return clearName();
681 }
682
683 // 2D matrix
684 14617 unsigned Segment::virtualWidth() const {
685 14617 unsigned groupLen = groupLength();
686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14617 times.
14617 unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen;
687
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14617 times.
14617 if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED
688 14617 return vWidth;
689 }
690
691 14906 unsigned Segment::virtualHeight() const {
692 14906 unsigned groupLen = groupLength();
693
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14906 times.
14906 unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen;
694
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14906 times.
14906 if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED
695 14906 return vHeight;
696 }
697
698 // Constants for mapping mode "Pinwheel"
699 #ifndef WLED_DISABLE_2D
700 constexpr int Fixed_Scale = 16384; // fixpoint scaling factor (14bit for fraction)
701 // Pinwheel helper function: matrix dimensions to number of rays
702 ✗ static int getPinwheelLength(int vW, int vH) {
703 // Returns multiple of 8, prevents over drawing
704 ✗ return (max(vW, vH) + 15) & ~7;
705 }
706 ✗ static void setPinwheelParameters(int i, int vW, int vH, int& startx, int& starty, int* cosVal, int* sinVal, bool getPixel = false) {
707 ✗ int steps = getPinwheelLength(vW, vH);
708 ✗ int baseAngle = ((0xFFFF + steps / 2) / steps); // 360° / steps, in 16 bit scale round to nearest integer
709 ✗ int rotate = 0;
710 ✗ if (getPixel) rotate = baseAngle / 2; // rotate by half a ray width when reading pixel color
711 ✗ for (int k = 0; k < 2; k++) // angular steps for two consecutive rays
712 {
713 ✗ int angle = (i + k) * baseAngle + rotate;
714 ✗ cosVal[k] = (cos16_t(angle) * Fixed_Scale) >> 15; // step per pixel in fixed point, cos16 output is -0x7FFF to +0x7FFF
715 ✗ sinVal[k] = (sin16_t(angle) * Fixed_Scale) >> 15; // using explicit bit shifts as dividing negative numbers is not equivalent (rounding error is acceptable)
716 }
717 ✗ startx = (vW * Fixed_Scale) / 2; // + cosVal[0] / 4; // starting position = center + 1/4 pixel (in fixed point)
718 ✗ starty = (vH * Fixed_Scale) / 2; // + sinVal[0] / 4;
719 ✗ }
720 #endif
721
722 // 1D strip
723 10434 uint16_t Segment::virtualLength() const {
724 #ifndef WLED_DISABLE_2D
725
2/2
✓ Branch 0 taken 5928 times.
✓ Branch 1 taken 4506 times.
10434 if (is2D()) {
726 5928 unsigned vW = virtualWidth();
727 5928 unsigned vH = virtualHeight();
728 unsigned vLen;
729
4/5
✓ Branch 0 taken 291 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5439 times.
5928 switch (map1D2D) {
730 291 case M12_pBar:
731 291 vLen = vH;
732 291 break;
733 36 case M12_pCorner:
734 36 vLen = max(vW,vH); // get the longest dimension
735 36 break;
736 162 case M12_pArc:
737 162 vLen = sqrt32_bw(vH*vH + vW*vW); // use diagonal
738 162 break;
739 ✗ case M12_sPinwheel:
740 ✗ vLen = getPinwheelLength(vW, vH);
741 ✗ break;
742 5439 default:
743 5439 vLen = vW * vH; // use all pixels from segment
744 5439 break;
745 }
746 5928 return vLen;
747 }
748 #endif
749 4506 unsigned groupLen = groupLength(); // is always >= 1
750 4506 unsigned vLength = (length() + groupLen - 1) / groupLen;
751
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4506 times.
4506 if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
752 4506 return vLength;
753 }
754
755 // pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
756 // if clipping start > stop the clipping range is inverted
757 // _modeBlend==true -> old effect during transition
758 // _modeBlend==false -> new effect during transition
759 263702 bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
760 #ifndef WLED_DISABLE_MODE_BLEND
761 if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) {
762 bool invert = _clipStart > _clipStop; // ineverted start & stop
763 int start = invert ? _clipStop : _clipStart;
764 int stop = invert ? _clipStart : _clipStop;
765 if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
766 unsigned len = stop - start;
767 if (len < 2) return false;
768 unsigned shuffled = hashInt(i) % len;
769 unsigned pos = (shuffled * 0xFFFFU) / len;
770 return (progress() <= pos) ^ _modeBlend;
771 }
772 const bool iInside = (i >= start && i < stop);
773 //if (!invert && iInside) return _modeBlend;
774 //if ( invert && !iInside) return _modeBlend;
775 //return !_modeBlend;
776 return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/wled-dev/WLED/pull/3877#discussion_r1554633876)
777 }
778 #endif
779 263702 return false;
780 }
781
782 1042216 void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
783 {
784
5/6
✓ Branch 0 taken 1042216 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1042208 times.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 1042208 times.
1042216 if (!isActive() || i < 0) return; // not active or invalid index
785 #ifndef WLED_DISABLE_2D
786 1042208 int vStrip = 0;
787 #endif
788 1042208 int vL = vLength();
789 // if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits
790 // in such case "i" will be > virtualLength()
791
2/2
✓ Branch 0 taken 21617 times.
✓ Branch 1 taken 1020591 times.
1042208 if (i >= vL) {
792 // check if this is a virtual strip
793 #ifndef WLED_DISABLE_2D
794 21617 vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
795 21617 i &= 0xFFFF; //truncate vstrip index
796
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 21573 times.
21617 if (i >= vL) return; // if pixel would still fall out of segment just exit
797 #else
798 return;
799 #endif
800 }
801
802 #ifndef WLED_DISABLE_2D
803
2/2
✓ Branch 0 taken 810691 times.
✓ Branch 1 taken 231473 times.
1042164 if (is2D()) {
804 810691 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
805 810691 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
806 // pre-scale color for all pixels
807 810691 col = color_fade(col, _segBri);
808 810691 _colorScaled = true;
809
4/6
✓ Branch 0 taken 785640 times.
✓ Branch 1 taken 21225 times.
✓ Branch 2 taken 3146 times.
✓ Branch 3 taken 680 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
810691 switch (map1D2D) {
810 785640 case M12_Pixels:
811 // use all available pixels as a long strip
812 785640 setPixelColorXY(i % vW, i / vW, col);
813 785640 break;
814 21225 case M12_pBar:
815 // expand 1D effect vertically or have it play on virtual strips
816
2/2
✓ Branch 0 taken 19985 times.
✓ Branch 1 taken 1240 times.
21225 if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col);
817
2/2
✓ Branch 0 taken 32352 times.
✓ Branch 1 taken 1240 times.
33592 else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col);
818 21225 break;
819 3146 case M12_pArc:
820 // expand in circular fashion from center
821
2/2
✓ Branch 0 taken 145 times.
✓ Branch 1 taken 3001 times.
3146 if (i == 0)
822 145 setPixelColorXY(0, 0, col);
823 else {
824 3001 float r = i;
825 3001 float step = HALF_PI / (2.8284f * r + 4); // we only need (PI/4)/(r/sqrt(2)+1) steps
826
2/2
✓ Branch 0 taken 77083 times.
✓ Branch 1 taken 3001 times.
80084 for (float rad = 0.0f; rad <= (HALF_PI/2)+step/2; rad += step) {
827 77083 int x = roundf(sin_t(rad) * r);
828 77083 int y = roundf(cos_t(rad) * r);
829 // exploit symmetry
830 77083 setPixelColorXY(x, y, col);
831 77083 setPixelColorXY(y, x, col);
832 }
833 // Bresenham’s Algorithm (may not fill every pixel)
834 //int d = 3 - (2*i);
835 //int y = i, x = 0;
836 //while (y >= x) {
837 // setPixelColorXY(x, y, col);
838 // setPixelColorXY(y, x, col);
839 // x++;
840 // if (d > 0) {
841 // y--;
842 // d += 4 * (x - y) + 10;
843 // } else {
844 // d += 4 * x + 6;
845 // }
846 //}
847 }
848 3146 break;
849 680 case M12_pCorner:
850
2/2
✓ Branch 0 taken 10350 times.
✓ Branch 1 taken 680 times.
11030 for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col);
851
2/2
✓ Branch 0 taken 9670 times.
✓ Branch 1 taken 680 times.
10350 for (int y = 0; y < i; y++) setPixelColorXY(i, y, col);
852 680 break;
853 ✗ case M12_sPinwheel: {
854 // Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them
855 int startX, startY, cosVal[2], sinVal[2]; // in fixed point scale
856 ✗ setPinwheelParameters(i, vW, vH, startX, startY, cosVal, sinVal);
857
858 ✗ unsigned maxLineLength = max(vW, vH) + 2; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors
859 ✗ uint16_t lineCoords[2][maxLineLength]; // uint16_t to save ram
860 ✗ int lineLength[2] = {0};
861
862 static int prevRays[2] = {INT_MAX, INT_MAX}; // previous two ray numbers
863 ✗ int closestEdgeIdx = INT_MAX; // index of the closest edge pixel
864
865 ✗ for (int lineNr = 0; lineNr < 2; lineNr++) {
866 ✗ int x0 = startX; // x, y coordinates in fixed scale
867 ✗ int y0 = startY;
868 ✗ int x1 = (startX + (cosVal[lineNr] << 9)); // outside of grid
869 ✗ int y1 = (startY + (sinVal[lineNr] << 9)); // outside of grid
870 ✗ const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
871 ✗ const int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
872 ✗ uint16_t* coordinates = lineCoords[lineNr]; // 1D access is faster
873 ✗ int* length = &lineLength[lineNr]; // faster access
874 ✗ x0 /= Fixed_Scale; // convert to pixel coordinates
875 ✗ y0 /= Fixed_Scale;
876
877 // Bresenham's algorithm
878 ✗ int idx = 0;
879 ✗ int err = dx + dy;
880 while (true) {
881 ✗ if (unsigned(x0) >= vW || unsigned(y0) >= vH) {
882 ✗ closestEdgeIdx = min(closestEdgeIdx, idx-2);
883 ✗ break; // stop if outside of grid (exploit unsigned int overflow)
884 }
885 ✗ coordinates[idx++] = x0;
886 ✗ coordinates[idx++] = y0;
887 ✗ (*length)++;
888 // note: since endpoint is out of grid, no need to check if endpoint is reached
889 ✗ int e2 = 2 * err;
890 ✗ if (e2 >= dy) { err += dy; x0 += sx; }
891 ✗ if (e2 <= dx) { err += dx; y0 += sy; }
892 ✗ }
893 }
894
895 // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently
896 ✗ int diff = lineLength[0] - lineLength[1];
897 ✗ int longLineIdx = (diff > 0) ? 0 : 1;
898 ✗ int shortLineIdx = longLineIdx ? 0 : 1;
899 ✗ if (diff != 0) {
900 ✗ int idx = (lineLength[shortLineIdx] - 1) * 2; // last valid coordinate index
901 ✗ int lastX = lineCoords[shortLineIdx][idx++];
902 ✗ int lastY = lineCoords[shortLineIdx][idx++];
903 ✗ bool keepX = lastX == 0 || lastX == vW - 1;
904 ✗ for (int d = 0; d < abs(diff); d++) {
905 ✗ lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx];
906 ✗ idx++;
907 ✗ lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY;
908 ✗ idx++;
909 }
910 }
911
912 // draw and block-fill the line coordinates. Note: block filling only efficient if angle between lines is small
913 ✗ closestEdgeIdx += 2;
914 ✗ int max_i = getPinwheelLength(vW, vH) - 1;
915 ✗ bool drawFirst = !(prevRays[0] == i - 1 || (i == 0 && prevRays[0] == max_i)); // draw first line if previous ray was not adjacent including wrap
916 ✗ bool drawLast = !(prevRays[0] == i + 1 || (i == max_i && prevRays[0] == 0)); // same as above for last line
917 ✗ for (int idx = 0; idx < lineLength[longLineIdx] * 2;) { //!! should be long line idx!
918 ✗ int x1 = lineCoords[0][idx];
919 ✗ int x2 = lineCoords[1][idx++];
920 ✗ int y1 = lineCoords[0][idx];
921 ✗ int y2 = lineCoords[1][idx++];
922 int minX, maxX, minY, maxY;
923 ✗ (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1);
924 ✗ (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1);
925
926 // fill the block between the two x,y points
927 ✗ bool alwaysDraw = (drawFirst && drawLast) || // No adjacent rays, draw all pixels
928 ✗ (idx > closestEdgeIdx) || // Edge pixels on uneven lines are always drawn
929 ✗ (i == 0 && idx == 2) || // Center pixel special case
930 ✗ (i == prevRays[1]); // Effect drawing twice in 1 frame
931 ✗ for (int x = minX; x <= maxX; x++) {
932 ✗ for (int y = minY; y <= maxY; y++) {
933 ✗ bool onLine1 = x == x1 && y == y1;
934 ✗ bool onLine2 = x == x2 && y == y2;
935 ✗ if ((alwaysDraw) ||
936 ✗ (!onLine1 && (!onLine2 || drawLast)) || // Middle pixels and line2 if drawLast
937 ✗ (!onLine2 && (!onLine1 || drawFirst)) // Middle pixels and line1 if drawFirst
938 ) {
939 ✗ setPixelColorXY(x, y, col);
940 }
941 }
942 }
943 }
944 ✗ prevRays[1] = prevRays[0];
945 ✗ prevRays[0] = i;
946 ✗ break;
947 ✗ }
948 }
949 810691 return;
950
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 231473 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 231473 times.
231473 } else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) {
951 ✗ if (start < Segment::maxWidth*Segment::maxHeight) {
952 // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed)
953 ✗ int x = 0, y = 0;
954 ✗ if (vHeight() > 1) y = i;
955 ✗ if (vWidth() > 1) x = i;
956 ✗ setPixelColorXY(x, y, col);
957 ✗ return;
958 }
959 }
960 #endif
961
962 #ifndef WLED_DISABLE_MODE_BLEND
963 // if we blend using "push" style we need to "shift" new mode to left or right
964 if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) {
965 unsigned prog = 0xFFFF - progress();
966 unsigned dI = prog * vL / 0xFFFF;
967 if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI;
968 else i += dI;
969 }
970 #endif
971
972
4/8
✓ Branch 0 taken 231473 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 231473 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 231473 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 231473 times.
231473 if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D
973
974 231473 unsigned len = length();
975 // if color is unscaled
976
2/2
✓ Branch 0 taken 167533 times.
✓ Branch 1 taken 63940 times.
231473 if (!_colorScaled) col = color_fade(col, _segBri);
977
978 // expand pixel (taking into account start, grouping, spacing [and offset])
979 231473 i = i * groupLength();
980
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 231473 times.
231473 if (reverse) { // is segment reversed?
981 ✗ if (mirror) { // is segment mirrored?
982 ✗ i = (len - 1) / 2 - i; //only need to index half the pixels
983 } else {
984 ✗ i = (len - 1) - i;
985 }
986 }
987 231473 i += start; // starting pixel in a group
988
989 231473 uint32_t tmpCol = col;
990 // set all the pixels in the group
991
2/2
✓ Branch 0 taken 231473 times.
✓ Branch 1 taken 231473 times.
462946 for (int j = 0; j < grouping; j++) {
992
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 231473 times.
231473 unsigned indexSet = i + ((reverse) ? -j : j);
993
2/4
✓ Branch 0 taken 231473 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 231473 times.
✗ Branch 3 not taken.
231473 if (indexSet >= start && indexSet < stop) {
994
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 231473 times.
231473 if (mirror) { //set the corresponding mirrored pixel
995 ✗ unsigned indexMir = stop - indexSet + start - 1;
996 ✗ indexMir += offset; // offset/phase
997 ✗ if (indexMir >= stop) indexMir -= len; // wrap
998 #ifndef WLED_DISABLE_MODE_BLEND
999 // _modeBlend==true -> old effect
1000 if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip().getPixelColor(indexMir), col, 0xFFFFU - progress());
1001 #endif
1002 ✗ strip().setPixelColor(indexMir, tmpCol);
1003 }
1004 231473 indexSet += offset; // offset/phase
1005
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 231473 times.
231473 if (indexSet >= stop) indexSet -= len; // wrap
1006 #ifndef WLED_DISABLE_MODE_BLEND
1007 // _modeBlend==true -> old effect
1008 if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip().getPixelColor(indexSet), col, 0xFFFFU - progress());
1009 #endif
1010 231473 strip().setPixelColor(indexSet, tmpCol);
1011 }
1012 }
1013 }
1014
1015 #ifdef WLED_USE_AA_PIXELS
1016 // anti-aliased normalized version of setPixelColor()
1017 void Segment::setPixelColor(float i, uint32_t col, bool aa) const
1018 {
1019 if (!isActive()) return; // not active
1020 int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows)
1021 i -= int(i);
1022
1023 if (i<0.0f || i>1.0f) return; // not normalized
1024
1025 float fC = i * (virtualLength()-1);
1026 if (aa) {
1027 unsigned iL = roundf(fC-0.49f);
1028 unsigned iR = roundf(fC+0.49f);
1029 float dL = (fC - iL)*(fC - iL);
1030 float dR = (iR - fC)*(iR - fC);
1031 uint32_t cIL = getPixelColor(iL | (vStrip<<16));
1032 uint32_t cIR = getPixelColor(iR | (vStrip<<16));
1033 if (iR!=iL) {
1034 // blend L pixel
1035 cIL = color_blend(col, cIL, uint8_t(dL*255.0f));
1036 setPixelColor(iL | (vStrip<<16), cIL);
1037 // blend R pixel
1038 cIR = color_blend(col, cIR, uint8_t(dR*255.0f));
1039 setPixelColor(iR | (vStrip<<16), cIR);
1040 } else {
1041 // exact match (x & y land on a pixel)
1042 setPixelColor(iL | (vStrip<<16), col);
1043 }
1044 } else {
1045 setPixelColor(int(roundf(fC)) | (vStrip<<16), col);
1046 }
1047 }
1048 #endif
1049
1050 77157 uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
1051 {
1052
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 77157 times.
77157 if (!isActive()) return 0; // not active
1053
1054 77157 int vL = vLength();
1055
4/4
✓ Branch 0 taken 69638 times.
✓ Branch 1 taken 7519 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 69630 times.
77157 if (i >= vL || i < 0) return 0;
1056
1057 #ifndef WLED_DISABLE_2D
1058
2/2
✓ Branch 0 taken 37401 times.
✓ Branch 1 taken 32229 times.
69630 if (is2D()) {
1059 37401 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
1060 37401 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
1061
4/6
✓ Branch 0 taken 35857 times.
✓ Branch 1 taken 78 times.
✓ Branch 2 taken 1132 times.
✓ Branch 3 taken 334 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
37401 switch (map1D2D) {
1062 35857 case M12_Pixels:
1063
1/2
✓ Branch 0 taken 35857 times.
✗ Branch 1 not taken.
35857 return getPixelColorXY(i % vW, i / vW);
1064 break;
1065 78 case M12_pBar: {
1066 78 int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode
1067
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
78 if (vStrip > 0) return getPixelColorXY(vStrip - 1, vH - (i & 0xFFFF) -1);
1068
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 else return getPixelColorXY(0, vH - i -1);
1069 break; }
1070 1132 case M12_pArc:
1071
3/4
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 1070 times.
✓ Branch 2 taken 62 times.
✗ Branch 3 not taken.
1132 if (i >= vW && i >= vH) {
1072
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 unsigned vI = sqrt32_bw(i*i/2);
1073
1/2
✓ Branch 0 taken 62 times.
✗ Branch 1 not taken.
62 return getPixelColorXY(vI,vI); // use diagonal
1074 }
1075 case M12_pCorner:
1076 // use longest dimension
1077
2/6
✓ Branch 0 taken 1404 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1404 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1404 return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i);
1078 break;
1079 ✗ case M12_sPinwheel:
1080 // not 100% accurate, returns pixel at outer edge
1081 int x, y, cosVal[2], sinVal[2];
1082 ✗ setPinwheelParameters(i, vW, vH, x, y, cosVal, sinVal, true);
1083 ✗ int maxX = (vW-1) * Fixed_Scale;
1084 ✗ int maxY = (vH-1) * Fixed_Scale;
1085 // trace ray from center until we hit any edge - to avoid rounding problems, we use fixed point coordinates
1086 ✗ while ((x < maxX) && (y < maxY) && (x > Fixed_Scale) && (y > Fixed_Scale)) {
1087 ✗ x += cosVal[0]; // advance to next position
1088 ✗ y += sinVal[0];
1089 }
1090 ✗ x /= Fixed_Scale;
1091 ✗ y /= Fixed_Scale;
1092 ✗ return getPixelColorXY(x, y);
1093 break;
1094 }
1095 ✗ return 0;
1096 }
1097 #endif
1098
1099 #ifndef WLED_DISABLE_MODE_BLEND
1100 if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) {
1101 unsigned prog = 0xFFFF - progress();
1102 unsigned dI = prog * vL / 0xFFFF;
1103 if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI;
1104 else i += dI;
1105 }
1106 #endif
1107
1108
4/8
✓ Branch 0 taken 32229 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 32229 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 32229 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 32229 times.
32229 if (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D
1109
1110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32229 times.
32229 if (reverse) i = vL - i - 1;
1111 32229 i *= groupLength();
1112 32229 i += start;
1113 // offset/phase
1114 32229 i += offset;
1115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32229 times.
32229 if (i >= stop) i -= length();
1116 32229 return strip().getPixelColor(i);
1117 }
1118
1119 ✗ uint8_t Segment::differs(const Segment& b) const {
1120 ✗ uint8_t d = 0;
1121 ✗ if (start != b.start) d |= SEG_DIFFERS_BOUNDS;
1122 ✗ if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS;
1123 ✗ if (offset != b.offset) d |= SEG_DIFFERS_GSO;
1124 ✗ if (grouping != b.grouping) d |= SEG_DIFFERS_GSO;
1125 ✗ if (spacing != b.spacing) d |= SEG_DIFFERS_GSO;
1126 ✗ if (opacity != b.opacity) d |= SEG_DIFFERS_BRI;
1127 ✗ if (mode != b.mode) d |= SEG_DIFFERS_FX;
1128 ✗ if (speed != b.speed) d |= SEG_DIFFERS_FX;
1129 ✗ if (intensity != b.intensity) d |= SEG_DIFFERS_FX;
1130 ✗ if (palette != b.palette) d |= SEG_DIFFERS_FX;
1131 ✗ if (custom1 != b.custom1) d |= SEG_DIFFERS_FX;
1132 ✗ if (custom2 != b.custom2) d |= SEG_DIFFERS_FX;
1133 ✗ if (custom3 != b.custom3) d |= SEG_DIFFERS_FX;
1134 ✗ if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS;
1135 ✗ if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS;
1136
1137 //bit pattern: (msb first)
1138 // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected]
1139 ✗ if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT;
1140 ✗ if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL;
1141 ✗ for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL;
1142
1143 ✗ return d;
1144 }
1145
1146 1196 void Segment::refreshLightCapabilities() {
1147 1196 unsigned capabilities = 0;
1148 1196 unsigned segStartIdx = 0xFFFFU;
1149 1196 unsigned segStopIdx = 0;
1150
1151
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
1196 if (!isActive()) {
1152 ✗ _capabilities = 0;
1153 ✗ return;
1154 }
1155
1156
1/2
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
1196 if (start < Segment::maxWidth * Segment::maxHeight) {
1157 // we are withing 2D matrix (includes 1D segments)
1158
4/4
✓ Branch 0 taken 406262 times.
✓ Branch 1 taken 12504 times.
✓ Branch 2 taken 12504 times.
✓ Branch 3 taken 1196 times.
419962 for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) {
1159 406262 unsigned index = strip().getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical
1160
1/2
✓ Branch 0 taken 406262 times.
✗ Branch 1 not taken.
406262 if (index < 0xFFFFU) {
1161
2/2
✓ Branch 0 taken 1196 times.
✓ Branch 1 taken 405066 times.
406262 if (segStartIdx > index) segStartIdx = index;
1162
2/2
✓ Branch 0 taken 396984 times.
✓ Branch 1 taken 9278 times.
406262 if (segStopIdx < index) segStopIdx = index;
1163 }
1164
2/2
✓ Branch 0 taken 1196 times.
✓ Branch 1 taken 405066 times.
406262 if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment
1165 }
1166 } else {
1167 // we are on the strip located after the matrix
1168 ✗ segStartIdx = start;
1169 ✗ segStopIdx = stop;
1170 }
1171
1172
2/2
✓ Branch 0 taken 1196 times.
✓ Branch 1 taken 1196 times.
2392 for (unsigned b = 0; b < BusManager::getNumBusses(); b++) {
1173 1196 const Bus *bus = BusManager::getBus(b);
1174
3/6
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1196 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1196 times.
1196 if (!bus || !bus->isOk()) break;
1175
3/6
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1196 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1196 times.
1196 if (bus->getStart() >= segStopIdx || bus->getStart() + bus->getLength() <= segStartIdx) continue;
1176
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 1196 times.
✗ Branch 7 not taken.
1196 if (bus->hasRGB() || (strip().cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB;
1177
3/6
✓ Branch 0 taken 1196 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1196 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1196 times.
1196 if (!strip().cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT;
1178
2/8
✗ Branch 0 not taken.
✓ Branch 1 taken 1196 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1196 times.
1196 if (strip().correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider)
1179
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1195 times.
1196 if (bus->hasWhite()) {
1180
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode();
1181
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed
1182 // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses
1183
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB;
1184 // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments
1185
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if ( whiteSlider) capabilities |= SEG_CAPABILITY_W;
1186 }
1187 }
1188 1196 _capabilities = capabilities;
1189 }
1190
1191 /*
1192 * Fills segment with black
1193 */
1194 ✗ void Segment::clear() {
1195 ✗ if (!isActive()) return; // not active
1196 ✗ unsigned oldVW = _vWidth;
1197 ✗ unsigned oldVH = _vHeight;
1198 ✗ unsigned oldVL = _vLength;
1199 ✗ unsigned oldSB = _segBri;
1200 ✗ _vWidth = virtualWidth();
1201 ✗ _vHeight = virtualHeight();
1202 ✗ _vLength = virtualLength();
1203 ✗ _segBri = currentBri();
1204 ✗ fill(BLACK);
1205 ✗ _vWidth = oldVW;
1206 ✗ _vHeight = oldVH;
1207 ✗ _vLength = oldVL;
1208 ✗ _segBri = oldSB;
1209 }
1210
1211 /*
1212 * Fills segment with color
1213 */
1214 1188 void Segment::fill(uint32_t c) {
1215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1188 times.
1188 if (!isActive()) return; // not active
1216
2/2
✓ Branch 0 taken 758 times.
✓ Branch 1 taken 430 times.
1188 const int cols = is2D() ? vWidth() : vLength();
1217 1188 const int rows = vHeight(); // will be 1 for 1D
1218 // pre-scale color for all pixels
1219 1188 c = color_fade(c, _segBri);
1220 1188 _colorScaled = true;
1221
4/4
✓ Branch 0 taken 300538 times.
✓ Branch 1 taken 8744 times.
✓ Branch 2 taken 8744 times.
✓ Branch 3 taken 1188 times.
310470 for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
1222
2/2
✓ Branch 0 taken 236598 times.
✓ Branch 1 taken 63940 times.
300538 if (is2D()) setPixelColorXY(x, y, c);
1223 63940 else setPixelColor(x, c);
1224 }
1225 1188 _colorScaled = false;
1226 }
1227
1228 /*
1229 * fade out function, higher rate = quicker fade
1230 * fading is highly dependant on frame rate (higher frame rates, faster fading)
1231 * each frame will fade at max 9% or as little as 0.8%
1232 */
1233 770 void Segment::fade_out(uint8_t rate) {
1234
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 770 times.
770 if (!isActive()) return; // not active
1235
2/2
✓ Branch 0 taken 636 times.
✓ Branch 1 taken 134 times.
770 const int cols = is2D() ? vWidth() : vLength();
1236 770 const int rows = vHeight(); // will be 1 for 1D
1237
1238 770 rate = (256-rate) >> 1;
1239 770 const int mappedRate = 256 / (rate + 1);
1240
1241
4/4
✓ Branch 0 taken 276992 times.
✓ Branch 1 taken 8490 times.
✓ Branch 2 taken 8490 times.
✓ Branch 3 taken 770 times.
286252 for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
1242
2/2
✓ Branch 0 taken 256892 times.
✓ Branch 1 taken 20100 times.
276992 uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x);
1243
2/2
✓ Branch 0 taken 62779 times.
✓ Branch 1 taken 214213 times.
276992 if (color == colors[1]) continue; // already at target color
1244
2/2
✓ Branch 0 taken 856852 times.
✓ Branch 1 taken 214213 times.
1071065 for (int i = 0; i < 32; i += 8) {
1245 856852 uint8_t c2 = (colors[1]>>i); // get background channel
1246 856852 uint8_t c1 = (color>>i); // get foreground channel
1247 // we can't use bitshift since we are using int
1248 856852 int delta = (c2 - c1) * mappedRate / 256;
1249 // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues)
1250
6/6
✓ Branch 0 taken 521386 times.
✓ Branch 1 taken 335466 times.
✓ Branch 2 taken 53154 times.
✓ Branch 3 taken 468232 times.
✓ Branch 4 taken 20713 times.
✓ Branch 5 taken 32441 times.
856852 if (delta == 0) delta += (c2 == c1) ? 0 : (c2 > c1) ? 1 : -1;
1251 // stuff new value back into color
1252 856852 color &= ~(0xFF<<i);
1253 856852 color |= ((c1 + delta) & 0xFF) << i;
1254 }
1255
2/2
✓ Branch 0 taken 194512 times.
✓ Branch 1 taken 19701 times.
214213 if (is2D()) setPixelColorXY(x, y, color);
1256 19701 else setPixelColor(x, color);
1257 }
1258 }
1259
1260 // fades all pixels to secondary color
1261 ✗ void Segment::fadeToSecondaryBy(uint8_t fadeBy) {
1262 ✗ if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
1263 ✗ const int cols = is2D() ? vWidth() : vLength();
1264 ✗ const int rows = vHeight(); // will be 1 for 1D
1265
1266 ✗ for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
1267 ✗ if (is2D()) setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), colors[1], fadeBy));
1268 ✗ else setPixelColor(x, color_blend(getPixelColor(x), colors[1], fadeBy));
1269 }
1270 }
1271
1272 // fades all pixels to black using nscale8()
1273 998 void Segment::fadeToBlackBy(uint8_t fadeBy) {
1274
5/6
✓ Branch 0 taken 998 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 981 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 981 times.
998 if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
1275
2/2
✓ Branch 0 taken 961 times.
✓ Branch 1 taken 20 times.
981 const int cols = is2D() ? vWidth() : vLength();
1276 981 const int rows = vHeight(); // will be 1 for 1D
1277
1278
4/4
✓ Branch 0 taken 222825 times.
✓ Branch 1 taken 8410 times.
✓ Branch 2 taken 8410 times.
✓ Branch 3 taken 981 times.
232216 for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) {
1279
2/2
✓ Branch 0 taken 219825 times.
✓ Branch 1 taken 3000 times.
222825 if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy));
1280 3000 else setPixelColor(x, color_fade(getPixelColor(x), 255-fadeBy));
1281 }
1282 }
1283
1284 /*
1285 * blurs segment content, source: FastLED colorutils.cpp
1286 * Note: for blur_amount > 215 this function does not work properly (creates alternating pattern)
1287 */
1288 424 void Segment::blur(uint8_t blur_amount, bool smear) {
1289
5/6
✓ Branch 0 taken 424 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 193 times.
✓ Branch 3 taken 231 times.
✓ Branch 4 taken 193 times.
✓ Branch 5 taken 231 times.
424 if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
1290 #ifndef WLED_DISABLE_2D
1291
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 7 times.
231 if (is2D()) {
1292 // compatibility with 2D
1293 224 blur2D(blur_amount, blur_amount, smear); // symmetrical 2D blur
1294 //box_blur(map(blur_amount,1,255,1,3), smear);
1295 224 return;
1296 }
1297 #endif
1298
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 uint8_t keep = smear ? 255 : 255 - blur_amount;
1299 7 uint8_t seep = blur_amount >> 1;
1300 7 unsigned vlength = vLength();
1301 7 uint32_t carryover = BLACK;
1302 uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
1303 uint32_t last;
1304 7 uint32_t curnew = BLACK;
1305
2/2
✓ Branch 0 taken 1050 times.
✓ Branch 1 taken 7 times.
1057 for (unsigned i = 0; i < vlength; i++) {
1306 1050 uint32_t cur = getPixelColor(i);
1307 1050 uint32_t part = color_fade(cur, seep);
1308 1050 curnew = color_fade(cur, keep);
1309
2/2
✓ Branch 0 taken 1043 times.
✓ Branch 1 taken 7 times.
1050 if (i > 0) {
1310
2/2
✓ Branch 0 taken 750 times.
✓ Branch 1 taken 293 times.
1043 if (carryover) curnew = color_add(curnew, carryover);
1311 1043 uint32_t prev = color_add(lastnew, part);
1312 // optimization: only set pixel if color has changed
1313
1/2
✓ Branch 0 taken 1043 times.
✗ Branch 1 not taken.
1043 if (last != prev) setPixelColor(i - 1, prev);
1314 7 } else setPixelColor(i, curnew); // first pixel
1315 1050 lastnew = curnew;
1316 1050 last = cur; // save original value for comparison on next iteration
1317 1050 carryover = part;
1318 }
1319 7 setPixelColor(vlength - 1, curnew);
1320 }
1321
1322 /*
1323 * Put a value 0 to 255 in to get a color value.
1324 * The colours are a transition r -> g -> b -> back to r
1325 * Inspired by the Adafruit examples.
1326 */
1327 239556 uint32_t Segment::color_wheel(uint8_t pos) const {
1328
2/2
✓ Branch 0 taken 76317 times.
✓ Branch 1 taken 163239 times.
239556 if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip().paletteBlend < 2" should be better instead of "true"
1329 163239 uint8_t w = W(getCurrentColor(0));
1330 163239 pos = 255 - pos;
1331
2/2
✓ Branch 0 taken 52492 times.
✓ Branch 1 taken 110747 times.
163239 if (pos < 85) {
1332 52492 return RGBW32((255 - pos * 3), 0, (pos * 3), w);
1333
2/2
✓ Branch 0 taken 51415 times.
✓ Branch 1 taken 59332 times.
110747 } else if (pos < 170) {
1334 51415 pos -= 85;
1335 51415 return RGBW32(0, (pos * 3), (255 - pos * 3), w);
1336 } else {
1337 59332 pos -= 170;
1338 59332 return RGBW32((pos * 3), (255 - pos * 3), 0, w);
1339 }
1340 }
1341
1342 /*
1343 * Gets a single color from the currently selected palette.
1344 * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically.
1345 * @param mapping if true, LED position in segment is considered for color
1346 * @param moving FastLED palettes will usually wrap back to the start smoothly. Set to true if effect has moving palette and you want wrap.
1347 * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead
1348 * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling)
1349 * @returns Single color from palette
1350 */
1351 529272 uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool moving, uint8_t mcol, uint8_t pbri) const {
1352
2/2
✓ Branch 0 taken 482498 times.
✓ Branch 1 taken 46774 times.
529272 uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0);
1353 // default palette or no RGB support on segment
1354
5/6
✓ Branch 0 taken 354872 times.
✓ Branch 1 taken 174400 times.
✓ Branch 2 taken 14071 times.
✓ Branch 3 taken 340801 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 188471 times.
529272 if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) {
1355
1/2
✓ Branch 0 taken 340801 times.
✗ Branch 1 not taken.
340801 return color_fade(color, pbri, true);
1356 }
1357
1358 188471 const int vL = vLength();
1359 188471 unsigned paletteIndex = i;
1360
3/4
✓ Branch 0 taken 5873 times.
✓ Branch 1 taken 182598 times.
✓ Branch 2 taken 5873 times.
✗ Branch 3 not taken.
188471 if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1);
1361 // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined/no interpolation of palette entries)
1362 // ColorFromPalette interpolations are: NOBLEND, LINEARBLEND, LINEARBLEND_NOWRAP
1363 188471 TBlendType blend = NOBLEND;
1364
2/6
✓ Branch 0 taken 188471 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 188471 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
188471 switch (strip().paletteBlend) { // NOTE: paletteBlend should be global
1365
2/2
✓ Branch 0 taken 88600 times.
✓ Branch 1 taken 99871 times.
188471 case 0: blend = moving ? LINEARBLEND : LINEARBLEND_NOWRAP; break;
1366 ✗ case 1: blend = LINEARBLEND; break;
1367 ✗ case 2: blend = LINEARBLEND_NOWRAP; break;
1368 }
1369
1/2
✓ Branch 0 taken 188471 times.
✗ Branch 1 not taken.
188471 CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, blend);
1370 188471 palcol.w = W(color);
1371
1372 188471 return palcol.color32;
1373 }
1374
1375
1376 ///////////////////////////////////////////////////////////////////////////////
1377 // WS2812FX class implementation
1378 ///////////////////////////////////////////////////////////////////////////////
1379
1380 //do not call this method from system context (network callback)
1381 3542 void WS2812FX::finalizeInit() {
1382 //reset segment runtimes
1383 3542 restartRuntime();
1384
1385 // for the lack of better place enumerate ledmaps here
1386 // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs
1387 // unfortunately this means we do not get updates after uploads
1388 // the other option is saving UI settings which will cause enumeration
1389 3542 enumerateLedmaps();
1390
1391 3542 _hasWhiteChannel = _isOffRefreshRequired = false;
1392
1393 3542 unsigned digitalCount = 0;
1394 #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
1395 // determine if it is sensible to use parallel I2S outputs on ESP32 (i.e. more than 5 outputs = 1 I2S + 4 RMT)
1396 unsigned maxLedsOnBus = 0;
1397 for (const auto &bus : busConfigs) {
1398 if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type)) {
1399 digitalCount++;
1400 if (bus.count > maxLedsOnBus) maxLedsOnBus = bus.count;
1401 }
1402 }
1403 DEBUG_PRINTF_P(PSTR("Maximum LEDs on a bus: %u\nDigital buses: %u\n"), maxLedsOnBus, digitalCount);
1404 // we may remove 300 LEDs per bus limit when NeoPixelBus is updated beyond 2.9.0
1405 if (maxLedsOnBus <= 300 && useParallelI2S) BusManager::useParallelOutput(); // must call before creating buses
1406 else useParallelI2S = false; // enforce single I2S
1407 #endif
1408
1409 // create buses/outputs
1410 3542 unsigned mem = 0;
1411 3542 digitalCount = 0;
1412
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3542 times.
3542 for (const auto &bus : busConfigs) {
1413 ✗ mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer
1414 ✗ if (mem <= MAX_LED_MEMORY) {
1415 ✗ if (BusManager::add(bus) == -1) break;
1416 } else DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount);
1417 }
1418 3542 busConfigs.clear();
1419 3542 busConfigs.shrink_to_fit();
1420
1421 //if busses failed to load, add default (fresh install, FS issue, ...)
1422
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3542 times.
3542 if (BusManager::getNumBusses() == 0) {
1423 DEBUG_PRINTLN(F("No busses, init default"));
1424 ✗ constexpr unsigned defDataTypes[] = {LED_TYPES};
1425 ✗ constexpr unsigned defDataPins[] = {DATA_PINS};
1426 ✗ constexpr unsigned defCounts[] = {PIXEL_COUNTS};
1427 ✗ constexpr unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0]));
1428 ✗ constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0]));
1429 ✗ constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0]));
1430
1431 static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins),
1432 "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES");
1433
1434 ✗ unsigned prevLen = 0;
1435 ✗ unsigned pinsIndex = 0;
1436 ✗ digitalCount = 0;
1437 ✗ for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) {
1438 uint8_t defPin[OUTPUT_MAX_PINS];
1439 // if we have less types than requested outputs and they do not align, use last known type to set current type
1440 ✗ unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1];
1441 ✗ unsigned busPins = Bus::getNumberOfPins(dataType);
1442
1443 // if we need more pins than available all outputs have been configured
1444 ✗ if (pinsIndex + busPins > defNumPins) break;
1445
1446 // Assign all pins first so we can check for conflicts on this bus
1447 ✗ for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j];
1448
1449 ✗ for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) {
1450 ✗ bool validPin = true;
1451 // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware
1452 // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc.
1453 // Pin should not be already allocated, read/only or defined for current bus
1454 ✗ while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) {
1455 ✗ if (validPin) {
1456 DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output."));
1457 ✗ defPin[j] = 1; // start with GPIO1 and work upwards
1458 ✗ validPin = false;
1459 ✗ } else if (defPin[j] < WLED_NUM_PINS) {
1460 ✗ defPin[j]++;
1461 } else {
1462 DEBUG_PRINTLN(F("No available pins left! Can't configure output."));
1463 ✗ return;
1464 }
1465 // is the newly assigned pin already defined or used previously?
1466 // try next in line until there are no clashes or we run out of pins
1467 bool clash;
1468 ✗ do {
1469 ✗ clash = false;
1470 // check for conflicts on current bus
1471 ✗ for (const auto &pin : defPin) {
1472 ✗ if (&pin != &defPin[j] && pin == defPin[j]) {
1473 ✗ clash = true;
1474 ✗ break;
1475 }
1476 }
1477 // We already have a clash on current bus, no point checking next buses
1478 ✗ if (!clash) {
1479 // check for conflicts in defined pins
1480 ✗ for (const auto &pin : defDataPins) {
1481 ✗ if (pin == defPin[j]) {
1482 ✗ clash = true;
1483 ✗ break;
1484 }
1485 }
1486 }
1487 ✗ if (clash) defPin[j]++;
1488 ✗ if (defPin[j] >= WLED_NUM_PINS) break;
1489 } while (clash);
1490 }
1491 }
1492 ✗ pinsIndex += busPins;
1493
1494 ✗ unsigned start = prevLen;
1495 // if we have less counts than pins and they do not align, use last known count to set current count
1496 ✗ unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1];
1497 // analog always has length 1
1498 ✗ if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1;
1499 ✗ prevLen += count;
1500 ✗ BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer);
1501 ✗ mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0);
1502 ✗ if (BusManager::add(defCfg) == -1) break;
1503 }
1504 }
1505 DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage());
1506
1507 3542 _length = 0;
1508
2/2
✓ Branch 0 taken 3542 times.
✓ Branch 1 taken 3542 times.
7084 for (int i=0; i<BusManager::getNumBusses(); i++) {
1509 3542 Bus *bus = BusManager::getBus(i);
1510
4/8
✓ Branch 0 taken 3542 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3542 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3542 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3542 times.
3542 if (!bus || !bus->isOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break;
1511 //RGBW mode is enabled if at least one of the strips is RGBW
1512 3542 _hasWhiteChannel |= bus->hasWhite();
1513 //refresh is required to remain off if at least one of the strips requires the refresh.
1514
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 3542 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
3542 _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog
1515 3542 unsigned busEnd = bus->getStart() + bus->getLength();
1516
1/2
✓ Branch 0 taken 3542 times.
✗ Branch 1 not taken.
3542 if (busEnd > _length) _length = busEnd;
1517
1518 // This must be done after all buses have been created, as some kinds (parallel I2S) interact
1519 3542 bus->begin();
1520 3542 bus->setBrightness(bri);
1521 }
1522 DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
1523
1524 3542 Segment::maxWidth = _length;
1525 3542 Segment::maxHeight = 1;
1526
1527 //segments are created in makeAutoSegments();
1528 DEBUG_PRINTLN(F("Loading custom palettes"));
1529 3542 loadCustomPalettes(); // (re)load all custom palettes
1530 DEBUG_PRINTLN(F("Loading custom ledmaps"));
1531 3542 deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist)
1532 }
1533
1534 8551 void WS2812FX::service() {
1535 8551 unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
1536 8551 now = nowUp + timebase;
1537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8551 times.
8551 if (_suspend) return;
1538 8551 unsigned long elapsed = nowUp - _lastServiceShow;
1539
1540
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8551 times.
8551 if (elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited
1541
2/4
✓ Branch 0 taken 8551 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8551 times.
✗ Branch 3 not taken.
8551 if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime
1542
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8551 times.
8551 if (elapsed < _frametime) return; // too early for service
1543 }
1544
1545 8551 bool doShow = false;
1546
1547 8551 _isServicing = true;
1548 8551 _segment_index = 0;
1549
1550
2/2
✓ Branch 0 taken 8551 times.
✓ Branch 1 taken 8551 times.
17102 for (segment &seg : _segments) {
1551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8551 times.
8551 if (_suspend) break; // immediately stop processing segments if suspend requested during service()
1552
1553 // process transition (mode changes in the middle of transition)
1554
1/2
✓ Branch 0 taken 8551 times.
✗ Branch 1 not taken.
8551 seg.handleTransition();
1555 // reset the segment runtime data if needed
1556
1/2
✓ Branch 0 taken 8551 times.
✗ Branch 1 not taken.
8551 seg.resetIfRequired();
1557
1558
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8551 times.
8551 if (!seg.isActive()) continue;
1559
1560 // last condition ensures all solid segments are updated at the same time
1561
4/8
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 8530 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
8551 if (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
1562 {
1563 8530 doShow = true;
1564
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 unsigned frameDelay = FRAMETIME;
1565
1566
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 if (!seg.freeze) { //only run effect function if not frozen
1567 8530 int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
1568 // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio
1569 // when cctFromRgb is true we implicitly calculate WW and CW from RGB values
1570
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8530 times.
8530 if (cctFromRgb) BusManager::setSegmentCCT(-1);
1571
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 else BusManager::setSegmentCCT(seg.currentBri(true), correctWB);
1572 // Effect blending
1573 // When two effects are being blended, each may have different segment data, this
1574 // data needs to be saved first and then restored before running previous mode.
1575 // The blending will largely depend on the effect behaviour since actual output (LEDs) may be
1576 // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer
1577 // would need to be allocated for each effect and then blended together for each pixel.
1578
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 seg.beginDraw(); // set up parameters for get/setPixelColor()
1579 #ifndef WLED_DISABLE_MODE_BLEND
1580 Segment::setClippingRect(0, 0); // disable clipping (just in case)
1581 if (seg.isInTransition()) {
1582 // a hack to determine if effect has changed
1583 uint8_t m = seg.currentMode();
1584 Segment::modeBlend(true); // set semaphore
1585 bool sameEffect = (m == seg.currentMode());
1586 Segment::modeBlend(false); // clear semaphore
1587 // set clipping rectangle
1588 // new mode is run inside clipping area and old mode outside clipping area
1589 unsigned p = seg.progress();
1590 unsigned w = seg.is2D() ? Segment::vWidth() : Segment::vLength();
1591 unsigned h = Segment::vHeight();
1592 unsigned dw = p * w / 0xFFFFU + 1;
1593 unsigned dh = p * h / 0xFFFFU + 1;
1594 unsigned orgBS = blendingStyle;
1595 if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead)
1596 else if (sameEffect && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
1597 // when effect stays the same push will look awful, change it to swipe
1598 switch (blendingStyle) {
1599 case BLEND_STYLE_PUSH_BR:
1600 case BLEND_STYLE_PUSH_TR:
1601 case BLEND_STYLE_PUSH_RIGHT: blendingStyle = BLEND_STYLE_SWIPE_RIGHT; break;
1602 case BLEND_STYLE_PUSH_BL:
1603 case BLEND_STYLE_PUSH_TL:
1604 case BLEND_STYLE_PUSH_LEFT: blendingStyle = BLEND_STYLE_SWIPE_LEFT; break;
1605 case BLEND_STYLE_PUSH_DOWN: blendingStyle = BLEND_STYLE_SWIPE_DOWN; break;
1606 case BLEND_STYLE_PUSH_UP: blendingStyle = BLEND_STYLE_SWIPE_UP; break;
1607 }
1608 }
1609 switch (blendingStyle) {
1610 case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
1611 Segment::setClippingRect(0, w, 0, h);
1612 break;
1613 case BLEND_STYLE_SWIPE_RIGHT: // left-to-right
1614 case BLEND_STYLE_PUSH_RIGHT: // left-to-right
1615 Segment::setClippingRect(0, dw, 0, h);
1616 break;
1617 case BLEND_STYLE_SWIPE_LEFT: // right-to-left
1618 case BLEND_STYLE_PUSH_LEFT: // right-to-left
1619 Segment::setClippingRect(w - dw, w, 0, h);
1620 break;
1621 case BLEND_STYLE_PINCH_OUT: // corners
1622 Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!!
1623 break;
1624 case BLEND_STYLE_INSIDE_OUT: // outward
1625 Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2);
1626 break;
1627 case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D)
1628 case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D)
1629 Segment::setClippingRect(0, w, 0, dh);
1630 break;
1631 case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D)
1632 case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D)
1633 Segment::setClippingRect(0, w, h - dh, h);
1634 break;
1635 case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D
1636 Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h);
1637 break;
1638 case BLEND_STYLE_OPEN_V: // vertical-outward (2D)
1639 Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2);
1640 break;
1641 case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D)
1642 Segment::setClippingRect(0, dw, 0, dh);
1643 break;
1644 case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D)
1645 Segment::setClippingRect(w - dw, w, 0, dh);
1646 break;
1647 case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D)
1648 Segment::setClippingRect(w - dw, w, h - dh, h);
1649 break;
1650 case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D)
1651 Segment::setClippingRect(0, dw, h - dh, h);
1652 break;
1653 }
1654 frameDelay = (*_mode[m])(); // run new/current mode
1655 // now run old/previous mode
1656 Segment::tmpsegd_t _tmpSegData;
1657 Segment::modeBlend(true); // set semaphore
1658 seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
1659 seg.beginDraw(); // set up parameters for get/setPixelColor()
1660 frameDelay = min(frameDelay, (unsigned)(*_mode[seg.currentMode()])()); // run old mode
1661 seg.call++; // increment old mode run counter
1662 seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
1663 Segment::modeBlend(false); // unset semaphore
1664 blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment
1665 } else
1666 #endif
1667
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 frameDelay = (*_mode[seg.mode])(); // run effect mode (not in transition)
1668 8530 seg.call++;
1669
8/10
✓ Branch 0 taken 5902 times.
✓ Branch 1 taken 2628 times.
✓ Branch 2 taken 5902 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 359 times.
✓ Branch 5 taken 5543 times.
✓ Branch 6 taken 359 times.
✓ Branch 7 taken 8171 times.
✓ Branch 8 taken 359 times.
✗ Branch 9 not taken.
8530 if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition
1670 8530 BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments
1671 }
1672
1673 8530 seg.next_time = nowUp + frameDelay;
1674 }
1675 8551 _segment_index++;
1676 }
1677 #ifndef WLED_DISABLE_MODE_BLEND
1678 Segment::setClippingRect(0, 0); // disable clipping for overlays
1679 #endif
1680 8551 _isServicing = false;
1681 8551 _triggered = false;
1682
1683 #ifdef WLED_DEBUG
1684 if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
1685 #endif
1686
2/2
✓ Branch 0 taken 8530 times.
✓ Branch 1 taken 21 times.
8551 if (doShow) {
1687 yield();
1688 8530 Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette
1689 8530 _lastServiceShow = nowUp; // update timestamp, for precise FPS control
1690
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 if (!_suspend) show();
1691 }
1692 #ifdef WLED_DEBUG
1693 if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
1694 #endif
1695 }
1696
1697 2373073 void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) const {
1698 2373073 i = getMappedPixelIndex(i);
1699
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2373073 times.
2373073 if (i >= _length) return;
1700 2373073 BusManager::setPixelColor(i, col);
1701 }
1702
1703 791136 uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const {
1704 791136 i = getMappedPixelIndex(i);
1705
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 791136 times.
791136 if (i >= _length) return 0;
1706 791136 return BusManager::getPixelColor(i);
1707 }
1708
1709 8530 void WS2812FX::show() {
1710 // avoid race condition, capture _callback value
1711 8530 show_callback callback = _callback;
1712
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8530 times.
8530 if (callback) callback();
1713 8530 unsigned long showNow = millis();
1714
1715 // some buses send asynchronously and this method will return before
1716 // all of the data has been sent.
1717 // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
1718 8530 BusManager::show();
1719
1720 8530 size_t diff = showNow - _lastShow;
1721
1722
1/2
✓ Branch 0 taken 8530 times.
✗ Branch 1 not taken.
8530 if (diff > 0) { // skip calculation if no time has passed
1723 8530 size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
1724 8530 _cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
1725 8530 _lastShow = showNow;
1726 }
1727 8530 }
1728
1729 ✗ void WS2812FX::setTargetFps(unsigned fps) {
1730 ✗ if (fps <= 250) _targetFps = fps;
1731 ✗ if (_targetFps > 0) _frametime = 1000 / _targetFps;
1732 ✗ else _frametime = MIN_FRAME_DELAY; // unlimited mode
1733 ✗ }
1734
1735 ✗ void WS2812FX::setCCT(uint16_t k) {
1736 ✗ for (segment &seg : _segments) {
1737 ✗ if (seg.isActive() && seg.isSelected()) {
1738 ✗ seg.setCCT(k);
1739 }
1740 }
1741 ✗ }
1742
1743 // 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
1744 // direct=false immediately triggers an effect redraw
1745 ✗ void WS2812FX::setBrightness(uint8_t b, bool direct) {
1746 if (gammaCorrectBri) b = gamma8(b);
1747 ✗ if (_brightness == b) return;
1748 ✗ _brightness = b;
1749 ✗ if (_brightness == 0) { //unfreeze all segments on power off
1750 ✗ for (segment &seg : _segments) {
1751 ✗ seg.freeze = false;
1752 }
1753 }
1754 // setting brightness with NeoPixelBusLg has no effect on already painted pixels,
1755 // so we need to force an update to existing buffer
1756 ✗ BusManager::setBrightness(b);
1757 ✗ if (!direct) {
1758 ✗ unsigned long t = millis();
1759 ✗ if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon
1760 }
1761 }
1762
1763 ✗ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const {
1764 ✗ uint8_t totalLC = 0;
1765 ✗ for (const segment &seg : _segments) {
1766 ✗ if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities();
1767 }
1768 ✗ return totalLC;
1769 }
1770
1771 ✗ uint8_t WS2812FX::getFirstSelectedSegId() const {
1772 ✗ size_t i = 0;
1773 ✗ for (const segment &seg : _segments) {
1774 ✗ if (seg.isActive() && seg.isSelected()) return i;
1775 ✗ i++;
1776 }
1777 // if none selected, use the main segment
1778 ✗ return getMainSegmentId();
1779 }
1780
1781 ✗ void WS2812FX::setMainSegmentId(unsigned n) {
1782 ✗ _mainSegment = 0;
1783 ✗ if (n < _segments.size()) {
1784 ✗ _mainSegment = n;
1785 }
1786 ✗ return;
1787 }
1788
1789 ✗ uint8_t WS2812FX::getLastActiveSegmentId() const {
1790 ✗ for (size_t i = _segments.size() -1; i > 0; i--) {
1791 ✗ if (_segments[i].isActive()) return i;
1792 }
1793 ✗ return 0;
1794 }
1795
1796 51 uint8_t WS2812FX::getActiveSegmentsNum() const {
1797 51 uint8_t c = 0;
1798
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 51 times.
102 for (size_t i = 0; i < _segments.size(); i++) {
1799
1/2
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
51 if (_segments[i].isActive()) c++;
1800 }
1801 51 return c;
1802 }
1803
1804 2883 uint16_t WS2812FX::getLengthTotal() const {
1805 2883 unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D
1806
2/4
✓ Branch 0 taken 2883 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2883 times.
2883 if (isMatrix && _length > len) len = _length; // for 2D with trailing strip
1807 2883 return len;
1808 }
1809
1810 ✗ uint16_t WS2812FX::getLengthPhysical() const {
1811 ✗ unsigned len = 0;
1812 ✗ for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
1813 ✗ Bus *bus = BusManager::getBus(b);
1814 ✗ if (bus->isVirtual()) continue; //exclude non-physical network busses
1815 ✗ len += bus->getLength();
1816 }
1817 ✗ return len;
1818 }
1819
1820 //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw.
1821 //returns if there is an RGBW bus (supports RGB and White, not only white)
1822 //not influenced by auto-white mode, also true if white slider does not affect output white channel
1823 ✗ bool WS2812FX::hasRGBWBus() const {
1824 ✗ for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
1825 ✗ const Bus *bus = BusManager::getBus(b);
1826 ✗ if (!bus || !bus->isOk()) break;
1827 ✗ if (bus->hasRGB() && bus->hasWhite()) return true;
1828 }
1829 ✗ return false;
1830 }
1831
1832 ✗ bool WS2812FX::hasCCTBus() const {
1833 ✗ if (cctFromRgb && !correctWB) return false;
1834 ✗ for (size_t b = 0; b < BusManager::getNumBusses(); b++) {
1835 ✗ const Bus *bus = BusManager::getBus(b);
1836 ✗ if (!bus || !bus->isOk()) break;
1837 ✗ if (bus->hasCCT()) return true;
1838 }
1839 ✗ return false;
1840 }
1841
1842 ✗ void WS2812FX::purgeSegments() {
1843 // remove all inactive segments (from the back)
1844 ✗ int deleted = 0;
1845 ✗ if (_segments.size() <= 1) return;
1846 ✗ for (size_t i = _segments.size()-1; i > 0; i--)
1847 ✗ if (_segments[i].stop == 0) {
1848 ✗ deleted++;
1849 ✗ _segments.erase(_segments.begin() + i);
1850 }
1851 ✗ if (deleted) {
1852 ✗ _segments.shrink_to_fit();
1853 ✗ setMainSegmentId(0);
1854 }
1855 }
1856
1857 ✗ Segment& WS2812FX::getSegment(unsigned id) {
1858 ✗ return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
1859 }
1860
1861 ✗ void WS2812FX::resetSegments() {
1862 ✗ _segments.clear(); // destructs all Segment as part of clearing
1863 #ifndef WLED_DISABLE_2D
1864 ✗ segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length);
1865 #else
1866 segment seg = Segment(0, _length);
1867 #endif
1868 ✗ _segments.push_back(seg);
1869 ✗ _segments.shrink_to_fit(); // just in case ...
1870 ✗ _mainSegment = 0;
1871 ✗ }
1872
1873 ✗ void WS2812FX::makeAutoSegments(bool forceReset) {
1874 ✗ if (autoSegments) { //make one segment per bus
1875 ✗ unsigned segStarts[MAX_NUM_SEGMENTS] = {0};
1876 ✗ unsigned segStops [MAX_NUM_SEGMENTS] = {0};
1877 ✗ size_t s = 0;
1878
1879 #ifndef WLED_DISABLE_2D
1880 // 2D segment is the 1st one using entire matrix
1881 ✗ if (isMatrix) {
1882 ✗ segStarts[0] = 0;
1883 ✗ segStops[0] = Segment::maxWidth*Segment::maxHeight;
1884 ✗ s++;
1885 }
1886 #endif
1887
1888 ✗ for (size_t i = s; i < BusManager::getNumBusses(); i++) {
1889 ✗ const Bus *bus = BusManager::getBus(i);
1890 ✗ if (!bus || !bus->isOk()) break;
1891
1892 ✗ segStarts[s] = bus->getStart();
1893 ✗ segStops[s] = segStarts[s] + bus->getLength();
1894
1895 #ifndef WLED_DISABLE_2D
1896 ✗ if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix
1897 ✗ if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight;
1898 #endif
1899
1900 //check for overlap with previous segments
1901 ✗ for (size_t j = 0; j < s; j++) {
1902 ✗ if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) {
1903 //segments overlap, merge
1904 ✗ segStarts[j] = min(segStarts[s],segStarts[j]);
1905 ✗ segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0;
1906 ✗ s--;
1907 }
1908 }
1909 ✗ s++;
1910 }
1911
1912 ✗ _segments.clear();
1913 ✗ _segments.reserve(s); // prevent reallocations
1914 // there is always at least one segment (but we need to differentiate between 1D and 2D)
1915 #ifndef WLED_DISABLE_2D
1916 ✗ if (isMatrix)
1917 ✗ _segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight));
1918 else
1919 #endif
1920 ✗ _segments.push_back(Segment(segStarts[0], segStops[0]));
1921 ✗ for (size_t i = 1; i < s; i++) {
1922 ✗ _segments.push_back(Segment(segStarts[i], segStops[i]));
1923 }
1924 DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size());
1925
1926 } else {
1927
1928 ✗ if (forceReset || getSegmentsNum() == 0) resetSegments();
1929 //expand the main seg to the entire length, but only if there are no other segments, or reset is forced
1930 ✗ else if (getActiveSegmentsNum() == 1) {
1931 ✗ size_t i = getLastActiveSegmentId();
1932 #ifndef WLED_DISABLE_2D
1933 ✗ _segments[i].start = 0;
1934 ✗ _segments[i].stop = Segment::maxWidth;
1935 ✗ _segments[i].startY = 0;
1936 ✗ _segments[i].stopY = Segment::maxHeight;
1937 ✗ _segments[i].grouping = 1;
1938 ✗ _segments[i].spacing = 0;
1939 #else
1940 _segments[i].start = 0;
1941 _segments[i].stop = _length;
1942 #endif
1943 }
1944 }
1945 ✗ _mainSegment = 0;
1946
1947 ✗ fixInvalidSegments();
1948 ✗ }
1949
1950 ✗ void WS2812FX::fixInvalidSegments() {
1951 //make sure no segment is longer than total (sanity check)
1952 ✗ for (size_t i = getSegmentsNum()-1; i > 0; i--) {
1953 ✗ if (isMatrix) {
1954 #ifndef WLED_DISABLE_2D
1955 ✗ if (_segments[i].start >= Segment::maxWidth * Segment::maxHeight) {
1956 // 1D segment at the end of matrix
1957 ✗ if (_segments[i].start >= _length || _segments[i].startY > 0 || _segments[i].stopY > 1) { _segments.erase(_segments.begin()+i); continue; }
1958 ✗ if (_segments[i].stop > _length) _segments[i].stop = _length;
1959 ✗ continue;
1960 }
1961 ✗ if (_segments[i].start >= Segment::maxWidth || _segments[i].startY >= Segment::maxHeight) { _segments.erase(_segments.begin()+i); continue; }
1962 ✗ if (_segments[i].stop > Segment::maxWidth) _segments[i].stop = Segment::maxWidth;
1963 ✗ if (_segments[i].stopY > Segment::maxHeight) _segments[i].stopY = Segment::maxHeight;
1964 #endif
1965 } else {
1966 ✗ if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; }
1967 ✗ if (_segments[i].stop > _length) _segments[i].stop = _length;
1968 }
1969 }
1970 // if any segments were deleted free memory
1971 ✗ purgeSegments();
1972 // this is always called as the last step after finalizeInit(), update covered bus types
1973 ✗ for (segment &seg : _segments)
1974 ✗ seg.refreshLightCapabilities();
1975 ✗ }
1976
1977 //true if all segments align with a bus, or if a segment covers the total length
1978 //irrelevant in 2D set-up
1979 ✗ bool WS2812FX::checkSegmentAlignment() const {
1980 ✗ bool aligned = false;
1981 ✗ for (const segment &seg : _segments) {
1982 ✗ for (unsigned b = 0; b<BusManager::getNumBusses(); b++) {
1983 ✗ const Bus *bus = BusManager::getBus(b);
1984 ✗ if (!bus || !bus->isOk()) break;
1985 ✗ if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
1986 }
1987 ✗ if (seg.start == 0 && seg.stop == _length) aligned = true;
1988 ✗ if (!aligned) return false;
1989 }
1990 ✗ return true;
1991 }
1992
1993 // used by analog clock overlay
1994 ✗ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) {
1995 ✗ if (i2 < i) std::swap(i,i2);
1996 ✗ for (unsigned x = i; x <= i2; x++) setPixelColor(x, col);
1997 ✗ }
1998
1999 #ifdef WLED_DEBUG
2000 void WS2812FX::printSize() {
2001 size_t size = 0;
2002 for (const Segment &seg : _segments) size += seg.getSize();
2003 DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData());
2004 for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT());
2005 DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr)));
2006 DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *)));
2007 DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t));
2008 }
2009 #endif
2010
2011 3542 void WS2812FX::loadCustomPalettes() {
2012 #ifndef WLED_DISABLE_FPP
2013 byte tcp[72]; //support gradient palettes with up to 18 entries
2014 CRGBPalette16 targetPalette;
2015 customPalettes.clear(); // start fresh
2016 for (int index = 0; index<10; index++) {
2017 char fileName[32];
2018 sprintf_P(fileName, PSTR("/palette%d.json"), index);
2019
2020 StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers
2021 if (WLED_FS.exists(fileName)) {
2022 DEBUG_PRINT(F("Reading palette from "));
2023 DEBUG_PRINTLN(fileName);
2024
2025 if (readObjectFromFile(fileName, nullptr, &pDoc)) {
2026 JsonArray pal = pDoc[F("palette")];
2027 if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries)
2028 if (pal[0].is<int>() && pal[1].is<const char *>()) {
2029 // we have an array of index & hex strings
2030 size_t palSize = MIN(pal.size(), 36);
2031 palSize -= palSize % 2; // make sure size is multiple of 2
2032 for (size_t i=0, j=0; i<palSize && pal[i].as<int>()<256; i+=2, j+=4) {
2033 uint8_t rgbw[] = {0,0,0,0};
2034 tcp[ j ] = (uint8_t) pal[ i ].as<int>(); // index
2035 colorFromHexString(rgbw, pal[i+1].as<const char *>()); // will catch non-string entires
2036 for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component
2037 DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3]));
2038 }
2039 } else {
2040 size_t palSize = MIN(pal.size(), 72);
2041 palSize -= palSize % 4; // make sure size is multiple of 4
2042 for (size_t i=0; i<palSize && pal[i].as<int>()<256; i+=4) {
2043 tcp[ i ] = (uint8_t) pal[ i ].as<int>(); // index
2044 tcp[i+1] = gamma8((uint8_t) pal[i+1].as<int>()); // R
2045 tcp[i+2] = gamma8((uint8_t) pal[i+2].as<int>()); // G
2046 tcp[i+3] = gamma8((uint8_t) pal[i+3].as<int>()); // B
2047 DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3]));
2048 }
2049 }
2050 customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp));
2051 } else {
2052 DEBUG_PRINTLN(F("Wrong palette format."));
2053 }
2054 }
2055 } else {
2056 break;
2057 }
2058 }
2059 #endif
2060 3542 }
2061
2062 //load custom mapping table from JSON file (called from finalizeInit() or deserializeState())
2063 3542 bool WS2812FX::deserializeMap(unsigned n) {
2064 // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one.
2065 #ifndef WLED_DISABLE_FPP
2066
2067 char fileName[32];
2068 strcpy_P(fileName, PSTR("/ledmap"));
2069 if (n) sprintf(fileName +7, "%d", n);
2070 strcat_P(fileName, PSTR(".json"));
2071 bool isFile = WLED_FS.exists(fileName);
2072
2073 customMappingSize = 0; // prevent use of mapping if anything goes wrong
2074 currentLedmap = 0;
2075 if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI)
2076
2077 if (!isFile && n==0 && isMatrix) {
2078 setUpMatrix();
2079 return false;
2080 }
2081
2082 if (!isFile || !requestJSONBufferLock(7)) return false;
2083
2084 StaticJsonDocument<64> filter;
2085 filter[F("width")] = true;
2086 filter[F("height")] = true;
2087 if (!readObjectFromFile(fileName, nullptr, pDoc, &filter)) {
2088 DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName);
2089 releaseJSONBufferLock();
2090 return false; // if file does not load properly then exit
2091 }
2092
2093 suspend();
2094
2095 JsonObject root = pDoc->as<JsonObject>();
2096 // if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps)
2097 if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) {
2098 Segment::maxWidth = min(max(root[F("width")].as<int>(), 1), 128);
2099 Segment::maxHeight = min(max(root[F("height")].as<int>(), 1), 128);
2100 }
2101
2102 if (customMappingTable) free(customMappingTable);
2103 customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
2104 if (customMappingTable) {
2105 DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName);
2106 File f = WLED_FS.open(fileName, "r");
2107 f.find("\"map\":[");
2108 while (f.available()) { // f.position() < f.size() - 1
2109 char number[32];
2110 size_t numRead = f.readBytesUntil(',', number, sizeof(number)-1); // read a single number (may include array terminating "]" but not number separator ',')
2111 number[numRead] = 0;
2112 if (numRead > 0) {
2113 char *end = strchr(number,']'); // we encountered end of array so stop processing if no digit found
2114 bool foundDigit = (end == nullptr);
2115 int i = 0;
2116 if (end != nullptr) do {
2117 if (number[i] >= '0' && number[i] <= '9') foundDigit = true;
2118 if (foundDigit || &number[i++] == end) break;
2119 } while (i < 32);
2120 if (!foundDigit) break;
2121 int index = atoi(number);
2122 if (index < 0 || index > 16384) index = 0xFFFF;
2123 customMappingTable[customMappingSize++] = index;
2124 if (customMappingSize > getLengthTotal()) break;
2125 } else break; // there was nothing to read, stop
2126 }
2127 currentLedmap = n;
2128 f.close();
2129
2130 #ifdef WLED_DEBUG
2131 DEBUG_PRINT(F("Loaded ledmap:"));
2132 for (unsigned i=0; i<customMappingSize; i++) {
2133 if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
2134 DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
2135 }
2136 DEBUG_PRINTLN();
2137 #endif
2138 /*
2139 JsonArray map = root[F("map")];
2140 if (!map.isNull() && map.size()) { // not an empty map
2141 customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal());
2142 for (unsigned i=0; i<customMappingSize; i++) customMappingTable[i] = (uint16_t) (map[i]<0 ? 0xFFFFU : map[i]);
2143 currentLedmap = n;
2144 }
2145 */
2146 } else {
2147 DEBUG_PRINTLN(F("ERROR LED map allocation error."));
2148 }
2149 #else
2150 3542 setUpMatrix();
2151
2152 #endif
2153
2154 3542 resume();
2155
2156 3542 releaseJSONBufferLock();
2157 3542 return (customMappingSize > 0);
2158 }
2159
2160
2161 const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])=====";
2162 const char JSON_palette_names[] PROGMEM = R"=====([
2163 "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean",
2164 "Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash",
2165 "Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64",
2166 "Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn",
2167 "Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura",
2168 "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
2169 "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
2170 "Candy2","Traffic Light"
2171 ])=====";
2172