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:
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
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")