def main(argv=None): argv = argv or sys.argv op = argv[1] filename = argv[2] if op == 'font': w = int(argv[3]) h = int(argv[4]) title = argv[5] print(cvt_font(filename, 24, 32, '0;1', title)) return if op == 'img': starttile = int(argv[3]) title = argv[4] im = Image.open(filename) c = pilbmp2chr(im, 8, 8, lambda k: formatTilePlanar(k, '0;1')) tiles, itiles, nam = chrtonam(c) nam = bytearray(starttile + c for c in nam) tiles = b''.join(tiles) ctiles = zeroelim(tiles) out = [ "; image converted with cvtfont.py", ".export %s_zet, %s_chr_size, %s_nam" % (title, title, title), ".exportzp %s_w, %s_h" % (title, title), '.segment "RODATA"', "%s_w = %d" % (title, (im.size[0] + 7) // 8), "%s_h = %d" % (title, (im.size[1] + 7) // 8), "%s_chr_size = %d" % (title, len(tiles)), "%s_zet:" % title, ca65_bytearray(ctiles), "%s_nam:" % title, ca65_bytearray(nam) ] print('\n'.join(out)) return
def main(argv=None): argv = argv or sys.argv infilename, outfilename = argv[1:3] im = Image.open(infilename) tiles = pilbmp2chr(im, formatTile=snesformat) utiles, tilemap = flipuniq(tiles) assert len(utiles) <= 64 pbtiles = b"".join(pb16.pb16(b"".join(utiles))) tmrows = [bytes(tilemap[i:i + 32]) for i in range(0, len(tilemap), 32)] utmrows, tmrowmap = uniq(tmrows) iutmrows = iur_encode_tilemap(b''.join(utmrows)) palette = im.getpalette()[:48] snespalette = bytearray() for i in range(0, 48, 3): r = palette[i] >> 3 g = palette[i + 1] >> 3 b = palette[i + 2] >> 3 bgr = (b << 10) | (g << 5) | r snespalette.append(bgr & 0xFF) snespalette.append(bgr >> 8) out = b"".join( (bytes([len(utiles) * 2]), pbtiles, bytes([16 * len(utmrows)]), iutmrows, bytes(tmrowmap), snespalette)) with open(outfilename, "wb") as outfp: outfp.write(out)
def read_strip(im, strip, g2l, hotspot, tile_ht): paletteid, l, t, w, h, lpad, tpad, dstl, dstt = strip # Crop and convert to subpalette cropped = im.crop((l, t, l + w, t + h)).point(g2l[paletteid]) # Add padding at left and top for exceeding the crop rect, # and at right and bottom to a multiple of one tile wnew = -(-(w + lpad) // TILE_W) * TILE_W hnew = -(-(h + tpad) // tile_ht) * tile_ht padded = Image.new('P', (wnew, hnew), 0) padded.paste(cropped, (lpad, tpad)) # Convert image to tiles tilefmt = lambda x: pilbmp2nes.formatTilePlanar(x, TILE_PLANEMAP) striptiles = pilbmp2nes.pilbmp2chr(padded, TILE_W, tile_ht, tilefmt) # Convert coords to hotspot-relative dstl -= hotspot[0] dstt -= hotspot[1] # Convert tiles to horizontal strips tperrow = (tile_ht // 8) * (wnew // 8) tend = 0 for y in range(hnew // tile_ht): tstart, tend = tend, tend + tperrow yield paletteid, striptiles[tstart:tend], dstl, dstt + y * tile_ht
def main(argv=None): argv = argv or sys.argv specfilename = argv[1] imname = argv[2] outfilename = argv[3] lines = load_spec_file(specfilename) coords = [] for line in lines: coord = [x.strip() for x in line.split(',')] if len(coord) != 2 or not all(x.isdigit() for x in coord): print("%s is not coords" % line, sys.stderr) continue coords.append(tuple(int(x) for x in coord)) im = Image.open(imname) if im.mode != 'P': print("mkspritemap.py: %s must be indexed" % imname, file=sys.stderr)) sys.exit(1) overdraw = [0]*im.size[1] alltiles = [] out = array.array('B', [len(coords)]) for left, top in coords: out.extend((left, top)) for y in xrange(top, top + 8): overdraw[y] += 1 tdata = pilbmp2chr(im.crop((left, top, left+8, top+8))) alltiles.extend(tdata) compressed = pb53.pb53(''.join(alltiles))[0] out.fromstring(compressed) print("Num tiles: %d; max overdraw: %d; data size: %d bytes" % (len(alltiles), max(overdraw), len(compressed)))
def test_iur(): from PIL import Image from pilbmp2nes import pilbmp2chr, formatTilePlanar gbformat = lambda tile: formatTilePlanar(tile, "0,1") test_filenames = [ ("Proposed MF title screen", "../renders/title_bgonly.png"), ("Green Hill Zone for GB", "../../240p-test-mini/gameboy/tilesets/greenhillzone.png"), ("Gus portrait for DMG", "../../240p-test-mini/gameboy/tilesets/Gus_portrait.png"), ("Linearity quadrant for GB", "../../240p-test-mini/gameboy/tilesets/linearity-quadrant.png"), ("Sharpness for GB", "../../240p-test-mini/gameboy/tilesets/sharpness.png"), ("Stopwatch face for GB", "../../240p-test-mini/gameboy/tilesets/stopwatchface.png"), ] for title, filename in test_filenames: print("Stats for %s (%s)" % (title, os.path.basename(filename))) im = Image.open(filename) chrdata = pilbmp2chr(im, formatTile=gbformat) iur_encode(chrdata, report=True) print()
def im_to_gbc(im, palettes): imfinal, attrs = colorround(im, palettes, (8, 8), 4) gbformat = lambda im: formatTilePlanar(im, "0,1") tiles = pilbmp2chr(imfinal, formatTile=formatTileGB) utiles, tilemap = flipuniq(tiles) assert len(tilemap) == len(attrs) tilemap_hi = bytearray((t >> 8) | c for t, c in zip(tilemap, attrs)) tilemap_lo = bytearray(t & 0xFF for t in tilemap) return imfinal, utiles, tilemap_lo, tilemap_hi
def bmptowidesb53(infilename, palette, outfilename): im = Image.open(infilename) if im.size[0] != 512: raise ValueError("Image width is %d pixels (expected 512)" % im.size[0]) if im.size[1] != 240: raise ValueError("Image height is %d pixels (expected 240)" % im.size[0]) # Quantize picture to palette palette = b''.join(palette[0:1] + palette[i + 1:i + 4] for i in range(0, 16, 4)) palettes = [[tuple(savtool.bisqpal[r]) for r in palette[i:i + 4]] for i in range(0, 16, 4)] imf, attrs = savtool.colorround(im, palettes) # Convert to unique tiles chrdata = pilbmp2nes.pilbmp2chr(imf, 8, 8) chrdata, linear_namdata = chnutils.dedupe_chr(chrdata) print("%d distinct tiles" % len(chrdata)) # Split into separate 32x32 nametables nametables = [[ linear_namdata[i:i + 32] for i in range(x, len(linear_namdata), im.size[0] // 8) ] for x in range(0, im.size[0] // 8, 32)] nametables = [bytes(b for row in nt for b in row) for nt in nametables] # Pack attributes into bytes if len(attrs) % 2: attrs.append([0] * len(attrs[0])) attrs = [[lc | (rc << 2) for lc, rc in zip(row[0::2], row[1::2])] for row in attrs] attrs = [[tc | (bc << 4) for (tc, bc) in zip(t, b)] for (t, b) in zip(attrs[0::2], attrs[1::2])] # Split into separate 32x32 nametables attrs = [ bytes(b for row in attrs for b in row[i:i + 8]) for i in range(0, len(attrs[0]), 8) ] print([len(x) for x in attrs]) outdata = [ bytearray([len(chrdata) & 0xFF]), pb53(b''.join(chrdata), copyprev=False)[0] ] outdata.extend( pb53(nt + at, copyprev=False)[0] for nt, at in zip(nametables, attrs)) outdata.append(palette) with open(outfilename, 'wb') as outfp: outfp.writelines(outdata)
def bmptowidesb53(infilename, palette, outfilename): im = Image.open(infilename) if im.size[0] != 512: raise ValueError("Image width is %d pixels (expected 512)" % im.size[0]) if im.size[1] != 240: raise ValueError("Image height is %d pixels (expected 240)" % im.size[0]) # Quantize picture to palette palette = b''.join(palette[0:1] + palette[i + 1:i + 4] for i in range(0, 16, 4)) palettes = [[tuple(savtool.bisqpal[r]) for r in palette[i:i + 4]] for i in range(0, 16, 4)] imf, attrs = savtool.colorround(im, palettes) # Convert to unique tiles chrdata = pilbmp2nes.pilbmp2chr(imf, 8, 8) chrdata, linear_namdata = chnutils.dedupe_chr(chrdata) print("%d distinct tiles" % len(chrdata)) # Split into separate 32x32 nametables nametables = [[linear_namdata[i:i + 32] for i in range(x, len(linear_namdata), im.size[0] // 8)] for x in range(0, im.size[0] // 8, 32)] nametables = [bytes(b for row in nt for b in row) for nt in nametables] # Pack attributes into bytes if len(attrs) % 2: attrs.append([0] * len(attrs[0])) attrs = [[lc | (rc << 2) for lc, rc in zip(row[0::2], row[1::2])] for row in attrs] attrs = [[tc | (bc << 4) for (tc, bc) in zip(t, b)] for (t, b) in zip(attrs[0::2], attrs[1::2])] # Split into separate 32x32 nametables attrs = [bytes(b for row in attrs for b in row[i:i + 8]) for i in range(0, len(attrs[0]), 8)] print([len(x) for x in attrs]) outdata = [ bytearray([len(chrdata) & 0xFF]), pb53(b''.join(chrdata), copyprev=False)[0] ] outdata.extend(pb53(nt + at, copyprev=False)[0] for nt, at in zip(nametables, attrs)) outdata.append(palette) with open(outfilename, 'wb') as outfp: outfp.writelines(outdata)
def load_glyph_tiles_from(filename, cellw, cellh, fmt): """Load tiles from glyphs in filename. im -- PIL image object in indexed color or filename to load cellw -- width of each glyph box, multiple of 8 cellh -- height of glyph box, multiple of 8 fmt -- a pilbmp2nes planemap string, such as "0,1" for GB or "0;1" for NES The glyphs in `im` are left-aligned in their boxes, using color 0 for transparent and the highest color value for space between glyphs. Return a list of lists of tiles in column-major order. """ if isinstance(filename, str): im = Image.open(filename) else: im, filename = filename, '<image>' if im.mode != 'P': raise ValueError("%s: not indexed color" % filename) st = ImageStat.Stat(im) bordercolor = st.extrema[0][1] # Crop each glyph out of the image portions = (im.crop((x, y, x + cellw, y + cellh)) for y in range(0, im.size[1], cellh) for x in range(0, im.size[0], cellw)) # Crop out the internal border to right of each glyph # ImageChops.difference(im, flat).getbbox(): thanks Eugene Nagorny # http://stackoverflow.com/q/10615901/2738262 flatborder = Image.new(im.mode, (cellw, cellh), bordercolor) bboxes = ((portion, ImageChops.difference(portion, flatborder).getbbox()) for portion in portions) portionsC = (portion.crop(bb) if bb is not None else None for portion, bb in bboxes) # Now break each glyph down into tiles fmtTile = lambda im: formatTilePlanar(im, fmt) portionsT = [ pilbmp2chr(portion, 8, 32, fmtTile) if portion is not None else [] for portion in portionsC ] return portionsT
def main(argv=None): argv = argv or sys.argv infilename, outfilename = argv[1:3] im = Image.open(infilename) tiles = pilbmp2chr(im, formatTile=snesformat) utiles, tilemap = flipuniq(tiles) assert len(utiles) <= 64 pbtiles = b"".join(pb16.pb16(b"".join(utiles))) tmrows = [bytes(tilemap[i:i + 32]) for i in range(0, len(tilemap), 32)] use_utmrows = False use_iur = False if use_utmrows: from uniq import uniq utmrows, tmrowmap = uniq(tmrows) if len(utmrows) >= 16: raise ValueError("%s: too many unique rows: %d > 15" % (infilename, len(utmrows))) else: utmrows, tmrowmap = tmrows, list(range(len(tmrows))) if use_iur: from incruniq import iur_encode_tilemap iutmrows = iur_encode_tilemap(b''.join(utmrows)) else: iutmrows = b''.join(pb16.pb16(b''.join(utmrows))) palette = im.getpalette()[:48] snespalette = bytearray() for i in range(0, 48, 3): r = palette[i] >> 3 g = palette[i + 1] >> 3 b = palette[i + 2] >> 3 bgr = (b << 10) | (g << 5) | r snespalette.append(bgr & 0xFF) snespalette.append(bgr >> 8) print(snespalette.hex()) out = b"".join( (bytes([len(utiles) * 2]), pbtiles, bytes([len(utmrows) * 16]) if use_utmrows else b"", iutmrows, bytes(tmrowmap) if use_utmrows else b"", snespalette)) with open(outfilename, "wb") as outfp: outfp.write(out)
def bitmap_to_sav(im, max_tiles=None): """Convert a PIL bitmap without remapping the colors.""" from pilbmp2nes import pilbmp2chr from chnutils import dedupe_chr (w, h) = im.size im = pilbmp2chr(im, 8, 8) if max_tiles is not None: from jrtilevq import reduce_tiles im = reduce_tiles(im, max_tiles) namdata = bytearray([0xFF]) * 960 namdata.extend(bytes(64)) # If smaller than 16384 pixels, output as a tile sheet # with a blank nametable if len(im) > 256: im, linear_namdata = dedupe_chr(im) if len(im) > 256: raise IndexError("image has %d distinct tiles, which exceeds 256" % len(im)) width_in_tiles = w // 8 height_in_tiles = len(linear_namdata) // width_in_tiles topborder = (31 - height_in_tiles) // 2 leftborder = (32 - width_in_tiles) // 2 rightborder = 32 - width_in_tiles - leftborder offset = topborder * 32 + leftborder for i in range(0, len(linear_namdata), width_in_tiles): namdata[offset:offset + width_in_tiles] = linear_namdata[i:i + width_in_tiles] offset += 32 assert len(namdata) == 1024 chrdata = b''.join(im) chrpad = b'\xFF' * (6144 - len(chrdata)) assert len(chrdata) <= 4096 assert len(chrdata) + len(chrpad) == 6144 assert len(namdata) == 1024 sav = b''.join((chrdata, chrpad, namdata, b'\xFF' * 768, default_palette, default_palette, b'\xFF' * 224)) assert len(sav) == 8192 return sav
def bitmap_to_sav(im): """Convert a PIL bitmap without remapping the colors.""" from pilbmp2nes import pilbmp2chr from chnutils import dedupe_chr (w, h) = im.size im = pilbmp2chr(im, 8, 8) if len(im) <= 256: # smaller than 16384 pixels: output as a tile sheet # with a blank nametable chrdata = ''.join(im) namdata = '\xFF' * 960 + '\x00' * 64 else: from array import array im, linear_namdata = dedupe_chr(im) if len(im) > 256: raise IndexError("image has %d distinct tiles, which exceeds 256" % len(im)) width_in_tiles = w // 8 height_in_tiles = len(linear_namdata) // width_in_tiles topborder = (31 - height_in_tiles) // 2 leftborder = (32 - width_in_tiles) // 2 rightborder = 32 - width_in_tiles - leftborder namdata = array('B', '\xFF' * (32 * topborder)) for i in range(0, len(linear_namdata), width_in_tiles): namdata.fromstring('\xFF' * leftborder) namdata.extend(linear_namdata[i:i + width_in_tiles]) namdata.fromstring('\xFF' * rightborder) namdata.fromstring('\xFF' * (960 - len(namdata))) namdata.fromstring('\x00' * 64) namdata = namdata.tostring() assert len(namdata) == 1024 chrdata = ''.join(im) chrdata = chrdata + '\xFF' * (4096 - len(chrdata)) sav = ''.join((chrdata, '\xFF' * 2048, namdata, '\xFF' * 768, default_palette, default_palette, '\xFF' * 224)) return sav
def bitmap_to_sav(im): """Convert a PIL bitmap without remapping the colors.""" from pilbmp2nes import pilbmp2chr from chnutils import dedupe_chr (w, h) = im.size im = pilbmp2chr(im, 8, 8) if len(im) <= 256: # smaller than 16384 pixels: output as a tile sheet # with a blank nametable chrdata = "".join(im) namdata = "\xFF" * 960 + "\x00" * 64 else: from array import array im, linear_namdata = dedupe_chr(im) if len(im) > 256: raise IndexError("image has %d distinct tiles, which exceeds 256" % len(im)) width_in_tiles = w // 8 height_in_tiles = len(linear_namdata) // width_in_tiles topborder = (31 - height_in_tiles) // 2 leftborder = (32 - width_in_tiles) // 2 rightborder = 32 - width_in_tiles - leftborder namdata = array("B", "\xFF" * (32 * topborder)) for i in range(0, len(linear_namdata), width_in_tiles): namdata.fromstring("\xFF" * leftborder) namdata.extend(linear_namdata[i : i + width_in_tiles]) namdata.fromstring("\xFF" * rightborder) namdata.fromstring("\xFF" * (960 - len(namdata))) namdata.fromstring("\x00" * 64) namdata = namdata.tostring() assert len(namdata) == 1024 chrdata = "".join(im) chrdata = chrdata + "\xFF" * (4096 - len(chrdata)) sav = "".join((chrdata, "\xFF" * 2048, namdata, "\xFF" * 768, default_palette, default_palette, "\xFF" * 224)) return sav
def bitmap_to_sav(im): """Convert a PIL bitmap without remapping the colors.""" from pilbmp2nes import pilbmp2chr from chnutils import dedupe_chr (w, h) = im.size im = pilbmp2chr(im, 8, 8) namdata = bytearray([0xFF] * 960) namdata.extend([0x00] * 64) # If smaller than 16384 pixels, output as a tile sheet # with a blank nametable if len(im) > 256: im, linear_namdata = dedupe_chr(im) if len(im) > 256: raise IndexError("image has %d distinct tiles, which exceeds 256" % len(im)) width_in_tiles = w // 8 height_in_tiles = len(linear_namdata) // width_in_tiles topborder = (31 - height_in_tiles) // 2 leftborder = (32 - width_in_tiles) // 2 rightborder = 32 - width_in_tiles - leftborder offset = topborder * 32 + leftborder for i in range(0, len(linear_namdata), width_in_tiles): namdata[offset:offset + width_in_tiles] = linear_namdata[i:i + width_in_tiles] offset += 32 assert len(namdata) == 1024 chrdata = b''.join(im) chrpad = b'\xFF' * (6144 - len(chrdata)) assert len(chrdata) <= 4096 assert len(chrdata) + len(chrpad) == 6144 assert len(namdata) == 1024 sav = b''.join((chrdata, chrpad, namdata, b'\xFF' * 768, default_palette, default_palette, b'\xFF' * 224)) assert len(sav) == 8192 return sav
def main(argv=None): global printStats import sys from pilbmp2nes import formatTilePlanar, pilbmp2chr if argv is None: argv = sys.argv if (argvTestingMode and len(argv) < 2 and sys.stdin.isatty() and sys.stdout.isatty()): argv.extend(raw_input('args:').split()) try: (infilename, outfilename, maxTiles, useDims, chrCodec, useIndexRLE, printStats) = parse_argv(argv) except Exception as e: sys.stderr.write("%s: %s\n" % (argv[0], str(e))) sys.exit(1) if printStats: print("filename: %s" % infilename, file=sys.stderr) im = Image.open(infilename) (w, h) = im.size if printStats: print >> sys.stderr, "size: %dx%d" % im.size if w % 8 != 0: raise ValueError("image width %d is not a multiple of 8" % w) if h % 8 != 0: raise ValueError("image height %d is not a multiple of 8" % h) ochrdata = pilbmp2chr(im, 8, 8, lambda im: formatTilePlanar(im, 2)) del im (chrdata, ntdata) = dedupe_chr(ochrdata) numTiles = len(chrdata) if numTiles > maxTiles: raise ValueError("%d distinct tiles exceed maximum %d" % (numTiles, maxTiles)) ochrdata = ''.join(ochrdata) chrdata = ''.join(chrdata) if printStats: print("CHR size before dedupe: %d" % len(ochrdata), file=sys.stderr) print("distinct tiles: %d of %d" % (numTiles, len(ntdata)), file=sys.stderr) print("unpacked CHR size: %d" % len(chrdata), file=sys.stderr) if useIndexRLE: cntdata = compress_nt(ntdata) if printStats: print("compressed nametable size: %s" % len(cntdata), file=sys.stderr) ntdata = cntdata else: ntdata = ''.join(chr(x) for x in ntdata) if chrCodec == 'packbits': from packbits import PackBits sz = len(chrdata) % 0x10000 pchrdata = PackBits(chrdata).flush().tostring() pchrdata = bytes([chr(sz >> 8), chr(sz & 0xFF)]) + pchrdata if printStats: print("packed CHR size: %d" % len(pchrdata), file=sys.stderr) chrdata = pchrdata elif chrCodec == 'pb8': from pb8 import pb8 sz = len(chrdata) // 16 pchrdata = pb8(chrdata) pchrdata = bytes([sz & 0xFF]) + pchrdata if printStats: print("packed CHR size: %d" % len(pchrdata), file=sys.stderr) chrdata = pchrdata if useDims: dimsdata = bytes([w // 8, h // 8, numTiles & 0xFF, 0]) else: dimsdata = '' outdata = b''.join([dimsdata, ntdata, chrdata]) # Write output file outfp = None try: if outfilename != '-': outfp = open(outfilename, 'wb') else: outfp = sys.stdout outfp.write(outdata) finally: if outfp and outfilename != '-': outfp.close()