diff --git a/main.go b/main.go index df37052..7bc0e56 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ var ( connections = flag.Int("connections", 4, "Number of simultaneous connections. Each connection posts a subimage") x = flag.Int("x", 0, "Offset of posted image from left border") y = flag.Int("y", 0, "Offset of posted image from top border") + order = flag.String("order", "rtl", "Draw order (shuffle, ltr, rtl, ttb, btt)") fetchImgPath = flag.String("fetch", "", "Enable fetching the screen area to the given local file, updating it each second") cpuprofile = flag.String("cpuprofile", "", "Destination file for CPU Profile") ) @@ -72,10 +73,10 @@ func taskFromFlags(stop chan bool, wg *sync.WaitGroup) { r.SetTask(pixelflut.FlutTask{ FlutTaskOpts: pixelflut.FlutTaskOpts{ - Address: *address, - MaxConns: *connections, - Offset: image.Pt(*x, *y), - Shuffle: true, + Address: *address, + MaxConns: *connections, + Offset: image.Pt(*x, *y), + RenderOrder: pixelflut.NewOrder(*order), }, Img: img, }) diff --git a/pixelflut/commands.go b/pixelflut/commands.go index 4130f70..ea86878 100644 --- a/pixelflut/commands.go +++ b/pixelflut/commands.go @@ -38,15 +38,35 @@ func OffsetCmd(x, y int) []byte { } // CommandsFromImage converts an image to the respective pixelflut commands -func commandsFromImage(img *image.NRGBA, offset image.Point) (cmds commands) { +func commandsFromImage(img *image.NRGBA, order RenderOrder, offset image.Point) (cmds commands) { b := img.Bounds() cmds = make([][]byte, b.Size().X*b.Size().Y) numCmds := 0 - for x := b.Min.X; x < b.Max.X; x++ { - for y := b.Min.Y; y < b.Max.Y; y++ { + max1 := b.Max.X + max2 := b.Max.Y + min1 := b.Min.X + min2 := b.Min.Y + dir := 1 + if order.IsVertical() { + max1, max2 = max2, max1 + min1, min2 = min2, min1 + } + if order.IsReverse() { + min1, max1 = max1, min1 + min2, max2 = max2, min2 + dir *= -1 + } + + for i1 := min1; i1 != max1; i1 += dir { + for i2 := min2; i2 != max2; i2 += dir { + x := i1 + y := i2 + if order.IsVertical() { + x, y = y, x + } + c := img.At(x, y).(color.NRGBA) - // ignore transparent pixels if c.A == 0 { continue } @@ -64,7 +84,13 @@ func commandsFromImage(img *image.NRGBA, offset image.Point) (cmds commands) { } } - return cmds[:numCmds] + cmds = cmds[:numCmds] + + if order == Shuffle { + cmds.Shuffle() + } + + return } func cmdsFetchImage(bounds image.Rectangle) (cmds commands) { diff --git a/pixelflut/flut.go b/pixelflut/flut.go index afe3b1c..1d470b3 100644 --- a/pixelflut/flut.go +++ b/pixelflut/flut.go @@ -18,13 +18,13 @@ type FlutTask struct { // FlutTaskOpts specifies parameters of the flut type FlutTaskOpts struct { - Address string - MaxConns int - Offset image.Point - Paused bool - Shuffle bool - RGBSplit bool - RandOffset bool + Address string + MaxConns int + Offset image.Point + Paused bool + RGBSplit bool + RandOffset bool + RenderOrder RenderOrder } // FlutTaskData contains the actual pixeldata to flut, separated because of size @@ -36,8 +36,8 @@ func (t FlutTask) String() string { img = t.Img.Bounds().Size().String() } return fmt.Sprintf( - " %d conns @ %s\n img %v offset %v\n shuffle %v rgbsplit %v randoffset %v paused %v", - t.MaxConns, t.Address, img, t.Offset, t.Shuffle, t.RGBSplit, t.RandOffset, t.Paused, + " %d conns @ %s\n img %v offset %v\n order %s rgbsplit %v randoffset %v paused %v", + t.MaxConns, t.Address, img, t.Offset, t.RenderOrder, t.RGBSplit, t.RandOffset, t.Paused, ) } @@ -46,6 +46,34 @@ func (t FlutTask) IsFlutable() bool { return t.Img != nil && t.MaxConns > 0 && t.Address != "" && !t.Paused } +type RenderOrder uint8 + +func (t RenderOrder) String() string { return []string{"→", "↓", "←", "↑", "random"}[t] } +func (t RenderOrder) IsVertical() bool { return t&0b01 != 0 } +func (t RenderOrder) IsReverse() bool { return t&0b10 != 0 } +func NewOrder(v string) RenderOrder { + switch v { + case "ltr", "l", "→": + return LeftToRight + case "rtl", "r", "←": + return RightToLeft + case "ttb", "t", "↓": + return TopToBottom + case "btt", "b", "↑": + return BottomToTop + default: + return Shuffle + } +} + +const ( + LeftToRight = 0b000 + TopToBottom = 0b001 + RightToLeft = 0b010 + BottomToTop = 0b011 + Shuffle = 0b100 +) + // Flut asynchronously sends the given image to pixelflut server at `address` // using `conns` connections. Pixels are sent column wise, unless `shuffle` // is set. Stops when stop is closed. @@ -55,23 +83,7 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) { return // @robustness: actually return an error here? } - var cmds commands - if t.RGBSplit { - // do a RGB split of white - imgmod := render.ImgColorFilter(t.Img, color.NRGBA{0xff, 0xff, 0xff, 0xff}, color.NRGBA{0xff, 0, 0, 0xff}) - cmds = append(cmds, commandsFromImage(imgmod, image.Pt(t.Offset.X-10, t.Offset.Y-10))...) - imgmod = render.ImgColorFilter(t.Img, color.NRGBA{0xff, 0xff, 0xff, 0xff}, color.NRGBA{0, 0xff, 0, 0xff}) - cmds = append(cmds, commandsFromImage(imgmod, image.Pt(t.Offset.X+10, t.Offset.Y))...) - imgmod = render.ImgColorFilter(t.Img, color.NRGBA{0xff, 0xff, 0xff, 0xff}, color.NRGBA{0, 0, 0xff, 0xff}) - cmds = append(cmds, commandsFromImage(imgmod, image.Pt(t.Offset.X-10, t.Offset.Y+10))...) - cmds = append(cmds, commandsFromImage(t.Img, t.Offset)...) - } else { - cmds = commandsFromImage(t.Img, t.Offset) - } - - if t.Shuffle { - cmds.Shuffle() - } + cmds := generateCommands(t) var messages [][]byte var maxOffsetX, maxOffsetY int @@ -91,7 +103,7 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) { msg = messages[i] } - time.Sleep(66 * time.Millisecond) // avoid crashing the server + time.Sleep(50 * time.Millisecond) // avoid crashing the server go bombAddress(msg, t.Address, maxOffsetX, maxOffsetY, stop, &bombWg) } @@ -100,3 +112,17 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) { wg.Done() } } + +func generateCommands(t FlutTask) (cmds commands) { + if t.RGBSplit { + white := color.NRGBA{0xff, 0xff, 0xff, 0xff} + imgmod := render.ImgColorFilter(t.Img, white, color.NRGBA{0xff, 0, 0, 0xff}) + cmds = append(cmds, commandsFromImage(imgmod, t.RenderOrder, t.Offset.Add(image.Pt(-10, -10)))...) + imgmod = render.ImgColorFilter(t.Img, white, color.NRGBA{0, 0xff, 0, 0xff}) + cmds = append(cmds, commandsFromImage(imgmod, t.RenderOrder, t.Offset.Add(image.Pt(10, 0)))...) + imgmod = render.ImgColorFilter(t.Img, white, color.NRGBA{0, 0, 0xff, 0xff}) + cmds = append(cmds, commandsFromImage(imgmod, t.RenderOrder, t.Offset.Add(image.Pt(-10, 10)))...) + } + cmds = append(cmds, commandsFromImage(t.Img, t.RenderOrder, t.Offset)...) + return +} diff --git a/pixelflut/net.go b/pixelflut/net.go index 11e6bfd..a9974d2 100644 --- a/pixelflut/net.go +++ b/pixelflut/net.go @@ -121,6 +121,7 @@ func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, sto if err == nil { break // we're supposed to exit } + fmt.Printf("[net] error: %s\n", err) } } diff --git a/rpc/repl.go b/rpc/repl.go index 6e93484..89a35b6 100644 --- a/rpc/repl.go +++ b/rpc/repl.go @@ -66,7 +66,7 @@ func RunREPL(f Fluter) { case "start": t.Paused = false - case "offset": + case "offset", "of": if len(args) == 1 && args[0] == "rand" { t.RandOffset = true t.Offset = image.Point{} @@ -79,20 +79,22 @@ func RunREPL(f Fluter) { } } - case "conns": + case "connections", "c": if len(args) == 1 { if conns, err := strconv.Atoi(args[0]); err == nil { t.MaxConns = conns } } - case "addr": + case "address", "a": if len(args) == 1 { t.Address = args[0] } - case "shuffle": - t.Shuffle = !t.Shuffle + case "order", "o": + if len(args) == 1 { + t.RenderOrder = pixelflut.NewOrder(args[0]) + } case "rgbsplit": t.RGBSplit = !t.RGBSplit @@ -118,7 +120,7 @@ func RunREPL(f Fluter) { t.Img = render.RenderText(input, textSize, textCol, bgCol) } - case "img": + case "img", "i": if len(args) > 0 { path := strings.Join(args, " ") if img, err := render.ReadImage(path); err != nil { @@ -151,17 +153,17 @@ func printHelp() { fmt.Println(`available commands: start start fluting stop pause fluting - conns set number of connections per client - addr : set target server + c set number of connections per client + a : set target server offset set top-left offset offset rand random offset for each draw metrics toggle bandwidth reporting (may cost some performance) - img set image + i set image txt send text txt [ [ []] enter interactive text mode rgbsplit toggle RGB split effect - shuffle toggle between column-wise & randomized draw order`) + o set order (l,r,t,b,shuffle)`) } // try to parse as hex-encoded RGB color,