refactor pixelflut package into proper API
This commit is contained in:
parent
7283fac957
commit
3f0acd9694
31
main.go
31
main.go
|
@ -49,38 +49,15 @@ func main() {
|
||||||
|
|
||||||
offset := image.Pt(*image_offsetx, *image_offsety)
|
offset := image.Pt(*image_offsetx, *image_offsety)
|
||||||
img := readImage(*image_path)
|
img := readImage(*image_path)
|
||||||
fetchedImg := image.NewNRGBA(img.Bounds().Add(offset))
|
|
||||||
|
|
||||||
|
var fetchedImg *image.NRGBA
|
||||||
if *fetchImgPath != "" {
|
if *fetchImgPath != "" {
|
||||||
fetchCmds := pixelflut.CmdsFetchImage(fetchedImg.Bounds())
|
fetchedImg = pixelflut.FetchImage(img.Bounds().Add(offset), *address, 1)
|
||||||
fetchMessages := fetchCmds.Chunk(1)
|
|
||||||
|
|
||||||
{
|
|
||||||
// @cleanup: encapsulate this in separate function exported from pixelflut
|
|
||||||
conn, err := net.Dial("tcp", *address)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// defer conn.Close()
|
|
||||||
|
|
||||||
go pixelflut.FetchPixels(fetchedImg, conn)
|
|
||||||
go pixelflut.Bomb2(fetchMessages[0], conn)
|
|
||||||
}
|
|
||||||
*connections -= 1
|
*connections -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate and split messages into equal chunks
|
// 🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊
|
||||||
commands := pixelflut.CommandsFromImage(img, offset)
|
pixelflut.Flut(img, offset, *shuffle, *address, *connections)
|
||||||
if *shuffle {
|
|
||||||
commands.Shuffle()
|
|
||||||
}
|
|
||||||
|
|
||||||
if *connections > 0 {
|
|
||||||
commandGroups := commands.Chunk(*connections)
|
|
||||||
for _, messages := range commandGroups {
|
|
||||||
go pixelflut.Bomb(messages, *address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate after timeout to save resources
|
// Terminate after timeout to save resources
|
||||||
timer, err := time.ParseDuration(*runtime)
|
timer, err := time.ParseDuration(*runtime)
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
package pixelflut
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/textproto"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flut asynchronously sends the given image to pixelflut server at `address`
|
||||||
|
// using `conns` connections. Pixels are sent row wise, unless `shuffle` is set.
|
||||||
|
func Flut(img image.Image, position image.Point, shuffle bool, address string, conns int) {
|
||||||
|
cmds := commandsFromImage(img, position)
|
||||||
|
if shuffle {
|
||||||
|
cmds.Shuffle()
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := cmds.Chunk(conns)
|
||||||
|
for _, msg := range messages {
|
||||||
|
go bombAddress(msg, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchImage asynchronously uses `conns` to fetch pixels within `bounds` from
|
||||||
|
// a pixelflut server at `address`, and writes them into the returned Image.
|
||||||
|
func FetchImage(bounds image.Rectangle, address string, conns int) (img *image.NRGBA) {
|
||||||
|
img = image.NewNRGBA(bounds)
|
||||||
|
cmds := cmdsFetchImage(bounds).Chunk(conns)
|
||||||
|
|
||||||
|
for i := 0; i < conns; i++ {
|
||||||
|
conn, err := net.Dial("tcp", address)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @cleanup: parsePixels calls conn.Close(), as deferring it here would
|
||||||
|
// instantly close it
|
||||||
|
go parsePixels(img, conn)
|
||||||
|
go bombConn(cmds[i], conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePixels(target *image.NRGBA, conn net.Conn) {
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
tp := textproto.NewReader(reader)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// @speed: textproto seems not the fastest, buffer text manually & split at \n ?
|
||||||
|
res, err := tp.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @speed: Split is ridiculously slow due to mallocs!
|
||||||
|
// chunk last 6 chars off -> color, remove first 3 chars, find space in
|
||||||
|
// remainder, then Atoi() xy?
|
||||||
|
res2 := strings.Split(res, " ")
|
||||||
|
x, _ := strconv.Atoi(res2[1])
|
||||||
|
y, _ := strconv.Atoi(res2[2])
|
||||||
|
col, _ := hex.DecodeString(res2[3])
|
||||||
|
|
||||||
|
target.Set(x, y, color.NRGBA{
|
||||||
|
uint8(col[0]),
|
||||||
|
uint8(col[1]),
|
||||||
|
uint8(col[2]),
|
||||||
|
255,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,18 @@
|
||||||
package pixelflut
|
package pixelflut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Commands represent a list of messages to be sent to a pixelflut server.
|
// Commands represent a list of messages to be sent to a pixelflut server.
|
||||||
type Commands [][]byte
|
type commands [][]byte
|
||||||
|
|
||||||
// Chunk splits commands into equally sized chunks, while flattening each chunk
|
// Chunk splits commands into equally sized chunks, while flattening each chunk
|
||||||
// so that all commands are concatenated as a single `[]byte`.
|
// so that all commands are concatenated as a single `[]byte`.
|
||||||
func (c Commands) Chunk(numChunks int) [][]byte {
|
func (c commands) Chunk(numChunks int) [][]byte {
|
||||||
chunks := make([][]byte, numChunks)
|
chunks := make([][]byte, numChunks)
|
||||||
chunkLength := len(c) / numChunks
|
chunkLength := len(c) / numChunks
|
||||||
for i := 0; i < numChunks; i++ {
|
for i := 0; i < numChunks; i++ {
|
||||||
|
@ -22,9 +25,49 @@ func (c Commands) Chunk(numChunks int) [][]byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shuffle reorders commands randomly, in place.
|
// Shuffle reorders commands randomly, in place.
|
||||||
func (c Commands) Shuffle() {
|
func (c commands) Shuffle() {
|
||||||
for i := range c {
|
for i := range c {
|
||||||
j := rand.Intn(i + 1)
|
j := rand.Intn(i + 1)
|
||||||
c[i], c[j] = c[j], c[i]
|
c[i], c[j] = c[j], c[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommandsFromImage converts an image to the respective pixelflut commands
|
||||||
|
func commandsFromImage(img image.Image, 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++ {
|
||||||
|
// ensure we're working with RGBA colors (non-alpha-pre-multiplied)
|
||||||
|
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
||||||
|
|
||||||
|
// ignore transparent pixels
|
||||||
|
if c.A == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// @incomplete: also send alpha? -> bandwidth tradeoff
|
||||||
|
// @speed: this sprintf call is quite slow..
|
||||||
|
cmd := fmt.Sprintf("PX %d %d %.2x%.2x%.2x\n",
|
||||||
|
x+offset.X, y+offset.Y, c.R, c.G, c.B)
|
||||||
|
cmds[numCmds] = []byte(cmd)
|
||||||
|
numCmds++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmds[:numCmds]
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdsFetchImage(bounds image.Rectangle) (cmds commands) {
|
||||||
|
cmds = make([][]byte, bounds.Size().X*bounds.Size().Y)
|
||||||
|
numCmds := 0
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
|
cmd := fmt.Sprintf("PX %d %d\n", x, y)
|
||||||
|
cmds[numCmds] = []byte(cmd)
|
||||||
|
numCmds++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package pixelflut
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CmdsFetchImage(bounds image.Rectangle) (cmds Commands) {
|
|
||||||
cmds = make([][]byte, bounds.Size().X*bounds.Size().Y)
|
|
||||||
numCmds := 0
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
cmd := fmt.Sprintf("PX %d %d\n", x, y)
|
|
||||||
cmds[numCmds] = []byte(cmd)
|
|
||||||
numCmds++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cmds
|
|
||||||
}
|
|
|
@ -1,34 +1,26 @@
|
||||||
package pixelflut
|
package pixelflut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/hex"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/textproto"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// @speed: add some performance reporting mechanism on these functions when
|
// @speed: add some performance reporting mechanism on these functions when
|
||||||
// called as goroutines
|
// called as goroutines
|
||||||
|
|
||||||
// Bomb writes the given message via plain TCP to the given address,
|
// bombAddress writes the given message via plain TCP to the given address,
|
||||||
// forever, as fast as possible.
|
// forever, as fast as possible.
|
||||||
func Bomb(message []byte, address string) {
|
func bombAddress(message []byte, address string) {
|
||||||
conn, err := net.Dial("tcp", address)
|
conn, err := net.Dial("tcp", address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
Bomb2(message, conn)
|
bombConn(message, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @cleanup: find common interface instead of Bomb2
|
func bombConn(message []byte, conn net.Conn) {
|
||||||
func Bomb2(message []byte, conn net.Conn) {
|
|
||||||
for {
|
for {
|
||||||
_, err := conn.Write(message)
|
_, err := conn.Write(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -36,31 +28,3 @@ func Bomb2(message []byte, conn net.Conn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchPixels(target *image.NRGBA, conn net.Conn) {
|
|
||||||
reader := bufio.NewReader(conn)
|
|
||||||
tp := textproto.NewReader(reader)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// @speed: textproto seems not the fastest, buffer text manually & split at \n ?
|
|
||||||
res, err := tp.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @speed: Split is ridiculously slow due to mallocs!
|
|
||||||
// chunk last 6 chars off -> color, remove first 3 chars, find space in
|
|
||||||
// remainder, then Atoi() xy?
|
|
||||||
res2 := strings.Split(res, " ")
|
|
||||||
x, _ := strconv.Atoi(res2[1])
|
|
||||||
y, _ := strconv.Atoi(res2[2])
|
|
||||||
col, _ := hex.DecodeString(res2[3])
|
|
||||||
|
|
||||||
target.Set(x, y, color.NRGBA{
|
|
||||||
uint8(col[0]),
|
|
||||||
uint8(col[1]),
|
|
||||||
uint8(col[2]),
|
|
||||||
255,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package pixelflut
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommandsFromImage converts an image to the respective pixelflut commands
|
|
||||||
func CommandsFromImage(img image.Image, offset image.Point) (commands Commands) {
|
|
||||||
b := img.Bounds()
|
|
||||||
commands = 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++ {
|
|
||||||
// ensure we're working with RGBA colors (non-alpha-pre-multiplied)
|
|
||||||
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
|
|
||||||
|
|
||||||
// ignore transparent pixels
|
|
||||||
if c.A == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// @incomplete: also send alpha? -> bandwidth tradeoff
|
|
||||||
// @speed: this sprintf call is quite slow..
|
|
||||||
cmd := fmt.Sprintf("PX %d %d %.2x%.2x%.2x\n",
|
|
||||||
x+offset.X, y+offset.Y, c.R, c.G, c.B)
|
|
||||||
commands[numCmds] = []byte(cmd)
|
|
||||||
numCmds++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commands[:numCmds]
|
|
||||||
}
|
|
Loading…
Reference in New Issue