【メモ】Android OpenCV-FloodFill

### これ
![](https://image.flaticon.com/icons/svg/217/217483.svg)


### 以下、プレビュー

![ezgif-5-43ace14d23.gif](https://qiita-image-store.s3.amazonaws.com/0/134166/44280f1d-826b-bc5c-9fd1-f6af795e45f0.gif)

### 以下、ソースコード

```java:MainActivity.class
package com.example.c_heo.floodfilltest;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnTouchListener, SurfaceHolder.Callback2, View.OnClickListener{
    private static final String TAG = MainActivity.class.getSimpleName();

    static {
        Log.d(TAG, "static initializer: Trying to load OpenCV library.");
        OpenCVLoader.initDebug();
    }

    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;

    private Bitmap bitmap;
    private Mat mat;

    private List<CircleCoordinate> circleCoordinateList = new ArrayList<>();
    private List<LineCoordinate> lineCoordinateList = new ArrayList<>();
    private List<SeedCoordinate> seedCoordinateList = new ArrayList<>();
    private Mode mode;
    private float preX, preY, postX, postY;

    private Paint circlePaint;
    private Paint linePaint;
    private Paint pathPaint; // TODO: memory管理のため、lineをpathに書き換え

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        surfaceView = findViewById(R.id.surfaceView);
        surfaceHolder = surfaceView.getHolder();
        surfaceHolder.addCallback(this);

        circlePaint = new Paint();
        circlePaint.setColor(Color.LTGRAY);

        linePaint = new Paint();
        linePaint.setColor(Color.LTGRAY);
        linePaint.setStrokeWidth(5.0f);

        findViewById(R.id.drawingButton).setOnClickListener(this);
        findViewById(R.id.floodFillButton).setOnClickListener(this);
    }

    @Override
    public void surfaceRedrawNeeded(SurfaceHolder holder) {

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        surfaceView.setOnTouchListener(this);
        Log.d(TAG, "surfaceChanged: [width, height] # ["+width+", "+height+"]");

        bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        mat = new Mat();

        circleCoordinateList.clear();
        lineCoordinateList.clear();

        mode = Mode.Drawing;

        Canvas canvas = surfaceHolder.lockCanvas();
        drawing(canvas);
        surfaceHolder.unlockCanvasAndPost(canvas);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        surfaceView.setOnTouchListener(null);

        // garbage collection
        mat.release();
        bitmap.recycle();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (mode == Mode.Drawing) {
            drawing(event);
        }
        else if (mode == Mode.FloodFill) {
            floodFilling(event);
        }


        Canvas canvas = surfaceHolder.lockCanvas();
        drawing(canvas);
        surfaceHolder.unlockCanvasAndPost(canvas);
        return true;
    }

    private void floodFilling(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN :
                addSeed(new SeedCoordinate((int) event.getX(), (int) event.getY()));
                break;
        }
    }

    private void drawing(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN :
                preX = event.getX();
                preY = event.getY();
                addCircle(new CircleCoordinate(preX, preY));
                break;

            case MotionEvent.ACTION_MOVE :
                postX = event.getX();
                postY = event.getY();
                addLine(new LineCoordinate(preX, preY, postX, postY));

                preX = postX;
                preY = postY;
                break;

            case MotionEvent.ACTION_UP :
                postX = event.getX();
                postY = event.getY();
                addCircle(new CircleCoordinate(postX, postY));

                preX = .0f;
                preY = .0f;
                postX = .0f;
                postY = .0f;
                break;
        }
    }

    private void addLine(LineCoordinate lineCoordinate) {
        lineCoordinateList.add(lineCoordinate);
    }
    private void addCircle(CircleCoordinate circleCoordinate) {
        circleCoordinateList.add(circleCoordinate);
    }

    private void addSeed(SeedCoordinate seedCoordinate) {
        seedCoordinateList.add(seedCoordinate);
    }

    // TODO: memory管理のため、canvasのdrawBitmap及び、setBitmapを以下のAPIに書き換え（: canvasのdrawBitmap withRect及び、setMatrix）
    private void drawing(Canvas canvas) {
        Canvas tempCanvas = new Canvas(bitmap);
        tempCanvas.drawColor(Color.GRAY);

        // draw Circles
        for (CircleCoordinate circleCoordinate : circleCoordinateList) {
            tempCanvas.drawCircle(circleCoordinate.cX, circleCoordinate.cY, 10.0f, circlePaint);
        }

        // draw Lines
        for (LineCoordinate lineCoordinate : lineCoordinateList) {
            tempCanvas.drawLine(lineCoordinate.startX, lineCoordinate.startY, lineCoordinate.stopX, lineCoordinate.stopY, linePaint);
        }

        // floodFill
        for (SeedCoordinate seedCoordinate : seedCoordinateList) {
            // load the input image
            Utils.bitmapToMat(bitmap, mat);
            Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2RGB);

            // define the seed point
            Point seedPoint = new Point(seedCoordinate.cX, seedCoordinate.cY);

            // flood fill with red
            Imgproc.floodFill(
                    mat,
                    new Mat(),
                    seedPoint,
                    new Scalar(204, 204, 204), // LTGRAY
                    new Rect(),
                    new Scalar(0, 0, 0),
                    new Scalar(0, 0, 0),
                    4
            );

            // show the output
            Utils.matToBitmap(mat, bitmap);
        }

        // draw
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.drawingButton :
                mode = Mode.Drawing;
                Log.d(TAG, "onClick: Drawing Mode.");
                break;

            case R.id.floodFillButton :
                mode = Mode.FloodFill;
                Log.d(TAG, "onClick: FloodFill Mode.");
                break;
        }
    }

    private class CircleCoordinate {
        float cX;
        float cY;

        CircleCoordinate(float cX, float cY) {
            this.cX = cX;
            this.cY = cY;
        }
    }

    private class LineCoordinate {
        float startX;
        float startY;
        float stopX;
        float stopY;

        LineCoordinate(float startX, float startY, float stopX, float stopY) {
            this.startX = startX;
            this.startY = startY;
            this.stopX = stopX;
            this.stopY = stopY;
        }
    }

    private class SeedCoordinate {
        int cX;
        int cY;

        SeedCoordinate(int cX, int cY) {
            this.cX = cX;
            this.cY = cY;
        }
    }

    private enum Mode {
        Drawing,
        FloodFill
    }
}

```