Drawing offscreen in Android to canvas performance - android

My question is about drawing performance. Let's say I have a Bitmap for image width=2400px and height=800px.
My Canvas is only 800px wide and 800px high. View containing the Canvas is a child of HorizontalScrollView so user can scroll to see whole image.
I load the Bitmap once and draw it every frame in onDraw method. Does the "offscreen" drawing cause performance hiccups in this scenario? If so, how to get it smoother?
Thanks.

Certainly the Bitmap size is a problem. In typical Android implementation, the texture with dimension over 4000px cannot be rendered. In some Android devices, this limit is 2000px.
Since your objective is to allow user to scroll to see the whole image, if I were you, I won't use HorizontalScrollView. Instead, you should implement a subclass of View and override the onDraw(Canvas canvas) method. You can then detect the touches and modify a Matrix. Such matrix will be used in calling Canvas.drawBitmap(Bitmap, Matrix,Paint)
Specifically for the oversize image problem, upon receiving the Bitmap object, you can slice it into 6 pieces. (2400 x 800 -> 800 x 800 x 6). Then you should control the viewport location and deduce the visible part of the image. In best case, you only need to draw 1 800x800 Bitmap. In worst case, you need to draw 4 800x800 Bitmap.

Related

Does every view have its own canvas/bitmap to draw on?

I have a relativelayout with three full screen child views of the same custom view class. I am wondering whether I should be worried about memory. Judging by this answer:
Understanding Canvas and Surface concepts
All views get passed the same canvas with underlying bitmap to draw on so that the memory is not tripled. Can anyone confirm?
It would make sense, otherwise a full screen textview would be very inefficient.
Bonus: is the purpose of canvas to define a drawable area of a bitmap and translating the view coordinates to bitmap coordinates?
As per the documentation http://developer.android.com/guide/topics/graphics/2d-graphics.html#draw-with-canvas:
When you're writing an application in which you would like to perform specialized drawing and/or control the animation of graphics, you should do so by drawing through a Canvas. A Canvas works for you as a pretense, or interface, to the actual surface upon which your graphics will be drawn — it holds all of your "draw" calls. Via the Canvas, your drawing is actually performed upon an underlying Bitmap, which is placed into the window.
In the onDraw(Canvas canvas), you are given a canvas object. This canvas has an underlying bitmap. All views are not given the same canvas. Canvas is just a layer above the common bitmap (which is pixels on the screen). canvas offers you to manipulate the bitmap as much as you want. So every view has a canvas, but not it's own bitmap.
So no, as far as memory is concerned, three view doesn't mean memory is tripled, because there is just one bitmap. You could however create your own bitmap, if you do so, then you will be jogging up the memory. If you create 3 bitmaps with size of the screen, your memory will be tripled.

Masking and cut transparent image

I have two images one over other. Uppper image is having some transparent portion, I want to cut that portion and place it in sd card.
Also the below image can be zoomed in/out/scaled.
Can any one help me in this ?
I appreciate if anyone even can provide me some idea.
Create a Bitmap object to draw to that is the same size as the Upper image.
Create a Canvas object and pass this bitmap to its constructor so that you are drawing into the bitmap.
Draw the Lower image on the canvas with a transformation Matrix that represents your zoom/pan etc.
Then iterate over the pixels in the upper bitmap and the one you just drew into and set the alpha value of the pixels in the new bitmap to that in the upper image. Maybe there is another way of applying an alpha mask but I did not see one after a quick skim of the Canvas class interface - maybe looking a bit closer would reveal something.
Alternatively for better performance, use OpenGL and write a shader to do it using your two images. You can render to texture and pull the data back from the render texture. More complicated to do than the other method.

Android Canvas -- Draw a Rectangle or a Picture of a Rectangle

If you always have to draw the same rectangle, it is faster to do it with a static bitmap or with canvas.drawRect()?
For this example, the are four layered rectangles. So a boarder with a fill color, and then a boarder between a middle color and the fill color.
So four paint.setColor() commands and four canvas.drawRect commands or one canvas.drawBitmap().
I strongly recommend drawRect().
Bitmaps take up a huge chunk of memory, and can lead to Out Of Memory Exceptions if not used correctly.
Written by android:
Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
To prevent headache, and unexpected crashes. Use drawRect();
If you are doing these 4 draws on a regular basis for different objects, consider writing a method that does all 4 for you. So you are not causing massive repetition.
For example:
public void DrawMyRect(Canvas canvas, int x, int y)
{
canvas.drawRect(x, y ,x + 15, y + 40, paint);
// Draw its line etc etc..
}
Alternatively, if you do go for drawing bitmap, as it does have advantages:
See this epic Link by Android, on how to properly use Bitmaps
The performance difference is probably negligible. The bitmap will use more memory, the canvas draw calls will use slightly more CPU. You can probably use a ShapeDrawable if you want to reduce the calls without the overhead of a bitmap.

