Mondrian Art Generator
Piet Mondrian was a 20th-century Dutch painter and one of the founders of neoplasticism, an abstract art movement. His most iconic paintings relied on blocks of primary colors (blue, yellow, red), black, and white. Using a minimalist approach, he separated these colors with horizontal and vertical elements.
This program generates random paintings that follow Mondrian’s style. You can find out more about Piet Mondrian at https://en.wikipedia.org/wiki/Piet_Mondrian.
The algorithm works by creating a data structure (the canvas dictionary) with randomly spaced vertical and horizontal lines.
Next, it removes some of the line segments to create larger rectangles.
Finally, the algorithm randomly fills some rectangles with yellow, red, blue, or black.
You can find another version of this Mondrian art generator at https://github.com/asweigart/mondrian_art_generator/ along with several sample images.
mondrian_art_generator.py
1"""Mondrian Art Generator, by Al Sweigart al@inventwithpython.com
2Randomly generates art in the style of Piet Mondrian.
3More info at: https://en.wikipedia.org/wiki/Piet_Mondrian
4This code is available at https://nostarch.com/big-book-small-python-programming
5Tags: large, artistic, bext"""
6
7import sys, random
8
9try:
10 import bext
11except ImportError:
12 print('This program requires the bext module, which you')
13 print('can install by following the instructions at')
14 print('https://pypi.org/project/Bext/')
15 sys.exit()
16
17# Set up the constants:
18MIN_X_INCREASE = 6
19MAX_X_INCREASE = 16
20MIN_Y_INCREASE = 3
21MAX_Y_INCREASE = 6
22WHITE = 'white'
23BLACK = 'black'
24RED = 'red'
25YELLOW = 'yellow'
26BLUE = 'blue'
27
28# Setup the screen:
29width, height = bext.size()
30# We can't print to the last column on Windows without it adding a
31# newline automatically, so reduce the width by one:
32width -= 1
33
34height -= 3
35
36while True: # Main application loop.
37 # Pre-populate the canvas with blank spaces:
38 canvas = {}
39 for x in range(width):
40 for y in range(height):
41 canvas[(x, y)] = WHITE
42
43 # Generate vertical lines:
44 numberOfSegmentsToDelete = 0
45 x = random.randint(MIN_X_INCREASE, MAX_X_INCREASE)
46 while x < width - MIN_X_INCREASE:
47 numberOfSegmentsToDelete += 1
48 for y in range(height):
49 canvas[(x, y)] = BLACK
50 x += random.randint(MIN_X_INCREASE, MAX_X_INCREASE)
51
52 # Generate horizontal lines:
53 y = random.randint(MIN_Y_INCREASE, MAX_Y_INCREASE)
54 while y < height - MIN_Y_INCREASE:
55 numberOfSegmentsToDelete += 1
56 for x in range(width):
57 canvas[(x, y)] = BLACK
58 y += random.randint(MIN_Y_INCREASE, MAX_Y_INCREASE)
59
60 numberOfRectanglesToPaint = numberOfSegmentsToDelete - 3
61 numberOfSegmentsToDelete = int(numberOfSegmentsToDelete * 1.5)
62
63 # Randomly select points and try to remove them.
64 for i in range(numberOfSegmentsToDelete):
65 while True: # Keep selecting segments to try to delete.
66 # Get a random start point on an existing segment:
67 startx = random.randint(1, width - 2)
68 starty = random.randint(1, height - 2)
69 if canvas[(startx, starty)] == WHITE:
70 continue
71
72 # Find out if we're on a vertical or horizontal segment:
73 if (canvas[(startx - 1, starty)] == WHITE and
74 canvas[(startx + 1, starty)] == WHITE):
75 orientation = 'vertical'
76 elif (canvas[(startx, starty - 1)] == WHITE and
77 canvas[(startx, starty + 1)] == WHITE):
78 orientation = 'horizontal'
79 else:
80 # The start point is on an intersection,
81 # so get a new random start point:
82 continue
83
84 pointsToDelete = [(startx, starty)]
85
86 canDeleteSegment = True
87 if orientation == 'vertical':
88 # Go up one path from the start point, and
89 # see if we can remove this segment:
90 for changey in (-1, 1):
91 y = starty
92 while 0 < y < height - 1:
93 y += changey
94 if (canvas[(startx - 1, y)] == BLACK and
95 canvas[(startx + 1, y)] == BLACK):
96 # We've found a four-way intersection.
97 break
98 elif ((canvas[(startx - 1, y)] == WHITE and
99 canvas[(startx + 1, y)] == BLACK) or
100 (canvas[(startx - 1, y)] == BLACK and
101 canvas[(startx + 1, y)] == WHITE)):
102 # We've found a T-intersection; we can't
103 # delete this segment:
104 canDeleteSegment = False
105 break
106 else:
107 pointsToDelete.append((startx, y))
108
109 elif orientation == 'horizontal':
110 # Go up one path from the start point, and
111 # see if we can remove this segment:
112 for changex in (-1, 1):
113 x = startx
114 while 0 < x < width - 1:
115 x += changex
116 if (canvas[(x, starty - 1)] == BLACK and
117 canvas[(x, starty + 1)] == BLACK):
118 # We've found a four-way intersection.
119 break
120 elif ((canvas[(x, starty - 1)] == WHITE and
121 canvas[(x, starty + 1)] == BLACK) or
122 (canvas[(x, starty - 1)] == BLACK and
123 canvas[(x, starty + 1)] == WHITE)):
124 # We've found a T-intersection; we can't
125 # delete this segment:
126 canDeleteSegment = False
127 break
128 else:
129 pointsToDelete.append((x, starty))
130 if not canDeleteSegment:
131 continue # Get a new random start point.
132 break # Move on to delete the segment.
133
134 # If we can delete this segment, set all the points to white:
135 for x, y in pointsToDelete:
136 canvas[(x, y)] = WHITE
137
138 # Add the border lines:
139 for x in range(width):
140 canvas[(x, 0)] = BLACK # Top border.
141 canvas[(x, height - 1)] = BLACK # Bottom border.
142 for y in range(height):
143 canvas[(0, y)] = BLACK # Left border.
144 canvas[(width - 1, y)] = BLACK # Right border.
145
146 # Paint the rectangles:
147 for i in range(numberOfRectanglesToPaint):
148 while True:
149 startx = random.randint(1, width - 2)
150 starty = random.randint(1, height - 2)
151
152 if canvas[(startx, starty)] != WHITE:
153 continue # Get a new random start point.
154 else:
155 break
156
157 # Flood fill algorithm:
158 colorToPaint = random.choice([RED, YELLOW, BLUE, BLACK])
159 pointsToPaint = set([(startx, starty)])
160 while len(pointsToPaint) > 0:
161 x, y = pointsToPaint.pop()
162 canvas[(x, y)] = colorToPaint
163 if canvas[(x - 1, y)] == WHITE:
164 pointsToPaint.add((x - 1, y))
165 if canvas[(x + 1, y)] == WHITE:
166 pointsToPaint.add((x + 1, y))
167 if canvas[(x, y - 1)] == WHITE:
168 pointsToPaint.add((x, y - 1))
169 if canvas[(x, y + 1)] == WHITE:
170 pointsToPaint.add((x, y + 1))
171
172 # Draw the canvas data structure:
173 for y in range(height):
174 for x in range(width):
175 bext.bg(canvas[(x, y)])
176 print(' ', end='')
177
178 print()
179
180 # Prompt user to create a new one:
181 try:
182 input('Press Enter for another work of art, or Ctrl-C to quit.')
183 except KeyboardInterrupt:
184 sys.exit()
After entering the source code and running it a few times, try making experimental changes to it. On your own, you can also try to figure out how to do the following:
Create programs with different color palettes.
Use the Pillow module to produce image files of Mondrian art. You can learn about this module from Chapter 19 of Automate the Boring Stuff with Python at https://automatetheboringstuff.com/2e/chapter19/.