@@ -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
577587void 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}
0 commit comments