/* FX_2Dfcn.cpp contains all 2D utility functions LICENSE The MIT License (MIT) Copyright (c) 2022 Blaz Kristan (https://blaz.at/home) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Parts of the code adapted from WLED Sound Reactive */ #include "wled.h" #include "FX.h" #include "palettes.h" // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels // this converts physical (possibly irregular) LED arrangement into well defined // array of logical pixels: fist entry corresponds to left-topmost logical pixel // followed by horizontal pixels, when Segment::maxWidth logical pixels are added they // are followed by next row (down) of Segment::maxWidth pixels (and so forth) // note: matrix may be comprised of multiple panels each with different orientation // but ledmap takes care of that. ledmap is constructed upon initialization // so matrix should disable regular ledmap processing void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // isMatrix is set in cfg.cpp or set.cpp if (isMatrix) { // calculate width dynamically because it may have gaps Segment::maxWidth = 1; Segment::maxHeight = 1; for (size_t i = 0; i < panel.size(); i++) { Panel &p = panel[i]; if (p.xOffset + p.width > Segment::maxWidth) { Segment::maxWidth = p.xOffset + p.width; } if (p.yOffset + p.height > Segment::maxHeight) { Segment::maxHeight = p.yOffset + p.height; } } // safety check if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { DEBUG_PRINTLN(F("2D Bounds error.")); isMatrix = false; Segment::maxWidth = _length; Segment::maxHeight = 1; panels = 0; panel.clear(); // release memory allocated by panels resetSegments(); return; } customMappingSize = 0; // prevent use of mapping if anything goes wrong if (customMappingTable) delete[] customMappingTable; customMappingTable = new uint16_t[getLengthTotal()]; if (customMappingTable) { customMappingSize = getLengthTotal(); // fill with empty in case we don't fill the entire matrix unsigned matrixSize = Segment::maxWidth * Segment::maxHeight; for (unsigned i = 0; ias(); gapSize = map.size(); if (!map.isNull() && gapSize >= matrixSize) { // not an empty map gapTable = new int8_t[gapSize]; if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } } } DEBUG_PRINTLN(F("Gaps loaded.")); releaseJSONBufferLock(); } unsigned x, y, pix=0; //pixel for (size_t pan = 0; pan < panel.size(); pan++) { Panel &p = panel[pan]; unsigned h = p.vertical ? p.height : p.width; unsigned v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ for(size_t i = 0; i < h; i++) { y = (p.vertical?p.rightStart:p.bottomStart) ? v-j-1 : j; x = (p.vertical?p.bottomStart:p.rightStart) ? h-i-1 : i; x = p.serpentine && j%2 ? h-x-1 : x; size_t index = (p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x); if (!gapTable || (gapTable && gapTable[index] > 0)) customMappingTable[index] = pix; // a useful pixel (otherwise -1 is retained) if (!gapTable || (gapTable && gapTable[index] >= 0)) pix++; // not a missing pixel } } } // delete gap array as we no longer need it if (gapTable) delete[] gapTable; #ifdef WLED_DEBUG DEBUG_PRINT(F("Matrix ledmap:")); for (unsigned i=0; i= 1) return isActive() ? (x%width) + (y%height) * width : 0; } void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels int W = width(); int H = height(); if (x >= W || y >= H) return; // if pixel would fall out of segment just exit uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally int xX = (x+g), yY = (y+j); if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); #endif strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); } } } } #ifdef WLED_USE_AA_PIXELS // anti-aliased version of setPixelColorXY() void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) { if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); float fX = x * (cols-1); float fY = y * (rows-1); if (aa) { unsigned xL = roundf(fX-0.49f); unsigned xR = roundf(fX+0.49f); unsigned yT = roundf(fY-0.49f); unsigned yB = roundf(fY+0.49f); float dL = (fX - xL)*(fX - xL); float dR = (xR - fX)*(xR - fX); float dT = (fY - yT)*(fY - yT); float dB = (yB - fY)*(yB - fY); uint32_t cXLYT = getPixelColorXY(xL, yT); uint32_t cXRYT = getPixelColorXY(xR, yT); uint32_t cXLYB = getPixelColorXY(xL, yB); uint32_t cXRYB = getPixelColorXY(xR, yB); if (xL!=xR && yT!=yB) { setPixelColorXY(xL, yT, color_blend(col, cXLYT, uint8_t(sqrtf(dL*dT)*255.0f))); // blend TL pixel setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(sqrtf(dR*dT)*255.0f))); // blend TR pixel setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(sqrtf(dL*dB)*255.0f))); // blend BL pixel setPixelColorXY(xR, yB, color_blend(col, cXRYB, uint8_t(sqrtf(dR*dB)*255.0f))); // blend BR pixel } else if (xR!=xL && yT==yB) { setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dL*255.0f))); // blend L pixel setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(dR*255.0f))); // blend R pixel } else if (xR==xL && yT!=yB) { setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dT*255.0f))); // blend T pixel setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(dB*255.0f))); // blend B pixel } else { setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel) } } else { setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col); } } #endif // returns RGBW values of pixel uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels if (x >= width() || y >= height()) return 0; return strip.getPixelColorXY(start + x, startY + y); } // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ if (!isActive() || blur_amount == 0) return; // not active const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); if (row >= rows) return; // blur one row uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; uint32_t curnew = BLACK; for (unsigned x = 0; x < cols; x++) { uint32_t cur = getPixelColorXY(x, row); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (x > 0) { if (carryover) curnew = color_add(curnew, carryover, true); uint32_t prev = color_add(lastnew, part, true); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(x - 1, row, prev); } else // first pixel setPixelColorXY(x, row, curnew); lastnew = curnew; last = cur; // save original value for comparison on next iteration carryover = part; } setPixelColorXY(cols-1, row, curnew); // set last pixel } // blurCol: perform a blur on a column of a rectangular matrix void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); if (col >= cols) return; // blur one column uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; uint32_t curnew = BLACK; for (unsigned y = 0; y < rows; y++) { uint32_t cur = getPixelColorXY(col, y); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (y > 0) { if (carryover) curnew = color_add(curnew, carryover, true); uint32_t prev = color_add(lastnew, part, true); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(col, y - 1, prev); } else // first pixel setPixelColorXY(col, y, curnew); lastnew = curnew; last = cur; //save original value for comparison on next iteration carryover = part; } setPixelColorXY(col, rows - 1, curnew); } void Segment::blur2D(uint8_t blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); const uint8_t keep = smear ? 255 : 255 - blur_amount; const uint8_t seep = blur_amount >> (1 + smear); uint32_t lastnew; uint32_t last; for (unsigned row = 0; row < rows; row++) { uint32_t carryover = BLACK; uint32_t curnew = BLACK; for (unsigned x = 0; x < cols; x++) { uint32_t cur = getPixelColorXY(x, row); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (x > 0) { if (carryover) curnew = color_add(curnew, carryover, true); uint32_t prev = color_add(lastnew, part, true); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(x - 1, row, prev); } else setPixelColorXY(x, row, curnew); // first pixel lastnew = curnew; last = cur; // save original value for comparison on next iteration carryover = part; } setPixelColorXY(cols-1, row, curnew); // set last pixel } for (unsigned col = 0; col < cols; col++) { uint32_t carryover = BLACK; uint32_t curnew = BLACK; for (unsigned y = 0; y < rows; y++) { uint32_t cur = getPixelColorXY(col, y); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (y > 0) { if (carryover) curnew = color_add(curnew, carryover, true); uint32_t prev = color_add(lastnew, part, true); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(col, y - 1, prev); } else setPixelColorXY(col, y, curnew); // first pixel lastnew = curnew; last = cur; //save original value for comparison on next iteration carryover = part; } setPixelColorXY(col, rows - 1, curnew); } } // 2D Box blur void Segment::box_blur(unsigned radius, bool smear) { if (!isActive() || radius == 0) return; // not active if (radius > 3) radius = 3; const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor const unsigned cols = virtualWidth(); const unsigned rows = virtualHeight(); uint16_t *tmpRSum = new uint16_t[cols*rows]; uint16_t *tmpGSum = new uint16_t[cols*rows]; uint16_t *tmpBSum = new uint16_t[cols*rows]; uint16_t *tmpWSum = new uint16_t[cols*rows]; // fill summed-area table (https://en.wikipedia.org/wiki/Summed-area_table) for (unsigned x = 0; x < cols; x++) { unsigned rS, gS, bS, wS; unsigned index; rS = gS = bS = wS = 0; for (unsigned y = 0; y < rows; y++) { index = x * cols + y; if (x > 0) { unsigned index2 = (x - 1) * cols + y; tmpRSum[index] = tmpRSum[index2]; tmpGSum[index] = tmpGSum[index2]; tmpBSum[index] = tmpBSum[index2]; tmpWSum[index] = tmpWSum[index2]; } else { tmpRSum[index] = 0; tmpGSum[index] = 0; tmpBSum[index] = 0; tmpWSum[index] = 0; } uint32_t c = getPixelColorXY(x, y); rS += R(c); gS += G(c); bS += B(c); wS += W(c); tmpRSum[index] += rS; tmpGSum[index] += gS; tmpBSum[index] += bS; tmpWSum[index] += wS; } } // do a box blur using pre-calculated sums for (unsigned x = 0; x < cols; x++) { for (unsigned y = 0; y < rows; y++) { // sum = D + A - B - C where k = (x,y) // +----+-+---- (x) // | | | // +----A-B // | |k| // +----C-D // | //(y) unsigned x0 = x < radius ? 0 : x - radius; unsigned y0 = y < radius ? 0 : y - radius; unsigned x1 = x >= cols - radius ? cols - 1 : x + radius; unsigned y1 = y >= rows - radius ? rows - 1 : y + radius; unsigned A = x0 * cols + y0; unsigned B = x1 * cols + y0; unsigned C = x0 * cols + y1; unsigned D = x1 * cols + y1; unsigned r = tmpRSum[D] + tmpRSum[A] - tmpRSum[C] - tmpRSum[B]; unsigned g = tmpGSum[D] + tmpGSum[A] - tmpGSum[C] - tmpGSum[B]; unsigned b = tmpBSum[D] + tmpBSum[A] - tmpBSum[C] - tmpBSum[B]; unsigned w = tmpWSum[D] + tmpWSum[A] - tmpWSum[C] - tmpWSum[B]; setPixelColorXY(x, y, RGBW32(r/d, g/d, b/d, w/d)); } } delete[] tmpRSum; delete[] tmpGSum; delete[] tmpBSum; delete[] tmpWSum; } void Segment::moveX(int8_t delta, bool wrap) { if (!isActive()) return; // not active const int cols = virtualWidth(); const int rows = virtualHeight(); if (!delta || abs(delta) >= cols) return; uint32_t newPxCol[cols]; for (int y = 0; y < rows; y++) { if (delta > 0) { for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); } else { for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); } for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); } } void Segment::moveY(int8_t delta, bool wrap) { if (!isActive()) return; // not active const int cols = virtualWidth(); const int rows = virtualHeight(); if (!delta || abs(delta) >= rows) return; uint32_t newPxCol[rows]; for (int x = 0; x < cols; x++) { if (delta > 0) { for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); } else { for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); } for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); } } // move() - move all pixels in desired direction delta number of pixels // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move // @param wrap around void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { if (delta==0) return; switch (dir) { case 0: moveX( delta, wrap); break; case 1: moveX( delta, wrap); moveY( delta, wrap); break; case 2: moveY( delta, wrap); break; case 3: moveX(-delta, wrap); moveY( delta, wrap); break; case 4: moveX(-delta, wrap); break; case 5: moveX(-delta, wrap); moveY(-delta, wrap); break; case 6: moveY(-delta, wrap); break; case 7: moveX( delta, wrap); moveY(-delta, wrap); break; } } void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { if (!isActive() || radius == 0) return; // not active if (soft) { // Xiaolin Wu’s algorithm int rsq = radius*radius; int x = 0; int y = radius; unsigned oldFade = 0; while (x < y) { float yf = sqrtf(float(rsq - x*x)); // needs to be floating point unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep if (oldFade > fade) y--; oldFade = fade; setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); x++; } } else { // Bresenham’s Algorithm int d = 3 - (2*radius); int y = radius, x = 0; while (y >= x) { setPixelColorXY(cx+x, cy+y, col); setPixelColorXY(cx-x, cy+y, col); setPixelColorXY(cx+x, cy-y, col); setPixelColorXY(cx-x, cy-y, col); setPixelColorXY(cx+y, cy+x, col); setPixelColorXY(cx-y, cy+x, col); setPixelColorXY(cx+y, cy-x, col); setPixelColorXY(cx-y, cy-x, col); x++; if (d > 0) { y--; d += 4 * (x - y) + 10; } else { d += 4 * x + 6; } } } } // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { if (!isActive() || radius == 0) return; // not active // draw soft bounding circle if (soft) drawCircle(cx, cy, radius, col, soft); // fill it const int cols = virtualWidth(); const int rows = virtualHeight(); for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius && int(cx)+x>=0 && int(cy)+y>=0 && int(cx)+x= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; const int dx = abs(x1-x0), sx = x0 dx; if (steep) { // we need to go along longest dimension std::swap(x0,y0); std::swap(x1,y1); } if (x0 > x1) { // we need to go in increasing fashion std::swap(x0,x1); std::swap(y0,y1); } float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); float intersectY = y0; for (int x = x0; x <= x1; x++) { unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep unsigned seep = 0xFFFF - keep; // how much background to keep int y = int(intersectY); if (steep) std::swap(x,y); // temporaryly swap if steep // pixel coverage is determined by fractional part of y co-ordinate setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); intersectY += gradient; if (steep) std::swap(x,y); // restore if steep } } else { // Bresenham's algorithm int err = (dx>dy ? dx : -dy)/2; // error direction for (;;) { setPixelColorXY(x0, y0, c); if (x0==x1 && y0==y1) break; int e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } } } #include "src/font/console_font_4x6.h" #include "src/font/console_font_5x8.h" #include "src/font/console_font_5x12.h" #include "src/font/console_font_6x8.h" #include "src/font/console_font_7x9.h" // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM 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) { if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries const int cols = virtualWidth(); const int rows = virtualHeight(); const int font = w*h; CRGB col = CRGB(color); CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set setPixelColorXY(x0, y0, col); } } } } #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu if (!isActive()) return; // not active // extract the fractional parts and derive their inverses unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; // calculate the intensities for each affected pixel uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy), WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; // multiply the intensities by the colour, and saturating-add them to the pixels for (int i = 0; i < 4; i++) { CRGB led = getPixelColorXY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1)); led.r = qadd8(led.r, c.r * wu[i] >> 8); led.g = qadd8(led.g, c.g * wu[i] >> 8); led.b = qadd8(led.b, c.b * wu[i] >> 8); setPixelColorXY(int((x >> 8) + (i & 1)), int((y >> 8) + ((i >> 1) & 1)), led); } } #undef WU_WEIGHT #endif // WLED_DISABLE_2D