You Can Be the Next Jackson Pollock! Well, probably not at least you can create some cool-looking art with Python code. Standing On the Shoulders Of Giants I'm still learning Generative Art and I needed a place to start so I took code and simplified it for learning purposes. Check it out on . Absolute-Tinkerer's Github Digital Paintbrush So what is Generative Art anyway? Basically, it's writing code that follows some rules and uses random inputs to create unique art each time it's run. There are many types of Generative Art but we're just going to focus on . Flow Fields So, What Do We Need? We're going to be using four pieces of code: (from Absolute-Tinkerer) painter (from Absolute-Tinkerer) utils numpy (pip install numpy) our code below Perlin Noise? What's That? TL;DR In the 1980's this really smart guy working on the movie Tron came up with a way to make computer imagery look more organic and less artificial. You can read more about it on . We're using Perlin Noise to make our images look better. Wikipedia Show Me The Code Already! I'll show you the code and then we'll break down what it's doing. I'm still learning Generative Art so if I'm describing something incorrectly, please let me know in the comments below. import math import random import numpy as np from PyQt5.QtGui import QColor, QPen from PyQt5.QtCore import QPointF import painter from utils import QColor_HSV, save, Perlin2D def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001): seed = random.randint(0, 100000000) # Set the random seed for repeatability np.random.seed(seed) p = painter.Painter(width, height) # Allow smooth drawing p.setRenderHint(p.Antialiasing) # Draw the background color p.fillRect(0, 0, width, height, QColor( *backgroundColor )) # Set the pen color p.setPen(QPen(QColor(150, 150, 225, 5), 2)) print('Creating Noise...') p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH) print('Noise Generated!') MAX_LENGTH = 2 * width STEP_SIZE = step * 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 = (color + 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_{seed}', folder='.', overwrite=True) draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35) We import our Python modules: import math import random import numpy as np from PyQt5.QtGui import QColor, QPen from PyQt5.QtCore import QPointF Then we import in code from Absolute-Tinkerer's project (see above): import painter from utils import QColor_HSV, save, Perlin2D Now we come to the function which has 2 positional arguments and 5 keyword arguments: draw def draw(width, height, color=200, backgroundColor=(0,0,0), perlinFactorW=2, perlinFactorH=2, step=0.001): We'll describe the arguments when we get to them in the code. seed = random.randint(0, 100000000) # Set the random seed for repeatability np.random.seed(seed) We randomly select an integer between 0 and 100000000 and then use that number set a random seed in numpy. p = painter.Painter(width, height) # Allow smooth drawing p.setRenderHint(p.Antialiasing) # Draw the background color p.fillRect(0, 0, width, height, QColor( *backgroundColor )) # Set the pen color p.setPen(QPen(QColor(150, 150, 225, 5), 2)) In this part of the code, we define our which is an instance of (a class allowing us to "paint" pixels). Using our painter, we turn on antialiasing to smooth the angles, fill a rectangle with our provided background color and then finally set our pen color to draw. painter QPainter print('Creating Noise...') p_noise = Perlin2D(width, height, perlinFactorW, perlinFactorH) print('Noise Generated!') Here we're using Absolute-Tinkerer's Perlin Noise generator to make our noise. This will allow angle calculations further on in the code. MAX_LENGTH = 2 * width STEP_SIZE = step * max(width, height) NUM = int(width * height / 1000) POINTS = [(random.randint(0, width - 1), random.randint(0, height - 1)) for i in range(NUM)] acts as a guard so that we don't draw/calculate values outside of our image edges and it's also used in some color calculations. MAX_LENGTH is used to calculate drawing points with smaller values leading to organic curves and larger values generating "chaotic" features. dictates how many points we create. STEP_SIZE NUM Finally, calculates points (X, Y coordinates) using list comprehension. POINTS for k, (x_s, y_s) in enumerate(POINTS): print(f'{100 * (k + 1) / len(POINTS):.1f}'.rjust(5) + '% Complete', end='\r') For each one of our points, get its (X, Y) coordinates and display our percentage completed for drawing our image. # The current line length tracking variable c_len = 0 Set our tracking variable to 0. # Actually draw the flow field while c_len < MAX_LENGTH: Keep looping until our tracking variable is less than our image limit. # Set the pen color for this segment sat = 200 * (MAX_LENGTH - c_len) / MAX_LENGTH hue = (color + 130 * (height - y_s) / height) % 360 p.setPen(QPen(QColor_HSV(hue, sat, 255, 20), 2)) Calculate our variable for our saturation value and for our hue value. (Read a nice explanation of .) Next, we translate our values into a color and apply it to our painter's drawing pen. sat hue hue and saturation # 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) Using our current (X, Y) coordinates in and we calculate our variable. Next, we find the cos of the angle, multiply it by , add it to our current X value. Follow a similar process with the current Y value. Now we have the coordinates of our new point! x_s y_s angle STEP_SIZE # Draw the line p.drawLine(QPointF(x_s, y_s), QPointF(x_f, y_f)) Create s for our current and new points and then draw a line between them. QPointF # Update the line length c_len += math.sqrt((x_f - x_s) ** 2 + (y_f - y_s) ** 2) Square the difference between the old and new X values and add it to the squared difference between the old and new Y values. Take the square root of that value and add it to the current value of . c_len # 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 If we're outside of the image bounds, do nothing. Otherwise, save the new coordinates to the old coordinates variables. That way, they'll be available for the next iteration of the loop. save(p, fname=f'image_{seed}', folder='.', overwrite=True) Finally some simple code! We take our painter instance and save its pixels to our jpg image. draw(3000, 2000, color=63, perlinFactorW=4, perlinFactorH=5, step=0.35) Call the code and generate our masterpiece! What Did We Learn? By using some somewhat complicated math we can generate unique images with very limited inputs. Bonus Section A.K.A Tweak Some Params If we call the function using the parameters listed above we'll get a chaotic image that reminds us of . Why is that? Great question random Internet friend! With large step values (0.35 vs the default 0.001) we move farther apart between points and generate fewer points. That leads to lines instead of organic soft curves. draw string art Try calling the function with different values for the step and Perlin factors to see what images you can make!