parent
230a3d3f24
commit
4860070a6b
3
main.go
3
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")
|
||||
)
|
||||
|
@ -75,7 +76,7 @@ func taskFromFlags(stop chan bool, wg *sync.WaitGroup) {
|
|||
Address: *address,
|
||||
MaxConns: *connections,
|
||||
Offset: image.Pt(*x, *y),
|
||||
Shuffle: true,
|
||||
RenderOrder: pixelflut.NewOrder(*order),
|
||||
},
|
||||
Img: img,
|
||||
})
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -22,9 +22,9 @@ type FlutTaskOpts struct {
|
|||
MaxConns int
|
||||
Offset image.Point
|
||||
Paused bool
|
||||
Shuffle 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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
rpc/repl.go
22
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 <n> set number of connections per client
|
||||
addr <host>:<port> set target server
|
||||
c <n> set number of connections per client
|
||||
a <host>:<port> set target server
|
||||
offset <x> <y> set top-left offset
|
||||
offset rand random offset for each draw
|
||||
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>]] 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,
|
||||
|
|
Loading…
Reference in New Issue