diff --git a/README.md b/README.md index 9f4ec00..e8c6b06 100644 --- a/README.md +++ b/README.md @@ -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 directory, or pull up the documentation by running `godoc` within the 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). diff --git a/artist/artutil/util.go b/artist/artutil/util.go deleted file mode 100644 index 19b7e98..0000000 --- a/artist/artutil/util.go +++ /dev/null @@ -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 -} diff --git a/artist/canvas.go b/artist/canvas.go deleted file mode 100644 index 96296f1..0000000 --- a/artist/canvas.go +++ /dev/null @@ -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 -} diff --git a/artist/doc.go b/artist/doc.go deleted file mode 100644 index c0a0a6f..0000000 --- a/artist/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package artist provides a simple 2D drawing library for canvas.Canvas. -package artist diff --git a/artist/icon.go b/artist/icon.go deleted file mode 100644 index 9e36d47..0000000 --- a/artist/icon.go +++ /dev/null @@ -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 -} diff --git a/artist/inset.go b/artist/inset.go deleted file mode 100644 index 971a1be..0000000 --- a/artist/inset.go +++ /dev/null @@ -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] -} diff --git a/artist/pattern.go b/artist/pattern.go deleted file mode 100644 index 0e428bb..0000000 --- a/artist/pattern.go +++ /dev/null @@ -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) -} diff --git a/artist/patterns/border.go b/artist/patterns/border.go deleted file mode 100644 index 76f3d74..0000000 --- a/artist/patterns/border.go +++ /dev/null @@ -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), - } -} diff --git a/artist/patterns/doc.go b/artist/patterns/doc.go deleted file mode 100644 index c36fb42..0000000 --- a/artist/patterns/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package patterns provides a basic set of types that satisfy the -// artist.Pattern interface. -package patterns diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go deleted file mode 100644 index 0692325..0000000 --- a/artist/patterns/texture.go +++ /dev/null @@ -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 -} diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go deleted file mode 100644 index be40f46..0000000 --- a/artist/patterns/uniform.go +++ /dev/null @@ -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)) -} diff --git a/artist/shapes/doc.go b/artist/shapes/doc.go deleted file mode 100644 index 98eb8f6..0000000 --- a/artist/shapes/doc.go +++ /dev/null @@ -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 diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go deleted file mode 100644 index 79d3b7b..0000000 --- a/artist/shapes/ellipse.go +++ /dev/null @@ -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 -} diff --git a/artist/shapes/line.go b/artist/shapes/line.go deleted file mode 100644 index 312354b..0000000 --- a/artist/shapes/line.go +++ /dev/null @@ -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 -} diff --git a/artist/shapes/plot.go b/artist/shapes/plot.go deleted file mode 100644 index 6749768..0000000 --- a/artist/shapes/plot.go +++ /dev/null @@ -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] - }} -} diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go deleted file mode 100644 index 49f81d5..0000000 --- a/artist/shapes/rectangle.go +++ /dev/null @@ -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) -} diff --git a/shatter/shatter.go b/shatter/shatter.go deleted file mode 100644 index 806dfda..0000000 --- a/shatter/shatter.go +++ /dev/null @@ -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:]...) -}