refactored colorization and kwargs
This commit is contained in:
parent
153e5f49eb
commit
a83a6c5681
169
ascii.py
169
ascii.py
|
@ -12,6 +12,7 @@ import numpy as np
|
||||||
import numpngw
|
import numpngw
|
||||||
|
|
||||||
ASCII_CHARS = "$@%#*+=-:. "
|
ASCII_CHARS = "$@%#*+=-:. "
|
||||||
|
BRAIL_CHARS = "⠿⠾⠼⠸⠰⠠ "
|
||||||
HEADERS = {'User-Agent': 'Gimme ascii.'}
|
HEADERS = {'User-Agent': 'Gimme ascii.'}
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,43 +27,51 @@ def scale_image(image, maxDim=100):
|
||||||
"""
|
"""
|
||||||
original_width, original_height = image.size
|
original_width, original_height = image.size
|
||||||
original_width = original_width * 2
|
original_width = original_width * 2
|
||||||
if original_width > original_height:
|
if original_width <= maxDim and original_height <= maxDim:
|
||||||
if original_width > maxDim:
|
new_width, new_height = image.size
|
||||||
new_width = maxDim
|
elif original_width > original_height:
|
||||||
aspect_ratio = original_height/float(original_width)
|
new_width = maxDim
|
||||||
new_height = int(aspect_ratio * new_width)
|
aspect_ratio = original_height/float(original_width)
|
||||||
else:
|
new_height = int(aspect_ratio * new_width)
|
||||||
new_width, new_height = image.size
|
|
||||||
else:
|
else:
|
||||||
if original_height > maxDim:
|
new_height = maxDim
|
||||||
new_height = maxDim
|
aspect_ratio = original_width/float(original_height)
|
||||||
aspect_ratio = original_width/float(original_height)
|
new_width = int(aspect_ratio * new_height)
|
||||||
new_width = int(aspect_ratio * new_height)
|
|
||||||
else:
|
|
||||||
new_width, new_height = image.size
|
|
||||||
image = image.resize((new_width, new_height))
|
image = image.resize((new_width, new_height))
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
def pixels_to_chars(image, reverse=False):
|
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
|
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.
|
||||||
"""
|
"""
|
||||||
range_width = int(255 / len(ASCII_CHARS)) + (255 % len(ASCII_CHARS) > 0)
|
scales = {"ascii": ASCII_CHARS,
|
||||||
|
"ascii_reverse": "".join(reversed(ASCII_CHARS)),
|
||||||
|
"brail": 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())
|
pixels = list(image.getdata())
|
||||||
chars = []
|
pixels = [pixels[i:i + image.size[0]] for i in range(0, len(pixels),
|
||||||
for pixel in pixels:
|
|
||||||
if reverse:
|
|
||||||
index = -int(pixel/range_width)-1
|
|
||||||
else:
|
|
||||||
index = int(pixel/range_width)
|
|
||||||
chars.append(ASCII_CHARS[index])
|
|
||||||
|
|
||||||
chars = "".join(chars)
|
|
||||||
chars = [chars[i:i + image.size[0]] for i in range(0, len(chars),
|
|
||||||
image.size[0])]
|
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)
|
return "\n".join(chars)
|
||||||
|
|
||||||
|
|
||||||
|
@ -132,46 +141,75 @@ def open_image(imagePath):
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
def colorize(chars, image, code):
|
def alpha_composite(front, back):
|
||||||
|
"""Alpha composite two RGBA images.
|
||||||
|
|
||||||
|
Source: http://stackoverflow.com/a/9166671/284318
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
front -- PIL RGBA Image object
|
||||||
|
back -- PIL RGBA Image object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Colorizes the ascii matrix. Moves iteratively through the matrix and
|
front = np.asarray(front)
|
||||||
calls char_color on each pixel/character. Spaces are skipped to save
|
back = np.asarray(back)
|
||||||
time and CPU cycles.
|
result = np.empty(front.shape, dtype='float')
|
||||||
|
alpha = np.index_exp[:, :, 3:]
|
||||||
|
rgb = np.index_exp[:, :, :3]
|
||||||
|
falpha = front[alpha] / 255.0
|
||||||
|
balpha = back[alpha] / 255.0
|
||||||
|
result[alpha] = falpha + balpha * (1 - falpha)
|
||||||
|
old_setting = np.seterr(invalid='ignore')
|
||||||
|
result[rgb] = (front[rgb] * falpha + back[rgb] * balpha * (1 - falpha)) / result[alpha]
|
||||||
|
np.seterr(**old_setting)
|
||||||
|
result[alpha] *= 255
|
||||||
|
np.clip(result, 0, 255)
|
||||||
|
# astype('uint8') maps np.nan and np.inf to 0
|
||||||
|
result = result.astype('uint8')
|
||||||
|
result = Image.fromarray(result, 'RGBA')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def alpha_composite_with_color(image, color=(255, 255, 255)):
|
||||||
|
"""Alpha composite an RGBA image with a single color image of the
|
||||||
|
specified color and the same size as the original image.
|
||||||
|
|
||||||
|
Keyword Arguments:
|
||||||
|
image -- PIL RGBA Image object
|
||||||
|
color -- Tuple r, g, b (default 255, 255, 255)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
prefix = {"irc": "\03", "ansi":"\033"}
|
back = Image.new('RGBA', size=image.size, color=color + (255,))
|
||||||
chars = chars.split("\n")
|
return alpha_composite(image, back)
|
||||||
for j in range(0, image.size[1]):
|
|
||||||
new_row = ""
|
|
||||||
for k in range(0, image.size[0]):
|
|
||||||
if chars[j][k] == " ":
|
|
||||||
continue
|
|
||||||
new_row += prefix[code] + char_color(image.getpixel((k,j)), code)
|
|
||||||
new_row += chars[j][k]
|
|
||||||
chars[j] = new_row
|
|
||||||
|
|
||||||
chars = "\n".join(chars)
|
|
||||||
if code == "ansi":
|
|
||||||
chars += "\033[0m" # to avoid lingering effects in terminals
|
|
||||||
return chars
|
|
||||||
|
|
||||||
|
|
||||||
def image_to_ascii(image, reverse=False, colors=None):
|
def image_to_ascii(image=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Reads an image file and converts it to ascii art. Returns a
|
Reads an image file and converts it to ascii art. Returns a
|
||||||
newline-delineated string. If reverse is True, the ascii scale is
|
newline-delineated string. If reverse is True, the ascii scale is
|
||||||
reversed.
|
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 = scale_image(image)
|
||||||
image_grey = image.convert('L') # convert to grayscale
|
|
||||||
|
|
||||||
chars = pixels_to_chars(image_grey, reverse)
|
if kwargs["brail"]:
|
||||||
|
scale = "brail"
|
||||||
|
else:
|
||||||
|
scale = "ascii"
|
||||||
|
if kwargs["reverse"]:
|
||||||
|
scale += "_reverse"
|
||||||
|
|
||||||
|
chars = pixels_to_chars(image, scale, kwargs["color"])
|
||||||
|
|
||||||
if colors:
|
|
||||||
chars = colorize(chars, image, colors)
|
|
||||||
image.close()
|
image.close()
|
||||||
image_grey.close()
|
|
||||||
del(image)
|
del(image)
|
||||||
del(image_grey)
|
|
||||||
return chars
|
return chars
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,17 +228,17 @@ def ascii_to_image(image_ascii):
|
||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
def handle_gif(image, output, reverse=False):
|
def handle_gif(**kwargs):
|
||||||
"""
|
"""
|
||||||
Handle gifs seperately.
|
Handle gifs seperately.
|
||||||
"""
|
"""
|
||||||
image = open_image(args.imagePath)
|
image = open_image(kwargs["imagePath"])
|
||||||
ascii_seq = []
|
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)
|
image.seek(1)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
im = ascii_to_image(image_to_ascii(image, reverse))
|
im = ascii_to_image(image_to_ascii(image, **kwargs))
|
||||||
ascii_seq.append(im)
|
ascii_seq.append(im)
|
||||||
image.seek(image.tell()+1)
|
image.seek(image.tell()+1)
|
||||||
except EOFError:
|
except EOFError:
|
||||||
|
@ -210,7 +248,7 @@ def handle_gif(image, output, reverse=False):
|
||||||
# duration=60, loop=0, optimize=True)
|
# duration=60, loop=0, optimize=True)
|
||||||
ascii_seq = [new_image] + ascii_seq
|
ascii_seq = [new_image] + ascii_seq
|
||||||
np_ascii_seq = [np.array(im) for im in 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)
|
numpngw.write_apng(file, np_ascii_seq)
|
||||||
|
|
||||||
|
|
||||||
|
@ -234,6 +272,7 @@ if __name__=='__main__':
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-i",
|
"-i",
|
||||||
"--image",
|
"--image",
|
||||||
|
dest="drawImage",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Outputs the ascii art as an image rather than text. Requires \
|
help="Outputs the ascii art as an image rather than text. Requires \
|
||||||
--output.")
|
--output.")
|
||||||
|
@ -260,21 +299,25 @@ if __name__=='__main__':
|
||||||
action="store_const",
|
action="store_const",
|
||||||
const="irc",
|
const="irc",
|
||||||
help="Shortcut for '--color 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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.animated: # --animated includes --image
|
if args.animated: # --animated includes --image
|
||||||
args.image = True
|
args.drawImage = True
|
||||||
if args.image: # --image requires --output
|
if args.drawImage: # --image requires --output
|
||||||
if not args.output:
|
if not args.output:
|
||||||
parser.error("--image requires --output")
|
parser.error("--image requires --output")
|
||||||
|
|
||||||
image = open_image(args.imagePath)
|
|
||||||
if args.animated:
|
if args.animated:
|
||||||
handle_gif(image, args.output, args.reverse)
|
handle_gif(**vars(args))
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
image_ascii = image_to_ascii(image, args.reverse, args.color)
|
image_ascii = image_to_ascii(None, **vars(args))
|
||||||
if args.image:
|
if args.drawImage:
|
||||||
image = ascii_to_image(image_ascii)
|
image = ascii_to_image(image_ascii)
|
||||||
image.save(args.output, "PNG")
|
image.save(args.output, "PNG")
|
||||||
elif args.output:
|
elif args.output:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user