Week 12 — Image

All things images ⟶ load, position, tint, filter and manipulate pixels of images in p5js.

Images used this week are frames from the cat video in /Examples/1.2_VideoPixels ⟶ /assets

Loading and Displaying Images

  • loadImage(path) ⟶ loads an image. Path is the exact location of the file. eg., “image.png” or “images/image.png”
  • Load images in preload.
  • image(img, x, y, width, height) ⟶ draws the image.
var img;

function preload() {
    img = loadImage("../assets/cat.jpg");
}

function setup() {
    createCanvas(640, 360);
}

function draw() {
    image(img, 0, 0, img.width, img.height);
}
A grey and white kitten lying on a knitted blanket.
/assets/cat.jpg
  • image() takes 4 more parameters ⟶ to draw a part (source rectangle) of the image.
// d ⟶ destination (canvas), s ⟶ source (image)
image(img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight);

Image Tint and Filter

Tint

  • tint(r, g, b, alpha) ⟶ brings out certain colors of the image. eg., tint(255, 0, 0) ⟶ redder image.
  • alpha ⟶ transparency.
  • Inspiration ⟶ Andy Warhol’s screen prints. eg., Marilyn Monroe, Elvis. Basic colors are brought out and repeated.

Example: Warhol style (code in folder /12.3-Tint)

var catImg;
var numCats = 5;

function draw() {
    background(30, 90, 200);
    for (var i = 0; i < numCats; i++) {
        tint(i * 50, i * 20, 255 - i * 40, 150);
        image(catImg, i * 60, i * 20, catImg.width / 2, catImg.height / 2);
    }
}
Five overlapping copies of the cat photo on a blue background, each tinted a different color with transparency.
Warhol style
  • Without alpha ⟶ looks like a collage. With alpha ⟶ looks like a screen print.
  • img.width / 2 ⟶ resize to half the source image size.
  • background(img) ⟶ draws an image as the background (image must be the same size as the canvas).

Filter

  • filter(MODE, level) ⟶ applies a filter to the canvas. Call it after image().
// level 0 to 1 ⟶ pixels above the level are white, below are black
filter(THRESHOLD, map(mouseX, 0, width, 0, 1));

// level 2 to 255 ⟶ number of colors per channel. Most visible between 2 and 5
filter(POSTERIZE, 3);

// film negative. No level
filter(INVERT);

// black and white
filter(GRAY);

Aspect ratio ⟶ ratio between width and height. Keep it while resizing, otherwise the image is stretched.

var resizedHeight = (img.height * width) / img.width;
image(img, 0, 0, width, resizedHeight, 0, 0, img.width, img.height);

Code in folder /12.3-Filter

filter(THRESHOLD) ⟶ level follows mouseX
  • Public domain images and art ⟶ Wikimedia Commons. eg., Venus of Urbino by Titian.

Image Pixels

Copy

  • img.copy() ⟶ copies a part of the image and pastes it somewhere else. Like select and copy in Photoshop.
// source image, source selection (x, y, w, h), destination (x, y, w, h)
img.copy(srcImage, sx, sy, sw, sh, dx, dy, dw, dh);

Example: Glitch art ⟶ misaligned bands (code in folder /12.4-Copy)

var bandMisalign = 60;
var bandHeight = 20;

function setup() {
    createCanvas(640, 360);
    for (var i = 0; i < img.height; i += bandHeight) {
        img.copy(img, 0, i, img.width, bandHeight, random(-bandMisalign, bandMisalign), i, img.width, bandHeight);
    }
}
The cat photo cut into horizontal bands, each shifted sideways by a random amount.
glitch ⟶ misaligned bands
  • Larger bandMisalign ⟶ bigger shifts. Smaller bandHeight ⟶ more bands.
  • New random image on every refresh ⟶ generative image.

Pixels

  • img.get(x, y) ⟶ color of the pixel at x, y.
  • All the pixels of an image can become data.
  • Large images ⟶ a lot of computation. Use smaller images or skip pixels (larger step).

Example: Pixels to ellipses (code in folder /12.4-Pixels)

var step = 20;

function draw() {
    background(255);
    for (var x = 0; x < img.width; x += step) {
        for (var y = 0; y < img.height; y += step) {
            fill(img.get(x, y));
            // random offset ⟶ not exactly on a grid, looks more natural
            ellipse(x + random(-2, 2), y + random(-2, 2), step, step);
        }
    }
}
  • noLoop() in setup ⟶ draws once. Otherwise the random offsets change every frame.
The cat photo redrawn as a grid of overlapping circles, each filled with the color of the pixel underneath.
pixels ⟶ ellipses

Animated Images or Sprites

  • Sprite ⟶ a single image with all the frames of an animation. A different part of the image is drawn each frame.
  • The eye doesn’t see the shift, it sees movement.
  • Common technique in animation. Easy for the computer and low on memory.
  • Successor of Eadweard Muybridge’s animal locomotion studies (1800s) ⟶ successive photographs of animals and people in motion played back quickly.

Example: Sprite sheet (code in folder /12.5-SpriteSheet)

var numFrames = 8;

function setup() {
    createCanvas(400, 400);
    frameW = spriteSheet.width / numFrames;
    frameH = spriteSheet.height;
    frameRate(8);
}

function draw() {
    background(255);
    var index = frameCount % numFrames;
    image(spriteSheet, 0, 0, frameW, frameH, index * frameW, 0, frameW, frameH);
}
A horizontal strip of eight small frames of the cat moving.
/assets/cat-sprite.png ⟶ 8 frames in one image
one frame of the sheet per draw()

Example: Image sequence (code in folder /12.5-ImageSequence)

var frames = [];

