flexible-elements-were-a-mistake #11
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user