KalyanChakravarthy.net

home photos apps about

Drawing anti-aliased unicode text with python

Sun 16 November 2014

For an app I was hacking during the weekend, I needed to generate images for all characters of Berber alphabet and had to figure out several things

converting hexcode to python unicode

Since there were a lot of characters and they were contiguous, I could loop through them - for this, I had to figure out how to convert hex code into python unicode character

hexCode = '0x2d62'                # U+2d62
intValue = int( hexCode, 16 )     # hex is base 16

# using unicode(intValue) won't work
pyUnicodeChar = unichr( intValue )

drawing on the image

Next came the part of drawing it on a ImageDraw surface -

# Load the True Type font for anti-aliasing
font = ImageFont.truetype( 'T_I_UNICODE.ttf', 400 )

# Init the drawing surface
image = Image.new( 'RGBA', (500,500) )
drawPad = ImageDraw.Draw(image)

# Draw pyUnicodeChar string at origin in black with opacity=1
drawPad.text( (0,0), pyUnicodeChar, fill=(0,0,0,225) )

computing font size for centring

Note: ImageFont or ImageDraw, do not provide functionality to centre the text and has to be done manually by computing its size, which can be done this way

# using ImageDraw instance
textSize = drawPad.textsize(pyUnicodeChar, font=font)

# Or, using ImageFont instance
textSize = font.getsize( pyUnicodeChar )

python implementation

Here is the full code to generate the image shown above

# -*- coding: utf-8 -*-
# Above comment is needed if we use unicode chars directly in code

from PIL import Image, ImageDraw, ImageFont
import sys, os

# Constants
fontFile = 'T_I_UNICODE.ttf'

# Unicode characters from the Berber alphabet system
hexcodeChars = ['0x2d62', '0x2d65', '0x2d4b', '0x2d3e']

# Load the font
font = ImageFont.truetype(fontFile, 400)

# Convert the hexCodes to python unicode strings
unicodeChars = map( lambda c: unichr( int(c, 16) ), hexcodeChars )

# Compute the font sizes, to derive an optimal image size
unicodeCharSizes = map( lambda c: font.getsize(c), unicodeChars )
maxCharDimension = max( map( lambda s: max(s), unicodeCharSizes ) )

layoutPadding = 10
gridSize = maxCharDimension*2 + layoutPadding*3

# Initalize the image
theImage = Image.new( 'RGB', (gridSize,gridSize), color='white' )
theDrawPad = ImageDraw.Draw(theImage)

i = 0
for char, size in zip(unicodeChars, unicodeCharSizes):
    x, y = ( i % 2, i / 2 )
    xSize, ySize = size

    # get the grid cell position, and then center the font in that cell
    # as only way to do it is to compute size of char & position it manually
    xPos = (maxCharDimension * x) + (maxCharDimension-xSize)/2.0
    yPos = (maxCharDimension * y) + (maxCharDimension-ySize)/2.0

    theDrawPad.text( (xPos, yPos), char, font=font, fill='black' )

    i += 1

theImage.save('antialiased-berber-chars.png')

note: PIL & Pillow

  • For using the Image/ImageDraw/ImageFont library, install PIL using
    pip install Pillow
  • the original PIL is quite buggy and is not maintained to by knowledge
    • for example font size returned for true-type fonts is wrong and inconsistent.
  • Pillow is the drop-in replacement/fork for it, which addresses all the issues and is actively maintained.
  • If you have older PIL installed, remove it using pip uninstall PIL