From 3004e4d88e55bdc2e00f7236a2b2111e4d05fd4a Mon Sep 17 00:00:00 2001 From: Kevin Wallace Date: Sat, 25 Apr 2026 02:32:32 -0700 Subject: orca ui replica wip --- Makefile | 2 +- autorun.sh | 9 +- cmd/orca/NotoSans-Bold.ttf | Bin 0 -> 630968 bytes cmd/orca/NotoSans-Regular.ttf | Bin 0 -> 629024 bytes cmd/orca/below.png | Bin 0 -> 1904 bytes cmd/orca/main.go | 125 +++++++++++++++++++++++ cmd/orca/orca.png | Bin 0 -> 25521 bytes fb/fb.go | 228 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 9 +- go.sum | 8 +- 10 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 cmd/orca/NotoSans-Bold.ttf create mode 100644 cmd/orca/NotoSans-Regular.ttf create mode 100644 cmd/orca/below.png create mode 100644 cmd/orca/main.go create mode 100644 cmd/orca/orca.png create mode 100644 fb/fb.go diff --git a/Makefile b/Makefile index 07e308d..75dc005 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ export GOARM=7 USB_ROOT=/Volumes/MP_SD GOSOURCES:=$(shell find . -name *.go) -BINS:=bin/cvend-ipp bin/pic32-ipp bin/beep +BINS:=bin/cvend-ipp bin/pic32-ipp bin/beep bin/orca all: ${BINS} diff --git a/autorun.sh b/autorun.sh index 4d6697c..30e0382 100644 --- a/autorun.sh +++ b/autorun.sh @@ -1,5 +1,6 @@ -#!/bin/bash -x +set -x cd "$(dirname "$0")" -#systemd-run -p StandardOutput=tty -d bin/cvend-ipp -#systemd-run -p StandardOutput=tty -d bin/pic32-ipp -systemd-run -p StandardOutput=tty -d bin/beep +stty -F /dev/ttymxc1 115200 cs8 -cstopb -parenb +# systemd-run -p StandardOutput=tty -d bin/cvend-ipp +# systemd-run -p StandardOutput=tty -d bin/pic32-ipp +systemd-run -p StandardOutput=tty -p TTYPath=/dev/ttymxc1 -d bin/orca diff --git a/cmd/orca/NotoSans-Bold.ttf b/cmd/orca/NotoSans-Bold.ttf new file mode 100644 index 0000000..07f0d25 Binary files /dev/null and b/cmd/orca/NotoSans-Bold.ttf differ diff --git a/cmd/orca/NotoSans-Regular.ttf b/cmd/orca/NotoSans-Regular.ttf new file mode 100644 index 0000000..4bac02f Binary files /dev/null and b/cmd/orca/NotoSans-Regular.ttf differ diff --git a/cmd/orca/below.png b/cmd/orca/below.png new file mode 100644 index 0000000..d4bf5af Binary files /dev/null and b/cmd/orca/below.png differ diff --git a/cmd/orca/main.go b/cmd/orca/main.go new file mode 100644 index 0000000..79c5eab --- /dev/null +++ b/cmd/orca/main.go @@ -0,0 +1,125 @@ +package main + +import ( + "embed" + "flag" + "image" + "image/color" + "image/draw" + _ "image/png" + "io" + "io/fs" + "log" + "os" + "time" + + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" + "pm3.dev/fb" +) + +// openSFNT opens the specified font file +// It keeps the file open indefinitely for lazy loading when the font is used. +// It never attempts to close the file and is meant only for loading fonts permanently at init time. +func openSFNT(fs fs.FS, path string) *sfnt.Font { + f, err := fs.Open(path) + if err != nil { + panic(err) + } + font, err := sfnt.ParseReaderAt(f.(io.ReaderAt)) + if err != nil { + panic(err) + } + return font +} + +func openImage(fs fs.FS, path string) image.Image { + f, err := fs.Open(path) + if err != nil { + panic(err) + } + img, _, err := image.Decode(f) + if err != nil { + panic(err) + } + return img +} + +//go:embed *.ttf *.png +var embedFS embed.FS +var qtFonts = os.DirFS("/usr/lib/fonts") + +var ( + dejaVuSans = openSFNT(qtFonts, "DejaVuSans.ttf") + notoSans_Regular = openSFNT(embedFS, "NotoSans-Regular.ttf") + notoSans_Bold = openSFNT(embedFS, "NotoSans-Bold.ttf") +) + +var ( + orcaImg = openImage(embedFS, "orca.png") + belowImg = openImage(embedFS, "below.png") +) + +type justify int + +const ( + _ justify = iota + justifyLeft + justifyCenter + justifyRight +) + +func drawText(img draw.Image, sfnt *sfnt.Font, size float64, just justify, c color.Color, dot fixed.Point26_6, text string) { + face, err := opentype.NewFace(sfnt, &opentype.FaceOptions{ + Size: size, + DPI: 72, + Hinting: font.HintingNone, + }) + if err != nil { + panic(err) + } + defer face.Close() + drawer := font.Drawer{ + Dst: img, + Src: &image.Uniform{c}, + Face: face, + Dot: dot, + } + _, adv := drawer.BoundString(text) + var offset fixed.Int26_6 + switch just { + case justifyCenter: + offset = adv / 2 + case justifyRight: + offset = adv + } + drawer.Dot.X -= offset + drawer.DrawString(text) +} + +func main() { + flag.Parse() + log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime | log.Lmicroseconds) + log.Println(os.Args[0], "start") + + fb, err := fb.Open() + if err != nil { + log.Fatalf("open fb: %v", err) + } + defer fb.Close() + + for { + now := time.Now() + img := image.NewRGBA(fb.Bounds()) + draw.Draw(img, img.Bounds(), &image.Uniform{color.RGBA{34, 31, 32, 255}}, image.Point{}, draw.Src) + drawText(img, dejaVuSans, 20, justifyLeft, color.White, fixed.P(50, 25), now.Format("Jan 02, 2006")) + drawText(img, dejaVuSans, 20, justifyRight, color.White, fixed.P(750, 25), now.Format("3:04 pm")) + drawText(img, notoSans_Regular, 16, justifyCenter, color.White, fixed.P(400, 25), "V1234567-D98765") + draw.Draw(img, orcaImg.Bounds().Add(image.Point{400 - orcaImg.Bounds().Dx()/2, 75}), orcaImg, image.Point{}, draw.Over) + drawText(img, notoSans_Bold, 48, justifyCenter, color.White, fixed.P(400, 330), "Tap below") + draw.Draw(img, belowImg.Bounds().Add(image.Point{400 - belowImg.Bounds().Dx()/2, 380}), belowImg, image.Point{}, draw.Over) + fb.WriteRGBA(img) + } +} diff --git a/cmd/orca/orca.png b/cmd/orca/orca.png new file mode 100644 index 0000000..ef3af1d Binary files /dev/null and b/cmd/orca/orca.png differ diff --git a/fb/fb.go b/fb/fb.go new file mode 100644 index 0000000..2c28d6d --- /dev/null +++ b/fb/fb.go @@ -0,0 +1,228 @@ +package fb + +import ( + "image" + "image/color" + "image/draw" + "io" + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + Width = 800 + Height = 480 +) + +type fb struct { + fd int + data []byte +} + +var _ interface { + io.Closer + draw.Image +} = &fb{} + +func Open() (*fb, error) { + fd, err := unix.Open("/dev/fb0", unix.O_RDWR, 0) + if err != nil { + return nil, err + } + data, err := syscall.Mmap(fd, 0, Width*Height*4, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return nil, err + } + return &fb{ + fd: fd, + data: data, + }, nil +} + +func (fb *fb) Close() error { + if err := syscall.Munmap(fb.data); err != nil { + return err + } + if err := unix.Close(fb.fd); err != nil { + return err + } + return nil +} + +func (fb *fb) ColorModel() color.Model { return color.RGBAModel } // draw.Image +func (fb *fb) Bounds() image.Rectangle { return image.Rect(0, 0, Width, Height) } // draw.Image + +func (fb *fb) At(x, y int) color.Color { // draw.Image + if x < 0 || x >= Width || y < 0 || y >= Height { + return color.RGBA{} + } + off := (y*Width + x) * 4 + bs := fb.data[off : off+4] + return color.RGBA{bs[2], bs[1], bs[0], bs[3]} +} + +func (fb *fb) Set(x, y int, c color.Color) { // draw.Image + rgba := color.RGBAModel.Convert(c).(color.RGBA) + if x < 0 || x >= Width || y < 0 || y >= Height { + return + } + off := (y*Width + x) * 4 + bs := fb.data[off : off+4] + bs[0] = rgba.B + bs[1] = rgba.G + bs[2] = rgba.R + bs[3] = rgba.A +} + +// swizzle swizzles RGBA to BGRA, or vice versa +func swizzle(bs []byte) { + for i := 0; i < len(bs); i += 4 { + bs[i], bs[i+2] = bs[i+2], bs[i] + } +} + +func (fb *fb) ReadRGBA() *image.RGBA { + img := image.NewRGBA(fb.Bounds()) + pix := img.Pix[:Width*Height*4] + copy(pix, fb.data) + swizzle(pix) + return img +} + +func (fb *fb) WriteRGBA(img *image.RGBA) { // destructive: swizzles img's RGBA to BGRA in-place + if fb.Bounds() != img.Bounds() { + panic("img bounds mismatch") + } + pix := img.Pix[:Width*Height*4] + swizzle(pix) + copy(fb.data, pix) +} + +// finfo: { +// ID:[68 73 83 80 51 32 66 71 0 0 0 0 0 0 0 0] +// SMemStart:437256192 +// SMemLen:1536000 +// Type:0 +// TypeAux:0 +// Visual:2 +// XPanStep:1 +// YPanStep:1 +// YWrapStep:1 +// LineLength:3200 +// MMIOStart:0 +// MMIOLen:0 +// Accel:0 +// Capabilities:0 +// Reserved:[0 0] +// } +// vinfo: { +// XRes:800 +// YRes:480 +// XResVirtual:800 +// YResVirtual:480 +// XOffset:0 +// YOffset:0 +// BitsPerPixel:32 +// Grayscale:0 +// R:{Offset:16 Length:8 MSBRight:0} +// G:{Offset:8 Length:8 MSBRight:0} +// B:{Offset:0 Length:8 MSBRight:0} +// A:{Offset:24 Length:8 MSBRight:0} +// Nonstd:0 +// Activate:0 +// Height:4294967295 +// Width:4294967295 +// AccelFlags:0 +// Pixclock:37037 +// LeftMargin:40 +// RightMargin:60 +// UpperMargin:10 +// LowerMargin:10 +// HSyncLen:20 +// VSyncLen:10 +// Sync:1073741824 +// VMode:0 +// Rotate:0 +// Colorspace:0 +// Reserved:[0 0 0 0] +// } + +// // include/uapi/linux/fb.h + +// type FBFInfo struct { +// ID [16]byte +// SMemStart uint32 +// SMemLen uint32 +// Type uint32 +// TypeAux uint32 +// Visual uint32 +// XPanStep uint16 +// YPanStep uint16 +// YWrapStep uint16 +// LineLength uint32 +// MMIOStart uint32 +// MMIOLen uint32 +// Accel uint32 +// Capabilities uint16 +// Reserved [2]uint16 +// } + +// type FBBitfield struct { +// Offset uint32 +// Length uint32 +// MSBRight uint32 +// } + +// type FBVInfo struct { +// XRes uint32 +// YRes uint32 +// XResVirtual uint32 +// YResVirtual uint32 +// XOffset uint32 +// YOffset uint32 + +// BitsPerPixel uint32 +// Grayscale uint32 +// R, G, B, A FBBitfield + +// Nonstd uint32 + +// Activate uint32 + +// Height uint32 +// Width uint32 + +// AccelFlags uint32 + +// Pixclock uint32 +// LeftMargin uint32 +// RightMargin uint32 +// UpperMargin uint32 +// LowerMargin uint32 +// HSyncLen uint32 +// VSyncLen uint32 +// Sync uint32 +// VMode uint32 +// Rotate uint32 +// Colorspace uint32 +// Reserved [4]uint32 +// } + +// func FBGetFInfo(fd int) (finfo FBFInfo, err error) { +// const FBIOGET_FSCREENINFO = 0x4602 +// _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), FBIOGET_FSCREENINFO, uintptr(unsafe.Pointer(&finfo))) +// if errno != 0 { +// err = errno +// } +// return +// } + +// func FBGetVInfo(fd int) (vinfo FBVInfo, err error) { +// const FBIOGET_VSCREENINFO = 0x4600 +// _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), FBIOGET_VSCREENINFO, uintptr(unsafe.Pointer(&vinfo))) +// if errno != 0 { +// err = errno +// } +// return +// } diff --git a/go.mod b/go.mod index 2166508..7e7c8b6 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,10 @@ module pm3.dev -go 1.24.0 +go 1.25.0 -require golang.org/x/sys v0.41.0 // indirect +require ( + golang.org/x/image v0.39.0 + golang.org/x/sys v0.43.0 +) + +require golang.org/x/text v0.36.0 // indirect diff --git a/go.sum b/go.sum index cdc9b1c..b91bbf0 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,6 @@ -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= +golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -- cgit v1.2.3