jamiepfuller

c642png.py

Oct 31st, 2019
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.82 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. import sys
  4. import math
  5. import random
  6. import argparse
  7. from PIL import Image
  8.  
  9. p = argparse.ArgumentParser(description = 'Convert an image to a Commodore 64 multi-colour bitmap.')
  10. p.add_argument('input', help='The image file to read.')
  11. p.add_argument('-o','--output', help='Output filename.', default=False)
  12. p.add_argument('-d','--dither', help='Dither Level 0-32.', default=False, type=int)
  13. p.add_argument('-b', '--background', help='Set the background colour 0-15. Default: Auto', default = False, type=int)
  14. args = p.parse_args()
  15.  
  16. background = args.background
  17. oof=args.output
  18. if (oof==False):
  19.     oof=args.input[:-4]+"_c64.png"
  20.  
  21. dither=args.dither 
  22. if (dither==False):
  23.     dither=0
  24.  
  25. if background != False and (background < 0 or background > 15):
  26.     p.print_usage()
  27.     print("Invalid background colour " + str(background))
  28.     exit()
  29.  
  30. # Open and convert the image to 160x200 RGB frame
  31. im = Image.open(args.input).convert('RGB').resize((160, 200))
  32.  
  33. # The C64 palette
  34. palette = (
  35.     (0,0,0),
  36.     (255,255,255),
  37.     (137,64,54),
  38.     (122,191,199),
  39.     (138,70,174),
  40.     (104,169,65),
  41.     (62,49,162),
  42.     (208,220,113),
  43.     (144,95,37),
  44.     (92,71,0),
  45.     (187,119,109),
  46.     (85,85,85),
  47.     (128,128,128),
  48.     (172,234,136),
  49.     (124,112,218),
  50.     (171,171,171),
  51.  
  52.     # (0x00, 0x00, 0x00),
  53.     # (0xFF, 0xFF, 0xFF),
  54.     # (0x88, 0x00, 0x00),
  55.     # (0xAA, 0xFF, 0xEE),
  56.     # (0xCC, 0x44, 0xCC),
  57.     # (0x00, 0xCC, 0x55),
  58.     # (0x00, 0x00, 0xAA),
  59.     # (0xEE, 0xEE, 0x77),
  60.     # (0xDD, 0x88, 0x55),
  61.     # (0x66, 0x44, 0x00),
  62.     # (0xFF, 0x77, 0x77),
  63.     # (0x33, 0x33, 0x33),
  64.     # (0x77, 0x77, 0x77),
  65.     # (0xAA, 0xFF, 0x66),
  66.     # (0x00, 0x88, 0xFF),
  67.     # (0xBB, 0xBB, 0xBB),
  68. )
  69.  
  70. palette_names = (
  71.     'Black',
  72.     'White',
  73.     'Red',
  74.     'Cyan',
  75.     'Violet',
  76.     'Green',
  77.     'Blue',
  78.     'Yellow',
  79.     'Orange',
  80.     'Brown',
  81.     'Lightred',
  82.     'Dark Grey',
  83.     'Medium Grey',
  84.     'Light Green',
  85.     'Light Blue',
  86.     'Light Grey',
  87. )
  88.  
  89. # Colour usage counters, palette order
  90. ccounter = [0] * 16
  91.  
  92. # Create the canvas. This represents an 'ideal' C64 image where
  93. # there are no colour restrictions within the blocks
  94. canvas = [[[[0] * 4 for row in range(8)] for ccol in range(40)] for crow in range(25)]
  95.  
  96. # Convert an (r, g, b) value to the nearest C64 palette entry
  97. def rgb2pal(colour):
  98.    
  99.     s = -1
  100.     c = -1
  101.    
  102.     # Random noise dither
  103.     colour = list(colour)
  104.     colour[0] += random.randint(-dither,dither)
  105.     colour[1] += random.randint(-dither,dither)
  106.     colour[2] += random.randint(-dither,dither)
  107.     for x in range(0, len(palette)):
  108.         d = abs(math.sqrt(
  109.             (colour[0] - palette[x][0]) ** 2 +
  110.             (colour[1] - palette[x][1]) ** 2 +
  111.             (colour[2] - palette[x][2]) ** 2
  112.         ))
  113.        
  114.         if s == -1 or d < s:
  115.             s = d
  116.             c = x
  117.    
  118.     return c
  119.  
  120. # Find the nearest C64 palette entry from a list of palette codes
  121. def pal2pal(colour, colours):
  122.    
  123.     s = -1
  124.     c = -1
  125.    
  126.     colour = palette[colour]
  127.    
  128.     for x in range(0, len(colours)):
  129.         d = abs(math.sqrt(
  130.             (colour[0] - palette[colours[x]][0]) ** 2 +
  131.             (colour[1] - palette[colours[x]][1]) ** 2 +
  132.             (colour[2] - palette[colours[x]][2]) ** 2
  133.         ))
  134.        
  135.         if s == -1 or d < s:
  136.             s = d
  137.             c = colours[x]
  138.    
  139.     return c
  140.  
  141.  
  142.  
  143. # Render the PIL image to the canvas
  144. for cy in range(0, 25):
  145.     for cx in range(0, 40):
  146.        
  147.         ox = cx * 4
  148.         oy = cy * 8
  149.        
  150.         for y in range(0, 8):
  151.             for x in range(0, 4):
  152.                
  153.                 p = rgb2pal(im.getpixel((ox + x, oy + y)))
  154.                 canvas[cy][cx][y][x] = p
  155.                 ccounter[p] += 1
  156.  
  157. if background == False:
  158.    
  159.     # Select the background based on the most used colour
  160.     background = ccounter.index(max(ccounter))
  161.     print("Using %s (%d) for background colour" % (palette_names[background], background))
  162.  
  163. else:
  164.    
  165.     print("Background fixed at %s (%d)" % (palette_names[background], background))
  166.  
  167. # Initialise the bitmap buffer
  168. bitmap_bytes = []
  169. screen_bytes = []
  170. colour_bytes = []
  171.  
  172. last_colours = False
  173.  
  174. for cy in range(0, 25):
  175.     for cx in range(0, 40):
  176.        
  177.         # Make a list of all the colours used in this block
  178.         # Background is always used, even if not referenced
  179.         colours = [background]
  180.         ccounter = [0]
  181.        
  182.         for row in canvas[cy][cx]:
  183.             for colour in row:
  184.                
  185.                 if not colour in colours:
  186.                     colours.append(colour)
  187.                     ccounter.append(0)
  188.                
  189.                 ccounter[colours.index(colour)] += 1
  190.        
  191.         # Sort the colours, background first then most used to least
  192.         colours = [background] + list(x[1] for x in sorted(zip(ccounter[1:], colours[1:]), reverse = True))
  193.        
  194.         if len(colours) > 4:
  195.             print("Block %dx%d has too many non-background colours: %s -> %s" % (cx, cy, colours[1:], colours[1:4]))
  196.            
  197.             # Crop colour list to the background and 3 most used values
  198.             colours = colours[:4]
  199.            
  200.             # Replace removed colours in the block with the nearest valid match
  201.             for y in range(8):
  202.                 for x in range(4):
  203.                    
  204.                     p = canvas[cy][cx][y][x]
  205.                    
  206.                     if not p in colours:
  207.                         canvas[cy][cx][y][x] = pal2pal(p, colours)
  208.        
  209.         # If all the colours of this block are present in the previous block,
  210.         # use that same order. This may help with RLE compression.
  211.         if last_colours != False and set(colours).issubset(set(last_colours)):
  212.             colours = last_colours
  213.        
  214.         elif len(colours) != 4:
  215.             colours += [0] * (4 - len(colours))
  216.        
  217.         last_colours = colours
  218.        
  219.         # Output this block
  220.         for row in canvas[cy][cx]:
  221.             bitmap_bytes.append(colours.index(row[0]) << 6 | colours.index(row[1]) << 4 | colours.index(row[2]) << 2 | colours.index(row[3]))
  222.        
  223.         screen_bytes.append(colours[1] << 4 | colours[2])
  224.         colour_bytes.append(colours[3])
  225.  
  226.  
  227. im = Image.new("P", (320, 200), 0)
  228.  
  229. im.putpalette([
  230.     0,0,0,
  231.     255,255,255,
  232.     137,64,54,
  233.     122,191,199,
  234.     138,70,174,
  235.     104,169,65,
  236.     62,49,162,
  237.     208,220,113,
  238.     144,95,37,
  239.     92,71,0,
  240.     187,119,109,
  241.     85,85,85,
  242.     128,128,128,
  243.     172,234,136,
  244.     124,112,218,
  245.     171,171,171])
  246.  
  247. pixels=im.load()
  248.  
  249. for cy in range(0, 25):
  250.     for cx in range(0, 40):
  251.        
  252.         ox = cx * 4
  253.         oy = cy * 8
  254.        
  255.         for y in range(0, 8):
  256.             for x in range(0, 4):
  257.                 pixels[((cx*8)+(x*2),(cy*8)+y)]=canvas[cy][cx][y][x]
  258.                 pixels[((cx*8)+(x*2)+1,(cy*8)+y)]=canvas[cy][cx][y][x]
  259. im.save(oof)
Advertisement
Add Comment
Please, Sign In to add comment