diff --git a/dither.py b/dither.py index 486d84a..23d6f79 100644 --- a/dither.py +++ b/dither.py @@ -1,46 +1,60 @@ -# coding: utf-8 +import numpy as np from PIL import Image +import tqdm + img = Image.open('peacock.jpg') resized = img.resize((128,128)) -hex_colors = """#000000 -#1D2B53 -#7E2553 -#008751 -#AB5236 -#5F574F -#C2C3C7 -#FFF1E8 -#FF004D -#FFA300 -#FFEC27 -#00E436 -#29ADFF -#83769C -#FF77A8 -#FFCCAA -#291814 -#111D35 -#422136 -#125359 -#742F29 -#49333B -#A28879 -#F3EF7D -#BE1250 -#FF6C24 -#A8E72E -#00B543 -#065AB5 -#754665 -#FF6E59 -#FF9D81""" -pico_palette_32 = [x.strip() for x in hex_colors.splitlines()] -def hex_to_rgb(h): - dec = lambda x: int(x, base=16) - return (dec(h[1:3]), dec(h[3:5]), dec(h[5:7])) -pico_rgb = [hex_to_rgb(x) for x in pico_palette_32] -import numpy as np +# sRGB values for the PICO-8 palette. +# The first 16 entries correspond with the default palette (0-15). +# The last 16 entries correspond with the "secret" palette (128-143). +PICO_RGB = [ + (0, 0, 0), + (29, 43, 83), + (126, 37, 83), + (0, 135, 81), + (171, 82, 54), + (95, 87, 79), + (194, 195, 199), + (255, 241, 232), + (255, 0, 77), + (255, 163, 0), + (255, 236, 39), + (0, 228, 54), + (41, 173, 255), + (131, 118, 156), + (255, 119, 168), + (255, 204, 170), + (41, 24, 20), + (17, 29, 53), + (66, 33, 54), + (18, 83, 89), + (116, 47, 41), + (73, 51, 59), + (162, 136, 121), + (243, 239, 125), + (190, 18, 80), + (255, 108, 36), + (168, 231, 46), + (0, 181, 67), + (6, 90, 181), + (117, 70, 101), + (255, 110, 89), + (255, 157, 129) +] + +def component_to_linear(x): + x /= 255 + if x <= 0.04045: + return x/12.92 + else: + return ((x + 0.055)/1.055)**2.4 + +def rgb_to_linear(rgb): + return tuple(component_to_linear(c) for c in rgb) + +PICO_RGB_LINEAR = [rgb_to_linear(x) for x in PICO_RGB] + bayer = np.array([[0]]) np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]]) bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]]) @@ -48,7 +62,7 @@ bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]]) bayer = np.block([[4*bayer, 4*bayer+3],[4*bayer+2, 4*bayer+1]]) def dither_pixel(img, xy, pal): global bayer - pal2 = [np.array(list(c)) for c in pal] + pal2 = [np.array(rgb_to_linear(c)) for c in pal] counts = [0]*len(pal) x, y = xy def diff(c1, c2): @@ -56,8 +70,8 @@ def dither_pixel(img, xy, pal): def nearest(rgb): cost, index = min((diff(rgb,c), i) for i,c in enumerate(pal2)) return index - color = np.array(list(img.getpixel(xy))) - err = np.array([0,0,0]) + color = np.array(rgb_to_linear(img.getpixel(xy))) + err = np.array([0.0,0.0,0.0]) for _ in range(64): err += color i = nearest(err) @@ -70,10 +84,13 @@ def dither_pixel(img, xy, pal): return i dithered = Image.new('P', resized.size) -dithered.putpalette([x for rgb in pico_rgb[:16] for x in rgb]) -import qtdm -import tqdm -for x in tqdm.tqdm(range(128)): - for y in range(128): - i = dither_pixel(resized, (x,y), pico_rgb[:16]) - dithered.putpixel((x,y), i) +dithered.putpalette([x for rgb in PICO_RGB[:16] for x in rgb]) +def do_it(): + global dithered, resized + for x in tqdm.tqdm(range(128)): + for y in range(128): + i = dither_pixel(resized, (x,y), PICO_RGB[:16]) + dithered.putpixel((x,y), i) +# import cProfile +# cProfile.run('do_it()') +do_it()