How do I draw only part of a possibly large and/or zoomed SVG?

I would like to show a zoomable and scrollable map-like SVG in my app. The only way to do this without writing your own library etc. is to use an existing library all of which seem to render the SVG to a Bitmap, which can be assigned to an ImageView, for example.
The underlying bitmap quickly gets very large which may result in an out-of-memory exception. How do I draw only part of a possibly large and/or zoomed SVG to a Bitmap? Scaling up a small bitmap looks bad and is not an option.
I am going to answer this question myself because i have been looking for an answer for a while, found it scattered all over the web (but not here) and think that it may be helpful for others.
Can you not use Google's svg-android library? You can scale the Canvas to the correct size before rendering.
Would this be like how Google maps works? They appear to tile and increase/decrease detail as you zoom in and out.
I have done something similar in the past, not sure if it is how Google did it but my scheme worked exceptionally well for me. What I did was break my map into rectangular regions, I would then calculate which regions had any portion that was visible and pull in data only from those regions. Each rectangular region was sub-divided to provide more detail if the user zoomed in. I kept subdividing down to the level I wanted, I stopped at three, because I used a 10 X 10 grid so each level was effectively a magnification of 10 - at level three I was viewing 100 times the detail I was viewing at level one which was sufficient for my needs.
I was even able to animate this so that when you zoomed in you appeared to smoothly "fly" closer to the terrain.
First of all, I am using libsvg-android which requires the NDK but which seems to be very fast, especially if you have to redraw your SVG frequently. Actually, I am using this modified version of the library which enables you to query the width and height of the SVG as specified in the SVG document.
My first approach was this:
long svgID = SvgRaster.svgAndroidCreate();
SvgRaster.svgAndroidParseBuffer(svgID, svgString);
SvgRaster.svgAndroidSetAntialiasing(svgID, true);
Bitmap bitmap = Bitmap.createBitmap(500, 500, Bitmap.Config.RGB_565);
SvgRaster.svgAndroidRenderToAreaUniform(svgID, new Canvas(bitmap), -300, -200, 1500, 1500);
First, the SVG source is parsed. Then, a Bitmap is created that will be assigned to an ImageView later on. Next, the SVG is rendered (zoomed to 1500x1500 pixels in size) to the bitmap. Note the translation (-300, -200) applied because the user has scrolled the image view.
This does not work. It seems that negative offsets are not supported.
Instead, the canvas has to be translated like this:
Canvas canvas = new Canvas(bitmap);
canvas.translate(-300, -200);
SvgRaster.svgAndroidRenderToAreaUniform(svgID, canvas, 0, 0, 1500, 1500);
This works as expected. The translation of the SVG is applied to the canvas; the scale is applied by adjusting the size of the rendered SVG. In this way, you can have a large SVG document and render the proportion you are interested in to a much smaller bitmap.
I've written an ImageView subclass which is scrollable in both the x and the y directions and zoomable. Its image resource is a low resolution raster image of the SVG. When the user scrolls or zooms the image, this version of the SVG is shown. When the user stops interacting with the image, I render the currently visible part of the SVG to a Bitmap the size of my image view and draw this instead of the low resolution image.

In the onDraw() method, why does the supplied Canvas already have scale?

In a current test project I have a custom View, that I have named SVGView, that simply paints some vector graphics (Paths, etc.) to the Canvas.
The XML layout file consists of nothing more than a FrameLayout, which contains a single child SVGView. The android:layout_width and android:layout_height attributes for the SVGView in the current example are both set to 300px (which the containing FrameLayout has more than enough room to accommodate). In the SVGView's onMeasure(), the call to setMeasuredDimension() is given the arguments 300, 300.
When the onDraw() method is called to draw the graphics to the Canvas object, I find that the Canvas doesn't have an identity matrix; rather, the Canvas has some amount of scale transformation already applied. What's the reason for this? I (wrongly) assumed that the Canvas would have a scaling of 1, because the actual dimensions of the View, its Canvas, and its representation on the screen all match.
I'm asking this in relation to the problem I have described in detail in my earlier question here: Canvas drawing cache bitmap is slightly blurred or has anti-alias. I'm finding that when I draw the cache bitmap back to the Canvas, it's slightly blurred compared to the original vector graphics, despite the fact that I'm drawing the bitmap back using no rescaling and using a Paint with anti-alias turned off. I'm now just absolutely convinced there there has to be some scaling process that I'm unaware of causing this to happen to the bitmap. To discover that the Canvas argument to onDraw() has some scaling transformation already applied is possibly a big clue to solving this problem -- but I can't understand why it has such scaling, when surely the Canvas' dimensions perfectly match the area of the screen it's ultimately painting to.
Thanks,
Trev
Have you declared a minSdkVersion or targetSdkVersion in your manifest? If you are targeting an API level lower than 4 your app will run in scaled compatibility mode as if it were targeting a G1 screen.

Categories

Resources