Flutter Gooey Blobs
Published: 2024-07-11 20:59:36

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/gooey-blobs_640fcfb3316f48f20b3ec6e2c92a9736.jpg" alt="Flutter Gooey Blobs with CustomPainter."/></figure>

Have you ever seen these gooey blobs on the internet?

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image_f05f6c80470d23899c3920503db6205b.gif" alt="A gif of two blobs merging with each other."/></figure>

They look kinda cool, right? Have you ever thought it would be really cool to create something like this in Flutter?

A few days ago, Twitter user [@double__glitch](https://twitter.com/double__glitch) demonstrated creating [gooey blobs in Figma](https://twitter.com/double__glitch/status/1618213807503060993). Seeing his technique reminded me of drawing something on a canvas, which led to me thinking about CustomPainter, and I thought, “I can do that! Somehow...”

After an hour or so, I did manage to re-create it in Flutter! Here’s how:

## Why CustomPainter?

As I said earlier, the technique shown by [@double__glitch](https://twitter.com/double__glitch) is like drawing on a canvas. CustomPainter serves exactly that purpose. It provides you with set of APIs ranging from drawing shapes on a canvas to adding layer composite effects, combining image filters, blend modes, and more.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image-1-copy-2_e4c9c3ffcbb62093af2749a537fe2d82.gif" alt="Six different paint effects."/></figure>

We should be able to do this in Flutter, too! If you aren’t aware, Flutter converts your widget layers into a set of canvas painting instructions as part of the rendering pipeline. Somewhere down the line, these instructions are rendered on the screen. 

Before diving into it, here’s something that you’ll need to know to achieve this effect:

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/zelda-take-this_9e1a0c1f57b5bf57d7dcfeab2bd6f949.jpg" alt="Zelda meme that says, &amp;quot;It&amp;#39;s dangerous to go alone! Take this: saveLayer.&amp;quot;"/></figure>

### saveLayer

`saveLayer` allows you to create composite effects on canvas. Normally each canvas instruction is painted individually, thus applying composite effects on a group of shapes. With `saveLayer`, you can group those shapes and apply an effect on those as one single layer on the canvas.

```
canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), paint);

// drawing instructions here are grouped together. 

canvas.restore();
```

[Save this code](https://takrutvik.pieces.cloud/?p=1dcc45831d)

`saveLayer` takes the size of the layer and a `Paint` object. Any canvas instructions called after calling `saveLayer` will be grouped, and once you call `canvas.restore`, that group will be flattened out into a layer on which the `Paint` object’s image filters and blend modes will be applied.

Now, back to creating our gooey blobs!

## Adding Blurred Circles

We’ll use the `canvas.drawCircle()` to draw two circles on the canvas and add a blur filter to the layer using `saveLayer`.

```
import 'dart:ui';

import 'package:flutter/material.dart';

class BlobsView extends StatefulWidget {
  const BlobsView({super.key});

  @override
  State<BlobsView> createState() => _BlobsViewState();
}

class _BlobsViewState extends State<BlobsView> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: CustomPaint(
          painter: _BlobsPainter(),
          size: MediaQuery.of(context).size,
        ),
      ),
    );
  }
}

class _BlobsPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final blackCirclesPainter = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.fill;

    final blurLayerPaint = Paint()
      ..color = const Color(0xff808080)
      ..style = PaintingStyle.fill
      ..imageFilter = ImageFilter.blur(
        sigmaX: 10,
        sigmaY: 10,
        tileMode: TileMode.decal,
      );

    canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), blurLayerPaint);

    canvas.drawCircle(Offset(size.width / 2 - 50, size.height / 2), 70, blackCirclesPainter);
    canvas.drawCircle(Offset(size.width / 2 + 50, size.height / 2), 60, blackCirclesPainter);

    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
```

[Save this code](https://takrutvik.pieces.cloud/?p=36a6428584)

Here’s how it looks. Nothing fancy right now.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/screenshot-2024-07-07-at-52848-pm_50bb5ef87dabe7fb5fa0d0ef6eb3e600.jpg" alt="Two blurry conjoined circles."/></figure>

## Adding a colorDodge Layer

Next, we’ll add certain layers on top of our canvas with specific blend mode applied to them. The blend mode will specify how the layer should be composed with its background. In this article, we won’t dive deep into how these blend modes work, but if you’re curious, I would recommend reading [these w3 docs](https://www.w3.org/TR/compositing-1/#blendingcolordodge) on the different blend modes mentioned in this article and how they work.

First is the `colorDodge` layer. We’ll achieve this by creating a rectangle, giving it a bright color like `Color(0xff808080)`, and setting its blend mode to `BlendMode.colorDodge`.

This will go right after the `canvas.restore`  call:

```
canvas.drawRect(
      Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
      Paint()
        ..color = const Color(0xff808080)
        ..style = PaintingStyle.fill
        ..blendMode = BlendMode.colorDodge,
    );
```

[Save this code](https://takrutvik.pieces.cloud/?p=136b46a1a0)

Now we get something interesting.

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/screenshot-2024-07-07-at-52918-pm_96c5f146682b0df9f2db44e7a60b82b8.jpg" alt="Two conjoined circles that are only a little blurry."/></figure>

`colorDoge` is one of the blend modes that can be used to brighten the destination image (our canvas in this case) with respect to to the source image (blend mode layer). It basically brightens the brighter areas and adds more contrast and saturation to them. In our case, it helps create sharper outlines for our circles, replacing the blurred outlines. Darker areas of the destination image aren’t affected as much as the brighter areas.

## Adding a colorBurn Layer

Now we’ll add a `colorBurn` layer on top of our whole canvas. Similar to what we did earlier, we’ll create a rectangle, make it black, and set its blend mode to `BlendMode.colorBurn`.

```
canvas.drawRect(
      Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
      Paint()
        ..color = Colors.black
        ..style = PaintingStyle.fill
        ..blendMode = BlendMode.colorBurn,
    );
```

[Save this code](https://takrutvik.pieces.cloud/?p=33394fadd5)

We’re getting close!

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/screenshot-2024-07-07-at-52931-pm_b76dc0d63332c4144b1eda0d5feef982.jpg" alt="Two conjoined circles that are not blurry."/></figure>

`colorBurn` is basically the opposite of `colorDodge`. It darkens the destination image with respect to the source image. With this, the inside blurred areas of the circles become completely black, creating a sharp, gooey shape.

## Adding A Gradient Layer

As before, we’ll add a gradient layer with a blend mode set to `screen`. Setting the blend mode to `screen` will compose this layer as an overlay on top of our gooey shapes, excluding the white areas of the canvas.

```
canvas.drawRect(
      Rect.fromCenter(center: Offset(size.width / 2, size.height / 2), width: size.width, height: size.height),
      Paint()
        ..style = PaintingStyle.fill
        ..shader = const RadialGradient(
          colors: [Colors.yellow, Colors.pink],
        ).createShader(
          Rect.fromCenter(
            center: Offset(size.width / 2, size.height / 2),
            width: size.width,
            height: size.height,
          ),
        )
        ..blendMode = BlendMode.screen,
    );
```

[Save this code](https://takrutvik.pieces.cloud/?p=0f934fa062)

With that, our gooey blobs are ready! 🎉

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/final-version-after-adding-drag-controls_af11f69f8f93cff24333364b7206ce2f.gif" alt="Two circles melting into each other."/></figure>

This is the final version after adding drag controls.

We finally have gooey blobs in Flutter and that wasn’t crazy hard to do. It’s interesting how using composite effects with various blend modes can help you create some interesting visuals like this.

Use these blobs cautiously though :)

<figure><img src="https://d37oebn0w9ir6a.cloudfront.net/account_32099/image-2_fd2826d41c52306daa91bca3c7a21719.gif" alt="The evil ghost blob from Ghostbusters."/></figure>

## Conclusion

You can find the source code for these gooey blobs [here](https://github.com/rutvik110/Flutter-Animations/blob/master/lib/custom_painters/blobs.dart) among some of my other creative work. Feel free to poke around and play with them :)