console
// Context Free Art Javascript
"use strict";
const cfa = {
canvas: document.querySelector("canvas"),
stack: [],
draws: [],
// affine adjustments
ajustments: {
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];
},
z(m, v) {
m[11] += v;
},
rotate(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;
},
scale(m, v) {
let x, y;
if (Array.isArray(v)) {
x = v[0];
y = v[1];
} else {
x = v;
y = x;
}
m[0] *= x;
m[1] *= x;
m[2] *= y;
m[3] *= y;
},
// colors adjust
hue(m, v) {
m[6] += v;
m[6] %= 360;
},
sat(m, v) {
this.adjustColor(m, v, 7);
},
bri(m, v) {
this.adjustColor(m, v, 8);
},
alpha(m, v) {
this.adjustColor(m, v, 9);
},
adjustColor(m, v, p) {
if (v > 0) {
m[p] += v * (1 - m[p]);
} else {
m[p] += v * m[p];
}
},
raf(m, v) {
m[10] = v ? 1 : 0;
}
},
normal(s, e) {
let rand = (Math.random() + Math.random() + Math.random() + Math.random() + Math.random() + Math.random()) / 6;
return s + rand * (e - s);
},
random(s, e) {
return s + Math.random() * (e - s);
},
adjust(s, p) {
const m = [
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
s[10], // z-index
s[11], // raf
s[12] // primitive
];
for (const c in p) {
this.ajustments[c](m, p[c]);
}
return m;
},
// primitives
setTransform(s) {
this.ctx.setTransform(
-this.scale * s[0],
this.scale * s[1],
this.scale * s[2],
-this.scale * s[3],
this.scale * s[4] + this.offsetX,
-this.scale * s[5] + this.offsetY
);
},
SQUARE(s, p = null) {
this.primitive(s, p, 1);
},
primitive(s, p, i) {
p && (s = this.adjust(s, p));
s[12] = i;
this.draws.push(s);
this.bbox(s);
},
1(s) {
// SQUARE
this.setTransform(s);
this.fillStyle(s);
this.ctx.fillRect(-0.5, -0.5, 1, 1);
},
fillStyle(s) {
this.ctx.fillStyle = `hsla(${Math.round(s[6])},${Math.round(
s[7] * 100
)}%,${Math.round(s[8] * 100)}%,${s[9]})`;
},
bbox(s) {
const x = s[4] * this.scale;
const y = s[5] * this.scale;
if (x < this.rect[0]) this.rect[0] = x;
else if (x > this.rect[1]) this.rect[1] = x;
if (y < this.rect[2]) this.rect[2] = y;
else if (y > this.rect[3]) this.rect[3] = y;
},
center(s) {
const br = this.rect;
const scale =
Math.min(this.width / (br[1] - br[0]), this.height / (br[3] - br[2])) * s;
this.scale *= scale;
this.offsetX = this.width * 0.5 - (br[0] + br[1]) * 0.5 * scale;
this.offsetY = this.height * 0.5 + (br[3] + br[2]) * 0.5 * scale;
},
// rendering iterator
next () {
requestAnimationFrame(_ => this.iter.next());
},
*render() {
let s = 0;
for (const draw of this.draws) {
this[draw[12]](draw);
if (s++ > this.speed && draw[10]) {
s = 0;
this.speed *= this.acc;
if (this.speed < 1) this.speed = 1;
yield this.next();
}
}
yield this.next();
},
// call shape
rule(name, s, p) {
s = this.adjust(s, p);
const x = (s[0] * s[0] + s[1] * s[1]) * this.scale;
const y = (s[2] * s[2] + s[3] * s[3]) * this.scale;
if (x < this.minSize && y < this.minSize) {
// too small
return;
}
s[12] = this.shape[name] * 1;
this.stack.push(s);
},
run(code) {
// inject code
this.shape = {};
let k = 3;
for (const rule in code) {
this.shape[rule] = k;
this[k++] = code[rule];
}
// reset canvas
this.ctx = this.canvas.getContext("2d");
this.width = this.canvas.width = this.canvas.offsetWidth * 2;
this.height = this.canvas.height = this.canvas.offsetHeight * 2;
if (code.setup.background) {
this.ctx.fillStyle = code.setup.background;
this.ctx.fillRect(0, 0, this.width, this.height);
}
// init setup
this.rect = [0, 0, 0, 0];
this.stack.length = 0;
this.draws.length = 0;
this.scale = 100;
this.speed = code.setup.speed || 100;
this.minSize = code.setup.minSize || 1.0;
this.acc = code.setup.acc || 1.0;
// push start shape
this.rule(code.setup.start, [1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0], false);
// run rules
const t1 = performance.now();
let iter = 0;
do {
iter++;
const s = this.stack.shift();
this[s[12]](s);
} while (this.stack.length);
const t2 = performance.now();
console.log(iter, t2 - t1);
// zSorting
if (code.setup.zSorting) {
this.draws.sort(function(d0, d1) {
return d0[11] - d1[11];
});
}
// centering
this.center(code.setup.zoom || 1.0);
// start rendering loop
this.iter && (this.iter.return());
this.iter = this.render();
this.iter.next();
}
};
["click", "touchdown"].forEach(event => {
document.addEventListener(event, e => cfa.run(code), false);
});
/////////////////////////////////////////////
// Adapted from https://www.contextfreeart.org/gallery2/index.html#design/3748
// Living curves by mikelovesrobots, September 18th, 2016
const code = {
setup: {
background: "#000",
minSize: 4,
zoom: 1,
speed: 1000,
start: "init"
},
init(s) {
this.rule('stroke', s, {hue: Math.random() * 90, sat: 1, bri: 0.5});
},
stroke(s) {
const curve = this.normal(-4, 4);
for (let i = 0; i < 368; i++) {
s = this.adjust(s, {rotate: curve, y: 0.5});
this.SQUARE(s, {scale:4, rotate: 45});
}
this.rule('stroke', s, {scale: 0.9, alpha: -0.1});
this.rule('stroke', s, {scale: 0.5, alpha: -0.9});
}
};
cfa.run(code);
<canvas></canvas>
<!-- Click to generate a new image -->
html, body {
overflow: hidden;
touch-action: none;
content-zooming: none;
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #fff;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
user-select: none;
cursor: pointer;
}