Blog FQSoft

Just Simple Code Documentation

Flutter

Flutter : Membuat Game Mewarnai dengan Algoritma Flood Fill

Faiq Himmah 31 August 2024

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

flutter flood fill