commit 5a6eb08db17fdd0eb3aa0873442cae850d664507 Author: kris Date: Tue Dec 29 18:24:29 2020 +0000 Floyd-Steinberg dithering to DHGR colour palette. Doesn't yet take into account the restrictions on neighbouring colours. diff --git a/dither.py b/dither.py new file mode 100644 index 0000000..a3d310f --- /dev/null +++ b/dither.py @@ -0,0 +1,155 @@ +import argparse +from PIL import Image +from colormath.color_objects import sRGBColor, LabColor +from colormath.color_conversions import convert_color +from colormath import color_diff + +import numpy as np + +X_RES = 560 +Y_RES = 192 + +# for each y from top to bottom +# for each x from left to right +# oldpixel := pixel[x][y] +# newpixel := find_closest_palette_color(oldpixel) +# pixel[x][y] := newpixel +# quant_error := oldpixel - newpixel +# pixel[x+1][y ] := pixel[x+1][y ] + quant_error * 7/16 +# pixel[x-1][y+1] := pixel[x-1][y+1] + quant_error * 3/16 +# pixel[x ][y+1] := pixel[x ][y+1] + quant_error * 5/16 +# pixel[x+1][y+1] := pixel[x+1][y+1] + quant_error * 1/16 + +RGB = { + (False, False, False, False): np.array((0, 0, 0)), # Black + (False, False, False, True): np.array((148, 12, 125)), # Magenta + (False, False, True, False): np.array((99, 77, 0)), # Brown + (False, False, True, True): np.array((249, 86, 29)), # Orange + (False, True, False, False): np.array((51, 111, 0)), # Dark green + (False, True, False, True): np.array((126, 126, 126)), # Grey1 + (False, True, True, False): np.array((67, 200, 0)), # Green + (False, True, True, True): np.array((221, 206, 23)), # Yellow + (True, False, False, False): np.array((32, 54, 212)), # Dark blue + (True, False, False, True): np.array((188, 55, 255)), # Violet + (True, False, True, False): np.array((126, 126, 126)), # Grey2 + (True, False, True, True): np.array((255, 129, 236)), # Pink + (True, True, False, False): np.array((7, 168, 225)), # Med blue + (True, True, False, True): np.array((158, 172, 255)), # Light blue + (True, True, True, False): np.array((93, 248, 133)), # Aqua + (True, True, True, True): np.array((255, 255, 255)), # White +} + + +def find_closest_color(pixel): + least_diff = 1e9 + best_colour = None + for v in RGB.values(): + diff = np.sum(np.power(v - np.array(pixel), 2)) + if diff < least_diff: + least_diff = diff + best_colour = v + return best_colour + + +def dither(filename): + im = Image.open(filename) + if im.mode != "RGB": + im = im.convert("RGB") + im.resize((X_RES, Y_RES), resample=Image.LANCZOS) + im.show() + for y in range(Y_RES): + print(y) + for x in range(X_RES): + # print(x) + oldpixel = im.getpixel((x, y)) + newpixel = find_closest_color(oldpixel, newpixel, x) + + quant_error = oldpixel - newpixel + # print(quant_error) + if x < (X_RES-1): + im.putpixel((x + 1, y), tuple(( + np.array(im.getpixel((x + 1, y))) + quant_error * 7 / + 16).astype(np.int))) + if x > 0 and y < Y_RES-1: + im.putpixel((x - 1, y + 1), + tuple((np.array(im.getpixel( + (x - 1, y + 1))) + quant_error * 3 / + 16).astype(np.int))) + if y < Y_RES-1: + im.putpixel((x, y + 1), + tuple((np.array(im.getpixel( + (x, y + 1)) + quant_error * 5 / 16)).astype( + np.int))) + if x < (X_RES-1) and y < (Y_RES-1): + im.putpixel((x + 1, y + 1), + tuple((np.array(im.getpixel( + (x + 1, y + 1)) + quant_error * 1 / + 16)).astype(np.int))) + im.show() + # + # c = {} + # for value in True, False: + # if value: + # s.set(x, y) + # else: + # s.unset(x, y) + # + # c[value] = convert_color( + # sRGBColor(*s.colours(x, y)[0], is_upscaled=True), + # LabColor) + # + # diffs = [ + # ( + # color_diff.delta_e_cie2000(oldpixel, newpixel), + # newpixel, + # value + # ) + # for value, newpixel in c.items()] + # + # print(diffs) + # diff, newpixel, value = min(diffs) + # if value: + # s.set(x, y) + # else: + # s.unset(x, y) + # + # put(im, (x, y), np.array(newpixel.get_value_tuple())) + # yield x, y, value + # print(oldpixel, newpixel) + # quant_error = np.array(oldpixel.get_value_tuple()) - np.array( + # newpixel.get_value_tuple()) + # print("qe = %s" % quant_error) + # + # if x < (screen.X_RES - 1): + # nr = (np.array(im.getpixel((x + 1, y)), dtype=np.float) / 256 + + # quant_error * 7 / 16) + # print(nr * 256) + # put(im, (x + 1, y), nr) + # print(im.getpixel((x+1, y))) + # if y < (screen.Y_RES - 1): + # put(im, (x - 1, y + 1), + # np.array(im.getpixel((x - 1, y + 1)), dtype=np.float) / 256 + + # quant_error * 3 / 16) + # put(im, (x, y + 1), + # np.array(im.getpixel((x, y + 1)), dtype=np.float) / 256 + + # quant_error * 5 / 16) + # put(im, (x + 1, y + 1), + # np.array(im.getpixel((x + 1, y + 1)), dtype=np.float) / 256 + + # quant_error * 1 / 16) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("input", type=str, help="Input file to process") + + args = parser.parse_args() + dither(args.input) + + +# +# def put(image, xy, lab_value): +# # print(lab_value) +# image.putpixel(xy, tuple((lab_value * 256).astype(int))) + +if __name__ == "__main__": + main()