Made similar changes to the Pattern interface and all of artist

This commit is contained in:
Sasha Koshka 2023-03-12 01:04:06 -05:00
parent 3d28ebe4cf
commit 0f8affd2b2
6 changed files with 86 additions and 76 deletions

View File

@ -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 // Pattern is capable of drawing to a canvas within the bounds of a given
// clipping rectangle. // clipping rectangle.
type Pattern interface { type Pattern interface {
// Draw draws to destination, using the bounds of destination as a width // Draw draws the pattern onto the destination canvas, using the
// and height for things like gradients, bevels, etc. The pattern may // specified bounds. The given bounds can be smaller or larger than the
// not draw outside the union of destination.Bounds() and clip. The // bounds of the destination canvas. The destination canvas can be cut
// clipping rectangle effectively takes a subset of the pattern. To // using canvas.Cut() to draw only a specific subset of a pattern.
// change the bounds of the pattern itself, use canvas.Cut() on the Draw (destination canvas.Canvas, bounds image.Rectangle)
// destination before passing it to Draw().
Draw (destination canvas.Canvas, clip image.Rectangle)
} }
// Draw lets you use several clipping rectangles to draw a pattern. // Fill fills the destination canvas with the given pattern.
func Draw ( 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, destination canvas.Canvas,
source Pattern, source Pattern,
clips ...image.Rectangle, bounds image.Rectangle,
subsets ...image.Rectangle,
) ( ) (
updatedRegion image.Rectangle, updatedRegion image.Rectangle,
) { ) {
for _, clip := range clips { for _, subset := range subsets {
source.Draw(destination, clip) source.Draw(canvas.Cut(destination, subset), bounds)
updatedRegion = updatedRegion.Union(clip) updatedRegion = updatedRegion.Union(subset)
} }
return return
} }
// DrawBounds lets you specify an overall bounding rectangle for drawing a // DrawShatter is like an inverse of DrawClip, drawing nothing in the areas
// pattern. The destination is cut to this rectangle. // specified by "rocks".
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".
func DrawShatter ( func DrawShatter (
destination canvas.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
bounds image.Rectangle,
rocks ...image.Rectangle, rocks ...image.Rectangle,
) ( ) (
updatedRegion image.Rectangle, updatedRegion image.Rectangle,
) { ) {
tiles := shatter.Shatter(destination.Bounds(), rocks...) tiles := shatter.Shatter(bounds, rocks...)
return Draw(destination, source, tiles...) return DrawClip(destination, source, bounds, tiles...)
} }
// AllocateSample returns a new canvas containing the result of a pattern. The // 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 // 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 // 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. // it because that is horrible and cruel to the computer.
func AllocateSample (source Pattern, width, height int) (allocated canvas.Canvas) { func AllocateSample (source Pattern, width, height int) canvas.Canvas {
allocated = canvas.NewBasicCanvas(width, height) allocated := canvas.NewBasicCanvas(width, height)
source.Draw(allocated, allocated.Bounds()) Fill(allocated, source)
return return allocated
} }

View File

@ -37,9 +37,9 @@ type Border struct {
// Draw draws the border pattern onto the destination canvas within the clipping // Draw draws the border pattern onto the destination canvas within the clipping
// bounds. // bounds.
func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) { func (pattern Border) Draw (destination canvas.Canvas, bounds image.Rectangle) {
bounds := clip.Canon().Intersect(destination.Bounds()) drawBounds := bounds.Canon().Intersect(destination.Bounds())
if bounds.Empty() { return } if drawBounds.Empty() { return }
srcSections := nonasect(pattern.Bounds(), pattern.Inset) srcSections := nonasect(pattern.Bounds(), pattern.Inset)
srcTextures := [9]Texture { } srcTextures := [9]Texture { }
@ -47,9 +47,9 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
srcTextures[index].Canvas = canvas.Cut(pattern, section) srcTextures[index].Canvas = canvas.Cut(pattern, section)
} }
dstSections := nonasect(destination.Bounds(), pattern.Inset) dstSections := nonasect(bounds, pattern.Inset)
for index, section := range dstSections { for index, section := range dstSections {
srcTextures[index].Draw(canvas.Cut(destination, section), clip) srcTextures[index].Draw(destination, section)
} }
} }

View File

