Generative Art in Flutter

Roaa Khaddam
Flutter Community
Published in
17 min readAug 9, 2023

--

Recently, I gave a talk at FlutterCon Berlin 2023 about creating animations in Flutter with low level APIs, as I was preparing the talk, the content progressed to be a lot about art, more specifically, generative art and its technological history.

Animated generative art in Flutter — FlutterCon Berlin 2023 — Roaa Khaddam

I was hugely inspired by Vera Molnar, a great Hungarian artist based in France who is now in her 90’s and still produces work. She is considered one of the pioneers of computer generated generative art.

From a young age, Molnar was fascinated by randomness and was one of the early adapters of using algorithms to render artwork, and in a time when computer generated art was claimed to be dehumanized and its authenticity highly doubted, she argued that working with the randomness in machines and their ability to efficiently produce a high number of outputs was in fact proof of the opposite of those arguments.

Vera Molnar Quote — High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Flutter Generative Art Talk Slides — Vera Molnar Quote — Image Source

Because when you work with a computer, you’re using it as a tool to achieve a closer alignment with your own imagination. You are using your creativity and curiosity and expressing yourself in a medium that gives you more space for exploration than manual labor could ever provide.

Over the years, Molnar’s methods and equipment for producing generative artwork kept evolving. In fact, as early as a decade before she even had access to a computer, she was already creating “generative” art.

By definition, generative art is any practice where the artist uses a system, such as a set of natural language rules, a computer program, or a machine, and then sets it into motion with some degree of autonomy.

In the 50s, she introduced us to the term “Machine Imaginere” or the imaginary machine. She used fundamental principles from mathematics along with analog ‘randomness generators’ such as a roll of dice. In this way, she ‘produced’ sequences of drawings, making modifications to a single parameter at a time. She would then meticulously handcraft each of these variations.

Vera Molnar — Machine Imaginere — High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Artwork Generated by a “Machine Imaginere” — Artwork Source

After she gained access to computers, she initially worked with IBM mainframes that didn’t even have monitors. As she transitioned to more advanced systems, she used programming languages like BASIC and Fortran to feed the machine with specific instructions that would control a connected pen plotter. The pen plotter would then use an ink pen and a robot arm to translate the code into drawings.

Vera Molnar — High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Vera Molnar creating generative art with code and a pen plotter — Source: YouTube

I was really captivated when I learned how far back drawing with code goes, and how brilliant artists like Molnar and many others were composing art that I couldn’t imagine is possible with the limited technology of their time.

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Artwork by Vera Molar that inspires the work in this talk/tutorial
Fieder Nake — High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Artworks by Fieder Nake in the 1960’s & 1970’s — Sources: 1, 2

Today, extraordinary works of art are being produced with the sophisticated technology of our time, and Flutter makes for a great tool for such work with its cross platform capabilities and out-of-the-box APIs.

So inspired by Vera Molnar’s artistic style and ideas, we will go over some basic principles of generative art by exploring Flutter’s CustomPainter and the Canvas API and we will introduce animations to make our artwork come to life.

Let’s start coding! 👩🏻‍💻

Flutter’s CustomPainter & The Canvas API

You can quickly access the Canvas API to start freely painting on the screen by adding a CustomPaint widget in your widget’s build method, And providing it with a CustomPainter implementation.

@override
Widget build(BuildContext context) {
return CustomPaint(
painter: SquareCustomPainter(),
);
}

class SquareCustomPainter extends CustomPainter {
//...
}

The CustomPainter implementation overrides 2 methods, the paint method is where the painting is performed with the Canvas, and the shouldRepaint method allows you to specify when the paint method must be called again to update the canvas. We can return false for now.

class SquareCustomPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
//...
}

@override
bool shouldRepaint(covariant SquareCustomPainter oldDelegate) {
return false;
}
}

Let’s start simple by drawing a rectangle using the canvas’s drawRect API inside the paint method

@override
void paint(Canvas canvas, Size size) {
canvas.drawRect();
}

