GCC Code Coverage Report


Directory: ./
File: overlays/wled/FX_2Dfcn.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 238 330 72.1%
Functions: 13 14 92.9%
Branches: 215 399 53.9%

Line Branch Exec Source
1 /*
2 FX_2Dfcn.cpp contains all 2D utility functions
3
4 Copyright (c) 2022 Blaz Kristan (https://blaz.at/home)
5 Licensed under the EUPL v. 1.2 or later
6 Adapted from code originally licensed under the MIT license
7
8 Parts of the code adapted from WLED Sound Reactive
9 */
10 #include "wled.h"
11 #include "FX.h"
12 #include "palettes.h"
13
14 // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels
15 // this converts physical (possibly irregular) LED arrangement into well defined
16 // array of logical pixels: fist entry corresponds to left-topmost logical pixel
17 // followed by horizontal pixels, when Segment::maxWidth logical pixels are added they
18 // are followed by next row (down) of Segment::maxWidth pixels (and so forth)
19 // note: matrix may be comprised of multiple panels each with different orientation
20 // but ledmap takes care of that. ledmap is constructed upon initialization
21 // so matrix should disable regular ledmap processing
22 3542 void WS2812FX::setUpMatrix() {
23 #ifndef WLED_DISABLE_2D
24 // isMatrix is set in cfg.cpp or set.cpp
25
2/2
✓ Branch 0 taken 961 times.
✓ Branch 1 taken 2581 times.
3542 if (isMatrix) {
26 // calculate width dynamically because it may have gaps
27 961 Segment::maxWidth = 1;
28 961 Segment::maxHeight = 1;
29
2/2
✓ Branch 0 taken 961 times.
✓ Branch 1 taken 961 times.
1922 for (size_t i = 0; i < panel.size(); i++) {
30 961 Panel &p = panel[i];
31
1/2
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
961 if (p.xOffset + p.width > Segment::maxWidth) {
32 961 Segment::maxWidth = p.xOffset + p.width;
33 }
34
1/2
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
961 if (p.yOffset + p.height > Segment::maxHeight) {
35 961 Segment::maxHeight = p.yOffset + p.height;
36 }
37 }
38
39 // safety check
40
3/6
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 961 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 961 times.
961 if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) {
41 DEBUG_PRINTLN(F("2D Bounds error."));
42 ✗ isMatrix = false;
43 ✗ Segment::maxWidth = _length;
44 ✗ Segment::maxHeight = 1;
45 ✗ panels = 0;
46 ✗ panel.clear(); // release memory allocated by panels
47 ✗ resetSegments();
48 ✗ return;
49 }
50
51 961 customMappingSize = 0; // prevent use of mapping if anything goes wrong
52
53
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 961 times.
961 if (customMappingTable) free(customMappingTable);
54 961 customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
55
56
1/2
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
961 if (customMappingTable) {
57
1/2
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
961 customMappingSize = getLengthTotal();
58
59 // fill with empty in case we don't fill the entire matrix
60 961 unsigned matrixSize = Segment::maxWidth * Segment::maxHeight;
61
2/2
✓ Branch 0 taken 373922 times.
✓ Branch 1 taken 961 times.
374883 for (unsigned i = 0; i<matrixSize; i++) customMappingTable[i] = 0xFFFFU;
62
2/4
✓ Branch 0 taken 961 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 961 times.
961 for (unsigned i = matrixSize; i<getLengthTotal(); i++) customMappingTable[i] = i; // trailing LEDs for ledmap (after matrix) if it exist
63
64 // we will try to load a "gap" array (a JSON file)
65 // the array has to have the same amount of values as mapping array (or larger)
66 // "gap" array is used while building ledmap (mapping array)
67 // and discarded afterwards as it has no meaning after the process
68 // content of the file is just raw JSON array in the form of [val1,val2,val3,...]
69 // there are no other "key":"value" pairs in it
70 // allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
71 961 char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json"));
72 961 bool isFile = WLED_FS.exists(fileName);
73 961 size_t gapSize = 0;
74 961 int8_t *gapTable = nullptr;
75
76 #ifndef WLED_DISABLE_FPP
77 if (isFile && requestJSONBufferLock(20)) {
78 DEBUG_PRINT(F("Reading LED gap from "));
79 DEBUG_PRINTLN(fileName);
80 // read the array into global JSON buffer
81 if (readObjectFromFile(fileName, nullptr, pDoc)) {
82 // the array is similar to ledmap, except it has only 3 values:
83 // -1 ... missing pixel (do not increase pixel count)
84 // 0 ... inactive pixel (it does count, but should be mapped out (-1))
85 // 1 ... active pixel (it will count and will be mapped)
86 JsonArray map = pDoc->as<JsonArray>();
87 gapSize = map.size();
88 if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
89 gapTable = static_cast<int8_t*>(malloc(gapSize));
90 if (gapTable) for (size_t i = 0; i < gapSize; i++) {
91 gapTable[i] = constrain(map[i], -1, 1);
92 }
93 }
94 }
95 DEBUG_PRINTLN(F("Gaps loaded."));
96 releaseJSONBufferLock();
97 }
98 #endif
99
100 961 unsigned x, y, pix=0; //pixel
101
2/2
✓ Branch 0 taken 961 times.
✓ Branch 1 taken 961 times.
1922 for (size_t pan = 0; pan < panel.size(); pan++) {
102 961 Panel &p = panel[pan];
103
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 961 times.
961 unsigned h = p.vertical ? p.height : p.width;
104
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 961 times.
961 unsigned v = p.vertical ? p.width : p.height;
105
2/2
✓ Branch 0 taken 12269 times.
✓ Branch 1 taken 961 times.
13230 for (size_t j = 0; j < v; j++){
106
2/2
✓ Branch 0 taken 373922 times.
✓ Branch 1 taken 12269 times.
386191 for(size_t i = 0; i < h; i++) {
107
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 373922 times.
373922 y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j;
108
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 373922 times.
373922 x = (p.vertical?p.bottomStart:p.rightStart) ? h-i-1 : i;
109
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
373922 x = p.serpentine && j%2 ? h-x-1 : x;
110
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 373922 times.
373922 size_t index = (p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x);
111
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
373922 if (!gapTable || (gapTable && gapTable[index] > 0)) customMappingTable[index] = pix; // a useful pixel (otherwise -1 is retained)
112
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 373922 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
373922 if (!gapTable || (gapTable && gapTable[index] >= 0)) pix++; // not a missing pixel
113 }
114 }
115 }
116
117 // delete gap array as we no longer need it
118
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 961 times.
961 if (gapTable) free(gapTable);
119
120 #ifdef WLED_DEBUG
121 DEBUG_PRINT(F("Matrix ledmap:"));
122 for (unsigned i=0; i<customMappingSize; i++) {
123 if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
124 DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
125 }
126 DEBUG_PRINTLN();
127 #endif
128 } else { // memory allocation error
129 DEBUG_PRINTLN(F("ERROR 2D LED map allocation error."));
130 ✗ isMatrix = false;
131 ✗ panels = 0;
132 ✗ panel.clear();
133 ✗ Segment::maxWidth = _length;
134 ✗ Segment::maxHeight = 1;
135 ✗ resetSegments();
136 }
137 }
138 #else
139 isMatrix = false; // no matter what config says
140 #endif
141 }
142
143
144 ///////////////////////////////////////////////////////////
145 // Segment:: routines
146 ///////////////////////////////////////////////////////////
147
148 #ifndef WLED_DISABLE_2D
149
150 // raw setColor function without checks (checks are done in setPixelColorXY())
151 2141600 void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const
152 {
153 2141600 const int baseX = start + x;
154 2141600 const int baseY = startY + y;
155 #ifndef WLED_DISABLE_MODE_BLEND
156 // if blending modes, blend with underlying pixel
157 if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) col = color_blend16(strip().getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
158 #endif
159 2141600 strip().setPixelColorXY(baseX, baseY, col);
160
161 // Apply mirroring
162
2/4
✓ Branch 0 taken 2141600 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2141600 times.
2141600 if (mirror || mirror_y) {
163 ✗ const int mirrorX = start + width() - x - 1;
164 ✗ const int mirrorY = startY + height() - y - 1;
165 ✗ if (mirror) strip().setPixelColorXY(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY, col);
166 ✗ if (mirror_y) strip().setPixelColorXY(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY, col);
167 ✗ if (mirror && mirror_y) strip().setPixelColorXY(mirrorX, mirrorY, col);
168 }
169 2141600 }
170
171 // pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
172 // if clipping start > stop the clipping range is inverted
173 // _modeBlend==true -> old effect during transition
174 // _modeBlend==false -> new effect during transition
175 2900507 bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const {
176 #ifndef WLED_DISABLE_MODE_BLEND
177 if (_clipStart != _clipStop && blendingStyle != BLEND_STYLE_FADE) {
178 const bool invertX = _clipStart > _clipStop;
179 const bool invertY = _clipStartY > _clipStopY;
180 const int startX = invertX ? _clipStop : _clipStart;
181 const int stopX = invertX ? _clipStart : _clipStop;
182 const int startY = invertY ? _clipStopY : _clipStartY;
183 const int stopY = invertY ? _clipStartY : _clipStopY;
184 if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
185 const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth())
186 const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight())
187 if (len < 2) return false;
188 const unsigned shuffled = hashInt(x + y * width) % len;
189 const unsigned pos = (shuffled * 0xFFFFU) / len;
190 return progress() > pos;
191 }
192 bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside;
193 bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside;
194 const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend;
195 if (xInside && yInside) return clip; // covers window & corners (inverted)
196 return !clip;
197 }
198 #endif
199 2900507 return false;
200 }
201
202 2242501 void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
203 {
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2242501 times.
2242501 if (!isActive()) return; // not active
205
206 2242501 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
207 2242501 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
208
209 #ifndef WLED_DISABLE_MODE_BLEND
210 unsigned prog = 0xFFFF - progress();
211 if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
212 unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
213 unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
214 if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x += dX;
215 else x -= dX;
216 if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
217 else y += dY;
218 }
219 #endif
220
221
11/12
✓ Branch 0 taken 2237035 times.
✓ Branch 1 taken 5466 times.
✓ Branch 2 taken 2151729 times.
✓ Branch 3 taken 85306 times.
✓ Branch 4 taken 2151000 times.
✓ Branch 5 taken 729 times.
✓ Branch 6 taken 2141600 times.
✓ Branch 7 taken 9400 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 2141600 times.
✓ Branch 10 taken 100901 times.
✓ Branch 11 taken 2141600 times.
2242501 if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit
222
223 // if color is unscaled
224
2/2
✓ Branch 0 taken 317970 times.
✓ Branch 1 taken 1823630 times.
2141600 if (!_colorScaled) col = color_fade(col, _segBri);
225
226
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141600 times.
2141600 if (reverse ) x = vW - x - 1;
227
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141600 times.
2141600 if (reverse_y) y = vH - y - 1;
228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141600 times.
2141600 if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
229 2141600 unsigned groupLen = groupLength();
230
231
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2141600 times.
2141600 if (groupLen > 1) {
232 ✗ int W = width();
233 ✗ int H = height();
234 ✗ x *= groupLen; // expand to physical pixels
235 ✗ y *= groupLen; // expand to physical pixels
236 ✗ const int maxY = std::min(y + grouping, H);
237 ✗ const int maxX = std::min(x + grouping, W);
238 ✗ for (int yY = y; yY < maxY; yY++) {
239 ✗ for (int xX = x; xX < maxX; xX++) {
240 ✗ _setPixelColorXY_raw(xX, yY, col);
241 }
242 }
243 } else {
244 2141600 _setPixelColorXY_raw(x, y, col);
245 }
246 }
247
248 #ifdef WLED_USE_AA_PIXELS
249 // anti-aliased version of setPixelColorXY()
250 void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
251 {
252 if (!isActive()) return; // not active
253 if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
254
255 float fX = x * (vWidth()-1);
256 float fY = y * (vHeight()-1);
257 if (aa) {
258 unsigned xL = roundf(fX-0.49f);
259 unsigned xR = roundf(fX+0.49f);
260 unsigned yT = roundf(fY-0.49f);
261 unsigned yB = roundf(fY+0.49f);
262 float dL = (fX - xL)*(fX - xL);
263 float dR = (xR - fX)*(xR - fX);
264 float dT = (fY - yT)*(fY - yT);
265 float dB = (yB - fY)*(yB - fY);
266 uint32_t cXLYT = getPixelColorXY(xL, yT);
267 uint32_t cXRYT = getPixelColorXY(xR, yT);
268 uint32_t cXLYB = getPixelColorXY(xL, yB);
269 uint32_t cXRYB = getPixelColorXY(xR, yB);
270
271 if (xL!=xR && yT!=yB) {
272 setPixelColorXY(xL, yT, color_blend(col, cXLYT, uint8_t(sqrtf(dL*dT)*255.0f))); // blend TL pixel
273 setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(sqrtf(dR*dT)*255.0f))); // blend TR pixel
274 setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(sqrtf(dL*dB)*255.0f))); // blend BL pixel
275 setPixelColorXY(xR, yB, color_blend(col, cXRYB, uint8_t(sqrtf(dR*dB)*255.0f))); // blend BR pixel
276 } else if (xR!=xL && yT==yB) {
277 setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dL*255.0f))); // blend L pixel
278 setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(dR*255.0f))); // blend R pixel
279 } else if (xR==xL && yT!=yB) {
280 setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dT*255.0f))); // blend T pixel
281 setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(dB*255.0f))); // blend B pixel
282 } else {
283 setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel)
284 }
285 } else {
286 setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col);
287 }
288 }
289 #endif
290
291 // returns RGBW values of pixel
292 780550 uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
293
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 780550 times.
780550 if (!isActive()) return 0; // not active
294
295 780550 const int vW = vWidth();
296 780550 const int vH = vHeight();
297
298 #ifndef WLED_DISABLE_MODE_BLEND
299 unsigned prog = 0xFFFF - progress();
300 if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
301 unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
302 unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
303 if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX;
304 else x += dX;
305 if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
306 else y += dY;
307 }
308 #endif
309
310
11/12
✓ Branch 0 taken 779213 times.
✓ Branch 1 taken 1337 times.
✓ Branch 2 taken 768961 times.
✓ Branch 3 taken 10252 times.
✓ Branch 4 taken 768232 times.
✓ Branch 5 taken 729 times.
✓ Branch 6 taken 758907 times.
✓ Branch 7 taken 9325 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 758907 times.
✓ Branch 10 taken 21643 times.
✓ Branch 11 taken 758907 times.
780550 if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit
311
312
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 758907 times.
758907 if (reverse ) x = vW - x - 1;
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 758907 times.
758907 if (reverse_y) y = vH - y - 1;
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 758907 times.
758907 if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
315 758907 x *= groupLength(); // expand to physical pixels
316 758907 y *= groupLength(); // expand to physical pixels
317
3/6
✓ Branch 0 taken 758907 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 758907 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 758907 times.
758907 if (x >= width() || y >= height()) return 0;
318 758907 return strip().getPixelColorXY(start + x, startY + y);
319 }
320
321 // 2D blurring, can be asymmetrical
322 228 void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
323
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 228 times.
228 if (!isActive()) return; // not active
324 228 const unsigned cols = vWidth();
325 228 const unsigned rows = vHeight();
326 uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
327 uint32_t last;
328
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 4 times.
228 if (blur_x) {
329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
224 const uint8_t keepx = smear ? 255 : 255 - blur_x;
330 224 const uint8_t seepx = blur_x >> 1;
331
2/2
✓ Branch 0 taken 2778 times.
✓ Branch 1 taken 224 times.
3002 for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
332 2778 uint32_t carryover = BLACK;
333 2778 uint32_t curnew = BLACK;
334
2/2
✓ Branch 0 taken 84246 times.
✓ Branch 1 taken 2778 times.
87024 for (unsigned x = 0; x < cols; x++) {
335 84246 uint32_t cur = getPixelColorXY(x, row);
336 84246 uint32_t part = color_fade(cur, seepx);
337 84246 curnew = color_fade(cur, keepx);
338
2/2
✓ Branch 0 taken 81468 times.
✓ Branch 1 taken 2778 times.
84246 if (x > 0) {
339
2/2
✓ Branch 0 taken 40551 times.
✓ Branch 1 taken 40917 times.
81468 if (carryover) curnew = color_add(curnew, carryover);
340 81468 uint32_t prev = color_add(lastnew, part);
341 // optimization: only set pixel if color has changed
342
2/2
✓ Branch 0 taken 56235 times.
✓ Branch 1 taken 25233 times.
81468 if (last != prev) setPixelColorXY(x - 1, row, prev);
343 2778 } else setPixelColorXY(x, row, curnew); // first pixel
344 84246 lastnew = curnew;
345 84246 last = cur; // save original value for comparison on next iteration
346 84246 carryover = part;
347 }
348 2778 setPixelColorXY(cols-1, row, curnew); // set last pixel
349 }
350 }
351
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 4 times.
228 if (blur_y) {
352
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
224 const uint8_t keepy = smear ? 255 : 255 - blur_y;
353 224 const uint8_t seepy = blur_y >> 1;
354
2/2
✓ Branch 0 taken 5618 times.
✓ Branch 1 taken 224 times.
5842 for (unsigned col = 0; col < cols; col++) {
355 5618 uint32_t carryover = BLACK;
356 5618 uint32_t curnew = BLACK;
357
2/2
✓ Branch 0 taken 84246 times.
✓ Branch 1 taken 5618 times.
89864 for (unsigned y = 0; y < rows; y++) {
358 84246 uint32_t cur = getPixelColorXY(col, y);
359 84246 uint32_t part = color_fade(cur, seepy);
360 84246 curnew = color_fade(cur, keepy);
361
2/2
✓ Branch 0 taken 78628 times.
✓ Branch 1 taken 5618 times.
84246 if (y > 0) {
362
2/2
✓ Branch 0 taken 40383 times.
✓ Branch 1 taken 38245 times.
78628 if (carryover) curnew = color_add(curnew, carryover);
363 78628 uint32_t prev = color_add(lastnew, part);
364 // optimization: only set pixel if color has changed
365
2/2
✓ Branch 0 taken 55036 times.
✓ Branch 1 taken 23592 times.
78628 if (last != prev) setPixelColorXY(col, y - 1, prev);
366 5618 } else setPixelColorXY(col, y, curnew); // first pixel
367 84246 lastnew = curnew;
368 84246 last = cur; //save original value for comparison on next iteration
369 84246 carryover = part;
370 }
371 5618 setPixelColorXY(col, rows - 1, curnew);
372 }
373 }
374 }
375
376 /*
377 // 2D Box blur
378 void Segment::box_blur(unsigned radius, bool smear) {
379 if (!isActive() || radius == 0) return; // not active
380 if (radius > 3) radius = 3;
381 const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor
382 const unsigned cols = vWidth();
383 const unsigned rows = vHeight();
384 uint16_t *tmpRSum = new uint16_t[cols*rows];
385 uint16_t *tmpGSum = new uint16_t[cols*rows];
386 uint16_t *tmpBSum = new uint16_t[cols*rows];
387 uint16_t *tmpWSum = new uint16_t[cols*rows];
388 // fill summed-area table (https://en.wikipedia.org/wiki/Summed-area_table)
389 for (unsigned x = 0; x < cols; x++) {
390 unsigned rS, gS, bS, wS;
391 unsigned index;
392 rS = gS = bS = wS = 0;
393 for (unsigned y = 0; y < rows; y++) {
394 index = x * cols + y;
395 if (x > 0) {
396 unsigned index2 = (x - 1) * cols + y;
397 tmpRSum[index] = tmpRSum[index2];
398 tmpGSum[index] = tmpGSum[index2];
399 tmpBSum[index] = tmpBSum[index2];
400 tmpWSum[index] = tmpWSum[index2];
401 } else {
402 tmpRSum[index] = 0;
403 tmpGSum[index] = 0;
404 tmpBSum[index] = 0;
405 tmpWSum[index] = 0;
406 }
407 uint32_t c = getPixelColorXY(x, y);
408 rS += R(c);
409 gS += G(c);
410 bS += B(c);
411 wS += W(c);
412 tmpRSum[index] += rS;
413 tmpGSum[index] += gS;
414 tmpBSum[index] += bS;
415 tmpWSum[index] += wS;
416 }
417 }
418 // do a box blur using pre-calculated sums
419 for (unsigned x = 0; x < cols; x++) {
420 for (unsigned y = 0; y < rows; y++) {
421 // sum = D + A - B - C where k = (x,y)
422 // +----+-+---- (x)
423 // | | |
424 // +----A-B
425 // | |k|
426 // +----C-D
427 // |
428 //(y)
429 unsigned x0 = x < radius ? 0 : x - radius;
430 unsigned y0 = y < radius ? 0 : y - radius;
431 unsigned x1 = x >= cols - radius ? cols - 1 : x + radius;
432 unsigned y1 = y >= rows - radius ? rows - 1 : y + radius;
433 unsigned A = x0 * cols + y0;
434 unsigned B = x1 * cols + y0;
435 unsigned C = x0 * cols + y1;
436 unsigned D = x1 * cols + y1;
437 unsigned r = tmpRSum[D] + tmpRSum[A] - tmpRSum[C] - tmpRSum[B];
438 unsigned g = tmpGSum[D] + tmpGSum[A] - tmpGSum[C] - tmpGSum[B];
439 unsigned b = tmpBSum[D] + tmpBSum[A] - tmpBSum[C] - tmpBSum[B];
440 unsigned w = tmpWSum[D] + tmpWSum[A] - tmpWSum[C] - tmpWSum[B];
441 setPixelColorXY(x, y, RGBW32(r/d, g/d, b/d, w/d));
442 }
443 }
444 delete[] tmpRSum;
445 delete[] tmpGSum;
446 delete[] tmpBSum;
447 delete[] tmpWSum;
448 }
449 */
450 17 void Segment::moveX(int delta, bool wrap) {
451
3/6
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 17 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 17 times.
17 if (!isActive() || !delta) return; // not active
452 17 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
453 17 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
454 17 int absDelta = abs(delta);
455
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (absDelta >= vW) return;
456 17 uint32_t newPxCol[vW];
457 int newDelta;
458 17 int stop = vW;
459 17 int start = 0;
460
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0
461 else {
462
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
17 if (delta < 0) start = absDelta;
463 17 stop = vW - absDelta;
464 17 newDelta = delta > 0 ? delta : 0;
465 }
466
2/2
✓ Branch 0 taken 207 times.
✓ Branch 1 taken 17 times.
224 for (int y = 0; y < vH; y++) {
467
2/2
✓ Branch 0 taken 6042 times.
✓ Branch 1 taken 207 times.
6249 for (int x = 0; x < stop; x++) {
468 6042 int srcX = x + newDelta;
469
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6042 times.
6042 if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
470
1/2
✓ Branch 0 taken 6042 times.
✗ Branch 1 not taken.
6042 newPxCol[x] = getPixelColorXY(srcX, y);
471 }
472
3/4
✓ Branch 0 taken 6042 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6042 times.
✓ Branch 3 taken 207 times.
6249 for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]);
473 }
474 17 }
475
476 10 void Segment::moveY(int delta, bool wrap) {
477
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
10 if (!isActive() || !delta) return; // not active
478 10 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
479 10 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
480 10 int absDelta = abs(delta);
481
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (absDelta >= vH) return;
482 10 uint32_t newPxCol[vH];
483 int newDelta;
484 10 int stop = vH;
485 10 int start = 0;
486
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 times.
10 if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0
487 else {
488
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (delta < 0) start = absDelta;
489 1 stop = vH - absDelta;
490 1 newDelta = delta > 0 ? delta : 0;
491 }
492
2/2
✓ Branch 0 taken 320 times.
✓ Branch 1 taken 10 times.
330 for (int x = 0; x < vW; x++) {
493
2/2
✓ Branch 0 taken 5088 times.
✓ Branch 1 taken 320 times.
5408 for (int y = 0; y < stop; y++) {
494 5088 int srcY = y + newDelta;
495
2/2
✓ Branch 0 taken 4608 times.
✓ Branch 1 taken 480 times.
5088 if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
496
1/2
✓ Branch 0 taken 5088 times.
✗ Branch 1 not taken.
5088 newPxCol[y] = getPixelColorXY(x, srcY);
497 }
498
3/4
✓ Branch 0 taken 5088 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5088 times.
✓ Branch 3 taken 320 times.
5408 for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]);
499 }
500 10 }
501
502 // move() - move all pixels in desired direction delta number of pixels
503 // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down
504 // @param delta number of pixels to move
505 // @param wrap around
506 27 void Segment::move(unsigned dir, unsigned delta, bool wrap) {
507
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (delta==0) return;
508
3/9
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
27 switch (dir) {
509 17 case 0: moveX( delta, wrap); break;
510 ✗ case 1: moveX( delta, wrap); moveY( delta, wrap); break;
511 1 case 2: moveY( delta, wrap); break;
512 ✗ case 3: moveX(-delta, wrap); moveY( delta, wrap); break;
513 ✗ case 4: moveX(-delta, wrap); break;
514 ✗ case 5: moveX(-delta, wrap); moveY(-delta, wrap); break;
515 9 case 6: moveY(-delta, wrap); break;
516 ✗ case 7: moveX( delta, wrap); moveY(-delta, wrap); break;
517 }
518 }
519
520 1 void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
521
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
1 if (!isActive() || radius == 0) return; // not active
522
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (soft) {
523 // Xiaolin Wu’s algorithm
524 1 const int rsq = radius*radius;
525 1 int x = 0;
526 1 int y = radius;
527 1 unsigned oldFade = 0;
528
2/2
✓ Branch 0 taken 181 times.
✓ Branch 1 taken 1 times.
182 while (x < y) {
529 181 float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
530 181 uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep
531
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 107 times.
181 if (oldFade > fade) y--;
532 181 oldFade = fade;
533 int px, py;
534
2/2
✓ Branch 0 taken 2896 times.
✓ Branch 1 taken 181 times.
3077 for (uint8_t i = 0; i < 16; i++) {
535 2896 int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
536 2896 int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
537
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1448 times.
2896 int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
538
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1448 times.
2896 int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
539
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1448 times.
2896 if (swaps) {
540 1448 px = cx + (y - adj) * dx;
541 1448 py = cy + x * dy;
542 } else {
543 1448 px = cx + x * dx;
544 1448 py = cy + (y - adj) * dy;
545 }
546 2896 uint32_t pixCol = getPixelColorXY(px, py);
547
2/2
✓ Branch 0 taken 1448 times.
✓ Branch 1 taken 1448 times.
5792 setPixelColorXY(px, py, adj ?
548 1448 color_blend(pixCol, col, fade) :
549 1448 color_blend(col, pixCol, fade));
550 }
551 181 x++;
552 }
553 } else {
554 // pre-scale color for all pixels
555 ✗ col = color_fade(col, _segBri);
556 ✗ _colorScaled = true;
557 // Bresenham’s Algorithm
558 ✗ int d = 3 - (2*radius);
559 ✗ int y = radius, x = 0;
560 ✗ while (y >= x) {
561 ✗ for (int i = 0; i < 4; i++) {
562 ✗ int dx = (i & 1) ? -x : x;
563 ✗ int dy = (i & 2) ? -y : y;
564 ✗ setPixelColorXY(cx + dx, cy + dy, col);
565 ✗ setPixelColorXY(cx + dy, cy + dx, col);
566 }
567 ✗ if (d > 0) {
568 ✗ d += 4 * (x - y) + 10;
569 ✗ y--;
570 } else {
571 ✗ d += 4 * x + 6;
572 }
573 ✗ x++;
574 }
575 ✗ _colorScaled = false;
576 }
577 }
578
579 // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
580 67 void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) {
581
3/6
✓ Branch 0 taken 67 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 67 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 67 times.
67 if (!isActive() || radius == 0) return; // not active
582 67 const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
583 67 const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
584 // draw soft bounding circle
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 if (soft) drawCircle(cx, cy, radius, col, soft);
586 // pre-scale color for all pixels
587 67 col = color_fade(col, _segBri);
588 67 _colorScaled = true;
589 // (r+0.5)^2 = r*r + r + 0.25; use the exact radius for a "soft" circle so the
590 // fill stays inside the anti-aliased edge drawn above by Wu's algorithm
591
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 67 times.
67 const int radius2 = soft ? radius * radius : radius * radius + radius;
592 // fill it
593
2/2
✓ Branch 0 taken 461 times.
✓ Branch 1 taken 67 times.
528 for (int y = -radius; y <= radius; y++) {
594
2/2
✓ Branch 0 taken 4067 times.
✓ Branch 1 taken 461 times.
4528 for (int x = -radius; x <= radius; x++) {
595
2/2
✓ Branch 0 taken 3179 times.
✓ Branch 1 taken 888 times.
4067 if (x * x + y * y < radius2 &&
596
4/4
✓ Branch 0 taken 3050 times.
✓ Branch 1 taken 129 times.
✓ Branch 2 taken 2835 times.
✓ Branch 3 taken 215 times.
3179 int(cx)+x >= 0 && int(cy)+y >= 0 &&
597
4/4
✓ Branch 0 taken 2535 times.
✓ Branch 1 taken 300 times.
✓ Branch 2 taken 2226 times.
✓ Branch 3 taken 309 times.
2835 int(cx)+x < vW && int(cy)+y < vH)
598 2226 setPixelColorXY(cx + x, cy + y, col);
599 }
600 }
601 67 _colorScaled = false;
602 }
603
604 //line function
605 ✗ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) {
606 ✗ if (!isActive()) return; // not active
607 ✗ const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
608 ✗ const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
609 ✗ if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return;
610
611 ✗ const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
612 ✗ const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
613
614 // single pixel (line length == 0)
615 ✗ if (dx+dy == 0) {
616 ✗ setPixelColorXY(x0, y0, c);
617 ✗ return;
618 }
619
620 ✗ if (soft) {
621 // Xiaolin Wu’s algorithm
622 ✗ const bool steep = dy > dx;
623 ✗ if (steep) {
624 // we need to go along longest dimension
625 ✗ std::swap(x0,y0);
626 ✗ std::swap(x1,y1);
627 }
628 ✗ if (x0 > x1) {
629 // we need to go in increasing fashion
630 ✗ std::swap(x0,x1);
631 ✗ std::swap(y0,y1);
632 }
633 ✗ float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
634 ✗ float intersectY = y0;
635 ✗ for (int x = x0; x <= x1; x++) {
636 ✗ uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
637 ✗ uint8_t seep = 0xFF - keep; // how much background to keep
638 ✗ int y = int(intersectY);
639 ✗ if (steep) std::swap(x,y); // temporaryly swap if steep
640 // pixel coverage is determined by fractional part of y co-ordinate
641 ✗ setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep));
642 ✗ setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep));
643 ✗ intersectY += gradient;
644 ✗ if (steep) std::swap(x,y); // restore if steep
645 }
646 } else {
647 // pre-scale color for all pixels
648 ✗ c = color_fade(c, _segBri);
649 ✗ _colorScaled = true;
650 // Bresenham's algorithm
651 ✗ int err = (dx>dy ? dx : -dy)/2; // error direction
652 for (;;) {
653 ✗ setPixelColorXY(x0, y0, c);
654 ✗ if (x0==x1 && y0==y1) break;
655 ✗ int e2 = err;
656 ✗ if (e2 >-dx) { err -= dy; x0 += sx; }
657 ✗ if (e2 < dy) { err += dx; y0 += sy; }
658 ✗ }
659 ✗ _colorScaled = false;
660 }
661 }
662
663 #include "font/console_font_4x6.h"
664 #include "font/console_font_5x8.h"
665 #include "font/console_font_5x12.h"
666 #include "font/console_font_6x8.h"
667 #include "font/console_font_7x9.h"
668
669 // draws a raster font character on canvas
670 // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
671 15426 void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) {
672
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15426 times.
15426 if (!isActive()) return; // not active
673
2/4
✓ Branch 0 taken 15426 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 15426 times.
15426 if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
674 15426 chr -= 32; // align with font table entries
675 15426 const int font = w*h;
676
677 15426 CRGB col = CRGB(color);
678
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 15426 times.
✓ Branch 2 taken 15426 times.
✗ Branch 3 not taken.
15426 CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
679
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15426 times.
15426 if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient
680
681 //if (w<5 || w>6 || h!=8) return;
682
2/2
✓ Branch 0 taken 123568 times.
✓ Branch 1 taken 15426 times.
138994 for (int i = 0; i<h; i++) { // character height
683 123568 uint8_t bits = 0;
684
3/6
✓ Branch 0 taken 480 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 122128 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 960 times.
✗ Branch 5 not taken.
123568 switch (font) {
685 480 case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font
686 ✗ case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font
687 122128 case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font
688 ✗ case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font
689 960 case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
690 ✗ default: return;
691 }
692
1/2
✓ Branch 0 taken 123568 times.
✗ Branch 1 not taken.
123568 CRGBW c = ColorFromPalette(grad, (i+1)*255/h, _segBri, LINEARBLEND_NOWRAP);
693 123568 _colorScaled = true;
694
2/2
✓ Branch 0 taken 739488 times.
✓ Branch 1 taken 123568 times.
863056 for (int j = 0; j<w; j++) { // character width
695 int x0, y0;
696
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 6720 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 732768 times.
739488 switch (rotate) {
697 ✗ case -1: x0 = x + (h-1) - i; y0 = y + (w-1) - j; break; // -90 deg
698 6720 case -2:
699 6720 case 2: x0 = x + j; y0 = y + (h-1) - i; break; // 180 deg
700 ✗ case 1: x0 = x + i; y0 = y + j; break; // +90 deg
701 732768 default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
702 }
703
9/10
✓ Branch 0 taken 739488 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 13216 times.
✓ Branch 3 taken 726272 times.
✓ Branch 4 taken 13186 times.
✓ Branch 5 taken 30 times.
✓ Branch 6 taken 48 times.
✓ Branch 7 taken 13138 times.
✓ Branch 8 taken 726350 times.
✓ Branch 9 taken 13138 times.
739488 if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
704
2/2
✓ Branch 0 taken 3702 times.
✓ Branch 1 taken 9436 times.
13138 if (((bits>>(j+(8-w))) & 0x01)) { // bit set
705
1/2
✓ Branch 0 taken 3702 times.
✗ Branch 1 not taken.
3702 setPixelColorXY(x0, y0, c.color32);
706 }
707 }
708 123568 _colorScaled = false;
709 }
710 }
711
712 #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
713 1307 void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
714
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1307 times.
1307 if (!isActive()) return; // not active
715 // extract the fractional parts and derive their inverses
716 1307 unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
717 // calculate the intensities for each affected pixel
718 1307 uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy),
719 1307 WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)};
720 // multiply the intensities by the colour, and saturating-add them to the pixels
721
2/2
✓ Branch 0 taken 5228 times.
✓ Branch 1 taken 1307 times.
6535 for (int i = 0; i < 4; i++) {
722 5228 int wu_x = (x >> 8) + (i & 1); // precalculate x
723 5228 int wu_y = (y >> 8) + ((i >> 1) & 1); // precalculate y
724
1/2
✓ Branch 0 taken 5228 times.
✗ Branch 1 not taken.
5228 CRGB led = getPixelColorXY(wu_x, wu_y);
725 5228 CRGB oldLed = led;
726
2/2
✓ Branch 0 taken 1052 times.
✓ Branch 1 taken 4176 times.
5228 led.r = qadd8(led.r, c.r * wu[i] >> 8);
727
2/2
✓ Branch 0 taken 290 times.
✓ Branch 1 taken 4938 times.
5228 led.g = qadd8(led.g, c.g * wu[i] >> 8);
728
4/4
✓ Branch 0 taken 464 times.
✓ Branch 1 taken 4764 times.
✓ Branch 2 taken 2440 times.
✓ Branch 3 taken 2788 times.
10456 led.b = qadd8(led.b, c.b * wu[i] >> 8);
729
3/4
✓ Branch 0 taken 3166 times.
✓ Branch 1 taken 2062 times.
✓ Branch 2 taken 3166 times.
✗ Branch 3 not taken.
5228 if (led != oldLed) setPixelColorXY(wu_x, wu_y, led); // don't repaint if same color
730 }
731 }
732 #undef WU_WEIGHT
733
734 #endif // WLED_DISABLE_2D
735