Title: Draw text filled with a gradient brush using PIL in Python
This post continues the series on gradient brushes, this time to fill text with a gradient brush.
pil_fill_text
The following pil_fill_text method uses a gradient brush to fill some text on a PIL image.
def pil_fill_text(image, brush, text, font, x, y, align, anchor):
'''Fill the polygon with the brush.'''
dr = ImageDraw.Draw(image)
# Get the text's bounding box.
bounds = dr.textbbox((x, y), text, font=font, align=align, anchor=anchor)
# Get the filled bounding rectangle.
rect_image = brush.make_image(bounds)
# Translate the points.
xmin, ymin, xmax, ymax = bounds
xmin = int(xmin)
ymin = int(ymin)
xmax = int(xmax)
ymax = int(ymax)
x -= xmin
y -= ymin
# Make a mask image.
wid = xmax - xmin
hgt = ymax - ymin
mask = Image.new('L', (wid, hgt), 'black')
dr = ImageDraw.Draw(mask)
dr.multiline_text((x, y), text=text,
fill='white', font=font, anchor=anchor, align=align)
# Draw the filled rectangle on the PIL image.
image.paste(rect_image, (xmin, ymin), mask)
This method creates am ImageDraw.Draw object associated with the image and then calls its textbbox function to see how much room the text will require. It uses then uses the brush's make_image function to make a rectangle big enough to hold the text and filled with the brush. (See the earlier examples in the series for details about how that works. For example, see Make a RadialGradientBrush class for use with PIL in Python.)
Next, the code calculates the text's translated anchor point. We want to draw the text at the point (x, y) but the image we will draw on is located at the origin, so we need to move the text.
Now the program makes a mask image having the same size as the brush's filled rectangle. Initially the mask is black, and then the code draws the text on it in white.
The code finishes by copying the mask's filled rectangle onto the destination image. It uses the mask to copy only the parts of the image that correspond to the text.
Main Program
The following code shows how the main program draws the text on the lower left.
cx = round(margin + rect_wid / 2)
cy += rect_hgt + margin
center = (cx, cy)
# See how big the text will be so we can make the gradient fit.
text = 'Python'
font = ImageFont.truetype('times.ttf', 80)
align = 'center'
anchor = 'mm'
text_bounds = dr.textbbox(center, text, font=font,
align=align, anchor=anchor)
start_point = (0, text_bounds[1])
end_point = (0, text_bounds[3])
colors = []
for i in range(20):
colors.append((255, 0, 0, 255))
colors.append((255, 255, 0, 255))
offsets = None
radius = None
brush = RadialMultiGradientBrush(center, colors, offsets, radius)
pil_fill_text(image, brush, text, font, cx, cy, align, anchor)
if show_points:
pil_draw_circle(dr, center, 3, None, 'black')
dr.rectangle(text_bounds, outline='black')
This code defines the text it will display and then creates a big font. It uses the ImageDraw.Draw object's textbbox function to see how big the text will be. This isn't actually necessary to draw the text; the code uses it to figure out where it should position the text.
Next, the code defines a colors list, repeating red and yellow several times. It uses the colors to create a RadialMultiGradientBrush and then calls pil_fill_text to fill the text with the brush.
Finally, if show_points is True, the program draws a circle at the brush's center point and draws a rectangle around the text.
Conclusion
The key technique is using the text as a mask so you can copy a filled rectangle onto the result image only in the places indicated by the mask. You can use this technique more generally to display just about any drawn image.
Download the example to experiment with it and to see additional details.
|