Generate image from given text

Question:

I have a function that generates an image representation of the given text using Pillow, using as input text, font, font_size, and color.
The function works by generating an empty RGBA image to start from, then draws the text on it, and finally crops the result in order to only keep the text.

Now, with 99% of the cases the function works well enough, but with bigger font sizes I need a bigger canvas to start from, otherwise the text gets clipped.
For this reason I set the initial canvas to have very high values, such as (10k, 10k) pixels. This initial image size slows down the whole process, and I would like to know if there’s a way to get to the same result without having to resort to an initial empty image, or to generate the initial image size to be as small as possible in order to not waste resources

My function is this:

def text_to_image(
    text: str,
    font_filepath: str,
    font_size: int,
    color: Tuple[int, int, int, int],
) -> ImageType:
    # ?: very big canvas size as to not to clip the text when rendered with bigger font sizes
    canvas_size = (
        10000,
        10000,
    )
    img = Image.new("RGBA", canvas_size)

    draw = ImageDraw.Draw(img)
    draw_point = (0, 0)

    font = ImageFont.truetype(font_filepath, size=font_size)
    draw.multiline_text(draw_point, text, font=font, fill=color)

    text_window = img.getbbox()
    img = img.crop(text_window)

    return img

sample result:

enter image description here


Edit:

thanks to @AKX for the super quick response which solved my problem. For anyone interested, the function becomes

def text_to_image(
    text: str,
    font_filepath: str,
    font_size: int,
    color: Tuple[int, int, int, int],
) -> ImageType:
    font = ImageFont.truetype(font_filepath, size=font_size)

    img = Image.new("RGBA", font.getmask(text).size)

    draw = ImageDraw.Draw(img)
    draw_point = (0, 0)

    draw.multiline_text(draw_point, text, font=font, fill=color)

    text_window = img.getbbox()
    img = img.crop(text_window)

    return img
Asked By: HitLuca

||

Answers:

You can use the getmask() function to get a grayscale bitmap that’s exactly the size of a given text.

You can then create an empty image a desired background color.

Then use im.paste() with a solid color and that mask to draw in the text:


from PIL import Image, ImageFont

text = "Hello world!"
font_size = 36
font_filepath = "/Library/Fonts/Artegra_Sans-600-SemiBold-Italic.otf"
color = (67, 33, 116, 155)

font = ImageFont.truetype(font_filepath, size=font_size)
mask_image = font.getmask(text, "L")
img = Image.new("RGBA", mask_image.size)
img.im.paste(color, (0, 0) + mask_image.size, mask_image)  # need to use the inner `img.im.paste` due to `getmask` returning a core
img.save("yes.png")
Answered By: AKX

Thanks @HitLuca and @AKX for the code samples above. I found that the getmask function was cropping some of my text. I found using getsize_multiline function to get the text dimensions worked pretty well as an alternate. Below is my code.

from PIL import Image, ImageFont, ImageDraw, ImageColor

def text_to_image(
text: str,
font_filepath: str,
font_size: int,
color: (int, int, int), #color is in RGB
font_align="center"):

   font = ImageFont.truetype(font_filepath, size=font_size)
   box = font.getsize_multiline(text)
   img = Image.new("RGBA", (box[0], box[1]))
   draw = ImageDraw.Draw(img)
   draw_point = (0, 0)
   draw.multiline_text(draw_point, text, font=font, fill=color, align=font_align)
   return img
Answered By: Sue Donhym