Skip to content

Commit 9700147

Browse files
committed
Bugfixes and enhancements
- implemented ellipse drawing - combined drawCircle and fillCircle - improved speed by using raw methods and integer math - adding instead of overwriting pixels - minor cleaning in PS
1 parent 8a0de9a commit 9700147

File tree

6 files changed

+157
-159
lines changed

6 files changed

+157
-159
lines changed

wled00/FX.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,7 +2252,7 @@ static uint16_t ripple_base() {
22522252
unsigned cx = rippleorigin >> 8;
22532253
unsigned cy = rippleorigin & 0xFF;
22542254
unsigned mag = scale8(sin8_t((propF>>2)), amp);
2255-
if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, SEGMENT.getPixelColorXY(cx + propI, cy).nblend(col, (uint8_t)mag), true);
2255+
if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, SEGMENT.getPixelColorXY(cx + propI, cy).nblend(col, (uint8_t)mag), false, true);
22562256
} else
22572257
#endif
22582258
{
@@ -5728,7 +5728,7 @@ uint16_t mode_2Dfloatingblobs(void) {
57285728
int y = roundf(blob->y[i]);
57295729
CRGBA c = SEGMENT.color_from_palette(blob->color[i], false, PALETTE_FIXED, 0);
57305730
if (i > 0 && SEGMENT.check3) SEGMENT.drawLine(roundf(blob->x[i-1]), roundf(blob->y[i-1]), x, y, SEGCOLOR(2), SEGMENT.check1);
5731-
if (blob->r[i] > 1.f) SEGMENT.fillCircle(x, y, roundf(blob->r[i]), c, SEGMENT.check1);
5731+
if (blob->r[i] > 1.f) SEGMENT.drawCircle(x, y, roundf(blob->r[i]), c, true, SEGMENT.check1);
57325732
else SEGMENT.setPixelColorXY(x, y, c);
57335733
// move x
57345734
if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f));
@@ -6252,6 +6252,10 @@ uint16_t mode_2Dwavingcell() {
62526252
static const char _data_FX_MODE_2DWAVINGCELL[] PROGMEM = "Waving Cell@!,,Amplitude 1,Amplitude 2,Amplitude 3;;!;2";
62536253

62546254

6255+
////////////////////////////////////////////
6256+
// PARTICLE SYSTEM EFFECTS
6257+
////////////////////////////////////////////
6258+
62556259
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
62566260
/*
62576261
Particle System Vortex
@@ -6370,10 +6374,6 @@ uint16_t mode_particlevortex(void) {
63706374
static const char _data_FX_MODE_PARTICLEVORTEX[] PROGMEM = "PS Vortex@Rotation Speed,Particle Speed,Arms,Flip,Nozzle,Smear,Direction,Random Flip;;!;2;pal=27,c1=200,c2=0,c3=0";
63716375

63726376

6373-
////////////////////////////////////////////
6374-
// PARTICLE SYSTEM EFFECTS
6375-
////////////////////////////////////////////
6376-
63776377
/*
63786378
Particle Fireworks
63796379
Rockets shoot up and explode in a random color, sometimes in a defined pattern
@@ -7424,12 +7424,12 @@ uint16_t mode_particleblobs(void) {
74247424
}
74257425
#endif
74267426

7427-
PartSys->setMotionBlur(((SEGMENT.custom3) << 3) + 7);
7427+
PartSys->setMotionBlur(255 - ((SEGMENT.custom3 << 3) + 7));
74287428
PartSys->update(); // update and render
74297429

74307430
return FRAMETIME;
74317431
}
7432-
static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs,Size,Life,Blur,Wobble,Collide,Pulsate;;!;2v;sx=30,ix=64,c1=200,c2=130,c3=0,o3=1";
7432+
static const char _data_FX_MODE_PARTICLEBLOBS[] PROGMEM = "PS Blobs@Speed,Blobs,Size,Life,Trail,Wobble,Collide,Pulsate;;!;2v;sx=30,ix=64,c1=200,c2=130,c3=0,o3=1";
74337433

74347434
/*
74357435
Particle Galaxy, particles spiral like in a galaxy

wled00/FX.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -740,9 +740,8 @@ class Segment {
740740
void moveX(int delta, bool wrap = false) const;
741741
void moveY(int delta, bool wrap = false) const;
742742
void move(unsigned dir, unsigned delta, bool wrap = false) const;
743-
void drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA c, bool soft = false) const;
744-
void fillCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA c, bool soft = false) const;
745-
void drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CRGBA color, bool fill = false, bool soft = false);
743+
void drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA c, bool fill = false, bool soft = false) const;
744+
void drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CRGBA color, bool fill = false) const;
746745
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGBA c, bool soft = false) const;
747746
void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGBA color, CRGBA col2 = 0, int8_t rotate = 0) const;
748747
void setWuPixelColor(uint32_t x, uint32_t y, CRGBA c) const;

wled00/FX_2Dfcn.cpp

Lines changed: 87 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,10 @@ void Segment::move(unsigned dir, unsigned delta, bool wrap) const {
426426
}
427427
}
428428

429-
void Segment::drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, bool soft) const {
429+
void Segment::drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, bool fill, bool soft) const {
430430
if (!isActive() || radius == 0) return; // not active
431+
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
432+
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
431433
if (soft) {
432434
// Xiaolin Wu’s algorithm
433435
const int rsq = radius*radius;
@@ -440,20 +442,20 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, b
440442
if (oldFade > fade) y--;
441443
oldFade = fade;
442444
int px, py;
443-
for (uint8_t i = 0; i < 16; i++) {
444-
int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
445-
int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
446-
int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
447-
int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
448-
if (swaps) {
449-
px = cx + (y - adj) * dx;
450-
py = cy + x * dy;
451-
} else {
452-
px = cx + x * dx;
453-
py = cy + (y - adj) * dy;
454-
}
455-
if (px < 0 || py < 0 || px >= (int)vWidth() || py >= (int)vHeight()) continue;
456-
blendPixelColorXYRaw(px, py, col, (uint8_t)(adj ? fade : 255 - fade));
445+
for (size_t i = 0; i < 16; i++) {
446+
int swaps = (i & 0x4 ? 1 : 0); // 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1
447+
int adj = (i < 8) ? 0 : 1; // 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1
448+
int dx = (i & 1) ? -1 : 1; // 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1
449+
int dy = (i & 2) ? -1 : 1; // 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1
450+
if (swaps) {
451+
px = cx + (y - adj) * dx;
452+
py = cy + x * dy;
453+
} else {
454+
px = cx + x * dx;
455+
py = cy + (y - adj) * dy;
456+
}
457+
if (px < 0 || py < 0 || px >= vW || py >= vH) continue;
458+
blendPixelColorXYRaw(px, py, col, (uint8_t)(adj ? fade : 255 - fade));
457459
}
458460
x++;
459461
}
@@ -467,9 +469,10 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, b
467469
int dy = (i & 2) ? -y : y;
468470
int px = cx + dx;
469471
int py = cy + dy;
470-
if (px < 0 || py < 0) continue;
471-
if (px < (int)vWidth() && py < (int)vHeight()) setPixelColorXYRaw(px, py, col);
472-
if (py < (int)vWidth() && px < (int)vHeight()) setPixelColorXYRaw(py, px, col);
472+
if (px >= 0 && py >= 0 && px < vW && py < vH) setPixelColorXYRaw(px, py, getPixelColorXYRaw(px, py) + col);
473+
px = cx + dy;
474+
py = cy + dx;
475+
if (px >= 0 && py >= 0 && py < vW && px < vH) setPixelColorXYRaw(py, px, getPixelColorXYRaw(py, px) + col);
473476
}
474477
x++;
475478
if (d > 0) {
@@ -480,58 +483,67 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, b
480483
}
481484
}
482485
}
483-
}
484-
485-
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
486-
void Segment::fillCircle(uint16_t cx, uint16_t cy, uint16_t radius, CRGBA col, bool soft) const {
487-
if (!isActive() || radius == 0) return; // not active
488-
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
489-
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
490-
// draw soft bounding circle
491-
if (soft) drawCircle(cx, cy, radius, col, soft);
492-
// fill it
493-
uint32_t rSq = radius * radius;
494-
for (int y = -radius; y <= radius; y++) {
495-
for (int x = -radius; x <= radius; x++) {
496-
if (x * x + y * y <= rSq &&
497-
int(cx)+x >= 0 && int(cy)+y >= 0 &&
498-
int(cx)+x < vW && int(cy)+y < vH) {
499-
col += getPixelColorXYRaw(cx + x, cy + y);
500-
setPixelColorXYRaw(cx + x, cy + y, col);
486+
if (fill) {
487+
// fill it
488+
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
489+
uint32_t rSq = radius * radius;
490+
for (int y = -radius; y <= radius; y++) {
491+
for (int x = -radius; x <= radius; x++) {
492+
if (x * x + y * y <= rSq &&
493+
int(cx)+x >= 0 && int(cy)+y >= 0 &&
494+
int(cx)+x < vW && int(cy)+y < vH) {
495+
setPixelColorXYRaw(cx + x, cy + y, getPixelColorXYRaw(cx + x, cy + y) + col);
496+
}
501497
}
502498
}
503499
}
504500
}
505501

506-
/*
507502
// see https://www.geeksforgeeks.org/dsa/midpoint-ellipse-drawing-algorithm/
508-
void Segment::drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CRGBA color, bool fill, bool soft) {
509-
// all coodinates and radii are in 16.8 fixed point notation (use >> 8 to convert to pixel coordinates)
510-
if (!isActive()) return; // not active
503+
void Segment::drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CRGBA color, bool fill) const {
504+
if (!isActive() || rx == 0 || ry == 0) return;
511505
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
512506
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
513-
if (cx >= vW<<8 || cy >= vH<<8) return; // center outside segment
514-
int32_t rxSq = (rx * rx);
515-
int32_t rySq = (ry * ry);
507+
// all coodinates and radii are in 16.8 fixed point notation
508+
auto int168 = [](int32_t a) { return a >> 8; }; // convert 16.8 fixed point to integer
509+
auto mul168 = [](int32_t a, int32_t b) { return ((int64_t)a * b) >> 8; }; // 16.8 fixed point multiplication
510+
auto sqr168 = [&](int32_t a) { return mul168(a, a); }; // 16.8 fixed point squaring
511+
auto line = [&](int32_t x1, int32_t x2, int32_t y) { // draws horizontal line between simertically placed points
512+
y = int168(y);
513+
if (y < 0 || y >= vH) return;
514+
uint8_t k1 = 255 - (x1 && 0xFF); // softness factor for first point
515+
uint8_t k2 = x2 && 0xFF;
516+
x1 = int168(x1);
517+
x2 = int168(x2);
518+
if (x2 > x1) {
519+
for (uint32_t x = x1+1; x <= x2-1; x++) if (x >= 0 && x < vW) setPixelColorXYRaw(x, y, color);
520+
if (x1 >= 0 && x1 < vW) setPixelColorXYRaw(x1, y, color.setOpacity(k1)); // soften edges
521+
}
522+
if (x2 >= 0 && x2 < vW) setPixelColorXYRaw(x2, y, color.setOpacity(k2));
523+
};
524+
auto point = [&](int32_t x, int32_t y) { if (x >= 0 && y >= 0 && x < vW && y < vH) setPixelColorXYRaw(x, y, color); }; // draws a single point
525+
auto points = [&](int32_t x, int32_t y) { // draws 4 simertically placed points
526+
point(int168(cx + x), int168(cy + y));
527+
point(int168(cx - x), int168(cy + y));
528+
point(int168(cx + x), int168(cy - y));
529+
point(int168(cx - x), int168(cy - y));
530+
};
531+
532+
int32_t rxSq = sqr168(rx);
533+
int32_t rySq = sqr168(ry);
516534
int32_t x = 0, y = ry;
517-
//auto fEllipse = [&](int32_t x, int32_t y) {
518-
// return (rySq * x * x) + (rxSq * y * y) - (rxSq * rySq);
519-
//};
520535

521-
// Region 1
522-
int32_t d1 = roundf((float)(rySq - (rxSq * ry)) + (0.25f * rxSq)); // initial decision parameter
523-
int32_t dx = 2 * rySq * x;
524-
int32_t dy = 2 * rxSq * y;
536+
int32_t dx = 0; //2 * rySq * x;
537+
int32_t dy = mul168(2 * rxSq, y);
525538

539+
// Region 1
540+
int32_t d1 = (rySq - mul168(rxSq, ry)) + (rxSq >> 2); // initial decision parameter
526541
while (dx < dy) {
527542
if (fill) {
528-
drawLine((cx - x)>>8, (cy + y)>>8, (cx + x)>>8, (cy + y)>>8, color, false);
529-
drawLine((cx - x)>>8, (cy - y)>>8, (cx + x)>>8, (cy - y)>>8, color, false);
543+
line((cx - x), (cx + x), (cy + y));
544+
line((cx - x), (cx + x), (cy - y));
530545
} else {
531-
setWuPixelColor(cx - x, cy + y, color);
532-
setWuPixelColor(cx + x, cy + y, color);
533-
setWuPixelColor(cx - x, cy - y, color);
534-
setWuPixelColor(cx + x, cy - y, color);
546+
points(x, y);
535547
}
536548
if (d1 < 0) {
537549
x += 256; // increment x by 1 in 16.8 pixel notation
@@ -547,16 +559,15 @@ void Segment::drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CR
547559
}
548560

549561
// Region 2
550-
int32_t d2 = (rxSq * ((x + 0.5f) * (x + 0.5f))) + (rxSq * ((y - 1) * (y - 1))) - (rxSq * rySq);
562+
const int32_t x_plus_half = x + 128;
563+
const int32_t y_minus_1 = y - 256;
564+
int32_t d2 = mul168(rySq, sqr168(x_plus_half)) + mul168(rxSq, sqr168(y_minus_1)) - mul168(rxSq, rySq);
551565
while (y >= 0) {
552566
if (fill) {
553-
drawLine((cx - x)>>8, (cy + y)>>8, (cx + x)>>8, (cy + y)>>8, color, false);
554-
drawLine((cx - x)>>8, (cy - y)>>8, (cx + x)>>8, (cy - y)>>8, color, false);
567+
line((cx - x), (cx + x), (cy + y));
568+
line((cx - x), (cx + x), (cy - y));
555569
} else {
556-
setWuPixelColor(cx - x, cy + y, color);
557-
setWuPixelColor(cx + x, cy + y, color);
558-
setWuPixelColor(cx - x, cy - y, color);
559-
setWuPixelColor(cx + x, cy - y, color);
570+
points(x, y);
560571
}
561572
if (d2 > 0) {
562573
y -= 256;
@@ -571,7 +582,6 @@ void Segment::drawEllipse(uint32_t cx, uint32_t cy, uint32_t rx, uint32_t ry, CR
571582
}
572583
}
573584
}
574-
*/
575585

576586
//line function
577587
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGBA c, bool soft) const {
@@ -590,6 +600,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGBA
590600
}
591601

