Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw text on an angle (rotated) in Python

I am drawing text onto a numpy array image in Python (using a custom font). Currently I am converting the image to PIL, drawing the text and then converting back to a numpy array.

import numpy as np
import cv2

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

char_image = np.zeros((200, 300, 3), np.uint8)

# convert to pillow image
pillowImage = Image.fromarray(char_image)
draw = ImageDraw.Draw(pillowImage)

# add chars to image
font = ImageFont.truetype("arial.ttf", 32)
draw.text((50, 50), 'ABC', (255, 255, 255), font=font)

# convert back to numpy array
char_image = np.array(pillowImage, np.uint8)

# show image on screen
cv2.imshow('myImage', char_image)
cv2.waitKey(0)

Is there anyway to draw the text on a given angle, ie. 33 degrees?

Rotating the image once the text has been drawn is not an option

like image 524
Bug Avatar asked Mar 08 '23 14:03

Bug


2 Answers

You can use PIL to draw rotated text. I suggest drawing the text onto a blank image, rotating that image, and then pasting the rotated image into the main image. Something like:

Code:

def draw_rotated_text(image, angle, xy, text, fill, *args, **kwargs):
    """ Draw text at an angle into an image, takes the same arguments
        as Image.text() except for:

    :param image: Image to write text into
    :param angle: Angle to write text at
    """
    # get the size of our image
    width, height = image.size
    max_dim = max(width, height)

    # build a transparency mask large enough to hold the text
    mask_size = (max_dim * 2, max_dim * 2)
    mask = Image.new('L', mask_size, 0)

    # add text to mask
    draw = ImageDraw.Draw(mask)
    draw.text((max_dim, max_dim), text, 255, *args, **kwargs)

    if angle % 90 == 0:
        # rotate by multiple of 90 deg is easier
        rotated_mask = mask.rotate(angle)
    else:
        # rotate an an enlarged mask to minimize jaggies
        bigger_mask = mask.resize((max_dim*8, max_dim*8),
                                  resample=Image.BICUBIC)
        rotated_mask = bigger_mask.rotate(angle).resize(
            mask_size, resample=Image.LANCZOS)

    # crop the mask to match image
    mask_xy = (max_dim - xy[0], max_dim - xy[1])
    b_box = mask_xy + (mask_xy[0] + width, mask_xy[1] + height)
    mask = rotated_mask.crop(b_box)

    # paste the appropriate color, with the text transparency mask
    color_image = Image.new('RGBA', image.size, fill)
    image.paste(color_image, mask)

How does it work:

  1. Create a transparency mask.
  2. Draw the text onto the mask.
  3. Rotate the mask, and crop to proper size.
  4. Paste the desired color into the image, using the rotated transparency mask containing the text.

Test Code:

import numpy as np

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont

char_image = np.zeros((100, 150, 3), np.uint8)

# convert to pillow image
pillowImage = Image.fromarray(char_image)

# draw the text
font = ImageFont.truetype("arial.ttf", 32)
draw_rotated_text(pillowImage, 35, (50, 50), 'ABC', (128, 255, 128), font=font)

pillowImage.show()

Results:

Results Image

like image 132
Stephen Rauch Avatar answered Mar 16 '23 22:03

Stephen Rauch


Using matplotlib, first visualize array and draw on it, get the raw data from the figure back. Pro: both tools are quite high level and let you deal with many details of the process. ax.annotate() offers flexibility for where and how to draw and set font properties, and plt.matshow() offers flexibility that lets you deal with aspects of array visualization.

import matplotlib.pyplot as plt
import scipy as sp

# make Data array to draw in
M = sp.zeros((500,500))

dpi = 300.0

# create a frameless mpl figure
fig, axes = plt.subplots(figsize=(M.shape[0]/dpi,M.shape[1]/dpi),dpi=dpi)
axes.axis('off')
fig.subplots_adjust(bottom=0,top=1.0,left=0,right=1)
axes.matshow(M,cmap='gray')

# set custom font
import matplotlib.font_manager as fm
ttf_fname = '/usr/share/fonts/truetype/ubuntu-font-family/Ubuntu-B.ttf'
prop = fm.FontProperties(fname=ttf_fname)

# annotate something
axes.annotate('ABC',xy=(250,250),rotation=45,fontproperties=prop,color='white')

# get fig image data and read it back to numpy array
fig.canvas.draw()
w,h = fig.canvas.get_width_height()
Imvals = sp.fromstring(fig.canvas.tostring_rgb(),dtype='uint8')
ImArray = Imvals.reshape((w,h,3))

enter image description here

like image 41
grg rsr Avatar answered Mar 16 '23 22:03

grg rsr