I am creating an drawing application using canvas. I am using below code to flood fill but on low or medium dpi devices, some portion of image remains unfilled i.e. at edges of an non-uniform shape. Some dots remains unfilled at non uniform edges.
public class FloodFill extends AsyncTask<Void, Void, Void>
{
private int width;
private int height;
private int target;
private int replacement;
private Point node;
private ProgressDialog progress;
public FloodFill(Bitmap image, Point node2, int targetColor, int replacementColor)
{
flood = Bitmap.createBitmap(image);
image.recycle();
this.width = flood.getWidth();
this.height = flood.getHeight();
this.target = targetColor;
this.replacement = replacementColor;
this.node = node2;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
progress = new ProgressDialog(MainActivity.this);
progress.setMessage("Filling");
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.setIndeterminate(true);
progress.setCancelable(false);
progress.show();
}
#Override
protected Void doInBackground(Void... params) {
if (target != replacement)
{
Queue<Point> queue = new LinkedList<Point>();
do
{
int x = node.x;
int y = node.y;
while (x > 0 && flood.getPixel(x - 1, y) == target)
{
x--;
}
boolean spanUp = false;
boolean spanDown = false;
while (x < width && flood.getPixel(x, y) == target)
{
flood.setPixel(x, y, replacement);
if (!spanUp && y > 0 && flood.getPixel(x, y - 1) == target)
{
queue.add(new Point(x, y - 1));
spanUp = true;
}
else if (spanUp && y > 0 && flood.getPixel(x, y - 1) != target)
{
spanUp = false;
}
if (!spanDown && y < height - 1 && flood.getPixel(x, y + 1) == target)
{
queue.add(new Point(x, y + 1));
spanDown = true;
}
else if (spanDown && y < height - 1 && flood.getPixel(x, y + 1) != target)
{
spanDown = false;
}
x++;
}
} while ((node = queue.poll()) != null);
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
progress.dismiss();
invalidate();
}
}
I need help regarding this, whether I should try to implement another algorithm, if yes, then which one or I should try to implement anti-aliasing on image after flood fill.
Thanks in advance.
Related
Im working with my project with paiting/coloring features.Can anyone explain this flood fill algorithm and how this works? Is this flood fill queue? and what does queue mean(in this algorithm)?
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);
}
}
}
There is not a specific name for this algorithm, BUT when filling large areas, I would recommend QueueLinear Flood Fill Algorith which is way faster than the above.
Here is a link to implementation:
http://www.codeproject.com/Articles/16405/Queue-Linear-Flood-Fill-A-Fast-Flood-Fill-Algorith
i just try to use FloodFill class and i found strange problem with coloring.
Lets start with code:
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 method where i use FloodFill:
public void colorize()
{
bmp = ((BitmapDrawable)view.getDrawable()).getBitmap();
view.setOnTouchListener(new ImageView.OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
int x = (int)event.getX();
int y = (int)event.getY();
...
flood.floodFill(bmp, new Point(x, y), bmp.getPixel(x, y), color);
view.setImageBitmap(bmp);
...
}
});
}
If i try to use standard android color f.g: Color.RED and Color.GREEN, everything works fine. I can replace f.g red with green its works, but if i try to use custom color like this: f.g. Color.rgb(34, 198, 67) i get single point colored instead of filled shape.
Can You help me to find a solution for this problem?
Edit1:
I spoted something interesting. Custom colors seems to be different values on some pixels but i dont know why if i using flood-fill.
Problem solved. Bitmap where i used floodfill was RGB_565. I just convert it to ARGB_8888 and everything work fine.
In my app i used flood fill for filling the part of the bitmap with color. Everything worked fine, but I create the bitmap with createscaledbitmap , it does not work. Please suggest me.
my code is as follows.
I had floodfill run in asynch task.
#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 = resultBmp.getPixel((int) x, (int) y);
final int targetColor = paint.getColor();
new TheTask(resultBmp, p1, sourceColor, targetColor).execute();
invalidate();
}
return true;
}
public void clear() {
path.reset();
invalidate();
}
public int getCurrentPaintColor() {
return paint.getColor();
}
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();
}
}
}
my floodfill class
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);
}
}
}
my ondraw method is
#Override
protected void onDraw(Canvas canvas) {
this.canvas = canvas;
int w = mBitmap.getWidth();
int h = mBitmap.getHeight();
resultBmp = null;
int widthofBitMap = screenWidth;
int heightofBitMap = widthofBitMap * h / w;
resultBmp = Bitmap.createScaledBitmap(mBitmap, widthofBitMap, heightofBitMap, true);
canvas.drawBitmap(resultBmp, 0, 0, paint);
//if i create only bitmap without scaling it works fine.
// for ex
//canvas.drawBitmap(mBitmap,0,0,paint); works fine.
}
please suggest me
The flood fill method is taking every pixel and changing its color in thread, So instead of increasing the size using create scaled bitmap, I am using separate bitmaps for different screen sizes. Its a cumbersome process but there is no other way according to my research.
after your suggestions i got working code:
public class FingerPaint extends Activity {
private RelativeLayout drawingLayout;
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(myView);
drawingLayout.addView(myView);
}
public class MyView extends View {
private Paint paint;
private Path path;
Bitmap mBitmap;
ProgressDialog pd;
final Point p1 = new Point();
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.paint).copy(Bitmap.Config.ARGB_8888, true);
this.path = new Path();
}
#Override
protected void onDraw(Canvas canvas) {
this.canvas = canvas;
this.paint.setColor(Color.GREEN);
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();
}
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);
}
}
}
}
Now it is working fine.ThanQ
Use Async Task. Running every operation on Main Ui thread may cause's out of memory exception. My suggestion , use threads. Do Floodfill in background. Check this link. May help You.
Fill the complete canvas but keep the bound fill area as it is like circle, rectangle
private Paint paint;
private Path path;
Bitmap mBitmap;
ProgressDialog pd;
final Point p1 = new Point();
Canvas canvas;
private static final float TOUCH_TOLERANCE = 4;
float mX,mY;
public DrawingView(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.rose_sketch);
this.path = new Path();
}
#Override
protected void onDraw(Canvas canvas) {
this.canvas=canvas;
this.paint.setColor(Color.GREEN);
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:
//final Point p1 = new Point();
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();
}
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();
}
}
}
USE FLOODFILL NOW.
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);
}
}
}
Edit:
One of the users commented that the solution # Android flood-fill algorithm works faster than the solution posted here. So take look at the solution in the link although i haven't tested it myself.
I have seen this problem with my code whenver there is some really complex computations going on in the UI thread. If you have implemented flood fill in the UI thread, the computations are causing 99% CPU usage due to which other applications and services are not getting thier share of the CPU. As a result, android will try to kill your app inorder to restore the integrity of the System. As a simple solution, try to offload your computations to a AsyncTask or a thread.
I think you have to swich off the multitoutch of your screen because of that it went to out of memory....
How to implement the Flood-fill algorithm in android.But the code was written in c language.could we implement the algorithm in android.is there any open source code available or any website tutorial link
Algorithm for flood fill is a very simple recursive one.
//Initialize i and j to the place to start
floodFill(int arr[][], target_color, replace_color)
{
if(arr[i][j] == replace_color)
return;
replace(target_color, replace_color);
floodFill(int[i+1][j], target_color, replace_color);
floodFill(int[i][j+1], target_color, replace_color);
floodFill(int[i-1][j], target_color, replace_color);
floodFill(int[i][j-1], target_color, replace_color);
}
Flood Fill using queue. Use a asynctask to flood fill.
Parameters
bitamp to be filled
point where the user touches (x,y cordinates)
Color of pixel where use touches
Color to be replaced.
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);
}
}
}