There are multiple ways in which you can draw rectangles. For example, a Rect.fromCenter allows you to draw a rectangle by providing the offset of its center and its width and height. Here, we’re using the center of the canvas to center the rectangle.

@override
void paint(Canvas canvas, Size size) {
final center = Offset(
size.width / 2,
size.height / 2
);

canvas.drawRect(
Rect.fromCenter(
center: center,
width: size.shortestSide * 0.8,
height: size.shortestSide * 0.8,
),
/* ... */,
);
}

You can then add a Paint object from which, among others, you can specify the following properties:

  • style that takes either a PaintingStyle.stroke or a PaintingStyle.fill enum value to specify the style of the pain.
  • The color which applies to either the fill or the stroke depending on the style you provide.
  • strokeWidth for the stroke style.
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 3;

final center = Offset(
size.width / 2,
size.height / 2
);

canvas.drawRect(
Rect.fromCenter(
center: center,
width: size.shortestSide * 0.8,
height: size.shortestSide * 0.8,
),
paint,
);
}

Result:

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Drawing a rectangle using canvas.drawRect() method

The Flutter docs provide helpful images that show you how the different APIs of creating rectangles work:

Illustrating how different methods to draw rectangles with the canvas work — 1, 2, 3, 4, 5

Let’s use the fromPoints API as another example:

@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.stroke
..strokeWidth = 3;

final side = size.shortestSide * 0.8;

canvas.drawRect(
Rect.fromPoints(
Offset.zero,
Offset(side, side),
),
paint,
);
}

You will note that if you try that, you would see an outcome where the rectangle is placed on the top left corner rather than centered. Which brings me to an important concept when working with the Canvas.

Centering with the Canvas API

When you render a canvas using a CustomPainter or any other way (like custom RenderObjects), it will be positioned at the top left corner of the widget containing it, and any offsetting or translating you do, is done respective to the 0 by 0 point that is the top left corner.

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Canvas positioning in Flutter

So if you wanted to draw something centered, you need to:

  1. Start by saving the current status of the canvas with canvas.save()
  2. Translate the canvas to it’s center offset.
  3. Do the drawing.
  4. And reset the canvas using canvas.restore()
canvas.save(); // 1
canvas.translate(center.dx, center.dy); // 2
canvas.drawRect( // 3
Rect.fromPoints(
Offset(-side / 2, -side / 2),
Offset(side / 2, side / 2),
),
paint,
);
canvas.restore(); // 4
High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Drawing centered content on the Canvas in Flutter

🔗️ Check out the full commented code for drawing and centering squares with the CustomPainter

Generative Art Tools

There are a couple of tools involved when it comes to creating generative artwork, and I want to briefly mention some while we implementing those tools using the Canvas API.

Tiling — Generative Art Tool #1

Like tiling a wall, you tile pieces of your artwork by repeating them horizontally and vertically, so we can introduce tiling to our previously created square by creating a grid of it across the canvas. We do that by calculating the number of squares that can fit horizontally and vertically in the widget that contains our CustomPainter, and draw the squares using a simple for loop. You have access to the Size of that widget through the CustomPainter ‘s paint method.

We will also calculate how much to offset the drawing so that the final grid is centered

@override
void paint(Canvas canvas, Size size) {
final xCount = ((size.width + gap) / (side + gap)).floor();
final yCount = ((size.height + gap) / (side + gap)).floor();

final contentSize = Size(
(xCount * side) + ((xCount - 1) * gap),
(yCount * side) + ((yCount - 1) * gap),
);

final offset = Offset(
(size.width - contentSize.width) / 2,
(size.height - contentSize.height) / 2,
);

canvas.save();
canvas.translate(offset.dx, offset.dy);

for (int index = 0; index < totalCount; index++) {
int i = index ~/ yCount;
int j = index % yCount;

canvas.drawRect(
Rect.fromLTWH(
(i * (side + gap)),
(j * (side + gap)),
side,
side,
),
paint,
);
}
canvas.restore();
}

Result:

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Tiling — Generative Art Tool #1 — Tiling squares into a centered grid

🔗 Here’s the full commented code of that grid

Recursion — Generative Art Tool #2

