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)
|
||||
img := readImage(*image_path)
|
||||
fetchedImg := image.NewNRGBA(img.Bounds().Add(offset))
|
||||
|
||||
var fetchedImg *image.NRGBA
|
||||
if *fetchImgPath != "" {
|
||||
fetchCmds := pixelflut.CmdsFetchImage(fetchedImg.Bounds())
|
||||
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)
|
||||
}
|
||||
fetchedImg = pixelflut.FetchImage(img.Bounds().Add(offset), *address, 1)
|
||||
*connections -= 1
|
||||
}
|
||||
|
||||
// Generate and split messages into equal chunks
|
||||
commands := pixelflut.CommandsFromImage(img, offset)
|
||||
if *shuffle {
|
||||
commands.Shuffle()
|
||||
}
|
||||
|
||||
if *connections > 0 {
|
||||
commandGroups := commands.Chunk(*connections)
|
||||
for _, messages := range commandGroups {
|
||||
go pixelflut.Bomb(messages, *address)
|
||||
}
|
||||
}
|
||||
// 🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊🌊
|
||||
pixelflut.Flut(img, offset, *shuffle, *address, *connections)
|
||||
|
||||
// Terminate after timeout to save resources
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
chunkLength := len(c) / numChunks
|
||||
for i := 0; i < numChunks; i++ {
|
||||
|
@ -22,9 +25,49 @@ func (c Commands) Chunk(numChunks int) [][]byte {
|
|||
}
|
||||
|
||||
// Shuffle reorders commands randomly, in place.
|
||||
func (c Commands) Shuffle() {
|
||||
func (c commands) Shuffle() {
|
||||
for i := range c {
|
||||
j := rand.Intn(i + 1)
|
||||
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
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"net"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// @speed: add some performance reporting mechanism on these functions when
|
||||
// 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.
|
||||
func Bomb(message []byte, address string) {
|
||||
func bombAddress(message []byte, address string) {
|
||||
conn, err := net.Dial("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
Bomb2(message, conn)
|
||||
bombConn(message, conn)
|
||||
}
|
||||
|
||||
// @cleanup: find common interface instead of Bomb2
|
||||
func Bomb2(message []byte, conn net.Conn) {
|
||||
func bombConn(message []byte, conn net.Conn) {
|
||||
for {
|
||||
_, err := conn.Write(message)
|
||||
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