-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathexamples.py
398 lines (300 loc) · 13.8 KB
/
examples.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
"""
@author: The Absolute Tinkerer
"""
import os
import math
import time
import random
import numpy as np
from PIL import Image
from PyQt5.QtGui import QColor, QPen, QPixmap
from PyQt5.QtCore import QPointF, QRect
import painter
from utils import QColor_HSV, save, Perlin2D
def draw_white_noise(width, height, fname):
assert not os.path.exists(fname), 'File already exists!'
# Create a matrix of random values between zero and one
pixels = np.random.random(size=(height, width))
# Now modify the random values to be 0-255 (pixel color range)
pixels = 255*pixels
# The function to write the array of pixels to an image requires integers, not float values
pixels = pixels.astype(np.uint8)
# We choose to make random values grayscale, so each RGB element is identical. This code adds the third dimension
# to our pixels array
pixels = pixels[:, :, np.newaxis]
# We need to repeat each value to finalize the pixels arrays in the grayscale space
pixels = np.repeat(pixels, 3, axis=2)
# Now create the image from an array of pixels
im = Image.fromarray(pixels)
# Save the image to file
im.save(fname)
def draw_perlin(nx, ny, width, height, fname):
assert not os.path.exists(fname), 'File already exists'
# Initialize Perlin Noise
noise = (Perlin2D(width, height, nx, ny) + 1)/2
# Convert to pixels
pixels = 255 * noise
pixels = pixels.astype(np.uint8)
pixels = pixels[:, :, np.newaxis]
pixels = np.repeat(pixels, 3, axis=2)
# Create and save the image from pixels
im = Image.fromarray(pixels)
im.save(fname)
return noise
def draw_vectors(nx, ny, width, height, seed=random.randint(0, 100000000), flow_length=100, n_vectors=50):
p_path = f'{seed}_1_perlin_noise.jpg'
v_path = f'{seed}_2_vectors'
f_path = f'{seed}_3_flow_field'
# Ensure we don't overwrite paths
assert not os.path.exists(p_path), 'Perlin Noise image already exists!'
assert not os.path.exists(v_path), 'Vectors image already exists!'
assert not os.path.exists(f_path), 'Flow field image already exists!'
# Set the random seed for repeatability
np.random.seed(seed)
# Create the Perlin Noise image
noise = draw_perlin(nx, ny, width, height, p_path)
# Initialize the painter object for drawing
p = painter.Painter(width, height)
p.setRenderHint(p.Antialiasing) # allow smooth drawing
def draw_arrow(p, x_i, y_i, length=100, angle=0):
# Compute the second points and draw the arrow body
x_f = x_i + length*math.cos(math.radians(angle))
y_f = y_i - length*math.sin(math.radians(angle))
p.drawLine(x_i, y_i, x_f, y_f)
# Compute the arrow head second points
a_angle1, a_angle2 = math.radians(angle-30), math.radians(angle+30)
x1 = x_f - (length/10)*math.cos(a_angle1)
y1 = y_f + (length/10)*math.sin(a_angle1)
x2 = x_f - (length/10)*math.cos(a_angle2)
y2 = y_f + (length/10)*math.sin(a_angle2)
p.drawLine(x_f, y_f, x1, y1)
p.drawLine(x_f, y_f, x2, y2)
# Load the Perlin Noise image and draw it with the painter
p.drawPixmap(QRect(0, 0, width, height), QPixmap(p_path))
# Now we're drawing red arrows for vectors, so set the pen color to red
p.setPen(QColor(255, 0, 0))
# We need arrow locations, so create a grid of n_vectors x n_vectors, excluding the image border
_nx, _ny = n_vectors, n_vectors
dx, dy = width / (_nx + 1), height / (_ny + 1)
x_points = [dx + i*dx for i in range(_nx)]
y_points = [dy + i*dy for i in range(_ny)]
# Draw the arrows
for x in x_points:
for y in y_points:
angle = 360*noise[int(x), int(y)]
draw_arrow(p, x, y, length=min(dx, dy), angle=angle)
# Save the vector image
save(p, fname=v_path, folder='.')
# Now draw the flow field. Start by initializing a new painter
p = painter.Painter(width, height)
p.setRenderHint(p.Antialiasing) # allow smooth drawing
p.setPen(QColor(0, 0, 0)) # pen color set to black
# Step size between points
STEP_SIZE = 0.001 * max(width, height)
# Draw the flow field
for x in x_points:
for y in y_points:
# The starting position
x_s, y_s = x, y
# The current line length tracking variable
c_len = 0
while c_len < flow_length:
# angle between 0 and 2*pi
angle = 2 * noise[int(x_s), int(y_s)] * math.pi
# Compute the new point
x_f = x_s + STEP_SIZE * math.cos(angle)
y_f = y_s - STEP_SIZE * math.sin(angle)
# Draw the line
p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f))
# Update the line length
c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)
# Break from the loop if the new point is outside our image bounds
# or if we've exceeded the line length; otherwise update the point
if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > flow_length:
break
else:
x_s, y_s = x_f, y_f
save(p, fname=f_path, folder='.')
def draw_flow_field(width, height, seed=random.randint(0, 100000000)):
# Set the random seed for repeatability
np.random.seed(seed)
# These are color hues
colors = [200, 140, 70, 340, 280]
for i, mod in enumerate(colors):
print('Starting Image %s/%s' % (i + 1, len(colors)))
p = painter.Painter(width, height)
# Allow smooth drawing
p.setRenderHint(p.Antialiasing)
# Draw the background color
p.fillRect(0, 0, width, height, QColor(0, 0, 0))
# Set the pen color
p.setPen(QPen(QColor(150, 150, 225, 5), 2))
num = 1
for j in range(num):
print('Creating Noise... (%s/%s)' % (j + 1, num))
p_noise = Perlin2D(width, height, 2, 2)
print('Noise Generated! (%s/%s)' % (j + 1, num))
MAX_LENGTH = 2 * width
STEP_SIZE = 0.001 * max(width, height)
NUM = int(width * height / 1000)
POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)]
for k, (x_s, y_s) in enumerate(POINTS):
print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r')
# The current line length tracking variable
c_len = 0
# Actually draw the flow field
while c_len < MAX_LENGTH:
# Set the pen color for this segment
sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH
hue = (mod + 130 * (height - y_s) / height) % 360
p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2))
# angle between -pi and pi
angle = p_noise[int(x_s), int(y_s)] * math.pi
# Compute the new point
x_f = x_s + STEP_SIZE * math.cos(angle)
y_f = y_s + STEP_SIZE * math.sin(angle)
# Draw the line
p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f))
# Update the line length
c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)
# Break from the loop if the new point is outside our image bounds
# or if we've exceeded the line length; otherwise update the point
if x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or c_len > MAX_LENGTH:
break
else:
x_s, y_s = x_f, y_f
save(p, fname=f'image_{mod}_{num}_{seed}', folder='.', overwrite=True)
def draw_perlin_rounding(width, height, fname, seed=random.randint(0, 100000000)):
# Ensure we don't overwrite paths
assert not os.path.exists(fname), 'Image already exists!'
# Set the random seed for repeatability
np.random.seed(seed)
# Initialize a new painter
p = painter.Painter(width, height)
p.setRenderHint(p.Antialiasing)
# Draw the background color
p.fillRect(0, 0, width, height, QColor(0, 0, 0))
# Set the pen color
p.setPen(QColor(200, 200, 200))
print('Creating Noise...', end='', flush=True)
noise = Perlin2D(width, height, 1, 1)
print('Done!')
# The maximum line length and step size
MAX_LENGTH = 1000
STEP_SIZE = 0.001 * max(width, height)
# Compute a grid 200x200 points, centered in the screen
dx, dy = width / (200 + 1), height / (200 + 1)
POINTS = [[(i+1)*dx, (j+1)*dy] for i in range(200) for j in range(200)]
for i, (x_s, y_s) in enumerate(POINTS):
print(f'{100 * (i + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r')
# The current line length tracking variable
c_len = 0
while c_len < MAX_LENGTH:
# angle between -pi and pi
angle = math.pi*noise[int(x_s), int(y_s)]
# Round the angle to pi/4 increments
angle = round(angle / (math.pi / 4)) * (math.pi / 4)
# Compute the new point
x_f = x_s + STEP_SIZE * math.cos(angle)
y_f = y_s + STEP_SIZE * math.sin(angle)
# Draw the line
p.drawLine(x_s, y_s, x_f, y_f)
# Update the line length
c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2)
# Break from the loop if the new point is outside our image bounds
# or if we've exceeded the line length; otherwise update the point
if (x_f < 0 or x_f >= width or y_f < 0 or y_f >= height or
c_len > MAX_LENGTH):
break
else:
x_s, y_s = x_f, y_f
print('100% Complete!')
save(p, fname=f'{fname}_{seed}', folder='.')
class Body:
def __init__(self, x, y, vx, vy):
self._position = np.array([x, y], dtype=np.float64)
self._velocity = np.array([vx, vy], dtype=np.float64)
@property
def position(self):
return self._position
@property
def velocity(self):
return self._velocity
def update(self, dt):
# update the body position
self._position = self._position + dt*self._velocity
class ExpandingCircleRandom:
def __init__(self, radius, num_bodies, center=(0, 0), v_limits=(-2, 2)):
self._bodies = [Body(center[0] + radius*math.cos(i*2*math.pi/num_bodies),
center[1] + radius*math.sin(i*2*math.pi/num_bodies),
v_limits[0]+(v_limits[1]-v_limits[0])*random.random(),
v_limits[0]+(v_limits[1]-v_limits[0])*random.random()) for i in range(num_bodies)]
def draw(self, dt, painter):
# Connect the dots between each body
for i in range(len(self._bodies)):
# Handle the wrapping case
if i == len(self._bodies) - 1:
p1 = QPointF(*self._bodies[i].position)
p2 = QPointF(*self._bodies[0].position)
else:
p1 = QPointF(*self._bodies[i].position)
p2 = QPointF(*self._bodies[i+1].position)
painter.drawLine(p1, p2)
# Update the position of each body
for i in range(len(self._bodies)):
self._bodies[i].update(dt)
class ExpandingCircleNoise:
def __init__(self, radius, num_bodies, noise, center=(0, 0), v_max=2):
self._bodies = [Body(center[0] + radius*math.cos(i*2*math.pi/num_bodies),
center[1] + radius*math.sin(i*2*math.pi/num_bodies),
0, 0) for i in range(num_bodies)]
self._v_max = v_max
self._noise = noise
def draw(self, dt, painter):
# Connect the dots between each body
for i in range(len(self._bodies)):
# Handle the wrapping case
if i == len(self._bodies) - 1:
p1 = QPointF(*self._bodies[i].position)
p2 = QPointF(*self._bodies[0].position)
else:
p1 = QPointF(*self._bodies[i].position)
p2 = QPointF(*self._bodies[i + 1].position)
painter.drawLine(p1, p2)
# Try to update the velocity for each body. If we can't its because the point is beyond the noise
# field we've created, so at that point, just maintain velocity.
try:
a = math.pi*self._noise[int(p1.x()), int(p1.y())]
v = np.array([self._v_max*math.cos(a), self._v_max*math.sin(a)])
self._bodies[i]._velocity = v
except IndexError:
pass
# Update the position of each body
for i in range(len(self._bodies)):
self._bodies[i].update(dt)
def draw_delta_body(width, height, seed=random.randint(0, 100000000), mode='noise'):
assert mode in ['noise', 'random'], 'Mode must either be "noise" or "random"'
# Set the random seed for repeatability
np.random.seed(seed)
random.seed(seed)
# Initialize the painter
p = painter.Painter(width, height)
p.setRenderHint(p.Antialiasing) # Allow smooth drawing
# Draw the background color
p.fillRect(0, 0, width, height, QColor(0, 0, 0))
# Set the pen color
p.setPen(QPen(QColor(220, 220, 220, 5), 1))
# Initialize the expanding circle centered in the canvas
if mode == 'random':
circle = ExpandingCircleRandom(width/8, 100, center=(width/2, height/2), v_limits=(-2, 2))
elif mode == 'noise':
noise = Perlin2D(width, height, 5, 5)
circle = ExpandingCircleNoise(width/6, 200, noise, center=(width/4, height/2), v_max=5)
else:
circle = None
# Initialize the delta time we're applying to each update
dt = 0.3
iterations = 1000
for i in range(iterations):
circle.draw(dt, p)
save(p, fname=f'delta_{mode}_{seed}', folder='.', overwrite=True)