PIL cuts off top of letters - python

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.

Related

Python: SVG to PNG converting issue

UPDATE: I tried increasing size in the chess.svg.board and it somehow cleared all the rendering issues at size = 900 1800
I tried using the svglib and reportlab to make .png files from .svg, and here is how the code looks:
import sys
import chess.svg
import chess
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
board = chess.Board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR")
drawing = chess.svg.board(board, size=350)
f = open('file.svg', 'w')
f.write(drawing)
drawing = svg2rlg("file.svg")
renderPM.drawToFile(drawing, "file.png", fmt="png")
If you try to open file.png there is a lot of missing parts of the image, which i guess are rendering issues. How can you fix this?
Sidenote: also getting a lot of 'x_order_2: colinear!' messages when running this on a discord bot, but I am not sure if this affects anything yet.
THIS!! I am having the same error with the same libraries... I didn't find a solution but just a workaround which probably won't help too much in your case, where the shapes generating the bands are not very sparse vertically.
I'll try playing with the file dimensions too, but so far this is what I got. Note that my svg consists of black shapes on a white background (hence the 255 - x in the following code)
Since the appearance of the bands is extremely random, and processing the same file several times in a row produces different results, I decided to take advantage of randomness: what I do is I export the same svg a few times into different pngs, import them all into a list and then only take those pixels that are white in all the exported images, something like:
images_files = [my_convert_function(svgfile=file, index=i) for i in range(3)]
images = [255 - imageio.imread(x) for x in images_files]
result = reduce(lambda a,b: a & b, images)
imageio.imwrite(<your filename here>, result)
[os.remove(x) for x in images_files]
where my_convert_function contains your same svg2rlg and renderPM.drawToFile, and returns the name of the png file being written. The index 'i' is to save several copies of the same png with different names.
It's some very crude code but I hope it can help other people with the same issue
The format parameter has to be in uppercase
renderPM.drawToFile(drawing, "file.png", fmt="PNG")

PIL Custom Font Alternate Glyphs Not Utilized

I made a custom handwriting font and exported it to a .ttf file. The service I used, Caligriphr, allows for alternate glyphs for each character. When I type with the font in notepad, alternate glyphs display correctly. However, when I write text onto an image using PIL with the custom font, only one glyph is used
for each character. Below is my code to write on the image:
body = ""
with open('body.txt') as fin:
ls = fin.readlines()
for l in ls:
words = l.split(' ')
for word in words:
double = random.randint(1,2) == 2
if(double):
body += ' ' + word
else:
body += ' ' + word
image = Image.open('graph.jpg').convert("RGBA")
text = Image.new('RGBA', image.size, (255,255,255,0))
font = ImageFont.FreeTypeFont('Graphite.ttf', 100)
d = ImageDraw.Draw(text)
d.text(xy=offset, text=body, fill = (26,29,32, 230), font=font)
tilt = random.random() * 2
slt = text.rotate(tilt, expand=1)
sx, sy = slt.size
image.paste(slt, (0,0, sx, sy), slt)
image.save('sample.png')
Edit: showing code for how body string is constructed
Any help would be appreciated.
Alternate Glyphs Displayed in Notepad
PIL output not utilizing alternate glyphs
This may depend on whether you're using the original PIL library, or Pillow.
Getting contextual alternate glyphs in the drawn output requires that the rendering engine draw the string as an entire string (not character by character) and, while doing so, process certain data in the font that performs glyph substitutions from the default glyphs.
This data could be OpenType Layout tables in OpenType fonts, AAT tables in Apple TrueType fonts, or Graphite tables in Graphite fonts, depending on what the platform/library supports. Since you mention you got alternates in Notepad, that indicates that the font has OpenType Layout data.
Reading the Pillow ImageFont documentation, it doesn't give any indication as to whether it supports any of these font formats. However, looking at the Pillow project in Github, I see that [winbuild\config.py] pulls in Harfbuzz, and that would provide support for OpenType Layout data. So, it seems that Pillow ought to draw using the contextual alternate glyphs, though I don't know if anything is needed to trigger it. (In Notepad, it happens by default, but that's not true everywhere.)
If using Pillow, you might need to explicitly enable the Raqm layout engine when loading the font: "Raqm - A library for complex text layout" http://host-oman.github.io/libraqm/raqm-Raqm.html
This engine can be specified using the ImageFont.truetype() function:
font = ImageFont.truetype('Graphite.ttf', 100, layout_engine=ImageFont.LAYOUT_RAQM)
Docs: https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.truetype
Testing if Raqm is availble on your system:
>>> from PIL import features
>>> features.check('raqm')
True
If not available, on my Ubuntu 20 it installs via
$ sudo apt install libraqm0
Edit: It looks like you don't even have to specify the layout_engine parameter at all. On my system alternate glyphs are used as soon as I install the Raqm library, even without changing the code.

Fonts aliasing in Pillow

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.

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)

How can I find out why PIL isn't drawing the font correctly?

Here's the code I'm using:
from PIL import Image
import ImageFont, ImageDraw
import sys
import pdb
img = Image.new("RGBA",(300,300))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(sys.argv[1],30)
draw.text((0,100),"world",font=font,fill="red")
del draw
img.save(sys.argv[2],"PNG")
and here's the image that results:
img http://www.freeimagehosting.net/image.php?976a0d3eaa.png ( for some reason, I can't make it show on SO, so the link is http://www.freeimagehosting.net/image.php?976a0d3eaa.png )
The thing is, I don't understand why it isn't drawing the font correctly? I should be able to read the word "world" off of it. It's like the picture's been cut in half or something. Does anyone have any clue?
EDIT: after balpha's comment, I decided to try another font. I'm only interested in ttf fonts, so I tried with another one, and it worked. This is kind of strange. The original font I tried to run this with is Beautiful ES. I'm curious if you guys can reproduce the same image on your computers, and if you happen to know the reason for why that is.
PIL uses the freetype2 library, so most possibly it is an issue with the font file; for example, it could have bad metrics defined (e.g see the OS/2 related ones opening the font with FontForge).

Categories