I'm writing a tool for working with tiled images. One feature is to convert a whole image into a tileset and tilemap, e.g. a 160x144px image would have a set of unique 8x8 tiles and a 20x18 map of tile IDs.
The next goal is to support palettes. On some of the older platforms that used tiled graphics, you might have 8 palettes of 4 colors each, or 16 of 16 each. I want to automatically create a set of palettes that fits within the N-by-K limit, using as few palettes as possible; and assign those palettes to the tilemap, or alert if it's not possible.
There are some obvious first steps: if any single tile uses more than K colors, it won't be possible; and once that's been checked, any tile whose colors are a subset of another can trivially share its palette. The difficulty is handling partially overlapping palettes. Consider 17 tiles, each with 15 unique colors; if there's enough overlap, they can fit within 16x16 color palettes, but it might not be possible.
I expect a dynamic programming solution would work here. At any stage in the problem, one has a partial assignment of tiles to palettes; and the decision is to which of the N palettes to assign the next tile. (The tile might not even have any colors in common with the optimal choice of palette at that time; consider 4 tiles each with 4 unique colors, they could all use a single 16-color palette.)
Has this particular problem been solved already? Is there a known algorithm for it, or just the general tips of all dynamic programming?
SuperFamiconv is capable of doing this for a few systems, including SNES (16 palettes, 8 colors/palette) and GBC (8 palettes, 4 colors/palette). It's also open-source, so their algorithm is available.
It turns out that dynamic programming isn't necessary for a "good enough" solution given realistically-sized images. (Not sure how well it would do for huge ones, but it doesn't matter for my purposes.)
This is a translation of their algorithm to Python:
def optimize_palettes(tilepals, N, K):
"""
Return an optimized palette set for the given tile set.
tilepals -- A list of sets of unique colors used for each tile of the image
N -- The maximum number of palettes for one image
K -- The maximum number of colors for one tile palette
"""
# Check that each tilepal fits within the color limit
if any(len(s) > K for s in tilepals):
raise OverflowError, "A tile has more than %d unique colors" % K
# Remove duplicate tilepals
sets = []
for s in tilepals:
if s not in sets:
sets.append(s)
# Remove tilepals that are proper subsets of other tilepals
sets = [s for s in sets if not any(c != s and s.issubset(c) for c in sets)]
# Sort tilepals from most to fewest colors
sets.sort(key=len, reverse=True)
# Combine tilepals as long as they fit within the color limit
opt = []
for s in sets:
for cs in opt:
if len(s | cs) <= K:
cs.update(s)
break
else:
opt.append(s)
# Sort tilepals from most to fewest colors
opt.sort(key=len, reverse=True)
# Check that the palettes fit within the palette limit
if len(opt) > N:
raise OverflowError, "There are more than %d palettes" % N
return opt