Created new patterns
This commit is contained in:
parent
bf2fdb5eaa
commit
81090267a6
@ -1,7 +1,15 @@
|
|||||||
package theme
|
package artist
|
||||||
|
|
||||||
import "image"
|
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
|
// 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
|
// 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.
|
// the left at index three. These values may be negative.
|
@ -1,46 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Beveled is a pattern that has a highlight section and a shadow section.
|
|
||||||
type Beveled [2]Pattern
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Beveled) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
return QuadBeveled {
|
|
||||||
pattern[0],
|
|
||||||
pattern[1],
|
|
||||||
pattern[1],
|
|
||||||
pattern[0],
|
|
||||||
}.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QuadBeveled is like Beveled, but with four sides. A pattern can be specified
|
|
||||||
// for each one.
|
|
||||||
type QuadBeveled [4]Pattern
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern QuadBeveled) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
bottom := y > height / 2
|
|
||||||
right := x > width / 2
|
|
||||||
top := !bottom
|
|
||||||
left := !right
|
|
||||||
side := 0
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case top && left:
|
|
||||||
if x < y { side = 3 } else { side = 0 }
|
|
||||||
|
|
||||||
case top && right:
|
|
||||||
if width - x > y { side = 0 } else { side = 1 }
|
|
||||||
|
|
||||||
case bottom && left:
|
|
||||||
if x < height - y { side = 3 } else { side = 2 }
|
|
||||||
|
|
||||||
case bottom && right:
|
|
||||||
if width - x > height - y { side = 2 } else { side = 1 }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return pattern[side].AtWhen(x, y, width, height)
|
|
||||||
}
|
|
64
artist/patterns/border.go
Normal file
64
artist/patterns/border.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package patterns
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
|
|
||||||
|
type Border struct {
|
||||||
|
canvas.Canvas
|
||||||
|
artist.Inset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
|
||||||
|
bounds := clip.Canon().Intersect(destination.Bounds())
|
||||||
|
if bounds.Empty() { return }
|
||||||
|
|
||||||
|
srcSections := nonasect(pattern.Bounds(), pattern.Inset)
|
||||||
|
srcTextures := [9]Texture { }
|
||||||
|
for index, section := range srcSections {
|
||||||
|
srcTextures[index] = Texture {
|
||||||
|
Canvas: canvas.Cut(pattern, section),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dstSections := nonasect(destination.Bounds(), pattern.Inset)
|
||||||
|
for index, section := range dstSections {
|
||||||
|
srcTextures[index].Draw(canvas.Cut(destination, section), clip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,111 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Bordered is a pattern with a border and a fill.
|
|
||||||
type Bordered struct {
|
|
||||||
Fill Pattern
|
|
||||||
Stroke
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Bordered) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
outerBounds := image.Rectangle { Max: image.Point { width, height }}
|
|
||||||
innerBounds := outerBounds.Inset(pattern.Weight)
|
|
||||||
if (image.Point { x, y }).In (innerBounds) {
|
|
||||||
return pattern.Fill.AtWhen (
|
|
||||||
x - pattern.Weight,
|
|
||||||
y - pattern.Weight,
|
|
||||||
innerBounds.Dx(), innerBounds.Dy())
|
|
||||||
} else {
|
|
||||||
return pattern.Stroke.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke represents a stoke that has a weight and a pattern.
|
|
||||||
type Stroke struct {
|
|
||||||
Weight int
|
|
||||||
Pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
type borderInternal struct {
|
|
||||||
weight int
|
|
||||||
stroke Pattern
|
|
||||||
bounds image.Rectangle
|
|
||||||
dx, dy int
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiBordered is a pattern that allows multiple borders of different lengths
|
|
||||||
// to be inset within one another. The final border is treated as a fill color,
|
|
||||||
// and its weight does not matter.
|
|
||||||
type MultiBordered struct {
|
|
||||||
borders []borderInternal
|
|
||||||
lastWidth, lastHeight int
|
|
||||||
maxBorder int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMultiBordered creates a new MultiBordered pattern from the given list of
|
|
||||||
// borders.
|
|
||||||
func NewMultiBordered (borders ...Stroke) (multi *MultiBordered) {
|
|
||||||
internalBorders := make([]borderInternal, len(borders))
|
|
||||||
for index, border := range borders {
|
|
||||||
internalBorders[index].weight = border.Weight
|
|
||||||
internalBorders[index].stroke = border.Pattern
|
|
||||||
}
|
|
||||||
return &MultiBordered { borders: internalBorders }
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (multi *MultiBordered) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
if multi.lastWidth != width || multi.lastHeight != height {
|
|
||||||
multi.recalculate(width, height)
|
|
||||||
}
|
|
||||||
point := image.Point { x, y }
|
|
||||||
for index := multi.maxBorder; index >= 0; index -- {
|
|
||||||
border := multi.borders[index]
|
|
||||||
if point.In(border.bounds) {
|
|
||||||
return border.stroke.AtWhen (
|
|
||||||
point.X - border.bounds.Min.X,
|
|
||||||
point.Y - border.bounds.Min.Y,
|
|
||||||
border.dx, border.dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (multi *MultiBordered) recalculate (width, height int) {
|
|
||||||
bounds := image.Rect (0, 0, width, height)
|
|
||||||
multi.maxBorder = 0
|
|
||||||
for index, border := range multi.borders {
|
|
||||||
multi.maxBorder = index
|
|
||||||
multi.borders[index].bounds = bounds
|
|
||||||
multi.borders[index].dx = bounds.Dx()
|
|
||||||
multi.borders[index].dy = bounds.Dy()
|
|
||||||
bounds = bounds.Inset(border.weight)
|
|
||||||
if bounds.Empty() { break }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padded is a pattern that surrounds a central fill pattern with a border that
|
|
||||||
// can have a different width for each side.
|
|
||||||
type Padded struct {
|
|
||||||
Fill Pattern
|
|
||||||
Stroke Pattern
|
|
||||||
Sides []int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Padded) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
innerBounds := image.Rect (
|
|
||||||
pattern.Sides[3], pattern.Sides[0],
|
|
||||||
width - pattern.Sides[1], height - pattern.Sides[2])
|
|
||||||
if (image.Point { x, y }).In (innerBounds) {
|
|
||||||
return pattern.Fill.AtWhen (
|
|
||||||
x - pattern.Sides[3],
|
|
||||||
y - pattern.Sides[0],
|
|
||||||
innerBounds.Dx(), innerBounds.Dy())
|
|
||||||
} else {
|
|
||||||
return pattern.Stroke.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Checkered is a pattern that produces a grid of two alternating colors.
|
|
||||||
type Checkered struct {
|
|
||||||
First Pattern
|
|
||||||
Second Pattern
|
|
||||||
CellWidth, CellHeight int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Checkered) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
twidth := pattern.CellWidth * 2
|
|
||||||
theight := pattern.CellHeight * 2
|
|
||||||
x %= twidth
|
|
||||||
y %= theight
|
|
||||||
if x < 0 { x += twidth }
|
|
||||||
if y < 0 { x += theight }
|
|
||||||
|
|
||||||
n := 0
|
|
||||||
if x >= pattern.CellWidth { n ++ }
|
|
||||||
if y >= pattern.CellHeight { n ++ }
|
|
||||||
|
|
||||||
x %= pattern.CellWidth
|
|
||||||
y %= pattern.CellHeight
|
|
||||||
|
|
||||||
if n % 2 == 0 {
|
|
||||||
return pattern.First.AtWhen (
|
|
||||||
x, y, pattern.CellWidth, pattern.CellHeight)
|
|
||||||
} else {
|
|
||||||
return pattern.Second.AtWhen (
|
|
||||||
x, y, pattern.CellWidth, pattern.CellHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tiled is a pattern that tiles another pattern accross a grid.
|
|
||||||
type Tiled struct {
|
|
||||||
Pattern
|
|
||||||
CellWidth, CellHeight int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Tiled) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
x %= pattern.CellWidth
|
|
||||||
y %= pattern.CellHeight
|
|
||||||
if x < 0 { x += pattern.CellWidth }
|
|
||||||
if y < 0 { y += pattern.CellHeight }
|
|
||||||
return pattern.Pattern.AtWhen (
|
|
||||||
x, y, pattern.CellWidth, pattern.CellHeight)
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// EllipticallyBordered is a pattern with a border and a fill that is elliptical
|
|
||||||
// in shape.
|
|
||||||
type EllipticallyBordered struct {
|
|
||||||
Fill Pattern
|
|
||||||
Stroke
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern EllipticallyBordered) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
xf := (float64(x) + 0.5) / float64(width ) * 2 - 1
|
|
||||||
yf := (float64(y) + 0.5) / float64(height) * 2 - 1
|
|
||||||
distance := math.Sqrt(xf * xf + yf * yf)
|
|
||||||
|
|
||||||
var radius float64
|
|
||||||
if width < height {
|
|
||||||
// vertical
|
|
||||||
radius = 1 - float64(pattern.Weight * 2) / float64(width)
|
|
||||||
} else {
|
|
||||||
// horizontal
|
|
||||||
radius = 1 - float64(pattern.Weight * 2) / float64(height)
|
|
||||||
}
|
|
||||||
|
|
||||||
if distance < radius {
|
|
||||||
return pattern.Fill.AtWhen(x, y, width, height)
|
|
||||||
} else {
|
|
||||||
return pattern.Stroke.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Dotted is a pattern that produces a grid of circles.
|
|
||||||
type Dotted struct {
|
|
||||||
Background Pattern
|
|
||||||
Foreground Pattern
|
|
||||||
Size int
|
|
||||||
Spacing int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Dotted) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
xm := x % pattern.Spacing
|
|
||||||
ym := y % pattern.Spacing
|
|
||||||
if xm < 0 { xm += pattern.Spacing }
|
|
||||||
if ym < 0 { xm += pattern.Spacing }
|
|
||||||
radius := float64(pattern.Size) / 2
|
|
||||||
spacing := float64(pattern.Spacing) / 2 - 0.5
|
|
||||||
xf := float64(xm) - spacing
|
|
||||||
yf := float64(ym) - spacing
|
|
||||||
|
|
||||||
if math.Sqrt(xf * xf + yf * yf) > radius {
|
|
||||||
return pattern.Background.AtWhen(x, y, width, height)
|
|
||||||
} else {
|
|
||||||
return pattern.Foreground.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Gradient is a pattern that interpolates between two colors.
|
|
||||||
type Gradient struct {
|
|
||||||
First Pattern
|
|
||||||
Second Pattern
|
|
||||||
Orientation
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Gradient) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
var position float64
|
|
||||||
switch pattern.Orientation {
|
|
||||||
case OrientationVertical:
|
|
||||||
position = float64(y) / float64(height)
|
|
||||||
case OrientationDiagonalRight:
|
|
||||||
position = (float64(width - x) / float64(width) +
|
|
||||||
float64(y) / float64(height)) / 2
|
|
||||||
case OrientationHorizontal:
|
|
||||||
position = float64(x) / float64(width)
|
|
||||||
case OrientationDiagonalLeft:
|
|
||||||
position = (float64(x) / float64(width) +
|
|
||||||
float64(y) / float64(height)) / 2
|
|
||||||
}
|
|
||||||
|
|
||||||
firstColor := pattern.First.AtWhen(x, y, width, height)
|
|
||||||
secondColor := pattern.Second.AtWhen(x, y, width, height)
|
|
||||||
return LerpRGBA(firstColor, secondColor, position)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lerp linearally interpolates between two integer values.
|
|
||||||
func Lerp (first, second int, fac float64) (n int) {
|
|
||||||
return int(float64(first) * (1 - fac) + float64(second) * fac)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LerpRGBA linearally interpolates between two color.RGBA values.
|
|
||||||
func LerpRGBA (first, second color.RGBA, fac float64) (c color.RGBA) {
|
|
||||||
return color.RGBA {
|
|
||||||
R: uint8(Lerp(int(first.R), int(second.R), fac)),
|
|
||||||
G: uint8(Lerp(int(first.G), int(second.G), fac)),
|
|
||||||
B: uint8(Lerp(int(first.G), int(second.B), fac)),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Noisy is a pattern that randomly interpolates between two patterns in a
|
|
||||||
// deterministic fashion.
|
|
||||||
type Noisy struct {
|
|
||||||
Low Pattern
|
|
||||||
High Pattern
|
|
||||||
Seed uint32
|
|
||||||
Harsh bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the pattern interface.
|
|
||||||
func (pattern Noisy) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
// FIXME: this will occasionally generate "clumps"
|
|
||||||
special := uint32(x + y * 348905)
|
|
||||||
special += (pattern.Seed + 1) * 15485863
|
|
||||||
random := (special * special * special % 2038074743)
|
|
||||||
fac := float64(random) / 2038074743.0
|
|
||||||
|
|
||||||
if pattern.Harsh {
|
|
||||||
if fac > 0.5 {
|
|
||||||
return pattern.High.AtWhen(x, y, width, height)
|
|
||||||
} else {
|
|
||||||
return pattern.Low.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return LerpRGBA (
|
|
||||||
pattern.Low.AtWhen(x, y, width, height),
|
|
||||||
pattern.High.AtWhen(x, y, width, height), fac)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Orientation specifies an eight-way pattern orientation.
|
|
||||||
type Orientation int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OrientationVertical Orientation = iota
|
|
||||||
OrientationDiagonalRight
|
|
||||||
OrientationHorizontal
|
|
||||||
OrientationDiagonalLeft
|
|
||||||
)
|
|
||||||
|
|
||||||
// Split is a pattern that is divided in half between two sub-patterns.
|
|
||||||
type Split struct {
|
|
||||||
First Pattern
|
|
||||||
Second Pattern
|
|
||||||
Orientation
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Split) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
var first bool
|
|
||||||
switch pattern.Orientation {
|
|
||||||
case OrientationVertical:
|
|
||||||
first = x < width / 2
|
|
||||||
case OrientationDiagonalRight:
|
|
||||||
first = float64(x) / float64(width) +
|
|
||||||
float64(y) / float64(height) < 1
|
|
||||||
case OrientationHorizontal:
|
|
||||||
first = y < height / 2
|
|
||||||
case OrientationDiagonalLeft:
|
|
||||||
first = float64(width - x) / float64(width) +
|
|
||||||
float64(y) / float64(height) < 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if first {
|
|
||||||
return pattern.First.AtWhen(x, y, width, height)
|
|
||||||
} else {
|
|
||||||
return pattern.Second.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Striped is a pattern that produces stripes of two alternating colors.
|
|
||||||
type Striped struct {
|
|
||||||
First Stroke
|
|
||||||
Second Stroke
|
|
||||||
Orientation
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (pattern Striped) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
position := 0
|
|
||||||
switch pattern.Orientation {
|
|
||||||
case OrientationVertical:
|
|
||||||
position = x
|
|
||||||
case OrientationDiagonalRight:
|
|
||||||
position = x + y
|
|
||||||
case OrientationHorizontal:
|
|
||||||
position = y
|
|
||||||
case OrientationDiagonalLeft:
|
|
||||||
position = x - y
|
|
||||||
}
|
|
||||||
|
|
||||||
phase := pattern.First.Weight + pattern.Second.Weight
|
|
||||||
position %= phase
|
|
||||||
if position < 0 {
|
|
||||||
position += phase
|
|
||||||
}
|
|
||||||
|
|
||||||
if position < pattern.First.Weight {
|
|
||||||
return pattern.First.AtWhen(x, y, width, height)
|
|
||||||
} else {
|
|
||||||
return pattern.Second.AtWhen(x, y, width, height)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +1,37 @@
|
|||||||
package artist
|
package patterns
|
||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
|
|
||||||
// Texture is a struct that allows an image to be converted into a tiling
|
// Texture is a pattern that tiles the content of a canvas both horizontally and
|
||||||
// texture pattern.
|
// vertically.
|
||||||
type Texture struct {
|
type Texture struct {
|
||||||
data []color.RGBA
|
canvas.Canvas
|
||||||
width, height int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTexture converts an image into a texture.
|
// Draw tiles the pattern's canvas within the clipping bounds. The minimum
|
||||||
func NewTexture (source image.Image) (texture Texture) {
|
// points of the pattern's canvas and the destination canvas will be lined up.
|
||||||
bounds := source.Bounds()
|
func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
|
||||||
texture.width = bounds.Dx()
|
bounds := clip.Canon().Intersect(destination.Bounds())
|
||||||
texture.height = bounds.Dy()
|
if bounds.Empty() { return }
|
||||||
texture.data = make([]color.RGBA, texture.width * texture.height)
|
|
||||||
|
dstData, dstStride := destination.Buffer()
|
||||||
|
srcData, srcStride := pattern.Buffer()
|
||||||
|
srcBounds := pattern.Bounds()
|
||||||
|
|
||||||
index := 0
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||||
r, g, b, a := source.At(x, y).RGBA()
|
dstIndex := x + y * dstStride
|
||||||
texture.data[index] = color.RGBA {
|
srcIndex :=
|
||||||
uint8(r >> 8),
|
wrap(x - bounds.Min.X, srcBounds.Min.X, srcBounds.Max.X) +
|
||||||
uint8(g >> 8),
|
wrap(x - bounds.Min.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride
|
||||||
uint8(b >> 8),
|
dstData[dstIndex] = srcData[srcIndex]
|
||||||
uint8(a >> 8),
|
|
||||||
}
|
|
||||||
index ++
|
|
||||||
}}
|
}}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtWhen returns the color at the specified x and y coordinates, wrapped to the
|
func wrap (value, min, max int) int {
|
||||||
// image's width. the width and height are ignored.
|
difference := max - min
|
||||||
func (texture Texture) AtWhen (x, y, width, height int) (pixel color.RGBA) {
|
value = (value - min) % difference
|
||||||
x %= texture.width
|
if value < 0 { value += difference }
|
||||||
y %= texture.height
|
return value + min
|
||||||
if x < 0 { x += texture.width }
|
|
||||||
if y < 0 { y += texture.height }
|
|
||||||
return texture.data[x + y * texture.width]
|
|
||||||
}
|
}
|
||||||
|
@ -1,68 +1,14 @@
|
|||||||
package artist
|
package patterns
|
||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
|
||||||
|
|
||||||
// Uniform is an infinite-sized pattern of uniform color. It implements the
|
// Uniform is a pattern that draws a solid color.
|
||||||
// Pattern, color.Color, color.Model, and image.Image interfaces.
|
|
||||||
type Uniform color.RGBA
|
type Uniform color.RGBA
|
||||||
|
|
||||||
// NewUniform returns a new Uniform pattern of the given color.
|
// Draw fills the clipping rectangle with the pattern's color.
|
||||||
func NewUniform (c color.Color) (uniform Uniform) {
|
func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) {
|
||||||
r, g, b, a := c.RGBA()
|
shapes.FillColorRectangle(destination, color.RGBA(pattern), clip)
|
||||||
uniform.R = uint8(r >> 8)
|
|
||||||
uniform.G = uint8(g >> 8)
|
|
||||||
uniform.B = uint8(b >> 8)
|
|
||||||
uniform.A = uint8(a >> 8)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uhex creates a new Uniform pattern from an RGBA integer value.
|
|
||||||
func Uhex (color uint32) (uniform Uniform) {
|
|
||||||
return NewUniform(hex(color))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorModel satisfies the image.Image interface.
|
|
||||||
func (uniform Uniform) ColorModel () (model color.Model) {
|
|
||||||
return uniform
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert satisfies the color.Model interface.
|
|
||||||
func (uniform Uniform) Convert (in color.Color) (c color.Color) {
|
|
||||||
return color.RGBA(uniform)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bounds satisfies the image.Image interface.
|
|
||||||
func (uniform Uniform) Bounds () (rectangle image.Rectangle) {
|
|
||||||
rectangle.Min = image.Point { -1e9, -1e9 }
|
|
||||||
rectangle.Max = image.Point { 1e9, 1e9 }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// At satisfies the image.Image interface.
|
|
||||||
func (uniform Uniform) At (x, y int) (c color.Color) {
|
|
||||||
return color.RGBA(uniform)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AtWhen satisfies the Pattern interface.
|
|
||||||
func (uniform Uniform) AtWhen (x, y, width, height int) (c color.RGBA) {
|
|
||||||
return color.RGBA(uniform)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RGBA satisfies the color.Color interface.
|
|
||||||
func (uniform Uniform) RGBA () (r, g, b, a uint32) {
|
|
||||||
return color.RGBA(uniform).RGBA()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opaque scans the entire image and reports whether it is fully opaque.
|
|
||||||
func (uniform Uniform) Opaque () (opaque bool) {
|
|
||||||
return uniform.A == 0xFF
|
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// WrappedPattern is a pattern that is able to behave like an image.Image.
|
|
||||||
type WrappedPattern struct {
|
|
||||||
Pattern
|
|
||||||
Width, Height int
|
|
||||||
}
|
|
||||||
|
|
||||||
// At satisfies the image.Image interface.
|
|
||||||
func (pattern WrappedPattern) At (x, y int) (c color.Color) {
|
|
||||||
return pattern.Pattern.AtWhen(x, y, pattern.Width, pattern.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bounds satisfies the image.Image interface.
|
|
||||||
func (pattern WrappedPattern) Bounds () (rectangle image.Rectangle) {
|
|
||||||
rectangle.Min = image.Point { -1e9, -1e9 }
|
|
||||||
rectangle.Max = image.Point { 1e9, 1e9 }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColorModel satisfies the image.Image interface.
|
|
||||||
func (pattern WrappedPattern) ColorModel () (model color.Model) {
|
|
||||||
return color.RGBAModel
|
|
||||||
}
|
|
Reference in New Issue
Block a user