Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Punch a hole in a rectangle overlay with HW acceleration enabled on View

I have a view which does some basic drawing. After this I want to draw a rectangle with a hole punched in so that only a region of the previous drawing is visible. And I'd like to do this with hardware acceleration enabled for my view for best performance.

Currently I have two methods that work, but only works when disabled hardware acceleration and the other is too slow.

Method 1: SW Acceleration (Slow)

final int saveCount = canvas.save();  // Clip out a circle. circle.reset(); circle.addCircle(cx, cy, radius, Path.Direction.CW); circle.close(); canvas.clipPath(circle, Region.Op.DIFFERENCE);  // Draw the rectangle color. canvas.drawColor(backColor);  canvas.restoreToCount(saveCount); 

This does not work with hardware acceleration enabled for the view because 'canvas.clipPath' is not supported in this mode (I know i can force SW rendering, but I'd like to avoid that).

Method 2: HW Acceleration (V. Slow)

// Create a new canvas. final Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); final Canvas c = new Canvas(b);  // Draw the rectangle colour. c.drawColor(backColor);  // Erase a circle. c.drawCircle(cx, cy, radius, eraser);  // Draw the bitmap on our views  canvas. canvas.drawBitmap(b, 0, 0, null); 

Where eraser is created as

eraser = new Paint() eraser.setColor(0xFFFFFFFF); eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 

This is obviously slow -- a new Bitmap the size of the view is created every drawing call.

Method 3: HW Acceleration (Fast, does not work on some devices)

canvas.drawColor(backColor); canvas.drawCircle(cx, cy, radius, eraser); 

Same as the HW acceleration compatible method, but no extra canvas required. There is a major problem with this though -- it works with SW rendering forced, but on the HTC One X (Android 4.0.4 -- and probably some other devices) at least with HW rendering enabled it leaves the circle completely black. This is probably related to 22361.

Method 4: HW Acceleration (Acceptable, works on all devices)

As per Jan's suggestion for improving method 2, I avoided creating the bitmap in each call to onDraw, instead doing so in onSizeChanged:

if (w != oldw || h != oldh) {     b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);     c = new Canvas(b); } 

And then just used these in onDraw:

if (overlayBitmap == null) {    b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);    c = new Canvas(b); } b.eraseColor(Color.TRANSPARENT); c.drawColor(backColor); c.drawCircle(cx, cy, radius, eraser); canvas.drawBitmap(b, 0, 0, null); 

The performance is not as good as method 3, but much better than 2 and slightly better than 1.

The question

How can I achieve the same effect but do so in a manner compatible with HW acceleration (AND that works consistently on devices)? A method which increases the SW rendering performance would also be acceptable.

NB: When moving the circle around I am just invalidating a region -- not the entire canvas -- so there's no room for a performance improvement there.

like image 724
Joseph Earl Avatar asked Nov 23 '12 11:11

Joseph Earl


1 Answers

Instead of allocating a new canvas on each repaint, you should be able to allocate it once and then reuse the canvas on each repaint.

on init and on resize:

Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas c = new Canvas(b); 

on repaint:

b.eraseColor(Color.TRANSPARENT);     // needed if backColor is not opaque; thanks @JosephEarl c.drawColor(backColor); c.drawCircle(cx, cy, radius, eraser);  canvas.drawBitmap(b, 0, 0, null); 
like image 57
John Dvorak Avatar answered Sep 17 '22 15:09

John Dvorak