Title: Use random points to draw a Sierpinski gasket fractal in Python and PIL
As I mentioned in my post Draw a Sierpinski carpet fractal in Python and PIL, Wacław Sierpiński (1882 - 1969) described a bunch of interesting fractal shapes including the Sierpinski gasket. That gasket is a triangle recursively divided into smaller triangles. See the earlier post for information and pictures.
One of the cool features of the gasket is that you can generate it in a few different ways. This post explains one of the more amazing of those ways.
The program starts with three anchor points shown as blue dots in the picture. As it draws, the program keeps track of a current position, which initially is in the middle of the window.
The program then repeatedly picks one of the anchor points at random and moves halfway between the current position and the selected anchor point. It plots the resulting point and updates its current position to this new location.
Over time, the Sierpinski gasket sort of magically fades into existence. If you let the program run long enough, the fractal fills in until it looks pretty solid as shown in the picture on the right. Use the example program's scale widget to change the program's speed.
This is a self-similar fractal so, if you were to zoom in on part of the image, you would see the same structure as the fractal as a whole.
Like many recursive fractals, the code for this one is deceptively simple but it can generate very complicated results. The following draw_points method shows how the program plots its points.
def draw_points(self):
'''Draw some points on the gasket.'''
# See if we should stop.
if not self.drawing: return
# Make a list of new points.
new_points = []
# Generate points.
for i in range(self.speed_var.get()):
# Pick a random point.
point = random.randint(0, 2)
self.current_x = (self.current_x + self.anchors[point][0]) / 2
self.current_y = (self.current_y + self.anchors[point][1]) / 2
new_points.append((self.current_x, self.current_y))
# Draw the points on the gasket.
self.dr.point(new_points, fill='red')
# Display the current result.
self.display_image()
# Draw again later.
self.window.after(100, self.draw_points)
While the program is plotting points, the "Start" button says "Stop." If you click the button then, the program sets self.drawing to False. The draw_points method sees that and exits without doing anything.
After checking self.drawing, the method makes an empty list of points. It then loops through a range that goes up to the value in the program's self.speed_var variable, which is set by the scale widget. Inside the loop, the program picks a random anchor point, updates self.current_x and self.current_y to move halfway to the selected point, and appends the new point to the new_points list.
After it finishes the loop, the method uses self.dr (which is an ImageDraw.Draw object) to plot the new points on the program's image. The code then calls the following display_image method to display the result.
def display_image(self):
'''Draw the anchor points and display the image.'''
# Redraw the anchor points.
radius = 3
for point in self.anchors:
x1 = point[0] - radius
y1 = point[1] - radius
x2 = point[0] + radius
y2 = point[1] + radius
self.dr.ellipse(((x1, y1), (x2, y2)), fill='blue')
# Update the display.
self.canvas.delete(tk.ALL)
self.photo_image = ImageTk.PhotoImage(self.image)
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.photo_image)
This method redraws the anchor points so they appear above the fractal's plotted points. It then deletes the previous image from the program's Canvas widget and displays the current drawing on that widget.
Conclusion
The program contains a few other interesting details such as the tkinter code that builds its user interface and the way it manages the Start/Stop button, but overall it's pretty simple. It just picks a random anchor point and moves halfway to it.
It's interesting to see what happens if you change the program's anchor points. For example, if the points are not arranged in an equilateral triangle, the result looks like a gasket that isn't symmetric. If you use four points arranged in a rectangle, the points fill in the rectangle and you don't get an interesting fractal.
In my next post, I'll show how to build a program that lets you explore some of those and other interesting arrangements.
Meanwhile, download the example to experiment with it and to see additional details.
|
The picture on the left is a three-dimensional version of the Sierpinski gasket called the Sierpinski pyramid.
This picture was generated by techniques described in my book Build Your Own Ray Tracer With Python.
|
|
|