(function(doc) { const TWO_PI = Math.PI * 2; const HALF_PI = Math.PI / 2; const RADIUS_SCALE = 0.05; const RADIUS_SD = 15; const POLYGON_SIDES = 5; const POSITION_SD = 0.04; const BASE_DEFORMATIONS = 3; const LAYER_DEFORMATIONS = 3; const LAYERS = 40; const PALLETS = [ [0, 30, 40, 60], [60, 300, 250], [130, 220], [230, 220, 60] ]; const MID_POINT_SD = 0.3; // How much variation around the middle const ANGLE_SD = 0.2; // How much variation of the angle to extend out at const MAGNITUDE_SD = 0.2; const POLY_COUNT = 4; const HUE_SD = 3; const HUE_SHIFT = 15; // Use Park-Miller PRNG so we can seed it function createRandom(seed) { const m = 0x7fffffff; let i = seed % m; if (i <= 0) i += m; return function random() { i = (i * 0x41a7) % m; return i / m; }; } const random = createRandom(Date.now()); function randomGaussian(mean, sd) { let y1, x1, x2, w; do { x1 = random() * 2 - 1; x2 = random() * 2 - 1; w = x1 * x1 + x2 * x2; } while (w >= 1); w = Math.sqrt((-2 * Math.log(w)) / w); y1 = x1 * w; y2 = x2 * w; return y1 * sd + mean; } function createPoly(x, y, r, sides) { const points = []; const angle = TWO_PI / sides; let sx, sy; for (let i = 0; i < sides; i++) { const a = angle * i; sx = x + Math.cos(a) * r; sy = y + Math.sin(a) * r; points.push([sx, sy]); } return points; } function dividePoly(poly, dividePoint) { const points = []; for (let i = 0; i < poly.length; i++) { const [x1, y1] = poly[i]; const [x2, y2] = poly[(i + 1) % poly.length]; const [xd, yd] = dividePoint(x1, y1, x2, y2); points.push([x1, y1], [xd, yd]); } return points; } function drawPoly(ctx, poly, polyX, polyY, r, hue1, hue2, opacity) { // Construct a gradient at a random angle from the center to the radius of the polygon let angle = random() * TWO_PI; let x1 = polyX + r * Math.cos(angle); let y1 = polyY + r * Math.sin(angle); let x2 = polyX; let y2 = polyY; // Offset the start and end hues slightly from the specified hue const gradient = ctx.createLinearGradient(x1, y1, x2, y2); gradient.addColorStop(0, `hsla(${hue1}, 100%, 50%, ${opacity})`); gradient.addColorStop(1, `hsla(${hue2}, 100%, 50%, ${opacity * 0.5})`); // Construct the polygon and fill it with the gradient ctx.save(); ctx.beginPath(); for (let i = 0; i < poly.length; i++) { const [x, y] = poly[i]; ctx.lineTo(x, y); } const [x, y] = poly[0]; ctx.lineTo(x, y); ctx.fillStyle = gradient; ctx.fill(); ctx.restore(); } // Generated a point between the specified start and end points function deformPoint(x1, y1, x2, y2) { const hypot = Math.hypot(x1 - x2, y1 - y2); // Pick a random point (around the center) along the line between the two points const u = randomGaussian(0.5, MID_POINT_SD); const a = Math.atan2(y1 - y2, x1 - x2); let x = x1 - hypot * u * Math.cos(a); let y = y1 - hypot * u * Math.sin(a); // Angle that the deformation will extend let angle = Math.atan2(y1 - y2, x1 - x2); angle += HALF_PI * randomGaussian(0.5, ANGLE_SD); // Magnitude that the deformation will extend const magnitude = hypot * Math.abs(randomGaussian(0.25, MAGNITUDE_SD)); x += magnitude * Math.cos(angle); y += magnitude * Math.sin(angle); const point = [x, y]; return point; } function draw(ctx) { const { height, width } = ctx.canvas; const radiusMean = Math.min(height, width) * RADIUS_SCALE; // Generate polygons const hues = PALLETS[~~(random() * PALLETS.length)]; const shapes = []; for (let index = 0; index < POLY_COUNT; index++) { // Pick a point on the canvas to center the polygon const radius = randomGaussian(radiusMean, RADIUS_SD); const x = width * randomGaussian(0.5, POSITION_SD); const y = height * randomGaussian(0.5, POSITION_SD); const baseHue = hues[index % hues.length]; // Create the initial polygon to start with let polygon = createPoly(x, y, radius, POLYGON_SIDES); // Deform the base polygon for (let index = 0; index < BASE_DEFORMATIONS; index++) { polygon = dividePoly(polygon, deformPoint); } // Pick a random color const hue1 = randomGaussian(baseHue, HUE_SD) % 360; const hue2 = (hue1 - HUE_SHIFT) % 360; shapes.push({ polygon, x, y, radius, hue1, hue2 }); } // Draw polygons interleaved const opacity = 1 / LAYERS; for (let layer = 0; layer < LAYERS; layer++) { for (const shape of shapes) { let layerPolygon = shape.polygon; for (let d = 0; d < LAYER_DEFORMATIONS; d++) { layerPolygon = dividePoly(layerPolygon, deformPoint); } drawPoly( ctx, layerPolygon, shape.x, shape.y, shape.radius, shape.hue1, shape.hue2, opacity ); } } } function generateImage(ctx) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); const { height, width } = ctx.canvas; draw(ctx, 0, 0, width, height); } const canvas = doc.createElement("canvas"); canvas.height = 1024; canvas.width = 1024; const context = canvas.getContext("2d"); document.body.appendChild(canvas); setInterval(() => generateImage(context), 500); generateImage(context); })(document);