Skip to content

Commit

Permalink
first version of ASCII generator command
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathanlauga committed Nov 11, 2021
1 parent 991774d commit f6fc219
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 8 deletions.
3 changes: 3 additions & 0 deletions ascii_art/cmd/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Cmd submodules
"""
from .ascii_generator import ascii_generator
44 changes: 44 additions & 0 deletions ascii_art/cmd/ascii_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""ASCII Art generator Click command
"""
import click

from ascii_art.generator import generate_ascii_art_from_img
from ascii_art.io import get_img_from_url
from ascii_art.io import get_img_url_list_from_keyword


@click.command()
@click.argument("keyword")
@click.option("-c", "--cols", default=79, help="Number of columns for ASCII Art.")
@click.option("-s", "--scale", default=0.43, help="Height scale for ASCII Art.")
@click.option(
"-m",
"--more-levels",
default=True,
help="Whether you want a grayscale of 70 or 10.",
)
def ascii_generator(keyword, cols, scale, more_levels):
"""Shows differents ASCII Art given a keyword
You can change the number of columns and the scale.
There are 2 gray scale available 10 and 70 (default is 70)
"""

img_list = get_img_url_list_from_keyword(keyword)

for title, url in img_list:
img = get_img_from_url(url)
ascii_img = generate_ascii_art_from_img(img, cols, scale, more_levels)

print("Image name : %s" % title)
print(ascii_img)

ans = input("Do you want to keep this image ? (y/n)").lower()

if ans == "y":
print("Awesome !")
exit(0)


if __name__ == "__main__":
ascii_generator()
3 changes: 3 additions & 0 deletions ascii_art/generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
""" Generator submodule
"""
from .generate_ascii_art import generate_ascii_art_from_img
104 changes: 104 additions & 0 deletions ascii_art/generator/generate_ascii_art.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""ASCII Art generator functions
Based on https://www.geeksforgeeks.org/converting-image-ascii-image-python/
"""
import numpy as np

GRAY_SCALE_70 = (
"$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
)
GRAY_SCALE_10 = "@%#*+=-:. "


def get_average(image):
"""
Given PIL Image, return average value of grayscale value
"""
# get image as numpy array
im = np.array(image)

# get shape
w, h = im.shape

# get average
return np.average(im.reshape(w * h))


def generate_ascii_art_from_img(image, cols=70, scale=0.43, more_levels=True) -> str:
"""Generate a string of a ASCII Art given an image
Parameters
----------
image : Image
PIL Image (gray scale)
cols : int, optional
Number of columns for the output, by default 70
scale : float, optional
Scale for the height, by default 0.43
more_levels : bool, optional
Whether you want 70 gray scales or 10, by default True
Returns
-------
str
ASCII Art string
"""
# store dimensions
W, H = image.size[0], image.size[1]
# compute width of tile
w = W / cols
# compute tile height based on aspect ratio and scale
h = w / scale
# compute number of rows
rows = int(H / h)

# check if image size is too small
if cols > W or rows > H:
print("Image too small for specified cols!")
exit(0)

# ascii image is a list of character strings
aimg = []

# generate list of dimensions
for j in range(rows):
y1 = int(j * h)
y2 = int((j + 1) * h)

# correct last tile
if j == rows - 1:
y2 = H

# append an empty string
aimg.append("")

for i in range(cols):

# crop image to tile
x1 = int(i * w)
x2 = int((i + 1) * w)

# correct last tile
if i == cols - 1:
x2 = W

# crop image to extract tile
img = image.crop((x1, y1, x2, y2))

# get average luminance
avg = int(get_average(img))

# look up ascii char
if more_levels:
gsval = GRAY_SCALE_70[int((avg * 69) / 255)]
else:
gsval = GRAY_SCALE_10[int((avg * 9) / 255)]

# append ascii char to string
aimg[j] += gsval

# Convert as string
aimg = "\n".join(aimg)

# return txt image
return aimg
6 changes: 6 additions & 0 deletions ascii_art/io/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Input output submodule
"""
from .img import get_img_from_url
from .scrap import get_data_from_url
from .scrap import get_img_url_list_from_keyword
from .scrap import scrap_img_on_bing
25 changes: 25 additions & 0 deletions ascii_art/io/img.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from io import BytesIO

