merged ascii repo and sopel/ascii

This commit is contained in:
iou1name 2017-12-09 01:12:35 -05:00
parent 6cb6d15820
commit 72502d69e9

View File

@ -4,7 +4,6 @@
ASCII
"""
from io import BytesIO
import argparse
import requests
from PIL import Image, ImageFont, ImageDraw
@ -17,58 +16,65 @@ from tools import web
ASCII_CHARS = "$@%#*+=-:. "
BRAIL_CHARS = "⠿⠾⠼⠸⠰⠠ "
HEADERS = {'User-Agent': 'Gimme the ascii.'}
HEADERS = {'User-Agent': 'Gimme ascii.'}
def scale_image(image, size=(100,100)):
def scale_image(image, maxDim=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.
neither width or height is ever larger than the maxDim argument.
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
if original_width > original_height:
if original_width > size[0]:
new_width = size[0]
if original_width <= maxDim and original_height <= maxDim:
new_width, new_height = image.size
elif original_width > original_height:
new_width = maxDim
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 = size[1]
new_height = maxDim
aspect_ratio = original_width/float(original_height)
new_width = int(aspect_ratio * new_height)
else:
new_width, new_height = image.size
image = image.resize((new_width, new_height))
return image
def pixels_to_chars(image, scale="ascii"):
def pixels_to_chars(image, scale="ascii", color_code=None):
"""
Maps each pixel to an ascii char based on where it falls in the range
0-255 normalized to the length of ASCII_CHARS.
0-255 normalized to the length of the chosen scale.
"""
scales = {"ascii": ASCII_CHARS,
"ascii_reverse": "".join(list(reversed(ASCII_CHARS))),
"ascii_reverse": "".join(reversed(ASCII_CHARS)),
"brail": BRAIL_CHARS,
"brail_reverse": "".join(list(reversed(BRAIL_CHARS)))}
"brail_reverse": "".join(reversed(BRAIL_CHARS))}
color_prefix = {"irc": "\03", "ansi":"\033"}
range_width = int(255 / len(scales[scale])) + (255 % len(scales[scale]) > 0)
pixels = list(image.getdata())
chars = []
for pixel in pixels:
index = int(pixel/range_width)
chars.append(scales[scale][index])
chars = "".join(chars)
chars = [chars[i:i + image.size[0]] for i in range(0, len(chars),
pixels = [pixels[i:i + image.size[0]] for i in range(0, len(pixels),
image.size[0])]
chars = []
for row in pixels:
new_row = ""
for pixel in row:
R, G, B = pixel
L = R * 299/1000 + G * 587/1000 + B * 114/1000
index = int(L/range_width)
char = scales[scale][index]
if color_code and char is not " ":
prefix = color_prefix[color_code] + char_color(pixel,color_code)
char = prefix + char
new_row += char
chars.append(new_row)
return "\n".join(chars)
@ -124,11 +130,13 @@ def open_image(imagePath):
if imagePath.startswith("http"):
res = requests.get(imagePath, headers=HEADERS, verify=True,
timeout=20)
if res.status_code == 404:
return "404: file not found."
res.raise_for_status()
image = Image.open(BytesIO(res.content))
else:
image = Image.open(imagePath)
except FileNotFoundError as e:
except FileNotFoundError:
return f"File not found: {imagePath}"
except Exception as e:
return(f"Error opening image: {imagePath}\n{e}")
@ -136,21 +144,6 @@ def open_image(imagePath):
return image
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 alpha_composite(front, back):
"""Alpha composite two RGBA images.
@ -193,36 +186,33 @@ def alpha_composite_with_color(image, color=(255, 255, 255)):
return alpha_composite(image, back)
def image_to_ascii(image, reverse=False, colors=None, brail=False):
def image_to_ascii(image=None, reverse=False, brail=False, color=None,**kwargs):
"""
Reads an image file and converts it to ascii art. Returns a
newline-delineated string. If reverse is True, the ascii scale is
reversed.
"""
if not image:
image = open_image(kwargs["imagePath"])
if image.mode == "P":
image = image.convert(image.palette.mode)
if image.mode == "RGBA":
image = alpha_composite_with_color(image).convert("RGB")
image = scale_image(image)
image_grey = image.convert('L') # convert to grayscale
if reverse:
if brail:
scale = "brail_reverse"
else:
scale = "ascii_reverse"
else:
if brail:
scale = "brail"
else:
scale = "ascii"
chars = pixels_to_chars(image_grey, scale)
if reverse:
scale += "_reverse"
chars = pixels_to_chars(image, scale, color)
if colors:
chars = colorize(chars, image, colors)
image.close()
image_grey.close()
del(image)
del(image_grey)
return chars
@ -230,7 +220,7 @@ def ascii_to_image(image_ascii):
"""
Creates a plain image and draws text on it.
"""
# TODO: make font type and size non-fixed
# TODO: make font type, size and color non-fixed
width = len(image_ascii[:image_ascii.index("\n")]) * 8
height = (image_ascii.count("\n")+1) * 12 + 4
@ -241,17 +231,17 @@ def ascii_to_image(image_ascii):
return image
def handle_gif(image, output, reverse=False):
def handle_gif(imagePath, **kwargs):
"""
Handle gifs seperately.
"""
# image = open_image(args.imagePath)
image = open_image(imagePath)
ascii_seq = []
new_image = ascii_to_image(image_to_ascii(image, reverse))
new_image = ascii_to_image(image_to_ascii(image, **kwargs))
image.seek(1)
while True:
try:
im = ascii_to_image(image_to_ascii(image, reverse))
im = ascii_to_image(image_to_ascii(image, **kwargs))
ascii_seq.append(im)
image.seek(image.tell()+1)
except EOFError:
@ -261,7 +251,7 @@ def handle_gif(image, output, reverse=False):
# 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:
with open(kwargs["output"], "wb") as file:
numpngw.write_apng(file, np_ascii_seq)
@ -275,14 +265,18 @@ def ascii(bot, trigger):
"""
if not trigger.group(2):
return bot.say()
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("imagePath")
parser.add_argument("-r", "--reverse", action="store_true")
parser.add_argument("-r", "--reverse", action="store_true", help="Reverse.")
parser.add_argument("-c", "--color", action="store_true")
parser.add_argument("-b", "--brail", action="store_true")
parser.add_argument("-a", "--animated", action="store_true")
parser.add_argument("-h", "--help", action="store_true")
args = parser.parse_args(trigger.group(2).split())
if args.help:
return bot.say(parser.print_help())
if args.color:
args.color = "irc"
@ -293,24 +287,25 @@ def ascii(bot, trigger):
bot.reply("Internet requests only.")
return
image = open_image(args.imagePath)
if args.animated:
handle_gif(image, "temp.png", args.reverse)
args.output = "temp.png"
handle_gif(**vars(args))
file = {"file": open("temp.png", "rb")}
res = requests.post("https://uguu.se/api.php?d=upload-tool", files=file)
# print(res.text)
bot.say(res.text)
else:
image_ascii = image_to_ascii(image, args.reverse, args.color, args.brail)
image_ascii = image_to_ascii(None, **vars(args))
bot.say(image_ascii)
if __name__=='__main__':
import argparse
parser = argparse.ArgumentParser(
description="Converts an image file to ascii art.")
parser.add_argument(
"imagePath",
help="The full path to the image file.")
help="The path to the image file. May be a local path or internet URL.")
parser.add_argument(
"-r",
"--reverse",
@ -323,9 +318,10 @@ if __name__=='__main__':
parser.add_argument(
"-i",
"--image",
dest="drawImage",
action="store_true",
help="Outputs the ascii art as an image rather than plain text. \
Requires --output.")
help="Outputs the ascii art as an image rather than text. Requires \
--output.")
parser.add_argument(
"-a",
"--animated",
@ -335,23 +331,39 @@ if __name__=='__main__':
"-c",
"--color",
type=str,
help="Colorizes the ascii matrix.")
parser.set_defaults(reverse=False, image=False, animated=False)
help="Colorizes the ascii matrix. Currently supported modes are 'irc' \
and 'ansi' for generating color codes compliant with those standards.")
parser.add_argument(
"--ansi",
dest="color",
action="store_const",
const="ansi",
help="Shortcut for '--color ansi'.")
parser.add_argument(
"--irc",
dest="color",
action="store_const",
const="irc",
help="Shortcut for '--color irc'.")
parser.add_argument(
"-b",
"--brail",
action="store_true",
help="Uses brail unicode characters instead of ascii characters.")
args = parser.parse_args()
if args.animated: # --animated includes --image
args.image = True
if args.image: # --image requires --output
args.drawImage = True
if args.drawImage: # --image requires --output
if not args.output:
parser.error("--image requires --output")
image = open_image(args.imagePath)
if args.animated:
handle_gif(image, args.output, args.reverse)
handle_gif(**vars(args))
exit()
image_ascii = image_to_ascii(image, args.reverse, args.color)
if args.image:
image_ascii = image_to_ascii(None, **vars(args))
if args.drawImage:
image = ascii_to_image(image_ascii)
image.save(args.output, "PNG")
elif args.output: