Jawascript
Jawascript

Reputation: 703

Clear paint from canvas without clearing background image - Android

I have a canvas with a background image. I need to know if it's possible to clear the paint from this canvas for redraw without clearing its background image. Here is my example and my results so far.

JAVA

public void setCanvas() {
    if(mFile != null && mFile.exists()) {
        mPictureBitmap = BitmapFactory.decodeFile(mFile.getAbsolutePath());
        mBitmap = Bitmap.createScaledBitmap(mPictureBitmap, mImageView.getWidth(), mImageView.getHeight(), false);
        mBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, true);
        mCanvas = new Canvas(mBitmap);

        mImageView.setImageBitmap(mBitmap);
        mImageView.setOnTouchListener(this);
    }
}

private void draw() {
    mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); // THIS LINE CLEARS

    int lastIndex = mCordHashMap.size() - 1;
    int index = 0;

    for (LinkedHashMap.Entry<String, ArrayList<Circle>> entry : mCordHashMap.entrySet()) {
        ArrayList<Circle> coords = new ArrayList<Circle>();
        coords = entry.getValue();
        Path path = new Path();
        String key = entry.getKey();
        String surface = getSurfaceFromKey(key);
        changePaint(surface);

        if (coords.size() < 3) {
            for (Circle c : coords) {
                mCanvas.drawCircle(c.getmX(), c.getmY(), RADIUS, mCirclePaint);
            }
        } else {
            for (Circle c : coords) {
                if (c == coords.get(0)) {
                    path.moveTo(c.getmX(), c.getmY());
                } else {
                    path.lineTo(c.getmX(), c.getmY());
                }
            }

            path.close();
            mCanvas.drawPath(path, mPaint);
            mCanvas.drawPath(path, mStrokePaint);

        }

        if (index == lastIndex && !mSpecificPathSelected) {
            for (Circle c : coords) {
                mCanvas.drawCircle(c.getmX(), c.getmY(), RADIUS, mCirclePaint);
                mCanvas.drawCircle(c.getmX(), c.getmY(), RADIUS, mCircleStrokePaint);
            }
        }
        mImageView.invalidate();
        index++;
    }
}

XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <ExpandableListView
        android:id="@+id/surfaceList"
        android:background="@color/light_grey"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".20" />

    <ImageView
        android:id="@+id/drawContainer"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight=".80" 
        android:contentDescription="@string/app_name"/>

</LinearLayout>

So the code above loads an image into my Canvas. Then each time I touch the Canvas it draws a circle and eventually a path between the circles. However when I start adding more circles and paths, it's redrawing over itself, which looks terrible.

Now I can clear the paint from my canvas with this line.

mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

However I also lose the Bitmap I set as it's background, and now my background is black. So, how do I keep my background image but clear my paint? Is this possible or do I have to use a work around such as two Canvas's on top of each other?

I have already tried the following, and several variations of the same.

1) mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);

2) mCanvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
   mCanvas.drawBitmap(mBitmap, 0, 0, mPaint);

3) mBitmap.eraseColor(Color.TRANSPARENT);
   mCanvas.drawBitmap(mBitmap, 0, 0, mPaint);

Any help is appreciated

Upvotes: 4

Views: 4247

Answers (3)

Jawascript
Jawascript

Reputation: 703

I finally figured it out. I was able to use two ImageViews stacked in a RelativeLayout. Stack the ImageViews on top of each other and then set both ImageViews with the same picture but separate Bitmaps. The top one now acts as your foreground and your bottom acts as your background. Now you can draw on the top one, while clearing the background and never effecting the bottom one. The key is the attribute yourImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); which turns off hardware acceleration and allows your background to actually be rendered transparent. Without this attribute applied to your view you will have a black or white background that completely covers your bottom ImageView which makes it completely useless.

XML

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <ExpandableListView
        android:id="@+id/surfaceList"
        android:background="@color/light_grey"
        android:layout_width="250dp"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/background"
        android:layout_width="match_parent"
        android:layout_toRightOf="@+id/surfaceList"
        android:layout_height="match_parent"
        android:contentDescription="@string/app_name"/>

    <ImageView
        android:id="@+id/foreground"
        android:layout_width="match_parent"
        android:layout_toRightOf="@+id/surfaceList"
        android:layout_height="match_parent"
        android:contentDescription="@string/app_name"/>

</RelativeLayout>

JAVA

public void setCanvas() {
    if(mFile != null && mFile.exists()) { // check for null
        mFirstBitmap = BitmapFactory.decodeFile(mFile.getAbsolutePath()); // get bitmap from directory
        mSecondBitmap = Bitmap.createScaledBitmap(mPictureBitmap, mImageView.getWidth(), // size bitmap mImageView.getHeight(), false);
        mSecondBitmap = mSecondBitmap .copy(Bitmap.Config.ARGB_8888, true); // make bitmap mutable so it can be applied to the canvas
        mFirstBitmap = mSecondBitmap .copy(Bitmap.Config.ARGB_8888, true); // make second bitmap from copy, also mutable
        mCanvas = new Canvas(mSecondBitmap ); //create your canvas from 2nd bitmap
        mImageView.setImageBitmap(mSecondBitmap ); // set this imageview to 2nd bitmap
        mImageView2.setImageBitmap(mFirstBitmap); //set this imageview to 1st bitmap

        mImageView.setOnTouchListener(this); // set on touch listener
        mImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); // IMPORTANT set hardware acceleration off with this line. Otherwise your view's background will NOT be transparent
        mImageView.bringToFront(); // bring this imageview to the front, making it your foreground
    }
}

Hopefully this saves someone else a ton of time.

Upvotes: 2

user3482211
user3482211

Reputation: 466

Just if you aren't using imageView's there's another, even simplier solution:

I've got myself a new Bitmap(mBackgroundBitmap); new Canvas(mBackgorundCanvas), asigned mBackgroundBitmap to mBackgroundCanvas, and on every onDraw method firstly i dar my background bitmap:

canvas.drawBitmap(mBackgroundBitmap, offSetX, offSetY, mBitmapPaint);

and just then I start drawing my actual bitmap:

canvas.drawBitmap(mBitmap, offSetX, offSetY, mBitmapPaint);

I guess it is not memory-efficient solution (I mean having two bitmaps isnt very cool or pleasant, but then many of us doesnt use A big images, but instead (at least me) create my background from a fragment and then Tile it in X and Y directions.

If my answer is ridiculous don't judge me, I am just a rookie.

Upvotes: 1

Coeffect
Coeffect

Reputation: 8866

As far as I know, Canvas doesn't include layers. So you can't remove one layer (paint) while leaving the other (background). I believe you should be using either two canvases, or even the paint canvas and an ImageView to hold the background. Then when you want to export the image combine the canvas contents with the background.

Upvotes: 3

Related Questions