592602
if (soft) {
603+
// https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm
593604
// Xiaolin Wu’s algorithm
594605
const bool steep = dy > dx;
595606
if (steep) {
@@ -602,17 +613,19 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGBA
602613
std::swap(x0,x1);
603614
std::swap(y0,y1);
604615
}
605-
float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0);
606-
float intersectY = y0;
616+
int32_t grad = x1==x0 ? 1 << 8 : ((y1-y0)<<8)/(x1-x0); // gradient in 16.8 fixed point
617+
int32_t intY = (y0<<8); // y intersection in 16.8 fixed point
607618
for (int x = x0; x <= x1; x++) {
608-
uint8_t keep = float(0xFF) * (intersectY-int(intersectY)); // how much color to keep
609-
uint8_t seep = 0xFF - keep; // how much background to keep
610-
int y = int(intersectY);
619+
uint8_t keep = (intY & 0xFF); // fractional part of y in 16.8 fixed point
620+
uint8_t seep = 0xFF - keep;
621+
int y = intY >> 8;
611622
if (steep) std::swap(x,y); // temporaryly swap if steep
612623
// pixel coverage is determined by fractional part of y co-ordinate
613-
if (x >= 0 && y >= 0 && x < vW && y < vH) blendPixelColorXYRaw(x, y, c, seep);
614-
if (x+int(steep) >= 0 && y+int(!steep) >= 0 && x+int(steep) < vW && y+int(!steep) < vH) blendPixelColorXYRaw(x+int(steep), y+int(!steep), c, keep);
615-
intersectY += gradient;
624+
int x2 = x + steep;
625+
int y2 = y + !steep;
626+
if (x >= 0 && y >= 0 && x < vW && y < vH) blendPixelColorXYRaw(x, y, c, seep);
627+
if (x2 >= 0 && y2 >= 0 && x2 < vW && y2 < vH) blendPixelColorXYRaw(x2, y2, c, keep);
628+
intY += grad;
616629
if (steep) std::swap(x,y); // restore if steep
617630
}
618631
} else {
@@ -621,7 +634,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGBA
621634
const int sy = y0<y1 ? 1 : -1; // y step
622635
int err = (dx>dy ? dx : -dy)/2; // error direction
623636
for (;;) {
624-
setPixelColorXYRaw(x0, y0, c);
637+
setPixelColorXYRaw(x0, y0, getPixelColorXYRaw(x0, y0) + c);
625638
if (x0==x1 && y0==y1) break;
626639
int e2 = err;
627640
if (e2 >-dx) { err -= dy; x0 += sx; }
@@ -691,7 +704,7 @@ void Segment::setWuPixelColor(uint32_t x, uint32_t y, CRGBA c) const {
691704
int wu_y = (y >> 8) + ((i >> 1) & 1); // precalculate y
692705
if (/*wu_x >= 0 && wu_y >= 0 && */wu_x < (int)vWidth() && wu_y < (int)vHeight()) {
693706
c.a = wu[i]; // set alpha to weight
694-
setPixelColorXYRaw(wu_x, wu_y, getPixelColorXYRaw(wu_x, wu_y).add(c, true)); // also modifies resulting opacity; should use addPixelColorXYRaw but that crashes ESP
707+
setPixelColorXYRaw(wu_x, wu_y, getPixelColorXYRaw(wu_x, wu_y) + c); // also modifies resulting opacity; should use addPixelColorXYRaw but that crashes ESP
695708
}
696709
}
697710
}

wled00/FX_fcn.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1049,7 +1049,7 @@ void Segment::fade_out(uint8_t rate) const {
10491049
void Segment::fadeToSecondaryBy(uint8_t fadeBy) const {
10501050
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
10511051
// always fade all pixels (blending will take care of grouping, spacing and clipping)
1052-
for (unsigned i = 0; i < length(); i++) setPixelColorRaw(i, getPixelColorRaw(i).nblend(colors[1], fadeBy));
1052+
for (unsigned i = 0; i < length(); i++) blendPixelColorRaw(i, colors[1], fadeBy);
10531053
}
10541054

10551055
// fades all pixels to black using nscale8()

0 commit comments

Comments
 (0)