Skip to content

Instantly share code, notes, and snippets.

@nikhilr612
Created February 10, 2022 09:28
Show Gist options
  • Save nikhilr612/ff210dfb08ad7edc135149e1606d658f to your computer and use it in GitHub Desktop.
Save nikhilr612/ff210dfb08ad7edc135149e1606d658f to your computer and use it in GitHub Desktop.
Menu-based program to display images in a console using character control characters to change foreground colours.
import os
import sys
import time
from PIL import Image
import ctypes
h_stdout = ctypes.windll.kernel32.GetStdHandle(ctypes.c_long(-11));
ctypes.windll.kernel32.SetConsoleMode(h_stdout, ctypes.c_short(0x200));
def _win_mov_cursor(x, y):
ctypes.windll.kernel32.SetConsoleCursorPosition(h_stdout, ctypes.c_ulong((y << 16) | x));
def _win_print_at(x,y, ch):
_win_mov_cursor(x,y);
ch = ch.encode('utf-16');
ctypes.windll.kernel32.WriteConsoleA(h_stdout, ctypes.c_char_p(ch), len(ch), None, None);
def _win32_drawframe(im, back, dispose=True):
if not back:
_win_mov_cursor(0,0);
drawimg(im, dispose);
else:
drawimg(im, dispose, reverse=True);
vals = {0:0, 95:1, 128:-1, 135:2, 175:3, 215:4, 255:5};
colmap = {
(0,0,0): 0,
(128,0,0): 1,
(0,128,0): 2,
(128,128,0): 3,
(0,0,128): 4,
(128,0,128): 5,
(0,128,128): 6,
(192,192,192): 7,
(128,128,128): 8,
(255,0,0): 9,
(0,255,0): 10,
(255, 255, 0): 11,
(0,0,255): 12,
(255,0,255): 13,
(0, 255, 255): 14,
(255,255,255): 15
};
def luma_fromrgb(col):
r,g,b = col;
return (3*r+b+4*g)/8;
def loadimg(fname, convert=True):
try:
img = Image.open(fname);
except:
print("Failed to open file: ", fname);
return None
if convert:
return img.convert('RGB');
else:
return img;
def reset():
print('\u001b[38;5;7m');
def gscolcode(col):
ncol = (col[0]/255, col[1]/255, col[2]/255);
lma = luma_fromrgb(ncol);
#print("col:", ncol, "luma:", lma);
if(lma == 0):
return 0;
elif(lma == 1):
return 15;
else:
return 232 + round(23*lma);
def rval(v):
last,prev = 0,0;
for k in vals.keys():
prev, last = last, k;
if k >= v:
break;
if(abs(v-last) < abs(v-prev)):
return last;
else:
return prev;
def recal(col, tval):
lv = [];
for c in col:
if(c == 128):
if abs(c - 95) < abs(c - 135):
c = 95;
else:
c = 135;
lv.append(c);
return lv;
def rgbcode(npix):
rc = (rval(npix[0]), rval(npix[1]), rval(npix[2]));
#print("Closest colour for {} is {}".format(npix, rc));
if rc in colmap:
return colmap[rc];
else:
rc = recal(rc, npix);
return 16 + 36*vals[rc[0]] + 6*vals[rc[1]] + vals[rc[2]];
def putchar(ch, col, f=None):
s = '\u001b[38;5;{code}m'.format(code=col);
print(s, end='');
print(ch, end='');
if f != None:
f.write(s);
f.write(ch);
def _sysinit():
if(os.name.startswith('nt')):
os.system('color');
os.system('cls');
os.clrscr = 'cls';
else:
os.system('clear');
os.clrscr = 'clear';
def drawimg(image, dispose=True, greyscale=False, reverse=False, table=None, file=None):
f = gscolcode if greyscale else rgbcode;
pc, pv = None, 0;
if file != None:
file = open(file, 'w', encoding='utf-8');
start = time.time_ns();
for y in range(image.size[1]):
if reverse:
y = image.size[1]+(~y);
_win_mov_cursor(0,y);
for x in range(image.size[0]):
c = image.getpixel((x,y));
if c != pc:
pc = c;
if table != None and pc in table:
pv = table[pc];
else:
pv = f(c);
if table != None:
table[pc] = pv;
putchar('\u2588\u2588', pv, f=file);
print()
if file != None:
file.write("\n");
print("draw time: ", (time.time_ns() - start));
reset();
if(dispose):
if file != None:
file.close();
image.close();
def rendergif_normal(image):
lp = image.info.get('loop', 1);
lp = 0xffff if lp == 0 else lp;
for _ in range(lp):
for i in range(image.n_frames):
#os.system(os.clrscr);
_win_mov_cursor(0,0);
start = time.time_ns();
image.seek(i);
mspf = image.info.get('duration',100);
drawimg(image.convert('RGB'));
end = time.time_ns();
elapsed = (end - start) / 10_000_000;
time.sleep(max(mspf - elapsed, 0)/1_000);
def rendergif_weird(image):
lp = image.info.get('loop', 1);
lp = 0xffff if lp == 0 else lp;
print(image);
for _ in range(lp):
for i in range(image.n_frames):
start = time.time_ns();
image.seek(i);
mspf = image.info.get('duration',100);
_win32_drawframe(image.convert('RGB'), i & 1);
end = time.time_ns();
elapsed = (end - start) / 10_000_000;
to_wait = max(mspf - elapsed, 0);
#print("elapsed:", elapsed, "waiting:", to_wait, "mspf:", mspf);
time.sleep(to_wait/1_00);
def main():
_sysinit();
fname = input("Enter filename:");
print("Options:\n1. Draw Still, Greyscale\n2. Draw Still, Colour\n3. Draw Still, Reduced Palette\n4. Render Animated GIF (Colour)\n5. Export Still, Reduced Palette\n");
ch = input("Choice:")[0];
image = loadimg(fname) if ch != '5'else None;
d = {
'1': lambda: drawimg(image, greyscale=True),
'2': lambda: drawimg(image),
'3': lambda: drawimg(image, table={}),
'4': lambda: rendergif_weird(loadimg(fname, convert=False)),
'5': lambda: drawimg(image, table={}, file=input("Enter file name:"))
};
d.get(ch, lambda: print("Invalid option"))();
if __name__ == '__main__':
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment