Search code examples
javaandroidandroid-layoutandroid-imageviewflood-fill

Android - How to color whole area of an image on user's tap and center it on the sceen?


I'm working on a little project where the user can color whole areas of an white image by tapping on the touch-screen.

This is an example of image:

enter image description here

And this is not difficult, infact I use the flood fill algorithm to color the same-color areas separated by the black lines. My problem is to center the image which I modify, in the screen.

I use the following code:

JAVA:

import java.util.LinkedList;
import java.util.Queue;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class Main extends Activity {

private RelativeLayout dashBoard;
private MyView myView;
public ImageView image;

Button b_red, b_blue, b_green, b_orange, b_clear;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    myView = new MyView(this);
    setContentView(R.layout.activity_main);
    findViewById(R.id.dashBoard);
    findViewById(R.id.myImage);


    b_red = (Button) findViewById(R.id.b_red);
    b_blue = (Button) findViewById(R.id.b_blue);
    b_green = (Button) findViewById(R.id.b_green);
    b_orange = (Button) findViewById(R.id.b_orange);

    b_red.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            myView.changePaintColor(0xFFFF0000);
        }
    });

    b_blue.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            myView.changePaintColor(0xFF0000FF);
        }
    });

    b_green.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            myView.changePaintColor(0xFF00FF00);
        }
    });

    b_orange.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            myView.changePaintColor(0xFFFF9900);
        }
    });

    dashBoard = (RelativeLayout) findViewById(R.id.dashBoard);
    image = (ImageView) findViewById(R.id.myImage);
    dashBoard.addView(myView);
}

public class MyView extends View {

    private Paint paint;
    private Path path;
    public Bitmap mBitmap;
    public ProgressDialog pd;
    final Point p1 = new Point();
    public Canvas canvas;

    //Bitmap mutableBitmap ;
    public MyView(Context context) {

        super(context);

        this.paint = new Paint();
        this.paint.setAntiAlias(true);
        pd = new ProgressDialog(context);
        this.paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5f);
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.forme).copy(Bitmap.Config.ARGB_8888, true);
        this.path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        this.canvas = canvas;
        this.paint.setColor(Color.RED);

        canvas.drawBitmap(mBitmap, 0, 0, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:

                p1.x = (int) x;
                p1.y = (int) y;
                final int sourceColor = mBitmap.getPixel((int) x, (int) y);
                final int targetColor = paint.getColor();
                new TheTask(mBitmap, p1, sourceColor, targetColor).execute();
                invalidate();
        }
        return true;
    }

    public void clear() {
        path.reset();
        invalidate();
    }

    public int getCurrentPaintColor() {
        return paint.getColor();
    }

    public void changePaintColor(int color){
        this.paint.setColor(color);
    }

    class TheTask extends AsyncTask<Void, Integer, Void> {

        Bitmap bmp;
        Point pt;
        int replacementColor, targetColor;

        public TheTask(Bitmap bm, Point p, int sc, int tc) {
            this.bmp = bm;
            this.pt = p;
            this.replacementColor = tc;
            this.targetColor = sc;
            pd.setMessage("Filling....");
            pd.show();
        }

        @Override
        protected void onPreExecute() {
            pd.show();

        }

        @Override
        protected void onProgressUpdate(Integer... values) {

        }

        @Override
        protected Void doInBackground(Void... params) {
            FloodFill f = new FloodFill();
            f.floodFill(bmp, pt, targetColor, replacementColor);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            pd.dismiss();
            invalidate();
        }
    }
}

// Flood fill
public class FloodFill {

