Explore creative code with P5.js

p5.js brings the power and flexibility of Processing to the web. Scott Garner introduces a great tool for creative coders.

Spend a day with Brendan Dawes at Generate London and discover how data is Dawesome. In this all-day workshop he'll explain how to take the data that surrounds us every day and turn it into something beautiful using Processing and p5.js. Book now!

p5.js is a library designed to bring the power of Processing to the web. It aims to introduce artists, designers and educators to the world of programming while also offering versatile tools to bring devs and engineers into the visual arts.

Let's dive in and create our first 'sketch'. Our goal is to build a drawing tool that transforms a simple image into a field of animated stars. First, we'll define a few global variables and write our setup() function. p5's setup() is run once, when the sketch is loaded, so it's the ideal place to handle initialisation. 

Download the files you'll need here.

var hintImage, skyImage, stars = [];
function setup() {... }

Inside our setup function, we'll create a canvas and hide the mouse cursor so we can draw our own. By default, p5 adds an outline around shapes – we want to disable strokes in this case.

createCanvas(800,500);
noCursor();
noStroke();

The background image – this night sky scene provides the setting for our animation

Next, we'll load a pair of images. One will serve as the background – in this case, a night sky scene. The other will be the 'hint' image – the black and white design our final design will be based on. The idea is to put most of the stars over black pixels in our hint image, to recreate the design in our background scene. It would be easy to create these images with p5's text and drawing tools, but for the sake of brevity we'll use static assets.

hintImage = loadImage("//bit.ly/hintImage");
skyImage = loadImage("//bit.ly/skyImage");

The hint image, which dictates the positioning of our stars

The draw function

That's it for setup()! Another key function is draw(). It's called in a continuous loop, which is helpful for animations and adding elements over time.

function draw() {... }

Within the Draw function, our first task is to fill the canvas with our background image. p5 doesn't automatically clear the canvas between draw() calls, so we need to do this every frame or we'll end up with some strange accumulation effects. To place a loaded image on the canvas, use the image() function and give x and y coordinates for positioning.

image(skyImage, 0, 0);

Next, we'll grab the current mouse location and store it as a p5.Vector using createVector(). This object comes with handy functions to deal with points in space, but we're mostly just using it as a container.

var position = createVector(mouseX, mouseY);

Using our newly-stored mouse position, we can draw our cursor. We'll set the drawing colour with fill() by passing RGB values and use ellipse() to draw a circle at the mouse location.

fill(255, 192, 0); 
ellipse(position.x, position.y, 8, 8);

We only want to draw new stars while the mouse is pressed, so we'll check p5's mouseIsPressed before proceeding. If the mouse is down, we need to calculate a good place for our next star to end up. We'll do that with a custom function called findPixel(), which we'll define later.

Discover the creative side of code with Brendan Dawes at Generate London

 Once we have a target, we'll create a new instance of a custom Star object (more below) and push it onto the end of our star array. If we end up with over 2,000 stars, we'll start discarding the oldest ones. 

if (mouseIsPressed) {
var target = findPixel();
var star = new Star(position, target);
stars.push(star);
if (stars.length > 2000) stars.shift();
}

Finally, we'll loop through our array of stars and call update() and draw() on each of them. We'll dive into these methods later on.

for (var i = 0; i < stars.length; i++) {
stars[i].update();
stars[i].draw();
}

Helper functions

Now setup() and draw() are in place we'll work on the helper functions and objects. First, we'll define a function that finds a resting position for each new star. All we need to do is check some random pixels in our hint image, using get() to see if they're black or white.

We actually only have to look at their red value, because in both cases the RGB values match. If we find a non-white pixel, we'll return it because we've hit our design. If not, we'll just return the last pixel checked and it will serve as a random star.

function findPixel() {
var x, y;
for (var i = 0; i < 15; i++) {
x = floor(random(hintImage.width));
y = floor(random(hintImage.height));

if (red(hintImage.get(x,y)) < 255) break;
}
return createVector(x,y);
}

Last up is our custom Star object. Put simply, we want a reusable container that stores information about each star, and also provides a means for updating and drawing them. JavaScript doesn't provide a way to create classes in a traditional sense, but we can get the same result by defining a function and extending it for our own needs.

Each star has a few properties (starting position, final location and a randomly generated diameter), which we'll define in a 'constructor function' that is called for every new star created in our draw loop.

function Star(position, target) {
this.position = position;
this.target = target;
this.diameter = random(1, 5);
}

Next we'll add an update() method to Star, which will use p5.Vector's lerp() to calculate a new location between a star's current and target positions. In this case, we're moving four per cent of the remaining distance for every draw, which effectively gives us an ease-out effect.

If we wanted to get fancy, we could also simulate some physics here, but for now we'll keep it simple.

Star.prototype.update = function() {
this.position = p5.Vector.lerp(
this.position,
this.target,
0.04
);
};

Drawing stars

Finally, Star's draw() method actually draws the star to the canvas. Once again, we're using fill() and ellipse(), although this time we're calling fill() with a grayscale value and an alpha value for transparency.

Each frame, linear interpolation gives us a new point along the path between the current star position and its destination

 To give the stars a twinkle, the alpha value is determined using p5's noise() function. This returns the Perlin noise value for the specified coordinates, meaning you get a smooth, fluid sequence of random numbers. For the third parameter, we're passing a time-based value rather than a spatial value, so that the noise will animate over time. 

Star.prototype.draw = function() {
var alpha = noise(
this.target.x,
this.target.y,
millis()/1000.0
);

fill(255, alpha * 255);

ellipse(
this.position.x, this.position.y,
this.diameter, this.diameter
);
};

That's it for our first sketch! Click and drag to see new stars appear and conform to the hint image.

What's next?

From here, you might add some HTML elements to control variables using the p5.dom add-on or even add sound to the visuals using p5.sound.

We've only scratched the surface of what's possible with p5.js, and with new features on the way, there's even more to look forward to. Have fun!

Learn more about the creative side of coding at Generate London! Brendan Dawes will teach you how to use Processing and p5.js to turn data into things of beauty in his workshop; he'll also be talking about the joy of working with paper, plastic and pixels. Book your ticket now!

ABOUT THE AUTHOR

Scott is a multidisciplinary artist working to unite technical, design and craft skills into a cohesive creative voice.

Topics