graphicsdynamic-programmingcombinatoricscolor-palettegameboy

Assign palettes to tiles of an image to fit within N palettes of K colors each


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?


Solution

  • 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