This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/artist/shapes/ellipse.go

137 lines
3.9 KiB
Go

package shapes
import "math"
import "image"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
// FillEllipse draws the content of one canvas onto another, clipped by an
// ellipse stretched to the bounds of the source canvas. The offset point
// defines where the origin point of the source canvas is positioned in relation
// to the origin point of the destination canvas. To prevent the entire source
// canvas's bounds from being used, it must be cut with canvas.Cut().
func FillEllipse (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
bounds := source.Bounds()
realWidth, realHeight := bounds.Dx(), bounds.Dy()
bounds = bounds.Intersect(destination.Bounds()).Canon()
if bounds.Empty() { return }
updatedRegion = bounds
width, height := bounds.Dx(), bounds.Dy()
for y := 0; y < height; y ++ {
for x := 0; x < width; x ++ {
xf := (float64(x) + 0.5) / float64(realWidth) - 0.5
yf := (float64(y) + 0.5) / float64(realHeight) - 0.5
if math.Sqrt(xf * xf + yf * yf) <= 0.5 {
dstData[x + offset.X + (y + offset.Y) * dstStride] =
srcData[x + y * srcStride]
}
}}
return
}
// StrokeRectangle is similar to FillEllipse, but it draws an elliptical inset
// outline of the source canvas onto the destination canvas. To prevent the
// entire source canvas's bounds from being used, it must be cut with
// canvas.Cut().
func StrokeEllipse (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
weight int,
) {
if weight < 1 { return }
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
bounds := source.Bounds().Inset(weight - 1)
context := plottingContext {
dstData: dstData,
dstStride: dstStride,
srcData: srcData,
srcStride: srcStride,
weight: weight,
offset: offset,
bounds: bounds.Intersect(destination.Bounds()),
}
bounds.Max.X -= 1
bounds.Max.Y -= 1
radii := image.Pt (
bounds.Dx() / 2,
bounds.Dy() / 2)
center := bounds.Min.Add(radii)
x := float64(0)
y := float64(radii.Y)
// region 1 decision parameter
decision1 :=
float64(radii.Y * radii.Y) -
float64(radii.X * radii.X * radii.Y) +
(0.25 * float64(radii.X) * float64(radii.X))
decisionX := float64(2 * radii.Y * radii.Y * int(x))
decisionY := float64(2 * radii.X * radii.X * int(y))
// draw region 1
for decisionX < decisionY {
context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y))
context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y))
context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y))
context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y))
if (decision1 < 0) {
x ++
decisionX += float64(2 * radii.Y * radii.Y)
decision1 += decisionX + float64(radii.Y * radii.Y)
} else {
x ++
y --
decisionX += float64(2 * radii.Y * radii.Y)
decisionY -= float64(2 * radii.X * radii.X)
decision1 +=
decisionX - decisionY +
float64(radii.Y * radii.Y)
}
}
// region 2 decision parameter
decision2 :=
float64(radii.Y * radii.Y) * (x + 0.5) * (x + 0.5) +
float64(radii.X * radii.X) * (y - 1) * (y - 1) -
float64(radii.X * radii.X * radii.Y * radii.Y)
// draw region 2
for y >= 0 {
context.plotSource(image.Pt( int(x) + center.X, int(y) + center.Y))
context.plotSource(image.Pt(-int(x) + center.X, int(y) + center.Y))
context.plotSource(image.Pt( int(x) + center.X, -int(y) + center.Y))
context.plotSource(image.Pt(-int(x) + center.X, -int(y) + center.Y))
if decision2 > 0 {
y --
decisionY -= float64(2 * radii.X * radii.X)
decision2 += float64(radii.X * radii.X) - decisionY
} else {
y --
x ++
decisionX += float64(2 * radii.Y * radii.Y)
decisionY -= float64(2 * radii.X * radii.X)
decision2 +=
decisionX - decisionY +
float64(radii.X * radii.X)
}
}
}