@ -9,25 +9,24 @@ type Texture struct {
canvas.Canvas 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. // points of the pattern's canvas and the destination canvas will be lined up.
func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) { func (pattern Texture) Draw (destination canvas.Canvas, bounds image.Rectangle) {
realBounds := destination.Bounds() drawBounds := bounds.Canon().Intersect(destination.Bounds())
bounds := clip.Canon().Intersect(realBounds) if drawBounds.Empty() { return }
if bounds.Empty() { return }
dstData, dstStride := destination.Buffer() dstData, dstStride := destination.Buffer()
srcData, srcStride := pattern.Buffer() srcData, srcStride := pattern.Buffer()
srcBounds := pattern.Bounds() srcBounds := pattern.Bounds()
dstPoint := image.Point { } 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.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X)
srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y) 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 srcPoint.X = srcBounds.Min.X
dstPoint.X = bounds.Min.X dstPoint.X = drawBounds.Min.X
dstYComponent := dstPoint.Y * dstStride dstYComponent := dstPoint.Y * dstStride
srcYComponent := srcPoint.Y * srcStride srcYComponent := srcPoint.Y * srcStride
@ -42,7 +41,7 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
} }
dstPoint.X ++ dstPoint.X ++
if dstPoint.X >= bounds.Max.X { if dstPoint.X >= drawBounds.Max.X {
break break
} }
} }

View File

@ -9,9 +9,9 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
// Uniform is a pattern that draws a solid color. // Uniform is a pattern that draws a solid color.
type Uniform color.RGBA type Uniform color.RGBA
// Draw fills the clipping rectangle with the pattern's color. // Draw fills the bounding rectangle with the pattern's color.
func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) { func (pattern Uniform) Draw (destination canvas.Canvas, bounds image.Rectangle) {
shapes.FillColorRectangle(destination, color.RGBA(pattern), clip) shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds)
} }
// Uhex creates a new Uniform pattern from an RGBA integer value. // Uhex creates a new Uniform pattern from an RGBA integer value.

View File

