[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Draw a filled chrysanthemum curve in Python

[A filled chrysanthemum curve drawn in Python]

My previous post Draw a chrysanthemum curve in Python explains how to use some equations to draw the colorful chrysanthemum curve. This post shows how you can fill the curve with colors. The curve is self-overlapping, so the colors should be translucent so you can see the colors showing through other colors.

That turned out to be a lot harder than I expected because tkinter does not support translucent colors. The obvious workaround is to use PIL to draw an image and then display the image in tkinter but PIL, too, doesn't support drawing polygons with translucent colors, at least not in the obvious way. For example, you might expect that drawing a translucent red triangle on top of a blue rectangle would produce a purple triangle. Instead what you get is a translucent red triangle that completely replaces the color of the blue triangle below.

To overlay translucent colors in PIL, you need to draw the new shape onto a separate image and then use the alpha_composite method to overlay the new shape onto the old one.

That's the approach this example takes.

draw_chrysanthemum_curve

To see how the program does most of its work, see the previous example.

The following code shows the draw_chrysanthemum_curve method that does the actual drawing. I've omitted some of the less interesting code and highlighted the key new pieces in blue.

def draw_chrysanthemum_curve(self): '''Draw the chrysanthemum curve.''' # Define some colors. colors = [ 'Pink', 'Red', 'Orange', ... ] # Scale and translate. ymax = -11 ymin = 11 hgt = ymin - ymax wid = hgt self.window.update() canvas_wid = canvas_hgt = 400 scale = min(canvas_wid / wid, canvas_hgt / hgt) dx = canvas_wid / 2 dy = canvas_hgt / 2 # Draw the curve. num_lines = 5000 period = 21 # Generate the points. t = 0 r = 5 * (1 + math.sin(11 * t / 5)) \ - 4 * math.pow(math.sin(17 * t / 3), 4) \ * math.pow(math.sin(2 * math.cos(3 * t) - 28 * t), 8) pt1 = (dx + scale * r * math.sin(t), dy + scale * (-r) * math.cos(t)) # Make a PIL image to fit. pil_image = Image.new('RGBA', (canvas_wid, canvas_hgt), 'black') dr = ImageDraw.Draw(pil_image) # Set the origin to the middle of the window. origin = (dx, dy) dt = math.pi / num_lines for i in range(num_lines + 1): t = i * period * dt r = 5 * (1 + math.sin(11 * t / 5)) \ - 4 * math.pow(math.sin(17 * t / 3), 4) \ * math.pow(math.sin(2 * math.cos(3 * t) - 28 * t), 8) pt0 = pt1 pt1 = (dx + scale * r * math.sin(t), dy + scale * (-r) * math.cos(t)) outline = colors[int(t / math.pi)] rgba_color = ImageColor.getcolor(outline, 'RGBA') fill = rgba_color[:3] + (64,) # Draw the triangle from this edge to the origin on a # temporary image and then composite it onto the main image. temp_image = Image.new('RGBA', (canvas_wid, canvas_hgt), (0,0,0,0)) temp_dr = ImageDraw.Draw(temp_image) points = [ pt0, pt1, origin ] temp_dr.polygon(points, fill=fill, outline=None) pil_image.alpha_composite(temp_image) # Draw the curve's outer edge. dr.line((pt0, pt1), fill=outline) # Display the image in a label. tk_image = ImageTk.PhotoImage(pil_image) image_label = tk.Label(self.window, image=tk_image) image_label.pack(side=tk.TOP, padx=5, pady=5) self.image = tk_image # Save the image (optional). pil_image.save('test.png')

This method first defines a list of colors, sets scaling parameters, and generates its first point as in the previous example.

Next, the code creates a PIL image of the desired size and an associated ImageDraw object. It also sets variable origin to the point at the center of the image.

The method enters a for loop as before to generate the rest of the curve's points. After it generates a point, the code gets its color and converts the color name—like Cyan—into a translucent RGBA color—like (0, 255, 255, 64)—. The 64 means this color is 64/255 ≈ 25% opaque.

Next the program creates a temporary PIL image that's the same size as the main image. The temporary image is initially filled with the color (0, 0, 0, 0), which is completely transparent. The code uses that image's polygon method to draw a triangle connecting the origin and the next segment on the curve. The triangle uses the translucent color so the temporary image is now completely transparent except for the triangle, which is translucent.

The code then calls the main image's alpha_composite method to copy the translucent triangle onto the main image. Because the rest of the temporary image is transparent, that part does not modify the contents of the main image. The result of the compositing is the new triangle is copied onto the main image and its translucent triangle is overlaid on top of whatever is currently in the main image. (Yes, this is pretty confusing. No I don't know why PIL doesn't just let you draw translucent polygons onto the main image.)

After compositing the new triangle onto the main image, the code draws the curve's new edge. (It does not draw the triangle's edges that connect to the origin.)

When the method is finished drawing the whole chrysanthemum curve, it takes the usual steps to display a PIL image in a tkinter label. It converts the image into a ImageTk.PhotoImage, makes a label holding the image, and packs the label.

The code also saves a reference to the PhotoImage in a non-volatile variable, in this case self.image. If you don't do that, Python's garbage collector may reclaim the image's memory and then the label won't have anything to display when it needs to update itself. This is a key issue whenever you display a PIL image in a tkinter program so it's worth highlighting. Be sure you save the PhotoImage somewhere safe!

Conclusion

This example shows how to draw colorful curve, but the really interesting part is how you need to use compositing to let colors show through translucent shapes.

The steps that create the temporary image and then composite it with the main image add up after a while. The previous example can draw its image almost instantly. This example takes a bit over a second. It would be nice (and probably a lot faster) if PIL let you draw translucent shapes directly on an image without the compositing step but here we are.

Download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.