From 65a9b1cf371f1446111ca53228400d9800f58861 Mon Sep 17 00:00:00 2001 From: sloumdrone Date: Mon, 3 Jun 2019 22:06:58 -0700 Subject: [PATCH] Updated algorithm options --- lid | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 17 deletions(-) diff --git a/lid b/lid index 17e01a9..8cafefd 100755 --- a/lid +++ b/lid @@ -8,8 +8,8 @@ from PIL import Image import sys import os.path -# Main program to output the new image -def dither(r): +# Default dithering: 4 level ordered dither +def ordered_dither_4(r): im = Image.open(r["path"]).convert("L") width, height = im.size new = create_image(width, height) @@ -20,26 +20,128 @@ def dither(r): [192, 0] ] - count = 0 - print(" dithering...\033[?25l", end="") + total_px_count = width * height + counter = 1 + last_percent_val = 0 + last_hash_val = 0 + print("Ordered dither: 4 level") for row in range(0, height): - if not row % 7: - print_spinner(count) - count += 1 - if count > 3: - count = 0 for col in range(0, width): dotrow = 1 if row % 2 else 0 dotcol = 1 if col % 2 else 0 impx = get_pixel(im, col, row) newpixels[col, row] = int(impx > dots[dotrow][dotcol]) + counter += 1 + new_percent_val = round(counter / total_px_count * 100) + new_hash_val = round(counter / total_px_count * 10) + if new_percent_val != last_percent_val or new_hash_val != last_hash_val: + print("\r{:>3}% |{:<10}|".format(new_percent_val, "#" * new_hash_val), end="") + last_percent_val = new_percent_val + last_hash_val = new_hash_val - new.save("{}.{}".format(r["out"], r["-f"]), r["-f"].upper(), optimize=True, quality=25) + new.save("{}.{}".format(r["out"], r["-f"]), r["-f"].upper(), optimize=True, quality=r["-q"]) print("\033[20C\nfinished\033[?25h") +# 9 level ordered dither +def ordered_dither_9(r): + im = Image.open(r["path"]).convert("L") + width, height = im.size + new = create_image(width, height) + newpixels = new.load() + + dots = [ + [0, 196, 84], + [168, 140, 56], + [112, 28, 224] + ] + + total_px_count = width * height + counter = 1 + last_percent_val = 0 + last_hash_val = 0 + print("Ordered dither: 9 level") + for row in range(0, height): + for col in range(0, width): + if not row % 3: + dotrow = 2 + elif not row % 2: + dotrow = 1 + else: + dotrow = 0 + + if not col % 3: + dotcol = 2 + elif not col % 2: + dotcol = 1 + else: + dotcol = 0 + impx = get_pixel(im, col, row) + newpixels[col, row] = int(impx > dots[dotrow][dotcol]) + counter += 1 + new_percent_val = round(counter / total_px_count * 100) + new_hash_val = round(counter / total_px_count * 10) + if new_percent_val != last_percent_val or new_hash_val != last_hash_val: + print("\r{:>3}% |{:<10}|".format(new_percent_val, "#" * new_hash_val), end="") + last_percent_val = new_percent_val + last_hash_val = new_hash_val + + new.save("{}.{}".format(r["out"], r["-f"]), r["-f"].upper(), optimize=True, quality=r["-q"]) + print("\033[20C\nfinished\033[?25h") + +# Alternate dither mode +def error_dither(r): + im = Image.open(r["path"]).convert("L") + width, height = im.size + new = create_image(width, height) + newpixels = new.load() + i = im.load() + + total_px_count = width * height + counter = 1 + last_percent_val = 0 + last_hash_val = 0 + print("Error diffusion dither") + for row in range(0, height): + for col in range(0, width): + newpixels[col, row] = update_error(im, i, row, col) + counter += 1 + new_percent_val = round(counter / total_px_count * 100) + new_hash_val = round(counter / total_px_count * 10) + if new_percent_val != last_percent_val or new_hash_val != last_hash_val: + print("\r{:>3}% |{:<10}|".format(new_percent_val, "#" * new_hash_val), end="") + last_percent_val = new_percent_val + last_hash_val = new_hash_val + + new.save("{}.{}".format(r["out"], r["-f"]), r["-f"].upper(), optimize=True, quality=r["-q"]) + print("\033[20C\nfinished\033[?25h") + + +def update_error(im, i, r, c): + w, h = im.size + current = get_pixel(im, c, r) + if current > 128: + res = 1 + diff = -(255 - current) + else: + res = 0 + diff = abs(0 - current) + mov = [[0, 1, 0.4375],[1, 1, 0.0625],[1, 0, 0.3125],[1, -1, 0.1875]] + for x in mov: + if r + x[0] >= h or c + x[1] >= w or c + x[1] <= 0: + continue + p = get_pixel(im, c + x[1], r + x[0]) + p = round(diff * x[2] + p) + if p < 0: + p = 0 + elif p > 255: + p = 255 + i[c + x[1], r + x[0]] = p + return res + + + def print_spinner(n): - parts = ["- "," - "," - "," -"] - print("\r{}".format(parts[n]), end="") + print("\r|{:<10}|".format("#" * n), end="") # Create a new image with the given size def create_image(i, j): @@ -66,15 +168,16 @@ def display_help(): lid [option]... [infile] [outfile] options: - -f output format. defaults to the recommended setting: png - -q image quality. defaults to 90. only has effect on png/jpg + -f output format. defaults to: png + -q image quality. defaults to: 90. only has effect on png/jpg + -m dither mode. defaults to: o4. other options: e, o9 arguments: infile path to a valid image file on your system outfile file name to output, do not include file extension example usage: - lid -f jpeg -q 75 ~/my_picture.png my_dithered_picture + lid -f jpeg -q 75 -m e ~/my_picture.png my_dithered_picture """ print(helptext) @@ -85,7 +188,8 @@ def parse_input(): args = sys.argv[1:] argmap = { "-f": "png", # output format - "-q": 90, # optimization quality + "-q": 25, # optimization quality + "-m": "o4", # mode, defaults to ordered "path": None, # path to file "out": "lid_file" # output filename } @@ -120,6 +224,10 @@ def parse_input(): elif argmap["-f"] == "jpg": argmap["-f"] = "jpeg" + if not argmap["-m"] in ["o4", "e", "o9"]: + print("lid error: invalid dither mode provided for -m. Options: o4, o9, e. Default: o4") + return None + try: argmap["-q"] = int(argmap["-q"]) if argmap["-q"] < 1 or argmap["-q"] > 100: @@ -138,6 +246,14 @@ def parse_input(): if __name__ == "__main__": request = parse_input() if request: - dither(request) + if request["-m"] == "o4": + ordered_dither_4(request) + elif request["-m"] == "o9": + ordered_dither_9(request) + elif request["-m"] == "e": + error_dither(request) + else: + print("Unknown mode flag") + sys.exit(2) sys.exit(0) sys.exit(1)