    public void floodFill(Bitmap image, Point node, int targetColor, int replacementColor) {

        int width = image.getWidth();
        int height = image.getHeight();
        int target = targetColor;
        int replacement = replacementColor;

        if (target != replacement) {
            Queue<Point> queue = new LinkedList<Point>();
            do {

                int x = node.x;
                int y = node.y;
                while (x > 0 && image.getPixel(x - 1, y) == target) {
                    x--;
                }

                boolean spanUp = false;
                boolean spanDown = false;
                while (x < width && image.getPixel(x, y) == target) {
                    image.setPixel(x, y, replacement);
                    if (!spanUp && y > 0 && image.getPixel(x, y - 1) == target) {
                        queue.add(new Point(x, y - 1));
                        spanUp = true;
                    } else if (spanUp && y > 0 && image.getPixel(x, y - 1) != target) {
                        spanUp = false;
                    }
                    if (!spanDown && y < height - 1 && image.getPixel(x, y + 1) == target) {
                        queue.add(new Point(x, y + 1));
                        spanDown = true;
                    } else if (spanDown && y < (height - 1) && image.getPixel(x, y + 1) != target) {
                        spanDown = false;
                    }
                    x++;
                }

            } while ((node = queue.poll()) != null);
        }
    }
}
}

XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#969696"
tools:context=".Main" >

<RelativeLayout
    android:id="@+id/dashBoard"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_above="@+id/b_red"
    android:layout_alignParentLeft="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentTop="true"
    android:layout_marginBottom="10dp"
    android:background="#DDDDDD" >

    <ImageView
        android:id="@+id/myImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true" />

</RelativeLayout>

<Button
    android:id="@+id/b_red"
    android:layout_width="65dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"
    android:background="#FF0000" />

<Button
    android:id="@+id/b_green"
    android:layout_width="65dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@+id/b_red"
    android:background="#00FF00" />

<Button
    android:id="@+id/b_blue"
    android:layout_width="65dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@+id/b_green"
    android:background="#0000FF" />

<Button
    android:id="@+id/b_orange"
    android:layout_width="65dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_toRightOf="@+id/b_blue"
    android:background="#FF9900" />

<Button
    android:id="@+id/button5"
    android:layout_width="60dp"
    android:layout_height="40dp"
    android:layout_alignParentBottom="true"
    android:layout_alignParentRight="true"
    android:text="Clear" />

</RelativeLayout>

When I run the project I get:

enter image description here

As you can see the image isn't extended in all the light-grey area! I suppose this problem is cause by the intercation with a general View added to the RelativeLayout which contains the ImageView.

Can you tell how can I exetend the image to all the light-grey area? And how can I intercact with the ImageVie component without using the RelativeLayout component?

Thank you for your help!


Solution

  • Update:

    To get the size of your MyView, just override the onMeasure() method like this:

    @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
                 w = widthMeasureSpec - MeasureSpec.EXACTLY;
                 h = heightMeasureSpec - MeasureSpec.EXACTLY;
    
                Log.d("Dim", Integer.toString(w) + " | " + Integer.toString(h));
            }
    

    Alright, I have found something which I think might be helpful to you. The reason that your Bitmap doesn't fill the canvas is because, obviously, it's small. Normally Canvas.drawBitmap() will take up the space as big as the image. Thereby, in order to fill up your screen with the Bitmap you should scale it before you draw it.

    I did something very quick like this:

    mBitmap = BitmapFactory.decodeResource(getResources(),
                           R.drawable.forme).copy(Bitmap.Config.ARGB_8888, true);
    this.path = new Path();
    
    // Get the screen dimension
    DisplayMetrics displaymetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
    h = displaymetrics.heightPixels;
    w = displaymetrics.widthPixels;
    
    scaledBitmap = Bitmap.createScaledBitmap(mBitmap, w, h, false);
    

    And then in onDraw() you draw the scaled Bitmap instead:

    canvas.drawBitmap(scaledBitmap, 0, 0, paint);
    

    Also make sure that you send the scaledBitmap to your AsyncTask:

    ...
    final int sourceColor = scaledBitmap.getPixel((int) x, (int) y); //<- HERE
    ...
    new TheTask(scaledBitmap, p1, sourceColor, targetColor).execute(); //<- AND HERE
    

    IMPORTANT:

    Here I get the screen dimensions to scale the bitmap. But it's not good as you can see below. Your canvas should only take up dashBoard space not the entire screen. So I recommend you to find the dashBoard dimension instead and scale the Bitmap accordingly.

    PaintTest