diff --git a/artist/doc.go b/artist/doc.go index 388f29e..c0a0a6f 100644 --- a/artist/doc.go +++ b/artist/doc.go @@ -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 diff --git a/artist/bevel.go b/artist/patterns/bevel.go similarity index 100% rename from artist/bevel.go rename to artist/patterns/bevel.go diff --git a/artist/bordered.go b/artist/patterns/bordered.go similarity index 100% rename from artist/bordered.go rename to artist/patterns/bordered.go diff --git a/artist/checkered.go b/artist/patterns/checkered.go similarity index 100% rename from artist/checkered.go rename to artist/patterns/checkered.go diff --git a/artist/circlebordered.go b/artist/patterns/circlebordered.go similarity index 100% rename from artist/circlebordered.go rename to artist/patterns/circlebordered.go diff --git a/artist/dotted.go b/artist/patterns/dotted.go similarity index 100% rename from artist/dotted.go rename to artist/patterns/dotted.go diff --git a/artist/gradient.go b/artist/patterns/gradient.go similarity index 100% rename from artist/gradient.go rename to artist/patterns/gradient.go diff --git a/artist/noise.go b/artist/patterns/noise.go similarity index 100% rename from artist/noise.go rename to artist/patterns/noise.go diff --git a/artist/split.go b/artist/patterns/split.go similarity index 100% rename from artist/split.go rename to artist/patterns/split.go diff --git a/artist/striped.go b/artist/patterns/striped.go similarity index 100% rename from artist/striped.go rename to artist/patterns/striped.go diff --git a/artist/texture.go b/artist/patterns/texture.go similarity index 100% rename from artist/texture.go rename to artist/patterns/texture.go diff --git a/artist/uniform.go b/artist/patterns/uniform.go similarity index 100% rename from artist/uniform.go rename to artist/patterns/uniform.go diff --git a/artist/wrap.go b/artist/patterns/wrap.go similarity index 100% rename from artist/wrap.go rename to artist/patterns/wrap.go diff --git a/artist/rectangle.go b/artist/rectangle.go deleted file mode 100644 index 5adbe72..0000000 --- a/artist/rectangle.go +++ /dev/null @@ -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)) -} diff --git a/artist/shapes/doc.go b/artist/shapes/doc.go new file mode 100644 index 0000000..ff03446 --- /dev/null +++ b/artist/shapes/doc.go @@ -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 diff --git a/artist/ellipse.go b/artist/shapes/ellipse.go similarity index 96% rename from artist/ellipse.go rename to artist/shapes/ellipse.go index 7dc9d4c..39b6f13 100644 --- a/artist/ellipse.go +++ b/artist/shapes/ellipse.go @@ -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 diff --git a/artist/line.go b/artist/shapes/line.go similarity index 98% rename from artist/line.go rename to artist/shapes/line.go index 733c626..2b92c59 100644 --- a/artist/line.go +++ b/artist/shapes/line.go @@ -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, diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go new file mode 100644 index 0000000..3569b19 --- /dev/null +++ b/artist/shapes/rectangle.go @@ -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 + } +}