I graphed a fractal shape in Python using turtle, and am trying to get the area of this fractal after a sufficiently high iteration. This fractal is related to the Koch snowflake, for those interested.
I was able to fill in the fractal with black using begin_fill() and end_fill(). I then used this answer to get the color of each pixel in a valid range. If it wasn't equal to white, then I added one to the count. This solution works for a small iteration of the fractal. However, it takes an exorbitant amount of time when trying to go to a higher iteration.
Here is my code for the fractal.
def realSnowflake(length, n, s, show = False):#n: after n iterations#s: number of sides (in Koch snowflake, it is 3)#length: starting side lengthturtle.begin_fill()a = 360/sfor i in range(s):snowflake(length, n, s) turtle.right(a)turtle.end_fill()
Here is my code for finding the area.
count = 0
canvas = turtle.getcanvas()
for x in range(x1, x2+1): #limits calculated through mathfor y in range(y2, y1+1):if get_pixel_color(x, y, canvas) != "white":count += 1
I want to be able to find the area of this fractal faster. It takes the most amount of time not in graphing the fractal, but in the double for loop of x and y. I think if there is a way to find the area while turtle is filling, this would be optimal.
the complexity of the image drawn shouldn't affect the time it takesto count black pixels
Unfortunately, in this case it does. If we lookup an earlier source of the get_pixel_color()
code, we find the telling text, "is slow". But it's worse than that, it actually slows down!
This code is built atop canvas.find_overlapping()
which is looking for high level objects that sit over X,Y. In the case of tkinter filling an object for turtle, there is overlap, up to three layers in the code below. This increases as the factal gets more complex. Here's my code to demonstrate this:
from turtle import Screen, Turtle
from math import floor, ceil
from time import timedef koch_curve(turtle, iterations, length):if iterations == 0:turtle.forward(length)else:for angle in [60, -120, 60, 0]:koch_curve(turtle, iterations - 1, length / 3)turtle.left(angle)def koch_snowflake(turtle, iterations, length):turtle.begin_poly()turtle.begin_fill()for _ in range(3):koch_curve(turtle, iterations, length)turtle.right(120)turtle.end_fill()turtle.end_poly()return turtle.get_poly()def bounding_box(points):x_coordinates, y_coordinates = zip(*points)return [(min(x_coordinates), min(y_coordinates)), (max(x_coordinates), max(y_coordinates))]def get_pixel_color(x, y):ids = canvas.find_overlapping(x, y, x, y) # This is our bottleneck!if ids: # if list is not emptyindex = ids[-1]return canvas.itemcget(index, 'fill')return 'white' # default colorscreen = Screen()
screen.setup(500, 500)
turtle = Turtle(visible=False)
turtle.color('red')canvas = screen.getcanvas()
width, height = screen.window_width(), screen.window_height()for iterations in range(1, 7):screen.clear()turtle.clear()screen.tracer(False)polygon_start_time = time()polygon = koch_snowflake(turtle, iterations, 200)polygon_elapsed = round((time() - polygon_start_time) * 1000) # millisecondsscreen.tracer(True)((x_min, y_min), (x_max, y_max)) = bounding_box(polygon)screen.update()# Convert from turtle coordinates to tkinter coordinatesx1, y1 = floor(x_min), floor(-y_max)x2, y2 = ceil(x_max), ceil(-y_min)canvas.create_rectangle((x1, y1, x2, y2))count = 0pixel_count_start_time = time()for x in range(x1, x2 + 1):for y in range(y1, y2 + 1):if get_pixel_color(x, y) == 'red':count += 1pixel_count_elapsed = round((time() - pixel_count_start_time) * 1000)print(iterations, count, polygon_elapsed, pixel_count_elapsed, ((x1, y1), (x2, y2)))screen.exitonclick()
CONSOLE OUTPUT
> python3 test.py
1 23165 1 493 ((-1, -58), (201, 174))
2 26064 4 1058 ((-1, -58), (201, 174))
3 27358 9 1347 ((-1, -58), (201, 174))
4 28159 29 2262 ((0, -58), (201, 174))
5 28712 104 5925 ((0, -58), (201, 174))
6 28881 449 19759 ((0, -58), (200, 174))
>
The fields are as follows:
- Iterations
- Pixels counted
- Time to draw image in ms
- Time to count pixels in ms
- Computed bounding box (in tkinter coordinates)
FINAL ITERATION SCREEN OUTPUT
Note that the fractal is drawn by turtle but the bounding box is drawn by the underlying tkinter to verify that we converted coordinates correctly.
POSSIBLE SOLUTION
Find an approach that doesn't rely on find_overlapping()
. I think the next thing to investigate would be either to convert the canvas to a bitmap image, and pixel count it, or draw a bitmap image in the first place. There are several discusions on SO about converting a canvas to a bitmap, either directly or indirectly via Postscript. You could then load in that image and use one of the Python image libraries to count the pixels. Although more complicated, it should provide a constant time way of counting pixels. Alternately, there are libraries to draw bitmaps, which you could load into tkinter for visual verfication, but could then pixel count directly. Good luck!