Python Imaging Library - Text rendering
Asked Answered
D

7

47

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)
Dickerson answered 24/3, 2011 at 4:7 Comment(0)
D
70

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.

Dickerson answered 25/3, 2011 at 8:29 Comment(3)
I need the text in center, not left aligned. Is there a way to do that using the above solution?Microphotograph
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.Vulgarity
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.Ondrej
B
23

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

Bulb answered 24/3, 2011 at 4:57 Comment(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.Dickerson
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.Bulb
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.Bulb
@Bulb 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).Babu
N
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/

Ng answered 24/3, 2011 at 4:43 Comment(1)
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.Stevana
Q
7

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-

Quartis answered 30/11, 2017 at 2:11 Comment(1)
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.Cattegat
Q
7

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

Quenchless answered 9/7, 2020 at 10:49 Comment(1)
Using "L" still resulted in anti-aliased text for me, but draw.fontmode = "1" worked. For reference, the modes are documented here; ImageDraw's fontmode member doesn't seem to be documented anywhere; thanks for taking the time to post this answer!Norland
M
0

From your example:

d_usr = ImageDraw.Draw(image)
d_usr.fontmode = "1" ### NEW
d_usr.text((105,280), "Travis L.",(0,0,0), font=usr_font)
Macmillan answered 6/5 at 17:30 Comment(0)
C
-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)
Cleanser answered 1/11, 2014 at 2:4 Comment(4)
It indeed makes it more bold, but I cannot say that it is better. Perhaps that is subjective.Thermo
Bold and less crisp on the border, which I see as better. At least it comes near the standard quality of Photoshop or GIMP.Cleanser
Why does rendering the same thing in the same place a second time make a difference?Stonedead
@Stonedead - I would guess that pixels that are a shade of gray rather than fully black are darkened when you draw it twice.Houck

© 2022 - 2024 — McMap. All rights reserved.