When I implement my flood-fill
class it turns my entire Bitmap
black. Obviously this is not the desired effect. I've looked at the following threads:
- https://stackoverflow.com/questions/24030858/flood-fill-is-coloring-my-entire-screen
- Flood Fill Algorithm Resulting in Black Image
- flood fill coloring on android
From what I can see I'm doing everything they've come up with in those solutions, however it hasn't led me to a solution for my problem. So to cut to the chase, here's the code with some brief explanations.
XML
I am using a relative layout and positioning (stacking) two ImageViews
directly on top of each other. They both have the same image and this creates the illusion of you being able to draw on the image. However, you are in fact simply drawing on a transparent overlay.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
....
<ImageView
android:id="@+id/drawContainer2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/imageMapperSurfaces"
android:contentDescription="@string/image" />
<ImageView
android:id="@+id/drawContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/imageMapperSurfaces"
android:contentDescription="@string/image" />
...
</RelativeLayout>
Canvas
Then I create my Canvas
with this code and I make sure to set my layer types correctly.
public void setCanvas() {
if(mFile != null && mFile.exists()) {
mPictureBitmap = BitmapFactory.decodeFile(mFile.getAbsolutePath());
mBitmap = Bitmap.createScaledBitmap(mPictureBitmap, mImageView.getWidth(), mImageView.getHeight(), false);
mPictureBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);
mBitmap = mPictureBitmap.copy(Bitmap.Config.ARGB_8888, true);
mSceneBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);
mBlurBitmap = blurImage(mPictureBitmap);
mCanvas = new Canvas(mBitmap);
mImageView.setImageBitmap(mBitmap);
mImageView2.setImageBitmap(mPictureBitmap);
mBlur.setImageBitmap(mBlurBitmap);
// failure to set these layer types correctly will result in a black canvas after drawing.
mImageView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mImageView2.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mImageView.bringToFront();
mAllowedToDraw = true;
setImageViewOnTouch();
}
}
Flood-Fill Implementation
I grab the color, pass my params to the flood-fill
object, use the flood-fill
method, return the bitmap
, and finally draw the new bitmap
to my canvas
.
int targetColor = mSceneBitmap.getPixel((int) event.getX(), (int) event.getY());
FloodFill fill = new FloodFill(mBitmap, targetColor, Color.argb(100, 255, 0, 0));
fill.floodFill((int) event.getX(), (int) event.getY());
Bitmap bmp = fill.getImage();
mCanvas.drawBitmap(bmp, 0, 0, null);
mImageView.invalidate();
Flood-Fill Class
The boiler-plate Flood-fill
algorithm.
public class FloodFill {
protected Bitmap mImage = null;
protected int[] mTolerance = new int[] { 0, 0, 0, 0 };
protected int mWidth = 0;
protected int mHeight = 0;
protected int[] mPixels = null;
protected int mFillColor = 0;
protected int[] mStartColor = new int[] { 0, 0, 0, 0 };
protected boolean[] mPixelsChecked;
protected Queue<FloodFillRange> mRanges;
public FloodFill(Bitmap img) {
copyImage(img);
}
public FloodFill(Bitmap img, int targetColor, int newColor) {
useImage(img);
setFillColor(newColor);
setTargetColor(targetColor);
}
public void setTargetColor(int targetColor) {
mStartColor[0] = Color.red(targetColor);
Log.v("Red", "" + mStartColor[0]);
mStartColor[1] = Color.green(targetColor);
Log.v("Green", "" + mStartColor[1]);
mStartColor[2] = Color.blue(targetColor);
Log.v("Blue", "" + mStartColor[2]);
mStartColor[3] = Color.alpha(targetColor);
Log.v("Alpha", "" + mStartColor[3]);
}
public int getFillColor() {
return mFillColor;
}
public void setFillColor(int value) {
mFillColor = value;
}
public int[] getTolerance() {
return mTolerance;
}
public void setTolerance(int[] value) {
mTolerance = value;
}
public void setTolerance(int value) {
mTolerance = new int[] { value, value, value, value };
}
public Bitmap getImage() {
return mImage;
}
public void copyImage(Bitmap img) {
mWidth = img.getWidth();
mHeight = img.getHeight();
mImage = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mImage);
canvas.drawBitmap(img, 0, 0, null);
mPixels = new int[mWidth * mHeight];
mImage.getPixels(mPixels, 0, mWidth, 0, 0, mWidth, mHeight);
}
public void useImage(Bitmap img) {
mWidth = img.getWidth();
mHeight = img.getHeight();
mImage = img;
mPixels = new int[mWidth * mHeight];
mImage.getPixels(mPixels, 0, mWidth, 0, 0, mWidth, mHeight);
}
protected void prepare() {
mPixelsChecked = new boolean[mPixels.length];
mRanges = new LinkedList<FloodFillRange>();
}
public void floodFill(int x, int y) {
// Setup
prepare();
if (mStartColor[0] == 0) {
// ***Get starting color.
int startPixel = mPixels[(mWidth * y) + x];
mStartColor[0] = (startPixel >> 16) & 0xff;
mStartColor[1] = (startPixel >> 8) & 0xff;
mStartColor[2] = startPixel & 0xff;
}
LinearFill(x, y);
FloodFillRange range;
while (mRanges.size() > 0) {
range = mRanges.remove();
int downPxIdx = (mWidth * (range.Y + 1)) + range.startX;
int upPxIdx = (mWidth * (range.Y - 1)) + range.startX;
int upY = range.Y - 1;
int downY = range.Y + 1;
for (int i = range.startX; i <= range.endX; i++) {
if (range.Y > 0 && (!mPixelsChecked[upPxIdx]) && CheckPixel(upPxIdx)) LinearFill(i, upY);
if (range.Y < (mHeight - 1) && (!mPixelsChecked[downPxIdx]) && CheckPixel(downPxIdx)) LinearFill(i, downY);
downPxIdx++;
upPxIdx++;
}
}
mImage.setPixels(mPixels, 0, mWidth, 0, 0, mWidth, mHeight);
}
protected void LinearFill(int x, int y) {
int lFillLoc = x;
int pxIdx = (mWidth * y) + x;
while (true) {
mPixels[pxIdx] = mFillColor;
mPixelsChecked[pxIdx] = true;
lFillLoc--;
pxIdx--;
if (lFillLoc < 0 || (mPixelsChecked[pxIdx]) || !CheckPixel(pxIdx)) {
break;
}
}
lFillLoc++;
int rFillLoc = x;
pxIdx = (mWidth * y) + x;
while (true) {
mPixels[pxIdx] = mFillColor;
mPixelsChecked[pxIdx] = true;
rFillLoc++;
pxIdx++;
if (rFillLoc >= mWidth || mPixelsChecked[pxIdx] || !CheckPixel(pxIdx)) {
break;
}
}
rFillLoc--;
FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);
mRanges.offer(r);
}
protected boolean CheckPixel(int px) {
int red = (mPixels[px] >>> 16) & 0xff;
int green = (mPixels[px] >>> 8) & 0xff;
int blue = mPixels[px] & 0xff;
int alpha = (Color.alpha(mPixels[px]));
return (red >= (mStartColor[0] - mTolerance[0]) && red <= (mStartColor[0] + mTolerance[0])
&& green >= (mStartColor[1] - mTolerance[1]) && green <= (mStartColor[1] + mTolerance[1])
&& blue >= (mStartColor[2] - mTolerance[2]) && blue <= (mStartColor[2] + mTolerance[2])
&& alpha >= (mStartColor[3] - mTolerance[3]) && alpha <= (mStartColor[3] + mTolerance[3]));
}
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;
}
}
}
So that's it, we should have all the pieces to the puzzle but for some reason they aren't working. I'm at a loss and any help is appreciated. Thanks!