Android Canvas and Hardware Acceleration? - android

Hi all I was wondering if it was possible to draw to an offscreen Canvas / Bitmap and take advantage of hardware acceleration or do I have to draw inside the onDraw() method of the View
For example I draw to an offscreen Bitmap by doing the following:
Bitmap.Config config = Bitmap.Config.ARGB_8888;
Bitmap buffer = Bitmap.createBitmap(200, 200, config);
Canvas canvas = new Canvas(buffer);
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawLine(0, 0, 100, 100, paint);
However canvas.isHardwareAccelerated() returns false and drawing is sluggish compared to:
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.RED);
canvas.drawLine(0, 0, 100, 100, paint);
}
where canvas.isHardwareAccelerated() returns true. Is there a way to draw to a Bitmap while taking advantage of hardware acceleration? Or do I have to draw directly to the screen in the onDraw method?
Thank you for your help :) I know in Java I can draw to a BufferedImage offscreen and it'll be hardware accelerated but maybe its not the same on a phone...

Because in first case , you create the canvas, it is not backed up by a hardware layer.
Also, as of now, you cannot enable HWA on just any Canvas, it must belong to an HWA View.
A view on the other hand, has access to system's hardware capabilities. You can use that by calling setLayerType( LAYER_TYPE_HARDWARE) as described in docs:
Indicates that the view has a hardware layer. A hardware layer is
backed by a hardware specific texture (generally Frame Buffer Objects
or FBO on OpenGL hardware) and causes the view to be rendered using
Android's hardware rendering pipeline, but only if hardware
acceleration is turned on for the view hierarchy. When hardware
acceleration is turned off, hardware layers behave exactly as software
layers.
Also, The performance concern applies more to the rendering part, and less to drawing. All draw operations are recorded as a Picture object. Its the rendering operation where HWA plays an important role.

Related

Achieve setShadowLayer effect during drawCircle (or drawRoundRect) without turning off hardware acceleration

