15

I'm new in Android programming, and I've tried to write a simple app recently, just for practice! In this one, I want to color an image on user's tap, but I don't know how to start it. I've read different topics which say to use the "Flood Fill" algorithm. I've found it on the web, but I don't know how to put it into my simple app.

The code I've found:

private void FloodFill(Bitmap bmp, Point pt, int targetColor, int replacementColor) 
{
   Queue<Point> q = new LinkedList<Point>();
   q.add(pt);
   while (q.size() > 0) {
       Point n = q.poll();
       if (bmp.getPixel(n.x, n.y) != targetColor)
           continue;

    Point w = n, e = new Point(n.x + 1, n.y);

    while ((w.x > 0) && (bmp.getPixel(w.x, w.y) == targetColor)) {
        bmp.setPixel(w.x, w.y, replacementColor);
        if ((w.y > 0) && (bmp.getPixel(w.x, w.y - 1) == targetColor))
            q.add(new Point(w.x, w.y - 1));
        if ((w.y < bmp.getHeight() - 1) && (bmp.getPixel(w.x, w.y + 1) == targetColor))
            q.add(new Point(w.x, w.y + 1));

        w.x--;
    }

    while ((e.x < bmp.getWidth() - 1) && (bmp.getPixel(e.x, e.y) == targetColor)) {
        bmp.setPixel(e.x, e.y, replacementColor);
        if ((e.y > 0) && (bmp.getPixel(e.x, e.y - 1) == targetColor))
            q.add(new Point(e.x, e.y - 1));
        if ((e.y < bmp.getHeight() - 1) && (bmp.getPixel(e.x, e.y + 1) == targetColor))
            q.add(new Point(e.x, e.y + 1));

        e.x++;
    }
  }
}

I know how to draw lines on the screen following user's finger on touch event, but I'd also like to know how to fill a given image with some color, for example this one:

A little lion!

I saw these other questions on stack overflow:

It seems so easy to do, but I can't! Can you show me a little example please? I'd like to know how to set the canvas, the image to color, and how to do it.

Community
  • 1
  • 1
starscream
  • 741
  • 2
  • 11
  • 23
  • Please don't post the solution in the question. If it's different from other answers, post it as an answer and accept it. – thegrinner Jun 06 '13 at 19:24
  • @thegrinner that's not an answer he has some trouble so i asked him to post the code to check – Raghunandan Jun 06 '13 at 19:24
  • @Raghunandan Ah, that's unexpected. I'm hesitant to re-rollback myself, but if it is relevant he should add it back in with a different identification (ie not as "the solution"). – thegrinner Jun 06 '13 at 19:27
  • @thegrinner without the code i could not help him further so i asked him to edit the question and post the code. – Raghunandan Jun 06 '13 at 19:30
  • @Raghunandan Makes sense. What tripped me up is the part identifying it as a solution. If you rollback/repost it, I'd avoid that bit so no one else gets confused. – thegrinner Jun 06 '13 at 19:32
  • @user2349361 if you still have doubts ask a new question. way too much commenting – Raghunandan Jun 06 '13 at 19:56
  • this link is so faster : http://stackoverflow.com/a/17426163/401403 – Arash Jan 26 '14 at 17:38
  • @Arash can you please show me the full implementation of your given link QueueLinearFloodFill, I want to put color on Touch a particular area of PNG/JPG Image. I tried alot but didn't fill the area on touch. Please – Zia Ur Rahman Feb 26 '18 at 11:18

2 Answers2

19

android using flood fill algorithm getting out of memory exception. Check the link has an example.

You need the the co-ordinates of x and y touch and you can use asynctask to floofill a closed area. Use a progressdialog untill the floodfill fills the closed area with replacement color.

Note: I have faced problem when coloring large closed are. It took lot of time. I am not sure if using asynctask is the beast way. I hope someone can clarify on that part

You can modify the below according to your needs.

final Point p1 = new Point();
p1.x=(int) x; //x co-ordinate where the user touches on the screen
p1.y=(int) y; //y co-ordinate where the user touches on the screen  

FloodFill f= new FloodFill(); 
f.floodFill(bmp,pt,targetColor,replacementColor);

FloodFill algorithm to fill a closed area

    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);
    }
}
}

