4

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

Psypher
  • 10,717
  • 12
  • 59
  • 83
Jawascript
  • 683
  • 1
  • 7
  • 23

3 Answers3

3

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.

Coeffect
  • 8,772
  • 2
  • 27
  • 42
  • That makes sense. If you look closely, I am actually using an `ImageView` and setting the bitmap to its src content, however apparently that's not doing me any good. `"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."` If you have time, please post an example of either of these. – Jawascript Sep 04 '14 at 18:37
  • alright, so i've been trying to find something for a while. Do you have a link to someone succesfully using an imageview as a background and/or two canvases? – Jawascript Sep 04 '14 at 20:12
  • Sorry, at work. I was just browsing SO while waiting for a server deployment. I'll take a deeper look at this tonight. – Coeffect Sep 04 '14 at 20:21
  • thanks, its been driving me nuts for a while, literally everything i try with multiple canvases, image views, porterduff paint modes, it all results in a black canvas background that covers my imageview and blocks out the image – Jawascript Sep 04 '14 at 20:22
  • back on it again today, let me know if you find anything before I do. I'm currently researching a custom ImageView, and overriding the onDraw method, see if that helps. – Jawascript Sep 05 '14 at 14:04
  • Figured it out, see my answer. I'll give you the nod since you pointed me in the right direction. Have a nice day – Jawascript Sep 09 '14 at 14:05
2

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.

Jawascript
  • 683
  • 1
  • 7
  • 23
1

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.

user3482211
  • 446
  • 4
  • 17