• 6
name

A PHP Error was encountered

Severity: Notice

Message: Undefined index: userid

Filename: views/question.php

Line Number: 191

Backtrace:

File: /home/prodcxja/public_html/questions/application/views/question.php
Line: 191
Function: _error_handler

File: /home/prodcxja/public_html/questions/application/controllers/Questions.php
Line: 433
Function: view

File: /home/prodcxja/public_html/questions/index.php
Line: 315
Function: require_once

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.

      • 2
    • I don't think so -- if you just make the b and c say in onSizeChanged and not in onDraw then you end up drawing multiple times on the same canvas without clearing (so you'd end up with multiple layers). Actually it would work if I could clear b in onDraw which may well be possible -- I'll try it, thanks!
    • @Jan: Thanks this did work -- I created the Bitmap b and Canvas c in onSizeChanged, then I just needed to call b.eraseColor(Color.Transparent) before doing any drawing to erase any previous drawing on c. Post your comment as an answer and I'll mark it correct if I don't get any more useful answers in the next day or so. It still not as smooth as method 3, but much better than 2.
      • 1
    • @Jan Not if the color drawn with c.drawColor is not completely opaque, which is more probable than not given the use case of this view ;) Method 4 my question is based on your answer.

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);
  • 18
Reply Report
      • 1
    • It's a bit late, however I do have one question about that. I want to do that exact same thing (hole in rectangle), however my problem is that my view needs to take up the whole screen, therefore the bitmap created will have the size of the screen which will be about 4 MBs - 10 MBs depending on the size of the screen. Creating such large bitmap might cause an OutOfMemoryError. Is there any workaround about that?
      • 1
    • @MScott I can't imagine that 10 MB would be any challenge for any today's smartphone to allocate. Don't forget that the GPU has to hold this amount of memory anyways just so that it can feed it to the LCD. Twice, most likely - one copy to display to the user, the other to be updated by software in the meantime. You could fiddle with painting four rectangles, one for each side, but it won't work that well with antialiasing if they are semi-transparent and it can only produce rectangular holes.