Got rectangles all sorted

This commit is contained in:
Sasha Koshka 2023-02-23 20:55:19 -05:00
parent 48237f5687
commit d167559830
18 changed files with 102 additions and 139 deletions

View File

@ -1,5 +1,2 @@
// Package artist provides a simple 2D drawing library for canvas.Canvas.
// Artist's drawing functions take in things called patterns, which are sampled
// as a source in order to color and texture drawn shapes. Patterns can be
// mixed together and composited to create new, more complex patterns.
package artist

View File

@ -1,130 +0,0 @@
package artist
import "image"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
// Paste transfers one canvas onto another, offset by the specified point.
func Paste (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
sourceBounds :=
source.Bounds().Canon().
Intersect(destination.Bounds().Sub(offset))
if sourceBounds.Empty() { return }
updatedRegion = sourceBounds.Add(offset)
for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
dstData[x + offset.X + (y + offset.Y) * dstStride] =
srcData[x + y * srcStride]
}}
return
}
// FillRectangle draws a filled rectangle with the specified pattern.
func FillRectangle (
destination canvas.Canvas,
source Pattern,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
return FillRectangleClip(destination, source, bounds, bounds)
}
// FillRectangleClip is similar to FillRectangle, but it clips the pattern to
// a specified rectangle mask. That is—the pattern will be queried as if it
// were drawn without the mask, but only the area specified by the intersection
// of bounds and mask will be drawn to.
func FillRectangleClip (
destination canvas.Canvas,
source Pattern,
bounds image.Rectangle,
mask image.Rectangle,
) (
updatedRegion image.Rectangle,
) {
data, stride := destination.Buffer()
realBounds := bounds
bounds =
bounds.Canon().
Intersect(mask.Canon()).
Intersect(destination.Bounds())
if bounds.Empty() { return }
updatedRegion = bounds
realWidth, realHeight := realBounds.Dx(), realBounds.Dy()
patternOffset := realBounds.Min.Sub(bounds.Min)
width, height := bounds.Dx(), bounds.Dy()
for y := 0; y < height; y ++ {
for x := 0; x < width; x ++ {
data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] =
source.AtWhen (
x - patternOffset.X, y - patternOffset.Y,
realWidth, realHeight)
}}
return
}
// FillRectangleShatter shatters a bounding rectangle and draws its tiles in one
// fell swoop.
func FillRectangleShatter (
destination canvas.Canvas,
source Pattern,
glass image.Rectangle,
rocks ...image.Rectangle,
) (
updatedRegions []image.Rectangle,
) {
tiles := shatter.Shatter(glass, rocks...)
for _, tile := range tiles {
FillRectangleClip(destination, source, glass, tile)
}
return tiles
}
// StrokeRectangle draws the outline of a rectangle with the specified line
// weight and pattern.
func StrokeRectangle (
destination canvas.Canvas,
source Pattern,
weight int,
bounds image.Rectangle,
) {
bounds = bounds.Canon()
insetBounds := bounds.Inset(weight)
if insetBounds.Empty() {
FillRectangle(destination, source, bounds)
return
}
// top
FillRectangle (destination, source, image.Rect (
bounds.Min.X, bounds.Min.Y,
bounds.Max.X, insetBounds.Min.Y))
// bottom
FillRectangle (destination, source, image.Rect (
bounds.Min.X, insetBounds.Max.Y,
bounds.Max.X, bounds.Max.Y))
// left
FillRectangle (destination, source, image.Rect (
bounds.Min.X, insetBounds.Min.Y,
insetBounds.Min.X, insetBounds.Max.Y))
// right
FillRectangle (destination, source, image.Rect (
insetBounds.Max.X, insetBounds.Min.Y,
bounds.Max.X, insetBounds.Max.Y))
}

11
artist/shapes/doc.go Normal file
View File

@ -0,0 +1,11 @@
// Package shapes provides some basic shape drawing routines.
//
// A word about patterns:
//
// Most drawing routines have a version that samples from other canvases, and a
// version that samples from a solid color. None of these routines can use
// patterns directly, but it is entirely possible to have a pattern draw to an
// off-screen canvas and then draw a shape based on that canvas. As a little
// bonus, you can save the canvas for later so you don't have to render the
// pattern again when you need to redraw the shape.
package shapes

View File

@ -1,14 +1,15 @@
package artist
package shapes
import "math"
import "image"
import "image/color"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
// FillEllipse draws a filled ellipse with the specified pattern.
func FillEllipse (
destination canvas.Canvas,
source Pattern,
source artist.Pattern,
bounds image.Rectangle,
) (
updatedRegion image.Rectangle,
@ -37,7 +38,7 @@ func FillEllipse (
// and pattern.
func StrokeEllipse (
destination canvas.Canvas,
source Pattern,
source artist.Pattern,
weight int,
bounds image.Rectangle,
) {
@ -130,7 +131,7 @@ func StrokeEllipse (
type ellipsePlottingContext struct {
data []color.RGBA
stride int
source Pattern
source artist.Pattern
width, height int
weight int
bounds image.Rectangle

View File

@ -1,4 +1,4 @@
package artist
package shapes
import "image"
import "image/color"
@ -10,7 +10,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
// pattern.
func Line (
destination canvas.Canvas,
source Pattern,
source canvas.Canvas,
weight int,
min image.Point,
max image.Point,

View File

@ -0,0 +1,84 @@
package shapes
import "image"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/shatter"
// FillRectangle draws a rectangular subset of one canvas onto the other. 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 from being drawn, it must be cut with
// canvas.Cut().
func FillRectangle (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
) (
updatedRegion image.Rectangle,
) {
dstData, dstStride := destination.Buffer()
srcData, srcStride := source.Buffer()
sourceBounds :=
source.Bounds().Canon().
Intersect(destination.Bounds().Sub(offset))
if sourceBounds.Empty() { return }
updatedRegion = sourceBounds.Add(offset)
for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
dstData[x + offset.X + (y + offset.Y) * dstStride] =
srcData[x + y * srcStride]
}}
return
}
// StrokeRectangle is similar to FillRectangle, but it draws an 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 StrokeRectangle (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
weight int,
) {
bounds := source.Bounds()
insetBounds := bounds.Inset(weight)
if insetBounds.Empty() {
FillRectangle(destination, source, offset)
return
}
top := image.Rect (
bounds.Min.X, bounds.Min.Y,
bounds.Max.X, insetBounds.Min.Y)
bottom := image.Rect (
bounds.Min.X, insetBounds.Max.Y,
bounds.Max.X, bounds.Max.Y)
left := image.Rect (
bounds.Min.X, insetBounds.Min.Y,
insetBounds.Min.X, insetBounds.Max.Y)
right := image.Rect (
insetBounds.Max.X, insetBounds.Min.Y,
bounds.Max.X, insetBounds.Max.Y)
FillRectangle (destination, canvas.Cut(source, top), offset)
FillRectangle (destination, canvas.Cut(source, bottom), offset)
FillRectangle (destination, canvas.Cut(source, left), offset)
FillRectangle (destination, canvas.Cut(source, right), offset)
}
// FillRectangleShatter is like FillRectangle, but it does not draw in areas
// specified in "rocks".
func FillRectangleShatter (
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
rocks []image.Rectangle,
) {
tiles := shatter.Shatter(source.Bounds())
for _, tile := range tiles {
tile
}
}