Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- import sys
- import math
- import random
- import argparse
- from PIL import Image
- p = argparse.ArgumentParser(description = 'Convert an image to a Commodore 64 multi-colour bitmap.')
- p.add_argument('input', help='The image file to read.')
- p.add_argument('-o','--output', help='Output filename.', default=False)
- p.add_argument('-d','--dither', help='Dither Level 0-32.', default=False, type=int)
- p.add_argument('-b', '--background', help='Set the background colour 0-15. Default: Auto', default = False, type=int)
- args = p.parse_args()
- background = args.background
- oof=args.output
- if (oof==False):
- oof=args.input[:-4]+"_c64.png"
- dither=args.dither
- if (dither==False):
- dither=0
- if background != False and (background < 0 or background > 15):
- p.print_usage()
- print("Invalid background colour " + str(background))
- exit()
- # Open and convert the image to 160x200 RGB frame
- im = Image.open(args.input).convert('RGB').resize((160, 200))
- # The C64 palette
- palette = (
- (0,0,0),
- (255,255,255),
- (137,64,54),
- (122,191,199),
- (138,70,174),
- (104,169,65),
- (62,49,162),
- (208,220,113),
- (144,95,37),
- (92,71,0),
- (187,119,109),
- (85,85,85),
- (128,128,128),
- (172,234,136),
- (124,112,218),
- (171,171,171),
- # (0x00, 0x00, 0x00),
- # (0xFF, 0xFF, 0xFF),
- # (0x88, 0x00, 0x00),
- # (0xAA, 0xFF, 0xEE),
- # (0xCC, 0x44, 0xCC),
- # (0x00, 0xCC, 0x55),
- # (0x00, 0x00, 0xAA),
- # (0xEE, 0xEE, 0x77),
- # (0xDD, 0x88, 0x55),
- # (0x66, 0x44, 0x00),
- # (0xFF, 0x77, 0x77),
- # (0x33, 0x33, 0x33),
- # (0x77, 0x77, 0x77),
- # (0xAA, 0xFF, 0x66),
- # (0x00, 0x88, 0xFF),
- # (0xBB, 0xBB, 0xBB),
- )
- palette_names = (
- 'Black',
- 'White',
- 'Red',
- 'Cyan',
- 'Violet',
- 'Green',
- 'Blue',
- 'Yellow',
- 'Orange',
- 'Brown',
- 'Lightred',
- 'Dark Grey',
- 'Medium Grey',
- 'Light Green',
- 'Light Blue',
- 'Light Grey',
- )
- # Colour usage counters, palette order
- ccounter = [0] * 16
- # Create the canvas. This represents an 'ideal' C64 image where
- # there are no colour restrictions within the blocks
- canvas = [[[[0] * 4 for row in range(8)] for ccol in range(40)] for crow in range(25)]
- # Convert an (r, g, b) value to the nearest C64 palette entry
- def rgb2pal(colour):
- s = -1
- c = -1
- # Random noise dither
- colour = list(colour)
- colour[0] += random.randint(-dither,dither)
- colour[1] += random.randint(-dither,dither)
- colour[2] += random.randint(-dither,dither)
- for x in range(0, len(palette)):
- d = abs(math.sqrt(
- (colour[0] - palette[x][0]) ** 2 +
- (colour[1] - palette[x][1]) ** 2 +
- (colour[2] - palette[x][2]) ** 2
- ))
- if s == -1 or d < s:
- s = d
- c = x
- return c
- # Find the nearest C64 palette entry from a list of palette codes
- def pal2pal(colour, colours):
- s = -1
- c = -1
- colour = palette[colour]
- for x in range(0, len(colours)):
- d = abs(math.sqrt(
- (colour[0] - palette[colours[x]][0]) ** 2 +
- (colour[1] - palette[colours[x]][1]) ** 2 +
- (colour[2] - palette[colours[x]][2]) ** 2
- ))
- if s == -1 or d < s:
- s = d
- c = colours[x]
- return c
- # Render the PIL image to the canvas
- for cy in range(0, 25):
- for cx in range(0, 40):
- ox = cx * 4
- oy = cy * 8
- for y in range(0, 8):
- for x in range(0, 4):
- p = rgb2pal(im.getpixel((ox + x, oy + y)))
- canvas[cy][cx][y][x] = p
- ccounter[p] += 1
- if background == False:
- # Select the background based on the most used colour
- background = ccounter.index(max(ccounter))
- print("Using %s (%d) for background colour" % (palette_names[background], background))
- else:
- print("Background fixed at %s (%d)" % (palette_names[background], background))
- # Initialise the bitmap buffer
- bitmap_bytes = []
- screen_bytes = []
- colour_bytes = []
- last_colours = False
- for cy in range(0, 25):
- for cx in range(0, 40):
- # Make a list of all the colours used in this block
- # Background is always used, even if not referenced
- colours = [background]
- ccounter = [0]
- for row in canvas[cy][cx]:
- for colour in row:
- if not colour in colours:
- colours.append(colour)
- ccounter.append(0)
- ccounter[colours.index(colour)] += 1
- # Sort the colours, background first then most used to least
- colours = [background] + list(x[1] for x in sorted(zip(ccounter[1:], colours[1:]), reverse = True))
- if len(colours) > 4:
- print("Block %dx%d has too many non-background colours: %s -> %s" % (cx, cy, colours[1:], colours[1:4]))
- # Crop colour list to the background and 3 most used values
- colours = colours[:4]
- # Replace removed colours in the block with the nearest valid match
- for y in range(8):
- for x in range(4):
- p = canvas[cy][cx][y][x]
- if not p in colours:
- canvas[cy][cx][y][x] = pal2pal(p, colours)
- # If all the colours of this block are present in the previous block,
- # use that same order. This may help with RLE compression.
- if last_colours != False and set(colours).issubset(set(last_colours)):
- colours = last_colours
- elif len(colours) != 4:
- colours += [0] * (4 - len(colours))
- last_colours = colours
- # Output this block
- for row in canvas[cy][cx]:
- bitmap_bytes.append(colours.index(row[0]) << 6 | colours.index(row[1]) << 4 | colours.index(row[2]) << 2 | colours.index(row[3]))
- screen_bytes.append(colours[1] << 4 | colours[2])
- colour_bytes.append(colours[3])
- im = Image.new("P", (320, 200), 0)
- im.putpalette([
- 0,0,0,
- 255,255,255,
- 137,64,54,
- 122,191,199,
- 138,70,174,
- 104,169,65,
- 62,49,162,
- 208,220,113,
- 144,95,37,
- 92,71,0,
- 187,119,109,
- 85,85,85,
- 128,128,128,
- 172,234,136,
- 124,112,218,
- 171,171,171])
- pixels=im.load()
- for cy in range(0, 25):
- for cx in range(0, 40):
- ox = cx * 4
- oy = cy * 8
- for y in range(0, 8):
- for x in range(0, 4):
- pixels[((cx*8)+(x*2),(cy*8)+y)]=canvas[cy][cx][y][x]
- pixels[((cx*8)+(x*2)+1,(cy*8)+y)]=canvas[cy][cx][y][x]
- im.save(oof)
Advertisement
Add Comment
Please, Sign In to add comment