The easiest way to make interesting art with programming is to use simple functions. Because an image is just like a giant chessboard, and every square is defined with a single color (using a simple function).
Here is how your picture (grid) might look like:
This is an example of an image 10×10 pixels. Keep in mind your average Instagram image is 1,000×1,000 pixels.
(the principles are the same, only the numbers are bigger).
The simplest thing you can do with your code is to make a grayscale image. Upper left corner can be completely dark and bottom right can be completely light. Something like this:
And the formula to get pixel values looks very simple:
const pixelValue = ((x + y) / 2) / IMG_SIZE;
We’re just taking the average of two coordinates. And we’re dividing it by the size of the image to get our results in the range of 0 to 1;
This is important because our RGBA colors can only go from 0 to 255. And this is why in out color function we need to multiply everything with 255.
const colorValue = Math.round(pixelValue * 255);
So out only task is to find creative ways to transform x and y values into zero to one [0, 1] results. And this is enough to transform an image of any size into colors.
NOTE: if we use the same values for R, G, and B – then we will always end up with grayscale result. I’ll show you later how to work with colors but first let’s see what can we creatively do with grayscale.
Here is the full function for getting (currently grayscale) colors:
const getColor = (x, y) => {
const pixelValue = ((x + y) / 2) / IMG_SIZE;
const colorValue = Math.round(pixelValue * 255);
return {
red: colorValue,
green: colorValue,
blue: colorValue
};
}
But let’s first set up your project so you can follow along…
Project Setup
We will start with default HTML code:
<!DOCTYPE html>
<html>
<head>
<title>Programming For Art</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// all JS code goes here
</script>
</body>
</html>
And here is your JavaScript that does all the work. Most of it you won’t have to touch at all. Your main focus will be getColor function:
// just creating canvas
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const IMG_SIZE = 200; // set any image size you want
ctx.canvas.width = IMG_SIZE;
ctx.canvas.height = IMG_SIZE;
// this is just a giant array with all pixel values
const imgData = ctx.createImageData(IMG_SIZE, IMG_SIZE);
const getColor = (x, y) => {
const pixelValue = ((x + y) / 2) / IMG_SIZE;
const colorValue = Math.round(pixelValue * 255);
return {
red: colorValue,
green: colorValue,
blue: colorValue
};
}
// Iterate through every pixel
for (let row = 0; row < IMG_SIZE; row++) {
for (let col = 0; col < IMG_SIZE; col++) {
// every pixel is defined with 4 numbers (RGBA)
// that's why we jump by 4 array items at a time
const idx = (row * IMG_SIZE + col) * 4;
// Modify pixel data
imgData.data[idx + 0] = getColor(row, col).red;
imgData.data[idx + 1] = getColor(row, col).green;
imgData.data[idx + 2] = getColor(row, col).blue;
imgData.data[idx + 3] = 255; // no transparency
}
}
// Draw image data to the canvas
ctx.putImageData(imgData, 0, 0);
After this we’re mostly going to do changes in getColor function and express our creativity there.
Playing With Creativity
Since I said all the solutions must fall between 0 and 1 we can start to think which functions fall under that category.
Normalized average (that we used at the beginning) is definitely one of them. Second one is Math.sin (almost). Because it’s results fall between -1 and 1. But that is easily fixable with:
const pixelValue = Math.abs(Math.sin((x + y) / 2))
And we’ll get an image like this:
The interval repeats every 2*PI (cca 6.28) so we have a lot of iterations. But if we slowed down the changes 10 times, we could see much clearly what is going on.
const pixelValue = Math.abs(Math.sin(((x + y) / 20)));
And how the colors are gradually changing due to our Math.sin function from completely dark to completely light colors and back:
We could also make this in color. For example, if we wanted green color, we could just set red and blue to zero:
return {
red: 0,
green: colorValue,
blue: 0
};
You can progress further by remembering the circle formula from school:
r*r = (x*x + y*y)
So what happens if we incorporate this into our pixelValue code?
const pixelValue = Math.abs(Math.sin(((x*x + y*y) / 20)));
Ok – this looks very interesting. We can probably zoom it in if we divide it by 50 instead. (Basically what we’re doing is increasing the size of the interval required for our function to repeat itself so it looks “zoomed in”.)
const pixelValue = Math.abs(Math.sin(((x*x + y*y) / 50)));
Wow, this looks really nice. But what about colors?
Wouldn’t it be nice if could add some other colors? For example blue. So whenever we have green approaching zero (decreasing in intensity and turning black, we could have blue increase in intensity).
return {
red: 0,
green: colorValue,
blue: 255 - colorValue
};
Or we could have different colors dominate depending on the green color intensity. For example:
- red for green values 0-99
- blue for green values of 100-199
return {
red: (colorValue < 100)
&& (255 - colorValue) || 0,
green: (colorValue >= 200)
&& colorValue || 0,
blue: (colorValue >= 100 && colorValue < 200)
&& colorValue || 0,
};
As you can see, with only a few simple formulas (average, Math.sin, circle) you can create very interesting designs. And there are plenty more you can use. So definitely play with them and I wish you good luck and a lot of fun.
P.S. -> the real art is to know how to double your salary without doing any extra work