From 524ebebedbabcedc01e08e0bee8ee7ed08688613 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 14 Jul 2019 14:18:44 -0700 Subject: [PATCH] Lemon-shaped Ellipse Tool (WIP) * Add initial Ellipse Tool to the Editor Mode. Currently there's something wrong with the algorithm and the ellipses have a sort of 'lemon shape' to them. * Refactor the IterLine/IterLine2 functions to be more consistent. IterLine used to be the raw algorithm that took a bunch of coordinate numbers and IterLine2 took two render.Point's and was the main one used throughout the app. Now, IterLine takes the two Points and the raw algorithm function removed. --- canvas/draw.go | 2 +- interface.go | 87 ---------------------- shapes.go | 198 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 88 deletions(-) create mode 100644 shapes.go diff --git a/canvas/draw.go b/canvas/draw.go index 1f7b8be..f16381c 100644 --- a/canvas/draw.go +++ b/canvas/draw.go @@ -44,7 +44,7 @@ func (e *Engine) DrawPoint(color render.Color, point render.Point) { // DrawLine draws a line between two points. func (e *Engine) DrawLine(color render.Color, a, b render.Point) { e.canvas.ctx2d.Set("fillStyle", RGBA(color)) - for pt := range render.IterLine2(a, b) { + for pt := range render.IterLine(a, b) { e.canvas.ctx2d.Call("fillRect", int(pt.X), int(pt.Y), diff --git a/interface.go b/interface.go index 17883f9..c30948a 100644 --- a/interface.go +++ b/interface.go @@ -3,7 +3,6 @@ package render import ( "fmt" "image" - "math" "git.kirsle.net/apps/doodle/lib/events" ) @@ -195,89 +194,3 @@ var ( Purple = RGBA(153, 0, 153, 255) Pink = RGBA(255, 153, 255, 255) ) - -// IterLine is a generator that returns the X,Y coordinates to draw a line. -// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) -func IterLine(x1, y1, x2, y2 int32) chan Point { - generator := make(chan Point) - - go func() { - var ( - dx = float64(x2 - x1) - dy = float64(y2 - y1) - ) - var step float64 - if math.Abs(dx) >= math.Abs(dy) { - step = math.Abs(dx) - } else { - step = math.Abs(dy) - } - - dx = dx / step - dy = dy / step - x := float64(x1) - y := float64(y1) - for i := 0; i <= int(step); i++ { - generator <- Point{ - X: int32(x), - Y: int32(y), - } - x += dx - y += dy - } - - close(generator) - }() - - return generator -} - -// IterLine2 works with two Points rather than four coordinates. -func IterLine2(p1 Point, p2 Point) chan Point { - return IterLine(p1.X, p1.Y, p2.X, p2.Y) -} - -// IterRect loops through all the points forming a rectangle between the -// top-left point and the bottom-right point. -func IterRect(p1, p2 Point) chan Point { - generator := make(chan Point) - - go func() { - var ( - TopLeft = p1 - BottomRight = p2 - TopRight = Point{ - X: BottomRight.X, - Y: TopLeft.Y, - } - BottomLeft = Point{ - X: TopLeft.X, - Y: BottomRight.Y, - } - dedupe = map[Point]interface{}{} - ) - - // Trace all four edges and yield it. - var edges = []struct { - A Point - B Point - }{ - {TopLeft, TopRight}, - {TopLeft, BottomLeft}, - {BottomLeft, BottomRight}, - {TopRight, BottomRight}, - } - for _, edge := range edges { - for pt := range IterLine2(edge.A, edge.B) { - if _, ok := dedupe[pt]; !ok { - generator <- pt - dedupe[pt] = nil - } - } - } - - close(generator) - }() - - return generator -} diff --git a/shapes.go b/shapes.go new file mode 100644 index 0000000..44caefe --- /dev/null +++ b/shapes.go @@ -0,0 +1,198 @@ +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. +// https://en.wikipedia.org/wiki/Digital_differential_analyzer_(graphics_algorithm) +func IterLine(p1 Point, p2 Point) chan Point { + var ( + x1 = p1.X + y1 = p1.Y + x2 = p2.X + y2 = p2.Y + ) + generator := make(chan Point) + + go func() { + var ( + dx = float64(x2 - x1) + dy = float64(y2 - y1) + ) + var step float64 + if math.Abs(dx) >= math.Abs(dy) { + step = math.Abs(dx) + } else { + step = math.Abs(dy) + } + + dx = dx / step + dy = dy / step + x := float64(x1) + y := float64(y1) + for i := 0; i <= int(step); i++ { + generator <- Point{ + X: int32(x), + Y: int32(y), + } + x += dx + y += dy + } + + close(generator) + }() + + return generator +} + +// IterRect loops through all the points forming a rectangle between the +// top-left point and the bottom-right point. +func IterRect(p1, p2 Point) chan Point { + generator := make(chan Point) + + go func() { + var ( + TopLeft = p1 + BottomRight = p2 + TopRight = Point{ + X: BottomRight.X, + Y: TopLeft.Y, + } + BottomLeft = Point{ + X: TopLeft.X, + Y: BottomRight.Y, + } + dedupe = map[Point]interface{}{} + ) + + // Trace all four edges and yield it. + var edges = []struct { + A Point + B Point + }{ + {TopLeft, TopRight}, + {TopLeft, BottomLeft}, + {BottomLeft, BottomRight}, + {TopRight, BottomRight}, + } + for _, edge := range edges { + for pt := range IterLine(edge.A, edge.B) { + if _, ok := dedupe[pt]; !ok { + generator <- pt + dedupe[pt] = nil + } + } + } + + close(generator) + }() + + 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 +// bottom-right corners of a rectangle that encompasses the ellipse. +func IterEllipse2(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) + ) + + 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) +}