Using the programming power of recursion to create artwork elements that can appear repeatedly nested until one or more conditions are met, gives you endless possibilities for manipulating lines and shapes in your canvas. For our grid of squares, we’ll implement recursion in a way that is inspired by one of Vera Molnar’s artworks.

We can do that by using the canvas.drawRect() method in a recursive function that simply keeps drawing rectangles with a side length that is reduced on each recursive call until a specified minimum length is reached.

// Paint method
for (int index = 0; index < totalCount; index++) {
int i = index ~/ yCount;
int j = index % yCount;

drawNestedSquares( // Recursively draws squares
canvas,
Offset(
(i * (sideLength + gap)),
(j * (sideLength + gap)),
),
sideLength,
paint,
);
}

The recursive function:

void drawNestedSquares(
Canvas canvas,
Offset start,
double side,
Paint paint,
) {
if (sideLength < minSideLength) return;
canvas.drawRect(
Rect.fromLTWH(
start.dx,
start.dy,
side,
side,
),
paint,
);
final nextSideLength = side * 0.8;
final nextStart = Offset(
start.dx + side / 2 - nextSideLength / 2,
start.dy + side / 2 - nextSideLength / 2,
);
drawNestedSquares(canvas, nextStart, nextSideLength, paint);
}

The result so far:

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Recursively drawn squares — Recursion as a generative art tool #2

🔗 The full commented code

Randomness — Generative Art Tool #3

This wouldn’t be a work inspired by Vera Molnar if it did not include what fascinated her the most, randomness, which is actually a powerful tool that can result in truly mesmerizing outcomes.

We can introduce randomness here in a couple of ways:

  • Randomizing the side length of the next square in the recursive function.
  • Introducing a randomized depth value that would eventually randomize the size of the smallest square.
void drawNestedSquares(
Canvas canvas,
Offset start,
double side,
Paint paint,
int depth, // ⬅️
) {
if (side < minSideLength || depth <= 0) return;
canvas.drawRect(
Rect.fromLTWH(
start.dx,
start.dy,
side,
side,
),
paint,
);
final nextSideLength = side * (random.nextDouble() * 0.5 + 0.5); // ⬅️

final nextStart = Offset(
start.dx + side / 2 - nextSideLength / 2,
start.dy + side / 2 - nextSideLength / 2,
);
drawNestedSquares(canvas, nextStart, nextSideLength, paint, depth - 1);
}

The outcome:

Recursively drawn square with randomization — Randomness as a generative art tool #3

🔗 And here’s the full commented code

Widgetbook & Experimenting with input parameters

Similar to what generative artists have been doing since the beginning, you can set up various parameters as system inputs and keep experimenting with non randomized values until you find something that aligns with what you imagined, or even better, you might accidentally stumble on some effects that are event crazier than your imagination!

To be able to quickly and easily do this in Flutter, I used the Widgetbook package and set up a separate main.widgetbook.dart file so that I can run a separate Flutter app. In this Widgetbook app, in addition to being able to test out different input parameters to the artworks I build, I can create a catalogue of all those artwork widgets, and I can access them easily while building them.

Setting up Widgetbook in your project should be easy by following the docs. What is relevant for this article is the Knobs feature. And this is what I will be using throughout the article to illustrate some concepts and to experiment with system inputs.

For example, consider our randomized recursive squares, how would it look if the grid gap was 0? What if the squares were thicker, smaller, or larger? We can experiment with all of that and see results instantly by using knobs with our widgets:

WidgetbookUseCase(
name: 'Randomized',
builder: (context) {
return RandomizedRecursiveSquaresGrid(
gap: context.knobs.double.slider( // ⬅️
label: 'Gap',
initialValue: 5,
min: 0,
max: 50,
),
// ...
);
},
)

Here’s a preview of how this would look like:

Flutter generative art — using widgetbook knobs
Using Widgetbook knobs to experiment with input parameters

🔗 You can try it yourself with this demo link

Okay, black and white is a bit boring, let’s bring some colors into the mix!

Colors

