From 857e5ec09879cb77551c0b0c7e8fef0657661254 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Tue, 16 Jul 2019 18:27:00 -0700 Subject: [PATCH] Better Ellipse Drawing Algorithm --- ellipse.go | 64 ++++++++++++++++++++++++++++++ functions.go | 8 ++++ shapes.go | 107 ++++----------------------------------------------- 3 files changed, 79 insertions(+), 100 deletions(-) create mode 100644 ellipse.go diff --git a/ellipse.go b/ellipse.go new file mode 100644 index 0000000..d259f3c --- /dev/null +++ b/ellipse.go @@ -0,0 +1,64 @@ +package render + +// MidpointEllipse implements an ellipse plotting algorithm. +func MidpointEllipse(center, radius Point) chan Point { + yield := make(chan Point) + go func() { + + var ( + pos = NewPoint(radius.X, 0) + delta = NewPoint( + 2*radius.Y*radius.Y*pos.X, + 2*radius.X*radius.X*pos.Y, + ) + err = radius.X*radius.X - + radius.Y*radius.Y*radius.X + + (radius.Y*radius.Y)/4 + ) + + for delta.Y < delta.X { + yield <- NewPoint(center.X+pos.X, center.Y+pos.Y) + yield <- NewPoint(center.X+pos.X, center.Y-pos.Y) + yield <- NewPoint(center.X-pos.X, center.Y+pos.Y) + yield <- NewPoint(center.X-pos.X, center.Y-pos.Y) + + pos.Y++ + + if err < 0 { + delta.Y += 2 * radius.X * radius.X + err += delta.Y + radius.X*radius.X + } else { + pos.X-- + delta.Y += 2 * radius.X * radius.X + delta.X -= 2 * radius.Y * radius.Y + err += delta.Y - delta.X + radius.X*radius.X + } + } + + err = radius.X*radius.X*(pos.Y*pos.Y+pos.Y) + + radius.Y*radius.Y*(pos.X-1)*(pos.X-1) - + radius.Y*radius.Y*radius.X*radius.X + + for pos.X >= 0 { + yield <- NewPoint(center.X+pos.X, center.Y+pos.Y) + yield <- NewPoint(center.X+pos.X, center.Y-pos.Y) + yield <- NewPoint(center.X-pos.X, center.Y+pos.Y) + yield <- NewPoint(center.X-pos.X, center.Y-pos.Y) + + pos.X-- + + if err > 0 { + delta.X -= 2 * radius.Y * radius.Y + err += radius.Y*radius.Y - delta.X + } else { + pos.Y++ + delta.Y += 2 * radius.X * radius.X + delta.X -= 2 * radius.Y * radius.Y + err += delta.Y - delta.X + radius.Y*radius.Y + } + } + + close(yield) + }() + return yield +} diff --git a/functions.go b/functions.go index d049f5d..7b719a3 100644 --- a/functions.go +++ b/functions.go @@ -98,3 +98,11 @@ func TrimBox(src, dst *Rect, p Point, S Rect, thickness int32) { dst.W = S.W - thickness } } + +// AbsInt32 returns the absolute value of an int32. +func AbsInt32(v int32) int32 { + if v < 0 { + return -v + } + return v +} diff --git a/shapes.go b/shapes.go index 44caefe..fb873cb 100644 --- a/shapes.go +++ b/shapes.go @@ -2,8 +2,6 @@ package render import ( "math" - - "git.kirsle.net/apps/doodle/pkg/log" ) // IterLine is a generator that returns the X,Y coordinates to draw a line. @@ -93,106 +91,15 @@ func IterRect(p1, p2 Point) chan Point { return generator } -// IterEllipse is a generator that draws out the pixels of an ellipse. -func IterEllipse(rx, ry, xc, yc float32) chan Point { - generator := make(chan Point) - - mkPoint := func(x, y float32) Point { - return NewPoint(int32(x), int32(y)) - } - - go func() { - var ( - dx float32 - dy float32 - d1 float32 - d2 float32 - x float32 - y = ry - ) - - d1 = (ry * ry) - (rx * rx * ry) + (0.25 * rx * rx) - dx = 2 * ry * ry * x - dy = 2 * rx * rx * y - - // For region 1 - for dx < dy { - // Yields points based on 4-way symmetry. - for _, point := range []Point{ - mkPoint(x+xc, y+yc), - mkPoint(-x+xc, y+yc), - mkPoint(x+xc, -y+yc), - mkPoint(-x+xc, -y+yc), - } { - generator <- point - } - - if d1 < 0 { - x++ - dx = dx + (2 * ry * ry) - d1 = d1 + dx + (ry * ry) - } else { - x++ - y-- - dx = dx + (2 * ry * ry) - dy = dy - (2 * rx * rx) - d1 = d1 + dx - dy + (ry * ry) - } - } - - d2 = ((ry * ry) + ((x + 0.5) * (x + 0.5))) + - ((rx * rx) * ((y - 1) * (y - 1))) - - (rx * rx * ry * ry) - - // Region 2 - for y >= 0 { - // Yields points based on 4-way symmetry. - for _, point := range []Point{ - mkPoint(x+xc, y+yc), - mkPoint(-x+xc, y+yc), - mkPoint(x+xc, -y+yc), - mkPoint(-x+xc, -y+yc), - } { - generator <- point - } - - if d2 > 0 { - y-- - dy = dy - (2 * rx * rx) - d2 = d2 + (rx * rx) - dy - } else { - y-- - x++ - dx = dx + (2 * ry * ry) - dy = dy - (2 * rx * rx) - d2 = d2 + dx - dy + (rx * rx) - } - } - - close(generator) - }() - - return generator -} - -// IterEllipse2 iterates an Ellipse using two Points as the top-left and +// IterEllipse iterates an Ellipse using two Points as the top-left and // bottom-right corners of a rectangle that encompasses the ellipse. -func IterEllipse2(A, B Point) chan Point { +func IterEllipse(A, B Point) chan Point { var ( - // xc = float32(A.X+B.X) / 2 - // yc = float32(A.Y+B.Y) / 2 - xc = float32(B.X) - yc = float32(B.Y) - rx = float32(B.X - A.X) - ry = float32(B.Y - A.Y) + width = AbsInt32(B.X - A.X) + height = AbsInt32(B.Y - A.Y) + radius = NewPoint(width/2, height/2) + center = NewPoint(AbsInt32(B.X-radius.X), AbsInt32(B.Y-radius.Y)) ) - if rx < 0 { - rx = -rx - } - if ry < 0 { - ry = -ry - } - log.Info("Ellipse btwn=%s-%s radius=%f,%f at center %f,%f", A, B, rx, ry, xc, yc) - return IterEllipse(rx, ry, xc, yc) + return MidpointEllipse(center, radius) }