from PIL import Image

from .scrap import get_data_from_url


def get_img_from_url(url: str) -> Image:
"""Returns a gray image given an url
Parameters
----------
url : str
Url where the image is
Returns
-------
Image
Gray image
"""
ans = get_data_from_url(url)
# Open as grayscale
img = Image.open(BytesIO(ans.content)).convert("L")

return img
135 changes: 135 additions & 0 deletions ascii_art/io/scrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"""Scrap functions
"""
import re

import requests
from bs4 import BeautifulSoup


DEFAULT_HEADER = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
"Accept-Language": "en-GB,en;q=0.5",
}
BING_IMG_URL = "https://www.bing.com/images/search?q="


class RequestException(Exception):
pass


def create_header(url: str) -> dict:
"""Create a header dictionnary if it need
to be changed (e.g. for an API)
Parameters
----------
url : str
url to request (needed to get the host)
Returns
-------
dict
header dictionnary
"""
header = DEFAULT_HEADER

url_split = url.split("//")
http = url_split[0]
host = url_split[1].split("/")[0]

header["Host"] = host
header["Referer"] = http + "//" + host
header["Origin"] = http + "//" + host

return header


def get_data_from_url(url: str) -> requests.Response:
"""Return answer of a request get
from a wanted url
Parameters
----------
url : str
url to request
Returns
-------
requests.Response
answer from requests.get function
Raises
------
TypeError
url must be a string
ValueError
url must start with 'http'
"""

if not isinstance(url, str):
raise TypeError("url must be a string")
if not url.startswith("http"):
raise ValueError("url must start with 'http'")

headers = create_header(url)
r = requests.get(url, headers=headers)

return r


def scrap_img_on_bing(keyword: str) -> list:
"""Scrap images on first page of Bing image page
Parameters
----------
keyword : str
kerword for the research
Returns
-------
list
List of tuple (title, image url)
Raises
------
RequestException
Request response is not valid
"""

url = BING_IMG_URL + keyword
ans = get_data_from_url(url)

if ans.status_code != 200:
raise RequestException(
"Request response is not valid (status code %s)" % ans.status_code
)

soup = BeautifulSoup(ans.text, "html.parser")
images = soup.find_all("img")

img_urls = []

# regex for height and width on url
regex = re.compile(r"&w=[0-9]*&h=[0-9]*", re.IGNORECASE)
for item in images:
if not item.has_attr("src2"):
continue

img_url = item.get("src2")
img_url = regex.sub("&w=200&h=200", img_url)
img_ = (item.get("alt"), img_url)

img_urls.append(img_)

return img_urls


def get_img_url_list_from_keyword(keyword: str, engine="bing") -> list:

if engine not in ["bing"]:
raise ValueError("engine muste be in ['bing']")

if engine == "bing":
img_list = scrap_img_on_bing(keyword)

return img_list
2 changes: 0 additions & 2 deletions ascii_art/scrap/__init__.py

This file was deleted.

2 changes: 0 additions & 2 deletions ascii_art/scrap/scrap_img.py

This file was deleted.

2 changes: 0 additions & 2 deletions ascii_art/scrap/tests/test_scrap_img.py

This file was deleted.

11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Setup.py file
"""
from distutils.core import setup
from setuptools import find_packages
from setuptools import setup


with open("version", "r") as f:
version = f.read()
Expand All @@ -14,5 +16,10 @@
author="Nathan LAUGA",
author_email="[email protected]",
url=url,
packages=["ascii_art"],
packages=find_packages(),
entry_points={
"console_scripts": [
"ascii-generator = ascii_art.cmd:ascii_generator",
],
},
)

0 comments on commit f6fc219

Please sign in to comment.