2022-05-08 08:50:48 +00:00
|
|
|
|
/*
|
|
|
|
|
FX_2Dfcn.cpp contains all 2D utility functions
|
2023-01-06 08:10:39 +00:00
|
|
|
|
|
2022-05-08 08:50:48 +00:00
|
|
|
|
Copyright (c) 2022 Blaz Kristan (https://blaz.at/home)
|
2024-10-15 22:07:19 +00:00
|
|
|
|
Licensed under the EUPL v. 1.2 or later
|
|
|
|
|
Adapted from code originally licensed under the MIT license
|
2022-05-08 08:50:48 +00:00
|
|
|
|
|
|
|
|
|
Parts of the code adapted from WLED Sound Reactive
|
|
|
|
|
*/
|
|
|
|
|
#include "wled.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
|
2022-12-16 21:31:07 +00:00
|
|
|
|
// 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)
|
2022-05-08 08:50:48 +00:00
|
|
|
|
// 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() {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
#ifndef WLED_DISABLE_2D
|
2022-12-16 21:31:07 +00:00
|
|
|
|
// isMatrix is set in cfg.cpp or set.cpp
|
2022-05-08 08:50:48 +00:00
|
|
|
|
if (isMatrix) {
|
2023-12-28 22:32:47 +00:00
|
|
|
|
// calculate width dynamically because it may have gaps
|
2023-01-02 19:56:00 +00:00
|
|
|
|
Segment::maxWidth = 1;
|
|
|
|
|
Segment::maxHeight = 1;
|
2025-04-22 20:37:18 +00:00
|
|
|
|
for (const Panel &p : panel) {
|
2023-01-02 19:56:00 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-08 08:50:48 +00:00
|
|
|
|
|
|
|
|
|
// safety check
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth > 255 || Segment::maxHeight > 255 || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) {
|
2023-01-02 19:56:00 +00:00
|
|
|
|
DEBUG_PRINTLN(F("2D Bounds error."));
|
2022-05-08 08:50:48 +00:00
|
|
|
|
isMatrix = false;
|
2023-01-02 19:56:00 +00:00
|
|
|
|
Segment::maxWidth = _length;
|
|
|
|
|
Segment::maxHeight = 1;
|
|
|
|
|
panel.clear(); // release memory allocated by panels
|
2025-04-22 20:37:18 +00:00
|
|
|
|
panel.shrink_to_fit(); // release memory if allocated
|
2023-01-22 10:29:31 +00:00
|
|
|
|
resetSegments();
|
2022-05-08 08:50:48 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 20:37:18 +00:00
|
|
|
|
suspend();
|
|
|
|
|
waitForIt();
|
|
|
|
|
|
2023-12-28 22:32:47 +00:00
|
|
|
|
customMappingSize = 0; // prevent use of mapping if anything goes wrong
|
|
|
|
|
|
2025-04-22 20:37:18 +00:00
|
|
|
|
d_free(customMappingTable);
|
|
|
|
|
customMappingTable = static_cast<uint16_t*>(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM
|
2022-05-08 08:50:48 +00:00
|
|
|
|
|
2024-04-09 06:25:07 +00:00
|
|
|
|
if (customMappingTable) {
|
2023-12-28 22:32:47 +00:00
|
|
|
|
customMappingSize = getLengthTotal();
|
2022-05-08 08:50:48 +00:00
|
|
|
|
|
2023-01-02 19:56:00 +00:00
|
|
|
|
// fill with empty in case we don't fill the entire matrix
|
2023-12-28 22:32:47 +00:00
|
|
|
|
unsigned matrixSize = Segment::maxWidth * Segment::maxHeight;
|
|
|
|
|
for (unsigned i = 0; i<matrixSize; i++) customMappingTable[i] = 0xFFFFU;
|
|
|
|
|
for (unsigned i = matrixSize; i<getLengthTotal(); i++) customMappingTable[i] = i; // trailing LEDs for ledmap (after matrix) if it exist
|
2022-05-08 08:50:48 +00:00
|
|
|
|
|
2023-02-10 18:49:43 +00:00
|
|
|
|
// we will try to load a "gap" array (a JSON file)
|
|
|
|
|
// the array has to have the same amount of values as mapping array (or larger)
|
|
|
|
|
// "gap" array is used while building ledmap (mapping array)
|
|
|
|
|
// and discarded afterwards as it has no meaning after the process
|
|
|
|
|
// content of the file is just raw JSON array in the form of [val1,val2,val3,...]
|
|
|
|
|
// there are no other "key":"value" pairs in it
|
|
|
|
|
// allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
|
2025-01-09 12:29:06 +00:00
|
|
|
|
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json"));
|
2023-02-10 18:49:43 +00:00
|
|
|
|
bool isFile = WLED_FS.exists(fileName);
|
2023-02-11 17:41:30 +00:00
|
|
|
|
size_t gapSize = 0;
|
2023-02-10 18:49:43 +00:00
|
|
|
|
int8_t *gapTable = nullptr;
|
2023-02-09 19:15:55 +00:00
|
|
|
|
|
|
|
|
|
if (isFile && requestJSONBufferLock(20)) {
|
|
|
|
|
DEBUG_PRINT(F("Reading LED gap from "));
|
|
|
|
|
DEBUG_PRINTLN(fileName);
|
2023-02-10 18:49:43 +00:00
|
|
|
|
// read the array into global JSON buffer
|
2023-12-21 20:30:17 +00:00
|
|
|
|
if (readObjectFromFile(fileName, nullptr, pDoc)) {
|
2023-02-09 19:15:55 +00:00
|
|
|
|
// the array is similar to ledmap, except it has only 3 values:
|
|
|
|
|
// -1 ... missing pixel (do not increase pixel count)
|
2023-02-10 18:49:43 +00:00
|
|
|
|
// 0 ... inactive pixel (it does count, but should be mapped out (-1))
|
|
|
|
|
// 1 ... active pixel (it will count and will be mapped)
|
2023-12-21 20:30:17 +00:00
|
|
|
|
JsonArray map = pDoc->as<JsonArray>();
|
2023-02-09 19:15:55 +00:00
|
|
|
|
gapSize = map.size();
|
2023-12-28 22:32:47 +00:00
|
|
|
|
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
|
2025-04-26 18:08:15 +00:00
|
|
|
|
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
|
2023-02-10 18:49:43 +00:00
|
|
|
|
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
|
2023-02-09 19:15:55 +00:00
|
|
|
|
gapTable[i] = constrain(map[i], -1, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-11 17:41:30 +00:00
|
|
|
|
DEBUG_PRINTLN(F("Gaps loaded."));
|
2023-02-09 19:15:55 +00:00
|
|
|
|
releaseJSONBufferLock();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 18:11:46 +00:00
|
|
|
|
unsigned x, y, pix=0; //pixel
|
2025-04-22 20:37:18 +00:00
|
|
|
|
for (const Panel &p : panel) {
|
2024-04-26 18:11:46 +00:00
|
|
|
|
unsigned h = p.vertical ? p.height : p.width;
|
|
|
|
|
unsigned v = p.vertical ? p.width : p.height;
|
2023-01-02 19:56:00 +00:00
|
|
|
|
for (size_t j = 0; j < v; j++){
|
2023-02-09 19:15:55 +00:00
|
|
|
|
for(size_t i = 0; i < h; i++) {
|
2023-01-02 19:56:00 +00:00
|
|
|
|
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;
|
2023-02-09 19:15:55 +00:00
|
|
|
|
size_t index = (p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x);
|
2023-02-10 18:49:43 +00:00
|
|
|
|
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
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-02 19:56:00 +00:00
|
|
|
|
|
2023-02-10 18:49:43 +00:00
|
|
|
|
// delete gap array as we no longer need it
|
2025-04-26 18:08:15 +00:00
|
|
|
|
p_free(gapTable);
|
2025-04-22 20:37:18 +00:00
|
|
|
|
resume();
|
2023-02-09 19:15:55 +00:00
|
|
|
|
|
2022-05-08 08:50:48 +00:00
|
|
|
|
#ifdef WLED_DEBUG
|
|
|
|
|
DEBUG_PRINT(F("Matrix ledmap:"));
|
2023-09-10 16:52:14 +00:00
|
|
|
|
for (unsigned i=0; i<customMappingSize; i++) {
|
2022-12-16 21:31:07 +00:00
|
|
|
|
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
|
2024-02-17 10:33:42 +00:00
|
|
|
|
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
DEBUG_PRINTLN();
|
|
|
|
|
#endif
|
2022-12-22 17:13:32 +00:00
|
|
|
|
} else { // memory allocation error
|
2024-04-09 06:25:07 +00:00
|
|
|
|
DEBUG_PRINTLN(F("ERROR 2D LED map allocation error."));
|
2022-05-08 08:50:48 +00:00
|
|
|
|
isMatrix = false;
|
2023-01-02 19:56:00 +00:00
|
|
|
|
panel.clear();
|
|
|
|
|
Segment::maxWidth = _length;
|
|
|
|
|
Segment::maxHeight = 1;
|
2023-02-14 16:11:58 +00:00
|
|
|
|
resetSegments();
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-22 17:13:32 +00:00
|
|
|
|
#else
|
|
|
|
|
isMatrix = false; // no matter what config says
|
2022-07-10 20:23:25 +00:00
|
|
|
|
#endif
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-09 22:35:26 +00:00
|
|
|
|
|
2022-07-10 20:23:25 +00:00
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
// Segment:: routines
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
2022-08-03 12:23:24 +00:00
|
|
|
|
#ifndef WLED_DISABLE_2D
|
2025-04-22 20:37:18 +00:00
|
|
|
|
// pixel is clipped if it falls outside clipping range
|
2024-04-03 16:38:06 +00:00
|
|
|
|
// if clipping start > stop the clipping range is inverted
|
2024-09-11 15:28:48 +00:00
|
|
|
|
bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const {
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) {
|
|
|
|
|
const bool invertX = _clipStart > _clipStop;
|
2025-01-14 21:39:20 +00:00
|
|
|
|
const bool invertY = _clipStartY > _clipStopY;
|
2025-04-22 20:37:18 +00:00
|
|
|
|
const int cStartX = invertX ? _clipStop : _clipStart;
|
|
|
|
|
const int cStopX = invertX ? _clipStart : _clipStop;
|
|
|
|
|
const int cStartY = invertY ? _clipStopY : _clipStartY;
|
|
|
|
|
const int cStopY = invertY ? _clipStartY : _clipStopY;
|
2024-04-03 16:38:06 +00:00
|
|
|
|
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
|
2025-04-22 20:37:18 +00:00
|
|
|
|
const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth())
|
|
|
|
|
const unsigned len = width * (cStopY - cStartY); // assumes full segment height (faster than virtualHeight())
|
2024-04-03 16:38:06 +00:00
|
|
|
|
if (len < 2) return false;
|
|
|
|
|
const unsigned shuffled = hashInt(x + y * width) % len;
|
|
|
|
|
const unsigned pos = (shuffled * 0xFFFFU) / len;
|
2025-04-22 20:37:18 +00:00
|
|
|
|
return progress() <= pos;
|
2024-04-03 16:38:06 +00:00
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) {
|
|
|
|
|
const int cx = (cStopX-cStartX+1) / 2;
|
|
|
|
|
const int cy = (cStopY-cStartY+1) / 2;
|
|
|
|
|
const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT);
|
|
|
|
|
const unsigned prog = out ? progress() : 0xFFFFU - progress();
|
|
|
|
|
int radius2 = max(cx, cy) * prog / 0xFFFF;
|
|
|
|
|
radius2 = 2 * radius2 * radius2;
|
|
|
|
|
if (radius2 == 0) return out;
|
|
|
|
|
const int dx = x - cx;
|
|
|
|
|
const int dy = y - cy;
|
|
|
|
|
const bool outside = dx * dx + dy * dy > radius2;
|
|
|
|
|
return out ? outside : !outside;
|
|
|
|
|
}
|
|
|
|
|
bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside;
|
|
|
|
|
bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside;
|
|
|
|
|
const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside;
|
2024-04-03 16:38:06 +00:00
|
|
|
|
return !clip;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-07 19:33:10 +00:00
|
|
|
|
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
|
2022-05-08 08:50:48 +00:00
|
|
|
|
{
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit
|
|
|
|
|
setPixelColorXYRaw(x, y, col);
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-13 16:25:25 +00:00
|
|
|
|
#ifdef WLED_USE_AA_PIXELS
|
2022-06-13 19:55:51 +00:00
|
|
|
|
// anti-aliased version of setPixelColorXY()
|
2025-01-09 12:29:06 +00:00
|
|
|
|
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
|
2022-06-13 19:55:51 +00:00
|
|
|
|
{
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-06-13 19:55:51 +00:00
|
|
|
|
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
|
|
|
|
|
|
2024-09-28 16:14:43 +00:00
|
|
|
|
float fX = x * (vWidth()-1);
|
|
|
|
|
float fY = y * (vHeight()-1);
|
2022-06-13 19:55:51 +00:00
|
|
|
|
if (aa) {
|
2024-04-26 18:11:46 +00:00
|
|
|
|
unsigned xL = roundf(fX-0.49f);
|
|
|
|
|
unsigned xR = roundf(fX+0.49f);
|
|
|
|
|
unsigned yT = roundf(fY-0.49f);
|
|
|
|
|
unsigned yB = roundf(fY+0.49f);
|
2022-08-30 15:20:58 +00:00
|
|
|
|
float dL = (fX - xL)*(fX - xL);
|
|
|
|
|
float dR = (xR - fX)*(xR - fX);
|
|
|
|
|
float dT = (fY - yT)*(fY - yT);
|
|
|
|
|
float dB = (yB - fY)*(yB - fY);
|
2022-06-13 19:55:51 +00:00
|
|
|
|
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) {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
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
|
2022-06-13 19:55:51 +00:00
|
|
|
|
} else if (xR!=xL && yT==yB) {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
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
|
2022-06-13 19:55:51 +00:00
|
|
|
|
} else if (xR==xL && yT!=yB) {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
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
|
2022-06-13 19:55:51 +00:00
|
|
|
|
} else {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel)
|
2022-06-13 19:55:51 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2022-07-10 20:23:25 +00:00
|
|
|
|
setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col);
|
2022-06-13 19:55:51 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-13 16:25:25 +00:00
|
|
|
|
#endif
|
2022-06-13 19:55:51 +00:00
|
|
|
|
|
2022-05-30 20:21:13 +00:00
|
|
|
|
// returns RGBW values of pixel
|
2024-09-04 09:38:03 +00:00
|
|
|
|
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return 0; // not active
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
|
|
|
|
|
return getPixelColorXYRaw(x,y);
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 19:19:34 +00:00
|
|
|
|
// 2D blurring, can be asymmetrical
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const unsigned cols = vWidth();
|
|
|
|
|
const unsigned rows = vHeight();
|
2025-04-22 20:37:18 +00:00
|
|
|
|
const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; };
|
2025-04-26 18:08:15 +00:00
|
|
|
|
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
|
2024-08-14 20:15:48 +00:00
|
|
|
|
uint32_t last;
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (blur_x) {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
const uint8_t keepx = smear ? 255 : 255 - blur_x;
|
2025-04-26 18:08:15 +00:00
|
|
|
|
const uint8_t seepx = blur_x >> 1;
|
2024-10-03 19:19:34 +00:00
|
|
|
|
for (unsigned row = 0; row < rows; row++) { // blur rows (x direction)
|
|
|
|
|
uint32_t carryover = BLACK;
|
|
|
|
|
uint32_t curnew = BLACK;
|
|
|
|
|
for (unsigned x = 0; x < cols; x++) {
|
2025-04-22 20:37:18 +00:00
|
|
|
|
uint32_t cur = getPixelColorRaw(XY(x, row));
|
2024-10-03 19:19:34 +00:00
|
|
|
|
uint32_t part = color_fade(cur, seepx);
|
|
|
|
|
curnew = color_fade(cur, keepx);
|
|
|
|
|
if (x > 0) {
|
|
|
|
|
if (carryover) curnew = color_add(curnew, carryover);
|
|
|
|
|
uint32_t prev = color_add(lastnew, part);
|
|
|
|
|
// optimization: only set pixel if color has changed
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (last != prev) setPixelColorRaw(XY(x - 1, row), prev);
|
|
|
|
|
} else setPixelColorRaw(XY(x, row), curnew); // first pixel
|
2024-10-03 19:19:34 +00:00
|
|
|
|
lastnew = curnew;
|
|
|
|
|
last = cur; // save original value for comparison on next iteration
|
|
|
|
|
carryover = part;
|
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel
|
2024-08-14 20:15:48 +00:00
|
|
|
|
}
|
2022-05-27 11:39:22 +00:00
|
|
|
|
}
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (blur_y) {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
const uint8_t keepy = smear ? 255 : 255 - blur_y;
|
2025-04-26 18:08:15 +00:00
|
|
|
|
const uint8_t seepy = blur_y >> 1;
|
2024-10-03 19:19:34 +00:00
|
|
|
|
for (unsigned col = 0; col < cols; col++) {
|
|
|
|
|
uint32_t carryover = BLACK;
|
|
|
|
|
uint32_t curnew = BLACK;
|
|
|
|
|
for (unsigned y = 0; y < rows; y++) {
|
2025-04-22 20:37:18 +00:00
|
|
|
|
uint32_t cur = getPixelColorRaw(XY(col, y));
|
2024-10-03 19:19:34 +00:00
|
|
|
|
uint32_t part = color_fade(cur, seepy);
|
|
|
|
|
curnew = color_fade(cur, keepy);
|
|
|
|
|
if (y > 0) {
|
|
|
|
|
if (carryover) curnew = color_add(curnew, carryover);
|
|
|
|
|
uint32_t prev = color_add(lastnew, part);
|
|
|
|
|
// optimization: only set pixel if color has changed
|
2025-04-22 20:37:18 +00:00
|
|
|
|
if (last != prev) setPixelColorRaw(XY(col, y - 1), prev);
|
|
|
|
|
} else setPixelColorRaw(XY(col, y), curnew); // first pixel
|
2024-10-03 19:19:34 +00:00
|
|
|
|
lastnew = curnew;
|
|
|
|
|
last = cur; //save original value for comparison on next iteration
|
|
|
|
|
carryover = part;
|
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
setPixelColorRaw(XY(col, rows - 1), curnew);
|
2024-08-14 20:15:48 +00:00
|
|
|
|
}
|
2024-05-09 22:02:28 +00:00
|
|
|
|
}
|
2024-08-14 20:15:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 19:19:34 +00:00
|
|
|
|
/*
|
2024-08-14 20:15:48 +00:00
|
|
|
|
// 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
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const unsigned cols = vWidth();
|
|
|
|
|
const unsigned rows = vHeight();
|
2024-08-14 20:15:48 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2024-05-09 22:02:28 +00:00
|
|
|
|
}
|
2024-08-14 20:15:48 +00:00
|
|
|
|
// 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));
|
|
|
|
|
}
|
2022-05-27 11:39:22 +00:00
|
|
|
|
}
|
2024-08-14 20:15:48 +00:00
|
|
|
|
delete[] tmpRSum;
|
|
|
|
|
delete[] tmpGSum;
|
|
|
|
|
delete[] tmpBSum;
|
|
|
|
|
delete[] tmpWSum;
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
2024-10-03 19:19:34 +00:00
|
|
|
|
*/
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::moveX(int delta, bool wrap) const {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
if (!isActive() || !delta) return; // not active
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
|
|
|
|
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
|
2025-04-22 20:37:18 +00:00
|
|
|
|
const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
|
2024-10-03 19:19:34 +00:00
|
|
|
|
int absDelta = abs(delta);
|
|
|
|
|
if (absDelta >= vW) return;
|
2024-09-28 16:14:43 +00:00
|
|
|
|
uint32_t newPxCol[vW];
|
2024-10-03 19:19:34 +00:00
|
|
|
|
int newDelta;
|
|
|
|
|
int stop = vW;
|
|
|
|
|
int start = 0;
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0
|
2024-10-03 19:19:34 +00:00
|
|
|
|
else {
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (delta < 0) start = absDelta;
|
2024-10-03 19:19:34 +00:00
|
|
|
|
stop = vW - absDelta;
|
|
|
|
|
newDelta = delta > 0 ? delta : 0;
|
|
|
|
|
}
|
2024-09-28 16:14:43 +00:00
|
|
|
|
for (int y = 0; y < vH; y++) {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
for (int x = 0; x < stop; x++) {
|
2024-10-05 21:31:31 +00:00
|
|
|
|
int srcX = x + newDelta;
|
|
|
|
|
if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true
|
2025-04-22 20:37:18 +00:00
|
|
|
|
newPxCol[x] = getPixelColorRaw(XY(srcX, y));
|
2022-05-19 16:27:04 +00:00
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
for (int x = 0; x < stop; x++) setPixelColorRaw(XY(x + start, y), newPxCol[x]);
|
2022-05-19 16:27:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::moveY(int delta, bool wrap) const {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
if (!isActive() || !delta) return; // not active
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
|
|
|
|
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
|
2025-04-22 20:37:18 +00:00
|
|
|
|
const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; };
|
2024-10-03 19:19:34 +00:00
|
|
|
|
int absDelta = abs(delta);
|
|
|
|
|
if (absDelta >= vH) return;
|
2024-09-28 16:14:43 +00:00
|
|
|
|
uint32_t newPxCol[vH];
|
2024-10-03 19:19:34 +00:00
|
|
|
|
int newDelta;
|
|
|
|
|
int stop = vH;
|
|
|
|
|
int start = 0;
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0
|
2024-10-03 19:19:34 +00:00
|
|
|
|
else {
|
2024-10-05 21:31:31 +00:00
|
|
|
|
if (delta < 0) start = absDelta;
|
2024-10-03 19:19:34 +00:00
|
|
|
|
stop = vH - absDelta;
|
|
|
|
|
newDelta = delta > 0 ? delta : 0;
|
|
|
|
|
}
|
2024-09-28 16:14:43 +00:00
|
|
|
|
for (int x = 0; x < vW; x++) {
|
2024-10-03 19:19:34 +00:00
|
|
|
|
for (int y = 0; y < stop; y++) {
|
2024-10-05 21:31:31 +00:00
|
|
|
|
int srcY = y + newDelta;
|
|
|
|
|
if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true
|
2025-04-22 20:37:18 +00:00
|
|
|
|
newPxCol[y] = getPixelColorRaw(XY(x, srcY));
|
2022-05-19 16:27:04 +00:00
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
for (int y = 0; y < stop; y++) setPixelColorRaw(XY(x, y + start), newPxCol[y]);
|
2022-05-19 16:27:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-30 20:21:13 +00:00
|
|
|
|
// 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
|
2023-04-27 15:31:55 +00:00
|
|
|
|
// @param wrap around
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::move(unsigned dir, unsigned delta, bool wrap) const {
|
2022-05-30 20:21:13 +00:00
|
|
|
|
if (delta==0) return;
|
|
|
|
|
switch (dir) {
|
2023-04-27 15:31:55 +00:00
|
|
|
|
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;
|
2022-05-30 20:21:13 +00:00
|
|
|
|
}
|
2022-05-08 08:50:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const {
|
2023-09-10 16:52:14 +00:00
|
|
|
|
if (!isActive() || radius == 0) return; // not active
|
2024-05-09 22:02:28 +00:00
|
|
|
|
if (soft) {
|
|
|
|
|
// Xiaolin Wu’s algorithm
|
2024-12-19 19:19:42 +00:00
|
|
|
|
const int rsq = radius*radius;
|
2024-05-09 22:02:28 +00:00
|
|
|
|
int x = 0;
|
|
|
|
|
int y = radius;
|
|
|
|
|
unsigned oldFade = 0;
|
|
|
|
|
while (x < y) {
|
|
|
|
|
float yf = sqrtf(float(rsq - x*x)); // needs to be floating point
|
2024-12-19 19:19:42 +00:00
|
|
|
|
uint8_t fade = float(0xFF) * (ceilf(yf) - yf); // how much color to keep
|
2024-05-09 22:02:28 +00:00
|
|
|
|
if (oldFade > fade) y--;
|
|
|
|
|
oldFade = fade;
|
2024-12-19 19:19:42 +00:00
|
|
|
|
int px, py;
|
|
|
|
|
for (uint8_t i = 0; i < 16; i++) {
|
|
|
|
|
int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
|
|
|
|
|
int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
|
|
|
|
|
int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
|
|
|
|
|
int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
|
|
|
|
|
if (swaps) {
|
|
|
|
|
px = cx + (y - adj) * dx;
|
|
|
|
|
py = cy + x * dy;
|
|
|
|
|
} else {
|
|
|
|
|
px = cx + x * dx;
|
|
|
|
|
py = cy + (y - adj) * dy;
|
|
|
|
|
}
|
|
|
|
|
uint32_t pixCol = getPixelColorXY(px, py);
|
|
|
|
|
setPixelColorXY(px, py, adj ?
|
|
|
|
|
color_blend(pixCol, col, fade) :
|
|
|
|
|
color_blend(col, pixCol, fade));
|
|
|
|
|
}
|
2024-05-09 22:02:28 +00:00
|
|
|
|
x++;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Bresenham’s Algorithm
|
|
|
|
|
int d = 3 - (2*radius);
|
|
|
|
|
int y = radius, x = 0;
|
|
|
|
|
while (y >= x) {
|
2024-12-19 19:19:42 +00:00
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
int dx = (i & 1) ? -x : x;
|
|
|
|
|
int dy = (i & 2) ? -y : y;
|
|
|
|
|
setPixelColorXY(cx + dx, cy + dy, col);
|
|
|
|
|
setPixelColorXY(cx + dy, cy + dx, col);
|
|
|
|
|
}
|
2024-05-09 22:02:28 +00:00
|
|
|
|
x++;
|
|
|
|
|
if (d > 0) {
|
|
|
|
|
y--;
|
|
|
|
|
d += 4 * (x - y) + 10;
|
|
|
|
|
} else {
|
|
|
|
|
d += 4 * x + 6;
|
|
|
|
|
}
|
2023-01-06 08:10:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 17:35:22 +00:00
|
|
|
|
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const {
|
2023-09-10 16:52:14 +00:00
|
|
|
|
if (!isActive() || radius == 0) return; // not active
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
|
|
|
|
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
|
2024-05-09 22:02:28 +00:00
|
|
|
|
// draw soft bounding circle
|
|
|
|
|
if (soft) drawCircle(cx, cy, radius, col, soft);
|
|
|
|
|
// fill it
|
2024-04-26 18:11:46 +00:00
|
|
|
|
for (int y = -radius; y <= radius; y++) {
|
|
|
|
|
for (int x = -radius; x <= radius; x++) {
|
2022-05-23 19:04:16 +00:00
|
|
|
|
if (x * x + y * y <= radius * radius &&
|
2024-09-28 16:14:43 +00:00
|
|
|
|
int(cx)+x >= 0 && int(cy)+y >= 0 &&
|
|
|
|
|
int(cx)+x < vW && int(cy)+y < vH)
|
2023-01-06 17:11:52 +00:00
|
|
|
|
setPixelColorXY(cx + x, cy + y, col);
|
2022-05-20 17:35:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-03 12:23:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-19 20:49:04 +00:00
|
|
|
|
//line function
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) const {
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2024-09-28 16:14:43 +00:00
|
|
|
|
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
|
|
|
|
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
|
|
|
|
|
if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return;
|
2024-05-09 22:02:28 +00:00
|
|
|
|
|
|
|
|
|
const int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1; // x distance & step
|
|
|
|
|
const int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; // y distance & step
|
|
|
|
|
|
|
|
|
|
// single pixel (line length == 0)
|
|
|
|
|
if (dx+dy == 0) {
|
|
|
|
|
setPixelColorXY(x0, y0, c);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (soft) {
|
|
|
|
|
// Xiaolin Wu’s algorithm
|
|
|
|
|
const bool steep = dy > 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++) {
|
2024-12-19 17:20:56 +00:00
|
|
|
|
uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
|
|
|
|
|
uint8_t seep = 0xFF - keep; // how much background to keep
|
2024-05-09 22:02:28 +00:00
|
|
|
|
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
|
2025-04-22 20:37:18 +00:00
|
|
|
|
blendPixelColorXY(x, y, c, seep);
|
|
|
|
|
blendPixelColorXY(x+int(steep), y+int(!steep), c, keep);
|
2024-05-09 22:02:28 +00:00
|
|
|
|
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; }
|
|
|
|
|
}
|
2022-05-19 20:49:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-20 17:35:22 +00:00
|
|
|
|
|
2022-09-05 01:18:59 +00:00
|
|
|
|
#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"
|
2022-06-17 19:19:12 +00:00
|
|
|
|
|
|
|
|
|
// draws a raster font character on canvas
|
2022-09-09 15:21:13 +00:00
|
|
|
|
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
|
2025-04-22 20:37:18 +00:00
|
|
|
|
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) const {
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-09-05 01:18:59 +00:00
|
|
|
|
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
|
|
|
|
|
chr -= 32; // align with font table entries
|
2022-08-11 09:46:30 +00:00
|
|
|
|
const int font = w*h;
|
2022-05-25 19:15:08 +00:00
|
|
|
|
|
2025-04-26 18:08:15 +00:00
|
|
|
|
// if col2 == BLACK then use currently selected palette for gradient otherwise create gradient from color and col2
|
2025-04-22 20:37:18 +00:00
|
|
|
|
CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient
|
2023-01-17 18:54:44 +00:00
|
|
|
|
|
2022-08-11 09:46:30 +00:00
|
|
|
|
for (int i = 0; i<h; i++) { // character height
|
2022-07-10 20:23:25 +00:00
|
|
|
|
uint8_t bits = 0;
|
2022-08-11 09:46:30 +00:00
|
|
|
|
switch (font) {
|
2025-04-22 20:37:18 +00:00
|
|
|
|
case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 4x6 font
|
2022-08-12 22:58:27 +00:00
|
|
|
|
case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font
|
|
|
|
|
case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font
|
|
|
|
|
case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font
|
|
|
|
|
case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font
|
2022-08-11 09:46:30 +00:00
|
|
|
|
default: return;
|
2022-06-17 19:19:12 +00:00
|
|
|
|
}
|
2025-04-22 20:37:18 +00:00
|
|
|
|
CRGBW c = ColorFromPalette(grad, (i+1)*255/h, 255, LINEARBLEND_NOWRAP); // NOBLEND is faster
|
2022-08-11 09:46:30 +00:00
|
|
|
|
for (int j = 0; j<w; j++) { // character width
|
2023-08-16 19:02:00 +00:00
|
|
|
|
int x0, y0;
|
|
|
|
|
switch (rotate) {
|
2023-09-24 14:48:59 +00:00
|
|
|
|
case -1: x0 = x + (h-1) - i; y0 = y + (w-1) - j; break; // -90 deg
|
|
|
|
|
case -2:
|
|
|
|
|
case 2: x0 = x + j; y0 = y + (h-1) - i; break; // 180 deg
|
|
|
|
|
case 1: x0 = x + i; y0 = y + j; break; // +90 deg
|
|
|
|
|
default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation
|
2023-08-16 19:02:00 +00:00
|
|
|
|
}
|
2024-09-28 16:14:43 +00:00
|
|
|
|
if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen
|
2023-08-16 19:02:00 +00:00
|
|
|
|
if (((bits>>(j+(8-w))) & 0x01)) { // bit set
|
2025-04-22 20:37:18 +00:00
|
|
|
|
setPixelColorXYRaw(x0, y0, c.color32);
|
2022-05-25 19:15:08 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 17:35:22 +00:00
|
|
|
|
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
|
2025-04-22 20:37:18 +00:00
|
|
|
|
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu_pixel procedure by reddit u/sutaburosu
|
2023-07-12 18:52:34 +00:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-05-20 17:35:22 +00:00
|
|
|
|
// extract the fractional parts and derive their inverses
|
2024-04-26 18:11:46 +00:00
|
|
|
|
unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
|
2022-05-20 17:35:22 +00:00
|
|
|
|
// 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
|
2022-08-11 09:46:30 +00:00
|
|
|
|
for (int i = 0; i < 4; i++) {
|
2024-09-29 16:37:18 +00:00
|
|
|
|
int wu_x = (x >> 8) + (i & 1); // precalculate x
|
|
|
|
|
int wu_y = (y >> 8) + ((i >> 1) & 1); // precalculate y
|
|
|
|
|
CRGB led = getPixelColorXY(wu_x, wu_y);
|
|
|
|
|
CRGB oldLed = led;
|
2022-08-02 16:27:32 +00:00
|
|
|
|
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);
|
2024-09-29 16:37:18 +00:00
|
|
|
|
if (led != oldLed) setPixelColorXY(wu_x, wu_y, led); // don't repaint if same color
|
2022-05-20 17:35:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#undef WU_WEIGHT
|
2022-08-03 12:23:24 +00:00
|
|
|
|
|
|
|
|
|
#endif // WLED_DISABLE_2D
|