Fonts aliasing in Pillow - python

I'm using Pillow 3.1.1 image library and Python 3.5.1.
I'm trying to use Pillow for drawing fonts on images. But results looks absolutely ugly and unacceptable.
1st example: looks like font not antialiased. But docs contains absolutely nothing on aliasing fonts. I've tried to make some changes (e.g. set fonttype), but texts still looks terrible.
1st example
And second example. Sometimes characters just overlay each other. And I don't have any idea how it could be fixed.
2nd example
I'm so frustrated with my experience. Is it possible to fix aliasing problem in Pillow or I should look to ImageMagick side?
Aliasing problem is my main concern, I cannot use fonts rendered such way.
Thanks for your attention!
Code example:
from PIL import Image, ImageDraw, ImageFont
DEFAULT_OFFSET = (100, 160, )
def draw_text(image, text):
base = Image.open(image).convert('RGBA')
txt_image = Image.new('RGBA', base.size, (255, 255, 255, 0))
ttf = get_font()
fnt = ImageFont.truetype(ttf, 40)
d = ImageDraw.Draw(txt_image)
# just return some string in format 'blah-blah\nblah-blah'
multiline = generate_multiline(txt_image, text)
d.multiline_text(DEFAULT_OFFSET, multiline, align='left', font=fnt, fill=(40, 40, 40, 200))
out = Image.alpha_composite(base, txt_image)
out.show()

Accordingly to martineau comment, Pillow doesn't support font anti-aliasing.

Related

Specific unicode character not displayed properly in python image

I am trying to create an image with some unicode characters but some of them aren't being displayed properly. You can see in this sample image:
The character in question I am trying to print is \u2BEA, which is a half star image. Unfortunately the output just shows me the generic missing character icon.
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
output_image = Image.new('RGB', (200,100), (0,0,0))
text = '\u2605\u2605\u2605\u2BEA\u2606'
font = ImageFont.truetype("C:\\Windows\\Fonts\\yugothb.ttc", 18)
draw = ImageDraw.Draw(output_image)
draw.text((10, 10), text, (255,255,255), font=font)
output_image.show()
Note: you may need to adjust the font location for your computer
In my sample code I'm using the Yu Gothic Bold font and not getting the correct output. I have tried using other fonts such as Arial and Calibri, which produced even worse results.
My thought process is that the character isn't part of of the font but I have yet to find a font that supports it.
Does anyone know of a free font that I can use that will display this character?
Unicode 'u2BEA' defined as "STAR WITH LEFT HALF BLACK", but not defined in any font file on my platform.
Maybe you can download and use following font files,
BabelStone Han
https://www.babelstone.co.uk/Fonts/Download/BabelStoneHan.ttf
Symbola
https://dn-works.com/wp-content/uploads/2020/UFAS-Fonts/Symbola.zip
Unifont
https://unifoundry.com/pub/unifont/unifont-13.0.06/font-builds/unifont-13.0.06.ttf
Note: Last one with large font than '\u2605' and '\u2606'.
I didn't find any font that includes \u2BEA or \u2BE8 (ref: https://www.unicode.org/charts/nameslist/n_2B00.html#2BEA), but the following icons by Font Awesome might meet your needs: Star Icon (Solid) Stat Half Alt Icon (Solid) Star Icon (Regular)
In Font Awesome, f005 refers to the 'star' icon, and f5c0 refers to the 'star-half-alt' icon.
So, you can download Font Awesome Free for Desktop at https://use.fontawesome.com/releases/v5.15.3/fontawesome-free-5.15.3-desktop.zip, then draw solid \uF005\uF005\uF005\uF5C0 with 'Font Awesome 5 Free-Solid-900.otf' and a single hollow star \uF005 with 'Font Awesome 5 Free-Regular-400.otf'.
The following works properly:
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
output_image = Image.new('RGB', (200,100), (0,0,0))
solid = ImageFont.truetype('fontawesome-free-5.15.3-desktop/otfs/Font Awesome 5 Free-Solid-900.otf', 18)
regular = ImageFont.truetype('fontawesome-free-5.15.3-desktop/otfs/Font Awesome 5 Free-Regular-400.otf', 18)
draw = ImageDraw.Draw(output_image)
draw.text((10, 10), '\uF005' * 3 + '\uF5C0', (255,255,255), font=solid)
draw.text((90, 10), '\uF005', (255,255,255), font=regular)
output_image.show()

