Flutter 3.x+Organic BlobsCustomPainter & ClipPath

Flutter Blob Generator

Create smooth organic vector blob shapes with random seed generators and export Flutter CustomPainter & ClipPath code instantly.

Fill
Background
Flutter CustomPainter Code
// Widget usage
// Animated organic morphing blob
SizedBox(
  width: 300.0,
  height: 300.0,
  child: const AnimatedBlob(
    numPoints: 6,
    variance: 0.45,
    angleJitter: 0.50,
    smoothness: 0.35,
    seed: 42,
    color: const Color(0xFF6366F1),
  ),
)

// โ”€โ”€โ”€ Animated Blob Widget โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import 'dart:math' as math;
import 'package:flutter/material.dart';

class AnimatedBlob extends StatefulWidget {
  final double size;
  final int numPoints;
  final double variance;
  final double angleJitter;
  final double smoothness;
  final int seed;
  final Color color;

  const AnimatedBlob({
    Key? key,
    this.size = 300.0,
    this.numPoints = 6,
    this.variance = 0.45,
    this.angleJitter = 0.50,
    this.smoothness = 0.35,
    this.seed = 42,
    this.color = const Color(0xFF6366F1),
  }) : super(key: key);

  @override
  State<AnimatedBlob> createState() => _AnimatedBlobState();
}

class _AnimatedBlobState extends State<AnimatedBlob> with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 4),
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return CustomPaint(
          size: Size(widget.size, widget.size),
          painter: BlobPainter(
            time: _controller.value * math.pi * 2,
            numPoints: widget.numPoints,
            variance: widget.variance,
            angleJitter: widget.angleJitter,
            smoothness: widget.smoothness,
            seed: widget.seed,
            color: widget.color,
          ),
        );
      },
    );
  }
}

class BlobPainter extends CustomPainter {
  final double time;
  final int numPoints;
  final double variance;
  final double angleJitter;
  final double smoothness;
  final int seed;
  final Color color;

  BlobPainter({
    required this.time,
    required this.numPoints,
    required this.variance,
    required this.angleJitter,
    required this.smoothness,
    required this.seed,
    required this.color,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final cx = size.width / 2;
    final cy = size.height / 2;
    final radius = size.width * 0.42;

    // Seeded LCG pseudo-random generator
    int s = (seed + 1) * 2654435761;
    double rand() {
      s = (s ^ (s >> 15)) * (s | 1);
      s ^= s + (s ^ (s >> 7)) * (s | 61);
      return ((s ^ (s >> 14)) & 0xFFFFFFFF) / 4294967296.0;
    }

    final List<Offset> pts = [];
    final step = (math.pi * 2) / numPoints;

    for (int i = 0; i < numPoints; i++) {
      final baseAngle = i * step - math.pi / 2;
      final jitter = (rand() - 0.5) * step * angleJitter;
      final angle = baseAngle + jitter;

      final phase = rand() * math.pi * 2;
      final speedMult = 0.7 + rand() * 0.6;
      final wave = math.sin(time * speedMult + phase);
      final r = radius * (1 - variance / 2 + wave * (variance / 2));

      pts.add(Offset(cx + math.cos(angle) * r, cy + math.sin(angle) * r));
    }

    final path = Path();
    final n = pts.length;
    
    if (n > 0) {
      path.moveTo(pts[0].dx, pts[0].dy);
      
      for (int i = 0; i < n; i++) {
        final prev = pts[(i - 1 + n) % n];
        final curr = pts[i];
        final next = pts[(i + 1) % n];
        final next2 = pts[(i + 2) % n];

        final c1 = Offset(
          curr.dx + (next.dx - prev.dx) * smoothness,
          curr.dy + (next.dy - prev.dy) * smoothness,
        );
        final c2 = Offset(
          next.dx - (next2.dx - curr.dx) * smoothness,
          next.dy - (next2.dy - curr.dy) * smoothness,
        );

        path.cubicTo(c1.dx, c1.dy, c2.dx, c2.dy, next.dx, next.dy);
      }
      path.close();
    }

    final paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant BlobPainter oldDelegate) {
    return oldDelegate.time != time ||
        oldDelegate.numPoints != numPoints ||
        oldDelegate.variance != variance ||
        oldDelegate.angleJitter != angleJitter ||
        oldDelegate.smoothness != smoothness ||
        oldDelegate.seed != seed ||
        oldDelegate.color != color;
  }
}
Animated blob uses custom maths to dynamically morph points natively at 60fps in Flutter.
Flutter Generator Suite SwitcherView All Tools Suite โ†’