@ -13,6 +13,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
func FillEllipse ( func FillEllipse (
destination canvas.Canvas, destination canvas.Canvas,
source canvas.Canvas, source canvas.Canvas,
bounds image.Rectangle,
) ( ) (
updatedRegion image.Rectangle, updatedRegion image.Rectangle,
) { ) {
@ -20,15 +21,17 @@ func FillEllipse (
srcData, srcStride := source.Buffer() srcData, srcStride := source.Buffer()
offset := source.Bounds().Min.Sub(destination.Bounds().Min) offset := source.Bounds().Min.Sub(destination.Bounds().Min)
bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) drawBounds :=
realBounds := destination.Bounds() source.Bounds().Sub(offset).
Intersect(destination.Bounds()).
Intersect(bounds)
if bounds.Empty() { return } if bounds.Empty() { return }
updatedRegion = bounds updatedRegion = bounds
point := image.Point { } point := image.Point { }
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ { for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
if inEllipse(point, realBounds) { if inEllipse(point, bounds) {
offsetPoint := point.Add(offset) offsetPoint := point.Add(offset)
dstIndex := point.X + point.Y * dstStride dstIndex := point.X + point.Y * dstStride
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
@ -41,6 +44,7 @@ func FillEllipse (
func StrokeEllipse ( func StrokeEllipse (
destination canvas.Canvas, destination canvas.Canvas,
source canvas.Canvas, source canvas.Canvas,
bounds image.Rectangle,
weight int, weight int,
) { ) {
if weight < 1 { return } if weight < 1 { return }
@ -48,10 +52,9 @@ func StrokeEllipse (
dstData, dstStride := destination.Buffer() dstData, dstStride := destination.Buffer()
srcData, srcStride := source.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) offset := source.Bounds().Min.Sub(destination.Bounds().Min)
realBounds := destination.Bounds() if drawBounds.Empty() { return }
if bounds.Empty() { return }
context := ellipsePlottingContext { context := ellipsePlottingContext {
plottingContext: plottingContext { plottingContext: plottingContext {
@ -61,9 +64,9 @@ func StrokeEllipse (
srcStride: srcStride, srcStride: srcStride,
weight: weight, weight: weight,
offset: offset, 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.center = bounds.Min.Add(context.radii)
context.plotEllipse() context.plotEllipse()

View File

@ -10,6 +10,7 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter"
func FillRectangle ( func FillRectangle (
destination canvas.Canvas, destination canvas.Canvas,
source canvas.Canvas, source canvas.Canvas,
bounds image.Rectangle,
) ( ) (
updatedRegion image.Rectangle, updatedRegion image.Rectangle,
) { ) {
@ -17,13 +18,16 @@ func FillRectangle (
srcData, srcStride := source.Buffer() srcData, srcStride := source.Buffer()
offset := source.Bounds().Min.Sub(destination.Bounds().Min) offset := source.Bounds().Min.Sub(destination.Bounds().Min)
bounds := source.Bounds().Sub(offset).Intersect(destination.Bounds()) drawBounds :=
if bounds.Empty() { return } source.Bounds().Sub(offset).
updatedRegion = bounds Intersect(destination.Bounds()).
Intersect(bounds)
if drawBounds.Empty() { return }
updatedRegion = drawBounds
point := image.Point { } point := image.Point { }
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ { for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ { for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
offsetPoint := point.Add(offset) offsetPoint := point.Add(offset)
dstIndex := point.X + point.Y * dstStride dstIndex := point.X + point.Y * dstStride
srcIndex := offsetPoint.X + offsetPoint.Y * srcStride srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
@ -36,15 +40,16 @@ func FillRectangle (
func StrokeRectangle ( func StrokeRectangle (
destination canvas.Canvas, destination canvas.Canvas,
source canvas.Canvas, source canvas.Canvas,
bounds image.Rectangle,
weight int, weight int,
) (
updatedRegion image.Rectangle,
) { ) {
bounds := destination.Bounds()
insetBounds := bounds.Inset(weight) insetBounds := bounds.Inset(weight)
if insetBounds.Empty() { if insetBounds.Empty() {
FillRectangle(destination, source) return FillRectangle(destination, source, bounds)
return
} }
FillRectangleShatter(destination, source, insetBounds) return FillRectangleShatter(destination, source, insetBounds)
} }
// FillRectangleShatter is like FillRectangle, but it does not draw in areas // FillRectangleShatter is like FillRectangle, but it does not draw in areas
@ -52,15 +57,19 @@ func StrokeRectangle (
func FillRectangleShatter ( func FillRectangleShatter (
destination canvas.Canvas, destination canvas.Canvas,
source canvas.Canvas, source canvas.Canvas,
bounds image.Rectangle,
rocks ...image.Rectangle, rocks ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) { ) {
tiles := shatter.Shatter(destination.Bounds(), rocks...) tiles := shatter.Shatter(bounds, rocks...)
offset := source.Bounds().Min.Sub(destination.Bounds().Min)
for _, tile := range tiles { for _, tile := range tiles {
FillRectangle ( FillRectangle (
canvas.Cut(destination, tile), 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 // FillColorRectangle fills a rectangle within the destination canvas with a
@ -92,11 +101,15 @@ func FillColorRectangleShatter (
color color.RGBA, color color.RGBA,
bounds image.Rectangle, bounds image.Rectangle,
rocks ...image.Rectangle, rocks ...image.Rectangle,
) (
updatedRegion image.Rectangle,
) { ) {
tiles := shatter.Shatter(bounds, rocks...) tiles := shatter.Shatter(bounds, rocks...)
for _, tile := range tiles { for _, tile := range tiles {
FillColorRectangle(destination, color, tile) FillColorRectangle(destination, color, tile)
updatedRegion = updatedRegion.Union(tile)
} }
return
} }
// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset // StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
@ -106,11 +119,12 @@ func StrokeColorRectangle (
color color.RGBA, color color.RGBA,
bounds image.Rectangle, bounds image.Rectangle,
weight int, weight int,
) (
updatedRegion image.Rectangle,
) { ) {
insetBounds := bounds.Inset(weight) insetBounds := bounds.Inset(weight)
if insetBounds.Empty() { if insetBounds.Empty() {
FillColorRectangle(destination, color, bounds) return FillColorRectangle(destination, color, bounds)
return
} }
FillColorRectangleShatter(destination, color, bounds, insetBounds) return FillColorRectangleShatter(destination, color, bounds, insetBounds)
} }