Choosing a PIL.ImageFont by font name rather than filename, and cross-platform font

ImageFont.truetype requires a filename to work, such as:
font = ImageFont.truetype("ariblk.ttf") # Arial Black
Is there a way with PIL, to load a font by name, rather than filename?
Context: I would like to load a bold (with heavy weight) sans-serif font, that would work on any platform Windows, Linux, Mac.
I don't think ImageFont.truetype("ariblk.ttf") will work cross-platform, is it possible to load it with ImageFont.truetype("Arial Black") or, better, ImageFont.truetype("sans-serif;bold") that would work on all platforms?
Looking at the documentation of Pillow's ImageFont module, there's no such an option, no.
A handy workaround might be to use Matplotlib's font_manager module for that: A module for finding, managing, and using fonts across platforms. Using the FontProperties and findfont, you should get the a valid path to a font with the given properties, which you can then use in the common ImageFont.truetype call.
Here's a small example, which runs perfectly fine on my Windows machine. Unfortunately, I don't have any other OS nearby to test.
from matplotlib import font_manager
from PIL import Image, ImageDraw, ImageFont
font = font_manager.FontProperties(family='sans-serif', weight='bold')
file = font_manager.findfont(font)
print(file)
img = Image.new('RGB', (400, 300), (255, 255, 255))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(file, 48)
draw.text((20, 20), 'Hello World', font=font, fill=(255, 0, 0))
img.save('test.png')
The print output:
...\Lib\site-packages\matplotlib\mpl-data\fonts\ttf\DejaVuSans-Bold.ttf
The image output:
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
Matplotlib: 3.3.4
Pillow: 8.1.0
----------------------------------------
The top answer by HansHirse is fantastic but just to add to it for anyone still looking, you can also use matplotlib.font_manager to find specific fonts:
from matplotlib import font_manager
#print all available fonts
print(font_manager.get_font_names())
#Find the path for a specific font:
file = font_manager.findfont('Helvetica Neue')
#Load the font to pillow
font = ImageFont.truetype(file, 20)
#draw text onto image using pillow:
draw.text((20, 20), 'Hello World', font=font, fill=(0, 0, 0))
If you are particularly worried about fonts not being available on other systems but would like to keep your default, you can combine these in a try...except... cause:
try:
file = font_manager.findfont('Helvetica Neue')
font = ImageFont.truetype(file, fontsize)
except:
font_search = font_manager.FontProperties(family='sans-serif', weight='normal')
file = font_manager.findfont(font_search)
font = ImageFont.truetype(file, fontsize)
This way you can be confident you either get the default font or a similar font if it is not being available.

Rendering Emoji with PIL