Currently, I have an app which support Android 2.3 and above.
In my custom view drawing operation, I need to drop shadow while drawing circle.
ballPaint.setShadowLayer(shadowSize, shadowSize, shadowSize, historyChartBallShadow);
canvas.drawCircle(px, py, this.ballSize, ballPaint);
I also understand that, with hardware acceleration turned on, I will not such shadow effect
setShadowLayer Android API differences
However, I realize once hardware acceleration is turned off through view.setLayerType(View.LAYER_TYPE_SOFTWARE, null), my entire custom view drawing operation become very slow.
I was wondering, is there any other way to achieve similar shadow effect. (A "blurred" black circle shadow)
without turning off hardware acceleration?
p/s Even when I want to use BlurMaskFilter from Android draw with blur, I realize it doesn't support hardware acceleration too.
I had found a way to achieve such. First, we construct the ball + shadow off-screen image. Note, by using off-screen bitmap drawing technique, no GPU will be involved. The key is, don't use the Canvas from onDraw to perform drawCircle.
Instead, construct our very own Canvas, backed by an off-screen bitmap.
private Bitmap generateBallBitmap() {
final int ballShadowSize = Utils.dpToPixel(BALL_SHADOW_SIZE);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(historyChartBallColor);
paint.setShadowLayer(ballShadowSize, ballShadowSize, ballShadowSize, historyChartBallShadow);
Bitmap bitmap = Bitmap.createBitmap((int)((ballSize + ballShadowSize)*2f), (int)((ballSize + ballShadowSize)*2f), Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(ballSize, ballSize, ballSize, paint);
return bitmap;
}
In custom view onDraw function, just draw the off-screen bitmap directly.
if (null == this.ballBitmap) {
this.ballBitmap = generateBallBitmap();
}
canvas.drawBitmap(this.ballBitmap, px - this.ballSize, py, null);
For the entire process, I merely depend on the default value of layer type, without calling setLayerType explicitly.
The outcome is fast, yet shadow effect is visible too.

save hardware accelerated android canvas as bitmap

I am currently drawing directly on the Canvas object provided to my view's onDraw method. This canvas is hardware accelerated. In particular, I can draw multiple circles using RadialGradient shaders with the OVERLAY PorterDuff mode. However, when I try to apply the same draw procedure to a canvas which I manually create, I do not get the same results. I believe this is because the canvas object created for the bitmap is NOT hardware accelerated.
The code looks something like this for the view:
public class MyView extends View {
private Paint mPaint;
private List<Shader> mShaders;
public MyView(Context context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.OVERLAY));
mShaders = new ArrayList<Shader>(); // assume x, y, r, and color vals are defined.
mShaders.add(new RadialGradient(x1, y1, r1, c11, c12, Shader.TileMode.CLAMP));
mShaders.add(new RadialGradient(x2, y2, r2, c21, c22, Shader.TileMode.CLAMP));
}
#Override
protected void onDraw(Canvas canvas) {
mPaint.setShader(mShaders.get(0));
canvas.drawCircle(x1, y1, r1, mPaint);
mPaint.setShader(mShaders.get(1));
canvas.drawCircle(x2, y2, r2, mPaint);
}
}
Up to this point, everything is well and good. The circles are drawn as expected on the hardware accelerated canvas.
However, if I do this:
...
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
draw(canvas);
or some variation of this:
...
setDrawingCacheEnabled(true);
buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(getDrawingCache());
destroyDrawingCache();
setDrawingCacheEnabled(false);
or if I try drawing with the very same shaders and paint flags on, say, the non-hardware accelerated canvas provided by the WallpaperService Engine, the results are just not the same as they were when I did the very same thing on the hardware accelerated canvas object of the view.
Here is a screenshot showing what happens when the circles are drawn on an accelerated view canvas.
https://www.dropbox.com/s/vgvo3l6wz2d1b1c/good.png?dl=0
Here is what happens when drawn upon a manually created canvas, not associated with a view.
https://www.dropbox.com/s/zuo23z2y7ik2wmg/bad.png?dl=0
While I understand that there are significant differences to expect when drawing with hardware acceleration or not, it seems like there is no way to capture even the rendered appearance of the accelerated canvas without having to re-render it in software via the drawing cache, which doesn't work. How do I capture the rendered view as-is?
Shortly after writing this, I noticed that simply commenting out the line that sets the OVERLAY Xfermode eliminates the difference between drawing on an accelerated or non-accelerated canvas, but this is not a solution. Removing the OVERLAY mode produces a completely different effect than what I am trying to capture.

Can we hardware accelerate an off-screen canvas?

I'm creating an off-screen bitmap+canvas, drawing a bunch of smaller bitmaps into it, then drawing it into a view. The isHardwareAccelerated() method returns false for my canvas:
mBitmap = new Bitmap(500, 500, Bitmap.Config.RGB_565);
mCanvas = new Canvas(mBitmap);
mCanvas.isHardwareAccelerated(); // false
I can see that the canvas given to me in my View's onDraw() method is hardware accelerated though:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.isHardwareAccelerated(); // true
// My current approach, but maybe better to draw directly
// to the view's canvas if it's hardware accelerated?
canvas.drawBitmap(mBitmap, ...);
}
I'm wondering if we can turn this on for the off-screen canvas?
I'm also wondering if I should just draw directly to the view's hardware-accelerated canvas instead of bothering with the off-screen one, if it's not going to be accelerated. I thought it would be faster drawing everything to the offscreen one first.
I have to reorganize my code to test this out, just wondering if I'm missing something obvious for the off-screen one.
Thanks
------- Update -----------------------------------
I refactored my draw code to just draw directly to the view's canvas in onDraw(), instead of using the off-screen canvas. Performance is way better.
It would still be nice to enable hardware acceleration for the off-screen canvas though, there are definitely tons of use-cases for that.

Android clipPath equivalent for hardware accelerated View

