Generative Art in Flutter (d_art)
Generative art is a form of art that is created using algorithms, rather than traditional artistic techniques. It has been gaining popularity in recent years, and there are many tools and frameworks available for creating generative art. One such tool is Flutter, an open-source mobile application development framework which is created by Google.
In this article, we’ll explore how to create basic generative art with Flutter. We can create stunning and unique artworks built entirely by code. Moreover, we can use them in to create unique backgrounds or in buttons or where you want to use them to get more attention.
There is an artist which leeds and plays Flutter into a generative art tool. Robert Felker is a digital artist who has been creating generative art and has developed a unique style that is inspired by nature and mathematics. Felker often uses techniques such as particle systems and cellular automata to create his art, and these can be implemented in Flutter using a variety of different approaches. And that called as d_art (means Dart art).
To get started, we will need to install Flutter and set up a new project. And follow the classes that I wrote. Of course you are free to show your imagination. It is important to know what the code will do and foresee what it can do but the most important thing is that wow expression on your face when you encounter an unexpected image. We don’t cover it in this example, but sometimes we try to get errors are included to get a surprising picture.
import 'package:flutter/material.dart';
import 'package:quest1/work-1/canvas.dart';
import 'dart:math';
import 'package:quest1/work-1/particle.dart';
class MyPainter extends StatefulWidget {
const MyPainter({Key? key}) : super(key: key);
@override
State<MyPainter> createState() => _MyPainter();
}
Color getRandomColor(Random rgn) {
var a = rgn.nextInt(255);
var r = rgn.nextInt(255);
var g = rgn.nextInt(255);
var b = rgn.nextInt(255);
return Color.fromARGB(a, r, g, b);
}
double maxRadius = 6;
double maxSpeed = 0.2;
double maxTheta = 2.0 * pi;
class _MyPainter extends State<MyPainter> with SingleTickerProviderStateMixin {
late List<MyParticle> particles;
late Animation<double> animation;
late AnimationController controller;
Random rgn = Random(DateTime.now().millisecondsSinceEpoch);
@override
void initState() {
super.initState();
controller =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween<double>(begin: 0, end: 300).animate(controller)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.repeat();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
this.particles = List.generate(100, (index) {
var p = MyParticle(
position: const Offset(-1, -1),
color: getRandomColor(rgn),
speed: rgn.nextDouble() * maxSpeed,
theta: rgn.nextDouble() * maxTheta,
radius: rgn.nextDouble() * maxRadius);
p.color = getRandomColor(rgn);
p.position = const Offset(-1, -1);
p.speed = rgn.nextDouble() * maxSpeed;
p.theta = rgn.nextDouble() * maxTheta;
p.radius = rgn.nextDouble() * maxRadius;
return p;
});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: CustomPaint(
child: Container(),
painter: MyCanvas(rgn, particles, animation.value),
));
}
}
import 'dart:ui';
class MyParticle {
Offset position;
Color color;
double speed;
double theta;
double radius;
MyParticle({
required this.position,
required this.color,
required this.speed,
required this.theta,
required this.radius,
});
}
import 'package:flutter/material.dart';
import 'dart:math';
import 'package:quest1/work-1/particle.dart';
Offset polarToCartesian(double speed, double theta) {
return Offset(speed * cos(theta), speed * sin(theta));
}
class MyCanvas extends CustomPainter {
Random rgn;
double animValue;
List<MyParticle> particles;
MyCanvas(this.rgn, this.particles, this.animValue);
@override
void paint(Canvas canvas, Size size) {
//update the objects
this.particles.forEach((p) {
var velocity = polarToCartesian(p.speed, p.theta);
var dx = p.position.dx + velocity.dx;
var dy = p.position.dy + velocity.dy;
//if either position falls outside the canvas
// reinitialize them
if (p.position.dx < 0 || p.position.dx > size.width) {
dx = rgn.nextDouble() * size.width;
}
if (p.position.dy < 0 || p.position.dy > size.height) {
dy = rgn.nextDouble() * size.height;
}
p.position = Offset(dx, dy);
});
//Color _randomColor = Colors.primaries[Random().nextInt(Colors.primaries.length)];
//Paint the objects
this.particles.forEach((p) {
var paint = Paint();
paint.color = Colors.orange;
canvas.drawCircle(p.position, p.radius, paint);
});
// var dx = size.width / 2;
// var dy = size.height / 2;
// var c = Offset(dx, dy);
// var radius = 100.0;
// var paint = Paint();
// paint.color = Colors.red;
// canvas.drawCircle(c, radius, paint);
}
// @override
// bool shouldREpaint(CustomPainter o) {
// return true;
// }
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true;
}
// @override
// noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
import 'package:flutter/material.dart';
import 'package:quest1/work-1/painter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyPainter(),
);
}
}
The result is;
Once we have done this, we can begin experimenting with different algorithms and techniques to create our own generative art. After that we are going to make changes in main and build our app on this animated background.
import 'package:flutter/material.dart';
import 'package:quest1/work-1/painter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.orange,
title: const Text("d_art"),
),
body: Stack(children: [
const MyPainter(),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Text(
"Your",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Text(
"Lovely",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Text(
"App",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.white),
),
Text(
"Background",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.w600,
color: Colors.white),
),
],
),
],
),
]),
));
}
}
I have only add text over this example but I believe that you got the point. The result is;
One of the key benefits of using Flutter for generative art is the ability to create interactive experiences. By using Flutter’s built-in gesture recognition and animation capabilities, we can create art that responds to the user’s inputs and changes in real-time. This can create a truly immersive and engaging experience for the viewer.
In conclusion, Flutter is a powerful tool for creating generative art, and by following the techniques of artists like Robert Felker, we can create stunning and unique pieces that are entirely generated by code. Whether you are a seasoned artist or a beginner, Flutter provides a wide range of capabilities and resources for creating beautiful and engaging generative art.
Useful resources and links;
Bonus in one class;
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: CircleGenerativeArt(),
),
);
}
class CircleGenerativeArt extends StatefulWidget {
@override
_CircleGenerativeArtState createState() => _CircleGenerativeArtState();
}
class _CircleGenerativeArtState extends State<CircleGenerativeArt> {
final Random random = Random();
Color getRandomColor(Random rgn) {
var a = rgn.nextInt(255);
var r = rgn.nextInt(255);
var g = rgn.nextInt(255);
var b = rgn.nextInt(255);
return Color.fromARGB(a, r, g, b);
}
double getSize(Random rdm) {
double size = rdm.nextDouble() * 100;
return size;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomPaint(
painter: CirclePainter(
color: getRandomColor(random),
size: getSize(random),
),
child: Container(),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Regenerate the circle when the button is pressed
setState(() {
getRandomColor(random);
getSize(random);
});
},
child: const Icon(Icons.refresh),
),
);
}
}
class CirclePainter extends CustomPainter {
CirclePainter({
required this.color,
required this.size,
});
final Color color;
final double size;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
final center = Offset(size.width / 2, size.height / 2);
canvas.drawCircle(center, this.size, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}