pil_helpers.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. from PIL import ImageDraw, ImageFont, Image
  2. import numpy as np
  3. from typing import Sequence
  4. def add_string(image, position, font_size: int, text, fill_color_for_pil: str, font_file,
  5. horizontal_alignment="left", vertical_alignment="top"):
  6. """
  7. Add a string to a PIL image
  8. :param PIL.Image image: PIL Image
  9. :param sequence position: 2-membered, coordinates as required by PIL (assuming origin at top right)
  10. :param str text: text to be added
  11. :param str fill_color_for_pil: PIL color to fill text
  12. :param str font_file: absolute path of a font file on disk
  13. """
  14. assert horizontal_alignment in ["left", "center", "right"], "unknown setting for horizontal alignment"
  15. assert vertical_alignment in ["top", "center", "bottom"], "unknown setting for vertical alignment"
  16. image_draw_obj = ImageDraw.Draw(image)
  17. corrected_font_size = 8 * round(font_size / 8)
  18. font = ImageFont.truetype(font=font_file, size=corrected_font_size)
  19. text_width, text_height = font.getsize(text)
  20. x_pos, y_pos = position
  21. if horizontal_alignment == "right":
  22. x_pos -= text_width
  23. elif horizontal_alignment == "center":
  24. x_pos -= int(text_width / 2)
  25. if vertical_alignment == "bottom":
  26. y_pos -= text_height
  27. elif vertical_alignment == "center":
  28. y_pos -= int(text_height / 2)
  29. image_draw_obj.text((x_pos, y_pos), text, fill=fill_color_for_pil, font=font)
  30. return image
  31. def numpy_to_pil_image(image_np: np.ndarray):
  32. """
  33. convert an image from numpy to PIL
  34. :param numpy.ndarray image_np: format X, Y, Color; Color format RGBA. Origin at bottom left, i.e. X=0, Y=0
  35. :rtype: PIL.Image
  36. :return: equivalent PIL Image in RGBA mode, with origin at top right
  37. """
  38. # swap axes as we have axis order XY and PIL expects YX
  39. frame_data_YX = image_np.swapaxes(0, 1)
  40. # convert to uint8
  41. frame_data_for_YX_uint8 = np.array(frame_data_YX * 255, dtype="uint8")
  42. # flip Y as origin in PIL is top left
  43. frame_data_for_pil = np.flip(frame_data_for_YX_uint8, axis=0)
  44. # import into PIL
  45. return Image.fromarray(frame_data_for_pil, mode="RGBA")
  46. def pil_image_to_numpy(image_PIL):
  47. """
  48. convert an image from PIL to numpy
  49. :param PIL.Image image_PIL: PIL Image in RGBA mode, with origin at top right
  50. :rtype: numpy.ndarray
  51. :return: equivalent image in numpy format X, Y, Color; Color format RGBA. Origin at bottom left, i.e. X=0, Y=0
  52. """
  53. # convert frame back to numpy.ndarray, float in range [0, 1]
  54. frame_data = np.array(np.array(image_PIL) / 255, dtype=np.float)
  55. # swap the axes as PIL return YX
  56. frame_dataXY = frame_data.swapaxes(0, 1)
  57. # flip Y as origin in PIL is top left
  58. frame_data_Y_flipped = np.flip(frame_dataXY, axis=1)
  59. return frame_data_Y_flipped
  60. def draw_lines(image_PIL: Image, point_sequence: Sequence, color_for_PIL: str):
  61. """
  62. Draw line on a PIL Image replacing underlying pixels
  63. :param PIL.Image image_PIL: PIL Image
  64. :param Sequence point_sequence: each 2-membered, format XY as expected by PIL, i.e., origin at top left
  65. :param str color_for_PIL: PIL color
  66. """
  67. image_draw_obj = ImageDraw.Draw(image_PIL)
  68. alpha = image_PIL.getchannel("A")
  69. image_draw_obj.line(xy=point_sequence, fill=color_for_PIL)
  70. image_PIL.putalpha(alpha)
  71. return image_PIL