Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple script providing multiline text draw with emoji support (Pillow & emojicdn)
"""Simple script providing multiline text draw with emoji support (Pillow & emojicdn)"""
import io
from requests import get
from textwrap import wrap
from urllib.parse import quote_plus
from emoji import emojize, demojize, UNICODE_EMOJI
from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError
#-----#
__author__ = "kubinka0505"
__date__ = "07.01.2021"
__version__ = "0.5"
__credits__ = __author__
__status__ = "Development"
__license__ = "Apache 2.0"
__changelog__ = {
"0.1": ["Initial release"],
"0.2": [
"Increased code readability",
"Easier alignment manipulation",
"Modified Lines → Images system",
],
"0.3": ["Typo fixes"],
"0.4": ["Typo fixes"],
"0.5": ["Fixed an issue where the text line spacing was incorrect if it contained characters of different heights"],
}
#-----#
class EmojiUtils:
"""Various emoji utility."""
#---#
def __init__(self, Name: str):
"""Emoji have to start and end with the semicolons!"""
self.Name = Name
#---#
def get_image(self, Style: int = 5) -> Image.open:
"""Gets Emoji image from "emojicdn.elk.sh" API using style lists from its GitHub main page."""
Styles = {
0: 'apple', 1: 'google', 2: 'microsoft', 3: 'samsung',
4: 'whatsapp', 5: 'twitter', 6: 'facebook',
7: 'joypixels', 8: 'openmoji', 9: 'emojidex',
10: 'lg', 11: 'htc', 12: 'mozilla'
}
#---#
Emoji_Image = Image.open(get(
"https://emojicdn.elk.sh/{0}?style={1}".format(
quote_plus(emojize(self.Name, use_aliases = True)),
str(Styles[Style]).lower()
), stream = True
).raw
)
#---#
ImageIO = io.BytesIO()
Emoji_Image.save(ImageIO, "PNG")
ImageIO.seek(0)
return Image.open(io.BytesIO(ImageIO.read()))
ImageIO.close()
#---#
def is_available(Name: str):
"""Checks if emoji is available to use."""
return emojize(Name, use_aliases = True) in UNICODE_EMOJI
#---#
def search(Name: str):
"""Searches for emojis by `self.Name`."""
return [x for x in UNICODE_EMOJI.values() if x.__contains__(Name[1:-1])]
#----------#
Image_Size = (5000, 5000)
Text = r":snake:"
Text_Wrap = 12
Text_Color = "#000000"
Font_Path = r"Font.otf"
Font_Size = 250
Emoji_Style = "apple"
#----------#
# Supported emojis (+ aliases (?)) lists
# https://www.webfx.com/tools/emoji-cheat-sheet/
#
# Supported emoji styles
# https://github.com/benborgers/emojicdn#emoji-style
#
# Check if emoji is available:
# ```python
# >>> Emoji = ":thinking:"
# >>> Emoji = EmojiUtils.search(Emoji)[0]
# >>>
# >>> EmojiUtils.is_available(Emoji)
# True
# ```
#----------#
Base = Image.new("RGBA", Image_Size, (0,) * 4)
Text = wrap(emojize(Text, use_aliases = True), Text_Wrap)
Font = ImageFont.truetype(Font_Path, Font_Size)
__Styles = {Value: Key for Key, Value in enumerate(EmojiUtils.get_image.__code__.co_consts[1:14])}
Images_List = []
Expanded_Images_List = []
Spacing_Y = 0
for Line in Text:
Line_Image = Base.copy()
Line_ImageDraw = ImageDraw.Draw(Line_Image)
Spacing_X = 0
#---#
for Character in Line:
if Character in UNICODE_EMOJI:
try:
Emoji = EmojiUtils(demojize(Character)).get_image(__Styles[Emoji_Style])
except UnidentifiedImageError as Error:
raise SystemExit("{0}: The `{1}` emoji is not supported on the {2} style. Image processing aborted.".format(
Error.__class__.__name__,
demojize(Character),
Emoji_Style.title(),
)
)
#---#
Emoji = Emoji.resize(
(Font.getsize("|")[1],) * 2,
Image.LANCZOS
)
#---#
Line_Image.paste(
Emoji,
(Spacing_X, 0),
Emoji
)
Spacing_X += Emoji.size[0]
Character = Character.replace(Character, "")
#---#
Line_ImageDraw.text(
xy = (Spacing_X, 0),
text = Character,
font = Font,
fill = tuple(int(Text_Color.lstrip("#")[x:x+2], 16) for x in (0, 2, 4))
)
#---#
Spacing_X += Font.getsize(Character)[0]
#---#
Spacing_Y += Font.getsize(Line)[1]
Line_Image = Line_Image.crop(Line_Image.getbbox())
Images_List.append(Line_Image)
#---#
for IMG in Images_List:
Text_Image = Image.new("RGBA", (IMG.size[0], IMG.size[1] + Font.getsize("|")[1]), (0,) * 4)
Text_Image.paste(IMG, (0, Font.getsize("|")[1]), IMG)
Expanded_Images_List.append(Text_Image)
Spacing_Y = 0
for IMG in Expanded_Images_List:
Base.paste(IMG, ((Base.size[0] - IMG.size[0]) // 2, Spacing_Y), IMG)
Spacing_Y += IMG.size[1] // 3 * 2 # Change values for line height
#---#
Base = Base.crop(Base.getbbox())
Base.show()
@alisentinel
Copy link

alisentinel commented Dec 18, 2020

Thanks for your great solution but it errors
Details:
Traceback (most recent call last): File "test.py", line 126, in <module> Image.LANCZOS File "ANACONDA_PATH\site-packages\PIL\Image.py", line 1900, in resize im = im.resize(size, resample, box) File "ANACONDA_PATH\site-packages\PIL\Image.py", line 1922, in resize return self._new(self.im.resize(size, resample, box)) ValueError: height and width must be > 0

@kubinka0505
Copy link
Author

kubinka0505 commented Dec 20, 2020

@alisentinel
The issue was with emojis scaling conditional statement, I think it does work now.

@NightMachinery
Copy link

NightMachinery commented Mar 14, 2021

Is it possible to make bidirectional text (containing RTL text) work with this? I have a script for non-emoji normal bidirectional rendering (which I forked off the internet): https://github.com/NightMachinary/.shells/blob/9b6bac7871958d6ac594681e2c7c7282dbe022d9/scripts/python/text2img.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment