Flutter : Membuat Game Mewarnai dengan Algoritma Flood Fill
di pub.dev sebenarnya sudah ada package flutter untuk flood fill namun package ini masih menggunakan package image versi 3, sedangkan versi 4 masih belum support. karena itulah kami akan membuat tutorial untuk penerapan algoritma flood fill dengan package 4. Bagi yang ingin mengetahui kegunaan dari package image silahkan kunjungi link ini.
1 - Membuat class FloodFill dan class Point di file yang sama.
Misal dengan nama file flood_filler.dart
class FloodFill {
}
class Point{
final int x;
final int y;
Point(this.x,this.y);
}Class FloodFill : berfungsi untuk memporoses proses pewarnaan
Class Point : berfungsi sebagai objek setiap pixel berdasarkan posisi koordinat X dan Y
2 - Membuat Method floodFill()
Beberapa parameter yang dibutuhkan adalah:
- image type img.Image : Untuk Objek image
- x type int : Posisi pixel dengan koordinat X
- y type int : Posisi pixel dengan koordinat Y
- targetColor type Color : menetukan warna pixel yang hendak dirubah
- replacementColor type Color : menentukan warna baru
- tolerance type double : untuk kerapatan warna
void floodFill(img.Image image, int x, int y, Color targetColor ,Color replacementColor, double tolerance) {
// Jika targetColor sama dengan replacementColor, tidak perlu melakukan apa pun.
if (targetColor == replacementColor) return;
//Prevent Coloring with black
if(targetColor.red==0 && targetColor.green == 0 && targetColor.blue == 0) return;
// Ambil warna pixel saat ini.
img.Pixel currentColor = image.getPixel(x, y);
// Jika warna saat ini tidak sama dengan targetColor, hentikan rekursi.
if ((currentColor.r.toInt() != targetColor.red) && (currentColor.g.toInt()!=targetColor.green) && (currentColor.b.toInt()==targetColor.blue)) return;
// Membuat matriks untuk melacak pixel yang sudah dikunjungi
List<List<bool>> visited = List.generate(image.width, (_) => List.generate(image.height, (_) => false));
final stack = <Point>[];
stack.add(Point(x, y));
while (stack.isNotEmpty) {
final point = stack.removeLast();
final px = point.x;
final py = point.y;
if(px < 0 || px >= image.width || py < 0 || py >= image.height) continue;
// Jika pixel sudah dikunjungi, lewati
if (visited[px][py]) continue;
img.Pixel currentPixel = image.getPixel(px, py);
if (isWithinTolerance(currentPixel, targetColor, tolerance)){
image.setPixelRgb(px, py, replacementColor.red,replacementColor.green,replacementColor.blue);
// Tandai pixel ini sebagai sudah dikunjungi
visited[px][py] = true;
stack.add(Point(px+1, py));//kanan
stack.add(Point(px-1, py));//kiri
stack.add(Point(px, py+1));//bawah
stack.add(Point(px, py-1));//atas
}
}
}3 - Membuat method isWithinTolerance()
bool isWithinTolerance(img.Pixel pixel, Color targetColor, double tolerance) {
double colorDistance = sqrt(
pow((pixel.r.toInt() - targetColor.red), 2) +
pow((pixel.g.toInt() - targetColor.green), 2) +
pow((pixel.b.toInt() - targetColor.blue), 2)
);
return colorDistance <= tolerance;
}4 - Contoh Implementasi Di Page Flutter
class ColoringPage extends StatefulWidget {
img.Image image;
ColoringPage({super.key,required this.image});
@override
State<StatefulWidget> createState() {
return ColoringPageState();
}
}
class ColoringPageState extends State<ColoringPage> {
final GlobalKey _key = GlobalKey();
Size? imageSize;
Size? widgetSize;
Offset? widgetPosition;
Color replacementColor = Colors.blue;
int startX = 0;
int startY = 0;
Offset offset = Offset.zero;
final List<Color> _defaultColors = [
Colors.red,Colors.pink,Colors.purple,Colors.deepPurple,Colors.indigo,
Colors.blue,Colors.lightBlue,Colors.cyan,Colors.teal,Colors.green,
Colors.lightGreen,Colors.lime,Colors.yellow,Colors.amber,Colors.orange,
Colors.deepOrange,Colors.brown,Colors.grey,Colors.blueGrey,Colors.red[900]!,
];
void changeColor(Color color) {
setState(() => replacementColor = color);
Navigator.of(context).pop();
}
_onTapDown(TapDownDetails details){
startX = details.localPosition.dx.round();
startY = details.localPosition.dy.round();
img.Pixel colorChange = widget.image.getPixel(startX,startY);
Color targetColor = Color.fromRGBO(colorChange.r.toInt(), colorChange.g.toInt(), colorChange.b.toInt(), 1.0);
FloodFill().floodFill(widget.image, startX, startY,targetColor, replacementColor,50.0);
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyComponent().appBarComp(context, title: "E-Coloring",iconImage: "assets/images/icon_home_coloring.png"),
body: Center(
key: _key,
child: GestureDetector(
onTapDown: _onTapDown,
child: Container(
child: Image.memory(Uint8List.fromList(img.encodePng(widget.image)),
gaplessPlayback: true,
fit: BoxFit.fitWidth,
),
)
),
),
floatingActionButton: FloatingActionButton(
onPressed: (){
showDialog(context: context,
builder: (buider){
return AlertDialog(
content: Container(
height: 500,
color: MyFunction.get1stColor3(),
padding: const EdgeInsets.symmetric(vertical: 10,horizontal: 30),
child: BlockPicker(
pickerColor: replacementColor,
onColorChanged: changeColor,
availableColors: _defaultColors,
),
),
);
}
);
},
child: const Icon(Icons.color_lens_rounded),
),
);
}
}Untuk hasil X dan Y yang akurat dan mudah maka pastikan main widget container dari gambar gunakan widget Center(). Untuk selain widget center butuh proses perhitungan tambahan dengan menghitung size widget container