diff --git a/io.go b/io.go new file mode 100644 index 0000000..d4fa70c --- /dev/null +++ b/io.go @@ -0,0 +1,33 @@ +package main + +import ( + "image" + _ "image/gif" + _ "image/jpeg" + "image/png" + "log" + "os" +) + +func readImage(path string) (img image.Image) { + reader, err := os.Open(path) + if err != nil { + log.Fatal(err) + } + img, _, err2 := image.Decode(reader) + if err2 != nil { + log.Fatal(err2) + } + return img +} + +func writeImage(path string, img image.Image) { + f, err := os.Create(path) + if err != nil { + log.Fatal(err) + } + if err := png.Encode(f, img); err != nil { + f.Close() + log.Fatal(err) + } +} diff --git a/main.go b/main.go index 92e984a..934629e 100644 --- a/main.go +++ b/main.go @@ -1,23 +1,15 @@ package main import ( - "bufio" - "encoding/hex" "flag" - "fmt" "image" - "image/color" - _ "image/gif" - _ "image/jpeg" - "image/png" "log" - "math/rand" "net" - "net/textproto" "os" "runtime/pprof" - "strings" "time" + + "github.com/SpeckiJ/Hochwasser/pixelflut" ) var err error @@ -67,23 +59,23 @@ func main() { b2 := b1.Add(image.Pt(b1.Dx(), 0)) b3 := b1.Add(image.Pt(0, b1.Dy())) b4 := b1.Add(b1.Size()) - go fetchImage(fetchedImg.SubImage(b1).(*image.NRGBA)) - go fetchImage(fetchedImg.SubImage(b2).(*image.NRGBA)) - go fetchImage(fetchedImg.SubImage(b3).(*image.NRGBA)) - go fetchImage(fetchedImg.SubImage(b4).(*image.NRGBA)) + go pixelflut.FetchImage(fetchedImg.SubImage(b1).(*image.NRGBA), *address) + go pixelflut.FetchImage(fetchedImg.SubImage(b2).(*image.NRGBA), *address) + go pixelflut.FetchImage(fetchedImg.SubImage(b3).(*image.NRGBA), *address) + go pixelflut.FetchImage(fetchedImg.SubImage(b4).(*image.NRGBA), *address) *connections -= 4 } // Generate and split messages into equal chunks - commands := genCommands(img, offset) + commands := pixelflut.CommandsFromImage(img, offset) if *shuffle { - shuffleCommands(commands) + commands.Shuffle() } if *connections > 0 { - commandGroups := chunkCommands(commands, *connections) + commandGroups := commands.Chunk(*connections) for _, messages := range commandGroups { - go bomb(messages) + go pixelflut.Bomb(messages, *address) } } @@ -98,137 +90,3 @@ func main() { writeImage(*fetchImgPath, fetchedImg) } } - -func bomb(messages []byte) { - conn, err := net.Dial("tcp", *address) - if err != nil { - log.Fatal(err) - } - - defer conn.Close() - - // Start bombardement - for { - _, err := conn.Write(messages) - if err != nil { - log.Fatal(err) - } - } -} - -func readImage(path string) (img image.Image) { - reader, err := os.Open(path) - if err != nil { - log.Fatal(err) - } - - img, _, err2 := image.Decode(reader) - if err2 != nil { - log.Fatal(err2) - } - - return img -} - -func writeImage(path string, img image.Image) { - f, err := os.Create(path) - if err != nil { - log.Fatal(err) - } - if err := png.Encode(f, img); err != nil { - f.Close() - log.Fatal(err) - } -} - -// Creates message based on given image -func genCommands(img image.Image, offset image.Point) (commands [][]byte) { - 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] -} - -// Splits messages into equally sized chunks -func chunkCommands(commands [][]byte, numChunks int) [][]byte { - chunks := make([][]byte, numChunks) - - chunkLength := len(commands) / numChunks - for i := 0; i < numChunks; i++ { - cmdOffset := i * chunkLength - for j := 0; j < chunkLength; j++ { - chunks[i] = append(chunks[i], commands[cmdOffset+j]...) - } - } - return chunks -} - -func shuffleCommands(slice [][]byte) { - for i := range slice { - j := rand.Intn(i + 1) - slice[i], slice[j] = slice[j], slice[i] - } -} - -func fetchImage(img *image.NRGBA) { - // FIXME @speed: this is unusably s l o w w w - // bottleneck seems to be our pixel reading/parsing code. cpuprofile! - // -> should buffer it just as in bomb() - - conn, err := net.Dial("tcp", *address) - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - reader := bufio.NewReader(conn) - tp := textproto.NewReader(reader) - - b := img.Bounds() - for { - for x := b.Min.X; x < b.Max.X; x++ { - for y := b.Min.Y; y < b.Max.Y; y++ { - // request pixel - fmt.Fprintf(conn, "PX %d %d\n", x, y) - if err != nil { - log.Fatal(err) - } - - // read pixel - // @speed try to run this in a separate goroutine? - // we probably need to buffer the responses then - res, err := tp.ReadLine() - if err != nil { - log.Fatal(err) - } - res2 := strings.Split(res, " ") - col, _ := hex.DecodeString(res2[3]) - img.Set(x, y, color.NRGBA{ - uint8(col[0]), - uint8(col[1]), - uint8(col[2]), - 255, - }) - } - } - } -} diff --git a/pixelflut/commands.go b/pixelflut/commands.go new file mode 100644 index 0000000..04fb8de --- /dev/null +++ b/pixelflut/commands.go @@ -0,0 +1,30 @@ +package pixelflut + +import ( + "math/rand" +) + +// Commands represent a list of messages to be sent to a pixelflut server. +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 { + chunks := make([][]byte, numChunks) + chunkLength := len(c) / numChunks + for i := 0; i < numChunks; i++ { + cmdOffset := i * chunkLength + for j := 0; j < chunkLength; j++ { + chunks[i] = append(chunks[i], c[cmdOffset+j]...) + } + } + return chunks +} + +// Shuffle reorders commands randomly, in place. +func (c Commands) Shuffle() { + for i := range c { + j := rand.Intn(i + 1) + c[i], c[j] = c[j], c[i] + } +} diff --git a/pixelflut/fetch.go b/pixelflut/fetch.go new file mode 100644 index 0000000..55c3814 --- /dev/null +++ b/pixelflut/fetch.go @@ -0,0 +1,57 @@ +package pixelflut + +import ( + "bufio" + "encoding/hex" + "fmt" + "image" + "image/color" + "log" + "net" + "net/textproto" + "strings" +) + +func FetchImage(img *image.NRGBA, address string) { + // FIXME @speed: this is unusably s l o w w w + // bottleneck seems to be our pixel reading/parsing code. cpuprofile! + // -> should buffer it just as in bomb() + + conn, err := net.Dial("tcp", address) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + reader := bufio.NewReader(conn) + tp := textproto.NewReader(reader) + + b := img.Bounds() + for { + for x := b.Min.X; x < b.Max.X; x++ { + for y := b.Min.Y; y < b.Max.Y; y++ { + // request pixel + fmt.Fprintf(conn, "PX %d %d\n", x, y) + if err != nil { + log.Fatal(err) + } + + // read pixel + // @speed try to run this in a separate goroutine? + // we probably need to buffer the responses then + res, err := tp.ReadLine() + if err != nil { + log.Fatal(err) + } + res2 := strings.Split(res, " ") + col, _ := hex.DecodeString(res2[3]) + img.Set(x, y, color.NRGBA{ + uint8(col[0]), + uint8(col[1]), + uint8(col[2]), + 255, + }) + } + } + } +} diff --git a/pixelflut/net.go b/pixelflut/net.go new file mode 100644 index 0000000..8b42d4c --- /dev/null +++ b/pixelflut/net.go @@ -0,0 +1,23 @@ +package pixelflut + +import ( + "net" + "log" +) + +// Bomb writes the given message via plain TCP to the given address, +// forever, as fast as possible. +func Bomb(message []byte, address string) { + conn, err := net.Dial("tcp", address) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + for { + _, err := conn.Write(message) + if err != nil { + log.Fatal(err) + } + } +} diff --git a/pixelflut/send.go b/pixelflut/send.go new file mode 100644 index 0000000..c4644b3 --- /dev/null +++ b/pixelflut/send.go @@ -0,0 +1,34 @@ +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] +}