new offset implementation
allows specifying a mask image for the random offset. Offsetter interface could also be used for a random walk / DVD-bounce incremental offset.
This commit is contained in:
parent
4e0cc35c26
commit
8d6aa732d7
2
main.go
2
main.go
|
@ -75,7 +75,7 @@ func taskFromFlags(stop chan bool, wg *sync.WaitGroup) {
|
||||||
FlutTaskOpts: pixelflut.FlutTaskOpts{
|
FlutTaskOpts: pixelflut.FlutTaskOpts{
|
||||||
Address: *address,
|
Address: *address,
|
||||||
MaxConns: *connections,
|
MaxConns: *connections,
|
||||||
Offset: image.Pt(*x, *y),
|
Offset: pixelflut.RandOffsetter{Point: image.Pt(*x, *y)},
|
||||||
RenderOrder: pixelflut.NewOrder(*order),
|
RenderOrder: pixelflut.NewOrder(*order),
|
||||||
},
|
},
|
||||||
Img: img,
|
Img: img,
|
||||||
|
|
|
@ -45,7 +45,7 @@ func FetchImage(bounds *image.Rectangle, address string, conns int, stop chan bo
|
||||||
}
|
}
|
||||||
|
|
||||||
go readPixels(img, conn, stop)
|
go readPixels(img, conn, stop)
|
||||||
go bombConn(cmds[i], 0, 0, conn, stop)
|
go bombConn(cmds[i], &RandOffsetter{}, conn, stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
return img
|
return img
|
||||||
|
|
|
@ -39,7 +39,7 @@ 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, order RenderOrder, offset image.Point) (cmds commands) {
|
func commandsFromImage(img *image.NRGBA, order RenderOrder, offset RandOffsetter) (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
|
||||||
|
@ -74,9 +74,9 @@ func commandsFromImage(img *image.NRGBA, order RenderOrder, offset image.Point)
|
||||||
|
|
||||||
var cmd []byte
|
var cmd []byte
|
||||||
cmd = append(cmd, []byte("PX ")...)
|
cmd = append(cmd, []byte("PX ")...)
|
||||||
cmd = strconv.AppendUint(cmd, uint64(x+offset.X), 10)
|
cmd = strconv.AppendUint(cmd, uint64(x), 10)
|
||||||
cmd = append(cmd, ' ')
|
cmd = append(cmd, ' ')
|
||||||
cmd = strconv.AppendUint(cmd, uint64(y+offset.Y), 10)
|
cmd = strconv.AppendUint(cmd, uint64(y), 10)
|
||||||
cmd = append(cmd, ' ')
|
cmd = append(cmd, ' ')
|
||||||
appendColor(&cmd, c)
|
appendColor(&cmd, c)
|
||||||
cmd = append(cmd, '\n')
|
cmd = append(cmd, '\n')
|
||||||
|
|
|
@ -20,11 +20,10 @@ type FlutTask struct {
|
||||||
type FlutTaskOpts struct {
|
type FlutTaskOpts struct {
|
||||||
Address string
|
Address string
|
||||||
MaxConns int
|
MaxConns int
|
||||||
Offset image.Point
|
|
||||||
Paused bool
|
Paused bool
|
||||||
RGBSplit bool // @cleanup: replace with `FX: []Effect`
|
RGBSplit bool // @cleanup: replace with `FX: []Effect`
|
||||||
RandOffset bool
|
|
||||||
RenderOrder RenderOrder
|
RenderOrder RenderOrder
|
||||||
|
Offset RandOffsetter
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlutTaskData contains the actual pixeldata to flut, separated because of size
|
// FlutTaskData contains the actual pixeldata to flut, separated because of size
|
||||||
|
@ -36,8 +35,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 order %s rgbsplit %v randoffset %v paused %v",
|
" %d conns @ %s\n img %v offset %v\n order %s rgbsplit %v paused %v",
|
||||||
t.MaxConns, t.Address, img, t.Offset, t.RenderOrder, t.RGBSplit, t.RandOffset, t.Paused,
|
t.MaxConns, t.Address, img, t.Offset, t.RenderOrder, t.RGBSplit, t.Paused,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +83,11 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
var messages [][]byte
|
var messages [][]byte
|
||||||
var maxOffsetX, maxOffsetY int
|
var maxOffsetX, maxOffsetY int
|
||||||
if t.RandOffset {
|
if t.Offset.Random {
|
||||||
maxX, maxY := CanvasSize(t.Address)
|
maxX, maxY := CanvasSize(t.Address)
|
||||||
maxOffsetX = maxX - t.Img.Bounds().Canon().Dx()
|
maxOffsetX = maxX - t.Img.Bounds().Canon().Dx()
|
||||||
maxOffsetY = maxY - t.Img.Bounds().Canon().Dy()
|
maxOffsetY = maxY - t.Img.Bounds().Canon().Dy()
|
||||||
|
t.Offset.SetMaximumOffset(image.Pt(maxOffsetX, maxOffsetY))
|
||||||
messages = cmds.Chunk(1) // each connection should send the full img
|
messages = cmds.Chunk(1) // each connection should send the full img
|
||||||
} else {
|
} else {
|
||||||
messages = cmds.Chunk(t.MaxConns)
|
messages = cmds.Chunk(t.MaxConns)
|
||||||
|
@ -102,7 +102,7 @@ func Flut(t FlutTask, stop chan bool, wg *sync.WaitGroup) {
|
||||||
|
|
||||||
time.Sleep(50 * 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, &t.Offset, stop, &bombWg)
|
||||||
}
|
}
|
||||||
bombWg.Wait()
|
bombWg.Wait()
|
||||||
if wg != nil {
|
if wg != nil {
|
||||||
|
@ -114,6 +114,7 @@ func generateCommands(t FlutTask) (cmds commands) {
|
||||||
if t.RGBSplit {
|
if t.RGBSplit {
|
||||||
white := color.NRGBA{0xff, 0xff, 0xff, 0xff}
|
white := color.NRGBA{0xff, 0xff, 0xff, 0xff}
|
||||||
imgmod := render.ImgColorFilter(t.Img, white, color.NRGBA{0xff, 0, 0, 0xff})
|
imgmod := render.ImgColorFilter(t.Img, white, color.NRGBA{0xff, 0, 0, 0xff})
|
||||||
|
// FIXME: this offset is ignored with the latest changes, restore this behaviour.
|
||||||
cmds = append(cmds, commandsFromImage(imgmod, t.RenderOrder, t.Offset.Add(image.Pt(-10, -10)))...)
|
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})
|
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)))...)
|
cmds = append(cmds, commandsFromImage(imgmod, t.RenderOrder, t.Offset.Add(image.Pt(10, 0)))...)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package pixelflut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -13,6 +14,71 @@ const (
|
||||||
timeoutMax = 10 * time.Second
|
timeoutMax = 10 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Offsetter interface {
|
||||||
|
Next() (x, y int)
|
||||||
|
SetMaximumOffset(max image.Point)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RandOffsetter struct {
|
||||||
|
image.Point
|
||||||
|
Mask *image.NRGBA
|
||||||
|
Random bool
|
||||||
|
Max image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RandOffsetter) String() string {
|
||||||
|
mask := "nil"
|
||||||
|
if r.Mask != nil {
|
||||||
|
mask = r.Mask.Bounds().Size().String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[%v + random %v, mask %v]", r.Point, r.Random, mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// override image.Point interface
|
||||||
|
func (r RandOffsetter) Add(p image.Point) RandOffsetter {
|
||||||
|
r.Point = r.Point.Add(p)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RandOffsetter) SetMaximumOffset(max image.Point) {
|
||||||
|
if r.Mask != nil {
|
||||||
|
mask := r.Mask.Bounds().Canon().Max.Sub(r.Point)
|
||||||
|
r.Max.X = clamp(mask.X, 1, max.X)
|
||||||
|
r.Max.Y = clamp(mask.Y, 1, max.Y)
|
||||||
|
} else {
|
||||||
|
r.Max.X = clamp(max.X, 1, max.X)
|
||||||
|
r.Max.Y = clamp(max.Y, 1, max.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clamp(val, min, max int) int {
|
||||||
|
if val < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if val > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RandOffsetter) Next() (x, y int) {
|
||||||
|
if r.Random {
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
x := rand.Intn(r.Max.X)
|
||||||
|
y := rand.Intn(r.Max.Y)
|
||||||
|
if r.Mask != nil {
|
||||||
|
if _, _, _, a := r.Mask.At(x, y).RGBA(); a != 0 {
|
||||||
|
return r.X + x, r.Y + y
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return r.X + x, r.Y + y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.X, r.Y
|
||||||
|
}
|
||||||
|
|
||||||
// Performance contains pixelflut metrics
|
// Performance contains pixelflut metrics
|
||||||
type Performance struct {
|
type Performance struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
@ -94,7 +160,7 @@ func initPerfReporter() *Performance {
|
||||||
|
|
||||||
// bombAddress opens a TCP connection to `address`, and writes `message` repeatedly, until `stop` is closed.
|
// bombAddress opens a TCP connection to `address`, and writes `message` repeatedly, until `stop` is closed.
|
||||||
// It retries with exponential backoff on network errors.
|
// It retries with exponential backoff on network errors.
|
||||||
func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, stop chan bool, wg *sync.WaitGroup) {
|
func bombAddress(message []byte, address string, offsetter Offsetter, stop chan bool, wg *sync.WaitGroup) {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
|
@ -115,7 +181,7 @@ func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, sto
|
||||||
|
|
||||||
fmt.Printf("[net] bombing %s with new connection\n", address)
|
fmt.Printf("[net] bombing %s with new connection\n", address)
|
||||||
|
|
||||||
err = bombConn(message, maxOffsetX, maxOffsetY, conn, stop)
|
err = bombConn(message, offsetter, conn, stop)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
timeout = timeoutMin
|
timeout = timeoutMin
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -127,25 +193,25 @@ func bombAddress(message []byte, address string, maxOffsetX, maxOffsetY int, sto
|
||||||
|
|
||||||
// bombConn writes the given message to the given connection in a tight loop, until `stop` is closed.
|
// bombConn writes the given message to the given connection in a tight loop, until `stop` is closed.
|
||||||
// Does no transformation on the given message, so make sure packet splitting / nagle works.
|
// Does no transformation on the given message, so make sure packet splitting / nagle works.
|
||||||
func bombConn(message []byte, maxOffsetX, maxOffsetY int, conn net.Conn, stop chan bool) error {
|
func bombConn(message []byte, offsetter Offsetter, conn net.Conn, stop chan bool) error {
|
||||||
PerformanceReporter.connsReporter <- 1
|
PerformanceReporter.connsReporter <- 1
|
||||||
defer func() { PerformanceReporter.connsReporter <- -1 }()
|
defer func() { PerformanceReporter.connsReporter <- -1 }()
|
||||||
|
|
||||||
var msg = make([]byte, len(message)+16) // leave some space for offset cmd
|
var msg = make([]byte, len(message)+16) // leave some space for offset cmd
|
||||||
msg = message
|
msg = message
|
||||||
randOffset := maxOffsetX > 0 && maxOffsetY > 0
|
// randOffset := maxOffsetX > 0 && maxOffsetY > 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
if randOffset {
|
// if randOffset {
|
||||||
msg = append(
|
msg = append(
|
||||||
OffsetCmd(rand.Intn(maxOffsetX), rand.Intn(maxOffsetY)),
|
OffsetCmd(offsetter.Next()),
|
||||||
message...,
|
message...,
|
||||||
)
|
)
|
||||||
}
|
// }
|
||||||
b, err := conn.Write(msg)
|
b, err := conn.Write(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
20
rpc/repl.go
20
rpc/repl.go
|
@ -67,15 +67,25 @@ func RunREPL(f Fluter) {
|
||||||
t.Paused = false
|
t.Paused = false
|
||||||
|
|
||||||
case "offset", "of":
|
case "offset", "of":
|
||||||
if len(args) == 1 && args[0] == "rand" {
|
if len(args) >= 1 && args[0][0] == 'r' {
|
||||||
t.RandOffset = true
|
t.Offset.Random = true
|
||||||
t.Offset = image.Point{}
|
t.Offset.Mask = nil
|
||||||
|
if len(args) >= 2 {
|
||||||
|
fmt.Println(strings.Join(args[1:], " "))
|
||||||
|
mask, err := render.ReadImage(strings.Join(args[1:], " "))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("couldn't read mask image: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Offset.Mask = mask
|
||||||
|
}
|
||||||
} else if len(args) == 2 {
|
} else if len(args) == 2 {
|
||||||
t.RandOffset = false
|
t.Offset.Random = false
|
||||||
|
t.Offset.Mask = nil
|
||||||
x, err := strconv.Atoi(args[0])
|
x, err := strconv.Atoi(args[0])
|
||||||
y, err2 := strconv.Atoi(args[1])
|
y, err2 := strconv.Atoi(args[1])
|
||||||
if err == nil && err2 == nil {
|
if err == nil && err2 == nil {
|
||||||
t.Offset = image.Pt(x, y)
|
t.Offset.Point = image.Pt(x, y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue