Changed pkg.go.dev link
This commit is contained in:
parent
33c787d70b
commit
54ea1c283f
@ -20,5 +20,5 @@ it. It will be placed in `~/.local/lib/nasin/plugins`.
|
|||||||
You can find out more about how to use Tomo and Nasin by visiting the examples
|
You can find out more about how to use Tomo and Nasin by visiting the examples
|
||||||
directory, or pull up the documentation by running `godoc` within the
|
directory, or pull up the documentation by running `godoc` within the
|
||||||
repository. You can also view it on the web on
|
repository. You can also view it on the web on
|
||||||
[pkg.go.dev](https://pkg.go.dev/git.tebibyte.media/sashakoshka/tomo) (although
|
[pkg.go.dev](https://pkg.go.dev/git.tebibyte.media/tomo/tomo) (although
|
||||||
it may be slightly out of date).
|
it may be slightly out of date).
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
// Package artutil provides utility functions for working with graphical types
|
|
||||||
// defined in artist, canvas, and image.
|
|
||||||
package artutil
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
import "tomo/artist"
|
|
||||||
import "tomo/shatter"
|
|
||||||
|
|
||||||
// Fill fills the destination canvas with the given pattern.
|
|
||||||
func Fill (destination artist.Canvas, source artist.Pattern) (updated image.Rectangle) {
|
|
||||||
source.Draw(destination, destination.Bounds())
|
|
||||||
return destination.Bounds()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawClip lets you draw several subsets of a pattern at once.
|
|
||||||
func DrawClip (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Pattern,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
subsets ...image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
for _, subset := range subsets {
|
|
||||||
source.Draw(artist.Cut(destination, subset), bounds)
|
|
||||||
updatedRegion = updatedRegion.Union(subset)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas
|
|
||||||
// specified by "rocks".
|
|
||||||
func DrawShatter (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Pattern,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
rocks ...image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
tiles := shatter.Shatter(bounds, rocks...)
|
|
||||||
return DrawClip(destination, source, bounds, tiles...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllocateSample returns a new canvas containing the result of a pattern. The
|
|
||||||
// resulting canvas can be sourced from shape drawing functions. I beg of you
|
|
||||||
// please do not call this every time you need to draw a shape with a pattern on
|
|
||||||
// it because that is horrible and cruel to the computer.
|
|
||||||
func AllocateSample (source artist.Pattern, width, height int) artist.Canvas {
|
|
||||||
allocated := artist.NewBasicCanvas(width, height)
|
|
||||||
Fill(allocated, source)
|
|
||||||
return allocated
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hex creates a color.RGBA value from an RGBA integer value.
|
|
||||||
func Hex (color uint32) (c color.RGBA) {
|
|
||||||
c.A = uint8(color)
|
|
||||||
c.B = uint8(color >> 8)
|
|
||||||
c.G = uint8(color >> 16)
|
|
||||||
c.R = uint8(color >> 24)
|
|
||||||
return
|
|
||||||
}
|
|
111
artist/canvas.go
111
artist/canvas.go
@ -1,111 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/draw"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Image represents an immutable canvas.
|
|
||||||
type Image interface {
|
|
||||||
image.Image
|
|
||||||
RGBAAt (x, y int) color.RGBA
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canvas is like draw.Image but is also able to return a raw pixel buffer for
|
|
||||||
// more efficient drawing. This interface can be easily satisfied using a
|
|
||||||
// BasicCanvas struct.
|
|
||||||
type Canvas interface {
|
|
||||||
draw.Image
|
|
||||||
Buffer () (data []color.RGBA, stride int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicCanvas is a general purpose implementation of tomo.Canvas.
|
|
||||||
type BasicCanvas struct {
|
|
||||||
pix []color.RGBA
|
|
||||||
stride int
|
|
||||||
rect image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBasicCanvas creates a new basic canvas with the specified width and
|
|
||||||
// height, allocating a buffer for it.
|
|
||||||
func NewBasicCanvas (width, height int) (canvas BasicCanvas) {
|
|
||||||
canvas.pix = make([]color.RGBA, height * width)
|
|
||||||
canvas.stride = width
|
|
||||||
canvas.rect = image.Rect(0, 0, width, height)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromImage creates a new BasicCanvas from an image.Image.
|
|
||||||
func FromImage (img image.Image) (canvas BasicCanvas) {
|
|
||||||
bounds := img.Bounds()
|
|
||||||
canvas = NewBasicCanvas(bounds.Dx(), bounds.Dy())
|
|
||||||
point := image.Point { }
|
|
||||||
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
|
|
||||||
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
|
|
||||||
canvasPoint := point.Sub(bounds.Min)
|
|
||||||
canvas.Set (
|
|
||||||
canvasPoint.X, canvasPoint.Y,
|
|
||||||
img.At(point.X, point.Y))
|
|
||||||
}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// you know what it do
|
|
||||||
func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
|
|
||||||
return canvas.rect
|
|
||||||
}
|
|
||||||
|
|
||||||
// you know what it do
|
|
||||||
func (canvas BasicCanvas) At (x, y int) (color.Color) {
|
|
||||||
if !image.Pt(x, y).In(canvas.rect) { return nil }
|
|
||||||
return canvas.pix[x + y * canvas.stride]
|
|
||||||
}
|
|
||||||
|
|
||||||
// you know what it do
|
|
||||||
func (canvas BasicCanvas) ColorModel () (model color.Model) {
|
|
||||||
return color.RGBAModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// you know what it do
|
|
||||||
func (canvas BasicCanvas) Set (x, y int, c color.Color) {
|
|
||||||
if !image.Pt(x, y).In(canvas.rect) { return }
|
|
||||||
r, g, b, a := c.RGBA()
|
|
||||||
canvas.pix[x + y * canvas.stride] = color.RGBA {
|
|
||||||
R: uint8(r >> 8),
|
|
||||||
G: uint8(g >> 8),
|
|
||||||
B: uint8(b >> 8),
|
|
||||||
A: uint8(a >> 8),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// you know what it do
|
|
||||||
func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
|
|
||||||
return canvas.pix, canvas.stride
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reallocate efficiently reallocates the canvas. The data within will be
|
|
||||||
// garbage. This method will do nothing if this is a cut image.
|
|
||||||
func (canvas *BasicCanvas) Reallocate (width, height int) {
|
|
||||||
if canvas.rect.Min != (image.Point { }) { return }
|
|
||||||
|
|
||||||
previousLen := len(canvas.pix)
|
|
||||||
newLen := width * height
|
|
||||||
bigger := newLen > previousLen
|
|
||||||
smaller := newLen < previousLen / 2
|
|
||||||
if bigger || smaller {
|
|
||||||
canvas.pix = make (
|
|
||||||
[]color.RGBA,
|
|
||||||
((height * width) / 4096) * 4096 + 4096)
|
|
||||||
}
|
|
||||||
canvas.stride = width
|
|
||||||
canvas.rect = image.Rect(0, 0, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cut returns a sub-canvas of a given canvas.
|
|
||||||
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
|
|
||||||
// println(canvas.Bounds().String(), bounds.String())
|
|
||||||
bounds = bounds.Intersect(canvas.Bounds())
|
|
||||||
if bounds.Empty() { return }
|
|
||||||
reduced.rect = bounds
|
|
||||||
reduced.pix, reduced.stride = canvas.Buffer()
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
// Package artist provides a simple 2D drawing library for canvas.Canvas.
|
|
||||||
package artist
|
|
@ -1,13 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
type Icon interface {
|
|
||||||
// Draw draws the icon to the destination canvas at the specified point,
|
|
||||||
// using the specified color (if the icon is monochrome).
|
|
||||||
Draw (destination Canvas, color color.RGBA, at image.Point)
|
|
||||||
|
|
||||||
// Bounds returns the bounds of the icon.
|
|
||||||
Bounds () image.Rectangle
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
|
|
||||||
// Side represents one side of a rectangle.
|
|
||||||
type Side int; const (
|
|
||||||
SideTop Side = iota
|
|
||||||
SideRight
|
|
||||||
SideBottom
|
|
||||||
SideLeft
|
|
||||||
)
|
|
||||||
|
|
||||||
// Inset represents an inset amount for all four sides of a rectangle. The top
|
|
||||||
// side is at index zero, the right at index one, the bottom at index two, and
|
|
||||||
// the left at index three. These values may be negative.
|
|
||||||
type Inset [4]int
|
|
||||||
|
|
||||||
// I allows you to create an inset in a CSS-ish way:
|
|
||||||
//
|
|
||||||
// - One argument: all sides are set to this value
|
|
||||||
// - Two arguments: the top and bottom sides are set to the first value, and
|
|
||||||
// the left and right sides are set to the second value.
|
|
||||||
// - Three arguments: the top side is set by the first value, the left and
|
|
||||||
// right sides are set by the second vaue, and the bottom side is set by the
|
|
||||||
// third value.
|
|
||||||
// - Four arguments: each value corresponds to a side.
|
|
||||||
//
|
|
||||||
// This function will panic if an argument count that isn't one of these is
|
|
||||||
// given.
|
|
||||||
func I (sides ...int) Inset {
|
|
||||||
switch len(sides) {
|
|
||||||
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
|
|
||||||
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
|
|
||||||
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
|
|
||||||
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
|
|
||||||
default: panic("I: illegal argument count.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply returns the given rectangle, shrunk on all four sides by the given
|
|
||||||
// inset. If a measurment of the inset is negative, that side will instead be
|
|
||||||
// expanded outward. If the rectangle's dimensions cannot be reduced any
|
|
||||||
// further, an empty rectangle near its center will be returned.
|
|
||||||
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
|
|
||||||
smaller = bigger
|
|
||||||
if smaller.Dx() < inset[3] + inset[1] {
|
|
||||||
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
|
|
||||||
smaller.Max.X = smaller.Min.X
|
|
||||||
} else {
|
|
||||||
smaller.Min.X += inset[3]
|
|
||||||
smaller.Max.X -= inset[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
if smaller.Dy() < inset[0] + inset[2] {
|
|
||||||
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
|
|
||||||
smaller.Max.Y = smaller.Min.Y
|
|
||||||
} else {
|
|
||||||
smaller.Min.Y += inset[0]
|
|
||||||
smaller.Max.Y -= inset[2]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inverse returns a negated version of the inset.
|
|
||||||
func (inset Inset) Inverse () (prime Inset) {
|
|
||||||
return Inset {
|
|
||||||
inset[0] * -1,
|
|
||||||
inset[1] * -1,
|
|
||||||
inset[2] * -1,
|
|
||||||
inset[3] * -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal returns the sum of SideRight and SideLeft.
|
|
||||||
func (inset Inset) Horizontal () int {
|
|
||||||
return inset[SideRight] + inset[SideLeft]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertical returns the sum of SideTop and SideBottom.
|
|
||||||
func (inset Inset) Vertical () int {
|
|
||||||
return inset[SideTop] + inset[SideBottom]
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
|
|
||||||
// Pattern is capable of drawing to a canvas within the bounds of a given
|
|
||||||
// clipping rectangle.
|
|
||||||
type Pattern interface {
|
|
||||||
// Draw draws the pattern onto the destination canvas, using the
|
|
||||||
// specified bounds. The given bounds can be smaller or larger than the
|
|
||||||
// bounds of the destination canvas. The destination canvas can be cut
|
|
||||||
// using canvas.Cut() to draw only a specific subset of a pattern.
|
|
||||||
Draw (destination Canvas, bounds image.Rectangle)
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
package patterns
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "tomo/artist"
|
|
||||||
|
|
||||||
// Border is a pattern that behaves similarly to border-image in CSS. It divides
|
|
||||||
// a source canvas into nine sections...
|
|
||||||
//
|
|
||||||
// Inset[1]
|
|
||||||
// ┌──┴──┐
|
|
||||||
// ┌─┌─────┬─────┬─────┐
|
|
||||||
// Inset[0]─┤ │ 0 │ 1 │ 2 │
|
|
||||||
// └─├─────┼─────┼─────┤
|
|
||||||
// │ 3 │ 4 │ 5 │
|
|
||||||
// ├─────┼─────┼─────┤─┐
|
|
||||||
// │ 6 │ 7 │ 8 │ ├─Inset[2]
|
|
||||||
// └─────┴─────┴─────┘─┘
|
|
||||||
// └──┬──┘
|
|
||||||
// Inset[3]
|
|
||||||
//
|
|
||||||
// ... Where the bounds of section 4 are defined as the application of the
|
|
||||||
// pattern's inset to the canvas's bounds. The bounds of the other eight
|
|
||||||
// sections are automatically sized around it.
|
|
||||||
//
|
|
||||||
// When drawn to a destination canvas, the bounds of sections 1, 3, 4, 5, and 7
|
|
||||||
// are expanded or contracted to fit the given drawing bounds. All sections are
|
|
||||||
// rendered as if they are Texture patterns, meaning these flexible sections
|
|
||||||
// will repeat to fill in any empty space.
|
|
||||||
//
|
|
||||||
// This pattern can be used to make a static image texture into something that
|
|
||||||
// responds well to being resized.
|
|
||||||
type Border struct {
|
|
||||||
artist.Canvas
|
|
||||||
artist.Inset
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw draws the border pattern onto the destination canvas within the given
|
|
||||||
// bounds.
|
|
||||||
func (pattern Border) Draw (destination artist.Canvas, bounds image.Rectangle) {
|
|
||||||
drawBounds := bounds.Canon().Intersect(destination.Bounds())
|
|
||||||
if drawBounds.Empty() { return }
|
|
||||||
|
|
||||||
srcSections := nonasect(pattern.Bounds(), pattern.Inset)
|
|
||||||
srcTextures := [9]Texture { }
|
|
||||||
for index, section := range srcSections {
|
|
||||||
srcTextures[index].Canvas = artist.Cut(pattern, section)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstSections := nonasect(bounds, pattern.Inset)
|
|
||||||
for index, section := range dstSections {
|
|
||||||
srcTextures[index].Draw(destination, section)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonasect (bounds image.Rectangle, inset artist.Inset) [9]image.Rectangle {
|
|
||||||
center := inset.Apply(bounds)
|
|
||||||
return [9]image.Rectangle {
|
|
||||||
// top
|
|
||||||
image.Rectangle {
|
|
||||||
bounds.Min,
|
|
||||||
center.Min },
|
|
||||||
image.Rect (
|
|
||||||
center.Min.X, bounds.Min.Y,
|
|
||||||
center.Max.X, center.Min.Y),
|
|
||||||
image.Rect (
|
|
||||||
center.Max.X, bounds.Min.Y,
|
|
||||||
bounds.Max.X, center.Min.Y),
|
|
||||||
|
|
||||||
// center
|
|
||||||
image.Rect (
|
|
||||||
bounds.Min.X, center.Min.Y,
|
|
||||||
center.Min.X, center.Max.Y),
|
|
||||||
center,
|
|
||||||
image.Rect (
|
|
||||||
center.Max.X, center.Min.Y,
|
|
||||||
bounds.Max.X, center.Max.Y),
|
|
||||||
|
|
||||||
// bottom
|
|
||||||
image.Rect (
|
|
||||||
bounds.Min.X, center.Max.Y,
|
|
||||||
center.Min.X, bounds.Max.Y),
|
|
||||||
image.Rect (
|
|
||||||
center.Min.X, center.Max.Y,
|
|
||||||
center.Max.X, bounds.Max.Y),
|
|
||||||
image.Rect (
|
|
||||||
center.Max.X, center.Max.Y,
|
|
||||||
bounds.Max.X, bounds.Max.Y),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
// Package patterns provides a basic set of types that satisfy the
|
|
||||||
// artist.Pattern interface.
|
|
||||||
package patterns
|
|
@ -1,77 +0,0 @@
|
|||||||
package patterns
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "tomo/artist"
|
|
||||||
|
|
||||||
// Texture is a pattern that tiles the content of a canvas both horizontally and
|
|
||||||
// vertically.
|
|
||||||
type Texture struct {
|
|
||||||
artist.Canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw tiles the pattern's canvas within the given bounds. The minimum
|
|
||||||
// point of the pattern's canvas will be lined up with the minimum point of the
|
|
||||||
// bounding rectangle.
|
|
||||||
func (pattern Texture) Draw (destination artist.Canvas, bounds image.Rectangle) {
|
|
||||||
dstBounds := bounds.Canon().Intersect(destination.Bounds())
|
|
||||||
if dstBounds.Empty() { return }
|
|
||||||
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
srcData, srcStride := pattern.Buffer()
|
|
||||||
srcBounds := pattern.Bounds()
|
|
||||||
|
|
||||||
// offset is a vector that is added to points in destination space to
|
|
||||||
// convert them to points in source space
|
|
||||||
offset := srcBounds.Min.Sub(bounds.Min)
|
|
||||||
|
|
||||||
// calculate the starting position in source space
|
|
||||||
srcPoint := dstBounds.Min.Add(offset)
|
|
||||||
srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X)
|
|
||||||
srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y)
|
|
||||||
srcStartPoint := srcPoint
|
|
||||||
|
|
||||||
// for each row
|
|
||||||
dstPoint := image.Point { }
|
|
||||||
for dstPoint.Y = dstBounds.Min.Y; dstPoint.Y < dstBounds.Max.Y; dstPoint.Y ++ {
|
|
||||||
srcPoint.X = srcStartPoint.X
|
|
||||||
dstPoint.X = dstBounds.Min.X
|
|
||||||
dstYComponent := dstPoint.Y * dstStride
|
|
||||||
srcYComponent := srcPoint.Y * srcStride
|
|
||||||
|
|
||||||
// for each pixel in the row
|
|
||||||
for {
|
|
||||||
// draw pixel
|
|
||||||
dstIndex := dstYComponent + dstPoint.X
|
|
||||||
srcIndex := srcYComponent + srcPoint.X
|
|
||||||
dstData[dstIndex] = srcData[srcIndex]
|
|
||||||
|
|
||||||
// increment X in source space. wrap to start if out of
|
|
||||||
// bounds.
|
|
||||||
srcPoint.X ++
|
|
||||||
if srcPoint.X >= srcBounds.Max.X {
|
|
||||||
srcPoint.X = srcBounds.Min.X
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment X in destination space. stop drawing this
|
|
||||||
// row if out of bounds.
|
|
||||||
dstPoint.X ++
|
|
||||||
if dstPoint.X >= dstBounds.Max.X {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment row in source space. wrap to start if out of
|
|
||||||
// bounds.
|
|
||||||
srcPoint.Y ++
|
|
||||||
if srcPoint.Y >= srcBounds.Max.Y {
|
|
||||||
srcPoint.Y = srcBounds.Min.Y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrap (value, min, max int) int {
|
|
||||||
difference := max - min
|
|
||||||
value = (value - min) % difference
|
|
||||||
if value < 0 { value += difference }
|
|
||||||
return value + min
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package patterns
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
import "tomo/artist"
|
|
||||||
import "tomo/artist/shapes"
|
|
||||||
import "tomo/artist/artutil"
|
|
||||||
|
|
||||||
// Uniform is a pattern that draws a solid color.
|
|
||||||
type Uniform color.RGBA
|
|
||||||
|
|
||||||
// Draw fills the bounding rectangle with the pattern's color.
|
|
||||||
func (pattern Uniform) Draw (destination artist.Canvas, bounds image.Rectangle) {
|
|
||||||
shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uhex creates a new Uniform pattern from an RGBA integer value.
|
|
||||||
func Uhex (color uint32) (uniform Uniform) {
|
|
||||||
return Uniform(artutil.Hex(color))
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
// Package shapes provides some basic shape drawing routines.
|
|
||||||
//
|
|
||||||
// A word about using patterns with shape routines:
|
|
||||||
//
|
|
||||||
// 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,231 +0,0 @@
|
|||||||
package shapes
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
import "tomo/artist"
|
|
||||||
|
|
||||||
// TODO: redo fill ellipse, stroke ellipse, etc. so that it only takes in
|
|
||||||
// destination and source, using the bounds of destination as the bounds of the
|
|
||||||
// ellipse and the bounds of source as the "clipping rectangle". Line up the Min
|
|
||||||
// of both canvases.
|
|
||||||
|
|
||||||
func FillEllipse (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Canvas,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
srcData, srcStride := source.Buffer()
|
|
||||||
|
|
||||||
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
|
|
||||||
drawBounds :=
|
|
||||||
source.Bounds().Sub(offset).
|
|
||||||
Intersect(destination.Bounds()).
|
|
||||||
Intersect(bounds)
|
|
||||||
if bounds.Empty() { return }
|
|
||||||
updatedRegion = bounds
|
|
||||||
|
|
||||||
point := image.Point { }
|
|
||||||
for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
|
|
||||||
for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
|
|
||||||
if inEllipse(point, bounds) {
|
|
||||||
offsetPoint := point.Add(offset)
|
|
||||||
dstIndex := point.X + point.Y * dstStride
|
|
||||||
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
|
|
||||||
dstData[dstIndex] = srcData[srcIndex]
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func StrokeEllipse (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Canvas,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
weight int,
|
|
||||||
) {
|
|
||||||
if weight < 1 { return }
|
|
||||||
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
srcData, srcStride := source.Buffer()
|
|
||||||
|
|
||||||
drawBounds := destination.Bounds().Inset(weight - 1)
|
|
||||||
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
|
|
||||||
if drawBounds.Empty() { return }
|
|
||||||
|
|
||||||
context := ellipsePlottingContext {
|
|
||||||
plottingContext: plottingContext {
|
|
||||||
dstData: dstData,
|
|
||||||
dstStride: dstStride,
|
|
||||||
srcData: srcData,
|
|
||||||
srcStride: srcStride,
|
|
||||||
weight: weight,
|
|
||||||
offset: offset,
|
|
||||||
bounds: bounds,
|
|
||||||
},
|
|
||||||
radii: image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2),
|
|
||||||
}
|
|
||||||
context.center = drawBounds.Min.Add(context.radii)
|
|
||||||
context.plotEllipse()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ellipsePlottingContext struct {
|
|
||||||
plottingContext
|
|
||||||
radii image.Point
|
|
||||||
center image.Point
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context ellipsePlottingContext) plotEllipse () {
|
|
||||||
x := float64(0)
|
|
||||||
y := float64(context.radii.Y)
|
|
||||||
|
|
||||||
// region 1 decision parameter
|
|
||||||
decision1 :=
|
|
||||||
float64(context.radii.Y * context.radii.Y) -
|
|
||||||
float64(context.radii.X * context.radii.X * context.radii.Y) +
|
|
||||||
(0.25 * float64(context.radii.X) * float64(context.radii.X))
|
|
||||||
decisionX := float64(2 * context.radii.Y * context.radii.Y * int(x))
|
|
||||||
decisionY := float64(2 * context.radii.X * context.radii.X * int(y))
|
|
||||||
|
|
||||||
// draw region 1
|
|
||||||
for decisionX < decisionY {
|
|
||||||
points := []image.Point {
|
|
||||||
image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
|
|
||||||
image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
|
|
||||||
image.Pt(-int(x) + context.center.X, int(y) + context.center.Y),
|
|
||||||
image.Pt( int(x) + context.center.X, int(y) + context.center.Y),
|
|
||||||
}
|
|
||||||
if context.srcData == nil {
|
|
||||||
context.plotColor(points[0])
|
|
||||||
context.plotColor(points[1])
|
|
||||||
context.plotColor(points[2])
|
|
||||||
context.plotColor(points[3])
|
|
||||||
} else {
|
|
||||||
context.plotSource(points[0])
|
|
||||||
context.plotSource(points[1])
|
|
||||||
context.plotSource(points[2])
|
|
||||||
context.plotSource(points[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decision1 < 0) {
|
|
||||||
x ++
|
|
||||||
decisionX += float64(2 * context.radii.Y * context.radii.Y)
|
|
||||||
decision1 += decisionX + float64(context.radii.Y * context.radii.Y)
|
|
||||||
} else {
|
|
||||||
x ++
|
|
||||||
y --
|
|
||||||
decisionX += float64(2 * context.radii.Y * context.radii.Y)
|
|
||||||
decisionY -= float64(2 * context.radii.X * context.radii.X)
|
|
||||||
decision1 +=
|
|
||||||
decisionX - decisionY +
|
|
||||||
float64(context.radii.Y * context.radii.Y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// region 2 decision parameter
|
|
||||||
decision2 :=
|
|
||||||
float64(context.radii.Y * context.radii.Y) * (x + 0.5) * (x + 0.5) +
|
|
||||||
float64(context.radii.X * context.radii.X) * (y - 1) * (y - 1) -
|
|
||||||
float64(context.radii.X * context.radii.X * context.radii.Y * context.radii.Y)
|
|
||||||
|
|
||||||
// draw region 2
|
|
||||||
for y >= 0 {
|
|
||||||
points := []image.Point {
|
|
||||||
image.Pt( int(x) + context.center.X, int(y) + context.center.Y),
|
|
||||||
image.Pt(-int(x) + context.center.X, int(y) + context.center.Y),
|
|
||||||
image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
|
|
||||||
image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
|
|
||||||
}
|
|
||||||
if context.srcData == nil {
|
|
||||||
context.plotColor(points[0])
|
|
||||||
context.plotColor(points[1])
|
|
||||||
context.plotColor(points[2])
|
|
||||||
context.plotColor(points[3])
|
|
||||||
} else {
|
|
||||||
context.plotSource(points[0])
|
|
||||||
context.plotSource(points[1])
|
|
||||||
context.plotSource(points[2])
|
|
||||||
context.plotSource(points[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
if decision2 > 0 {
|
|
||||||
y --
|
|
||||||
decisionY -= float64(2 * context.radii.X * context.radii.X)
|
|
||||||
decision2 += float64(context.radii.X * context.radii.X) - decisionY
|
|
||||||
} else {
|
|
||||||
y --
|
|
||||||
x ++
|
|
||||||
decisionX += float64(2 * context.radii.Y * context.radii.Y)
|
|
||||||
decisionY -= float64(2 * context.radii.X * context.radii.X)
|
|
||||||
decision2 +=
|
|
||||||
decisionX - decisionY +
|
|
||||||
float64(context.radii.X * context.radii.X)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillColorEllipse fills an ellipse within the destination canvas with a solid
|
|
||||||
// color.
|
|
||||||
func FillColorEllipse (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
|
|
||||||
realBounds := bounds
|
|
||||||
bounds = bounds.Intersect(destination.Bounds()).Canon()
|
|
||||||
if bounds.Empty() { return }
|
|
||||||
updatedRegion = bounds
|
|
||||||
|
|
||||||
point := image.Point { }
|
|
||||||
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
|
|
||||||
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
|
|
||||||
if inEllipse(point, realBounds) {
|
|
||||||
dstData[point.X + point.Y * dstStride] = color
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokeColorEllipse is similar to FillColorEllipse, but it draws an inset
|
|
||||||
// outline of an ellipse instead.
|
|
||||||
func StrokeColorEllipse (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
weight int,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
if weight < 1 { return }
|
|
||||||
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
insetBounds := bounds.Inset(weight - 1)
|
|
||||||
|
|
||||||
context := ellipsePlottingContext {
|
|
||||||
plottingContext: plottingContext {
|
|
||||||
dstData: dstData,
|
|
||||||
dstStride: dstStride,
|
|
||||||
color: color,
|
|
||||||
weight: weight,
|
|
||||||
bounds: bounds.Intersect(destination.Bounds()),
|
|
||||||
},
|
|
||||||
radii: image.Pt(insetBounds.Dx() / 2, insetBounds.Dy() / 2),
|
|
||||||
}
|
|
||||||
context.center = insetBounds.Min.Add(context.radii)
|
|
||||||
context.plotEllipse()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func inEllipse (point image.Point, bounds image.Rectangle) bool {
|
|
||||||
point = point.Sub(bounds.Min)
|
|
||||||
x := (float64(point.X) + 0.5) / float64(bounds.Dx()) - 0.5
|
|
||||||
y := (float64(point.Y) + 0.5) / float64(bounds.Dy()) - 0.5
|
|
||||||
return math.Hypot(x, y) <= 0.5
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
package shapes
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
import "tomo/artist"
|
|
||||||
|
|
||||||
// ColorLine draws a line from one point to another with the specified weight
|
|
||||||
// and color.
|
|
||||||
func ColorLine (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
weight int,
|
|
||||||
min image.Point,
|
|
||||||
max image.Point,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
|
|
||||||
updatedRegion.Max.X ++
|
|
||||||
updatedRegion.Max.Y ++
|
|
||||||
|
|
||||||
data, stride := destination.Buffer()
|
|
||||||
bounds := destination.Bounds()
|
|
||||||
context := linePlottingContext {
|
|
||||||
plottingContext: plottingContext {
|
|
||||||
dstData: data,
|
|
||||||
dstStride: stride,
|
|
||||||
color: color,
|
|
||||||
weight: weight,
|
|
||||||
bounds: bounds,
|
|
||||||
},
|
|
||||||
min: min,
|
|
||||||
max: max,
|
|
||||||
}
|
|
||||||
|
|
||||||
if abs(max.Y - min.Y) < abs(max.X - min.X) {
|
|
||||||
if max.X < min.X { context.swap() }
|
|
||||||
context.lineLow()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if max.Y < min.Y { context.swap() }
|
|
||||||
context.lineHigh()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type linePlottingContext struct {
|
|
||||||
plottingContext
|
|
||||||
min image.Point
|
|
||||||
max image.Point
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context *linePlottingContext) swap () {
|
|
||||||
temp := context.max
|
|
||||||
context.max = context.min
|
|
||||||
context.min = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context linePlottingContext) lineLow () {
|
|
||||||
deltaX := context.max.X - context.min.X
|
|
||||||
deltaY := context.max.Y - context.min.Y
|
|
||||||
yi := 1
|
|
||||||
|
|
||||||
if deltaY < 0 {
|
|
||||||
yi = -1
|
|
||||||
deltaY *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
D := (2 * deltaY) - deltaX
|
|
||||||
point := context.min
|
|
||||||
|
|
||||||
for ; point.X < context.max.X; point.X ++ {
|
|
||||||
context.plotColor(point)
|
|
||||||
if D > 0 {
|
|
||||||
D += 2 * (deltaY - deltaX)
|
|
||||||
point.Y += yi
|
|
||||||
} else {
|
|
||||||
D += 2 * deltaY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context linePlottingContext) lineHigh () {
|
|
||||||
deltaX := context.max.X - context.min.X
|
|
||||||
deltaY := context.max.Y - context.min.Y
|
|
||||||
xi := 1
|
|
||||||
|
|
||||||
if deltaX < 0 {
|
|
||||||
xi = -1
|
|
||||||
deltaX *= -1
|
|
||||||
}
|
|
||||||
|
|
||||||
D := (2 * deltaX) - deltaY
|
|
||||||
point := context.min
|
|
||||||
|
|
||||||
for ; point.Y < context.max.Y; point.Y ++ {
|
|
||||||
context.plotColor(point)
|
|
||||||
if D > 0 {
|
|
||||||
point.X += xi
|
|
||||||
D += 2 * (deltaX - deltaY)
|
|
||||||
} else {
|
|
||||||
D += 2 * deltaX
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func abs (n int) int {
|
|
||||||
if n < 0 { n *= -1}
|
|
||||||
return n
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package shapes
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// FIXME? drawing a ton of overlapping squares might be a bit wasteful.
|
|
||||||
|
|
||||||
type plottingContext struct {
|
|
||||||
dstData []color.RGBA
|
|
||||||
dstStride int
|
|
||||||
srcData []color.RGBA
|
|
||||||
srcStride int
|
|
||||||
color color.RGBA
|
|
||||||
weight int
|
|
||||||
offset image.Point
|
|
||||||
bounds image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context plottingContext) square (center image.Point) (square image.Rectangle) {
|
|
||||||
return image.Rect(0, 0, context.weight, context.weight).
|
|
||||||
Sub(image.Pt(context.weight / 2, context.weight / 2)).
|
|
||||||
Add(center).
|
|
||||||
Intersect(context.bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context plottingContext) plotColor (center image.Point) {
|
|
||||||
square := context.square(center)
|
|
||||||
for y := square.Min.Y; y < square.Max.Y; y ++ {
|
|
||||||
for x := square.Min.X; x < square.Max.X; x ++ {
|
|
||||||
context.dstData[x + y * context.dstStride] = context.color
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (context plottingContext) plotSource (center image.Point) {
|
|
||||||
square := context.square(center)
|
|
||||||
for y := square.Min.Y; y < square.Max.Y; y ++ {
|
|
||||||
for x := square.Min.X; x < square.Max.X; x ++ {
|
|
||||||
// we offset srcIndex here because we have already applied the
|
|
||||||
// offset to the square, and we need to reverse that to get the
|
|
||||||
// proper source coordinates.
|
|
||||||
srcIndex :=
|
|
||||||
x + context.offset.X +
|
|
||||||
(y + context.offset.Y) * context.dstStride
|
|
||||||
dstIndex := x + y * context.dstStride
|
|
||||||
context.dstData[dstIndex] = context.srcData [srcIndex]
|
|
||||||
}}
|
|
||||||
}
|
|
@ -1,130 +0,0 @@
|
|||||||
package shapes
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
import "tomo/artist"
|
|
||||||
import "tomo/shatter"
|
|
||||||
|
|
||||||
// TODO: return updatedRegion for all routines in this package
|
|
||||||
|
|
||||||
func FillRectangle (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Canvas,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
srcData, srcStride := source.Buffer()
|
|
||||||
|
|
||||||
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
|
|
||||||
drawBounds :=
|
|
||||||
source.Bounds().Sub(offset).
|
|
||||||
Intersect(destination.Bounds()).
|
|
||||||
Intersect(bounds)
|
|
||||||
if drawBounds.Empty() { return }
|
|
||||||
updatedRegion = drawBounds
|
|
||||||
|
|
||||||
point := image.Point { }
|
|
||||||
for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
|
|
||||||
for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
|
|
||||||
offsetPoint := point.Add(offset)
|
|
||||||
dstIndex := point.X + point.Y * dstStride
|
|
||||||
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
|
|
||||||
dstData[dstIndex] = srcData[srcIndex]
|
|
||||||
}}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func StrokeRectangle (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Canvas,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
weight int,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
insetBounds := bounds.Inset(weight)
|
|
||||||
if insetBounds.Empty() {
|
|
||||||
return FillRectangle(destination, source, bounds)
|
|
||||||
}
|
|
||||||
return FillRectangleShatter(destination, source, bounds, insetBounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillRectangleShatter is like FillRectangle, but it does not draw in areas
|
|
||||||
// specified in "rocks".
|
|
||||||
func FillRectangleShatter (
|
|
||||||
destination artist.Canvas,
|
|
||||||
source artist.Canvas,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
rocks ...image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
tiles := shatter.Shatter(bounds, rocks...)
|
|
||||||
for _, tile := range tiles {
|
|
||||||
FillRectangle (
|
|
||||||
artist.Cut(destination, tile),
|
|
||||||
source, tile)
|
|
||||||
updatedRegion = updatedRegion.Union(tile)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillColorRectangle fills a rectangle within the destination canvas with a
|
|
||||||
// solid color.
|
|
||||||
func FillColorRectangle (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
dstData, dstStride := destination.Buffer()
|
|
||||||
bounds = bounds.Canon().Intersect(destination.Bounds())
|
|
||||||
if bounds.Empty() { return }
|
|
||||||
|
|
||||||
updatedRegion = bounds
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
||||||
dstData[x + y * dstStride] = color
|
|
||||||
}}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillColorRectangleShatter is like FillColorRectangle, but it does not draw in
|
|
||||||
// areas specified in "rocks".
|
|
||||||
func FillColorRectangleShatter (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
rocks ...image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
tiles := shatter.Shatter(bounds, rocks...)
|
|
||||||
for _, tile := range tiles {
|
|
||||||
FillColorRectangle(destination, color, tile)
|
|
||||||
updatedRegion = updatedRegion.Union(tile)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
|
|
||||||
// outline of the given rectangle instead.
|
|
||||||
func StrokeColorRectangle (
|
|
||||||
destination artist.Canvas,
|
|
||||||
color color.RGBA,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
weight int,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
insetBounds := bounds.Inset(weight)
|
|
||||||
if insetBounds.Empty() {
|
|
||||||
return FillColorRectangle(destination, color, bounds)
|
|
||||||
}
|
|
||||||
return FillColorRectangleShatter(destination, color, bounds, insetBounds)
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
// Package shatter provides boolean operations for image.Rectangle.
|
|
||||||
package shatter
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
|
|
||||||
// Shatter takes in a bounding rectangle, and several rectangles to be
|
|
||||||
// subtracted from it. It returns a slice of rectangles that tile together to
|
|
||||||
// make up the difference between them. This is intended to be used for figuring
|
|
||||||
// out which areas of a container element's background are covered by other
|
|
||||||
// elements so it doesn't waste CPU cycles drawing to those areas.
|
|
||||||
func Shatter (
|
|
||||||
glass image.Rectangle,
|
|
||||||
rocks ...image.Rectangle,
|
|
||||||
) (
|
|
||||||
tiles []image.Rectangle,
|
|
||||||
) {
|
|
||||||
// in this function, the metaphor of throwing several rocks at a sheet
|
|
||||||
// of glass is used to illustrate the concept.
|
|
||||||
|
|
||||||
tiles = []image.Rectangle { glass }
|
|
||||||
for _, rock := range rocks {
|
|
||||||
|
|
||||||
// check each tile to see if the rock has collided with it
|
|
||||||
tileLen := len(tiles)
|
|
||||||
for tileIndex := 0; tileIndex < tileLen; tileIndex ++ {
|
|
||||||
tile := tiles[tileIndex]
|
|
||||||
if !rock.Overlaps(tile) { continue }
|
|
||||||
newTiles, n := shatterOnce(tile, rock)
|
|
||||||
if n > 0 {
|
|
||||||
// the tile was shattered into one or more sub
|
|
||||||
// tiles
|
|
||||||
tiles[tileIndex] = newTiles[0]
|
|
||||||
tiles = append(tiles, newTiles[1:n]...)
|
|
||||||
} else {
|
|
||||||
// the tile was entirely obscured by the rock
|
|
||||||
// and must be wholly removed
|
|
||||||
tiles = remove(tiles, tileIndex)
|
|
||||||
tileIndex --
|
|
||||||
tileLen --
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int) {
|
|
||||||
rock = rock.Intersect(glass)
|
|
||||||
|
|
||||||
// |'''''''''''|
|
|
||||||
// | |
|
|
||||||
// |###|'''| |
|
|
||||||
// |###|___| |
|
|
||||||
// | |
|
|
||||||
// |___________|
|
|
||||||
if rock.Min.X > glass.Min.X { tiles[n] = image.Rect (
|
|
||||||
glass.Min.X, rock.Min.Y,
|
|
||||||
rock.Min.X, rock.Max.Y,
|
|
||||||
); n ++ }
|
|
||||||
|
|
||||||
// |'''''''''''|
|
|
||||||
// | |
|
|
||||||
// | |'''|###|
|
|
||||||
// | |___|###|
|
|
||||||
// | |
|
|
||||||
// |___________|
|
|
||||||
if rock.Max.X < glass.Max.X { tiles[n] = image.Rect (
|
|
||||||
rock.Max.X, rock.Min.Y,
|
|
||||||
glass.Max.X, rock.Max.Y,
|
|
||||||
); n ++ }
|
|
||||||
|
|
||||||
// |###########|
|
|
||||||
// |###########|
|
|
||||||
// | |'''| |
|
|
||||||
// | |___| |
|
|
||||||
// | |
|
|
||||||
// |___________|
|
|
||||||
if rock.Min.Y > glass.Min.Y { tiles[n] = image.Rect (
|
|
||||||
glass.Min.X, glass.Min.Y,
|
|
||||||
glass.Max.X, rock.Min.Y,
|
|
||||||
); n ++ }
|
|
||||||
|
|
||||||
// |'''''''''''|
|
|
||||||
// | |
|
|
||||||
// | |'''| |
|
|
||||||
// | |___| |
|
|
||||||
// |###########|
|
|
||||||
// |###########|
|
|
||||||
if rock.Max.Y < glass.Max.Y { tiles[n] = image.Rect (
|
|
||||||
glass.Min.X, rock.Max.Y,
|
|
||||||
glass.Max.X, glass.Max.Y,
|
|
||||||
); n ++ }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func remove[ELEMENT any] (slice []ELEMENT, s int) []ELEMENT {
|
|
||||||
return append(slice[:s], slice[s + 1:]...)
|
|
||||||
}
|
|
Reference in New Issue
Block a user