parent
230a3d3f24
commit
4860070a6b
9
main.go
9
main.go
|
@ -25,6 +25,7 @@ var (
|
||||||
connections = flag.Int("connections", 4, "Number of simultaneous connections. Each connection posts a subimage")
|
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")
|
x = flag.Int("x", 0, "Offset of posted image from left border")
|
||||||
y = flag.Int("y", 0, "Offset of posted image from top 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")
|
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")
|
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{
|
r.SetTask(pixelflut.FlutTask{
|
||||||
FlutTaskOpts: pixelflut.FlutTaskOpts{
|
FlutTaskOpts: pixelflut.FlutTaskOpts{
|
||||||
Address: *address,
|
Address: *address,
|
||||||
MaxConns: *connections,
|
MaxConns: *connections,
|
||||||
Offset: image.Pt(*x, *y),
|
Offset: image.Pt(*x, *y),
|
||||||
Shuffle: true,
|
RenderOrder: pixelflut.NewOrder(*order),
|
||||||
},
|
},
|
||||||
Img: img,
|
Img: img,
|
||||||
})
|
})
|
||||||
|
|
|
@ -38,15 +38,35 @@ func OffsetCmd(x, y int) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandsFromImage converts an image to the respective pixelflut commands
|
// 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()
|
b := img.Bounds()
|
||||||
cmds = make([][]byte, b.Size().X*b.Size().Y)
|
cmds = make([][]byte, b.Size().X*b.Size().Y)
|
||||||
numCmds := 0
|
numCmds := 0
|
||||||
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
max1 := b.Max.X
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
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)
|
c := img.At(x, y).(color.NRGBA)
|
||||||
// ignore transparent pixels
|
|
||||||
if c.A == 0 {
|
if c.A == 0 {
|
||||||
continue
|
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) {
|
func cmdsFetchImage(bounds image.Rectangle) (cmds commands) {
|
||||||
|
|
|
@ -18,13 +18,13 @@ type FlutTask struct {
|
||||||
|
|
||||||
// FlutTaskOpts specifies parameters of the flut
|
// FlutTaskOpts specifies parameters of the flut
|
||||||
type FlutTaskOpts struct {
|
type FlutTaskOpts struct {
|
||||||
Address string
|
Address string
|
||||||
MaxConns int
|
MaxConns int
|
||||||
Offset image.Point
|
Offset image.Point
|
||||||
Paused bool
|
Paused bool
|
||||||
Shuffle bool
|
RGBSplit bool
|
||||||
RGBSplit bool
|
RandOffset bool
|
||||||
RandOffset bool
|
RenderOrder RenderOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlutTaskData contains the actual pixeldata to flut, separated because of size
|
// 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()
|
img = t.Img.Bounds().Size().String()
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
" %d conns @ %s\n img %v offset %v\n shuffle %v rgbsplit %v randoffset %v paused %v",
|
" %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.Shuffle, t.RGBSplit, t.RandOffset, t.Paused,
|
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
|
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`
|
// Flut asynchronously sends the given image to pixelflut server at `address`
|
||||||
// using `conns` connections. Pixels are sent column wise, unless `shuffle`
|
// using `conns` connections. Pixels are sent column wise, unless `shuffle`
|
||||||
// is set. Stops when stop is closed.
|
// 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?
|
return // @robustness: actually return an error here?
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmds commands
|
cmds := generateCommands(t)
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
var messages [][]byte
|
var messages [][]byte
|
||||||
var maxOffsetX, maxOffsetY int
|
var maxOffsetX, maxOffsetY int
|
||||||
|
@ -91,7 +103,7 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) {
|
||||||
msg = messages[i]
|
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)
|
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()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, sto
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break // we're supposed to exit
|
break // we're supposed to exit
|
||||||
}
|
}
|
||||||
|
fmt.Printf("[net] error: %s\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
22
rpc/repl.go
22
rpc/repl.go
|
@ -66,7 +66,7 @@ func RunREPL(f Fluter) {
|
||||||
case "start":
|
case "start":
|
||||||
t.Paused = false
|
t.Paused = false
|
||||||
|
|
||||||
case "offset":
|
case "offset", "of":
|
||||||
if len(args) == 1 && args[0] == "rand" {
|
if len(args) == 1 && args[0] == "rand" {
|
||||||
t.RandOffset = true
|
t.RandOffset = true
|
||||||
t.Offset = image.Point{}
|
t.Offset = image.Point{}
|
||||||
|
@ -79,20 +79,22 @@ func RunREPL(f Fluter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "conns":
|
case "connections", "c":
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
if conns, err := strconv.Atoi(args[0]); err == nil {
|
if conns, err := strconv.Atoi(args[0]); err == nil {
|
||||||
t.MaxConns = conns
|
t.MaxConns = conns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case "addr":
|
case "address", "a":
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
t.Address = args[0]
|
t.Address = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
case "shuffle":
|
case "order", "o":
|
||||||
t.Shuffle = !t.Shuffle
|
if len(args) == 1 {
|
||||||
|
t.RenderOrder = pixelflut.NewOrder(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
case "rgbsplit":
|
case "rgbsplit":
|
||||||
t.RGBSplit = !t.RGBSplit
|
t.RGBSplit = !t.RGBSplit
|
||||||
|
@ -118,7 +120,7 @@ func RunREPL(f Fluter) {
|
||||||
t.Img = render.RenderText(input, textSize, textCol, bgCol)
|
t.Img = render.RenderText(input, textSize, textCol, bgCol)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "img":
|
case "img", "i":
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
path := strings.Join(args, " ")
|
path := strings.Join(args, " ")
|
||||||
if img, err := render.ReadImage(path); err != nil {
|
if img, err := render.ReadImage(path); err != nil {
|
||||||
|
@ -151,17 +153,17 @@ func printHelp() {
|
||||||
fmt.Println(`available commands:
|
fmt.Println(`available commands:
|
||||||
start start fluting
|
start start fluting
|
||||||
stop pause fluting
|
stop pause fluting
|
||||||
conns <n> set number of connections per client
|
c <n> set number of connections per client
|
||||||
addr <host>:<port> set target server
|
a <host>:<port> set target server
|
||||||
offset <x> <y> set top-left offset
|
offset <x> <y> set top-left offset
|
||||||
offset rand random offset for each draw
|
offset rand random offset for each draw
|
||||||
metrics toggle bandwidth reporting (may cost some performance)
|
metrics toggle bandwidth reporting (may cost some performance)
|
||||||
|
|
||||||
img <filepath> set image
|
i <filepath> set image
|
||||||
txt <scale> <color <bgcolor> <txt> send text
|
txt <scale> <color <bgcolor> <txt> send text
|
||||||
txt [<scale> [<color> [<bgcolor>]] enter interactive text mode
|
txt [<scale> [<color> [<bgcolor>]] enter interactive text mode
|
||||||
rgbsplit toggle RGB split effect
|
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,
|
// try to parse as hex-encoded RGB color,
|
||||||
|
|
Loading…
Reference in New Issue