function preload() {
    for (var i = 0; i < 12; i++) {
        // nf(i, 3) ⟶ number to string with 3 digits. 1 ⟶ "001"
        frames.push(loadImage("../assets/cat-sequence/cat_" + nf(i, 3) + ".png"));
    }
}

function draw() {
    background(255);
    image(frames[frameCount % frames.length], width / 2, height / 2);
}
12 images, one per frame

p5.play library

  • p5.play ⟶ library for sprites and image sequence animations.
  • loadAnimation(first image, last image) ⟶ finds all the numbered images in between.
var ballerina;

function preload() {
    ballerina = loadAnimation("assets/ballerina_000.png", "assets/ballerina_012.png");
}

function draw() {
    background(255);
    animation(ballerina, width / 2, height / 2);
}
  • The examples above do the same thing without the library.

Other Tools for Working with Images

  • jsfeat ⟶ feature detection. eg., edges, stitching images together based on features.

  • imagetracer.js ⟶ traces an image into vectors. Like tracing in Illustrator.

  • Command line tools

    • FFmpeg ⟶ convert images, image sequence to a movie and back.
    • ImageMagick ⟶ convert, resize, stitch images.
# the sprite sheet and image sequence in /assets were made from a video like this
ffmpeg -ss 26 -t 1.6 -i cat.mp4 -vf "fps=5,scale=160:-1,tile=8x1" -frames:v 1 cat-sprite.png
ffmpeg -ss 26 -t 2.4 -i cat.mp4 -vf "fps=5,scale=320:-1" -start_number 0 cat_%03d.png

Example sketches

The p5 sketches for this week, from the course repo.

12.2 — Load Imagerepo ↗
var img;

function preload() {
  img = loadImage("../assets/cat.jpg");
}

function setup() {
  createCanvas(640, 360);
}

function draw() {
  background(220);
  // image(img, x, y, width, height)
  image(img, 0, 0, img.width, img.height);
}
12.3 — Filterrepo ↗
var img;

function preload() {
  img = loadImage("../assets/cat.jpg");
}

function setup() {
  createCanvas(640, 400);
}

function draw() {
  background(255);
  // keep the aspect ratio while fitting the canvas width
  var resizedHeight = (img.height * width) / img.width;
  // image(img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight)
  image(img, 0, 0, width, resizedHeight, 0, 0, img.width, img.height);
  // filter comes after image
  filter(THRESHOLD, map(mouseX, 0, width, 0, 1));
  // filter(POSTERIZE, map(mouseX, 0, width, 2, 5));
  // filter(INVERT);
  // filter(GRAY);
}
12.3 — Tintrepo ↗
// Andy Warhol style screen print
var catImg;
var numCats = 5;

function preload() {
  catImg = loadImage("../assets/cat.jpg");
}

function setup() {
  createCanvas(640, 400);
}

function draw() {
  background(30, 90, 200);
  for (var i = 0; i < numCats; i++) {
    // tint(r, g, b, alpha) ⟶ alpha for transparency like a screen print
    tint(i * 50, i * 20, 255 - i * 40, 150);
    image(catImg, i * 60, i * 20, catImg.width / 2, catImg.height / 2);
  }
}
12.4 — Copyrepo ↗
// glitch - misaligned bands
var img;
var bandMisalign = 60;
var bandHeight = 20;

function preload() {
  img = loadImage("../assets/cat.jpg");
}

function setup() {
  createCanvas(640, 360);
  for (var i = 0; i < img.height; i += bandHeight) {
    // img.copy(src, sx, sy, sw, sh, dx, dy, dw, dh)
    img.copy(
      img,
      0, i, img.width, bandHeight,
      random(-bandMisalign, bandMisalign), i, img.width, bandHeight
    );
  }
}

function draw() {
  background(255);
  image(img, 0, 0);
}
12.4 — Pixelsrepo ↗
var img;
var step = 20;

function preload() {
  img = loadImage("../assets/cat.jpg");
}

function setup() {
  createCanvas(640, 360);
  noStroke();
  noLoop();
}

function draw() {
  background(255);
  // larger step ⟶ fewer pixels to go through ⟶ faster
  for (var x = 0; x < img.width; x += step) {
    for (var y = 0; y < img.height; y += step) {
      // color of the pixel at x, y
      fill(img.get(x, y));
      // small random offset so it's not exactly on a grid
      ellipse(x + random(-2, 2), y + random(-2, 2), step, step);
    }
  }
}
12.5 — Image Sequencerepo ↗
// sequence of images cat_000.png to cat_011.png
var frames = [];
var numFrames = 12;

function preload() {
  for (var i = 0; i < numFrames; i++) {
    frames.push(loadImage("../assets/cat-sequence/cat_" + nf(i, 3) + ".png"));
  }
}

function setup() {
  createCanvas(400, 400);
  imageMode(CENTER);
  frameRate(10);
}

function draw() {
  background(255);
  image(frames[frameCount % numFrames], width / 2, height / 2);
}
12.5 — Sprite Sheetrepo ↗
// one image with 8 frames side by side
var spriteSheet;
var numFrames = 8;
var frameW;
var frameH;

function preload() {
  spriteSheet = loadImage("../assets/cat-sprite.png");
}

function setup() {
  createCanvas(400, 400);
  frameW = spriteSheet.width / numFrames;
  frameH = spriteSheet.height;
  frameRate(8);
}

function draw() {
  background(255);
  // shift the part of the image that is drawn each frame
  var index = frameCount % numFrames;
  image(spriteSheet, width / 2 - frameW, height / 2 - frameH, frameW * 2, frameH * 2, index * frameW, 0, frameW, frameH);
}