177 lines
4.9 KiB
Go
177 lines
4.9 KiB
Go
package compression
|
|
|
|
import (
|
|
"context"
|
|
"math/bits"
|
|
"sort"
|
|
)
|
|
|
|
type paletteLookupEntry struct {
|
|
start int
|
|
size int
|
|
}
|
|
|
|
// PaletteLookup is a dictionary of tile delta data, found in a palette buffer.
|
|
type PaletteLookup struct {
|
|
buffer []byte
|
|
entries map[TilePaletteKey]paletteLookupEntry
|
|
}
|
|
|
|
// Buffer returns the underlying slice.
|
|
func (lookup *PaletteLookup) Buffer() []byte {
|
|
return lookup.buffer
|
|
}
|
|
|
|
// Lookup finds the given tile again and returns the properties where and how to reproduce it.
|
|
func (lookup *PaletteLookup) Lookup(tile tileDelta) (index int, pal []byte, mask uint64) {
|
|
key := TilePaletteKeyFrom(tile[:])
|
|
entry, inLookup := lookup.entries[key]
|
|
if inLookup {
|
|
index = entry.start
|
|
pal = lookup.buffer[entry.start : entry.start+entry.size]
|
|
} else {
|
|
pal = key.Buffer()
|
|
}
|
|
var mapped [256]byte
|
|
for mappedIndex := len(pal) - 1; mappedIndex >= 0; mappedIndex-- {
|
|
mapped[pal[mappedIndex]] = byte(mappedIndex)
|
|
}
|
|
bitSize := uint(bits.Len(uint(len(pal) - 1)))
|
|
for tileIndex := PixelPerTile - 1; tileIndex >= 0; tileIndex-- {
|
|
mask <<= bitSize
|
|
mask |= uint64(mapped[tile[tileIndex]])
|
|
}
|
|
return
|
|
}
|
|
|
|
// PaletteLookupGenerator creates palette lookups based on a set of registered tiles.
|
|
type PaletteLookupGenerator struct {
|
|
keyUses map[TilePaletteKey]int
|
|
}
|
|
|
|
// Generate creates a lookup based on all currently registered tile deltas.
|
|
func (gen *PaletteLookupGenerator) Generate(ctx context.Context) (PaletteLookup, error) {
|
|
var lookup PaletteLookup
|
|
lookup.entries = make(map[TilePaletteKey]paletteLookupEntry)
|
|
|
|
remainder := make(map[TilePaletteKey]struct{})
|
|
for key := range gen.keyUses {
|
|
remainder[key] = struct{}{}
|
|
}
|
|
|
|
type sizedEntry struct {
|
|
entries map[TilePaletteKey]paletteLookupEntry
|
|
lastOffset int
|
|
}
|
|
sizedEntries := make(map[int]*sizedEntry)
|
|
knownSizes := []int{4, 8, 16}
|
|
for _, size := range knownSizes {
|
|
sizedEntries[size] = &sizedEntry{
|
|
entries: make(map[TilePaletteKey]paletteLookupEntry),
|
|
}
|
|
}
|
|
|
|
toDeleteCache := make([]TilePaletteKey, 0, len(remainder))
|
|
addToBuffer := func(data []byte) {
|
|
lookup.buffer = append(lookup.buffer, data...)
|
|
|
|
newSize := len(lookup.buffer)
|
|
|
|
for _, fitSize := range knownSizes {
|
|
fitLimit := newSize - fitSize
|
|
entry := sizedEntries[fitSize]
|
|
|
|
// remove all entries beyond a certain limit. as these bytes don't change, retrying won't help.
|
|
toDelete := toDeleteCache[0:0]
|
|
limit := newSize - 16 - len(data)
|
|
for key, entry := range entry.entries {
|
|
if entry.start < limit {
|
|
toDelete = append(toDelete, key)
|
|
}
|
|
}
|
|
for _, key := range toDelete {
|
|
delete(entry.entries, key)
|
|
}
|
|
|
|
// find any new keys
|
|
for start := entry.lastOffset; start < fitLimit; start++ {
|
|
tempKey := TilePaletteKeyFrom(lookup.buffer[start : start+fitSize])
|
|
if _, existing := entry.entries[tempKey]; !existing {
|
|
entry.entries[tempKey] = paletteLookupEntry{
|
|
start: start,
|
|
size: fitSize,
|
|
}
|
|
}
|
|
}
|
|
if fitLimit > 0 {
|
|
entry.lastOffset = fitLimit
|
|
}
|
|
}
|
|
}
|
|
|
|
addEarlyEntry := func(key TilePaletteKey, limitSize int) bool {
|
|
for _, fitSize := range knownSizes {
|
|
if key.size <= fitSize && fitSize <= limitSize {
|
|
entry := sizedEntries[fitSize]
|
|
for tempKey, paletteEntry := range entry.entries {
|
|
if tempKey.Contains(&key) && (!key.HasColor(0x00) || (lookup.buffer[paletteEntry.start] == 0x00)) {
|
|
lookup.entries[key] = paletteEntry
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
sizeLimitForSize := map[int]int{3: 4, 4: 8, 5: 8, 6: 8, 7: 8, 8: 8, 9: 16, 10: 16, 11: 16, 12: 16, 13: 16, 14: 16, 15: 16, 16: 16}
|
|
for size := PixelPerTile; (size > 2) && (ctx.Err() == nil); size-- {
|
|
keysInSize := make([]TilePaletteKey, 0, len(remainder))
|
|
for key := range remainder {
|
|
if key.size == size {
|
|
keysInSize = append(keysInSize, key)
|
|
}
|
|
}
|
|
sort.Slice(keysInSize, func(a, b int) bool { return keysInSize[a].LessThan(&keysInSize[b]) })
|
|
|
|
// fmt.Printf("Working on key size %v, have %v sized, %v total remaining\n", size, len(keysInSize), len(remainder))
|
|
for _, sizedKey := range keysInSize {
|
|
if ctx.Err() != nil {
|
|
return PaletteLookup{}, ctx.Err()
|
|
}
|
|
|
|
{
|
|
var earlyRemoved []TilePaletteKey
|
|
for key := range remainder {
|
|
if addEarlyEntry(key, sizeLimitForSize[size]) {
|
|
earlyRemoved = append(earlyRemoved, key)
|
|
}
|
|
}
|
|
for _, key := range earlyRemoved {
|
|
delete(remainder, key)
|
|
}
|
|
}
|
|
if _, stillRemaining := remainder[sizedKey]; stillRemaining {
|
|
bytes := sizedKey.Buffer()
|
|
lookup.entries[sizedKey] = paletteLookupEntry{start: len(lookup.buffer), size: len(bytes)}
|
|
addToBuffer(bytes)
|
|
|
|
delete(remainder, sizedKey)
|
|
}
|
|
}
|
|
}
|
|
|
|
return lookup, ctx.Err()
|
|
}
|
|
|
|
// Add registers a further delta to the generator.
|
|
func (gen *PaletteLookupGenerator) Add(delta tileDelta) {
|
|
key := TilePaletteKeyFrom(delta[:])
|
|
if key.size > 2 {
|
|
if gen.keyUses == nil {
|
|
gen.keyUses = make(map[TilePaletteKey]int)
|
|
}
|
|
gen.keyUses[key]++
|
|
}
|
|
}
|