The only part of my app that is still software rendered is the rendering of one view where I need to draw a round bitmap. I'm using clipPath to clip the bitmap that I need to render to the round shape.
I understand that clipPath is not hardware accelerated, but I am looking for an algorithm that would allow me to provide the equivalent functionality using hardware acceleration.
Specifically I need to create a round clipping region of a source bitmap and have that rendered to my canvas.
If you have to use clipPath you could shut down the hardware acceleration as below.
<application
android:label="#string/application_name"
android:hardwareAccelerated="false">
And you also could control hardware acceleration among Application, Activity, Window and View layers. Detail information describes on this Hardware Acceleration article on Android Development web site.
You could try to this, though I am not sure it is hardware accelerated :
in onCreate :
create a paint (called bitmapPaint) that uses setXfermode :
http://developer.android.com/reference/android/graphics/Paint.html#setXfermode(android.graphics.Xfermode)
put an AvoidXfermode, also its deprecated, it work pretty well. Pass it the white color and target mode with a high tolerance (like 240)
in onLayout :
create a bitmap of the same size as your view
draw your circle inside on of its canvas, in white, using anti alias for a clean border
in onDraw :
draw the bitmap with the white circle in your paint canvas
now, draw your bitmap inside your paint canvas using the bitmapPaint you created in onCreate
The bitmap should be rendered inside the circle only.
If your bitmap does not change a lot, clip it once to the shape into a new bitmap, and then draw the clipped bitmap in your onDraw.
Here is an example how to clip a circle from a source bitmap
Bitmap bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Canvas canvas = new Canvas(bitmap);
paint.setColor(Color.RED);
// Draw your shape here
canvas.drawCircle(cx, cy, radius, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(sourceBitmap, 0, 0, paint);

How to blit() in android?

I'm used to handle graphics with old-school libraries (allegro, GD, pygame), where if I want to copy a part of a bitmap into another... I just use blit.
I'm trying to figure out how to do that in android, and I got very confused.
So... we have these Canvas that are write-only, and Bitmaps that are read-only? It seems too stupid to be real, there must be something I'm missing, but I really can't figure it out.
edit: to be more precise... if bitmaps are read only, and canvas are write only, I can't blit A into B, and then B into C?
The code to copy one bitmap into another is like this:
Rect src = new Rect(0, 0, 50, 50);
Rect dst = new Rect(50, 50, 200, 200);
canvas.drawBitmap(originalBitmap, src, dst, null);
That specifies that you want to copy the top left corner (50x50) of a bitmap, and then stretch that into a 150x150 Bitmap and write it 50px offset from the top left corner of your canvas.
You can trigger drawing via invalidate() but I recommend using a SurfaceView if you're doing animation. The problem with invalidate is that it only draws once the thread goes idle, so you can't use it in a loop - it would only draw the last frame. Here are some links to other questions I've answered about graphics, they might be of use to explain what I mean.
How to draw a rectangle (empty or filled, and a few other options)
How to create a custom SurfaceView for animation
Links to the code for an app with randomly bouncing balls on the screen, also including touch control
Some more info about SurfaceView versus Invalidate()
Some difficulties with manually rotating things
In response to the comments, here is more information:
If you get the Canvas from a SurfaceHolder.lockCanvas() then I don't think you can copy the residual data that was in it into a Bitmap. But that's not what that control is for - you only use than when you've sorted everything out and you're ready to draw.
What you want to do is create a canvas that draws into a bitmap using
Canvas canvas = new Canvas(yourBitmap)
You can then do whatever transformations and drawing ops you want. yourBitmap will contain all the newest information. Then you use the surface holder like so:
Canvas someOtherCanvas = surfaceHolder.lockCanvas()
someOtherCanvas.drawBitmap(yourBitmap, ....)
That way you've always got yourBitmap which has whatever information in it you're trying to preserve.
In android you draw to the canvas, and when you want it to update you call invalidate which will the redraw this canvas to the screen. So I'm guessing you have overridden the onDraw method of your view so just add invalidate();
#Override
public void onDraw(Canvas canvas) {
// Draw a bitmap to the canvas at 0,0
canvas.drawBitmap(mBitmap, 0, 0, null);
// Add in your drawing functions here
super.onDraw(canvas);
// Call invalidate to draw to screen
invalidate();
}
The above code simply redraws the bitmap constantly, of course you want to add in extra thing to draw and consider using a timing function that calls invalidate so that it is not constantly running. I'd advice having a look at the lunarlander sources.

Categories

Resources