Title: Draw translucent butterfly curves in Python
My post Draw colorful butterfly curves in Python showed how to draw a colorful butterfly curve but the curve is still a line drawing. For this post, I wondered what it would look like if I filled sections of the butterfly with translucent colors.
Overlaying Translucent Polygons
Unfortunately, drawing translucent shapes isn't as easy as you might think. It would be nice to simply draw a polygon with a color that has alpha component less than 255, but PIL won't do that. Instead, you need to draw the shape on a new PIL image and then use Image.alpha_composite to copy the result onto a base image.
The following function does that.
from PIL import Image, ImageDraw
def fill_translucent_polygon(pil_image, points, fill, outline):
'''Fill a polygon with a translucent color.'''
# First draw on a new overlay image and then use alpha_composite
# to merge with the original image.
# Make the overlay image.
overlay_image = Image.new('RGBA', pil_image.size, (0, 0, 0, 0))
dr = ImageDraw.Draw(overlay_image)
# Draw the poylgon.
dr.polygon(points, fill=fill, outline=outline)
# Copy the overlay onto pil_image.
final_image = Image.alpha_composite(pil_image, overlay_image)
return final_image
This function first creates an image filled with a transparent color that fits the original image. It then uses the translucent color to draw the polygon on it.
The function uses Image.alpha_composite to copy the polygon image onto the original image and returns the result.
Drawing the Butterfly
The previous post generated the butterfly's points and then divided the list of points into pieces to color separately. That won't work this time because the pieces of the list aren't broken exactly where t values are multiples of 2 π so the polygons don't start and end properly. Instead some of the polygons start and end in unusual places so parts of the butterfly are filled in when they should not be.
To fix that, this example generates the butterfly pieces in rounds so each round uses values of t that span 2 π: 0 - 2 π, 2 π - 4 π, 4 π - 6 π, and so forth.
The following code shows the new draw_curve method that draws the butterfly.
def draw_curve(self):
'''Draw the butterfly curve.'''
# Generate the points.
num_rounds = int(self.num_rounds_var.get())
num_points_per_2pi = 200
dt = 2 * math.pi / num_points_per_2pi
polylines = [
[get_point(i * dt + j * 2 * math.pi)
for i in range(num_points_per_2pi)]
for j in range(num_rounds)]
# Transform to fit the canvas.
self.canvas.update()
wid = self.canvas.winfo_width()
hgt = self.canvas.winfo_height()
margin = 5
target_rect = (margin, margin, wid - margin, hgt - margin)
polylines = transform_list_of_lists(polylines, target_rect)
# Make a PIL image to hold the results.
pil_image = Image.new('RGBA', (wid, hgt), (0, 0, 0, 0))
colors = [
(255, 0, 0, 255),
(255, 128, 0, 255),
(255, 255, 0, 255),
(128, 255, 0, 255),
( 0, 255, 0, 255),
( 0, 255, 128, 255),
( 0, 255, 255, 255),
( 0, 128, 255, 255),
( 0, 0, 255, 255),
(128, 0, 255, 255),
(255, 0, 255, 255),
(255, 0, 128, 255),
]
random.shuffle(colors)
num_colors = len(colors)
# Draw.
opacity = int(self.opacity_var.get())
self.canvas.delete(tk.ALL)
for i, points in enumerate(polylines):
outline = colors[i % num_colors]
fill = (outline[0], outline[1], outline[2], opacity)
pil_image = fill_translucent_polygon(pil_image, points,
fill, outline)
# Display the result.
self.photo_image = ImageTk.PhotoImage(pil_image)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo_image)
The method first gets the number of rounds that you entered. It then calculates the amount dt by which t should change to move from 0 to 2 π in 200 steps.
The code then uses two nested list comprehensions to build the lists of points. The outer comprehension loops over the number of rounds. The inner loops through the t values needed to create that round of the butterfly.
After generating the points, the program uses transform_list_of_lists to transform the points so they fit on the program's canvas. This is slightly different from the way the previous example worked because that program used a single list of points and this one uses a list of lists. See the previous example and download this one to see how it works.
Having transformed the points, the program creates a PIL image to hold the drawing and defines the colors it will use. To make the result a bit more interesting, the code randomizes the color list.
Next, the program loops through the point lists to draw the butterflies. For each point list, the code calls fill_translucent_polygon to draw the list's butterfly on the main image using a new color. It outlines each butterfly with a color from the colors list and fills it with the same color at the opacity that you entered on the window.
After it has drawn all of the translucent butterfly segments, the program displays the result on its canvas.
Drawing the Curve
The result isn't as colorful as I was hoping it would be. Each round draws mostly over the previous one, so the colors just add up rather than partially overlapping to produce new shades the way they did in my post Draw a filled chrysanthemum curve in Python.
Download the example to experiment with it and to see additional details.
|