File size: 7,023 Bytes
83fd0ba
 
 
 
55f1779
 
83fd0ba
 
 
 
 
 
 
 
 
 
 
 
87951a7
 
83fd0ba
 
c84a261
83fd0ba
 
55f1779
83fd0ba
 
 
 
 
 
 
 
 
 
 
87951a7
55f1779
87951a7
 
 
 
 
 
 
 
83fd0ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55f1779
 
83fd0ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55f1779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83fd0ba
 
 
 
 
 
55f1779
 
 
 
 
 
 
83fd0ba
 
 
 
 
55f1779
 
 
83fd0ba
 
 
 
55f1779
 
83fd0ba
 
55f1779
83fd0ba
 
 
 
55f1779
83fd0ba
55f1779
 
83fd0ba
55f1779
83fd0ba
 
87951a7
83fd0ba
55f1779
83fd0ba
 
 
 
55f1779
87951a7
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
from PIL import Image, ImageDraw, ImageOps, ImageFilter, ImageFont
import numpy as np
import math
import gradio as gr
from skimage.metrics import structural_similarity as ssim
import cv2

##### 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

# Target number of characters per row
TARGET_WIDTH = 200

# Character dimensions (width and height in pixels) used to match image aspect ratio to character aspect ratio
CHAR_W = 6
CHAR_H = 14

##### ASCII ART GENERATION #####
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
    
    # Calculate the scaling factor to match the target character width
    scale_factor = TARGET_WIDTH / width
    
    # Resize the image, maintaining the aspect ratio considering the character dimensions
    new_width = TARGET_WIDTH
    new_height = int(scale_factor * height * (CHAR_W / CHAR_H))  # Adjust height to maintain aspect ratio
    
    # Resize the image based on the calculated dimensions
    im = image.resize((new_width, new_height))

    # 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)
    height = len(lines) 
    
    # 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


##### EVALUATION #####
def ascii_to_image(ascii_art, width, height):
    """Convert ASCII art back to an image by mapping characters to grayscale values."""
    char_to_gray = {c: int((i / CHAR_LEN) * 255) for i, c in enumerate(CHAR_ARRAY)}
    ascii_lines = ascii_art.split('\n')
    
    # Create an empty numpy array to store the pixel values
    ascii_image = np.zeros((height, width), dtype=np.uint8)
    
    for i, line in enumerate(ascii_lines):
        if i >= height:
            break
        for j, char in enumerate(line):
            if j >= width:
                break
            ascii_image[i, j] = char_to_gray.get(char, 255)  # Default to white if character not in mapping

    return Image.fromarray(ascii_image)

def calculate_ssi(original_image, ascii_art_image):
    """Calculate Structural Similarity Index (SSI) between the original and reconstructed images."""
    original_image = original_image.convert("L")
    
    # Resize the original image to match the dimensions of the ASCII art image
    original_image_resized = original_image.resize(ascii_art_image.size)
    
    # Convert both images to NumPy arrays
    original_array = np.array(original_image_resized)
    ascii_array = np.array(ascii_art_image)

    # Calculate SSI
    ssi_value, _ = ssim(original_array, ascii_array, full=True)
    return ssi_value


##### MAIN FUNCTION FOR GRADIO INTERFACE #####
def process_image(image):
    """Process the input image to generate both an ASCII art image and a downloadable text file."""
    resized_image = load_and_preprocess_image(image)
    ascii_art = create_ascii_art(resized_image)

    output_image = draw_ascii_image(ascii_art, char_width=CHAR_W, char_height=CHAR_H, font_size=10)
    
    # Convert the ASCII art back into an image for SSI comparison
    ascii_art_image = ascii_to_image(ascii_art, resized_image.width, resized_image.height)
    
    # Calculate SSI between the original and ASCII art image
    ssi_value = calculate_ssi(image, ascii_art_image)
    
    # 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)

    print(f"Structural Similarity Index (SSI): {ssi_value}")
    
    return output_image, ascii_txt_path, ssi_value


##### GRADIO INTERFACE #####
def gradio_interface(image):
    ascii_image, txt_file, ssi_value = process_image(image)
    return ascii_image, txt_file, ssi_value

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),
        gr.Textbox(label="SSI Value")
    ],
    title="ASCII Art Generator with SSI Metric",
    description="Upload an image, generate ASCII art, and calculate the Structural Similarity Index (SSI).",
    allow_flagging="never",
        examples=[
        ['images/building.jpg'],
        ['images/cat.webp'],
        ['images/dog.jpg'],
        ['images/people.jpg'],
        ['images/cartoon.png'],
        ['images/einstein.jpg'],
    ],
)


demo.launch()