How do I draw text at an angle using python's PIL?

38,306

Solution 1

Draw text into a temporary blank image, rotate that, then paste that onto the original image. You could wrap up the steps in a function. Good luck figuring out the exact coordinates to use - my cold-fogged brain isn't up to it right now.

This demo writes yellow text on a slant over an image:

# Demo to add rotated text to an image using PIL

import Image
import ImageFont, ImageDraw, ImageOps

im=Image.open("stormy100.jpg")

f = ImageFont.load_default()
txt=Image.new('L', (500,50))
d = ImageDraw.Draw(txt)
d.text( (0, 0), "Someplace Near Boulder",  font=f, fill=255)
w=txt.rotate(17.5,  expand=1)

im.paste( ImageOps.colorize(w, (0,0,0), (255,255,84)), (242,60),  w)

Solution 2

Here is a working version, inspired by the answer, but it works without opening or saving images.

The two images have colored background and alpha channel different from zero to show what's going on. Changing the two alpha channels from 92 to 0 will make them completely transparent.

from PIL import Image, ImageFont, ImageDraw

text = 'TEST'
font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf', 50)
width, height = font.getsize(text)

image1 = Image.new('RGBA', (200, 150), (0, 128, 0, 92))
draw1 = ImageDraw.Draw(image1)
draw1.text((0, 0), text=text, font=font, fill=(255, 128, 0))

image2 = Image.new('RGBA', (width, height), (0, 0, 128, 92))
draw2 = ImageDraw.Draw(image2)
draw2.text((0, 0), text=text, font=font, fill=(0, 255, 128))

image2 = image2.rotate(30, expand=1)

px, py = 10, 10
sx, sy = image2.size
image1.paste(image2, (px, py, px + sx, py + sy), image2)

image1.show()

Solution 3

Here's a fuller example of watermarking diagonally. Handles arbitrary image ratios, sizes and text lengths by calculating the angle of the diagonal and font size.

from PIL import Image, ImageFont, ImageDraw
import math

# sample dimensions
pdf_width = 1000
pdf_height = 1500

#text_to_be_rotated = 'Harry Moreno'
text_to_be_rotated = 'Harry Moreno ([email protected])'
message_length = len(text_to_be_rotated)

# load font (tweak ratio based on your particular font)
FONT_RATIO = 1.5
DIAGONAL_PERCENTAGE = .5
diagonal_length = int(math.sqrt((pdf_width**2) + (pdf_height**2)))
diagonal_to_use = diagonal_length * DIAGONAL_PERCENTAGE
font_size = int(diagonal_to_use / (message_length / FONT_RATIO))
font = ImageFont.truetype(r'./venv/lib/python3.7/site-packages/reportlab/fonts/Vera.ttf', font_size)
#font = ImageFont.load_default() # fallback

# target
image = Image.new('RGBA', (pdf_width, pdf_height), (0, 128, 0, 92))

# watermark
opacity = int(256 * .5)
mark_width, mark_height = font.getsize(text_to_be_rotated)
watermark = Image.new('RGBA', (mark_width, mark_height), (0, 0, 0, 0))
draw = ImageDraw.Draw(watermark)
draw.text((0, 0), text=text_to_be_rotated, font=font, fill=(0, 0, 0, opacity))
angle = math.degrees(math.atan(pdf_height/pdf_width))
watermark = watermark.rotate(angle, expand=1)

# merge
wx, wy = watermark.size
px = int((pdf_width - wx)/2)
py = int((pdf_height - wy)/2)
image.paste(watermark, (px, py, px + wx, py + wy), watermark)

image.show()

Here it is in a colab https://colab.research.google.com/drive/1ERl7PiX6xKy5H9EEMulBKPgglF6euCNA?usp=sharing you should provide an example image to the colab. 500 by 300300 by 500

Solution 4

The previous answers draw into a new image, rotate it, and draw it back into the source image. This leaves text artifacts. We don't want that.

Here is a version that instead crops the area of the source image that will be drawn onto, rotates it, draws into that, and rotates it back. This means that we draw onto the final surface immediately, without having to resort to masks.

def draw_text_90_into (text: str, into, at):
    # Measure the text area
    font = ImageFont.truetype (r'C:\Windows\Fonts\Arial.ttf', 16)
    wi, hi = font.getsize (text)

    # Copy the relevant area from the source image
    img = into.crop ((at[0], at[1], at[0] + hi, at[1] + wi))

    # Rotate it backwards
    img = img.rotate (270, expand = 1)

    # Print into the rotated area
    d = ImageDraw.Draw (img)
    d.text ((0, 0), text, font = font, fill = (0, 0, 0))

    # Rotate it forward again
    img = img.rotate (90, expand = 1)

    # Insert it back into the source image
    # Note that we don't need a mask
    into.paste (img, at)

Supporting other angles, colors etc is trivial to add.

Solution 5

I'm not saying this is going to be easy, or that this solution will necessarily be perfect for you, but look at the documentation here:

http://effbot.org/imagingbook/pil-index.htm

and especially pay attention to the Image, ImageDraw, and ImageFont modules.

Here's an example to help you out:


import Image
im = Image.new("RGB", (100, 100))
import ImageDraw
draw = ImageDraw.Draw(im)
draw.text((50, 50), "hey")
im.rotate(45).show()

To do what you really want you may need to make a bunch of separate correctly rotated text images and then compose them all together with some more fancy manipulation. And after all that it still may not look great. I'm not sure how antialiasing and such is handled for instance, but it might not be good. Good luck, and if anyone has an easier way, I'd be interested to know as well.

Share:
38,306
Syed Sami Zia
Author by

Syed Sami Zia

.

Updated on April 28, 2021

Comments

  • Syed Sami Zia
    Syed Sami Zia about 3 years

    Using Python I want to be able to draw text at different angles using PIL.

    For example, imagine you were drawing the number around the face of a clock. The number 3 would appear as expected whereas 12 would we drawn rotated counter-clockwise 90 degrees.

    Therefore, I need to be able to draw many different strings at many different angles.

  • Vaaal88
    Vaaal88 over 2 years
    I don't understand what artifacts you are talking about. Could you show that the other methods produce artifacts?
  • mafu
    mafu over 2 years
    @Vaaal88 I do not remember it exactly, I think it was either that (subpixel) aliasing is getting broken or that masking does not play nicely with alpha. I had some issues that were fixed with the above approach.
  • Shreyansh Dwivedi
    Shreyansh Dwivedi over 2 years
    How to save the image, no option to save the image, can you suggest that. @Harry Moreno