Spaces:
Runtime error
Runtime error
from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageFont | |
import numpy as np | |
import math | |
import gradio as gr | |
##### CONSTANTS ##### | |
# ASCII characters used to represent image pixels, reversed for better contrast in mapping | |
CHARS = ' .:-=+*#%@'[::-1] | |
# Convert the characters to a list for easier access | |
CHAR_ARRAY = list(CHARS) | |
# Number of available ASCII characters | |
CHAR_LEN = len(CHAR_ARRAY) | |
# Grayscale level for each ASCII character, determining how many shades of gray each character represents | |
GRAYSCALE_LEVEL = CHAR_LEN / 256 | |
# Scaling factor to resize the image | |
SCALE = 0.15 | |
# Character dimensions (width and height in pixels) used to match image aspect ratio to character aspect ratio | |
CHAR_W = 6 | |
CHAR_H = 14 | |
##### FUNCTIONS ##### | |
def getChar(inputInt, gamma=1.8): | |
"""Map a grayscale pixel value to an ASCII character with gamma correction applied.""" | |
# Adjust the input pixel intensity using gamma correction for perceptual brightness adjustment | |
inputInt = (inputInt / 255) ** gamma * 255 | |
# Map the corrected pixel value to an appropriate ASCII character | |
return CHAR_ARRAY[math.floor(inputInt * GRAYSCALE_LEVEL)] | |
def load_and_preprocess_image(image): | |
"""Resize and preprocess the input image, adjusting contrast and blurring for better ASCII conversion.""" | |
width, height = image.size | |
# Resize image, adjusting aspect ratio to fit ASCII character dimensions | |
im = image.resize((int(SCALE * width), int(SCALE * height * (CHAR_W / CHAR_H)))) | |
# Enhance contrast to bring out more detail in the ASCII representation | |
im = ImageOps.equalize(im, mask=None) | |
# Apply a slight blur to reduce noise and simplify pixel values | |
im = im.filter(ImageFilter.GaussianBlur(radius=0.5)) | |
return im | |
def create_ascii_art(im): | |
"""Convert a preprocessed image into ASCII art by mapping grayscale values to ASCII characters.""" | |
# Convert the image to grayscale | |
grayscale_image = im.convert("L") | |
# Get the pixel values as a NumPy array for easier processing | |
pix = np.array(grayscale_image) | |
width, height = grayscale_image.size | |
# Create a string that holds the ASCII art, where each character represents a pixel | |
ascii_art = "" | |
for i in range(height): | |
for j in range(width): | |
# Append the corresponding ASCII character for each pixel | |
ascii_art += getChar(pix[i, j]) | |
# Newline after each row to maintain image structure | |
ascii_art += '\n' | |
return ascii_art | |
def draw_ascii_image(ascii_art_string, char_width, char_height, font_size): | |
"""Draw the ASCII art string onto an image.""" | |
# Split the ASCII art string into lines | |
lines = ascii_art_string.split('\n') | |
# Determine the dimensions of the image based on the number of characters | |
width = max(len(line) for line in lines) # Maximum line width | |
height = len(lines) # Number of lines (height) | |
# Create a blank white image based on the ASCII art size and font size | |
img_width = width * char_width | |
img_height = height * char_height | |
ascii_image = Image.new("RGB", (img_width, img_height), "white") | |
draw = ImageDraw.Draw(ascii_image) | |
# Use default or custom font (Pillow's default font in this case) | |
font = ImageFont.load_default() | |
# Draw the ASCII art on the image | |
for i, line in enumerate(lines): | |
# Draw each line of the ASCII art onto the image | |
draw.text((0, i * char_height), line, font=font, fill="black") | |
return ascii_image | |
def process_image(image): | |
"""Process the input image to generate both an ASCII art image and a downloadable text file.""" | |
# Resize and preprocess the image | |
resized_image = load_and_preprocess_image(image) | |
# Generate the ASCII art as text | |
ascii_art = create_ascii_art(resized_image) | |
# Create an image from the ASCII art characters | |
output_image = draw_ascii_image(ascii_art, char_width=CHAR_W, char_height=CHAR_H, font_size=10) | |
# Save the ASCII art as a text file | |
ascii_txt_path = "ascii_art.txt" | |
with open(ascii_txt_path, "w") as text_file: | |
text_file.write(ascii_art) | |
return output_image, ascii_txt_path | |
##### GRADIO INTERFACE ##### | |
def gradio_interface(image): | |
"""Gradio interface function to handle user input and return the ASCII art image and text file.""" | |
ascii_image, txt_file = process_image(image) | |
return ascii_image, txt_file | |
# Set up the Gradio interface | |
demo = gr.Interface( | |
fn=gradio_interface, | |
inputs=gr.Image(type="pil", label="Upload an Image", height=300), | |
outputs=[ | |
gr.Image(type="pil", label="ASCII Art Image", height=300), | |
gr.File(label="Download ASCII Art Text File", height=50), | |
], | |
title="ASCII Art Generator", | |
description="Upload an image, and this tool will generate ASCII art and provide a downloadable text file of the result.", | |
allow_flagging="never", | |
examples=[ | |
['images/building.jpg'], | |
['images/cat.webp'], | |
['images/mountain.webp'], | |
['images/people.jpg'], | |
['images/Northeastern_seal.png'], | |
['images/einstein.jpg'], | |
], | |
) | |
demo.launch(share=True) | |