← back to Graphics Agentabrams
graphics/generative-art-canvas-qwen/index.html
187 lines
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Flow Field Art</title>
<style>
html, body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
#controls {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
color: white;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
}
button {
margin: 5px;
padding: 8px 12px;
font-size: 14px;
background: #444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #666;
}
</style>
</head>
<body>
<div id="controls">
<button onclick="resetField()">New Field</button>
<button onclick="saveCanvas()">Save as PNG</button>
</div>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let width, height;
let particles = [];
let palette;
function resizeCanvas() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
function resetField() {
palette = generatePalette();
particles = [];
for (let i = 0; i < 1000; i++) {
particles.push(new Particle());
}
}
function generatePalette() {
const hues = [];
for (let i = 0; i < 10; i++) {
hues.push(Math.random() * 360);
}
return hues;
}
function mapValue(value, min1, max1, min2, max2) {
return (value - min1) / (max1 - min1) * (max2 - min2) + min2;
}
function noise(x, y) {
const nx = x / 100;
const ny = y / 100;
const sinx = Math.sin(nx);
const siny = Math.sin(ny);
return Math.sin(sinx + siny) * 0.5 + 0.5;
}
function getDirection(x, y) {
const step = 10;
const dx = noise(x + step, y) - noise(x - step, y);
const dy = noise(x, y + step) - noise(x, y - step);
const angle = Math.atan2(dy, dx);
return { angle, magnitude: 1 };
}
function lerpColor(a, b, t) {
return [
a[0] + t * (b[0] - a[0]),
a[1] + t * (b[1] - a[1]),
a[2] + t * (b[2] - a[2])
];
}
function getColor(x, y) {
const nx = x / 100;
const ny = y / 100;
const t = (Math.sin(nx) + Math.cos(ny)) * 0.5 + 0.5;
const hue = palette[Math.floor(t * palette.length)];
return [hue, 1, 1];
}
function Particle() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = 0;
this.vy = 0;
this.alpha = 1;
}
Particle.prototype.update = function() {
const { angle } = getDirection(this.x, this.y);
this.vx = Math.cos(angle);
this.vy = Math.sin(angle);
this.x += this.vx;
this.y += this.vy;
this.alpha -= 0.005;
if (this.alpha <= 0) {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.alpha = 1;
}
};
Particle.prototype.draw = function(ctx) {
const color = getColor(this.x, this.y);
const [h, s, l] = color;
const rgb = hsvToRgb(h, s, l);
ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${this.alpha})`;
ctx.fillRect(this.x, this.y, 2, 2);
};
function hsvToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = function (p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function draw() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw(ctx);
}
requestAnimationFrame(draw);
}
resetField();
draw();
function saveCanvas() {
const link = document.createElement('a');
link.download = 'flow-field-art.png';
link.href = canvas.toDataURL();
link.click();
}
</script>
</body>
</html>