Elements are now pass-scoped value handles: tests that read bounds after a frame bind a ui.Handle. pixel tells frames apart by the build pass's root, since the Tester clock no longer moves on its own. Fix the regressions the upgrade brought: - Clipboard reads in input handlers go through c.Services(). - MyGo notes an overlay's opener after building it, so AutoFocus inside lost it; pixel.AutoFocus focuses through a Handle, and archtest bans the raw call. Dialogs hand the focus back again. - Elements around a press stay hovered: Tooltip tells a press-given focus by focus arriving under the pointer. - The web host moves the Tester clock with real time, at most 500 ms a call, and gives components the real clock. The gallery's source index also accepts CRLF checkouts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
package display
|
||
|
||
import (
|
||
"image"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||
)
|
||
|
||
type PictureOptions struct {
|
||
// W×H is the size in dots; zero takes the image's.
|
||
W, H int
|
||
Label string
|
||
}
|
||
|
||
type pictureKey struct{}
|
||
|
||
type pictureCache struct {
|
||
sum uint64
|
||
pal *pixel.Palette
|
||
w, h int
|
||
sp *pixel.Sprite
|
||
}
|
||
|
||
// Picture reduces img to the palette with Bayer dither.
|
||
func Picture(c *ui.Context, img image.Image, o PictureOptions) ui.Element {
|
||
k := pixel.K(c)
|
||
b := img.Bounds()
|
||
w, h := pixel.Or("Picture W", o.W, b.Dx()), pixel.Or("Picture H", o.H, b.Dy())
|
||
e := art(c, k, o.Label, w, h, nil)
|
||
pc := ui.Local(e, pictureKey{}, func() pictureCache { return pictureCache{} })
|
||
if sum := fingerprint(img); pc.sp == nil || pc.sum != sum || pc.pal != k.Palette() || pc.w != w || pc.h != h {
|
||
*pc = pictureCache{sum, k.Palette(), w, h, Reduce(img, w, h, k.Palette())}
|
||
}
|
||
sp := pc.sp
|
||
return e.Draw(func(p *ui.Painter, r ui.Rect) {
|
||
k.Canvas(p, r).Sprite(0, 0, sp, 0, pixel.Ink, 1)
|
||
})
|
||
}
|
||
|
||
// Reduce maps img onto pal as a dithered w×h sprite.
|
||
func Reduce(img image.Image, w, h int, pal *pixel.Palette) *pixel.Sprite {
|
||
b := img.Bounds()
|
||
return pixel.SpriteOf(w, h, func(x, y int) (pixel.Slot, bool) {
|
||
y0, y1 := span(b.Min.Y, b.Dy(), y, h)
|
||
x0, x1 := span(b.Min.X, b.Dx(), x, w)
|
||
var sum [4]uint32
|
||
n := uint32(0)
|
||
for sy := y0; sy < y1; sy++ {
|
||
for sx := x0; sx < x1; sx++ {
|
||
r, g, bl, a := img.At(sx, sy).RGBA()
|
||
sum[0], sum[1], sum[2], sum[3] = sum[0]+r>>8, sum[1]+g>>8, sum[2]+bl>>8, sum[3]+a>>8
|
||
n++
|
||
}
|
||
}
|
||
if sum[3]/n < 128 {
|
||
return 0, false
|
||
}
|
||
nudge := (float64(pixel.Bayer(x, y))+0.5)/16 - 0.5
|
||
return nearest(pal, [3]float64{
|
||
float64(sum[0]/n) + nudge*48, float64(sum[1]/n) + nudge*48, float64(sum[2]/n) + nudge*48,
|
||
}), true
|
||
})
|
||
}
|
||
|
||
// fingerprint mixes img's bounds and pixels, to notice edits.
|
||
func fingerprint(img image.Image) uint64 {
|
||
b := img.Bounds()
|
||
s := uint64(b.Min.X)<<48 ^ uint64(b.Min.Y)<<32 ^ uint64(b.Dx())<<16 ^ uint64(b.Dy())
|
||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||
for x := b.Min.X; x < b.Max.X; x++ {
|
||
r, g, bl, a := img.At(x, y).RGBA()
|
||
s = (s ^ uint64(r>>8)<<24 ^ uint64(g>>8)<<16 ^ uint64(bl>>8)<<8 ^ uint64(a>>8)) * 1099511628211
|
||
}
|
||
}
|
||
return s
|
||
}
|
||
|
||
// span is the source range under cell i of n.
|
||
func span(min, size, i, n int) (int, int) {
|
||
a, b := min+i*size/n, min+(i+1)*size/n
|
||
return a, max(b, a+1)
|
||
}
|
||
|
||
// nearest is the slot closest to rgb.
|
||
func nearest(pal *pixel.Palette, rgb [3]float64) pixel.Slot {
|
||
best, dist := pixel.Ink, -1.0
|
||
for s, c := range pal.Colors {
|
||
dr, dg, db := rgb[0]-float64(c.R), rgb[1]-float64(c.G), rgb[2]-float64(c.B)
|
||
if d := dr*dr + dg*dg + db*db; dist < 0 || d < dist {
|
||
best, dist = pixel.Slot(s), d
|
||
}
|
||
}
|
||
return best
|
||
}
|