File size: 5,159 Bytes
83fd0ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c84a261
83fd0ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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)