enter image description here

Edit:

enter image description here

Edit 8-7-2014 :

Filling a small closed area works fine with the above flood fill algorithm. However for large area the algorithm works slow and consumes lot of memory. Recently i came across a post which uses QueueLinear Flood Fill which is way faster that the above.

Source :

http://www.codeproject.com/Articles/16405/Queue-Linear-Flood-Fill-A-Fast-Flood-Fill-Algorith

Code :

public class QueueLinearFloodFiller {

    protected Bitmap image = null;
    protected int[] tolerance = new int[] { 0, 0, 0 };
    protected int width = 0;
    protected int height = 0;
    protected int[] pixels = null;
    protected int fillColor = 0;
    protected int[] startColor = new int[] { 0, 0, 0 };
    protected boolean[] pixelsChecked;
    protected Queue<FloodFillRange> ranges;

    // Construct using an image and a copy will be made to fill into,
    // Construct with BufferedImage and flood fill will write directly to
    // provided BufferedImage
    public QueueLinearFloodFiller(Bitmap img) {
        copyImage(img);
    }

    public QueueLinearFloodFiller(Bitmap img, int targetColor, int newColor) {
        useImage(img);

        setFillColor(newColor);
        setTargetColor(targetColor);
    }

    public void setTargetColor(int targetColor) {
        startColor[0] = Color.red(targetColor);
        startColor[1] = Color.green(targetColor);
        startColor[2] = Color.blue(targetColor);
    }

    public int getFillColor() {
        return fillColor;
    }

    public void setFillColor(int value) {
        fillColor = value;
    }

    public int[] getTolerance() {
        return tolerance;
    }

    public void setTolerance(int[] value) {
        tolerance = value;
    }

    public void setTolerance(int value) {
        tolerance = new int[] { value, value, value };
    }

    public Bitmap getImage() {
        return image;
    }

    public void copyImage(Bitmap img) {
        // Copy data from provided Image to a BufferedImage to write flood fill
        // to, use getImage to retrieve
        // cache data in member variables to decrease overhead of property calls
        width = img.getWidth();
        height = img.getHeight();

        image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(image);
        canvas.drawBitmap(img, 0, 0, null);

        pixels = new int[width * height];

        image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
    }

    public void useImage(Bitmap img) {
        // Use a pre-existing provided BufferedImage and write directly to it
        // cache data in member variables to decrease overhead of property calls
        width = img.getWidth();
        height = img.getHeight();
        image = img;

        pixels = new int[width * height];

        image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
    }

    protected void prepare() {
        // Called before starting flood-fill
        pixelsChecked = new boolean[pixels.length];
        ranges = new LinkedList<FloodFillRange>();
    }

    // Fills the specified point on the bitmap with the currently selected fill
    // color.
    // int x, int y: The starting coords for the fill
    public void floodFill(int x, int y) {
        // Setup
        prepare();

        if (startColor[0] == 0) {
            // ***Get starting color.
            int startPixel = pixels[(width * y) + x];
            startColor[0] = (startPixel >> 16) & 0xff;
            startColor[1] = (startPixel >> 8) & 0xff;
            startColor[2] = startPixel & 0xff;
        }

        // ***Do first call to floodfill.
        LinearFill(x, y);

        // ***Call floodfill routine while floodfill ranges still exist on the
        // queue
        FloodFillRange range;

        while (ranges.size() > 0) {
            // **Get Next Range Off the Queue
            range = ranges.remove();

            // **Check Above and Below Each Pixel in the Floodfill Range
            int downPxIdx = (width * (range.Y + 1)) + range.startX;
            int upPxIdx = (width * (range.Y - 1)) + range.startX;
            int upY = range.Y - 1;// so we can pass the y coord by ref
            int downY = range.Y + 1;

            for (int i = range.startX; i <= range.endX; i++) {
                // *Start Fill Upwards
                // if we're not above the top of the bitmap and the pixel above
                // this one is within the color tolerance
                if (range.Y > 0 && (!pixelsChecked[upPxIdx])
                        && CheckPixel(upPxIdx))
                    LinearFill(i, upY);

                // *Start Fill Downwards
                // if we're not below the bottom of the bitmap and the pixel
                // below this one is within the color tolerance
                if (range.Y < (height - 1) && (!pixelsChecked[downPxIdx])
                        && CheckPixel(downPxIdx))
                    LinearFill(i, downY);

                downPxIdx++;
                upPxIdx++;
            }
        }

        image.setPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
    }

