Created
February 10, 2022 09:28
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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