1

I'm trying to find the size, in points, of some text using Pillow in python. As I understand it, font sizes in points correspond to real physical inches on a target display or surface, with 72 points per inch. When I use Pillow's textsize method, I can find the size in pixels of some text rendered at a given font size (in points), but don't know how to get back to a coordinate system based in inches, because I don't have (and can't set) the pixel density of the image:

from PIL import Image, ImageFont, ImageDraw
image = Image.new('RGBA', (400, 300), (255, 255, 255))
font = ImageFont.truetype('/Library/Fonts/Arial.ttf', 16)
image.info['dpi'] = 100
print( ImageDraw.Draw(image).textsize('Loreum ipsum', font=font) )
image.info['dpi'] = 1000
print( ImageDraw.Draw(image).textsize('Loreum ipsum', font=font) )

will print

(101, 18)
(101, 18)

How do I get the size in points of the given text?

martineau
  • 119,623
  • 25
  • 170
  • 301
Anthony
  • 23
  • 5
  • Multiply the number of points by the dpi factor – WayToDoor Mar 12 '19 at 16:01
  • @WayToDoor, right, what DPI factor does Pillow use here? – Anthony Mar 12 '19 at 16:03
  • Use `image.info['dpi']` to find out? – WayToDoor Mar 12 '19 at 16:04
  • @WayToDoor, a new image has `image.info` set to `{}`; there's no DPI – Anthony Mar 12 '19 at 16:05
  • And it's fine, because an image has no dpi information. If you have set a specific DPI factor, then you can get the number of inches. If you don't then there is no was to get the number of inches from an image, since this is basically a setting you set when **printing** the image – WayToDoor Mar 12 '19 at 16:07
  • You may want to find out about the DPI factor of your screen if you want to show, for example, a ruler on screen, but this isn't really something you can do from python without a graphical interface – WayToDoor Mar 12 '19 at 16:07
  • @WayToDoor, I understand that an image doesn't necessarily contain information about its pixel density, but don't understand how the font size (in points, which are inch-based) maps to a pixel based coordinate system. – Anthony Mar 12 '19 at 16:13
  • https://stackoverflow.com/questions/139655/convert-pixels-to-points may help you then – WayToDoor Mar 12 '19 at 16:18

2 Answers2

5

TL;DR

There is no implicit pixel density, because the Pillow documentation is incorrect. When you create the font, you are specifying the size in pixels even though the Pillow documentation says it's in points. It's actually doing all of these operations in pixels.

More detail

The Pillow documentation for ImageFont.truetype says that the size argument is in points. However, looking at the source for the ImageFont module, it passes the size argument to core.getfont.

self.font = core.getfont(
    font, size, index, encoding, layout_engine=layout_engine
)

That call ultimately leads to a call to FT_Set_Pixel_Sizes, which is provided by the FreeType library. This call forwards the given size, unmodified.

error = FT_Set_Pixel_Sizes(self->face, 0, size);

The documentation for FT_Set_Pixel_Sizes states

FT_EXPORT( FT_Error )
  FT_Set_Pixel_Sizes( FT_Face  face,
                      FT_UInt  pixel_width,
                      FT_UInt  pixel_height );

Call FT_Request_Size to request the nominal size (in pixels).

face A handle to the target face object.

pixel_width The nominal width, in pixels.

pixel_height The nominal height, in pixels.

So really there are no physical distances involved here at all, and there is no assumed DPI for either ImageFont.truetype or ImageDraw.textsize, despite the misleading Pillow documentation. Everything is in pixels, so no DPI is required.

Sidenote: You may notice that the size you requested (16) is not exactly equal to the size you are getting back (18), but FreeType mentions that the size you ask for is not necessarily the size you get, and is at the discretion of the font itself.

Willie Conrad
  • 376
  • 2
  • 5
  • Terrific answer. Thanks for diving deep into this, and sharing what you found out. I just came across the same issue. – tyrex May 28 '21 at 14:28
1

Image don't have an "implicit" pixel density, they just have different number of pixels. The size of anything measured in pixels will depend on the display device's DPI or dots-per-inch.

For example on a 100 DPI device, 12 pixels would appear to be 12/100 or 0.12 inches long. To convert inches to points, multiply them by 72. So 12 pixels → 0.12 inches * 72 → 8.84 points.

martineau
  • 119,623
  • 25
  • 170
  • 301
  • I understand this, but I don't understand how the font computes the size, in pixels, of text at a certain size, in points, without assuming some pixel density. – Anthony Mar 12 '19 at 16:10
  • Fonts don't compute anything, they're effectively just data files. The text drawing software being used (PIL/pillow in this case) scales the character shape data found in the font file so that each character/glyph is rendered at the specified pixel size. – martineau Mar 12 '19 at 16:16
  • ok, but the `ImageFont.truetype` method takes a `size` _in points_, which is what has me confused. How do I establish a correspondence between points and pixels? – Anthony Mar 12 '19 at 16:22
  • Using basic algebra, you can use the mathematical relationships described in my answer to convert points to pixels (**assuming you know the—or assume some—DPI of the device being upon which things are being rendered**). – martineau Mar 12 '19 at 16:31
  • sure, I can work out the size in inches of some text given its size in pixels and the display density, using a little algebra. But I want to know, for example, when I tell Pillow to draw text at a size of 12 points, how many pixels it will use to represent a distance of 12 points. – Anthony Mar 12 '19 at 16:35
  • My answer shows how to convert from pixels to points. Just do it in reverse to go the other way. You may also find the answer to the question [How do I get monitor resolution in Python?](https://stackoverflow.com/questions/3129322/how-do-i-get-monitor-resolution-in-python) of interest. – martineau Mar 12 '19 at 16:39
  • I don't think my monitor resolution is relevant here. I just want to know the length, in pixels, of a line rendered by Pillow of some given length, in points. – Anthony Mar 12 '19 at 16:46
  • To do that, you're going to have to know (or assume) some DPI which is the only "unknown" value. Note DPI is basically the same thing as a device's resolution. – martineau Mar 12 '19 at 16:49
  • That the DPI is "unknown" is the purpose of my question! What value should I assume here? – Anthony Mar 12 '19 at 16:51
  • It depends of the OS — I think on Windows it's (conveniently) 72 DPI. Read the answers to the linked question.This issue is part of the reason why creating device-independent graphics can get somewhat complicated. – martineau Mar 12 '19 at 16:56
  • Also note what it says in pillow's documentation about the contents of an image's [info](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.info) dictionary attribute (i.e. about it not affecting much of anything it does). – martineau Mar 12 '19 at 17:04