diff --git a/2bit.py b/2bit.py index 0ca02f5..7b94bc0 100644 --- a/2bit.py +++ b/2bit.py @@ -2,21 +2,38 @@ bits = 2 outfile = "out.png" per_color = False dither = False +use_non_random_dither = False # End of the configuration section import PIL.Image, sys, random import pip._vendor.progress.bar as libbar +def format_dither(d): + d = float(int(float(dither) * 1000)) / 10 + return str(d) + "%" +def auto_dither(bits): + return 1.0 / (bits + 1) if len(sys.argv) >= 3: outfile = sys.argv[2] if len(sys.argv) >= 4: bits = int(sys.argv[3]) args = [f.lower() for f in sys.argv] if "--dither" in args: - dither = float(args[args.index("--dither") + 1].replace("%","")) + dither = args[args.index("--dither") + 1].replace("%","") + if dither == "auto": + dither = auto_dither(bits) + print("Using dither of " + format_dither(dither)) + else: + dither = float(dither) if dither > 1: dither /= 100 per_color = "--per-color" in args +use_non_random_dither = "--non-random-dither" in args +if use_non_random_dither: + counter_max = int(args[args.index("--non-random-dither") + 1]) + if not dither: + dither = auto_dither(bits) + print("Non-random dither has no effect if dither is disabled. Guessing you want "+format_dither(dither)+"% dither") def binprecision(start, bits, length): end = bin(int(start))[2:] while len(end) < bits: @@ -33,13 +50,20 @@ colors = [int(i*255.0/(2**bits-1)) for i in range(2**bits)] bar = libbar.IncrementalBar(max = image.height * image.width) bar.start() i = 0 +if use_non_random_dither: + counter = 1 for x in range(image.width): for y in range(image.height): i += 1 + if use_non_random_dither: + counter += 1 + counter %= counter_max + 1 bar.index = i if i % 193 == 0: bar.update() # I used to use 200 but then the last two digits of the current status were always "00" which made it look like the progress bar was fake pos = (x,y) color = image.getpixel(pos) + if type(color) == int: + color = (color,) if len(color) == 4: color = color[:3] # Exclude alpha layer color = list(color) @@ -47,7 +71,13 @@ for x in range(image.width): color = [float(sum(color))/len(color)] for z in range(len(color)): if dither: - color[z] += 255 * random.uniform(-dither,dither) + val = 255 * random.uniform(-dither, dither) + if use_non_random_dither: + val = 255*float(counter)/counter_max # goes from 0 to 255 + val *= dither # for dither = .1 and counter max = 4 it goes from 0 to 25.5 + val -= 255 * dither / 2 # for those values it goes from -12.75 to 12.75 + val = int(val) + color[z] += val color[z] = min(color[z], 255) color[z] = max(color[z], 0) index = int(binprecision(color[z], 8, bits), 2) diff --git a/README.md b/README.md index 36e72d5..928d2f2 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ Inspired by [2bit](http://2bit.neocities.org/) -Usage: `python3 2bit.py infile.png [outfile.png] [bits] [--per-color] [--dither dither_value]` +Usage: `python3 2bit.py infile.png [outfile.png] [bits] [--per-color] [--dither dither_value] [--non-random-dither num_pixels]` dither\_value can be like `50%`, `50` or `.5` +Note that using `--non-random-dither` without a dither argument will attempt to guess what dither percentage you want based on the number of bits + +The best number for num\_pixels is usually 7, but it should ideally be coprime with the height of the image + Warning: Will probably turn transparency black You can change the number of bits for different results. 1 would be just black and white, while 8 would be the original image in greyscale. diff --git a/test/run.py b/test/run.py index 9bf9b8e..57f3693 100644 --- a/test/run.py +++ b/test/run.py @@ -5,44 +5,44 @@ try: except: pass files = glob.glob("*") +del files[files.index("gentest.py")] del files[files.index("run.py")] if not os.path.isdir("out"): os.mkdir("out") i = 0 -max_i = len(files) * (16*3) - -def getparams(i): - percolor = dither = False - i %= 48 - if i % 16 >= 8: - percolor = True - if i >= 16: - dither = '15' - if i >= 32: - dither = '50' - return percolor, dither - +max_i = len(files) * (112) +def run(bits, percolor, dither, nonrandom): + if not percolor: + percolor = [] + pc = "" + else: + percolor = ["--per-color"] + pc = "per-color-" + if not dither: + dither = [] + dc = "" + else: + dc = "dither-" + dither + "%-" + dither = ["--dither", dither] + if not nonrandom: + nonrandom = [] + nc = "" + else: + nc = "nonrandom-dither-" + nonrandom + "pixels-" + nonrandom = ["--non-random-dither", nonrandom] + bits_formatted = str(bits + 1) + outfilename = "out/" + ".".join(f.split(".")[:-1]) + "-output-" + pc + dc + nc + bits_formatted + "bits.png" + print("On " + outfilename) + subprocess.call(["python3", "../2bit.py", f, outfilename, bits_formatted, *percolor, *dither, *nonrandom]) +print(str(max_i) + " images to generate") for f in files: if (os.path.isdir(f)): continue if (f[-3:]) == '.py': continue - for bits in range(16*3): - bits += 1 - percolor, dither = getparams(i) - if not percolor: - percolor = [] - pc = "" - else: - percolor = ["--per-color"] - pc = "per-color-" - if not dither: - dither = [] - dc = "" - else: - dc = "dither-" + dither + "%-" - dither = ["--dither", dither] - print(str(int(float(1000*i)/max_i)/10) + "% done") - bits_formatted = str((bits-1)%8+1) - outfilename = "out/" + ".".join(f.split(".")[:-1]) + "-output-" + pc + dc + bits_formatted + "bits.png" - print("On " + outfilename) - subprocess.call(["python3", "../2bit.py", f, outfilename, bits_formatted, *percolor, *dither]) - i += 1 + for bits in range(8): + for dither in [False, "15", "50", "auto"]: + for nonrandom in [False, "7", "10"]: + for percolor in [False, True]: + i += 1 + run(bits, percolor, dither, nonrandom) + print(str(int(float(1000*i)/max_i)/10) + "% done") + print("Done")