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.
Related
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.
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.
Background: Using Canvas, Paint and Path objects I draw several geometries on the canvas, mostly polygons and circles. They fill most of the Android screen.
Question: With Mathematica I can 'fast-copy' Graphics using Translate ( in x and y direction ), after which the resulting image is automatically zoomed out such that all copies are visible. ( For example. Draw a square that fills the entire screen, copy it using (2,2) and four squares appear. ) The premise is that copying is a faster operation. - Is a similar operation possible on Android?
There's nothing as convenient as that, but to achieve the effect you can draw directly to a Bitmap and re-use it - scaling and translating it yourself.
public void onDraw(Canvas canvas) {
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas bmpCanvas = new Canvas(bmp);
// draw into bmpCanvas
// ...
// draw bitmap using
// public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
canvas.drawBitmap(bmp, ...);
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);
I am looking at one of the sample applications from Google, which deals with touch drawing using canvas:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/TouchPaint.html
I have a few doubts:
I am not able to understand what's the role of Canvas versus the role
of the bitmap.
In the drawPoint function, I am not able to
understand this code snippet:
mCanvas.drawCircle(x, y, radius, mPaint);
mRect.set((int) (x - radius - 2), (int) (y - radius - 2),
(int) (x + radius + 2), (int) (y + radius + 2));
invalidate(mRect);
If the circle is already drawn into the canvas above, then what happens in the onDraw function where the following code is given:
canvas.drawBitmap(mBitmap, 0, 0, null);
Canvas vs Bitmap
A Bitmap is what the name suggests: A normal image as a bitmap. The Canvas class is an editor for bitmaps. You use it to change the bitmap data, it holds all drawing methods. This principle behaves similar to the shared preferences (if you already worked with them), you have a SharedPreferences class that holds the preferences, and an Editor class to change things.
Drawing the circles
This code does something similar to double buffering. drawPoint() basically draws a circle into the mBitmap object¹. But this bitmap object is not yet visible. It exists in the memory. When onDraw() is called, it has a Canvas argument that represents the drawing surface of the view. All that drawBitmap() does here is use the prepared bitmap from the memory and draw it inside the views graphical representation to make it visible.
¹ The used canvas mCanvas is tied to mBitmap inside onSizeChanged()
if you go to the developper refference:
drawBitmap(Bitmap bitmap, float left, float top, Paint paint)
Draw the
specified bitmap, with its top/left corner at (x,y), using the
specified paint, transformed by the current matrix.
Then if you see that mBitmap doesn't exist in the class , thats cause that var comes from the extend from another activity .
Canvas also has a setBitmap(Bitmap bitmap) function . Then the solution is that that paint in canvas if you have set into it a bitmap object.
From the Android SDK:
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
I'm assuming you're referring to this snippet:
#Override protected void onDraw(Canvas canvas) {
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0, 0, null);
}
}
Well it looks like an override of an inherited onDraw method which by default probably 'does nothing', hence the override to actually give it some behaviour, in this case given a non-null Bitmap instance, make the canvas draw it.