data-oriented-patterns #9
@ -1,5 +1,2 @@
 | 
				
			|||||||
// Package artist provides a simple 2D drawing library for canvas.Canvas.
 | 
					// 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
 | 
					package artist
 | 
				
			||||||
 | 
				
			|||||||
@ -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
									
								
							
							
						
						
									
										11
									
								
								artist/shapes/doc.go
									
									
									
									
									
										Normal 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
 | 
				
			||||||
@ -1,14 +1,15 @@
 | 
				
			|||||||
package artist
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "math"
 | 
					import "math"
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "image/color"
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FillEllipse draws a filled ellipse with the specified pattern.
 | 
					// FillEllipse draws a filled ellipse with the specified pattern.
 | 
				
			||||||
func FillEllipse (
 | 
					func FillEllipse (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source Pattern,
 | 
						source artist.Pattern,
 | 
				
			||||||
	bounds image.Rectangle,
 | 
						bounds image.Rectangle,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
@ -37,7 +38,7 @@ func FillEllipse (
 | 
				
			|||||||
// and pattern.
 | 
					// and pattern.
 | 
				
			||||||
func StrokeEllipse (
 | 
					func StrokeEllipse (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source Pattern,
 | 
						source artist.Pattern,
 | 
				
			||||||
	weight int,
 | 
						weight int,
 | 
				
			||||||
	bounds image.Rectangle,
 | 
						bounds image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
@ -130,7 +131,7 @@ func StrokeEllipse (
 | 
				
			|||||||
type ellipsePlottingContext struct {
 | 
					type ellipsePlottingContext struct {
 | 
				
			||||||
	data []color.RGBA
 | 
						data []color.RGBA
 | 
				
			||||||
	stride int
 | 
						stride int
 | 
				
			||||||
	source Pattern
 | 
						source artist.Pattern
 | 
				
			||||||
	width, height int
 | 
						width, height int
 | 
				
			||||||
	weight int
 | 
						weight int
 | 
				
			||||||
	bounds image.Rectangle
 | 
						bounds image.Rectangle
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package artist
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "image/color"
 | 
					import "image/color"
 | 
				
			||||||
@ -10,7 +10,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			|||||||
// pattern.
 | 
					// pattern.
 | 
				
			||||||
func Line (
 | 
					func Line (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source Pattern,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
	weight int,
 | 
						weight int,
 | 
				
			||||||
	min image.Point,
 | 
						min image.Point,
 | 
				
			||||||
	max image.Point,
 | 
						max image.Point,
 | 
				
			||||||
							
								
								
									
										84
									
								
								artist/shapes/rectangle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								artist/shapes/rectangle.go
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user