added color to ascii.py

This commit is contained in:
iou1name 2017-11-26 13:54:54 -05:00
parent d7e63993ee
commit ffd5a62241
3 changed files with 276 additions and 203 deletions

View File

@ -1,16 +1,21 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ASCII
"""
from io import BytesIO
from PIL import Image
import requests
from PIL import Image, ImageFont, ImageDraw
#import imageio
import numpy as np
import numpngw
import module
from tools import web
from PIL import ImageFont
from PIL import ImageDraw
ASCII_CHARS = "$@%#*+=-:. "
headers = {'User-Agent': 'we wuz ascii and shiet'}
HEADERS = {'User-Agent': 'Gimme the ascii.'}
def scale_image(image, size=(100,100)):
@ -18,19 +23,22 @@ def scale_image(image, size=(100,100)):
Resizes an image while preserving the aspect ratio. Chooses the
dimension to scale by based on whichever is larger, ensuring that
neither width or height is ever larger than the accepted size tuple.
Because text characters are typically pretty close to a 1:2 rectangle,
we weight the width twice as much.
"""
original_width, original_height = image.size
original_width = original_width * 2 # because characters are generally
if original_width > original_height: # displayed as a 1:2 square
original_width = original_width * 2
if original_width > original_height:
if original_width > size[0]:
new_width = 100
new_width = size[0]
aspect_ratio = original_height/float(original_width)
new_height = int(aspect_ratio * new_width)
else:
new_width, new_height = image.size
else:
if original_height > size[1]:
new_height = 100
new_height = size[1]
aspect_ratio = original_width/float(original_height)
new_width = int(aspect_ratio * new_height)
else:
@ -41,70 +49,130 @@ def scale_image(image, size=(100,100)):
def pixels_to_chars(image, reverse=False):
"""
Maps each pixel to an ascii char based on the range
in which it lies.
0-255 is divided into 11 ranges of 25 pixels each.
Maps each pixel to an ascii char based on where it falls in the range
0-255 normalized to the length of ASCII_CHARS.
"""
range_width = int(255 / len(ASCII_CHARS)) + (255 % len(ASCII_CHARS) > 0)
pixels_in_image = list(image.getdata())
pixels_to_chars = []
for pixel_value in pixels_in_image:
pixels = list(image.getdata())
chars = []
for pixel in pixels:
if reverse:
index = -int(pixel_value/range_width)-1
index = -int(pixel/range_width)-1
else:
index = int(pixel_value/range_width)
pixels_to_chars.append(ASCII_CHARS[ index ])
index = int(pixel/range_width)
chars.append(ASCII_CHARS[index])
return "".join(pixels_to_chars)
chars = "".join(chars)
chars = [chars[i:i + image.size[0]] for i in range(0, len(chars),
image.size[0])]
return "\n".join(chars)
def char_color(pixel, code="irc"):
"""
Maps a color to a character based on the original pixel color.
Calculates the distance from the provided color to each of the 16
colors in the IRC and ANSI color codes and selects the closest match.
"""
colors_index = ["white", "black", "blue", "green", "red", "brown", "purple",
"orange", "yellow", "light green", "teal", "cyan", "light blue", "pink",
"grey", "silver"]
colors_rgb = {"white": (0,0,0), "black": (255,255,255), "blue": (0,0,255),
"green": (0,255,0), "red": (255,0,0), "brown": (150,75,0),
"purple": (128,0,128), "orange": (255,128,0), "yellow": (255,255,0),
"light green": (191,255,0), "teal": (0,128,128), "cyan": (0,255,255),
"light blue": (65,105,225), "pink":(255,192,203), "grey": (128,128,128),
"silver": (192,192,192)}
colors_irc = {"white": "0", "black": "1", "blue": "2", "green": "3", "red": "4",
"brown": "5", "purple": "6", "orange": "7", "yellow": "8", "light green": "9",
"teal": "10", "cyan": "11", "light blue": "12", "pink": "13", "grey": "14",
"silver": "15"}
colors_ansi = {"white": "[1;37m", "black": "[0;30m", "blue": "[0;34m",
"green": "[0;32m", "red": "[0;31m", "brown": "[0;33m",
"purple": "[0;35m", "orange": "[1;31m", "yellow": "[1;33m",
"light green": "[1;32m", "teal": "[0;36m", "cyan": "[1;36m",
"light blue": "[1;34m", "pink": "[1;35m", "grey": "[1;30m",
"silver": "[0;37m"}
dist = [(abs(pixel[0] - colors_rgb[color][0])**2
+ abs(pixel[1] - colors_rgb[color][1])**2
+ abs(pixel[2] - colors_rgb[color][2])**2)**0.5
for color in colors_index]
color = colors_index[dist.index(min(dist))]
if code == "irc":
return colors_irc[color]
elif code == "ansi":
return colors_ansi[color]
def open_image(imagePath):
"""
Opens the image at the supplied file path in PIL. If an internet URL
is supplied, it will download the image and then open it.
is supplied, it will download the image and then open it. Returns a
PIL image object.
"""
try:
if imagePath.startswith("http"):
res = requests.get(imagePath, headers=headers, verify=True, timeout=20)
res = requests.get(imagePath, headers=HEADERS, verify=True,
timeout=20)
res.raise_for_status()
image = Image.open(BytesIO(res.content))
else:
image = Image.open(imagePath)
except FileNotFoundError as e:
return e
except OSError:
return e
return f"File not found: {imagePath}"
except Exception as e:
return("Error opening image file: " + imagePath)
return(f"Error opening image: {imagePath}\n{e}")
return image
def image_to_ascii(image, reverse=False):
def colorize(chars, image, code):
"""
Colorizes the ascii matrix.
"""
prefix = {"irc": "\03", "ansi":"\033"}
chars = chars.split("\n")
for j in range(0, image.size[1]):
new_row = ""
for k in range(0, image.size[0]):
new_row += prefix[code] + char_color(image.getpixel((k,j)), code)
new_row += chars[j][k]
chars[j] = new_row
return "\n".join(chars)
def image_to_ascii(image, reverse=False, colors=None):
"""
Reads an image file and converts it to ascii art. Returns a
newline-delineated string. If reverse is True, the ascii scale is
reversed.
"""
image = scale_image(image)
image = image.convert('L')
image_grey = image.convert('L') # convert to grayscale
chars = pixels_to_chars(image, reverse)
chars = pixels_to_chars(image_grey, reverse)
image_ascii = []
for index in range(0, len(chars), image.size[0]):
image_ascii.append( chars[index: index + image.size[0]] )
if colors:
chars = colorize(chars, image, colors)
image.close()
del image
return "\n".join(image_ascii)
image_grey.close()
del(image)
del(image_grey)
return chars
def ascii_to_image(image_ascii):
"""
Creates a plain image and draws text on it.
"""
# TODO: make font type and size non-fixed
width = len(image_ascii[:image_ascii.index("\n")]) * 8
height = (image_ascii.count("\n")+1) * 12 + 4
@ -115,7 +183,10 @@ def ascii_to_image(image_ascii):
return image
def handle_gif(output, reverse=False):
def handle_gif(image, output, reverse=False):
"""
Handle gifs seperately.
"""
image = open_image(args.imagePath)
ascii_seq = []
new_image = ascii_to_image(image_to_ascii(image, reverse))
@ -123,12 +194,17 @@ def handle_gif(output, reverse=False):
while True:
try:
im = ascii_to_image(image_to_ascii(image, reverse))
ascii_seq.append()
ascii_seq.append(im)
image.seek(image.tell()+1)
except EOFError:
break # end of sequence
new_image.save(args.output, save_all=True, append_images=ascii_seq, duration=60, loop=0, optimize=True)
#new_image.save(output, save_all=True, append_images=ascii_seq,
# duration=60, loop=0, optimize=True)
ascii_seq = [new_image] + ascii_seq
np_ascii_seq = [np.array(im) for im in ascii_seq]
with open(output, "wb") as file:
numpngw.write_apng(file, np_ascii_seq)
@module.rate(user=60)
@ -140,9 +216,13 @@ def ascii(bot, trigger):
Downloads an image and converts it to ascii.
"""
reverse = False
color = None
if trigger.group(3) == "-r":
imagePath = trigger.group(4)
reverse = True
elif trigger.group(3) == "-c":
imagePath = trigger.group(4)
color = "irc"
else:
imagePath = trigger.group(2)
@ -154,20 +234,43 @@ def ascii(bot, trigger):
return
image = open_image(imagePath)
image_ascii = image_to_ascii(image, reverse)
image_ascii = image_to_ascii(image, reverse, color)
bot.say(image_ascii)
if __name__=='__main__':
import argparse
# TODO: satisfy PEP8
parser = argparse.ArgumentParser(description="Converts an image file to ascii art.")
parser.add_argument("imagePath", help="The full path to the image file.")
parser.add_argument("-r", "--reverse", action="store_true", help="Reverses the ascii scale.")
parser.add_argument("-o", "--output", help="Outputs the ascii art into a file at the specified path.")
parser.add_argument("-i", "--image", action="store_true", help="Outputs the ascii art as an image rather than plain text. Requires --output.")
parser.add_argument("-a", "--animated", action="store_true", help="Handles animated GIFs. Includes --image.")
parser = argparse.ArgumentParser(
description="Converts an image file to ascii art.")
parser.add_argument(
"imagePath",
help="The full path to the image file.")
parser.add_argument(
"-r",
"--reverse",
action="store_true",
help="Reverses the ascii scale.")
parser.add_argument(
"-o",
"--output",
help="Outputs the ascii art into a file at the specified path.")
parser.add_argument(
"-i",
"--image",
action="store_true",
help="Outputs the ascii art as an image rather than plain text. \
Requires --output.")
parser.add_argument(
"-a",
"--animated",
action="store_true",
help="Handles animated GIFs. Includes --image.")
parser.add_argument(
"-c",
"--color",
type=str,
help="Colorizes the ascii matrix.")
parser.set_defaults(reverse=False, image=False, animated=False)
args = parser.parse_args()
@ -177,16 +280,17 @@ if __name__=='__main__':
if not args.output:
parser.error("--image requires --output")
image = open_image(args.imagePath)
if args.animated:
handle_gif(args.output, args.reverse)
handle_gif(image, args.output, args.reverse)
exit()
image_ascii = image_to_ascii(image, args.reverse, args.color)
if args.image:
image = ascii_to_image(image_ascii)
image.save(args.output, "PNG")
elif args.output:
with open(args.output, "w+") as file:
file.write(image_ascii)
else:
image = open_image(args.imagePath)
image_ascii = image_to_ascii(image, args.reverse)
if args.image:
image = ascii_to_image(image_ascii)
image.save(args.output, "PNG")
elif args.output:
with open(args.output, "w+") as file:
file.write(image_ascii)
else:
print(image_ascii)
print(image_ascii)

View File

@ -31,6 +31,7 @@ def help(bot, trigger):
name = name.lower()
if name in bot.doc:
print(bot.doc[name])
newlines = ['']
lines = list(filter(None, bot.doc[name][0]))
lines = list(map(str.strip, lines))

View File

@ -16,82 +16,50 @@ from module import rule, commands, priority, example
mangle_lines = {}
if sys.version_info.major >= 3:
unicode = str
unicode = str
def translate(text, in_lang='auto', out_lang='en', verify_ssl=True):
raw = False
if unicode(out_lang).endswith('-raw'):
out_lang = out_lang[:-4]
raw = True
raw = False
if unicode(out_lang).endswith('-raw'):
out_lang = out_lang[:-4]
raw = True
headers = {
'User-Agent': 'Mozilla/5.0' +
'(X11; U; Linux i686)' +
'Gecko/20071127 Firefox/2.0.0.11'
}
headers = {
'User-Agent': 'Mozilla/5.0' +
'(X11; U; Linux i686)' +
'Gecko/20071127 Firefox/2.0.0.11'
}
query = {
"client": "gtx",
"sl": in_lang,
"tl": out_lang,
"dt": "t",
"q": text,
}
url = "http://translate.googleapis.com/translate_a/single"
result = requests.get(url, params=query, timeout=40, headers=headers,
verify=verify_ssl).text
query = {
"client": "gtx",
"sl": in_lang,
"tl": out_lang,
"dt": "t",
"q": text,
}
url = "http://translate.googleapis.com/translate_a/single"
result = requests.get(url, params=query, timeout=40, headers=headers,
verify=verify_ssl).text
if result == '[,,""]':
return None, in_lang
if result == '[,,""]':
return None, in_lang
while ',,' in result:
result = result.replace(',,', ',null,')
result = result.replace('[,', '[null,')
while ',,' in result:
result = result.replace(',,', ',null,')
result = result.replace('[,', '[null,')
data = json.loads(result)
data = json.loads(result)
if raw:
return str(data), 'en-raw'
if raw:
return str(data), 'en-raw'
try:
language = data[2] # -2][0][0]
except:
language = '?'
try:
language = data[2] # -2][0][0]
except:
language = '?'
return ''.join(x[0] for x in data[0]), language
@rule(u'$nickname[,:]\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$')
@example('$nickname: "mon chien"? or $nickname: fr "mon chien"?')
@priority('low')
def tr(bot, trigger):
"""Translates a phrase, with an optional language hint."""
in_lang, out_lang, phrase = trigger.groups()
if (len(phrase) > 350) and (not trigger.admin):
return bot.reply('Phrase must be under 350 characters.')
if phrase.strip() == '':
return bot.reply('You need to specify a string for me to translate!')
in_lang = in_lang or 'auto'
out_lang = out_lang or 'en'
if in_lang != out_lang:
msg, in_lang = translate(phrase, in_lang, out_lang,
verify_ssl=bot.config.core.verify_ssl)
if sys.version_info.major < 3 and isinstance(msg, str):
msg = msg.decode('utf-8')
if msg:
msg = web.decode(msg) # msg.replace('&#39;', "'")
msg = '"%s" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)
else:
msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (in_lang, out_lang)
bot.reply(msg)
else:
bot.reply('Language guessing failed, so try suggesting one!')
return ''.join(x[0] for x in data[0]), language
@commands('translate', 'tr')
@ -99,110 +67,110 @@ def tr(bot, trigger):
@example('.tr היי', '"Hey" (iw to en, translate.google.com)')
@example('.tr mon chien', '"my dog" (fr to en, translate.google.com)')
def tr2(bot, trigger):
"""Translates a phrase, with an optional language hint."""
command = trigger.group(2)
"""Translates a phrase, with an optional language hint."""
command = trigger.group(2)
if not command:
return bot.reply('You did not give me anything to translate')
if not command:
return bot.reply('You did not give me anything to translate')
def langcode(p):
return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
def langcode(p):
return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
args = ['auto', 'en']
args = ['auto', 'en']
for i in range(2):
if ' ' not in command:
break
prefix, cmd = command.split(' ', 1)
if langcode(prefix):
args[i] = prefix[1:]
command = cmd
phrase = command
for i in range(2):
if ' ' not in command:
break
prefix, cmd = command.split(' ', 1)
if langcode(prefix):
args[i] = prefix[1:]
command = cmd
phrase = command
if (len(phrase) > 350) and (not trigger.admin):
return bot.reply('Phrase must be under 350 characters.')
if (len(phrase) > 350) and (not trigger.admin):
return bot.reply('Phrase must be under 350 characters.')
if phrase.strip() == '':
return bot.reply('You need to specify a string for me to translate!')
if phrase.strip() == '':
return bot.reply('You need to specify a string for me to translate!')
src, dest = args
if src != dest:
msg, src = translate(phrase, src, dest,
verify_ssl=bot.config.core.verify_ssl)
if sys.version_info.major < 3 and isinstance(msg, str):
msg = msg.decode('utf-8')
if msg:
msg = web.decode(msg) # msg.replace('&#39;', "'")
msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
else:
msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (src, dest)
src, dest = args
if src != dest:
msg, src = translate(phrase, src, dest,
verify_ssl=bot.config.core.verify_ssl)
if sys.version_info.major < 3 and isinstance(msg, str):
msg = msg.decode('utf-8')
if msg:
#msg = web.decode(msg) # msg.replace('&#39;', "'")
msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
else:
msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (src, dest)
bot.reply(msg)
else:
bot.reply('Language guessing failed, so try suggesting one!')
bot.reply(msg)
else:
bot.reply('Language guessing failed, so try suggesting one!')
def get_random_lang(long_list, short_list):
random_index = random.randint(0, len(long_list) - 1)
random_lang = long_list[random_index]
if random_lang not in short_list:
short_list.append(random_lang)
else:
return get_random_lang(long_list, short_list)
return short_list
random_index = random.randint(0, len(long_list) - 1)
random_lang = long_list[random_index]
if random_lang not in short_list:
short_list.append(random_lang)
else:
return get_random_lang(long_list, short_list)
return short_list
@commands('mangle', 'mangle2')
def mangle(bot, trigger):
"""Repeatedly translate the input until it makes absolutely no sense."""
verify_ssl = bot.config.core.verify_ssl
global mangle_lines
long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
lang_list = []
for __ in range(0, 8):
lang_list = get_random_lang(long_lang_list, lang_list)
random.shuffle(lang_list)
if trigger.group(2) is None:
try:
phrase = (mangle_lines[trigger.sender.lower()], '')
except:
bot.reply("What do you want me to mangle?")
return
else:
phrase = (trigger.group(2).strip(), '')
if phrase[0] == '':
bot.reply("What do you want me to mangle?")
return
for lang in lang_list:
backup = phrase
try:
phrase = translate(phrase[0], 'en', lang,
verify_ssl=verify_ssl)
except:
phrase = False
if not phrase:
phrase = backup
break
"""Repeatedly translate the input until it makes absolutely no sense."""
verify_ssl = bot.config.core.verify_ssl
global mangle_lines
long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
lang_list = []
for __ in range(0, 8):
lang_list = get_random_lang(long_lang_list, lang_list)
random.shuffle(lang_list)
if trigger.group(2) is None:
try:
phrase = (mangle_lines[trigger.sender.lower()], '')
except:
bot.reply("What do you want me to mangle?")
return
else:
phrase = (trigger.group(2).strip(), '')
if phrase[0] == '':
bot.reply("What do you want me to mangle?")
return
for lang in lang_list:
backup = phrase
try:
phrase = translate(phrase[0], 'en', lang,
verify_ssl=verify_ssl)
except:
phrase = False
if not phrase:
phrase = backup
break
try:
phrase = translate(phrase[0], lang, 'en', verify_ssl=verify_ssl)
except:
phrase = backup
continue
try:
phrase = translate(phrase[0], lang, 'en', verify_ssl=verify_ssl)
except:
phrase = backup
continue
if not phrase:
phrase = backup
break
bot.reply(phrase[0])
if not phrase:
phrase = backup
break
bot.reply(phrase[0])
@rule('(.*)')
@priority('low')
def collect_mangle_lines(bot, trigger):
global mangle_lines
mangle_lines[trigger.sender.lower()] = "%s said '%s'" % (trigger.nick, (trigger.group(0).strip()))
global mangle_lines
mangle_lines[trigger.sender.lower()] = "%s said '%s'" % (trigger.nick, (trigger.group(0).strip()))
if __name__ == "__main__":
from test_tools import run_example_tests
run_example_tests(__file__)
from test_tools import run_example_tests
run_example_tests(__file__)