Skip to content

Instantly share code, notes, and snippets.

@daknuett
Created February 15, 2017 21:50
Show Gist options
  • Save daknuett/48656e0bdbaad7eaf03fe06764cc7aa5 to your computer and use it in GitHub Desktop.
Save daknuett/48656e0bdbaad7eaf03fe06764cc7aa5 to your computer and use it in GitHub Desktop.
Convert Images to text
import shutil, math, sys
import matplotlib.image
import numpy as np
import matplotlib.pyplot as plt
#
# Copyright(c) 2017 Daniel Knüttel
#
# This program is free software.
# Anyways if you think this program is worth it
# and we meet shout a drink for me.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen
# der GNU Affero General Public License, wie von der Free Software Foundation,
# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
# veröffentlichten Version, weiterverbreiten und/oder modifizieren.
#
# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber
# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
# Siehe die GNU Affero General Public License für weitere Details.
#
# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem
# Programm erhalten haben. Wenn nicht, siehe <http://www.gnu.org/licenses/>.
"""
Convert images to text. Some kind of ascii art.
This is just a quick hack. do not expect it to
perform miracles.
The worst thing is to_grey. I should use a real library for this.
"""
localdiff_pixels = {\
" ": 0.0,
".": 0.3,
"-": 0.5,
";": 0.6,
"+": 0.7,
"/": 0.8,
"1": 0.9,
"2": 1.0,
"3": 1.2,
"9": 1.3,
"B": 1.4,
"8": 1.5,
"#": 1.6
}
normal_pixels = {\
" ": 0.0,
".": 0.1,
"-": 0.2,
"!": 0.3,
"+": 0.4,
"1": 0.5,
"D": 0.6,
"2": 0.7,
"3": 0.8,
"9": 0.9,
"B": 1.0,
"8": 1.25,
"#": 1.5
}
def to_grey(image):
return [[ sum(pixel)/len(pixel) for pixel in row] for row in image]
def to_buckets(image, height, width):
bucket_width_size = len(image[0]) // width
bucket_height_size = len(image) // height
new_image = np.zeros((height, width))
for i in range(height):
for j in range(width):
sector = [row[j * bucket_width_size: j * bucket_width_size + bucket_width_size]
for row in image[i * bucket_height_size: i * bucket_height_size + bucket_height_size]]
value = sum([sum(row) for row in sector]) / (bucket_width_size * bucket_height_size)
new_image[i,j] = value
return new_image
# negative slice indices do not work :-(
def slice_workaround(obj, x1, x2):
obj = list(obj)
if(x1 > 0):
return obj[x1:x2]
end = len(obj)
part1 = obj[end + x1:]
part2 = obj[:x2]
return part1 + part2
def D2_slice_workaround(obj, x1, x2, y1, y2):
sector = slice_workaround(obj, x1, x2)
result = [slice_workaround(row, y1, y2) for row in sector]
return result
def high_contrast(image, cluster_size_x = 6, cluster_size_y = 5):
new_image = [[None for i in j] for j in image]
for x in range(-2 * cluster_size_x, len(image) - cluster_size_x):
for y in range(-2 * cluster_size_y, len(image[x]) - cluster_size_y):
sector = D2_slice_workaround(image,
x, x + cluster_size_x * 2,
y, y + cluster_size_y * 2) # get a small sector
# calculate the brigthness of the center pixel
avg = sum([sum(row) for row in sector]) / (len(sector) * len(sector[0]))
value = image[x,y]
for pixel in sorted(list(localdiff_pixels.items()), key = lambda x: x[1]):
if(pixel[1] * avg >= value):
new_image[x + cluster_size_x][y + cluster_size_y] = pixel[0]
break
if(new_image[x + cluster_size_x][y + cluster_size_y] == None):
new_image[x + cluster_size_x][y + cluster_size_y] = "#"
return new_image
def print_high_contrast(image, height, width, cluster_size_x = 6, cluster_size_y = 5, f = sys.stdout):
image = to_buckets(to_grey(image), height, width)
image = high_contrast(image)
for row in image:
print("".join(row), file = f)
def print_image(image, height, width, cluster_size_x = 6, cluster_size_y = 5, f = sys.stdout):
new_im = to_buckets(to_grey(image), height, width)
avg = sum([sum(row) for row in new_im]) / (len(new_im) * len(new_im[0]))
for row in new_im:
for c in row:
for pixel in sorted(list(normal_pixels.items()), key = lambda x: x[1]):
if(pixel[1] * avg >= c):
print(pixel[0], end = "", file = f)
break
print(file = f)
class TextImageHeader(object):
def __init__(self, height, width, format_ = "localdiff" ):
self.height = height
self.width = width
self.format = format_
def __str__(self):
return '''\
TXIMG
height={}
width={}
format={}
'''.format(self.height, self.width, self.format)
@staticmethod
def read_header(file):
if(file.readline().strip() != "TXIMG"):
raise Exception("This file does not seem to be a TextImage")
height = int(file.readline().split("=")[1])
width = int(file.readline().split("=")[1])
format_ = file.readline().split("=")[1].strip()
return TextImageHeader(height, width, format_)
if __name__ == "__main__":
import argparse
formats = {"localdiff": print_high_contrast, "normal": print_image}
parser = argparse.ArgumentParser()
parser.add_argument("file", help = "The image file to read")
parser.add_argument("-f", "--format", help = "The format (localdiff/normal)", default = "localdiff")
parser.add_argument("-H", "--height", type = int, help = "The height of the output. Defaults to the terminal size")
parser.add_argument("-w", "--width", type = int, help = "The width of the output. Defaults to the terminal size")
parser.add_argument("-x", "--cluster-size-x", type = int, help = "The x size of the clusters for localdiff")
parser.add_argument("-y", "--cluster-size-y", type = int, help = "The y size of the clusters for localdiff")
args = parser.parse_args()
width, height = shutil.get_terminal_size()
if(args.width):
width = args.width
if(args.height):
height = args.height
image = matplotlib.image.imread(args.file)
if(not args.format in formats):
raise Exception("Format not supported: " + args.format)
header = TextImageHeader(height, width, format_ = args.format)
print(header)
formats[args.format](image, height, width, cluster_size_x = args.cluster_size_x, cluster_size_y = args.cluster_size_y)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment