|
| 1 | +from bisect import bisect_left |
| 2 | +from functools import partial |
| 3 | +import numpy as np |
| 4 | +from PIL import Image, ImageDraw, ImageFont |
| 5 | +import string |
| 6 | + |
| 7 | + |
| 8 | +# Trim off non space invisibles |
| 9 | +ACCEPTABLE_CHARS = string.printable[:-5] |
| 10 | +GRID_X = 8 |
| 11 | +GRID_Y = 8 |
| 12 | + |
| 13 | + |
| 14 | +def classify_chars(): |
| 15 | + char_dict = {} |
| 16 | + for c in ACCEPTABLE_CHARS: |
| 17 | + img = Image.new('L', (GRID_X, GRID_Y), (255)) |
| 18 | + draw = ImageDraw.Draw(img) |
| 19 | + font = ImageFont.truetype("FreeMono.ttf", 8) |
| 20 | + draw.text((0, 0), c, (0), font=font) |
| 21 | + char_dict[luminescence(img)] = c |
| 22 | + normalised_keys = [(char_dict[k], n * (255/len(char_dict))) for n, k in enumerate(sorted(char_dict))] |
| 23 | + normalised_char_dict = {nk[1]: nk[0] for nk in normalised_keys} |
| 24 | + return normalised_char_dict |
| 25 | + |
| 26 | + |
| 27 | +def nearest_match(iterable, f): |
| 28 | + """ Returns the key for the character closest to the given float `f`'s luminescence """ |
| 29 | + pos = bisect_left(iterable, f) |
| 30 | + if pos == 0: |
| 31 | + return iterable[0] |
| 32 | + if pos == len(iterable): |
| 33 | + return iterable[-1] |
| 34 | + before = iterable[pos - 1] |
| 35 | + after = iterable[pos] |
| 36 | + if after - f < f - before: |
| 37 | + return after |
| 38 | + else: |
| 39 | + return before |
| 40 | + |
| 41 | + |
| 42 | +def greyscale(file_path): |
| 43 | + """ Returns a greyscale version of the image at `file_path` |
| 44 | +
|
| 45 | + Parameters |
| 46 | + ---------- |
| 47 | + file_path: str |
| 48 | + path to the image file under operation |
| 49 | +
|
| 50 | + Returns |
| 51 | + ------- |
| 52 | + PIL.Image |
| 53 | +
|
| 54 | + """ |
| 55 | + return Image.open(file_path).convert('L') |
| 56 | + |
| 57 | + |
| 58 | +def char_to_image(char): |
| 59 | + img = Image.new('L', (GRID_X, GRID_Y)) |
| 60 | + d = ImageDraw.Draw(img) |
| 61 | + d.text((GRID_X, GRID_Y), char) |
| 62 | + return img |
| 63 | + |
| 64 | + |
| 65 | +def image_to_tiles(i): |
| 66 | + """ Given a PIL.Image returns a list of tiles conforming to the global x, y values. |
| 67 | + |
| 68 | + THIS IS WHAT WE DID AT THE DOJO BUT IT DOESN'T WORK SO WELL! |
| 69 | +
|
| 70 | + Parameters |
| 71 | + ---------- |
| 72 | + PIL.Image |
| 73 | + The image we will break up. |
| 74 | +
|
| 75 | + Returns |
| 76 | + ------- |
| 77 | + list |
| 78 | + A list of PIL.Image objects interspersed with '\n' characters to indicate line breaks |
| 79 | +
|
| 80 | + """ |
| 81 | + width, height = i.size |
| 82 | + range_x = width // GRID_X |
| 83 | + range_y = width // GRID_Y |
| 84 | + values = [] |
| 85 | + for y in range(range_y): |
| 86 | + for x in range(range_x): |
| 87 | + bounding = (x * GRID_X, y * GRID_Y, x * |
| 88 | + GRID_X + GRID_X, y * GRID_Y + GRID_Y) |
| 89 | + tile = i.crop(bounding) |
| 90 | + values.append(tile) |
| 91 | + values.append('\n') |
| 92 | + return values |
| 93 | + |
| 94 | + |
| 95 | +def luminescence(i): |
| 96 | + """ THIS IS WHAT WE DID AT THE DOJO BUT IT DOESN'T WORK SO WELL! """ |
| 97 | + divisor = GRID_X * GRID_Y |
| 98 | + total = 0 |
| 99 | + for x in range(GRID_X): |
| 100 | + for y in range(GRID_Y): |
| 101 | + total += i.getpixel((x, y)) |
| 102 | + return total / divisor |
| 103 | + |
| 104 | + |
| 105 | +def main(path_to_image, max_width): |
| 106 | + """ Added after dojo as we didn't finish. The numpy stuff is new after seeing that |
| 107 | + our approach didn't give great images """ |
| 108 | + ### Assign the characters to greyscale values |
| 109 | + char_dict = classify_chars() |
| 110 | + char_keys = [key for key in char_dict] |
| 111 | + char_keys.sort() |
| 112 | + ### End assign the characters to greyscale values |
| 113 | + ### Manipulate the image |
| 114 | + gscale = greyscale(path_to_image) |
| 115 | + width_scale = (max_width/(float(gscale.size[0]))) |
| 116 | + horizontal_scale = int((float(gscale.size[1])*float(width_scale))) |
| 117 | + gscale = gscale.resize((max_width, horizontal_scale), Image.ANTIALIAS) |
| 118 | + ### End manipulate the image |
| 119 | + ### Numpy it! |
| 120 | + image_array = np.array(gscale) |
| 121 | + to_float_keys = partial(nearest_match, char_keys) |
| 122 | + f = np.vectorize(to_float_keys) |
| 123 | + image_array = f(image_array) |
| 124 | + to_chars = lambda c: char_dict[c] |
| 125 | + g = np.vectorize(to_chars) |
| 126 | + image_array = g(image_array) |
| 127 | + ### End numpy it! |
| 128 | + print('\n'.join([''.join(row) for row in image_array])) |
0 commit comments