diff --git a/dither.py b/dither.py index a3d310f..e3b2d02 100644 --- a/dither.py +++ b/dither.py @@ -1,32 +1,19 @@ 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 + # XXX RGB values are used as keys in DOTS dict, need to be unique + (False, True, False, True): np.array((126, 126, 125)), # 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 @@ -39,11 +26,39 @@ RGB = { (True, True, True, True): np.array((255, 255, 255)), # White } +NAMES = { + (0, 0, 0): "Black", + (148, 12, 125): "Magenta", + (99, 77, 0): "Brown", + (249, 86, 29): "Orange", + (51, 111, 0): "Dark green", + (126, 126, 125): "Grey1", # XXX + (67, 200, 0): "Green", + (221, 206, 23): "Yellow", + (32, 54, 212): "Dark blue", + (188, 55, 255): "Violet", + (126, 126, 126): "Grey2", + (255, 129, 236): "Pink", + (7, 168, 225): "Med blue", + (158, 172, 255): "Light blue", + (93, 248, 133): "Aqua", + (255, 255, 255): "White" +} -def find_closest_color(pixel): +DOTS = {} +for k, v in RGB.items(): + DOTS[tuple(v)] = k + + +def find_closest_color(pixel, last_pixel, x: int): least_diff = 1e9 best_colour = None - for v in RGB.values(): + + last_dots = DOTS[tuple(last_pixel)] + other_dots = list(last_dots) + other_dots[x % 4] = not other_dots[x % 4] + other_dots = tuple(other_dots) + for v in (RGB[last_dots], RGB[other_dots]): diff = np.sum(np.power(v - np.array(pixel), 2)) if diff < least_diff: least_diff = diff @@ -51,91 +66,52 @@ def find_closest_color(pixel): return best_colour +class Dither: + PATTERN = None + ORIGIN = None + + def apply(self, image, x, y, quant_error): + pattern = self.PATTERN[:Y_RES - y, :X_RES - x] / np.sum(self.PATTERN) + for offset, error_fraction in np.ndenumerate(pattern): + coord = (x + offset[1] - self.ORIGIN[1], y + offset[0] - + self.ORIGIN[0]) + new_pixel = image.getpixel(coord) + error_fraction * quant_error + image.putpixel(coord, tuple(new_pixel.astype(int))) + + +class FloydSteinbergDither(Dither): + # 0 * 7 + # 3 5 1 + PATTERN = np.array(((0, 0, 7), (3, 5, 1))) + ORIGIN = (0, 1) + + +class KennawayDither(Dither): + # 0 * 7 5 3 1 + # 3 5 3 1 1 0 + PATTERN = np.array(((0, 0, 7, 5, 3, 1), (3, 5, 3, 1, 1, 0))) + ORIGIN = (0, 1) + + 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() + + # ditherer = FloydSteinbergDither() + ditherer = KennawayDither() for y in range(Y_RES): print(y) + newpixel = (0, 0, 0) for x in range(X_RES): - # print(x) oldpixel = im.getpixel((x, y)) newpixel = find_closest_color(oldpixel, newpixel, x) - + im.putpixel((x, y), tuple(newpixel)) 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))) + ditherer.apply(im, x, y, quant_error) 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(): @@ -146,10 +122,5 @@ def main(): 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()