When Vera Molnar first started creating computer generated art, she used a pen plotter machine that didn’t have colors (an older version of the device in the image below). So she would produce the drawing and then color it by hand. She experimented with many material to implement colors, and at some point she even worked with blood!

Later version of a pen plotter machine used by Molnar — Work manually colored post-printing — Source

Thankfully, we have Color classes in Flutter and we don’t need to do that.

Randomizing colors is tricky!

But it actually gets a bit tricky when you want to randomize colors. The easy and straightforward solution would be to have a list of colors that you randomly pick from. But this is not interesting enough and there is a better way!

An initial alternative that one can think of might be randomizing the R, G, and/or B, values in a color’s RGB format. However, as you see from the following GIF, this produces colors that aren’t so pretty or rich.

Color.fromARGB(a, r, g, b)
High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Random color by randomizing R, G, and/or B values of its RGB format — Try it yourself!

The best alternative is to use HSL values (Hue, Saturation, and lightness). You can simply set the saturation & lightness to the fixed values you like, and then randomize the hue value, which allows you to randomize through a spectrum of viable colors whose richness & brightness you can modify using the fixed values.

HSLColor.fromAHSL(a, h /* randomized */, s, l).toColor()
High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Random color by randomizing hue value in Hue, Saturation, Lightness format — Try it yourself!

Now if we introduced randomized colors to our previous artwork, we can get the following result:

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
Recursively and randomly drawn square with color randomization

With the hue value randomized, you can use knobs to adjust the saturation and lightness values to your liking and modify the richness and brightness of the colors of your artwork:

Adjusting Saturation & Lightness to manipulate richness & rightness of randomized colors — Try it yourself!

🔗 Here’s the full code of this result

Displacement — Generative Art Tool #4

When it comes to generative art, chaos is a good thing! And we can throw some chaos at our artwork by utilizing another useful tool, displacement.

For example, going back to our original square, I can say that I want to displace each corner from its original place by a randomized distance to create a randomized polygon. This way combining displacement with randomness for yet a deeper level of chaos.

High level Flutter Animations with Low Level APIs — FlutterCon Berlin 2023 Conference — Roaa Khaddam
The original square we want to add displacement to

To start, we can use drawPoints to draw the original rectangle points. (drawRect can only draw uniform rectangles)

// Paint Method
canvas.drawPoints(
PointMode.polygon,
[
topLeft, // Offset
topRight, // Offset
bottomRight, // Offset
bottomLeft, // Offset
topLeft, // Offset
],
paint,
);

Where topLeft , topRight , …etc are pre-calculated offsets based on the square side length and canvas centering offset. Let’s offset those points by a distance that randomly ranges between the negative and positive value of a predefined maxCornersOffset variable.

// Paint Method
topLeft += Offset(
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
);
topRight += Offset(
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
);
/* ... */
canvas.drawPoints(
PointMode.polygon,
[
topLeft, // Offset
topRight, // Offset
bottomRight, // Offset
bottomLeft, // Offset
topLeft, // Offset
],
paint,
);

Now we can go back to our Widgetbook and experiment with the maxCornerOffset input and see how the polygon gets more distorted as this value increases and vice versa.

Experimenting with the maximum displacement of a square’s original corners offsets — Try it yourself!

Repetition — Generative Art Tool #5

As a final tool in our generative art tool belt, we can implement some repetition simply by using a for loop with min and max values that we can provide as input parameters to the system.

// Paint Method
final repetition = random.nextInt(10) + minRepetition;

for (int i = 0; i < repetition; i++) {
topLeft += Offset(
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
);
topRight += Offset(
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
maxCornersOffset * 2 * (random.nextDouble() - 0.5),
);
/* ... */
canvas.drawPoints(
PointMode.polygon,
[
topLeft, // Offset
topRight, // Offset
bottomRight, // Offset
bottomLeft, // Offset
topLeft, // Offset
],
paint,
);
}

Using our Widgetbook, we can see the effect of the minRepetition value, among other inputs, on how our randomized polygons will look like.

Experimenting with repetition — Repetition as a generative art tool #5 — Try it yourself!

