Last active
August 24, 2024 18:19
-
-
Save wshanshan/c825efca4501a491447056849dd207d6 to your computer and use it in GitHub Desktop.
Create ASCII art from an image
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PIL import Image, ImageDraw, ImageFont | |
from colour import Color | |
import numpy as np | |
# The function convert an image to ascii art | |
# f: Input filename | |
# SC: the horizontal pixel sampling rate. It should be between 0(exclusive) and 1(inclusive). The larger the number, the more details in the output. | |
# If you want the ascii art output be the same size as input, use ~ 1/ font size width. | |
# GCF: >0. It's an image tuning factor. If GCF>1, the image will look brighter; if 0<GCF<1, the image will look darker. | |
# out_f: output filename | |
# color1, color2, bgcolor: follow W3C color naming https://www.w3.org/TR/css3-color/#svg-color | |
# | |
# Copyright 2017, Shanshan Wang, MIT license | |
def asciiart(in_f, SC, GCF, out_f, color1='black', color2='blue', bgcolor='white'): | |
# The array of ascii symbols from white to black | |
chars = np.asarray(list(' .,:irs?@9B&#')) | |
# Load the fonts and then get the the height and width of a typical symbol | |
# You can use different fonts here | |
font = ImageFont.load_default() | |
letter_width = font.getsize("x")[0] | |
letter_height = font.getsize("x")[1] | |
WCF = letter_height/letter_width | |
#open the input file | |
img = Image.open(in_f) | |
#Based on the desired output image size, calculate how many ascii letters are needed on the width and height | |
widthByLetter=round(img.size[0]*SC*WCF) | |
heightByLetter = round(img.size[1]*SC) | |
S = (widthByLetter, heightByLetter) | |
#Resize the image based on the symbol width and height | |
img = img.resize(S) | |
#Get the RGB color values of each sampled pixel point and convert them to graycolor using the average method. | |
# Refer to https://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/ to know about the algorithm | |
img = np.sum(np.asarray(img), axis=2) | |
# Normalize the results, enhance and reduce the brightness contrast. | |
# Map grayscale values to bins of symbols | |
img -= img.min() | |
img = (1.0 - img/img.max())**GCF*(chars.size-1) | |
# Generate the ascii art symbols | |
lines = ("\n".join( ("".join(r) for r in chars[img.astype(int)]) )).split("\n") | |
# Create gradient color bins | |
nbins = len(lines) | |
colorRange =list(Color(color1).range_to(Color(color2), nbins)) | |
#Create an image object, set its width and height | |
newImg_width= letter_width *widthByLetter | |
newImg_height = letter_height * heightByLetter | |
newImg = Image.new("RGBA", (newImg_width, newImg_height), bgcolor) | |
draw = ImageDraw.Draw(newImg) | |
# Print symbols to image | |
leftpadding=0 | |
y = 0 | |
lineIdx=0 | |
for line in lines: | |
color = colorRange[lineIdx] | |
lineIdx +=1 | |
draw.text((leftpadding, y), line, color.hex, font=font) | |
y += letter_height | |
# Save the image file | |
newImg.save(out_f) | |
# main() | |
if __name__=='__main__': | |
inputf = "IQaH96.jpeg" # Input image file name | |
SC = 0.1 # pixel sampling rate in width | |
GCF= 2 # contrast adjustment | |
asciiart(inputf, SC, GCF, "results.png") #default color, black to blue | |
asciiart(inputf, SC, GCF, "results_pink.png","blue","pink") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, as @vyacheslavparkhomenko pointed out this no longer works due to Pillow removing FreeTypeFont.getsize(). Moreover, ImageFont's default font is no longer monospaced, and thus deforms the image. I created this fork: https://gist.github.com/geconf/9c44d8de2d37408b08aca9202c041c6a and wrote a fix