Droider & Minimal
I just wrote a script that turns any photo into a pixel art grid that updates with every keystroke. Wanna see if the order holds up against random changes?
Sure, send it over. Just keep the grid perfectly aligned—any slip and the whole pattern becomes a mystery to solve.
import sys
from PIL import Image
def to_ascii(img, width=80, chars="@%#*+=-:. "):
# Resize preserving aspect ratio
w, h = img.size
aspect = h / w
new_w = width
new_h = int(aspect * new_w * 0.55) # adjust for font ratio
img = img.resize((new_w, new_h))
img = img.convert('L')
pixels = img.getdata()
ascii_str = ""
for i, p in enumerate(pixels):
ascii_str += chars[p // 25]
if (i + 1) % new_w == 0:
ascii_str += "\n"
return ascii_str
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python ascii_art.py <image_path>")
sys.exit(1)
path = sys.argv[1]
try:
img = Image.open(path)
except Exception as e:
print("Error opening image:", e)
sys.exit(1)
ascii_art = to_ascii(img)
print(ascii_art)
That looks clean enough. Just make sure the spacing in the character set matches the grid you expect; any uneven character width will throw off the alignment. Also, if you want the keystroke updates to be smooth, keep the image size constant so the refresh rate stays predictable.
import sys, os, curses, time
from PIL import Image
# Keep everything the same size
WIDTH = 80
HEIGHT = 40
CHARS = "@%#*+=-:. "
def render_ascii(img):
# Resize once to keep size constant
img = img.resize((WIDTH, HEIGHT))
img = img.convert('L')
ascii = ""
for y in range(HEIGHT):
for x in range(WIDTH):
p = img.getpixel((x, y))
ascii += CHARS[p // 25]
ascii += "\n"
return ascii
def main(stdscr, path):
curses.curs_set(0)
stdscr.nodelay(True)
img = Image.open(path)
ascii_art = render_ascii(img)
while True:
stdscr.clear()
stdscr.addstr(0, 0, ascii_art)
stdscr.refresh()
c = stdscr.getch()
if c != -1:
if c in (ord('q'), 27): # q or esc to quit
break
time.sleep(0.05)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python keystroke_ascii.py <image_path>")
sys.exit(1)
curses.wrapper(main, sys.argv[1])