Let’s add some finishing touches to make this a viable artwork by:

  1. Switching to dark mode, because why not!

2. Add tiling (our first generative art tool):

3. And finally, add colors:

And voila! As you can see, using a couple of generative art basics, and a few lines of code that implement basic functionality of Flutter’s Canvas API, we went from a simple boring square, all the way to a canvas filled with shapes, colors, and chaos!

But why stop here? Let’s add in some animations to bring this chaos into life!

Animation

Before we jump into animations, it’s best to invest some time into refactoring. My main goal is reusability and utility getters and methods. So I’m going to create a Polygon class that will store data for each polygon, like its corner offsets, its color , its level in a single repetition of overlayed polygons, and it’s normalized location in a grid of polygons.

class Polygon {
final Offset topLeft;
final Offset topRight;
final Offset bottomRight;
final Offset bottomLeft;
final Color color;
// Level in a set of overlapping polygons
final double level;
// Normalized location on x and y axis
final Location location;

List<Offset> get points => [
topLeft,
topRight,
bottomRight,
bottomLeft,
topLeft,
];
}

This way I can implement a utility method (e.g. generatePolygonSets ) that will populate that data, and make it possible for me to directly use Polygon getters in my CustomPainter’s paint method.

class PolygonsCustomPainter extends CustomPainter {
PolygonsCustomPainter({
required this.random,
this.strokeWidth = 2,
this.maxSideLength = 200,
this.maxCornersOffset = 20,
}) {
polygons = generateDistortedPolygonsSet( // Generates the polygons
random,
maxSideLength: maxSideLength,
maxCornersOffset: maxCornersOffset,
);
}

late final List<Polygon> polygons;
/* ... */

@override
void paint(Canvas canvas, Size size) {
for (int i = 0; i < polygons.length; i++) {
paint.color = polygons[i]
.color
.withOpacity(polygons[i].level);

canvas.drawPoints(
PointMode.polygon,
polygons[i].points, // Polygon getter
paint,
);
}
}
}

This code will loop over the pre-generated polygons and paint them using the Canvas’s drawPoints API with the points getter and a color that varies in opacity based on the level of the polygon, producing the same output as before.

Additionally, I can store the values of the original corner offsets of the polygon (its square points before the displacement), enabling me to implement a getLerpedPoints method that utilizes Flutter’s Offset.lerp() functionality. This basically allows us to get the value between 2 offsets given a normalized value between zero and one.

class Polygon {
// Displaced offsets
final Offset topLeft;
final Offset topRight;
final Offset bottomRight;
final Offset bottomLeft;
// Original offsets
final Offset topLeftOrigin;
final Offset topRightOrigin;
final Offset bottomRightOrigin;
final Offset bottomLeftOrigin;
/* ... */

List<Offset> getLerpedPoints(double value) {
return [
Offset.lerp(topLeft, topLeftOrigin, value)!,
Offset.lerp(topRight, topRightOrigin, value)!,
Offset.lerp(bottomRight, bottomRightOrigin, value)!,
Offset.lerp(bottomLeft, bottomLeftOrigin, value)!,
Offset.lerp(topLeft, topLeftOrigin, value)!,
];
}
}

🔗 Full code of the Polygon class

Now I can use this getLerpedPoints getter in the paint method with the drawPoints call, utilize the location data stored in the Polygon class, and add a little twist with a sine function, because, math!

// Paint Method
for (int i = 0; i < polygons.length; i++) {
paint.color = polygons[i]
.color
.withOpacity(polygons[i].level);

canvas.drawPoints(
PointMode.polygon,
polygons[i].getLerpedPoints(
// Some cool math ⬇️
0.5 * sin(polygons[i].location.x * 2 * pi) + 0.5
),
paint,
);
}

This will produce the following result:

Using a sine function to manipulate painting

Okay now let’s REALLY implement animations.

Thought Process 🤔

To decide what I want to animate, I started with a single polygons set and saw that I can animate the opacity of each polygon, making them fade one after the other, and I can also animate each polygon from a uniform square to it’s current shape, also one after the other. This is now easily possible after the refactor!