    // Finds the furthermost left and right boundaries of the fill area
    // on a given y coordinate, starting from a given x coordinate, filling as
    // it goes.
    // Adds the resulting horizontal range to the queue of floodfill ranges,
    // to be processed in the main loop.

    // int x, int y: The starting coords
    protected void LinearFill(int x, int y) {
        // ***Find Left Edge of Color Area
        int lFillLoc = x; // the location to check/fill on the left
        int pxIdx = (width * y) + x;

        while (true) {
            // **fill with the color
            pixels[pxIdx] = fillColor;

            // **indicate that this pixel has already been checked and filled
            pixelsChecked[pxIdx] = true;

            // **de-increment
            lFillLoc--; // de-increment counter
            pxIdx--; // de-increment pixel index

            // **exit loop if we're at edge of bitmap or color area
            if (lFillLoc < 0 || (pixelsChecked[pxIdx]) || !CheckPixel(pxIdx)) {
                break;
            }
        }

        lFillLoc++;

        // ***Find Right Edge of Color Area
        int rFillLoc = x; // the location to check/fill on the left

        pxIdx = (width * y) + x;

        while (true) {
            // **fill with the color
            pixels[pxIdx] = fillColor;

            // **indicate that this pixel has already been checked and filled
            pixelsChecked[pxIdx] = true;

            // **increment
            rFillLoc++; // increment counter
            pxIdx++; // increment pixel index

            // **exit loop if we're at edge of bitmap or color area
            if (rFillLoc >= width || pixelsChecked[pxIdx] || !CheckPixel(pxIdx)) {
                break;
            }
        }

        rFillLoc--;

        // add range to queue
        FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);

        ranges.offer(r);
    }

    // Sees if a pixel is within the color tolerance range.
    protected boolean CheckPixel(int px) {
        int red = (pixels[px] >>> 16) & 0xff;
        int green = (pixels[px] >>> 8) & 0xff;
        int blue = pixels[px] & 0xff;

        return (red >= (startColor[0] - tolerance[0])
                && red <= (startColor[0] + tolerance[0])
                && green >= (startColor[1] - tolerance[1])
                && green <= (startColor[1] + tolerance[1])
                && blue >= (startColor[2] - tolerance[2]) && blue <= (startColor[2] + tolerance[2]));
    }

    // Represents a linear range to be filled and branched from.
    protected class FloodFillRange {
        public int startX;
        public int endX;
        public int Y;

        public FloodFillRange(int startX, int endX, int y) {
            this.startX = startX;
            this.endX = endX;
            this.Y = y;
        }
    }
}
Community
  • 1
  • 1