I am trying to make images out of tweets, however some of them contain Emojis. I am using PIL to render my images and the Symbola font.
The text is in unicode utf-8 encoding and the Symbola font does include the emojis. Here is an abridged version of the code:
from PIL import Image, ImageFont, ImageDraw
text = u"\U0001f300" #CYCLONE emoji
image = Image.new("RGBA", (100,100), (255,255,255))
font = ImageFont.truetype("Symbola.ttf", 60, encoding='unic')
draw = ImageDraw.Draw(image)
draw.text((0,0), text, (0,0,0), font=font)
image.save("Test.png")
image.show()
This just renders and image with two rectangles instead of the emoji
Would appreciate any help or ideas.
Thanks!
EDIT: As falsetru pointed out, this code does run in Ubuntu, however it doesn't run on Windows or on Mac. Any ideas?
If the symbol CYCLONE u"\U0001f300" (I download a Symbola.tff from web) then is a very simple to use with PIL:
from PIL import Image, ImageDraw, ImageFont, ImageFilter
#configuration
font_size=36
width=500
height=100
back_ground_color=(255,255,255)
font_size=36
font_color=(0,0,0)
unicode_text =u"\U0001f300"
im = Image.new ( "RGB", (width,height), back_ground_color )
draw = ImageDraw.Draw ( im )
unicode_font = ImageFont.truetype("Symbola.ttf", font_size)
draw.text ( (10,10), unicode_text, font=unicode_font, fill=font_color )
im.show()
Take a look at this
There was a bug in Pillow, see #1774 or #3777. This should now be fixed in version 6.1 of Pillow with PR#3780, but only for Python 3.x.
If you are looking to write symbols with your original font, you can do this by merging your font with Symbola.ttf or any emojis font. You can merge the two fonts using fontforge (https://fontforge.org/).
start fontforge, open up your main font,
Click menu: element->merge fonts and choose your emoji font (Symbola.ttf).
answer no for any popup dialog.
optionally change your new font's name: element->font info.
finally go to file->generate fonts when done and save it as ttf (TrueType).
Now, you can use your generated font to draw text with emojis!

PIL cuts off top of letters

I've spent a lot of time making my first web application using Python, and I'm using pil for generating images. After reading a lot, I've managed to implement proper text aligning, wrapping, generating files with many extensions etc.
However, all the text generated by PIL is cut off at the top. Here's a sample.
It should say ŻÓĆjygpq in a variety of fonts (the font names are on the left).
I've found few posts here: fonts clipping with PIL,
but I'd like to avoid using another module (aggdraw); since I've figured out so many things in PIL already I'd like to stick to that.
I've tried many fonts in different sizes, but text is still cut off. I even tried to use PIL fonts, but it still doesn't work. [Also converting OTF to BDF, and to PIL].
This is on Ubuntu. What should I try next?
I hope to be wrong on this one, but the only correct fix relies on patching how _imagingft.c
renders the text. PIL depends on FreeType for this task, but PIL seems to be miscalculating the positioning. Also, the height in getsize is overestimated (although that doesn't cause problem). For the moment, I've put a patch to handle these issues at: http://pastebin.com/jP2iLkDN (there seems to be a better way to patch the render code).
Here are some examples of the output I get without the patch and with the patch, respectively:
Results using the code present in the linked discussion. On OSX:
On Ubuntu:
Here is the code to generate the top figures:
# -*- encoding: utf8 -*-
import sys
import Image, ImageDraw, ImageFont
im = Image.new("RGBA", (1000, 1000), 'white')
draw = ImageDraw.Draw(im)
start_y = 7
text = u'\u00d1\u00d3yŻ\u00d4Ćgp\u010c\u0137'
for i in xrange(28, 46, 2):
font = ImageFont.truetype('Junicode-Bold.ttf', i)
width, height = font.getsize(text)
draw.rectangle((0, start_y, width, height + start_y), outline='blue')
draw.text((0, start_y), text, font=font, fill='black')
start_y += height + 7
im.crop((0, 0, width + 1, start_y + 2)).save(sys.argv[1])
The bottom figures were generated according to code present in the linked topic about PIL cutting off parts of the text.
Not the best solution but I see people have solved this by adding a leading a trailing space to their text.

Python Imaging Library - Text rendering

I'm trying to render some text using PIL, but the result that comes out is, frankly, crap.
For example, here's some text I wrote in Photoshop:
and what comes out of PIL:
As you can see, the results from PIL is less than satisfactory. Maybe I'm just being picky, but is there any way to draw text using PIL that gets results more close to my reference image?
Here's the code I'm using on Python 2.7 with PIL 1.1.7
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
I came up with my own solution that I find acceptable.
What I did was render the text large, like 3x the size it needs to be then scale it resize it down with antialiasing, it's not 100% perfect, but it's a hell of a lot better than default, and doesn't require cairo or pango.
for example,
image = Image.new("RGBA", (600,150), (255,255,255))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", fontsize)
draw.text((10, 0), txt, (0,0,0), font=font)
img_resized = image.resize((188,45), Image.ANTIALIAS)
and you endup with this result,
which is a lot better than what I was getting before with the same font.
Try using pycairo - the python bindings for the Cairo drawing library -- it is usefull for more refined drawing, with antialiased lines,
and such - and you can generate vector based images as well
Correctly handling fonts, and layout is complicated, and requires the use of
the "pango" and "pangocairo" libraries as well. Although they are made
for serious font work (all GTK+ widgets do use pango for font rendering),
the available docuemtnation and examples are extremely poor.
The sample bellow shows the prints available in the system and renders the
sample text in a font family passed as parameter on the command line.
# -*- coding: utf-8 -*-
import cairo
import pango
import pangocairo
import sys
surf = cairo.ImageSurface(cairo.FORMAT_ARGB32, 320, 120)
context = cairo.Context(surf)
#draw a background rectangle:
context.rectangle(0,0,320,120)
context.set_source_rgb(1, 1, 1)
context.fill()
#get font families:
font_map = pangocairo.cairo_font_map_get_default()
families = font_map.list_families()
# to see family names:
print [f.get_name() for f in font_map.list_families()]
#context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
# Positions drawing origin so that the text desired top-let corner is at 0,0
context.translate(50,25)
pangocairo_context = pangocairo.CairoContext(context)
pangocairo_context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
layout = pangocairo_context.create_layout()
fontname = sys.argv[1] if len(sys.argv) >= 2 else "Sans"
font = pango.FontDescription(fontname + " 25")
layout.set_font_description(font)
layout.set_text(u"Travis L.")
context.set_source_rgb(0, 0, 0)
pangocairo_context.update_layout(layout)
pangocairo_context.show_layout(layout)
with open("cairo_text.png", "wb") as image_file:
surf.write_to_png(image_file)
I've never used PIL, but a quick review of the documentation for the Draw method indicates that PIL provides a way to render simple graphics. Photoshop provides a way to render complex graphics. To get anywhere close to Photoshop-like results requires, at a minimum, font hinting and anti-aliasing. PIL's documentation doesn't even hint at having such capabilities. You may want to look at using an external tool that might do a better job of rendering text on images. For example, ImageMagick (you'll want to use the 8-bit version, which handles standard 24-bit RGB). You can find some text drawing samples here: http://www.imagemagick.org/Usage/draw/
Suggestion: use Wand or a different Imaging library
Here's an example with wand -
from wand.color import Color
from wand.image import Image
from wand.drawing import Drawing
from wand.compat import nested
with Drawing() as draw:
with Image(width=1000, height=100, background=Color('lightblue')) as img:
draw.font_family = 'Indie Flower'
draw.font_size = 40.0
draw.push()
draw.fill_color = Color('hsl(0%, 0%, 0%)')
draw.text(0,int(img.height/2 + 20), 'Hello, world!')
draw.pop()
draw(img)
img.save(filename='image.png')
In python3 there is an option for aliased fonts. I couldn't find this answer anywhere, hopefully it helps someone like me who found this question on google and had to dig a long time to find the answer.
draw = ImageDraw.Draw(img)
draw.fontmode = "L"
Mentioned in the docs here
You can also try to write the font two times it increases the quality immense.
image = Image.new("RGBA", (288,432), (255,255,255))
usr_font = ImageFont.truetype("resources/HelveticaNeueLight.ttf", 25)
d_usr = ImageDraw.Draw(image)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
d_usr = d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)

Categories