- Mo 01 Oktober 2018
- GenerativeArt
- Peter Schuhmacher
- #Python
We have found in this inspiring blog post a lean engine that generates smart pastel canvas.
The algorithm starts at the left boundary where the first column is initiated with a color that oscillates randomly around a certain value. The algorithm marches now through the canvas to the right. Each pixel within a new column is computed as the mean value of the three nearest neighbouring pixels of the left column - and a certain amount of random noise is added at this step.
The results are images with a lot of atmospheric flair.
Vectorized code
We have modified the original code and replaced the two outer for-loops by vectorized forms. We use Python's slice-forms which allow for a lean writing of the array's indices and still give a fast access to the elements of the array. We have tested that earlier here.
Further we have split the initialisation into a lower and an upper part. So we get a landscape/atmosphere impression.
import sys
import numpy as np
from PIL import Image
import matplotlib.pylab as plt
import matplotlib.colors as mclr
np.set_printoptions(linewidth=200, precision=3)
def draw_pastell(nx=900, ny=1600, CL=180, rshift=3):
nz=3
mid = nx//2
dCL = 50
#---- start the coloring ----------
A = np.ones((nx,ny,nz)) *CL # initialize the image matrix
#np.random.seed(1234) # initialize RNG
#---- initialize the lower part ----
ix = slice(0,mid-1); iz = slice(0,nz) # color the left boundary
A[ix,0,iz] = CL + np.cumsum(np.random.randint(-rshift, rshift+1, size=(mid-1,nz)),axis=0 )
#---- initialize the upper part ----
ix = slice(mid,nx); iz = slice(0,nz) # color the left boundary
A[ix,0,iz] = CL-dCL + np.cumsum(np.random.randint(-rshift, rshift+1, size=(nx-mid,nz)),axis=0 )
#---- march to the right boundary -------------
ix = slice(1,nx-1); ixm = slice(0,nx-2); ixp = slice(2,nx)
for jy in range(1,ny): # smear the color to the right boundary
A[ix,jy,iz] = 0.3333*(A[ixm,jy-1,iz] + A[ix,jy-1,iz] + A[ixp,jy-1,iz]) + np.random.randint(-rshift, rshift+1, size=(nx-2,nz))
#---- show&save grafics ---------
im = Image.fromarray(A.astype(np.uint8)).convert('RGBA')
fig, ax = plt.subplots(figsize=(20,14));
fileName = 'pic_pastell_B_{}_{}.png'.format(CL,rshift)
im.save(fileName)
plt.axis('off'); plt.imshow(im)
# nx, ny : size of image (x,y)
# CL : color level
# rshift : spread of random numbers
draw_pastell(nx=900, ny=1600, CL=181, rshift=3)
draw_pastell(nx=900, ny=1600, CL=151, rshift=3)
draw_pastell(nx=900, ny=1600, CL=183, rshift=3)
Code with for-loops
def draw_pastell_FOR(width=1600, height=900, CL=180, rshift=3):
arr = np.ones((height, width,3)) * CL
np.random.seed(1234)
for y in range(1,height):
arr[y,0] = arr[y-1,0] + np.random.randint(-rshift, rshift+1, size=3)
for x in range(1, width):
for y in range(1,height-1):
arr[y,x] = ((arr[y-1, x-1] + arr[y,x-1] + arr[y+1,x-1])/3 ) + np.random.randint(-rshift, rshift+1, size=3)
im = Image.fromarray(arr.astype(np.uint8)).convert('RGBA')
filename = 'pic_pastell_FOR.png'
im.save(filename)
fig, ax = plt.subplots(figsize=(20,20));
plt.axis('off'); plt.imshow(im)
draw_pastell_FOR(width=1600, height=900, CL=120, rshift=3)
Comparing the speed
%timeit draw_pastell(width=1600, hight=900, CL=180, rshift=3)
Result:
1.8 s ± 23.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit draw_pastell_FOR(width=1600, hight=900, CL=180, rshift=3)
Result:
22.6 s ± 202 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
The difference of the runtime is about a factor 12.