fonts clipping with PIL

那年仲夏 提交于 2019-11-28 03:51:12

问题


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


回答1:


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




回答2:


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



回答3:


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



回答4:


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.




回答5:


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)


来源:https://stackoverflow.com/questions/1933766/fonts-clipping-with-pil

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!