Convert images to mosaics in p5.js
Tags: javascript, art, tutorial, codenewbie
Published: 2022-01-01 22:22:54

---
title: Convert images to mosaics in p5.js
published: true
description: 
tags: #javascript, #art, #tutorial #codenewbie
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jdtwd7uboo25sn97qthk.png
---

p5.js is a fun JavaScript library for drawing on an HTML5 canvas, and it has [some of the clearest tutorials I have seen](https://p5js.org/get-started/). It gives you functionality for things like image manipulation, drawing lines and shapes, displaying images, working with trigonometry, and more. And it is especially popular for [generative art](https://dev.to/aspittel/intro-to-generative-art-2hi7), such as fractals.

In this tutorial, I will show you how to use p5.js to convert an image like this:

![Photograph of large boulders on the sand at Singing Beach in Manchester By The Sea. Behind them is the Atlantic Ocean, in many shades of blue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz8m78ut9yjsiamaki9g.jpg)

to a mosaic of dots like this:

![The same photograph of Singing Beach, converted to a mosaic of dots, rather than pixels. Each dot is the color of the pixel of the original image that's located at the center of that dot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l2wy8sgh5y45yhm6hcg.png)

This tutorial assumes a working knowledge of JavaScript and familiarity with pre-algebra, but prior knowledge of p5.js isn't strictly necessary. You can follow along on this by creating an account on the [p5.js online editor](https://editor.p5js.org/) and logging in. The finished product can be found [here](https://editor.p5js.org/andyhaskell/sketches/fwaWSmAh_).

## 📝 Making a first canvas

As a basic p5.js program, let's start by making a canvas and drawing a single small dot there. We would do that by taking this code to the p5.js editor:

```javascript
function setup() {
  createCanvas(300, 200);
}

function draw() {
  ellipse(50, 60, 15, 15);
}
```

We are starting with basic implementations two of the major functions in a p5.js program: `setup` and `draw`.

The `setup` function runs at the beginnng of a p5.js program, and what we're doing in it is calling [createCanvas](https://p5js.org/reference/#/p5/createCanvas), a built-in function from p5.js, to create a small HTML5 `<canvas>` element of width 300 and height 200.

The `draw` function runs repeatedly in the JavaScript event loop, and what we're doing is calling `ellipse` to put a circle on the canvas, with a diameter of 15 pixels and its center at point `(50, 60)` of that canvas. Remember at school plotting points on Cartesian coordinate grids in math class? That is the same concept here with drawing on a canvas. In fact, a lot of concepts from math class can be used as tools to make cool art!

Now that we've got our setup and draw functions, press play on the p5.js editor, and you should see something like this:

![Blank white canvas in the p5.js editor, with a single white circle with a black outline in its top left corner](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2x9ytz1h69mm9htikvha.png)

One key difference between the Cartesian grids in math class, and the ones in an HTML5 canvas, though, is that as you can see, point `(50, 60)` is at the top-left of the canvas, not the bottom-left. Unlike in the graphs from math class, the y-axis on an HTML5 canvas goes from top to bottom, not bottom to top. The x-axis, though, still goes left to right.

By the way, since we're only drawing our picture once rather than repeatedly (like if we were making an animated p5.js sketch), it's kind of pointless to call `draw` repeatedly. So let's make it so we're only calling `draw` once.

```diff
  function setup() {
    createCanvas(300, 200);
+   noLoop();
  }
```

By adding a call to [noLoop](https://p5js.org/reference/#/p5/noloop), now after the first time we call `draw`, we don't call `draw` again unless our code calls [redraw](https://p5js.org/reference/#/p5/redraw).

Before we move on to loading an image, one other thing worth noting, circles/ellipses are not the only shape you can draw in p5. You can find code to draw other shapes, like lines, curves, rectangles, and more, in the links at [this reference](https://p5js.org/reference/#group-Shape).

## 📷 Loading an image

We've got our canvas made, but now we need a way of loading the image we're editing.

First, in the p5 editor, left of the `sketch.js` filename, click the right arrow to pop our the "sketch files" panel, click the down triangle on the line that says "sketch files", select "upload file" in the dropdown, and then upload your image.

Now, to use the image, add the following code to the p5.js editor, adding a `preload` function and replacing the `setup` function:

```javascript
let img;

function preload() { img = loadImage('./beach.jpg'); }

function setup() {
  createCanvas(img.width, img.height);
  noLoop();
}
```

The `preload` function runs before `setup` to load any assets needed for our p5.js program. What we're doing in our preload function is calling p5.js's [loadImage](https://p5js.org/reference/#/p5/loadImage) function to load an image, represented in JavaScript as a [p5.Image](https://p5js.org/reference/#/p5.Image) object, that we can manipulate. We store that Image in the `img` global variable. Note that if you're using an image besides `beach.jpg`, you'll want to change the name of the image you're loading in `loadImage`.

Now, in `setup`, we call `createCanvas` like before, but now we use the `Image` object to load the image. We then retrieve the image's width and height so the canvas we make is now the same size as the image.

Now that we've got the image's width and height, and a canvas made in that size, we're going to switch over to drawing the dots on our mosaic.

## 🐆 Plotting the dots

Circling back to our `draw` function, let's replace that function's entire code with this:

```javascript
function draw() { drawMosaic(5) }

function drawMosaic(dotRadius) {
  // [TODO] Add code to put the dots on the mosaic!
}
```

Just like how in programming languages like Go, it's a good idea to have the `main` relatively simple, I like having my `draw` function be just a one-liner that calls the function that does the bulk of the action. We're going to have `drawMosaic` be the central function of this program; it takes in the radius we want each dot to be, and it will be in charge of drawing all our dots.

We want dots all over the picture, so let's break up the image into columns; each column will be about 1.5 times the width of a dot (3 times the radius), and will be filled top to bottom with dots. So we'll need to know:

1. How many columns the image will have
2. With that knowledge, how to draw a column.

Let's start by just displaying a vertical line for each column. We'll get rid of the line later, but for now this is helpful as scaffolding, so if something is off about how we render the dots, such as what size they are, or where the dots are drawn, we can figure out what's being drawn in a given column relative to that column's lines.

So let's add these functions:

```javascript
const columnWidth = (dotRadius) => dotRadius * 3;

const numberOfColumns = (dotRadius) =>
  Math.ceil(width / columnWidth(dotRadius));

function drawColumnDots(dotRadius, offsetX) {
  // [TODO] Replace the line with a column of dots
  line(offsetX, 0, offsetX, height);
}

function drawMosaic(dotRadius) {
  for (let i = 0; i < numberOfColumns(dotRadius); i++) {
    offsetX = i * columnWidth(dotRadius);
    drawColumnDots(dotRadius, offsetX);
  }
}
```

Here's our functions so far:

* `columnWidth` is a helper function to get the width of a column. We have a column be triple the radius of a dot, so that we give each dot a bit of wiggle room as to where it will be drawn.
* `numberOfColumns` tells us how many columns of dots we can fit in the picture. Which is the width of the image divided by the width of a column.
* `drawColumnDots` will be in charge of adding all the dots to a given column, starting at the x-coordinate `offsetX` we pass in and ending at `offsetX + dotRadius`. For now, as scaffolding, we will just draw a straight vertical line at the left edge of the column.
* `drawMosaic` draws every column; we loop over the number of columns we have, and for each one we create a column that starts at the x-coordinate `i` times the width of a column. For example, if we have a column width of 15, then the sixth column of dots (zero indexed, so i = 5) of the mosaic starts at an `offsetX` of 75 pixels.

Press play on the p5.js editor, and you should see something like this:

![p5.js editor showing an HTML5 canvas displaying several vertical 1-pixel-wide black lines, indicating the left edge of each column of dots](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a2jf3d38vk737as67w0z.png)

But we didn't come here to draw some vertical lines, we came here to draw some dots, so let's do that!

```javascript
function drawColumnDots(dotRadius, offsetX) {
  // [TODO] Replace the line with a column of dots
  line(offsetX, 0, offsetX, height);

  let dotDiameter = 2 * dotRadius;
  let dotHeightWithPadding = dotDiameter + 2;
  let numDotsInColumn = Math.floor(height / dotHeightWithPadding);

  for (let i = 0; i < numDotsInColumn; i++) {
    let centerX = Math.floor(random(
      offsetX + dotRadius,
      offsetX + columnWidth(dotRadius) - dotRadius,
    ))
    
    let centerY = i * dotHeightWithPadding + dotRadius;

    ellipse(centerX, centerY, dotDiameter, dotDiameter);
  }
}
```

Here's what happens:

* First, we declare variables for the diameter of a dot, and the height of each dot, with two pixels of padding so the dots aren't touching each other. We then divide the height of the image by `dotHeightWithPadding` to get the number of dots in the column.
* Then, in the for loop, we will draw all the dots, from the top of the column to the bottom. First, we calculate the coordinates of the pixel at the center of the dot.
  * For the x-coordinate, the leftmost position a dot can be is `dotRadius` pixels to the right of the start of the column. And the rightmost column is `dotRadius` pixels to the left of the end of the column. So if a column is 15 pixels wide with a 5-pixel dot radius, we would randomly select an x-coordinate between 5 and 10 pixels to the right of the start of a column.
  * For the y-coordinate, each dot is `dotHeightWithPadding` pixels lower than the dot above it. We place the top dot's center at `dotRadius` pixels below the top of the pixel, so that the top dots don't get cut off.

![Our HTML5 canvas. Now, in each column marked by the lines, there are black circle outlines a couple pixels apart from each other.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lblzf44av9d1n3kvz09d.png)

Looks good, but we could use some randomness vertically too to so the dots aren't necessarily at the same height as the ones to the left and right of each other.

```diff
+ let topY = Math.floor(random(10));

  for (let i = 0; i < numDotsInColumn; i++) {
    let centerX = Math.floor(random(
      offsetX + dotRadius,
      offsetX + columnWidth(dotRadius) - dotRadius,
    ))
    
-   let centerY = i * dotHeightWithPadding + dotRadius;
+   let centerY = topY + i * dotHeightWithPadding + dotRadius;

    ellipse(centerX, centerY, dotDiameter, dotDiameter);
  }
```

![Our HTML5 canvas. Now, the columns of dots start at different pixel positions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnpa6knrxs98f0ojo1lz.png)

Looks good! Before we go on to fill in the colors of the columns, remove the call to `line`, since we no longer need that piece of scaffolding.

## 🎨 Giving the dots their colors

The last step of drawing our mosaic is to color the dots. Each dot will be the same color as the color of the pixel at the center of the dot. Here's how we would do that:

```javascript
  let dotColor = img.get(centerX, centerY);
  noStroke()
  fill(dotColor);

  ellipse(centerX, centerY, dotDiameter, dotDiameter);
```

Here's what happens:

* First, we use `Image.get` to retrieve the color of the pixel at the coordinates `(centerX, centerY)`. This is represented as an array of 4 numbers: red, green, blue, and alpha-transparency (how see-through a pixel is).
* We call [noStroke](https://p5js.org/reference/#/p5/nostroke) to remove the outline on the dots, and we call [fill](https://p5js.org/reference/#/p5/fill) to set the color of a dot.
* Finally, calling `ellipse` draws the dot in the color we selected.

Press play on the p5.js editor, and now the canvas will look like this:

![Our canvas, now showing all the dots from earlier, but each dot's color is taken from the image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j3i4elxpdz6htoyscq4j.png)

Cool! One other thing I'd like to add though. This picture has a lot of light-colored pixels, so the dots would stand out better on a dark-colored background. So let's refactor `drawMosaic` so that you can pick the color of the background.

```javascript
function draw() { drawMosaic(10, color(30, 30, 30)); }
  
function drawMosaic(dotRadius, backgroundColor) {
  background(backgroundColor);
  
  // ... rest of the code in the function ...
}
```

We add a new parameter `backgroundColor` to our `drawMosaic` function, and we pass that into [background](https://p5js.org/reference/#/p5/background) to draw a background. In `draw`, I picked the color `30, 30, 30`; since red/green/blue go from 0 to 255, this gives us a charcoal-black background color. I also made the dot radius 10 pixels instead of 5 to make the picture feel more abstract. Run the play button on the sketch, and now the mosaic looks like this!

![Our canvas, now showing all the dots from earlier, but each dot's color is taken from the image, and the background the dots are on is charcoal black](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l2wy8sgh5y45yhm6hcg.png).

We've made a cool piece of artwork with just 46 lines of code, but we've only scratched the surface of the kinds of art you can do with p5.js. If you had fun with this, you should check out the docs for more of p5's code, other people's sketches and YouTube videos for ideas on how you can work with p5 concepts, and check out your old notes from math class to see what other kinds of math, like trigonometry, can be used to make cool artwork!