Raghunandan
  • 132,755
  • 26
  • 225
  • 256
  • You're a BOSSS @Raghunandan!!! It works perfectly... Do you know how to centrate the image on the screen?! Because I execute the app and it shows me one part of it, and not the complete image! So I mean that the image to color is cut! – starscream Jun 06 '13 at 18:44
  • @user2349361 you are welcome. get the part of using asynctask clarified. I am not sure if its the best way – Raghunandan Jun 06 '13 at 18:47
  • I've tried to add an ImageView component but the code doesn't care about it and overwrites the image! – starscream Jun 06 '13 at 18:47
  • @user2349361 you can modify the above and use the same. yes it will slow if you try filling a large area. – Raghunandan Jun 06 '13 at 18:48
  • As you wrote above... it's a little slow on big areas! – starscream Jun 06 '13 at 18:48
  • The result is like this: http://www.nixweb.altervista.org/sheep.png And the problem is that isn't shown all the sheep! – starscream Jun 06 '13 at 18:52
  • @user2349361 you need to render the image on canvas properly it has nothing to do with flood fiil – Raghunandan Jun 06 '13 at 18:53
  • Have I to post it on the chat or as "Answer Your Question" ? – starscream Jun 06 '13 at 19:15
  • As you can see, the canvas is never used... I can't find any call to the function "onDraw". – starscream Jun 06 '13 at 19:21
  • @user2349361 check the edit. i tried the lion and it renders on the canvas properly. I am not sure why its not coloring properly. But sure works for me – Raghunandan Jun 06 '13 at 19:27
  • @user2349361 try it on some device and check. i don't see you problem. modify the above accordingly. with a custom draw floodfill works fine with a closed area. – Raghunandan Jun 06 '13 at 19:35
  • Maybe it dipends on image's dimensions! Now the lion is shown, not completly... but is bettere than the sheep! – starscream Jun 06 '13 at 19:35
  • I've tried to work on the views and I've seen that when I modify the on create method in this way: `setContentView(R.layout.activity_main); this.drawingLayout = (RelativeLayout) findViewById(R.id.drawingLayout); image = (ImageView) findViewById(R.id.immagine);` The app shows all the Image on the screen! Now is it possible to apply modifies to the ImageView and not as I did before!? – starscream Jun 06 '13 at 19:52
  • m bigger in android how to user this QueueLinearFloodFiller ? – Nirmal Shethwala May 12 '15 at 04:45
  • @NirmalShethwala in place of foodfill algoritm you will use `QueueLinearFloodFiller` and send the required params to the constructor. I don't understand what you mean by how to use? – Raghunandan May 12 '15 at 08:56
  • It creates a problem when i am fill black color in an image with black borders. Please help. – iMDroid May 16 '16 at 12:45
  • @iMDroid better post a question and describe your problem in detail. Posting a comment won't help. – Raghunandan May 17 '16 at 12:39
  • 1
    @NirmalShethwala, I called the QueueLinearFloodFiller and passed the parameters, but the Image is not filling with any color, and I also want to fill the color by touching the area but nothing happen. Bitmap bmp = BitmapFactory.decodeResource(this.getResources(), R.drawable.animal_08); QueueLinearFloodFiller queueFloodFiller = new QueueLinearFloodFiller(bmp, Color.RED, Color.BLUE); – Zia Ur Rahman Feb 22 '18 at 13:57
  • @starScream please tell me how did you call this function in main activity, and how did you use it in Imageview and applied OnTouch event to fill the area etc... Please explain me the implementation, I really need it. Thanks. – Zia Ur Rahman Feb 25 '18 at 19:47
  • Please anyone see and solve my this question. https://stackoverflow.com/questions/48988689/how-to-use-queuelinearfloodfiller-in-my-project – Zia Ur Rahman Feb 26 '18 at 14:00
  • How to use vector drawable in imageview? https://stackoverflow.com/q/50426729/4535930 – Bhavesh Butani May 19 '18 at 15:18
  • @BhaveshButani i have no idea. Vector drawables have group and paths. So maybe you need to check this https://medium.com/@emmaguy/dynamically-changing-svg-colours-on-android-b026a99137ad. maybe flood fill is not for this use case. then again i have no idea about this – Raghunandan May 20 '18 at 07:37
  • @Raghunandan The linear fill algorithm is filling up the whole image. Here is my code `var floodFiller = QueueLinearFloodFiller(mBitmap, sourceColor, targetColor ) floodFiller.floodFill(point1.x,point1.y)`. – Shafayat Mamun Jul 16 '21 at 17:18
  • Plaese ask a new question with details. It's been a while since I have worked on this. May be someone else can answer – Raghunandan Jul 17 '21 at 00:31
6

Thanks to the stackoverflow's users I've gotten to the right solution!

I wanted to know how to use the flood fill algorithm and integrate it in a simple Android project, and this is what I did:

Java code:

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.BitmapFactory.Options;
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);

    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);
    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);
        }
    }
}
}

And this is the XML code:

<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"
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" >

</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>

I hope it to be helpfull for you!!!

Have a nice day!!!

starscream
  • 741
  • 2
  • 11
  • 23
  • what does this forme stands for in this statement in java source code.. mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.forme).copy(Bitmap.Config.ARGB_8888, true); – N.R Jun 20 '17 at 05:16
  • @starScream, This is really great efforts. Please add the Zooming feature in this code, so that to zoom the image by pinching and panning. I am waiting. Hope you help me in this regard. Thanks – Zia Ur Rahman Feb 26 '18 at 17:36