JAVASCRIPT
// CFDG core functions (direct draws version)
"use strict";
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let width, height, scale, offsetX, offsetY, minSize;
const transforms = {
x(m, v) {
m[4] += v * m[0];
m[5] += v * m[1];
},
y(m, v) {
m[4] += v * m[2];
m[5] += v * m[3];
},
s(m, v) {
m[0] *= v[0];
m[1] *= v[0];
m[2] *= v[1];
m[3] *= v[1];
},
r(m, v) {
const rad = Math.PI * v / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
const r00 = cos * m[0] + sin * m[2];
const r01 = cos * m[1] + sin * m[3];
m[2] = cos * m[2] - sin * m[0];
m[3] = cos * m[3] - sin * m[1];
m[0] = r00;
m[1] = r01;
},
f(m, v) {
const rad = Math.PI * v / 180;
const x = Math.cos(rad);
const y = Math.sin(rad);
const n = 1 / (x * x + y * y);
const b00 = (x * x - y * y) / n;
const b01 = 2 * x * y / n;
const b10 = 2 * x * y / n;
const b11 = (y * y - x * x) / n;
const r00 = b00 * m[0] + b01 * m[2];
const r01 = b00 * m[1] + b01 * m[3];
m[2] = b10 * m[0] + b11 * m[2];
m[3] = b10 * m[1] + b11 * m[3];
m[0] = r00;
m[1] = r01;
},
skew(m, v) {
const x = Math.tan(Math.PI * v[0] / 180);
const y = Math.tan(Math.PI * v[1] / 180);
const r00 = m[0] + y * m[2];
const r01 = m[1] + y * m[3];
m[2] = x * m[0] + m[2];
m[3] = x * m[1] + m[3];
m[0] = r00;
m[1] = r01;
},
hue(m, v) {
m[6] += v;
m[6] %= 360;
},
sat(m, v) {
this.col(m, v, 7);
},
b(m, v) {
this.col(m, v, 8);
},
a(m, v) {
this.col(m, v, 9);
},
col(m, v, p) {
if (v > 0) {
m[p] += v * (1 - m[p]);
} else {
m[p] += v * m[p];
}
}
};
const copy = s => {
return [
s[0], // a00
s[1], // a10
s[2], // a01
s[3], // a11
s[4], // tx
s[5], // ty
s[6], // hue
s[7], // saturation
s[8], // brillance
s[9] // alpha
];
};
const transform = (s, p) => {
let m = copy(s);
for (const c in p) {
transforms[c](m, p[c]);
}
return m;
};
const randint = (s, e = 0) => {
if (e === 0) {
e = s;
s = 0;
}
return 1 + Math.floor(s + Math.random() * (e - s));
};
const loop = (n, s, t, f) => {
let ls = copy(s);
for (let i = 0; i < n; i++) {
f(ls, i);
ls = transform(ls, t);
}
};
const setTransform = s => {
ctx.setTransform(
-scale * s[0],
scale * s[1],
scale * s[2],
-scale * s[3],
scale * s[4] + offsetX,
-scale * s[5] + offsetY
);
};
const tooSmall = s => {
const x = (s[0] * s[0] + s[1] * s[1]) * scale;
const y = (s[2] * s[2] + s[3] * s[3]) * scale;
return (x < minSize || y < minSize);
}
const hsla = s => {
return `hsla(${Math.round(s[6])},${Math.round(s[7] * 100)}%,${Math.round(
s[8] * 100
)}%,${s[9]})`;
};
const SQUARE = (s, t) => {
s = transform(s, t);
setTransform(s);
ctx.fillStyle = hsla(s);
ctx.fillRect(-0.5, -0.5, 1, 1);
};
const CIRCLE = (s, t) => {
s = transform(s, t);
setTransform(s);
ctx.fillStyle = hsla(s);
ctx.beginPath();
ctx.arc(0, 0, 0.5, 0, 2 * Math.PI);
ctx.fill();
};
const TRIANGLE = (s, t) => {
s = transform(s, t);
setTransform(s);
ctx.fillStyle = hsla(s);
ctx.beginPath();
ctx.moveTo(0, 0.577350269);
ctx.lineTo(-0.5, -0.28867513);
ctx.lineTo(0.5, -0.28867513);
ctx.lineTo(0.0, 0.577350269);
ctx.closePath();
ctx.fill();
};
const INIT = (background, s, x, y, m) => {
width = canvas.width = canvas.offsetWidth * 2;
height = canvas.height = canvas.offsetHeight * 2;
ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);
const r = Math.max(width, height) / 2048;
scale = s * r;
offsetX = x * (width / 2048);
offsetY = y * (height / 2048);
minSize = m;
};
["click", "touchdown"].forEach(event => {
document.addEventListener(event, e => START(), false);
});
//////////////////////////////////////////////////////////////////
// Adapted from a CFDG program
// https://www.contextfreeart.org/gallery2/index.html#design/107
// kw-expanded by momo, September 23rd, 2005
//////////////////////////////////////////////////////////////////
const START = _ => {
INIT("#999999", 170, 1024, 1024, 1);
Scene([1, 0, 0, 1, 0, 0, 0, 0, 0, 1]);
};
const Scene = s => {
NODE(s, { hue: 40, sat: 0.8, b: 0.2 });
NODE(s, { hue: 40, sat: 0.8, b: 0.2, r: 90 });
NODE(s, { hue: 40, sat: 0.8, b: 0.2, r: 180 });
NODE(s, { hue: 40, sat: 0.8, b: 0.2, r: 270 });
};
const NODE = (s, t) => {
s = transform(s, t);
if (tooSmall(s)) return;
const r = Math.random() * 1.4;
let weight = 0;
switch (true) {
case r <= (weight += 0.2):
NODE(s, {f: 17, skew:[0, 5]});
break;
case r <= (weight += 0.2):
NODE(s, {f: 197, skew:[8, 0]});
break;
default:
NODE(s, {y: 1.2, r: 35, skew:[5, 0], s:[0.89, 0.89], hue: -20.5, a: -0.02});
STAR(s, {skew:[0, 20]});
}
};
const STAR = (s, t) => {
s = transform(s, t);
if (tooSmall(s)) return;
SHAPE(s);
SHAPE(s, {f: 180, a: -0.3});
SHAPE(s, {r: 90, a: -0.4});
SHAPE(s, {r:-90, a: -0.2});
STAR(s, {s:[0.65, 0.65], b: 0.25});
};
const SHAPE = (s, t) => {
s = transform(s, t);
const r = Math.random() * 1.4;
let weight = 0;
switch (true) {
case r <= (weight += 0.2):
SHAPE(s, {skew: [0, 10]});
break;
case r <= (weight += 0.2):
SHAPE(s, {skew: [10, 0]});
break;
default:
TRIANGLE(s, {x: -1.5, y: 1});
CIRCLE(s, {x: 1});
SQUARE(s, {x: 3, s:[1.5, 0.09]});
SQUARE(s, {x: 3, r: 5, s:[1.5, 0.09], a: -0.5});
}
};
START();