commit c240d1ed40edd491467e66f61b5803d9ada683e9 Author: Alex Denes Date: Sun Mar 12 13:43:32 2023 +0000 Initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b8d8c9c --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.redxen.eu/caskd/v6canvas + +go 1.20 + +require golang.org/x/net v0.8.0 + +require golang.org/x/sys v0.6.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..755e96f --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ebaea89 --- /dev/null +++ b/main.go @@ -0,0 +1,154 @@ +package main + +import ( + "flag" + "fmt" + "image" + "image/color" + _ "image/png" + "os" + "sync" + "time" + + "log" + "net" + + "golang.org/x/net/icmp" + "golang.org/x/net/ipv6" +) + +func main() { + var ( + dt uint + err error + ) + flag.UintVar(&dt, "timeout", 5000, "Frame display time (ms)") + flag.Parse() + + var ( + up = make(chan image.Image) + lo = make(chan image.Image) + dx = make(chan pixdelta) + ) + + c, err := icmp.ListenPacket("udp6", "::") + if err != nil { + log.Fatalln("Failed to listen for ICMP packets:", err) + } + go sendDelta(dx, c) + go syncColors(up, lo, dx) + + var imgs [2]image.Image + for i := range imgs { + imgs[i] = image.NewRGBA(image.Rect(0, 0, 511, 511)) + } + for _, v := range flag.Args() { + f, err := os.Open(v) + if err != nil { + log.Fatalln("Failed to open image:", err) + } + imgs[0] = imgs[1] + imgs[1], _, err = image.Decode(f) + if err != nil { + log.Fatalln("Failed to decode image:", err) + } + up <- imgs[0] + lo <- imgs[1] + time.Sleep(time.Duration(dt) * time.Millisecond) + } +} + +func sendDelta(d chan pixdelta, c *icmp.PacketConn) { + pm := icmp.Message{ + Type: ipv6.ICMPTypeEchoRequest, Code: 0, + Body: &icmp.Echo{Seq: 1}, + } + + bb, err := pm.Marshal(nil) + if err != nil { + log.Fatalln("Failed to encode ICMP packet") + } + + for { + select { + case delta := <-d: + dest := &net.UDPAddr{ + IP: convV6(delta.p, delta.c), + } + + if _, err := c.WriteTo(bb, dest); err != nil { + log.Println("Failed to send packet to:", dest) + } + } + } + +} +func syncColors(upstream, local chan image.Image, dx chan pixdelta) { + var ( + oimg, nimg image.Image + ) + + oimg = nil + nimg = nil + for { + select { + case nimg = <-local: + break + case oimg = <-upstream: + break + } + if oimg == nil || nimg == nil { + continue + } + + jobs := uint64(0) + + wg := new(sync.WaitGroup) + b := nimg.Bounds() + for x := 0; x < b.Dx(); x++ { + for y := 0; y < b.Dy(); y++ { + wg.Add(1) + go comparePixel(oimg, nimg, image.Point{X: x, Y: y}, dx, wg) + jobs++ + } + } + wg.Wait() + } +} + +// Framebuffer + +type pixdelta struct { + p image.Point + c color.Color +} + +func comparePixel(a, b image.Image, loc image.Point, r chan pixdelta, wg *sync.WaitGroup) { + defer wg.Done() + car, cab, cag, _ := a.At(loc.X, loc.Y).RGBA() + cbr, cbb, cbg, cba := b.At(loc.X, loc.Y).RGBA() + if (cba == 0xFFFF) && !(car == cbr && cag == cbg && cab == cbb) { + r <- pixdelta{c: b.At(loc.X, loc.Y), p: loc} + } +} + +// Network + +// fdcf:8538:9ad5:3333:XXXX:YYYY:11RR:GGBB +func convV6(p image.Point, c color.Color) (addr net.IP) { + addr = net.IP{ + 0xfd, 0xcf, 0x85, 0x38, 0x9a, 0xd5, 0x33, 0x33, + } + addr = append(addr, split2Bytes(uint16(p.X))...) + addr = append(addr, split2Bytes(uint16(p.Y))...) + r, g, b, _ := c.RGBA() + addr = append(addr, 0x11, uint8(r), uint8(g), uint8(b)) + return +} + +func split2Bytes(s uint16) []uint8 { + return []uint8{ + uint8((s & (0xFF << 8)) >> 8), + uint8(s & 0xFF), + } +}