45

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:

PhotoShop

and what comes out of PIL:

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)
Community
  • 1
  • 1
exiva
  • 1,247
  • 1
  • 11
  • 13

6 Answers6

69

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,

final result

which is a lot better than what I was getting before with the same font.

exiva
  • 1,247
  • 1
  • 11
  • 13
  • I need the text in center, not left aligned. Is there a way to do that using the above solution? – Aarohi Kulkarni Dec 01 '15 at 07:04
  • 3
    Keep in mind though that fonts usually provide different shapes for different sizes (not just scaling up and down one shape). This is especially noticeable in smaller target sizes. – Tamás Szelei Feb 13 '16 at 20:29
  • Another way to put it: don't assume that you need to do this. I've found that rendering text using this function is quite slow, and this could add unnecessary slowdown. – chris Jul 18 '17 at 13:48
21

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)

Rendred image

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 4
    Ugh. this is looking like way too many dependencies for a simple application. (I'm trying to get pango & cairo setup on 10.6 with homebrew) ...I might have to rethink some things. Thank you for the assistance though. – exiva Mar 24 '11 at 06:38
  • There are other imaging librariries that may be easier to setup. The first taht comes to my mind is pygame, but it can't do any serious work with text (no anti-aliasing or subpixel rendering at all). Maybe wrappers for imagemagick (pythonmagick) can be easier to deal with. – jsbueno Mar 24 '11 at 14:19
  • Just an update: Pygame does have antialiasing and nice rendering for fonts. Just some of its drawing primitives that does not. And it is an orderof magnitude easier to deal with than the pango + cairo example above. – jsbueno Nov 11 '15 at 11:44
  • 1
    @jsbueno Pygame's font rendering is ugly on Windows, most fonts metrics are broken and not even close to realistic rendering. (that is of pygame 1.9. Probably last versions are better, didn't try). – Mikhail V Nov 12 '16 at 21:35
12

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/

dave-holm
  • 358
  • 1
  • 9
  • Thanks dave, your answer was really helpful to me as I was looking to programmatically generate large individual alphabet letters as images in a variety of colours. Imagemagick was able to do the positioning and handled the image dimensions and other parameters painlessly. – MattH Nov 29 '12 at 23:33
6

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')

image that comes out-

Aditya Shankar
  • 702
  • 6
  • 12
  • Worked much better for me than the other two. This is the first time I've heard of "wand", which is a Python binding to ImageMagick. – YakovK Jun 25 '19 at 00:40
6

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

Baxorr
  • 298
  • 6
  • 20
  • 4
    Using `"L"` still resulted in anti-aliased text for me, but `draw.fontmode = "1"` worked. For reference, the modes are documented [here](https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes); `ImageDraw`'s `fontmode` member doesn't seem to be documented anywhere; thanks for taking the time to post this answer! – Reign of Error Nov 21 '20 at 23:05
-4

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)
sharpshadow
  • 1,256
  • 1
  • 9
  • 9