seedetcher/image/ninepatch/ninepatch.go
2025-01-23 01:45:19 +01:00

142 lines
2.9 KiB
Go

// package ninepatch contains an image.Image implementation of stretchable
// images in 9-patch format.
package ninepatch
import (
"image"
"image/color"
"seedetcher.com/gui/op"
)
type Template struct {
inner image.Rectangle
padding image.Rectangle
src image.RGBA64Image
srcb image.Rectangle
img op.Image
}
func New(src image.RGBA64Image) *Template {
inner := nineBounds(src, 0, 0)
b := src.Bounds()
padding := nineBounds(src, b.Max.Y-1, b.Max.X-1)
srcb := src.Bounds()
np := &Template{
inner: inner,
padding: padding,
src: src,
srcb: image.Rectangle{
Min: srcb.Min.Add(image.Pt(1, 1)),
Max: srcb.Max.Sub(image.Pt(1, 1)),
},
}
np.img = op.RegisterParameterizedImage(np.at)
return np
}
func nineBounds(img image.RGBA64Image, row, col int) image.Rectangle {
var res image.Rectangle
b := img.Bounds()
for x := b.Min.X + 1; x < b.Max.X-1; x++ {
c := img.RGBA64At(x, row)
_, _, _, a := c.RGBA()
if a != 0 {
res.Min.X = x
res.Max.X = x
break
}
}
for y := b.Min.Y + 1; y < b.Max.Y-1; y++ {
c := img.RGBA64At(col, y)
_, _, _, a := c.RGBA()
if a != 0 {
res.Min.Y = y
res.Max.Y = y
break
}
}
for x := b.Max.X - 2; x > res.Min.X; x-- {
c := img.RGBA64At(x, row)
_, _, _, a := c.RGBA()
if a != 0 {
res.Max.X = x + 1
break
}
}
for y := b.Max.Y - 2; y > res.Min.Y; y-- {
c := img.RGBA64At(col, y)
_, _, _, a := c.RGBA()
if a != 0 {
res.Max.Y = y + 1
break
}
}
return res
}
func (n *Template) Padding() (int, int, int, int) {
b := n.src.Bounds()
return n.padding.Min.Y, b.Max.X - n.padding.Max.X,
b.Max.Y - n.padding.Max.Y, n.padding.Min.X
}
func (n *Template) Bounds(r image.Rectangle) image.Rectangle {
top, end, bottom, start := n.Padding()
r.Min.X -= start
r.Max.X += end
r.Min.Y -= top
r.Max.Y += bottom
return r
}
func (n *Template) Add(ops op.Ctx, r image.Rectangle, mask bool) image.Rectangle {
r = n.Bounds(r)
op.ParamImageOp(ops, n.img, mask, r, nil, nil)
return r
}
func (n *Template) at(args op.ImageArguments, x, y int) color.RGBA64 {
x -= args.Bounds.Min.X
y -= args.Bounds.Min.Y
x += n.srcb.Min.X
y += n.srcb.Min.Y
x = adjust(x, args.Bounds.Dx(), n.inner.Min.X, n.inner.Max.X, n.srcb.Min.X, n.srcb.Max.X)
y = adjust(y, args.Bounds.Dy(), n.inner.Min.Y, n.inner.Max.Y, n.srcb.Min.Y, n.srcb.Max.Y)
return n.src.RGBA64At(x, y)
}
func adjust(v, sz, innerMin, innerMax, srcMin, srcMax int) int {
limit := 0
len := innerMin
if v >= limit+len {
limit += len
innersz := innerMax - innerMin
startsz := innerMin - srcMin
endsz := srcMax - innerMax
outerw := sz - startsz - endsz
stretch := outerw - innersz
len := stretch / 2
if v >= limit+len {
limit += len
len := innersz
if v >= limit+len {
limit += len
len := stretch - stretch/2
if v >= limit+len {
limit += len
v = v - limit + innerMax
} else {
v = innerMax - 1
}
} else {
v = v - limit + innerMin
}
} else {
v = innerMin
}
}
return v
}