Skip to content

Instantly share code, notes, and snippets.

@larshb
Last active December 25, 2018 18:12
Show Gist options
  • Save larshb/bd4d48bb65443f499b5d3ce36b64bb1c to your computer and use it in GitHub Desktop.
Save larshb/bd4d48bb65443f499b5d3ce36b64bb1c to your computer and use it in GitHub Desktop.
Convert bitmap images to ASCII-art
from struct import *
class Pixel:
def __init__(self, byte):
self.r = byte[0]
self.g = byte[1]
self.b = byte[2]
def bytestr(self, x):
return "{0:0{1}X}".format(x,2)
def __repr__(self):
return "#" + self.bytestr(self.r) + \
self.bytestr(self.g) + \
self.bytestr(self.b)
def __str__(self):
return str([hex(self.r), hex(self.g), hex(self.b)])
class Bitmap:
def __init__(self, filename):
try:
# Read file
raw = open(filename, "rb").read()
# Unpack header
ID, self.size, _, _, offset, DIB_size, \
self.width, self.height, color_planes, bits_per_pixel, \
pixel_array_compression, self.data_size, \
print_res_h, print_res_w, \
palette_colors, important_colors \
= unpack("=HIHHIIIIHHIIIIII", raw[0:0x36])
# Validate format
assert( ID == 0x4d42 \
and offset == 54 \
and DIB_size == 40 \
and color_planes == 1 \
and bits_per_pixel == 24 \
and pixel_array_compression == 0 \
and palette_colors == 0 \
and important_colors == 0)
# Parse pixel data
self.lines = []
raw_data = raw[offset:]
bytes_per_pixel = int(bits_per_pixel/8)
padding_bytes = self.width*bytes_per_pixel%4
for l in range(self.height):
start = l*padding_bytes + l*self.width*bytes_per_pixel
stop = start+self.width*bytes_per_pixel
self.lines.append([Pixel(raw_data[x:x+bytes_per_pixel]) for x in range(start, stop, bytes_per_pixel)]);
self.lines[0][0].r = 0xa
except AssertionError:
print("Unsupported format!")
raise
from bitmap import *
from subprocess import call
import sys
class Parameters:
def __init__(self):
try:
self.zoom = 4
self.brightness = 0
self.contrast = 1
self.invert = 0
self.open_after = 0
assert(len(sys.argv)%2 == 0)
for i in range(2, len(sys.argv), 2):
arg = sys.argv[i]
a = arg[1]
print(a)
val = int(sys.argv[i+1])
if arg[0] != '-' or len(arg) < 2:
raise Exception("Invalid argument: " + arg)
elif a == 'z': self.zoom = val
elif a == 'b': self.brightness = val
elif a == 'c': self.contrast = val
elif a == 'i': self.invert = val
elif a == 'o': self.open_after = val
else:
raise Exception("Invalid argument: " + arg)
except Exception as e:
print(e)
print()
print("Arg Description Default")
print("---+---------------------+-------")
print(" -b Brightness ", self.brightness)
print(" -c Contrast ", self.contrast)
print(" -i Invert ", self.invert)
print(" -z Zoom ", self.zoom)
print(" -o Open after convertion", self.open_after)
raise
open_output_file = 0
try:
# Check input arguments
p = Parameters()
bmp = Bitmap(sys.argv[1])
wzoom = int(p.zoom/3*2)
invert_factor = -1 if p.invert else 1
symbols = ["X","Y","x","y","+","-",",","-"," "]
thresholds = [ 10, 10, 5, 10, 5, 10, 5, 5]
top = 200
f = open("output.txt", "w")
for y in reversed(range(0, bmp.height-p.zoom, p.zoom)):
line = bmp.lines[y]
str = ""
for i in range(0, bmp.width-wzoom, wzoom):
v = 0
s = " "
for j in range(wzoom):
v += line[i+j].r
vt = top
for k in range(len(thresholds)):
if v > wzoom*vt*p.contrast+wzoom*p.brightness:
s=symbols[invert_factor*k-p.invert]
break
vt-=thresholds[k]
str+=s
print(str)
f.write(str)
f.write("\n")
f.close()
if p.open_after == 1:
call("start output.txt", shell=True)
except FileNotFoundError:
print("Please enter a valid filename")
except:
print("\nUh oh, something is wrong")
#print(bmp.data)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment