2024-06-02 11:45:32 -06:00
|
|
|
package xcanvas
|
|
|
|
|
|
|
|
import "sort"
|
2024-06-24 17:04:08 -06:00
|
|
|
import "math"
|
2024-06-02 11:45:32 -06:00
|
|
|
import "image"
|
|
|
|
import "github.com/jezek/xgbutil/xgraphics"
|
|
|
|
|
|
|
|
func (this *pen) textureRectangle (bounds image.Rectangle) {
|
|
|
|
if this.texture.Opaque() {
|
|
|
|
this.textureRectangleOpaque(bounds)
|
|
|
|
} else {
|
|
|
|
this.textureRectangleTransparent(bounds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) textureRectangleOpaque (bounds image.Rectangle) {
|
|
|
|
dstBounds := bounds.Intersect(this.image.Bounds())
|
|
|
|
var pos image.Point
|
|
|
|
|
|
|
|
dst := this.image.Pix
|
|
|
|
src := this.texture.pix
|
|
|
|
offset := this.texture.rect.Min.Sub(bounds.Min)
|
|
|
|
|
|
|
|
for pos.Y = dstBounds.Min.Y; pos.Y < dstBounds.Max.Y; pos.Y ++ {
|
|
|
|
for pos.X = dstBounds.Min.X; pos.X < dstBounds.Max.X; pos.X ++ {
|
|
|
|
srcPos := pos.Add(offset)
|
|
|
|
dstIndex := this.image.PixOffset(pos.X, pos.Y)
|
|
|
|
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
|
|
|
|
dst[dstIndex + 0] = src[srcIndex + 0]
|
|
|
|
dst[dstIndex + 1] = src[srcIndex + 1]
|
|
|
|
dst[dstIndex + 2] = src[srcIndex + 2]
|
|
|
|
dst[dstIndex + 3] = src[srcIndex + 3]
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) textureRectangleTransparent (bounds image.Rectangle) {
|
|
|
|
dstBounds := bounds.Intersect(this.image.Bounds())
|
|
|
|
var pos image.Point
|
|
|
|
|
|
|
|
dst := this.image.Pix
|
|
|
|
src := this.texture.pix
|
|
|
|
offset := this.texture.rect.Min.Sub(bounds.Min)
|
|
|
|
|
|
|
|
for pos.Y = dstBounds.Min.Y; pos.Y < dstBounds.Max.Y; pos.Y ++ {
|
|
|
|
for pos.X = dstBounds.Min.X; pos.X < dstBounds.Max.X; pos.X ++ {
|
|
|
|
srcPos := pos.Add(offset)
|
|
|
|
dstIndex := this.image.PixOffset(pos.X, pos.Y)
|
|
|
|
srcIndex := this.texture.PixOffset(srcPos.X, srcPos.Y)
|
|
|
|
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
|
|
|
B: dst[dstIndex + 0],
|
|
|
|
G: dst[dstIndex + 1],
|
|
|
|
R: dst[dstIndex + 2],
|
|
|
|
A: dst[dstIndex + 3],
|
|
|
|
}, xgraphics.BGRA {
|
|
|
|
B: src[srcIndex + 0],
|
|
|
|
G: src[srcIndex + 1],
|
|
|
|
R: src[srcIndex + 2],
|
|
|
|
A: src[srcIndex + 3],
|
|
|
|
})
|
|
|
|
dst[dstIndex + 0] = pixel.B
|
|
|
|
dst[dstIndex + 1] = pixel.G
|
|
|
|
dst[dstIndex + 2] = pixel.R
|
|
|
|
dst[dstIndex + 3] = pixel.A
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) fillRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
|
|
|
|
if c.A == 255 {
|
|
|
|
this.fillRectangleOpaque(c, bounds)
|
|
|
|
} else {
|
|
|
|
this.fillRectangleTransparent(c, bounds)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) fillRectangleOpaque (c xgraphics.BGRA, bounds image.Rectangle) {
|
|
|
|
bounds = bounds.Intersect(this.image.Bounds())
|
|
|
|
var pos image.Point
|
|
|
|
|
|
|
|
for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ {
|
|
|
|
for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
|
|
|
|
index := this.image.PixOffset(pos.X, pos.Y)
|
|
|
|
this.image.Pix[index + 0] = c.B
|
|
|
|
this.image.Pix[index + 1] = c.G
|
|
|
|
this.image.Pix[index + 2] = c.R
|
|
|
|
this.image.Pix[index + 3] = c.A
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) fillRectangleTransparent (c xgraphics.BGRA, bounds image.Rectangle) {
|
|
|
|
bounds = bounds.Intersect(this.image.Bounds())
|
|
|
|
var pos image.Point
|
|
|
|
|
|
|
|
for pos.Y = bounds.Min.Y; pos.Y < bounds.Max.Y; pos.Y ++ {
|
|
|
|
for pos.X = bounds.Min.X; pos.X < bounds.Max.X; pos.X ++ {
|
|
|
|
index := this.image.PixOffset(pos.X, pos.Y)
|
|
|
|
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
|
|
|
B: this.image.Pix[index + 0],
|
|
|
|
G: this.image.Pix[index + 1],
|
|
|
|
R: this.image.Pix[index + 2],
|
|
|
|
A: this.image.Pix[index + 3],
|
|
|
|
}, c)
|
|
|
|
this.image.Pix[index + 0] = pixel.B
|
|
|
|
this.image.Pix[index + 1] = pixel.G
|
|
|
|
this.image.Pix[index + 2] = pixel.R
|
|
|
|
this.image.Pix[index + 3] = pixel.A
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) strokeRectangle (c xgraphics.BGRA, bounds image.Rectangle) {
|
|
|
|
if this.weight > bounds.Dx() / 2 || this.weight > bounds.Dy() / 2 {
|
|
|
|
this.fillRectangle(c, bounds)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
top := image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Min.Y,
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Min.Y + this.weight)
|
|
|
|
bottom := image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Max.Y - this.weight,
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Max.Y)
|
|
|
|
left := image.Rect (
|
|
|
|
bounds.Min.X,
|
|
|
|
bounds.Min.Y + this.weight,
|
|
|
|
bounds.Min.X + this.weight,
|
|
|
|
bounds.Max.Y - this.weight)
|
|
|
|
right := image.Rect (
|
|
|
|
bounds.Max.X - this.weight,
|
|
|
|
bounds.Min.Y + this.weight,
|
|
|
|
bounds.Max.X,
|
|
|
|
bounds.Max.Y - this.weight)
|
|
|
|
|
|
|
|
this.fillRectangle(c, top,)
|
|
|
|
this.fillRectangle(c, bottom,)
|
|
|
|
this.fillRectangle(c, left,)
|
|
|
|
this.fillRectangle(c, right,)
|
|
|
|
}
|
|
|
|
|
|
|
|
// the polygon filling algorithm is adapted from:
|
|
|
|
// https://www.alienryderflex.com/polygon_fill/
|
|
|
|
// (if you write C like that i will disassemble you)
|
|
|
|
|
|
|
|
func (this *pen) fillPolygon (c xgraphics.BGRA, points ...image.Point) {
|
|
|
|
if len(points) < 3 { return }
|
|
|
|
|
|
|
|
// figure out the bounds of the polygon so we don't test empty space
|
|
|
|
var area image.Rectangle
|
|
|
|
area.Min = points[0]
|
|
|
|
area.Max = points[0]
|
|
|
|
for _, point := range points[1:] {
|
|
|
|
if point.X < area.Min.X { area.Min.X = point.X }
|
|
|
|
if point.Y < area.Min.Y { area.Min.Y = point.Y }
|
|
|
|
if point.X > area.Max.X { area.Max.X = point.X }
|
|
|
|
if point.Y > area.Max.Y { area.Max.Y = point.Y }
|
|
|
|
}
|
|
|
|
area = this.image.Bounds().Intersect(area)
|
|
|
|
if area.Empty() { return }
|
|
|
|
|
2024-06-24 17:11:21 -06:00
|
|
|
boundaries := make([]int, len(points))
|
2024-06-02 11:45:32 -06:00
|
|
|
context := fillingContext {
|
|
|
|
image: this.image,
|
|
|
|
color: this.fill,
|
|
|
|
min: area.Min.X,
|
|
|
|
max: area.Max.X,
|
|
|
|
points: points,
|
|
|
|
}
|
|
|
|
|
|
|
|
for context.y = area.Min.Y; context.y < area.Max.Y; context.y ++ {
|
|
|
|
// build boundary list
|
|
|
|
boundaryCount := 0
|
|
|
|
prevPoint := points[len(points) - 1]
|
|
|
|
for _, point := range points {
|
|
|
|
fy := float64(context.y)
|
|
|
|
fPointX := float64(point.X)
|
|
|
|
fPointY := float64(point.Y)
|
|
|
|
fPrevX := float64(prevPoint.X)
|
|
|
|
fPrevY := float64(prevPoint.Y)
|
|
|
|
addboundary :=
|
|
|
|
(fPointY < fy && fPrevY >= fy) ||
|
|
|
|
(fPrevY < fy && fPointY >= fy)
|
|
|
|
if addboundary {
|
2024-06-24 17:11:21 -06:00
|
|
|
boundaries[boundaryCount] = int(math.Round (
|
2024-06-02 11:45:32 -06:00
|
|
|
fPointX +
|
|
|
|
(fy - fPointY) /
|
|
|
|
(fPrevY - fPointY) *
|
2024-06-24 17:04:08 -06:00
|
|
|
(fPrevX - fPointX)))
|
2024-06-02 11:45:32 -06:00
|
|
|
boundaryCount ++
|
|
|
|
}
|
|
|
|
prevPoint = point
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort boundary list
|
2024-06-24 17:11:21 -06:00
|
|
|
context.boundaries = boundaries[:boundaryCount]
|
|
|
|
sort.Ints(context.boundaries)
|
2024-06-02 11:45:32 -06:00
|
|
|
|
|
|
|
// fill pixels between boundary pairs
|
|
|
|
if c.A == 255 {
|
|
|
|
context.fillPolygonHotOpaque()
|
|
|
|
} else {
|
|
|
|
context.fillPolygonHotTransparent()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
type fillingContext struct {
|
|
|
|
image *xgraphics.Image
|
|
|
|
color xgraphics.BGRA
|
|
|
|
min, max int
|
|
|
|
y int
|
|
|
|
boundaries []int
|
|
|
|
points []image.Point
|
|
|
|
}
|
|
|
|
|
|
|
|
func (context *fillingContext) fillPolygonHotOpaque () {
|
2024-06-24 16:42:43 -06:00
|
|
|
for index := 0; index < len(context.boundaries) - 1; index += 2 {
|
2024-06-02 11:45:32 -06:00
|
|
|
left := context.boundaries[index]
|
|
|
|
right := context.boundaries[index + 1]
|
|
|
|
|
|
|
|
// stop if we have exited the polygon
|
|
|
|
if left >= context.max { break }
|
|
|
|
// begin filling if we are within the polygon
|
|
|
|
if right > context.min {
|
|
|
|
// constrain boundaries to image size
|
|
|
|
if left < context.min { left = context.min }
|
|
|
|
if right > context.max { right = context.max }
|
|
|
|
|
|
|
|
// fill pixels in between
|
|
|
|
for x := left; x < right; x ++ {
|
|
|
|
index := context.image.PixOffset(x, context.y)
|
|
|
|
context.image.Pix[index + 0] = context.color.B
|
|
|
|
context.image.Pix[index + 1] = context.color.G
|
|
|
|
context.image.Pix[index + 2] = context.color.R
|
|
|
|
context.image.Pix[index + 3] = context.color.A
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (context *fillingContext) fillPolygonHotTransparent () {
|
2024-06-24 16:42:43 -06:00
|
|
|
for index := 0; index < len(context.boundaries) - 1; index += 2 {
|
2024-06-02 11:45:32 -06:00
|
|
|
left := context.boundaries[index]
|
|
|
|
right := context.boundaries[index + 1]
|
|
|
|
|
|
|
|
// stop if we have exited the polygon
|
|
|
|
if left >= context.max { break }
|
|
|
|
// begin filling if we are within the polygon
|
|
|
|
if right > context.min {
|
|
|
|
// constrain boundaries to image size
|
|
|
|
if left < context.min { left = context.min }
|
|
|
|
if right > context.max { right = context.max }
|
|
|
|
|
|
|
|
// fill pixels in between
|
|
|
|
for x := left; x < right; x ++ {
|
|
|
|
index := context.image.PixOffset(x, context.y)
|
|
|
|
pixel := xgraphics.BlendBGRA(xgraphics.BGRA {
|
|
|
|
B: context.image.Pix[index + 0],
|
|
|
|
G: context.image.Pix[index + 1],
|
|
|
|
R: context.image.Pix[index + 2],
|
|
|
|
A: context.image.Pix[index + 3],
|
|
|
|
}, context.color)
|
|
|
|
context.image.Pix[index + 0] = pixel.B
|
|
|
|
context.image.Pix[index + 1] = pixel.G
|
|
|
|
context.image.Pix[index + 2] = pixel.R
|
|
|
|
context.image.Pix[index + 3] = pixel.A
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) strokePolygon (c xgraphics.BGRA, points ...image.Point) {
|
|
|
|
prevPoint := points[len(points) - 1]
|
|
|
|
for _, point := range points {
|
|
|
|
this.line(c, prevPoint, point)
|
|
|
|
prevPoint = point
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *pen) polyLine (c xgraphics.BGRA, points ...image.Point) {
|
|
|
|
if len(points) < 2 { return }
|
|
|
|
prevPoint := points[0]
|
|
|
|
for _, point := range points[1:] {
|
|
|
|
this.line(c, prevPoint, point)
|
|
|
|
prevPoint = point
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func wrap (n, min, max int) int {
|
|
|
|
max -= min
|
|
|
|
n -= min
|
|
|
|
n %= max
|
|
|
|
if n < 0 { n += max }
|
|
|
|
return n + min
|
|
|
|
}
|