INTRO TO COMP MEDIA
IDEATION
Explore ways of changing the form of typography with generative coding
Illustrate characters using basic shapes
Experiment with changing the parameters with organic inputs like PoseNet (hand/body/face)
Changing width/height/thickness/color/roundness/shape/etc of characters with PoseNet
Generate text & random phrases
possibly from a database (movie quotes/weather/fortune reading)
To create a fun and explorative way to interact with typography
The output should be visually intriguing and vary depending on the input
EXPLORATION
Experimenting with pixel manipulation with typography and text. Implementing from Processing-Tutorial: Kinetic Typography 1 https://timrodenbroeker.de/processing-tutorial-kinetic-typography-1/, using createCanvas();, but very heavy on computing power.
p5.js code
let pg;
function setup() {
createCanvas(200, 200);
pg = createGraphics(200, 200);
}
function draw() {
background(220);
pixelDensity(1);
pg.textSize(150);
// pg.push();
// pg.translate(width/2, height/2);
pg.textAlign(CENTER, CENTER);
pg.text("a",width/2, height/2);
// pg.pop();
// image(pg, 0,0);
let tilesX = 50;
let tilesY = 5;
let tileW = int(width/tilesX);
let tileH = int(height/tilesY);
for (let y = 0; y < tilesY; y++) {
for (let x = 0; x < tilesX; x++) {
// WARP
let wave = int(sin(frameCount * 0.05 + ( x * y ) * 0.07) * 100);
let waveH = int(sin(mouseY * 0.05 + ( x * y ) * 0.07) * 100);
// SOURCE
let sx = x*tileW + wave;
let sy = y*tileH;
let sw = tileW;
let sh = tileH;
// DESTINATION
let dx = x*tileW;
let dy = y*tileH;
let dw = tileW;
let dh = tileH;
copy(pg, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}
ITERATIONS
Using textToPoints(); to get the outline and points in text. Following https://www.youtube.com/watch?v=wbDF6xcgvV8 Using mouse location to control the size of the ellipses that are mapped to the points retrieved from textToPoints();
VERSION 1
Instead of using mouse location, the size of the ellipse has an initial animation mapped to the sine wave. The size is then mapped to the location of the tip of the index finger by using ml5.
Problem: People don’t usually realize there’s an element that requires the hand to be captured by the camera.
Instead of having a set text, I also added an input and pushed the newest input into an array, and updated it on the canvas.
Problem: The longer the input text is, the more computing power it requires. The animation and interaction slow down a lot when the input is long.
FINAL VERSION
To fix the issues that came up in the previous version. I changed the interaction of using the hand to the tip of the nose so users get an initial response on the screen. To solve the other issue I mapped the “resolution” (number of points that are retrieved from the outline of text) of the points to the length of the input, so that the longer the input is the lower the resolution.
I also added two more forms of interaction so that when users turn their faces to the side, the font changes. When uses turn their heads up or down it toggles between two different modes.
p5.js code
let video;
let facemesh;
let modelLoaded = false;
let predictions = [];
//number of points
let resolution = 0.01;
let pts;
let caslon;
let s = 10;
let pointerX = 0;
let pointerY = 0;
let faceSide = 100;
let faceTurn = 0;
let faceTurns = [];
let faceState = false;
let faceVert = 100;
let faceUp = 0;
let faceUps = [];
let faceVertState = false;
let fonts = [];
let drawState = [];
let chosenFont;
let fontindex = 0;
let drawStateIndex = 0;
let input, button, noseX, noseY;
let inputTexts = [];
let num = 0;
let buttonIsClicked = false;
let drawingDots = false;
let newTextVal;
function preload(){
caslon = loadFont('BigCaslon.otf');
abril = loadFont('AbrilFatface.ttf');
roboto = loadFont('RobotoMono.ttf');
pacifico = loadFont('Pacifico.ttf');
fonts = [caslon, abril, roboto, pacifico];
}
function setup() {
createCanvas(windowWidth, 400);
video = createCapture(VIDEO);
video.size(width, height);
// maxFaces is the maximum number of faces detected in the input. Should be set to the minimum number for performance. Defaults to 10 unless you change it.
// Other available options:
// https://learn.ml5js.org/#/reference/facemesh
let options = {
maxFaces: 1,
};
facemesh = ml5.facemesh(video, options, modelReady);
console.log("Model loading...");
// An event that fills the global variable "predictions"
// with an array every time new predictions are made
facemesh.on("predict", function (results) {
predictions = results;
});
// Hide the video element
video.hide();
colorMode(HSB);
input = createInput('type here');
input.position(0, height);
input.size(100);
button = createButton("submit");
button.position(100, height);
button.mouseClicked(newText);
}
function modelReady() {
console.log("Model ready!");
modelLoaded = true;
}
function draw() {
background(0);
push();
scale(-1, 1);
image(video,-150,0,150,100);
pop();
if (modelLoaded) {
// If there are faces
if (predictions.length > 0) {
// DRAW SELECTION OF KEYPOINTS
// Get the first face
let face = predictions[0];
let nose = face.annotations.noseTip[0];
let top = face.annotations.silhouette[0];
let bottom = face.annotations.silhouette[18];
let left = face.annotations.silhouette[10];
let right = face.annotations.silhouette[26];
noseX = map(nose[0],640,0,0,width);
noseY = nose[1];
let leftX = map(left[0],640,0,0,width);
let leftY = left[1];
let rightX = map(right[0],640,0,0,width);
let rightY = right[1];
let topX = map(top[0],640,0,0,width);
let topY = top[1];
let bottomX = map(bottom[0],640,0,0,width);
let bottomY = bottom[1];
faceVert = dist(topX, topY, bottomX, bottomY);
faceSide = dist(rightX, rightY, leftX, leftY);
}
}
if (faceSide < 150){
faceTurn = 1;
}else{
faceTurn = 0;
}
faceTurns.push(faceTurn);
for (i = 0; i < faceTurns.length; i++) {
if (faceTurns[i+1] > faceTurns[i]){
faceState = true;
}
if (faceTurns[i+1] === faceTurns[i]){
faceState = false;
}
}
if (faceState){
fontindex ++;
}
chosenFont = fonts[fontindex%fonts.length];
if (faceVert < 130){
faceUp = 1;
}else{
faceUp = 0;
}
faceUps.push(faceUp);
for (i = 0; i < faceUps.length; i++) {
if (faceUps[i+1] > faceUps[i]){
faceVertState = true;
}
if (faceUps[i+1] === faceUps[i]){
faceVertState = false;
}
}
if (faceVertState){
drawStateIndex ++;
}
if (buttonIsClicked){
if (drawStateIndex%2 === 0){
drawText(newTextVal, chosenFont);
} else{
drawTextLine(newTextVal, chosenFont)
}
}
}
function drawText(text,fontChoice){
drawingDots = true;
let message = text;
let mid = textWidth(message);
if (mid < 30) {
resolution = map(mid,5,30,0.08,0.01);
} else if (mid > 30 && mid < 100) {
resolution = map(mid,30,100,0.01,0.001);
} else {
resolution = 0.001;
}
let xposition = width/2-mid*15;
pts = fontChoice.textToPoints(message, xposition, height/2+100, 300,{
sampleFactor: resolution,
simplifyThreshold: 0
});
for (let i = 0; i < pts.length; i++) {
textSize(10);
for(let i =0; i< pts.length; i++){
let wave = sin(frameCount * 0.04 + (pts[i].x * pts[i].y)) * 10;
let waveR = frameCount * 0.04 + (pts[i].x * pts[i].y)
let d = dist(noseX, noseY, pts[i].x, pts[i].y);
s = map(d, 0, 100, 30, 0,true);
let colorH = sin(frameCount*0.0001*i) * 360;
let colorS = map(d, 0, 100, 50, 10,true);
textAlign(CENTER,CENTER);
push();
strokeWeight(0.05+s/20);
stroke(colorH,colorS,100);
translate(pts[i].x-(wave+s/2), pts[i].y-(wave+s/2));
scale(wave+s);
line(1,0,1,2);
line(0,1,2,1);
pop();
}
}
}
function drawTextLine(text,fontChoice){
drawingDots = false;
let message = text;
let mid = textWidth(message);
if (mid < 30) {
resolution = map(mid,5,30,0.05,0.01);
} else if (mid > 30 && mid < 100) {
resolution = map(mid,30,100,0.01,0.001);
} else {
resolution = 0.001;
}
let xposition = width/2-mid*15;
pts = fontChoice.textToPoints(message, xposition, height/2+100, 300,{
sampleFactor: resolution,
simplifyThreshold: 0
});
textSize(10);
for(let i =0; i< pts.length; i++){
let wave = sin(frameCount * 0.04 + (pts[i].x * pts[i].y)) * 5;
let d = dist(noseX, noseY, pts[i].x, pts[i].y);
s = map(d, 0, 100, 10, 0,true);
let pos = map(d, 0, 100, 30, 0,true);
let colorH = sin(frameCount*0.0001*i) * 360;
let colorS = map(d, 0, 100, 50, 10,true);
fill(colorH,colorS,100);
ellipse(pts[i].x+wave+pos, pts[i].y+pos,wave+s);
let j = (i+1)%pts.length;
stroke(colorH,colorS,100);
strokeWeight(0.5+s);
let waveC = sin(frameCount * 0.04 + (pts[j].x * pts[j].y)) * 5;
line(pts[i].x+wave+pos, pts[i].y+wave+pos, pts[j].x+waveC+pos, pts[j].y+pos);
}
}
function newText() {
let num = inputTexts.length;
inputTexts.push(input.value());
newTextVal = inputTexts[num];
buttonIsClicked = true;
}