!pip3 install --user numpy matplotlib pillow
from matplotlib import pylab, pyplot, cm
import numpy
%matplotlib inline
pylab.rcParams['figure.figsize'] = (7, 7)
pylab.rcParams['animation.writer'] = 'ffmpeg'
pylab.rcParams['animation.codec'] = 'libvp8'
pylab.rcParams['animation.html'] = 'html5'
def ds(rp=2, base=.5, wrap=(False, False)):
power = 1
data = None
while True:
# length of side
side = 2**power + 1
sz = [side, side]
# create new numpy image
image = numpy.zeros(sz)
# if wrapped on either (x,y)
if wrap[0]:
sz[1] -= 1
if wrap[1]:
sz[0] -= 1
# copy & expand last image
if type(data) != type(None):
for x in range(data.shape[0]):
for y in range(data.shape[1]):
image[(2*x) % sz[0], (2*y) % sz[0]] = data[x, y]
# set corners to random data
else:
for x in ((0, 0), (sz[0]-1, 0), (0, sz[1]-1), (sz[0]-1, sz[1]-1)):
image[x] = max(min(image[x] + (numpy.random.random() - .5) + base, 1.0), 0.0)
# generate new pixels
for x in range(1, side, 2):
for y in range(1, side, 2):
xp1 = (x + 1) % sz[0]
yp1 = (y + 1) % sz[1]
mod = (numpy.random.random() - 0.5)/rp**power
image[x-1, y] = mod + (image[x-1, yp1] + image[x-1, y-1]) / 2
image[xp1, y] = mod + (image[xp1, yp1] + image[xp1, y-1]) / 2
image[x, y-1] = mod + (image[x-1, y-1] + image[xp1, y-1]) / 2
image[x, yp1] = mod + (image[x-1, yp1] + image[xp1, yp1]) / 2
image[x, y] = mod + (image[x-1, y-1] + image[x-1, yp1] + image[xp1, y-1] + image[xp1, yp1]) / 4
# normalize all pixels
for x in numpy.nditer(image, op_flags=['readwrite']):
x[...] = x if x >= 0. else 0.
x[...] = x if x <= 1. else 1.
# save current image
data = image[0:sz[0], 0:sz[1]]
power += 1
# expand to size (side, side)
if wrap[1]:
image[sz[0]] = image[0]
if wrap[0]:
image[..., sz[1]] = image[..., 0]
# return image
yield image
from matplotlib import animation
# save the first 9 iterations
ims = []
fig, ax = pyplot.subplots()
ax.set_axis_off()
for x,y in zip(ds(1.8, .5), range(9)):
ims.append([pyplot.imshow(x, extent=[0,1,0,1])])
pyplot.close(fig)
# animate!
animation.ArtistAnimation(fig, ims, interval=750, blit=True)
# diamond square
def dsn(n, *args):
p_gen = ds(*args)
for it, s in zip(p_gen, range(n)):
square = it
return p_gen, square
p_gen, planet = dsn(7, 1.8, .5, (True, True))
# https://matplotlib.org/examples/color/colormaps_reference.html
pyplot.imshow(cm.Vega20c(planet))
# show potential ds output
fig = pylab.figure()
fig.set_figwidth(15)
for n in range(0,6):
fig.add_subplot(2, 3, n+1)
tmp_gen = ds(1.8, .5, (False, False))
for it, s in zip(tmp_gen, range(6)):
copy = it
pyplot.imshow(cm.Vega20c(copy))
pylab.show()
def worley(image, n, nth=2, coef=.5):
import heapq
# image shape
sz = image.shape
# maximum possible distance between two points
max_dist = (sz[0]**2 + sz[1]**2)**.5
# random points on map
points = numpy.random.randint(0, min(sz), (n, 2))
# wrapping distance function
sd = lambda a,b,side: min((a - b) % side, (b - a) % side)
for x in range(sz[0]):
for y in range(sz[1]):
# compute distances (wrapping)
dist = [(sd(rx,x,sz[0])**2 + sd(ry,y,sz[1])**2)**.5 for (rx, ry) in points]
# compute noise, using nth shortest distance
noise = coef * heapq.nsmallest(nth, dist)[-1] / max_dist
# add noise and normalize
image[x, y] = image[x, y] - noise
image[x, y] = max(min(image[x, y], 1.), 0.)
copy = planet.copy()
fig = pylab.figure()
fig.set_figwidth(15)
fig.add_subplot(2, 3, 1)
pyplot.imshow(cm.Vega20c(copy))
pyplot.title('original')
# show potential worley output
for n in range(1,6):
fig.add_subplot(2, 3, n+1)
copy = planet.copy()
worley(copy, n, coef=-0.5)
pyplot.imshow(cm.Vega20c(copy))
pyplot.title('worley({})'.format(n))
pylab.show()
def shadow(image, a=.5, b=0, frac=.05, tol=.25, below=True):
b *= image.shape[0]
tol *= image.shape[0]
for x in range(image.shape[0]):
for y in range(image.shape[1]):
fx = a*x + b
if y - fx < tol and y > fx:
image[x, y, 0:3] *= (y - fx)/tol * (1 - frac) + frac
elif (below and y <= fx) or (not below and y >= fx):
image[x, y, 0:3] *= frac
copy = cm.Vega20c(planet.copy())
shadow(copy)
pyplot.imshow(copy)
def lens_distort(image, alpha = []):
from math import pi as π
from math import atan2, cos, sin, asin, hypot
# copy image
copy = image.copy()
# side
side = image.shape[0]
for cx in range(side):
for cy in range(side):
# normalized coordinates
nx, ny = cx/side - 0.5, cy/side - 0.5
# normalized visual distance
nr = hypot(nx, ny)
if nr > 0.5:
image[cx, cy] = [0., 0., 0., 0.]
continue
# angle of x,y
Ï• = atan2(ny, nx)
# distorted distance
d = asin(2*nr) / π
# map pixel on new image -> pixel on old image
image[cx, cy] = copy[round(side*(0.5 + d * cos(Ï•))), round(side*(0.5 + d * sin(Ï•)))]
copy = cm.Vega20c(planet.copy())
fig = pylab.figure()
fig.set_figwidth(15)
fig.add_subplot(1, 2, 1)
pyplot.imshow(copy)
fig.add_subplot(1, 2, 2)
copy = cm.Vega20c(planet.copy())
lens_distort(copy)
pyplot.imshow(copy)
pylab.show()
This effect will be more apparent if we superimpose a grid.
def grid(image, count=10, width=0.01, color=[0., 1., 0., 1.]):
side = image.shape[0]
width = round(width*side)
div = side / count
# latitude lines
cols = [round(div*x) for x in range(count)]
for w in range(1, width):
cols += [x + w for x in cols]
# longitude lines
rows = [round(div*x) for x in range(count)]
for w in range(1, width):
rows += [x + w for x in rows]
image[:,cols] = color
image[rows,:] = color
copy = cm.Vega20c(planet.copy())
grid(copy)
fig = pylab.figure()
fig.set_figwidth(15)
fig.add_subplot(1, 2, 1)
pyplot.imshow(copy)
fig.add_subplot(1, 2, 2)
lens_distort(copy)
pyplot.imshow(copy)
pylab.show()
def edge(image, thick=0.01, color=[1., 0., 0., 0.1]):
from math import hypot
side = image.shape[0]
for x in range(side):
for y in range(side):
# there should be pixel composition here...
nr = hypot(x/side - 0.5, y/side - 0.5)
if 0.5 < nr and nr < 0.5 + thick:
image[x,y] = color
copy = cm.Vega20c(planet.copy())
lens_distort(copy)
edge(copy)
pyplot.imshow(copy)
from math import hypot
def star(image, cx, cy, rad, color):
xrange = range(max(0, cx-rad), min(len(image), cx+rad+1))
yrange = range(max(0, cy-rad), min(len(image), cy+rad+1))
for x in xrange:
for y in yrange:
if hypot(x-cx, y-cy) < rad:
image[x,y] = color
def background(shape, color=[1., 1., 1., 1.], maxrad=3, count=500):
bg = numpy.full(shape, color)
for x,y in numpy.random.randint(0, shape[0], (count, 2)):
rad = numpy.random.randint(0, maxrad)
col = numpy.append(numpy.full((3), numpy.random.rand(1)), 1.)
star(bg, x, y, rad, col)
return bg
def set_bg(image, bg):
mask = numpy.all(image == [0., 0., 0., 0.], axis=2)
image[mask] = bg[mask]
copy = cm.Vega20c(planet.copy())
lens_distort(copy)
set_bg(copy, background(copy.shape))
pyplot.imshow(copy)
def gifify(gen, time, *funcs):
global im
from PIL import Image
from functools import reduce
fig, ax = pyplot.subplots()
ax.set_axis_off()
data = zip(zip(*gen), range(time))
im = None
def draw(n):
global im
# get data
try:
g, t = data.__next__()
except StopIteration:
return []
# compose layers
print('\r{}/{}'.format(t,time-1), end='')
images = (Image.fromarray(numpy.uint8(x * 255), 'RGBA') for x in g)
frame = reduce(lambda x,y: Image.alpha_composite(x, y), images)
frame = numpy.array(frame, dtype=float) / 255.0
# process image
for func in funcs:
func(frame)
# draw (using thread-unsafe memory optimization)
if not im:
im = pyplot.imshow(frame, animated=True)
else:
im.set_data(frame)
return [im]
anim = animation.FuncAnimation(fig, draw, frames=time, interval=75, repeat_delay=75, blit=True)
pyplot.close(fig)
return anim
def roll(image, inc=1, axis=1):
x = 0
while True:
x = (x + inc) % image.shape[0]
yield numpy.roll(image, int(x), axis=axis)
copy = cm.Vega20c(planet.copy())
gifify((roll(copy),), len(copy))
c_gen = ds(1.5, .35, (True, True))
for it, s in zip(c_gen, range(7)):
clouds = it
# worley noise
worley(clouds, 5, 2, coef=0.75)
clouds = cm.gray(clouds)
pyplot.imshow(clouds)
for x in range(clouds.shape[0]):
for y in range(clouds.shape[1]):
clouds[x, y] = [1., 1., 1., clouds[x, y, 0]]
copy = cm.Vega20c(planet.copy())
bg = background(copy.shape)
gen = (roll(copy, axis=(0,1)), roll(clouds, -2, 0), roll(clouds))
gifify(gen, 2*len(clouds), shadow, lens_distort, lambda x: set_bg(x, bg), edge)
# since we took advantage of generators
# we can iterate to a higher resolution
hi_p = p_gen.__next__()
hi_p = cm.Vega20c(hi_p)
pyplot.imshow(hi_p)
_, copy = dsn(7, 1.8, .5, (True, False))
copy = cm.Vega20c(copy)
bg = background(copy.shape)
gen = (roll(copy, axis=(1)),)
gifify(gen, len(copy), shadow, lens_distort, lambda x: set_bg(x, bg), edge)
# since we took advantage of generators
# we can iterate to a higher resolution
hi_p = p_gen.__next__()
hi_p = cm.Vega20c(hi_p)
fig = pylab.figure()
fig.set_figwidth(15)
fig.set_figheight(15)
im = pyplot.imshow(hi_p)