Hochwasser/pixelflut/commands.go

130 lines
2.9 KiB
Go

package pixelflut
import (
"fmt"
"image"
"image/color"
"math/rand"
"strconv"
)
// 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]
}
}
// OffsetCmd applies offset to all following requests. Not supported by all servers. example: https://github.com/TobleMiner/shoreline.
func OffsetCmd(x, y int) []byte {
return []byte(fmt.Sprintf("OFFSET %d %d\n", x, y))
}
// CommandsFromImage converts an image to the respective pixelflut 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
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.NRGBAAt(x, y)
if c.A == 0 {
continue
}
var cmd []byte
cmd = append(cmd, []byte("PX ")...)
cmd = strconv.AppendUint(cmd, uint64(x+offset.X), 10)
cmd = append(cmd, ' ')
cmd = strconv.AppendUint(cmd, uint64(y+offset.Y), 10)
cmd = append(cmd, ' ')
appendColor(&cmd, c)
cmd = append(cmd, '\n')
cmds[numCmds] = cmd
numCmds++
}
}
cmds = cmds[:numCmds]
if order == Shuffle {
cmds.Shuffle()
}
return
}
func appendColor(buf *[]byte, c color.NRGBA) {
var mask uint32 = 0xf0000000
// merge into uint32
var col = uint32(c.R)<<24 + uint32(c.G)<<16 + uint32(c.B)<<8 + uint32(c.A)
// if alpha is ff, drop it.
if 0xff&col == 0xff {
col = col >> 8
mask = mask >> 8
}
// add leading zeros if needed
for mask > 0xf {
if mask&col == 0 {
*buf = append(*buf, '0')
} else {
break
}
mask = mask >> 4
}
*buf = strconv.AppendUint(*buf, uint64(col), 16)
}
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
}