Canvas - Drawing on Images
This project adds an additional canvas function, some basic animation and does a little sculpting of randomness.
The new canvas function is
drawImage(image, x, y)
which draws an image on the canvas at coordinates x,y
to draw an image it needs to be loaded. One way to do this is load it from the same directory as the HTML and JavaScript:
var canvas, ctx;
var img = new Image();
img.src = "skyline.jpg";
img.onload = init;
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
}
Images loaded this way load asynchronously, which means they may not be completely loaded when drawImage is called. Telling the img object to call a function after the load is completed avoids that problem.
It’s also possible to pre-load the image with a data url, which is the raw image bits encoded in a string, e.g.:
img.src = "data:image/jpeg;base64,/9j/4QAYRXhpZgAA....";
Places like DataURL.net allow uploading images and offer a conversion service. However, this can also be done easily using the canvas. After loading the image using the first example:
var dataURL = canvas.toDataURL("image/jpeg", 0.2); // override default png and
// specify jpg quality 0..1
tb = document.createElement("textarea");
document.body.appendChild(tb);
tb.value = dataURL;
The encoded string for a jpg image can then be copied from the textarea on the page and pasted into a img.src=‘’; statement.
The downsides are that embedding images this way can result in a quite a large chunk of text in the code and they aren’t cached by the browser. The advantages are no waiting for images to load, they can be used immediately, and the images don’t need to be hosted externally.
Using an image as a backdrop, effects can be created by drawing rectangles with colors and opacity settings on top.
The CityScape
One idea for using images and rectangles was to find an image of a skyline, with silhouettes of buildings, and then add blinking windows to them using small rectangles.
I brought the image into Photoshop and used the rectangular selection marquee to plot out the areas where the “windows” should go.
By drawing the marquee and then selecting Transform (Ctl or Cmd T) the info box populates both the height and width of the rectangle as well as the x,y origin point of the top left.
The areas can then be mapped as entries in a JavaScript array:
areas = [
{ x: 0, y: 219, w: 17, h: 38 },
{ x: 39, y: 227, w: 30, h: 30 },
{ x: 80, y: 233, w: 11, h: 47 },
{ x: 101, y: 163, w: 21, h: 111 },
];
A function can be written that will take this area and for each area, draw however many rectangles will fit with coordinates inside the area.
The next task is to make the rectangles blink on an off and this introduces a very important tool for generative art: animation.
We need a loop that will run continuously and update the canvas each time it is called. The modern approach to this is via:
RequestAnimationFrame(callback)
tell the browser to call the function “callback” before the next canvas repaint
So setting up a basic loop that will continuously call the draw function to update the canvas:
window.requestAnimationFrame(draw); // call draw before next canvas repaint
function draw() {
// do canvas draw stuff
window.requestAnimationFrame(draw); // call draw again before next repaint
}
If things are running optimally the draw() function will generally be called about 60 times a second.
The final construct needed to implement CityScape was a way to control the random toggling of lights on and off. If random() was used each time the draw() function was called, 60 times a second, it would just be a spastic jitter. To slow it down, the random behavior needs to be tweaked to be a little less uniform. Shaping randomness in various ways is very useful in tuning the aesthetics of its contribution to a piece. In this case, it toggles a light on or off only a small fraction of the time:
if (Math.random() < 0.005) {
rects[i].d = !rects[i].d;
}
If something needed to happen only 1 every 10 times:
if (Math.random() < 0.1) {
rects[i].d = !rects[i].d;
}
Since random() returns floating point numbers fairly uniformly between 0 and 1, the above would be true only 10% of the time.
Putting these new constructs together into a finished piece: