how does git?
This commit is contained in:
parent
3a10d780e5
commit
8af5015e66
390
ascii.py
390
ascii.py
|
@ -1,195 +1,195 @@
|
|||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ASCII
|
||||
"""
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
#import imageio
|
||||
import numpy as np
|
||||
import numpngw
|
||||
|
||||
ASCII_CHARS = "$@%#*+=-:. "
|
||||
HEADERS = {'User-Agent': 'Gimme the ascii.'}
|
||||
|
||||
|
||||
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
|
||||
if original_width > original_height:
|
||||
if original_width > size[0]:
|
||||
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 = size[1]
|
||||
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, reverse=False):
|
||||
"""
|
||||
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:
|
||||
if reverse:
|
||||
index = -int(pixel_value/range_width)-1
|
||||
else:
|
||||
index = int(pixel_value/range_width)
|
||||
pixels_to_chars.append(ASCII_CHARS[index])
|
||||
|
||||
return "".join(pixels_to_chars)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
if imagePath.startswith("http"):
|
||||
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
|
||||
except Exception as e:
|
||||
return("Error opening image file: " + imagePath)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def image_to_ascii(image, reverse=False):
|
||||
"""
|
||||
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') # convert to grayscale
|
||||
|
||||
chars = pixels_to_chars(image, reverse)
|
||||
|
||||
image_ascii = []
|
||||
for index in range(0, len(chars), image.size[0]):
|
||||
image_ascii.append(chars[index: index + image.size[0]])
|
||||
image.close()
|
||||
del(image)
|
||||
return "\n".join(image_ascii)
|
||||
|
||||
|
||||
def ascii_to_image(image_ascii):
|
||||
"""
|
||||
Creates a plain image and draws text on it.
|
||||
"""
|
||||
# TODO: make font type, size, and image size non-fixed
|
||||
width = len(image_ascii[:image_ascii.index("\n")]) * 8
|
||||
height = (image_ascii.count("\n")+1) * 12 + 4
|
||||
|
||||
font = ImageFont.truetype("LiberationMono-Regular.ttf", 14)
|
||||
image = Image.new("RGB", (width, height), (255,255,255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.text((0,0), image_ascii, (0,0,0), font=font, spacing=0)
|
||||
return image
|
||||
|
||||
|
||||
def handle_gif(output, reverse=False):
|
||||
image = open_image(args.imagePath)
|
||||
ascii_seq = []
|
||||
new_image = ascii_to_image(image_to_ascii(image, reverse))
|
||||
image.seek(1)
|
||||
while True:
|
||||
try:
|
||||
im = ascii_to_image(image_to_ascii(image, reverse))
|
||||
ascii_seq.append(im)
|
||||
image.seek(image.tell()+1)
|
||||
except EOFError:
|
||||
break # end of sequence
|
||||
|
||||
#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]
|
||||
#images2gif.writeGif(output, ascii_seq, nq=10, subRectangles=False)
|
||||
#imageio.mimsave(output, np_ascii_seq)
|
||||
with open(output, "wb") as file:
|
||||
numpngw.write_apng(file, np_ascii_seq)
|
||||
|
||||
|
||||
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.")
|
||||
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.set_defaults(reverse=False, image=False, animated=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.animated: # --animated includes --image
|
||||
args.image = True
|
||||
if args.image: # --image requires --output
|
||||
if not args.output:
|
||||
parser.error("--image requires --output")
|
||||
|
||||
if args.animated:
|
||||
handle_gif(args.output, args.reverse)
|
||||
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)
|
||||
#! /usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ASCII.
|
||||
"""
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
#import imageio
|
||||
import numpy as np
|
||||
import numpngw
|
||||
|
||||
ASCII_CHARS = "$@%#*+=-:. "
|
||||
HEADERS = {'User-Agent': 'Gimme the ascii.'}
|
||||
|
||||
|
||||
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
|
||||
if original_width > original_height:
|
||||
if original_width > size[0]:
|
||||
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 = size[1]
|
||||
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, reverse=False):
|
||||
"""
|
||||
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:
|
||||
if reverse:
|
||||
index = -int(pixel_value/range_width)-1
|
||||
else:
|
||||
index = int(pixel_value/range_width)
|
||||
pixels_to_chars.append(ASCII_CHARS[index])
|
||||
|
||||
return "".join(pixels_to_chars)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
if imagePath.startswith("http"):
|
||||
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
|
||||
except Exception as e:
|
||||
return("Error opening image file: " + imagePath)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def image_to_ascii(image, reverse=False):
|
||||
"""
|
||||
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') # convert to grayscale
|
||||
|
||||
chars = pixels_to_chars(image, reverse)
|
||||
|
||||
image_ascii = []
|
||||
for index in range(0, len(chars), image.size[0]):
|
||||
image_ascii.append(chars[index: index + image.size[0]])
|
||||
image.close()
|
||||
del(image)
|
||||
return "\n".join(image_ascii)
|
||||
|
||||
|
||||
def ascii_to_image(image_ascii):
|
||||
"""
|
||||
Creates a plain image and draws text on it.
|
||||
"""
|
||||
# TODO: make font type, size, and image size non-fixed
|
||||
width = len(image_ascii[:image_ascii.index("\n")]) * 8
|
||||
height = (image_ascii.count("\n")+1) * 12 + 4
|
||||
|
||||
font = ImageFont.truetype("LiberationMono-Regular.ttf", 14)
|
||||
image = Image.new("RGB", (width, height), (255,255,255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.text((0,0), image_ascii, (0,0,0), font=font, spacing=0)
|
||||
return image
|
||||
|
||||
|
||||
def handle_gif(output, reverse=False):
|
||||
image = open_image(args.imagePath)
|
||||
ascii_seq = []
|
||||
new_image = ascii_to_image(image_to_ascii(image, reverse))
|
||||
image.seek(1)
|
||||
while True:
|
||||
try:
|
||||
im = ascii_to_image(image_to_ascii(image, reverse))
|
||||
ascii_seq.append(im)
|
||||
image.seek(image.tell()+1)
|
||||
except EOFError:
|
||||
break # end of sequence
|
||||
|
||||
#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]
|
||||
#images2gif.writeGif(output, ascii_seq, nq=10, subRectangles=False)
|
||||
#imageio.mimsave(output, np_ascii_seq)
|
||||
with open(output, "wb") as file:
|
||||
numpngw.write_apng(file, np_ascii_seq)
|
||||
|
||||
|
||||
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.")
|
||||
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.set_defaults(reverse=False, image=False, animated=False)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.animated: # --animated includes --image
|
||||
args.image = True
|
||||
if args.image: # --image requires --output
|
||||
if not args.output:
|
||||
parser.error("--image requires --output")
|
||||
|
||||
if args.animated:
|
||||
handle_gif(args.output, args.reverse)
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user