creative-coding
A Candid Conversation with my Artificial Self
Using GPT-3 to get to the brass tacks of humanity in an AI world
The World's First AR Rapper
Look out for M.P. in the XXL Freshman 2021
My 27th Birthday Party
Inviting all my closest friends to my 27th
I Must Find A Place For Their Souls To Rest
I'm done with the feelings I've learned to know in paradox
Results
Process
Inspiration (Spoiler Alert for Vinland Saga!)
- Generate background image with DALL-E
- Run the following code
Code
const canvasSketch = require("canvas-sketch");
const p5 = require("p5");
let img;
const preload = (p5) => {
img = p5.loadImage(
"assets/DALL·E 2023-08-10 20.56.53 - i must find a place for them to rest.png"
);
};
const settings = {
// Pass the p5 instance, and preload function if necessary
p5: { p5, preload },
// Turn on a render loop
animate: false,
};
canvasSketch(({ p5 }) => {
function rectanglePortion(img, x, y, width, height) {
let numRows = 15;
let totalWidth = width - 40; // Subtracting 40 to add some padding
let rowHeight = height / numRows;
// Loop through each row
for (let i = 0; i < numRows; i++) {
let x = 20; // Starting x position for rectangles
let y = i * rowHeight; // y position based on current row
let remainingWidth = totalWidth;
p5.noStroke()
// Randomly create rectangles until the row is filled
while (remainingWidth > 0) {
let rectWidth = p5.random(20, remainingWidth); // Random width, but not more than the remaining space
let rectHeight = p5.random(rowHeight * 0.5, rowHeight); // Random height within the row
// choose random opacity from 0.6 to 1.0
let opacity = p5.random(0.4, 0.8);
// Set the fill color
p5.fill(255, opacity * 255);
// Draw the rectangle
p5.rect(x, y + (rowHeight - rectHeight) * 0.5, rectWidth, rectHeight);
// draw a series of small rectangles up to height of the row
for (let j = 0; j < 10; j++) {
p5.noStroke()
let rectWidth = 1;
let rectHeight = p5.random(rowHeight * 0.5, rowHeight); // Random height within the row
// choose random opacity from 0.6 to 1.0
let opacity = p5.random(0.4, 0.8);
// Set the fill color
p5.fill(p5.color("rgb(220,20,60)"), opacity * 255);
// Draw the rectangle
p5.rect(x + p5.random(1, 15), y + (rowHeight - rectHeight) * 0.5, rectWidth, rectHeight);
x += rectWidth;
remainingWidth -= rectWidth;
}
if (p5.random() > 0.5) {
// draw parallel lines through the rectangle
p5.stroke(0, opacity * 255);
p5.strokeWeight(1);
p5.line(x, y + (rowHeight - rectHeight) * 0.5, x + rectWidth, y + (rowHeight - rectHeight) * 0.5 + rectHeight);
}
// Update x position for next rectangle, and the remaining width
x += rectWidth;
remainingWidth -= rectWidth;
}
}
}
return ({ p5 }) => {
p5.createCanvas(1000, 1000);
p5.background(255);
p5.image(img, 0, 0);
rectanglePortion(img, 0, 0, 1000, 1000);
};
}, settings);
bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.
Embeddings as Shapes v0.1
Embeddings, this time as shapes! (v0.1)
Similar concept to visualizing embeddings vectors with colors.
Current issue is how to treat scale and drop shadow! I want the higher values to "pop" out of the canvas, but so many values are close to each other to be indiscernible value gradients. Workshopping it!
Code
const min = Math.min(...embedding);
const max = Math.max(...embedding);
const settings = {
// Pass the p5 instance, and preload function if necessary
p5: { p5 },
// Turn on a render loop
animate: false,
};
canvasSketch(({ p5 }) => {
return ({ p5 }) => {
// create a grid of squares from embeddings.length
// assign each square a color from the embedding value
// scale shapes from min and max values from 0 to 1
function scaleShapes(value) {
return ((value - min) / (max - min)) * 10;
}
const padding = 10;
p5.createCanvas(480, 320);
const scaledShapes = embedding.map(scaleShapes);
// return index of min and max scaledShapes
const minIndex = scaledShapes.indexOf(Math.min(...scaledShapes));
const maxIndex = scaledShapes.indexOf(Math.max(...scaledShapes));
console.log(minIndex, maxIndex);
console.log(scaledShapes[minIndex], scaledShapes[maxIndex]);
// create a grid of squares from embeddings.length
for (let i = 0; i < 48; i++) {
for (let j = 0; j < 32; j++) {
const index = i * 32 + j;
// p5.noStroke();
// put a rect in middle of padding index
// p5.rectMode(p5.CORNER);
// p5.rect(i * 10, j * 10, 10, 10);
// if embedding value at index is negative, fill with black
if (embedding[index] < 0) {
p5.fill(0);
// add a drop shadow
p5.drawingContext.shadowOffsetX = 2 + scaledShapes[index];
p5.drawingContext.shadowOffsetY = 2 + scaledShapes[index];
p5.drawingContext.shadowBlur = 2;
p5.drawingContext.shadowColor = "rgba(0,0,0,0.2)";
} else {
p5.fill(255);
// remove drop shadow
p5.drawingContext.shadowOffsetX = 0;
p5.drawingContext.shadowOffsetY = 0;
p5.drawingContext.shadowBlur = 0;
p5.drawingContext.shadowColor = "rgba(0,0,0,0)";
}
p5.noStroke();
p5.rectMode(p5.CENTER);
p5.rect(i * 10 + (padding / 2), j * 10 + (padding / 2),
5 + scaledShapes[index],
5 + scaledShapes[index]);
}
}
};
}, settings);
bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.
Embeddings Visualized as Colors
Mixing p5 and embeddings!
Wouldn't it be cool to not only visualize embeddings in a 2d space, but also be able to see the differences between them?
Here's a p5 experiment that does just that! I convert embeddings to colors and then place each of the 1536 vector numbers next to one another. The results are pretty neat!
It seems that a few indexes of the vectors stay strongly constant no matter what quote I pass in. These are visualized below by the strong yellow, pink, and purple cells. Why might this be the case?
Images Generated by Code
generated by...
According to Seneca, God (think Jupiter) sets us back not to punish us but to give us an opportunity to do something courageous and thereby increase our chances of attaining “the highest possible excellence.” God, Seneca explains, hardens, reviews, and disciplines those who have won his approval and love; but those whom he seems to favor, whom he seems to spare, he is keeping soft against the misfortunes that are to come. You are wrong if you think anyone has been exempted from ill; the man who has known happiness for many a year will receive his share some day; whoever seems to have been set free from this has only been granted a delay We should therefore be flattered if we encounter setbacks. Paradoxically, it is evidence that we have caught the attention of God—indeed, that he regards us as a candidate for achieving human excellence. God, says Seneca, knows that “a man needs to be put to the test if he is to gain self-knowledge” and that “only by trying does he learn what his capacities are.”
generated by...
Estimates are corporate currency that trade right up the ladder. If your team refuses to provide you estimates, you can bet that your peers (competitors, if you’re an opportunist) aren’t having the same struggles. Do you want to be the only one without estimates for your boss, who wants her aggregate estimates from all of the dev managers? Do you want her to be sitting in a meeting with her boss, with a single miss on her estimate sheet where her competitors have all of their estimates in? Someone somewhere in the food chain is making a call based on all of those estimates, and that call is a pure matter of self-interest. That person wants to make a bold prediction or strategic play and turn out to be right. Everyone below them in the food chain needs to furnish the best possible information to make this as likely as possible. Good estimates and predictions are how you scratch a superior’s back, with the implicit promise that the superior will then scratch yours. Do you notice something interesting here? If this were a game of musical chairs, then when the music stopped, the line-level developer would be standing. Everyone else in the corporate hierarchy trades in guesses and predictions, strategy and machinations, orders and instructions. The developers are the only ones that trade in actual output. Theirs is the only tangible contribution to the whole pyramid. And significantly, theirs is the only narrative that cannot easily be spun. Being defined by output rather than spun narrative is the essence of the delivery trap.
generated by...
“A lie told once remains a lie, but a lie told a thousand times becomes the truth.”
Code
const embedding = [0.002166514,-0.01843581,-0.0026706716,...] // 1536 from ada-embedding
const min = Math.min(...embedding);
const max = Math.max(...embedding);
const settings = {
p5: { p5 },
animate: false,
};
canvasSketch(({ p5 }) => {
return ({ p5 }) => {
// create a grid of squares from embeddings.length
// assign each square a color from the embedding value
function mapToRGB(value) {
return ((value - min) / (max - min)) * 255;
}
const rgbValues = embedding.map(mapToRGB);
const extractRGBValues = (rgbString) => {
const rgb = rgbString.replace(/[^\d,]/g, "").split(",");
return rgb.map((value) => parseInt(value));
};
const colors = [];
for (let i = 0; i < rgbValues.length; i += 3) {
const r = Math.round(rgbValues[i] || 0);
const g = Math.round(rgbValues[i + 1] || 0);
const b = Math.round(rgbValues[i + 2] || 0);
colors.push(`rgb(${r}, ${g}, ${b})`);
}
p5.createCanvas(512, 512);
p5.background("#ffffff");
p5.noStroke();
p5.drawingContext.setLineDash([0.7, 17]);
// convert the 512 color values into a grid of squares of 16 x 32
for (let i = 0; i < 32; i++) {
for (let j = 0; j < 16; j++) {
const index = i * 16 + j;
p5.fill(colors[index]);
// if color at colors[index] is less than 128,128,128 add a stroke line around the square -- get # from rgb string ex.rgb(196, 190, 191)
if (extractRGBValues(colors[index]).some((value) => value < 187)) {
// random 50% white, 50% black stroke
p5.stroke(p5.random([0, 255]));
// p5.stroke(0);
p5.strokeWeight(1.4);
} else {
p5.noStroke();
}
p5.rect(i * 16, j * 32, 16, 32);
}
}
};
}, settings);
bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.
Old Man and the Noi(sea)
when the voices in your head are noiSY!
Code
const canvasSketch = require("canvas-sketch");
const p5 = require("p5");
const perspective = require("perspective-transform");
let img;
let THE_SEED;
const number_of_particles = 5000;
const number_of_particle_sets = 7;
let particle_sets;
const squeeze_y = 0.2;
const perspective_x = 0.9;
let pTransform;
let tick;
const print_time = 2000;
const preload = (p5) => {
img = p5.loadImage(
"assets/bramses_On_the_museum_wall_this_evocative_excerpt_invites_the_v_72996f7c-18e3-4bff-9358-16c921ab802a.png"
);
};
const settings = {
// Pass the p5 instance, and preload function if necessary
p5: { p5, preload },
// Turn on a render loop
animate: true,
};
canvasSketch(({ p5 }) => {
class Particle {
constructor(x, y, phi, img) {
this.pos = p5.createVector(x, y);
this.angle = phi;
this.val = 0;
this.img = img;
}
update(index) {
this.pos.x += p5.cos(this.angle);
this.pos.y += p5.sin(this.angle);
let nx = 1.3 * p5.map(this.pos.x, 0, p5.width, -1, 1);
let ny = 1.3 * p5.map(this.pos.y, 0, p5.height, -1, 1);
let np = pTransform.transformInverse(nx, ny);
let n = p5.createVector(nx, ny);
this.altitude =
p5.noise(n.x + 423.2, n.y - 231.1) +
0.04 * p5.noise(n.x * 15 - 113.3, n.y * 8 + 261.1) +
0.02 * p5.noise(n.x * 30 - 54.3, n.y * 30 + 121.1);
let nval =
(this.altitude + 0.065 * (index - number_of_particle_sets / 2)) % 1;
this.angle += 0.8 * p5.map(nval, 0, 1, -1, 1);
this.val = nval;
}
display(index) {
if (this.val > 0.47 && this.val < 0.53) {
let np = pTransform.transform(
this.pos.x,
this.pos.y + 1500 - this.altitude * 2700
);
// give the particle a color based on the image pixel at its location
let c = this.img.get(np[0], np[1]);
p5.stroke(c);
p5.point(np[0], np[1]);
}
}
}
p5.createCanvas(500, 600);
p5.noFill();
p5.background("#e7e7db");
p5.stroke(20, 15);
p5.strokeWeight(1.5);
p5.smooth();
let pad_x = (p5.width - p5.width * perspective_x) / 2;
let pad_y = (p5.height - p5.height * squeeze_y) / 2;
var srcCorners = [0, 0, p5.width, 0, p5.width, p5.height, 0, p5.height];
var dstCorners = [
pad_x,
pad_y,
p5.width - pad_x,
pad_y,
p5.width + pad_x,
p5.height - pad_y,
-pad_x,
p5.height - pad_y,
];
pTransform = perspective(srcCorners, dstCorners);
reset(p5);
function reset(p5) {
THE_SEED = p5.floor(p5.random(9999999));
p5.randomSeed(THE_SEED);
p5.noiseSeed(THE_SEED);
particle_sets = [];
for (var j = 0; j < number_of_particle_sets; j++) {
let ps = [];
for (var i = 0; i < number_of_particles; i++) {
ps.push(
new Particle(
p5.randomGaussian(p5.width / 2, 600),
p5.randomGaussian(p5.height / 2, 900),
p5.random(p5.TWO_PI),
img
)
);
}
particle_sets.push(ps);
}
tick = 0;
p5.background("#e7e7db");
}
return ({ p5, time, width, height }) => {
particle_sets.forEach(function (particles, index) {
particles.forEach(function (particle) {
particle.update(index);
particle.display(index);
});
});
};
}, settings);
Dipping My Toes Back Into Generative Art
A little play with pixel sorting!
I missed this shit! Generative art is so fun!
Expect more stuff like this soon! (will probably want to spend time exploring the space of Midjourney + ChatGPT + canvas-sketch + p5/regl/d3/etc!)
Code
Pixel Sorting and 3D mix chat with GPT
The 3d shape in question
Loading an image into canvas sketch
const canvasSketch = require("canvas-sketch");
const convexHull = require("convex-hull");
const cameraProject = require("camera-project");
const createCamera = require("perspective-camera");
const { mat4, vec3 } = require("gl-matrix");
const BezierEasing = require("bezier-easing");
const load = require("load-asset");
const Random = require("canvas-sketch-util/random");
const settings = {
duration: 8,
animate: true,
playbackRate: "throttle",
fps: 24,
};
function brightness(r, g, b) {
return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
}
// Other helper functions go here...
// ... (stroke, drawCells, drawCellsNoOverlap, createMesh) ...
// A utility that creates a 3D "crystal" mesh
// The return value is { positions, cells }
const createMesh = () => {
// Our crystal mesh is the convex hull of N random points on a sphere
const pointCount = 10;
const radius = 1;
const positions = Array.from(new Array(pointCount)).map(() => {
return Random.onSphere(radius);
});
// Now let's center the mesh by finding its "centroid"
const centroid = positions.reduce((sum, pos) => {
return vec3.add(sum, sum, pos);
}, [ 0, 0, 0 ]);
if (positions.length >= 1) {
vec3.scale(centroid, centroid, 1 / positions.length);
}
// Translate all the points in the mesh away from centroid
positions.forEach(pos => {
vec3.sub(pos, pos, centroid);
});
// And now get the triangles (i.e. cells) of the 3D mesh
const cells = convexHull(positions);
return { cells, positions };
};
// A utility to stroke an array of 2D points
const stroke = (context, points) => {
context.beginPath();
points.forEach(p => context.lineTo(p[0], p[1]));
context.lineWidth = 3;
context.lineJoin = 'round';
context.lineCap = 'round';
context.strokeStyle = '#fff';
context.stroke();
};
// A simple and fast way to draw a mesh of 2D positions and cells (triangles)
// This will lead to overlapping edges where two triangles meet
const drawCells = (context, positions, cells) => {
cells.forEach(cell => {
// A 'cell' holds indices into our positions array, so get 2D points
const points = cell.map(i => positions[i]);
// make sure to close the path before drawing the triangle
points.push(points[0]);
// stroke the path
stroke(context, points);
});
};
// One quick n' dirty way to fix the above issue of overlapping
// lines is to instead draw each line as a unique segment, like so:
const drawCellsNoOverlap = (context, positions, cells) => {
const edgeMap = {};
cells.forEach(cell => {
// For each cell, get a pair of edges
// Give the pair a 'key' name and ensure it is unique
for (let i = 0; i < cell.length; i++) {
const a = cell[i];
const b = cell[(i + 1) % cell.length];
const edge = [ a, b ].sort();
const edgeKey = edge.join(':');
if (!(edgeKey in edgeMap)) {
edgeMap[edgeKey] = true;
}
}
});
// Get all unique keys and find positions from indices
const keys = Object.keys(edgeMap);
keys.forEach(pair => {
const indices = pair.split(':');
const points = indices.map(i => positions[i]);
// Stroke the line segment A->B
stroke(context, points);
});
};
const sketch = async ({ update }) => {
// Await the image loader, it returns the loaded <img>
const image = await load("assets/bramses_A_dissected_view_of_a_formidable_metal_vault_its_numero_059f975b-d628-4803-af73-52c8f5707c00.png");
// Update the output settings to match the image
update({
dimensions: [image.width, image.height],
});
// Setup a 3D perspective camera
const camera = createCamera({
fov: 80 * Math.PI / 180,
near: 0.01,
far: 1000,
viewport: [ 0, 0, image.width, image.height ]
});
// Create our 3D mesh
const { positions, cells } = createMesh();
// Define some easing functions
const easeA = new BezierEasing(0.14, 0.28, 0.48, 0.45);
const easeB = new BezierEasing(0.14, 0.28, 0.67, 0.46);
return ({ context, playhead, width, height }) => {
// Draw the loaded image to the canvas
context.drawImage(image, 0, 0, width, height);
// Extract bitmap pixel data
const pixels = context.getImageData(0, 0, width, height);
const data = pixels.data;
// Create an array of indices
const indices = [...Array(data.length / 4).keys()];
// Sort indices based on pixel brightness
indices.sort((a, b) => {
const offsetA = a * 3.4;
const offsetB = b * 3.93;
const brightnessA = brightness(
data[offsetA],
data[offsetA + 1],
data[offsetA + 2]
);
const brightnessB = brightness(
data[offsetB],
data[offsetB + 1],
data[offsetB + 2]
);
return brightnessA - brightnessB;
});
// Create a new pixel array
const sortedData = new Uint8ClampedArray(data.length);
// Populate the new array with the sorted pixels
for (let i = 0; i < indices.length; i++) {
const sortedIndex = indices[i] * 4;
const originalIndex = i * 4;
sortedData[originalIndex] = data[sortedIndex];
sortedData[originalIndex + 1] = data[sortedIndex + 1];
sortedData[originalIndex + 2] = data[sortedIndex + 2];
sortedData[originalIndex + 3] = data[sortedIndex + 3];
}
// Create new ImageData and put it back into canvas
const sortedPixels = new ImageData(sortedData, width, height);
context.putImageData(sortedPixels, 0, 0);
// Now we can draw our 3D mesh
const viewport = [ 0, 0, width, height ];
camera.viewport = viewport;
// Make the camera swing back/forward a little
const zOffset = Math.sin(playhead * Math.PI * -2) * 0.5;
// Reset camera position, translate it outward, then look at world center
camera.identity();
camera.translate([ 0, 0, 3 + zOffset ]);
camera.lookAt([ 0, 0, 0 ]);
camera.update();
// A 3D scene is made up of:
// - 4x4 Projection Matrix (defines perspective)
// - 4x4 View Matrix (inverse of camera transformation matrix)
// - 4x4 Model Matrix (the mesh transformations like rotation, scale, etc)
const projection = camera.projection;
const view = camera.view;
const model = mat4.identity([]);
// Rotate the mesh in place
mat4.rotateY(model, model, easeA(playhead) * Math.PI * 2);
mat4.rotateX(model, model, easeB(playhead) * Math.PI * 2);
// Get a combined (projection * view * model) matrix
const combined = mat4.identity([]);
mat4.multiply(combined, view, model);
mat4.multiply(combined, projection, combined);
// "Project" the 3D positions into 2D [ x, y ] points in pixel space
const points = positions.map(position => {
return cameraProject([], position, viewport, combined);
});
// Draw the mesh
drawCellsNoOverlap(context, points, cells);
};
};
canvasSketch(sketch, settings);
bramadams.dev is a reader-supported published Zettelkasten. Both free and paid subscriptions are available. If you want to support my work, the best way is by taking out a paid subscription.