draw.py 4.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. from PIL import Image, ImageDraw, ImageFont
  2. import numpy as np
  3. from string import ascii_letters
  4. def text (text, font='arial.ttf', size=32, colour=(255,255,255), bg=(127,127,127), border=(0,0,0,0), crop_x='text', crop_y='text', align_x='centre'):
  5. """Return a PIL image of the specified text, cropped to its boundaries.
  6. Keyword arguments:
  7. text -- the text to display (str).
  8. font -- the .ttf font file to use (str)
  9. size -- the font size (int; see PIL docs)
  10. colour -- a tuple specifying the font colour in hex, in the form (R,G,B)
  11. bg -- a tuple specifying the background colour in hex, in the form (R,G,B)
  12. border -- a tuple of pixel sizes for a border in the form (left_x, right_x, top_y, bottom_y), surrounding the crop
  13. crop_x -- should be one of the following, specifiying whether the output's width be cropped to the maximum boundaries of the current text or the font:
  14. 'text': the width limits of the rendered text
  15. 'font': the width limits of the font's ascii characters given the text's length, with centred alignment
  16. crop_y -- should be one of the following, specifiying whether the output's height be cropped to the maximum boundaries of the current text or the font:
  17. 'text': the height limits of the rendered text
  18. 'font': the height limits of the font's ascii characters given the text's length
  19. align_x -- how to horizontally align the text (if any white space):
  20. 'left': align to the left
  21. 'centre': align to the centre
  22. 'right': align to the right
  23. """
  24. # get font info
  25. pil_font = ImageFont.truetype(font, size=size, encoding="unic")
  26. pil_fontsize = pil_font.getsize(text)
  27. # draw image
  28. im = Image.new('RGB', pil_fontsize, bg)
  29. im_draw = ImageDraw.Draw(im)
  30. im_draw.text((0,0), text, font=pil_font, fill=colour)
  31. # find which pixels could be filled by text
  32. if crop_x=='font' or crop_y=='font':
  33. letter_fontsizes = [pil_font.getsize(letter*len(text)) for letter in ascii_letters]
  34. canvas_max_size = (max([tup[0] for tup in letter_fontsizes]), max([tup[1] for tup in letter_fontsizes]))
  35. im_lims = Image.new('RGB', canvas_max_size, bg)
  36. im_lims_draw = ImageDraw.Draw(im_lims)
  37. for letter in ascii_letters:
  38. im_lims_draw.text((0,0), letter*len(text), font=pil_font, fill=colour)
  39. im_lims_arr = np.array(im_lims)
  40. text_lims_px = np.where(np.sum(im_lims_arr==bg, 2)!=im_lims_arr.shape[2])
  41. # find which pixels are filled with text
  42. im_arr = np.array(im)
  43. text_px = np.where(np.sum(im_arr==bg, 2)!=im_arr.shape[2]) # find non-background pixels
  44. # get x crop from either font or text limits
  45. if crop_x=='font':
  46. min_x = np.min(text_lims_px[1])
  47. max_x = np.max(text_lims_px[1])+1
  48. elif crop_x=='text':
  49. min_x = np.min(text_px[1])
  50. max_x = np.max(text_px[1])+1
  51. # get y crop from either font or text limits
  52. if crop_y=='font':
  53. min_y = np.min(text_lims_px[0])
  54. max_y = np.max(text_lims_px[0])+1
  55. elif crop_y=='text':
  56. min_y = np.min(text_px[0])
  57. max_y = np.max(text_px[0])+1
  58. min_x = int(min_x)
  59. max_x = int(max_x)
  60. min_y = int(min_y)
  61. max_y = int(max_y)
  62. # get the horizontal and vertical text size info (for alignment & drawing location)
  63. min_x_text = np.min(text_px[1])
  64. max_x_text = np.max(text_px[1])+1
  65. min_y_text = np.min(text_px[0])
  66. max_y_text = np.max(text_px[0])+1
  67. text_width = max_x_text - min_x_text
  68. # apply crop (create as new image to avoid default black background)
  69. im_out = Image.new('RGB', (border[0] + border[1] + max_x - min_x, border[2] + border[3] + max_y - min_y), bg)
  70. im_out_draw = ImageDraw.Draw(im_out)
  71. # get the text position adjustment (dictated by alignment)
  72. if align_x=='centre':
  73. align_adjust = round((max_x - min_x) * 0.5 - text_width * 0.5)
  74. elif align_x=='left':
  75. align_adjust = 0
  76. elif align_x=='right':
  77. align_adjust = max_x - text_width
  78. # draw the text in the necessary position
  79. im_out_draw.text((-min_x_text + border[0] + align_adjust, -min_y + border[2]), text, font=pil_font, fill=colour)
  80. return(im_out)