2022-05-08 10:50:48 +02:00
|
|
|
|
/*
|
|
|
|
|
FX_2Dfcn.cpp contains all 2D utility functions
|
2023-01-06 09:10:39 +01:00
|
|
|
|
|
2022-05-08 10:50:48 +02:00
|
|
|
|
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
|
2022-12-16 22:31:07 +01: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 10:50:48 +02: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 22:23:25 +02:00
|
|
|
|
#ifndef WLED_DISABLE_2D
|
2022-05-08 10:50:48 +02:00
|
|
|
|
// erase old ledmap, just in case.
|
|
|
|
|
if (customMappingTable != nullptr) delete[] customMappingTable;
|
|
|
|
|
customMappingTable = nullptr;
|
|
|
|
|
customMappingSize = 0;
|
|
|
|
|
|
2022-12-16 22:31:07 +01:00
|
|
|
|
// isMatrix is set in cfg.cpp or set.cpp
|
2022-05-08 10:50:48 +02:00
|
|
|
|
if (isMatrix) {
|
2023-01-02 20:56:00 +01:00
|
|
|
|
// calculate width dynamically because it will 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
|
|
|
|
// safety check
|
2023-01-02 20:56:00 +01:00
|
|
|
|
if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) {
|
|
|
|
|
DEBUG_PRINTLN(F("2D Bounds error."));
|
2022-05-08 10:50:48 +02:00
|
|
|
|
isMatrix = false;
|
2023-01-02 20:56:00 +01:00
|
|
|
|
Segment::maxWidth = _length;
|
|
|
|
|
Segment::maxHeight = 1;
|
|
|
|
|
panels = 0;
|
|
|
|
|
panel.clear(); // release memory allocated by panels
|
2023-01-22 11:29:31 +01:00
|
|
|
|
resetSegments();
|
2022-05-08 10:50:48 +02:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-02 20:56:00 +01:00
|
|
|
|
customMappingTable = new uint16_t[Segment::maxWidth * Segment::maxHeight];
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
|
|
|
|
if (customMappingTable != nullptr) {
|
2023-01-02 20:56:00 +01:00
|
|
|
|
customMappingSize = Segment::maxWidth * Segment::maxHeight;
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2023-01-02 20:56:00 +01:00
|
|
|
|
// fill with empty in case we don't fill the entire matrix
|
|
|
|
|
for (size_t i = 0; i< customMappingSize; i++) {
|
|
|
|
|
customMappingTable[i] = (uint16_t)-1;
|
|
|
|
|
}
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2023-02-10 19:49:43 +01: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)
|
|
|
|
|
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint
|
|
|
|
|
bool isFile = WLED_FS.exists(fileName);
|
2023-02-11 18:41:30 +01:00
|
|
|
|
size_t gapSize = 0;
|
2023-02-10 19:49:43 +01:00
|
|
|
|
int8_t *gapTable = nullptr;
|
2023-02-09 20:15:55 +01:00
|
|
|
|
|
|
|
|
|
if (isFile && requestJSONBufferLock(20)) {
|
|
|
|
|
DEBUG_PRINT(F("Reading LED gap from "));
|
|
|
|
|
DEBUG_PRINTLN(fileName);
|
2023-02-10 19:49:43 +01:00
|
|
|
|
// read the array into global JSON buffer
|
2023-02-09 20:15:55 +01:00
|
|
|
|
if (readObjectFromFile(fileName, nullptr, &doc)) {
|
|
|
|
|
// the array is similar to ledmap, except it has only 3 values:
|
|
|
|
|
// -1 ... missing pixel (do not increase pixel count)
|
2023-02-10 19:49:43 +01:00
|
|
|
|
// 0 ... inactive pixel (it does count, but should be mapped out (-1))
|
|
|
|
|
// 1 ... active pixel (it will count and will be mapped)
|
2023-02-09 20:15:55 +01:00
|
|
|
|
JsonArray map = doc.as<JsonArray>();
|
|
|
|
|
gapSize = map.size();
|
2023-02-10 19:49:43 +01:00
|
|
|
|
if (!map.isNull() && gapSize >= customMappingSize) { // not an empty map
|
2023-02-09 20:15:55 +01:00
|
|
|
|
gapTable = new int8_t[gapSize];
|
2023-02-10 19:49:43 +01:00
|
|
|
|
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
|
2023-02-09 20:15:55 +01:00
|
|
|
|
gapTable[i] = constrain(map[i], -1, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-02-11 18:41:30 +01:00
|
|
|
|
DEBUG_PRINTLN(F("Gaps loaded."));
|
2023-02-09 20:15:55 +01:00
|
|
|
|
releaseJSONBufferLock();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-02 20:56:00 +01:00
|
|
|
|
uint16_t x, y, pix=0; //pixel
|
|
|
|
|
for (size_t pan = 0; pan < panel.size(); pan++) {
|
|
|
|
|
Panel &p = panel[pan];
|
|
|
|
|
uint16_t h = p.vertical ? p.height : p.width;
|
|
|
|
|
uint16_t v = p.vertical ? p.width : p.height;
|
|
|
|
|
for (size_t j = 0; j < v; j++){
|
2023-02-09 20:15:55 +01:00
|
|
|
|
for(size_t i = 0; i < h; i++) {
|
2023-01-02 20:56:00 +01: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 20:15:55 +01:00
|
|
|
|
size_t index = (p.yOffset + (p.vertical?x:y)) * Segment::maxWidth + p.xOffset + (p.vertical?y:x);
|
2023-02-10 19:49:43 +01: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 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-02 20:56:00 +01:00
|
|
|
|
|
2023-02-10 19:49:43 +01:00
|
|
|
|
// delete gap array as we no longer need it
|
2023-02-09 20:15:55 +01:00
|
|
|
|
if (gapTable) delete[] gapTable;
|
|
|
|
|
|
2022-05-08 10:50:48 +02:00
|
|
|
|
#ifdef WLED_DEBUG
|
|
|
|
|
DEBUG_PRINT(F("Matrix ledmap:"));
|
|
|
|
|
for (uint16_t i=0; i<customMappingSize; i++) {
|
2022-12-16 22:31:07 +01:00
|
|
|
|
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
|
2022-05-08 10:50:48 +02:00
|
|
|
|
DEBUG_PRINTF("%4d,", customMappingTable[i]);
|
|
|
|
|
}
|
|
|
|
|
DEBUG_PRINTLN();
|
|
|
|
|
#endif
|
2022-12-22 18:13:32 +01:00
|
|
|
|
} else { // memory allocation error
|
2023-01-02 20:56:00 +01:00
|
|
|
|
DEBUG_PRINTLN(F("Ledmap alloc error."));
|
2022-05-08 10:50:48 +02:00
|
|
|
|
isMatrix = false;
|
2023-01-02 20:56:00 +01:00
|
|
|
|
panels = 0;
|
|
|
|
|
panel.clear();
|
|
|
|
|
Segment::maxWidth = _length;
|
|
|
|
|
Segment::maxHeight = 1;
|
2023-02-14 17:11:58 +01:00
|
|
|
|
resetSegments();
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-22 18:13:32 +01:00
|
|
|
|
#else
|
|
|
|
|
isMatrix = false; // no matter what config says
|
2022-07-10 22:23:25 +02:00
|
|
|
|
#endif
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
// absolute matrix version of setPixelColor()
|
2023-03-05 22:56:14 +01:00
|
|
|
|
void /*IRAM_ATTR*/ WS2812FX::setPixelColorXY(int x, int y, uint32_t col)
|
2022-07-10 22:23:25 +02:00
|
|
|
|
{
|
|
|
|
|
#ifndef WLED_DISABLE_2D
|
|
|
|
|
if (!isMatrix) return; // not a matrix set-up
|
2022-12-16 22:31:07 +01:00
|
|
|
|
uint16_t index = y * Segment::maxWidth + x;
|
2022-07-31 13:28:12 +02:00
|
|
|
|
#else
|
|
|
|
|
uint16_t index = x;
|
2022-11-23 16:54:32 +01:00
|
|
|
|
#endif
|
2022-07-10 22:23:25 +02:00
|
|
|
|
if (index < customMappingSize) index = customMappingTable[index];
|
2023-03-11 15:03:28 +01:00
|
|
|
|
if (index >= _length) return;
|
2022-07-10 22:23:25 +02:00
|
|
|
|
busses.setPixelColor(index, col);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// returns RGBW values of pixel
|
|
|
|
|
uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) {
|
|
|
|
|
#ifndef WLED_DISABLE_2D
|
2022-12-16 22:31:07 +01:00
|
|
|
|
uint16_t index = (y * Segment::maxWidth + x);
|
2022-07-31 13:28:12 +02:00
|
|
|
|
#else
|
|
|
|
|
uint16_t index = x;
|
2022-11-23 16:54:32 +01:00
|
|
|
|
#endif
|
2022-07-10 22:23:25 +02:00
|
|
|
|
if (index < customMappingSize) index = customMappingTable[index];
|
2023-03-11 15:03:28 +01:00
|
|
|
|
if (index >= _length) return 0;
|
2022-07-10 22:23:25 +02:00
|
|
|
|
return busses.getPixelColor(index);
|
2022-05-10 00:35:26 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
// Segment:: routines
|
|
|
|
|
///////////////////////////////////////////////////////////
|
|
|
|
|
|
2022-08-03 14:23:24 +02:00
|
|
|
|
#ifndef WLED_DISABLE_2D
|
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
|
2023-03-05 22:56:14 +01:00
|
|
|
|
uint16_t /*IRAM_ATTR*/ Segment::XY(uint16_t x, uint16_t y) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
|
|
|
|
uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1)
|
|
|
|
|
return isActive() ? (x%width) + (y%height) * width : 0;
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-03-05 22:56:14 +01:00
|
|
|
|
void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col)
|
2022-05-08 10:50:48 +02:00
|
|
|
|
{
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-08-22 16:47:25 +02:00
|
|
|
|
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
|
2022-07-10 22:23:25 +02:00
|
|
|
|
|
2022-08-06 12:39:12 +02:00
|
|
|
|
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
2022-07-10 22:23:25 +02:00
|
|
|
|
if (_bri_t < 255) {
|
|
|
|
|
byte r = scale8(R(col), _bri_t);
|
|
|
|
|
byte g = scale8(G(col), _bri_t);
|
|
|
|
|
byte b = scale8(B(col), _bri_t);
|
|
|
|
|
byte w = scale8(W(col), _bri_t);
|
|
|
|
|
col = RGBW32(r, g, b, w);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-06 12:39:12 +02:00
|
|
|
|
if (reverse ) x = virtualWidth() - x - 1;
|
|
|
|
|
if (reverse_y) y = virtualHeight() - y - 1;
|
|
|
|
|
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
x *= groupLength(); // expand to physical pixels
|
|
|
|
|
y *= groupLength(); // expand to physical pixels
|
|
|
|
|
if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
for (int j = 0; j < grouping; j++) { // groupping vertically
|
|
|
|
|
for (int g = 0; g < grouping; g++) { // groupping horizontally
|
|
|
|
|
uint16_t xX = (x+g), yY = (y+j);
|
|
|
|
|
if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
strip.setPixelColorXY(start + xX, startY + yY, col);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
|
2022-08-06 12:39:12 +02:00
|
|
|
|
if (mirror) { //set the corresponding horizontally mirrored pixel
|
|
|
|
|
if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col);
|
|
|
|
|
else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
2022-08-06 12:39:12 +02:00
|
|
|
|
if (mirror_y) { //set the corresponding vertically mirrored pixel
|
|
|
|
|
if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col);
|
|
|
|
|
else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col);
|
2022-06-05 10:16:56 +02:00
|
|
|
|
}
|
2022-08-06 12:39:12 +02:00
|
|
|
|
if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel
|
2022-07-10 22:23:25 +02:00
|
|
|
|
strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, col);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 21:55:51 +02:00
|
|
|
|
// anti-aliased version of setPixelColorXY()
|
2022-07-10 22:23:25 +02:00
|
|
|
|
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
|
2022-06-13 21:55:51 +02:00
|
|
|
|
{
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-06-13 21:55:51 +02:00
|
|
|
|
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
|
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-06-13 21:55:51 +02:00
|
|
|
|
|
2022-06-23 17:42:02 +02:00
|
|
|
|
float fX = x * (cols-1);
|
|
|
|
|
float fY = y * (rows-1);
|
2022-06-13 21:55:51 +02:00
|
|
|
|
if (aa) {
|
2022-06-23 17:42:02 +02:00
|
|
|
|
uint16_t xL = roundf(fX-0.49f);
|
|
|
|
|
uint16_t xR = roundf(fX+0.49f);
|
|
|
|
|
uint16_t yT = roundf(fY-0.49f);
|
|
|
|
|
uint16_t yB = roundf(fY+0.49f);
|
2022-08-30 17:20:58 +02: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 21:55:51 +02: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 22:23:25 +02: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 21:55:51 +02:00
|
|
|
|
} else if (xR!=xL && yT==yB) {
|
2022-07-10 22:23:25 +02: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 21:55:51 +02:00
|
|
|
|
} else if (xR==xL && yT!=yB) {
|
2022-07-10 22:23:25 +02: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 21:55:51 +02:00
|
|
|
|
} else {
|
2022-07-10 22:23:25 +02:00
|
|
|
|
setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel)
|
2022-06-13 21:55:51 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
2022-07-10 22:23:25 +02:00
|
|
|
|
setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col);
|
2022-06-13 21:55:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-30 22:21:13 +02:00
|
|
|
|
// returns RGBW values of pixel
|
2022-07-10 22:23:25 +02:00
|
|
|
|
uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
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
|
2022-08-06 12:39:12 +02:00
|
|
|
|
if (reverse ) x = virtualWidth() - x - 1;
|
|
|
|
|
if (reverse_y) y = virtualHeight() - y - 1;
|
|
|
|
|
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
|
2022-07-10 22:23:25 +02:00
|
|
|
|
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);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
// Blends the specified color with the existing pixel color.
|
|
|
|
|
void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) {
|
2022-05-11 09:37:38 +02:00
|
|
|
|
setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend));
|
|
|
|
|
}
|
2022-05-10 00:35:26 +02:00
|
|
|
|
|
2022-07-10 22:23:25 +02:00
|
|
|
|
// Adds the specified color with the existing pixel color perserving color balance.
|
2023-04-27 17:31:55 +02:00
|
|
|
|
void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2023-07-06 19:51:37 +02:00
|
|
|
|
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
|
2023-04-27 17:31:55 +02:00
|
|
|
|
uint32_t col = getPixelColorXY(x,y);
|
|
|
|
|
uint8_t r = R(col);
|
|
|
|
|
uint8_t g = G(col);
|
|
|
|
|
uint8_t b = B(col);
|
|
|
|
|
uint8_t w = W(col);
|
|
|
|
|
if (fast) {
|
|
|
|
|
r = qadd8(r, R(color));
|
|
|
|
|
g = qadd8(g, G(color));
|
|
|
|
|
b = qadd8(b, B(color));
|
|
|
|
|
w = qadd8(w, W(color));
|
|
|
|
|
col = RGBW32(r,g,b,w);
|
|
|
|
|
} else {
|
|
|
|
|
col = color_add(col, color);
|
|
|
|
|
}
|
|
|
|
|
setPixelColorXY(x, y, col);
|
2022-08-02 18:27:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-08-02 18:27:32 +02:00
|
|
|
|
CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade);
|
2023-01-12 19:13:07 +01:00
|
|
|
|
setPixelColorXY(x, y, pix);
|
2022-06-28 23:32:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 09:37:38 +02:00
|
|
|
|
// blurRow: perform a blur on a row of a rectangular matrix
|
2022-08-02 19:44:27 +02:00
|
|
|
|
void Segment::blurRow(uint16_t row, fract8 blur_amount) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2023-07-06 19:51:37 +02:00
|
|
|
|
const uint_fast16_t cols = virtualWidth();
|
|
|
|
|
const uint_fast16_t rows = virtualHeight();
|
2022-05-30 22:21:13 +02:00
|
|
|
|
|
2022-05-28 19:23:16 +02:00
|
|
|
|
if (row >= rows) return;
|
2022-05-10 10:37:27 +02:00
|
|
|
|
// blur one row
|
2022-05-08 10:50:48 +02:00
|
|
|
|
uint8_t keep = 255 - blur_amount;
|
|
|
|
|
uint8_t seep = blur_amount >> 1;
|
|
|
|
|
CRGB carryover = CRGB::Black;
|
2023-07-06 19:51:37 +02:00
|
|
|
|
for (uint_fast16_t x = 0; x < cols; x++) {
|
2022-08-05 23:03:38 +02:00
|
|
|
|
CRGB cur = getPixelColorXY(x, row);
|
2023-07-18 11:29:08 +02:00
|
|
|
|
CRGB before = cur; // remember color before blur
|
2022-05-10 00:35:26 +02:00
|
|
|
|
CRGB part = cur;
|
|
|
|
|
part.nscale8(seep);
|
|
|
|
|
cur.nscale8(keep);
|
|
|
|
|
cur += carryover;
|
2023-07-06 19:51:37 +02:00
|
|
|
|
if (x>0) {
|
2022-08-02 19:44:27 +02:00
|
|
|
|
CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part;
|
|
|
|
|
setPixelColorXY(x-1, row, prev);
|
2022-05-26 21:36:48 +02:00
|
|
|
|
}
|
2023-07-18 11:29:08 +02:00
|
|
|
|
if (before != cur) // optimization: only set pixel if color has changed
|
2023-07-06 19:51:37 +02:00
|
|
|
|
setPixelColorXY(x, row, cur);
|
2022-05-10 00:35:26 +02:00
|
|
|
|
carryover = part;
|
|
|
|
|
}
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-11 09:37:38 +02:00
|
|
|
|
// blurCol: perform a blur on a column of a rectangular matrix
|
2022-08-02 19:44:27 +02:00
|
|
|
|
void Segment::blurCol(uint16_t col, fract8 blur_amount) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2023-07-06 19:51:37 +02:00
|
|
|
|
const uint_fast16_t cols = virtualWidth();
|
|
|
|
|
const uint_fast16_t rows = virtualHeight();
|
2022-05-30 22:21:13 +02:00
|
|
|
|
|
2022-05-28 19:23:16 +02:00
|
|
|
|
if (col >= cols) return;
|
2022-05-10 10:37:27 +02:00
|
|
|
|
// blur one column
|
2022-05-08 10:50:48 +02:00
|
|
|
|
uint8_t keep = 255 - blur_amount;
|
|
|
|
|
uint8_t seep = blur_amount >> 1;
|
2022-05-10 00:35:26 +02:00
|
|
|
|
CRGB carryover = CRGB::Black;
|
2023-07-06 19:51:37 +02:00
|
|
|
|
for (uint_fast16_t y = 0; y < rows; y++) {
|
|
|
|
|
CRGB cur = getPixelColorXY(col, y);
|
2022-05-10 00:35:26 +02:00
|
|
|
|
CRGB part = cur;
|
2023-07-18 11:29:08 +02:00
|
|
|
|
CRGB before = cur; // remember color before blur
|
2022-05-10 00:35:26 +02:00
|
|
|
|
part.nscale8(seep);
|
|
|
|
|
cur.nscale8(keep);
|
|
|
|
|
cur += carryover;
|
2023-07-06 19:51:37 +02:00
|
|
|
|
if (y>0) {
|
|
|
|
|
CRGB prev = CRGB(getPixelColorXY(col, y-1)) + part;
|
|
|
|
|
setPixelColorXY(col, y-1, prev);
|
2022-05-26 21:36:48 +02:00
|
|
|
|
}
|
2023-07-18 11:29:08 +02:00
|
|
|
|
if (before != cur) // optimization: only set pixel if color has changed
|
2023-07-06 19:51:37 +02:00
|
|
|
|
setPixelColorXY(col, y, cur);
|
2022-05-10 00:35:26 +02:00
|
|
|
|
carryover = part;
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-27 13:39:22 +02:00
|
|
|
|
// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur])
|
2022-08-02 18:27:32 +02:00
|
|
|
|
void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
|
|
|
|
const uint16_t dim1 = vertical ? rows : cols;
|
|
|
|
|
const uint16_t dim2 = vertical ? cols : rows;
|
2022-05-27 13:39:22 +02:00
|
|
|
|
if (i >= dim2) return;
|
|
|
|
|
const float seep = blur_amount/255.f;
|
|
|
|
|
const float keep = 3.f - 2.f*seep;
|
|
|
|
|
// 1D box blur
|
|
|
|
|
CRGB tmp[dim1];
|
|
|
|
|
for (uint16_t j = 0; j < dim1; j++) {
|
|
|
|
|
uint16_t x = vertical ? i : j;
|
|
|
|
|
uint16_t y = vertical ? j : i;
|
2023-07-06 19:51:37 +02:00
|
|
|
|
int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow
|
|
|
|
|
int16_t yp = vertical ? y-1 : y; // "signed" to prevent underflow
|
2022-05-27 13:39:22 +02:00
|
|
|
|
uint16_t xn = vertical ? x : x+1;
|
|
|
|
|
uint16_t yn = vertical ? y+1 : y;
|
2022-08-02 18:27:32 +02:00
|
|
|
|
CRGB curr = getPixelColorXY(x,y);
|
|
|
|
|
CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp);
|
|
|
|
|
CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn);
|
2022-05-27 13:39:22 +02:00
|
|
|
|
uint16_t r, g, b;
|
|
|
|
|
r = (curr.r*keep + (prev.r + next.r)*seep) / 3;
|
|
|
|
|
g = (curr.g*keep + (prev.g + next.g)*seep) / 3;
|
|
|
|
|
b = (curr.b*keep + (prev.b + next.b)*seep) / 3;
|
|
|
|
|
tmp[j] = CRGB(r,g,b);
|
|
|
|
|
}
|
|
|
|
|
for (uint16_t j = 0; j < dim1; j++) {
|
|
|
|
|
uint16_t x = vertical ? i : j;
|
|
|
|
|
uint16_t y = vertical ? j : i;
|
2022-08-02 18:27:32 +02:00
|
|
|
|
setPixelColorXY(x, y, tmp[j]);
|
2022-05-27 13:39:22 +02:00
|
|
|
|
}
|
2022-08-03 14:23:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors.
|
|
|
|
|
// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors.
|
|
|
|
|
//
|
|
|
|
|
// 0 = no spread at all
|
|
|
|
|
// 64 = moderate spreading
|
|
|
|
|
// 172 = maximum smooth, even spreading
|
|
|
|
|
//
|
|
|
|
|
// 173..255 = wider spreading, but increasing flicker
|
|
|
|
|
//
|
|
|
|
|
// Total light is NOT entirely conserved, so many repeated
|
|
|
|
|
// calls to 'blur' will also result in the light fading,
|
|
|
|
|
// eventually all the way to black; this is by design so that
|
|
|
|
|
// it can be used to (slowly) clear the LEDs to black.
|
|
|
|
|
|
2022-08-05 23:03:38 +02:00
|
|
|
|
void Segment::blur1d(fract8 blur_amount) {
|
2022-08-03 14:23:24 +02:00
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-08-05 23:03:38 +02:00
|
|
|
|
for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount);
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 17:31:55 +02:00
|
|
|
|
void Segment::moveX(int8_t delta, bool wrap) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2023-04-27 17:31:55 +02:00
|
|
|
|
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);
|
2022-05-19 18:27:04 +02:00
|
|
|
|
}
|
2023-04-27 17:31:55 +02:00
|
|
|
|
for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]);
|
2022-05-19 18:27:04 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-27 17:31:55 +02:00
|
|
|
|
void Segment::moveY(int8_t delta, bool wrap) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2023-04-27 17:31:55 +02:00
|
|
|
|
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);
|
2022-05-19 18:27:04 +02:00
|
|
|
|
}
|
2023-04-27 17:31:55 +02:00
|
|
|
|
for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]);
|
2022-05-19 18:27:04 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-30 22:21:13 +02: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 17:31:55 +02:00
|
|
|
|
// @param wrap around
|
|
|
|
|
void Segment::move(uint8_t dir, uint8_t delta, bool wrap) {
|
2022-05-30 22:21:13 +02:00
|
|
|
|
if (delta==0) return;
|
|
|
|
|
switch (dir) {
|
2023-04-27 17:31:55 +02: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 22:21:13 +02:00
|
|
|
|
}
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-06 09:10:39 +01:00
|
|
|
|
void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2023-01-06 09:10:39 +01:00
|
|
|
|
// Bresenham’s Algorithm
|
|
|
|
|
int d = 3 - (2*radius);
|
|
|
|
|
int y = radius, x = 0;
|
|
|
|
|
while (y >= x) {
|
2023-01-06 18:11:52 +01:00
|
|
|
|
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);
|
2023-01-06 09:10:39 +01:00
|
|
|
|
x++;
|
|
|
|
|
if (d > 0) {
|
|
|
|
|
y--;
|
|
|
|
|
d += 4 * (x - y) + 10;
|
|
|
|
|
} else {
|
|
|
|
|
d += 4 * x + 6;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 19:35:22 +02:00
|
|
|
|
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
|
2022-08-02 18:27:32 +02:00
|
|
|
|
void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-05-20 19:35:22 +02:00
|
|
|
|
for (int16_t y = -radius; y <= radius; y++) {
|
|
|
|
|
for (int16_t x = -radius; x <= radius; x++) {
|
2022-05-23 21:04:16 +02:00
|
|
|
|
if (x * x + y * y <= radius * radius &&
|
|
|
|
|
int16_t(cx)+x>=0 && int16_t(cy)+y>=0 &&
|
2022-05-28 19:23:16 +02:00
|
|
|
|
int16_t(cx)+x<cols && int16_t(cy)+y<rows)
|
2023-01-06 18:11:52 +01:00
|
|
|
|
setPixelColorXY(cx + x, cy + y, col);
|
2022-05-20 19:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-03 14:23:24 +02:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-02 19:44:27 +02:00
|
|
|
|
void Segment::nscale8(uint8_t scale) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-05-28 19:23:16 +02:00
|
|
|
|
for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) {
|
2022-09-09 17:21:13 +02:00
|
|
|
|
setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale));
|
2022-05-08 10:50:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-19 22:49:04 +02:00
|
|
|
|
//line function
|
2022-08-03 14:23:24 +02:00
|
|
|
|
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-05-28 19:23:16 +02:00
|
|
|
|
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
|
2022-05-27 13:39:22 +02:00
|
|
|
|
const int16_t dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
|
2023-01-06 09:10:39 +01:00
|
|
|
|
const int16_t dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
|
2022-05-20 14:48:40 +02:00
|
|
|
|
int16_t err = (dx>dy ? dx : -dy)/2, e2;
|
|
|
|
|
for (;;) {
|
2023-01-06 18:11:52 +01:00
|
|
|
|
setPixelColorXY(x0,y0,c);
|
2022-05-19 22:49:04 +02:00
|
|
|
|
if (x0==x1 && y0==y1) break;
|
|
|
|
|
e2 = err;
|
|
|
|
|
if (e2 >-dx) { err -= dy; x0 += sx; }
|
|
|
|
|
if (e2 < dy) { err += dx; y0 += sy; }
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-20 19:35:22 +02:00
|
|
|
|
|
2022-09-05 03:18:59 +02: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"
|
2023-08-16 19:27:01 +02:00
|
|
|
|
#include "src/font/console_font_12x16.h"
|
|
|
|
|
#include "src/font/console_font_12x24.h"
|
|
|
|
|
#include "src/font/console_font_16x32.h"
|
|
|
|
|
#include "src/font/console_font_25x57.h"
|
2022-06-17 21:19:12 +02:00
|
|
|
|
|
|
|
|
|
// draws a raster font character on canvas
|
2023-08-16 19:27:01 +02:00
|
|
|
|
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48, 7x9=63, 12x16=192, 16x24=288, 16x32=512 and 25x57=1425 fonts ATM
|
2023-01-17 19:54:44 +01: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) {
|
2022-09-05 03:18:59 +02:00
|
|
|
|
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
|
|
|
|
|
chr -= 32; // align with font table entries
|
2022-07-10 22:23:25 +02:00
|
|
|
|
const uint16_t cols = virtualWidth();
|
|
|
|
|
const uint16_t rows = virtualHeight();
|
2022-08-11 11:46:30 +02:00
|
|
|
|
const int font = w*h;
|
2023-08-16 19:27:01 +02:00
|
|
|
|
int num_bytes;
|
2022-05-25 21:15:08 +02:00
|
|
|
|
|
2023-08-16 19:27:01 +02:00
|
|
|
|
if (w <= 8) {
|
|
|
|
|
num_bytes = 1;
|
|
|
|
|
}
|
|
|
|
|
else if(w <= 16){
|
|
|
|
|
num_bytes = 2;
|
|
|
|
|
}
|
|
|
|
|
else if (w <= 24){
|
|
|
|
|
num_bytes = 3;
|
|
|
|
|
}
|
|
|
|
|
else if (w <= 32){
|
|
|
|
|
num_bytes = 4;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-17 19:54:44 +01:00
|
|
|
|
CRGB col = CRGB(color);
|
|
|
|
|
CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col);
|
|
|
|
|
|
2023-08-16 19:27:01 +02:00
|
|
|
|
// modifications to support characters whose width is > 8 bits
|
2022-08-11 11:46:30 +02:00
|
|
|
|
//if (w<5 || w>6 || h!=8) return;
|
|
|
|
|
for (int i = 0; i<h; i++) { // character height
|
2022-05-25 21:15:08 +02:00
|
|
|
|
int16_t y0 = y + i;
|
|
|
|
|
if (y0 < 0) continue; // drawing off-screen
|
2022-05-28 19:23:16 +02:00
|
|
|
|
if (y0 >= rows) break; // drawing off-screen
|
2023-08-16 19:27:01 +02:00
|
|
|
|
const uint8_t *bits = 0;
|
2022-08-11 11:46:30 +02:00
|
|
|
|
switch (font) {
|
2023-08-16 19:27:01 +02:00
|
|
|
|
case 24: bits = &console_font_4x6[(chr * h * num_bytes) + (i * num_bytes)]; break; // 5x8 font
|
|
|
|
|
case 40: bits = &console_font_5x8[(chr * h * num_bytes) + (i * num_bytes)]; break; // 5x8 font
|
|
|
|
|
case 48: bits = &console_font_6x8[(chr * h * num_bytes) + (i * num_bytes)]; break; // 6x8 font
|
|
|
|
|
case 63: bits = &console_font_7x9[(chr * h * num_bytes) + (i * num_bytes)]; break; // 7x9 font
|
|
|
|
|
case 60: bits = &console_font_5x12[(chr * h * num_bytes) + (i * num_bytes)]; break; // 5x12 font
|
|
|
|
|
case 192: bits = &console_font_12x16[(chr * h * num_bytes) + (i * num_bytes)]; break; // 12x16 font
|
|
|
|
|
case 288: bits = &console_font_12x24[(chr * h * num_bytes) + (i * num_bytes)]; break; // 16x24 font
|
|
|
|
|
case 512: bits = &console_font_16x32[(chr * h * num_bytes) + (i * num_bytes)]; break; // 16x32 font
|
|
|
|
|
case 1425: bits = &console_font_25x57[(chr * h * num_bytes) + (i * num_bytes)]; break; // 25x57 font
|
2022-08-11 11:46:30 +02:00
|
|
|
|
default: return;
|
2022-06-17 21:19:12 +02:00
|
|
|
|
}
|
2023-08-16 19:27:01 +02:00
|
|
|
|
|
|
|
|
|
int j1 = 0;
|
|
|
|
|
int wb = w % 8;
|
|
|
|
|
if(wb == 0)
|
|
|
|
|
wb = 8; // get width of the first byte to process
|
|
|
|
|
for (int k = 1; k <= num_bytes; k++) { // loop through all bytes of the character
|
|
|
|
|
col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND);
|
|
|
|
|
for (int j = 0; j<wb; j++) { // character width in this byte
|
|
|
|
|
int16_t x0 = x + w - j1++; // run through pixels of font right to left
|
|
|
|
|
if ((x0 >= 0 || x0 < cols) && ((bits[num_bytes - k]>>(j+(8-wb))) & 0x01)) { // bit set & drawing on-screen
|
|
|
|
|
setPixelColorXY(x0, y0, col);
|
|
|
|
|
}
|
2022-05-25 21:15:08 +02:00
|
|
|
|
}
|
2023-08-16 19:27:01 +02:00
|
|
|
|
wb = 8; // process 8 bits for all other bytes
|
2022-05-25 21:15:08 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-20 19:35:22 +02:00
|
|
|
|
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
|
2022-08-02 18:27:32 +02:00
|
|
|
|
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
|
2023-07-12 20:52:34 +02:00
|
|
|
|
if (!isActive()) return; // not active
|
2022-05-20 19:35:22 +02:00
|
|
|
|
// extract the fractional parts and derive their inverses
|
|
|
|
|
uint8_t 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
|
2022-08-11 11:46:30 +02:00
|
|
|
|
for (int i = 0; i < 4; i++) {
|
2022-08-02 18:27:32 +02:00
|
|
|
|
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);
|
2022-05-20 19:35:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#undef WU_WEIGHT
|
2022-08-03 14:23:24 +02:00
|
|
|
|
|
|
|
|
|
#endif // WLED_DISABLE_2D
|