| 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 |