fonts clipping with PIL
Asked Answered
F

5

6

This image was created with PIL. See how the g's and the y's are cut off in this image? How can I prevent this?

http://img109.imageshack.us/img109/8874/screenshotep.png

The code that created this image is pretty straight forward (abbreviated):

import Image, ImageDraw, ImageFont

im = Image.new("RGBA", (200, 200), 'white')
draw = ImageDraw.Draw(im)

font = ImageFont.truetype("VeraSe.ttf", 12)

draw.text(
           (1, 1),
           " %s: " % "ggjyfFwe__",
           font=font,
           fill='black'
)

draw.text(
           (1, 30),
           " %s" % 15,
           font=font,
           fill='black'
)

im.show()

I tried it with a few different fonts, and it always gets clipped. Surprising;y, googleing "PIL font clipping" returns very few useful hits... I'm using python 2.6.4 and PIL 1.1.6 on Ubuntu 9.10

Florid answered 19/12, 2009 at 18:38 Comment(4)
Better tell us specific fonts you've tried, and what platform you are on. I just tried here on Win7 with consola.ttf and don't get the background overwriting that you see.Entropy
Unfortunately I don't have VeraSe.ttf here, and you haven't mentioned what platform you're on yet. I suspect Linux, not Windows, and that this is a platform-specific issue you're having.Entropy
This is also happening for me with georgia.ttf (part of the msttcorefonts package) using PIL 1.1.7 on Python 2.6 in Ubuntu 9.10. It seems like a problem with PIL where whatever buffer it temporarily renders to is not high enough.Hardin
If you still have interest on this, could you try the patch at https://mcmap.net/q/1914687/-pil-cuts-off-top-of-letters/… and report the results ?Covariance
B
3

Here's a late answer for this older question.

The problem appears to be that PIL and Pillow will clip the edges of rendered text. This most often shows on trailing wide characters and decenders (like 'y's). This can also appear on the top of some fonts. This has been a problem for at least ten years. It happens regardless of the size of the image on which text() is called. The conflict appears to choosing the bounding rectangle as "font.size * number_chars" instead of "whatever I actually need to render" and this occurs deep in the stack (_imagingft.c). Fixing this causes other problems, like lining up text rendered letter by letter.

Some solutions include:

  • Append a space to the end of your string. im.text(xy, my_text + ' ', ...)
  • For height issues, get the width of your text (font.getsize()), second render the text plus a good ascender and descender, chop the rendered text to the first reported width and the second actual height.
  • Use a different library such as AggDraw or pyvips.

This is referenced in various questions fonts clipping with PIL, PIL cuts off top of letters, Properly render text with a given font in Python and accurately detect its boundaries. These questions reference the same underlying issue but are not duplicates

Basic answered 19/12, 2018 at 21:58 Comment(0)
P
1

I couldn't solve this problem for some fonts using the approaches mentioned so far, so I ended up using aggdraw as a transparent replacement for PIL's text drawig methods.

Your code rewritten to aggdraw would look like:

import Image
import aggdraw

im = Image.new("RGBA", (200, 200), 'white')
draw = aggdraw.Draw(im)

# note that the color is specified in the font constructor in aggdraw
font = aggdraw.Font((0,0,0), "VeraSe.ttf", size=12, opacity=255)

draw.text((1, 1), " %s: " % "ggjyfFwe__", font) # no color here
draw.text((1, 30), " %s" % 15, font)

draw.flush() # don't forget this to update the underlying PIL image!

im.show()
Pyrognostics answered 31/1, 2012 at 13:17 Comment(1)
Works! Aggdraw required some patching on Windows though - aggdraw.cxx for the crash (comments.gmane.org/gmane.comp.python.image/1959) and setup.py to build with Freetype2 (got one from gnuwin32).Chape
M
0

My suggestion is, before you create the image object, to get the required size for the text.

This is done using font.getsize("text") (documentation).

In a image generating script I made, I first found the maximum height of one line of text, by calling the equvalient of font.getsize("Åj") (If you only need US-ASCII, you could find the height of "Aj" instead). Then I calculated the required image height and line offsets, including margins and line-spacing.

Metropolitan answered 20/12, 2009 at 10:48 Comment(1)
I already tried something like that. If the line height isn't enough, PIL will just overlap the text instead of clipping it...Florid
C
0

The "bug" still exists in 2012, with Ubuntu 11.10. Fontsize 11, 12, 13 and 15 clip the underscore completely.

#!/usr/bin/env python
""" demonstrates clipping of descenders for certain font sizes """
import Image, ImageDraw, ImageFont
fontPath = "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf"
im = Image.new('L', (256, 256))
ys=15
for i in range(10,21):
    fh = ImageFont.truetype(fontPath, i)
    sometext="%dgt_}" % (i)
    ImageDraw.Draw(im).text((10, ys ),sometext , 254, fh)
    ys+=i+5
im.show()
Cronin answered 20/3, 2012 at 17:19 Comment(0)
B
0

Here is an kludge that works well for me. It is a variant on gnud's answer. (Different enough to deserve a separate answer vs. comment I hope.) I have tested a lot of word placements and this has performed consistently.

When a text is drawn without fully reaching the full height of the font, clipping can occur. As gnud noted, by using characters such as "Aj" (I use "Fj") you avoid this bug.

Whenever a word is placed:

1) Do a draw.textsize(text, font=font) with your desired word. Store the height/width.

2) Add ' Fj' (spaceFJ) to the end of the word, and redo the textsize and store tis third height/width.

4) You will do the actual text draw with the word from item 2 (with the ' Fj' at the end). Having this addendum will keep the font from being clipped.

4) Before you do the actual text draw, crop the image where the ' Fj' will land (crop.load() is required to avoid a lazy copy). Then draw the text, and past the cropped image back over the ' Fj'.

This process avoids clipping, seems reasonably performant, and yields the full, unclipped text. Below is a copy/paste of a section of Python code I use for this. Partial example, but hopefully it adds some insight.

    # note: xpos & ypos were previous set = coordinates for text draw 
    #       the hard-coded addition of 4 to c_x likely will vary by font
    #       (I only use one font in this process, so kludged it.)
    width, height = draw.textsize(word, font=font)
    word2 = word + ' Fj'
    width2, height2 = draw.textsize(word2, font=font)
    # crop to overwrite ' Fj' with previous image bits
    c_w = width2 - width
    c_h = height2
    c_x = xpos + width + 4
    c_y = ypos
    box = (c_x, c_y, c_x + c_w, c_y + c_h)
    region = img.crop(box)
    region.load()
    draw.text((xpos, ypos), word2, (0,0,0), font=font)
    img.paste(region, box)
Bogosian answered 21/3, 2015 at 1:15 Comment(1)
Adding 'FJ 'with text that's being rotated doesn't seem to help and am still experiencing clipping. Not sure if I am doing anything wrong.Mithgarthr

© 2022 - 2024 — McMap. All rights reserved.