Title: Fill a polygon with a RadialGradientBrush with PIL in Python
The post Make a RadialGradientBrush class for use with PIL in Python explained how you can make a RadialGradientBrush class and use it to fill a rectangle with PIL in Python. This example shows how you can fill a polygon with that brush.
pil_fill_polygon
The RadialGradientBrush class is the same as before. The only new element is the following pil_fill_polygon method.
def pil_fill_polygon(image, brush, points):
'''Fill the polygon with the brush.'''
# Get the polygon's bounds.
bounds = get_bounds(points)
# Get the filled bounding rectangle.
rect_image = brush.make_image(bounds)
# Translate the points.
xmin = bounds[0]
ymin = bounds[1]
points = [(point[0]-xmin, point[1]-ymin) for point in points]
# Make a polygon mask image.
wid = bounds[2] - bounds[0]
hgt = bounds[3] - bounds[1]
mask = Image.new('L', (wid, hgt), 'black')
dr = ImageDraw.Draw(mask)
dr.polygon(points, 'white')
# Draw the filled rectangle on the PIL image.
image.paste(rect_image, (bounds[0], bounds[1]), mask)
This method calls the get_bounds function described shortly to get the polygon's bounds. It then uses the brush's make_image function to make a rectangle sized to fit the polygon and filled with the brush's gradient. The code then translates the polygon's points so they lie on the new image, which has upper left corner at (0, 0).
Next, the method creates a mask image. It makes a new black image with the same size and the brush image. It then draws the polygon on the mask image, filling it with white.
Now the code pastes the brush's filled rectangle onto the image passed into the function. It uses the mask image as a mask so only the parts of the brush image that correspond to white pixels in the mask are copied onto the result.
That's all there is to it. Overall it's a relatively simple exercise in pasting an image with a mask.
get_bounds
The following code shows the get_bounds helper function.
def get_bounds(points):
'''Get the bounds for these points.'''
xmin = ymin = math.inf
xmax = ymax = -math.inf
for x, y in points:
xmin = min(xmin, x)
xmax = max(xmax, x)
ymin = min(ymin, y)
ymax = max(ymax, y)
return round(xmin), round(ymin), round(xmax), round(ymax)
This function loops through the points in a list and keeps track of the points' bounds. After it has processed all of the points, it returns the points' smallest and largest X and Y coordinates.
Main Program
The main program creates its brush much as the previous example did. It then uses the new pil_fill_polygon method to fill a polygon with it.
The following code shows how the program draws its first polygon.
def fill_polygons(self):
# Get the canvas's dimensions.
self.canvas.update()
wid = self.canvas.winfo_width()
hgt = self.canvas.winfo_height()
# Make an image to draw on.
image = Image.new('RGBA', (wid, hgt), 'wheat')
dr = ImageDraw.Draw(image)
margin = 10
rect_wid = round((wid - 4 * margin) / 2)
rect_hgt = round((hgt - 4 * margin) / 2)
show_centers = False
# Polygon 1.
bbox = [margin, margin, margin + rect_wid, margin + rect_hgt]
points = get_star_points(-math.pi / 2, 7, 3, bbox)
center = ((bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2)
center_color = (255, 255, 0, 255)
outer_color = (0, 200, 0, 255)
brush = RadialGradientBrush(center, center_color, outer_color)
pil_fill_polygon(image, brush, points)
dr.polygon(points, fill=None, outline='black')
if show_centers: pil_draw_circle(dr, center, 3, None, 'black')
The code gets the dimensions of the program's canvas widget and makes an image to fit. It creates a bounding box and then uses the get_star_points from the post Draw stars in Python to get the vertices of a seven-pointed star. Next, the code defines the brush's center point and colors.
The code then calls pil_fill_polygon to draw the filled star on the image and uses PIL's polygon method to outline the star. Finally, if show_centers is True, the program draws a circle to show at the brush's center.
Conclusion
The only new code here is the pil_fill_polygon method, which uses a mask to draw the brush's filled rectangle only over the polygon. You can use a similar technique to draw filled text, ellipses, pie slices, rounded rectangles, and any other shape that PIL can draw. I'll probably implement a few of those in later posts, but in my next post I want to show how you can make a more powerful radial gradient brush.
Download the example to experiment with it and to see additional details.
|