24

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:

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!

Community
  • 1
  • 1
Jawascript
  • 683
  • 1
  • 7
  • 23

2 Answers2

0

I think you're line:

mCanvas.drawBitmap(bmp, 0, 0, null);

might need to be more like

mPaint = new Paint();    
mCanvas.drawBitmap(bmp, 0, 0, mPaint);
Cory Roy
  • 5,379
  • 2
  • 28
  • 47
0

I am not sure at everything but as far as I can tell you I would try with these solutions:

First: instead of using decodeFile I rather use decodeInputStream Second: As someone has anwsered You better use a Paint() when showing the view Third: I am going to ask why do you need that food-fill alghorithm? I think it's too laggy and It looks a little messy to use, why dont you create a new scaled bitmap or something like an opengl effect to do it? because that is the reason why there are graphics cards;

Rattata 2me
  • 37
  • 2
  • 5