// Package rle contains support for Run-Length Encoding of level chunks. package rle import ( "bytes" "encoding/binary" "errors" "strings" "git.kirsle.net/go/render" ) const NullColor = 0xFFFF // Grid is a 2D array of nullable integers to store a flat bitmap of a chunk. type Grid [][]*uint64 // NewGrid will return an initialized 2D grid of equal dimensions of the given size. // // The grid is indexed in [Y][X] notation, or: by row first and then column. func NewGrid(size int) (Grid, error) { if size == 0 { return nil, errors.New("no size given for RLE Grid: the chunker was probably not initialized") } var grid = make([][]*uint64, size) for i := 0; i < size; i++ { grid[i] = make([]*uint64, size) } return grid, nil } func MustGrid(size int) Grid { grid, err := NewGrid(size) if err != nil { panic(err) } return grid } type Pixel struct { Point render.Point Palette int } // Size of the grid. func (g Grid) Size() int { return len(g[0]) } // Compress the grid into a byte stream of RLE compressed data. // // The compressed format is a stream of: // // - A Uvarint for the palette index (0-255) or 0xffff (65535) for null. // - A Uvarint for how many pixels to repeat that color. func (g Grid) Compress() ([]byte, error) { // log.Error("BEGIN Compress()") // log.Warn("Visualized:\n%s", g.Visualize()) // Run-length encode the grid. var ( compressed []byte // final result lastColor uint64 // last color seen (current streak) runLength uint64 // current streak for the last color buffering bool // detect end of grid // Flush the buffer flush = func() { // log.Info("flush: %d for %d length", lastColor, runLength) compressed = binary.AppendUvarint(compressed, lastColor) compressed = binary.AppendUvarint(compressed, runLength) } ) for y, row := range g { for x, nullableIndex := range row { var index uint64 if nullableIndex == nil { index = NullColor } else { index = *nullableIndex } // First color of the grid if y == 0 && x == 0 { // log.Info("First color @ %dx%d is %d", x, y, index) lastColor = index runLength = 1 continue } // Buffer it until we get a change of color or EOF. if index != lastColor { // log.Info("Color %d streaks for %d until %dx%d", lastColor, runLength, x, y) flush() lastColor = index runLength = 1 buffering = false continue } buffering = true runLength++ } } // Flush the final buffer when we got to EOF on the grid. if buffering { flush() } // log.Error("RLE compressed: %v", compressed) return compressed, nil } // Decompress the RLE byte stream back into a populated 2D grid. func (g Grid) Decompress(compressed []byte) error { // log.Error("BEGIN Decompress() Length of stream: %d", len(compressed)) // log.Warn("Visualized:\n%s", g.Visualize()) // Prepare the 2D grid to decompress the RLE stream into. var ( size = g.Size() x, y = -1, -1 cursor int ) var reader = bytes.NewBuffer(compressed) for { var ( paletteIndexRaw, err1 = binary.ReadUvarint(reader) repeatCount, err2 = binary.ReadUvarint(reader) ) if err1 != nil || err2 != nil { break } // Handle the null color. var paletteIndex *uint64 if paletteIndexRaw != NullColor { paletteIndex = &paletteIndexRaw } // log.Warn("RLE index %v for %dpx - coord=%d,%d", paletteIndexRaw, repeatCount, x, y) for i := uint64(0); i < repeatCount; i++ { if cursor%size == 0 { y++ x = 0 } point := render.NewPoint(int(x), int(y)) g[point.Y][point.X] = paletteIndex x++ cursor++ } } // log.Warn("Visualized:\n%s", g.Visualize()) return nil } // Visualize the state of the 2D grid. func (g Grid) Visualize() string { var lines []string for _, row := range g { var line = "[" for _, col := range row { if col == nil { line += " " } else { line += Alphabetize(col) } } lines = append(lines, line+"]") } return strings.Join(lines, "\n") } const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" // Alphabetize converts a palette index value into a single character for // Visualize to display. // // It supports up to 36 palette indexes before it will wrap back around and // begin reusing symbols. func Alphabetize(value *uint64) string { if value == nil { return " " } var i = int(*value) return string(alphabet[i%len(alphabet)]) }