New tool helps creatives turn ideas into code

Scott Garner introduces the new tool for creative coders.

The new 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.

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();

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 (seen overleaf) 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 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.

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.

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
  );
};

Next page: drawing stars and what's next...