1

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?

Remy
  • 401
  • 2
  • 19

1 Answers1

0

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
Remy
  • 401
  • 2
  • 19