Contemplating what can be animated 🤔

Animations with the CustomPainter

To set up the animation, we can provide the CustomPainter with an animationController and pass it to the repaint parameter of its super constructor.

class PolygonsCustomPainter extends CustomPainter {
PolygonsCustomPainter({
required AnimationController animationController, // ⬅️
/* ... */
}) : super(repaint: animationController) { // ⬅️
polygons = generateDistortedPolygonsSet(/* ... */);
}

late final List<Polygon> polygons;
/* ... */
}

This basically triggers a repaint as the animation controller is running. And is equivalent to implementing the shouldRepaint method of the CustomPainter like so:

class PolygonsCustomPainter extends CustomPainter {
PolygonsCustomPainter({
required AnimationController animationController,
/* ... */
}) : super(repaint: animationController) {
polygons = generateDistortedPolygonsSet(/* ... */);
}

/* ... */

@override
bool shouldRepaint(covariant PolygonsCustomPainter oldDelegate) {
return animationController.value != oldDelegate.animationController.value;
}
}

Now since what we want to do is not simply animate everything from state A to state B, and we want to have consecutive animations, we need to implement some sort of delay, and to do that we can create multiple Animation type objects

class PolygonsCustomPainter extends CustomPainter {
PolygonsCustomPainter({
required AnimationController animationController,
/* ... */
}) : super(repaint: animationController) {
polygons = generateDistortedPolygonsSet(/* ... */);
polygonAnimations = generatePolygonAnimations(polygons, animationController);
}

late final List<Animation<double>> polygonAnimations;
late final List<Polygon> polygons;
/* ... */
}

Where each one is a Tween hooked to the parent animation controller with a custom curve and an Interval with begin and end values that we can specify by utilizing our Polygon class and creating getters for the normalized value of when we want each polygon animation to start, and this can use the level of the polygon which is its location within a single repetition of polygons

List<Animation<double>> generatePolygonAnimations(
List<Polygon> polygons,
AnimationController animationController,
) {
return List.generate(
polygons.length,
(i) {
return Tween<double>(begin: 0.2, end: 1).animate(
CurvedAnimation(
parent: animationController,
curve: Interval(
polygons[i].animationStart,
polygons[i].animationEnd,
curve: Curves.easeInOut,
),
),
);
},
);
}


class Polygon {
/* ... */
final double level;
final Location location;
double get animationStart => level; // ⬅️
double get animationEnd =>
(animationStart + 0.2) >= 1.0 ? 1.0 : (animationStart + 0.2); // ⬅️
}

Then we can easily use the animation objects in the loop in the paint method to animate the opacity and the lerped Offset points

// Paint method
for (int i = 0; i < polygons.length; i++) {
final opacity = polygons[i].level * polygonAnimations[i].value * 0.7; // ⬅️
paint.color = polygons[i].color.withOpacity(opacity);

canvas.drawPoints(
PointMode.polygon,
polygons[I].getLerpedPoints(1 - polygonAnimations[I].value), // ⬅️
paint,
);
}

Producing this output:

We can go a step further and utilize our access to the location data of each polygon and adjust the animationStart value so that it depends on the x (or y) location of the polygon set.

class Polygon {
/* ... */
final double level;
final Location location;
double get animationStart => location.x * level; // ⬅️
double get animationEnd =>
(animationStart + 0.2) >= 1.0 ? 1.0 : (animationStart + 0.2);
}

Which will result in:

🔗 Here’s the full code link

Experimenting with different inputs for the final animation — Try it yourself!

Conclusion

This was just an introduction on how you can unlock Flutter tooling and combine it with some basic generative art concepts to start creating beautiful artworks.

And while the generative artists of Vera Molnar’s time were using their coding skills to translate art from the digital to the physical world using pen plotters, I personally believe it will be great to translate this art into the hands of regular mobile app users through captivating and memorable user interfaces. Plus, we can all agree that we deserve more beauty in our lives!

Links

--

--

Roaa Khaddam
Flutter Community

Software Engineer | 💙 Flutter / Dart Google Developer Expert