diff --git a/artist/pattern.go b/artist/pattern.go index 558bbf6..6e43231 100644 --- a/artist/pattern.go +++ b/artist/pattern.go @@ -7,61 +7,55 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter" // Pattern is capable of drawing to a canvas within the bounds of a given // clipping rectangle. type Pattern interface { - // Draw draws to destination, using the bounds of destination as a width - // and height for things like gradients, bevels, etc. The pattern may - // not draw outside the union of destination.Bounds() and clip. The - // clipping rectangle effectively takes a subset of the pattern. To - // change the bounds of the pattern itself, use canvas.Cut() on the - // destination before passing it to Draw(). - Draw (destination canvas.Canvas, clip image.Rectangle) + // 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.Canvas, bounds image.Rectangle) } -// Draw lets you use several clipping rectangles to draw a pattern. -func Draw ( +// Fill fills the destination canvas with the given pattern. +func Fill (destination canvas.Canvas, source Pattern) (updated image.Rectangle) { + source.Draw(destination, destination.Bounds()) + return destination.Bounds() +} + +// Draw lets you draw several subsets of +func DrawClip ( destination canvas.Canvas, source Pattern, - clips ...image.Rectangle, + bounds image.Rectangle, + subsets ...image.Rectangle, ) ( updatedRegion image.Rectangle, ) { - for _, clip := range clips { - source.Draw(destination, clip) - updatedRegion = updatedRegion.Union(clip) + for _, subset := range subsets { + source.Draw(canvas.Cut(destination, subset), bounds) + updatedRegion = updatedRegion.Union(subset) } return } -// DrawBounds lets you specify an overall bounding rectangle for drawing a -// pattern. The destination is cut to this rectangle. -func DrawBounds ( - destination canvas.Canvas, - source Pattern, - bounds image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - return Draw(canvas.Cut(destination, bounds), source, bounds) -} - -// DrawShatter is like an inverse of Draw, drawing nothing in the areas -// specified in "rocks". +// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas +// specified by "rocks". func DrawShatter ( destination canvas.Canvas, source Pattern, + bounds image.Rectangle, rocks ...image.Rectangle, ) ( updatedRegion image.Rectangle, ) { - tiles := shatter.Shatter(destination.Bounds(), rocks...) - return Draw(destination, source, tiles...) + 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 Pattern, width, height int) (allocated canvas.Canvas) { - allocated = canvas.NewBasicCanvas(width, height) - source.Draw(allocated, allocated.Bounds()) - return +func AllocateSample (source Pattern, width, height int) canvas.Canvas { + allocated := canvas.NewBasicCanvas(width, height) + Fill(allocated, source) + return allocated } diff --git a/artist/patterns/border.go b/artist/patterns/border.go index 3202a0b..0ce56c0 100644 --- a/artist/patterns/border.go +++ b/artist/patterns/border.go @@ -37,9 +37,9 @@ type Border struct { // Draw draws the border pattern onto the destination canvas within the clipping // bounds. -func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { - bounds := clip.Canon().Intersect(destination.Bounds()) - if bounds.Empty() { return } +func (pattern Border) Draw (destination canvas.Canvas, bounds image.Rectangle) { + drawBounds := bounds.Canon().Intersect(destination.Bounds()) + if drawBounds.Empty() { return } srcSections := nonasect(pattern.Bounds(), pattern.Inset) srcTextures := [9]Texture { } @@ -47,9 +47,9 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { srcTextures[index].Canvas = canvas.Cut(pattern, section) } - dstSections := nonasect(destination.Bounds(), pattern.Inset) + dstSections := nonasect(bounds, pattern.Inset) for index, section := range dstSections { - srcTextures[index].Draw(canvas.Cut(destination, section), clip) + srcTextures[index].Draw(destination, section) } } diff --git a/artist/patterns/texture.go b/artist/patterns/texture.go index 47f0596..91e3e40 100644 --- a/artist/patterns/texture.go +++ b/artist/patterns/texture.go @@ -9,25 +9,24 @@ type Texture struct { canvas.Canvas } -// Draw tiles the pattern's canvas within the clipping bounds. The minimum +// Draw tiles the pattern's canvas within the given bounds. The minimum // points of the pattern's canvas and the destination canvas will be lined up. -func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { - realBounds := destination.Bounds() - bounds := clip.Canon().Intersect(realBounds) - if bounds.Empty() { return } +func (pattern Texture) Draw (destination canvas.Canvas, bounds image.Rectangle) { + drawBounds := bounds.Canon().Intersect(destination.Bounds()) + if drawBounds.Empty() { return } dstData, dstStride := destination.Buffer() srcData, srcStride := pattern.Buffer() srcBounds := pattern.Bounds() dstPoint := image.Point { } - srcPoint := bounds.Min.Sub(realBounds.Min).Add(srcBounds.Min) + srcPoint := drawBounds.Min.Sub(bounds.Min).Add(srcBounds.Min) srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X) srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y) - for dstPoint.Y = bounds.Min.Y; dstPoint.Y < bounds.Max.Y; dstPoint.Y ++ { + for dstPoint.Y = drawBounds.Min.Y; dstPoint.Y < drawBounds.Max.Y; dstPoint.Y ++ { srcPoint.X = srcBounds.Min.X - dstPoint.X = bounds.Min.X + dstPoint.X = drawBounds.Min.X dstYComponent := dstPoint.Y * dstStride srcYComponent := srcPoint.Y * srcStride @@ -42,7 +41,7 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { } dstPoint.X ++ - if dstPoint.X >= bounds.Max.X { + if dstPoint.X >= drawBounds.Max.X { break } } diff --git a/artist/patterns/uniform.go b/artist/patterns/uniform.go index 8efb8be..173367a 100644 --- a/artist/patterns/uniform.go +++ b/artist/patterns/uniform.go @@ -9,9 +9,9 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/shapes" // Uniform is a pattern that draws a solid color. type Uniform color.RGBA -// Draw fills the clipping rectangle with the pattern's color. -func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { - shapes.FillColorRectangle(destination, color.RGBA(pattern), clip) +// Draw fills the bounding rectangle with the pattern's color. +func (pattern Uniform) Draw (destination canvas.Canvas, bounds image.Rectangle) { + shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds) } // Uhex creates a new Uniform pattern from an RGBA integer value. diff --git a/artist/shapes/ellipse.go b/artist/shapes/ellipse.go index 0e5f0c5..c750e6f 100644 --- a/artist/shapes/ellipse.go +++ b/artist/shapes/ellipse.go @@ -13,6 +13,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas" func FillEllipse ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, ) ( updatedRegion image.Rectangle, ) { @@ -20,15 +21,17 @@ func FillEllipse ( srcData, srcStride := source.Buffer() offset := source.Bounds().Min.Sub(destination.Bounds().Min) - bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) - realBounds := destination.Bounds() + drawBounds := + source.Bounds().Sub(offset). + Intersect(destination.Bounds()). + Intersect(bounds) 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) { + 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 @@ -41,6 +44,7 @@ func FillEllipse ( func StrokeEllipse ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, weight int, ) { if weight < 1 { return } @@ -48,10 +52,9 @@ func StrokeEllipse ( dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - bounds := destination.Bounds().Inset(weight - 1) + drawBounds := destination.Bounds().Inset(weight - 1) offset := source.Bounds().Min.Sub(destination.Bounds().Min) - realBounds := destination.Bounds() - if bounds.Empty() { return } + if drawBounds.Empty() { return } context := ellipsePlottingContext { plottingContext: plottingContext { @@ -61,9 +64,9 @@ func StrokeEllipse ( srcStride: srcStride, weight: weight, offset: offset, - bounds: realBounds, + bounds: bounds, }, - radii: image.Pt(bounds.Dx() / 2, bounds.Dy() / 2), + radii: image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2), } context.center = bounds.Min.Add(context.radii) context.plotEllipse() diff --git a/artist/shapes/rectangle.go b/artist/shapes/rectangle.go index 8912ade..b52ae68 100644 --- a/artist/shapes/rectangle.go +++ b/artist/shapes/rectangle.go @@ -10,20 +10,24 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter" func FillRectangle ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, ) ( updatedRegion image.Rectangle, ) { dstData, dstStride := destination.Buffer() srcData, srcStride := source.Buffer() - offset := source.Bounds().Min.Sub(destination.Bounds().Min) - bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) - if bounds.Empty() { return } - updatedRegion = bounds + 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 = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ { - for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { + 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 @@ -36,15 +40,16 @@ func FillRectangle ( func StrokeRectangle ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, weight int, +) ( + updatedRegion image.Rectangle, ) { - bounds := destination.Bounds() insetBounds := bounds.Inset(weight) if insetBounds.Empty() { - FillRectangle(destination, source) - return + return FillRectangle(destination, source, bounds) } - FillRectangleShatter(destination, source, insetBounds) + return FillRectangleShatter(destination, source, insetBounds) } // FillRectangleShatter is like FillRectangle, but it does not draw in areas @@ -52,15 +57,19 @@ func StrokeRectangle ( func FillRectangleShatter ( destination canvas.Canvas, source canvas.Canvas, + bounds image.Rectangle, rocks ...image.Rectangle, +) ( + updatedRegion image.Rectangle, ) { - tiles := shatter.Shatter(destination.Bounds(), rocks...) - offset := source.Bounds().Min.Sub(destination.Bounds().Min) + tiles := shatter.Shatter(bounds, rocks...) for _, tile := range tiles { FillRectangle ( canvas.Cut(destination, tile), - canvas.Cut(source, tile.Add(offset))) + source, tile) + updatedRegion = updatedRegion.Union(tile) } + return } // FillColorRectangle fills a rectangle within the destination canvas with a @@ -92,11 +101,15 @@ func FillColorRectangleShatter ( 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 @@ -106,11 +119,12 @@ func StrokeColorRectangle ( color color.RGBA, bounds image.Rectangle, weight int, +) ( + updatedRegion image.Rectangle, ) { insetBounds := bounds.Inset(weight) if insetBounds.Empty() { - FillColorRectangle(destination, color, bounds) - return + return FillColorRectangle(destination, color, bounds) } - FillColorRectangleShatter(destination, color, bounds, insetBounds) + return FillColorRectangleShatter(destination, color, bounds, insetBounds) }