← 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>