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.
Related
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.
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.
In Android, is there a clean way to display only a given part of a bitmap in an ImageView (for example a single sprite of a sprite sheet), without having to :
Manually split the bitmap png into small pngs (=> more drawables embedded in the app, tedious manual operation possibly leading to bugs, or loose the spritesheet).
Have small pngs used in the ImageViews, with the sprite sheet being generated (=> complicated)
Create "dirty" subclasses for ImageView, Drawable, etc (=> the more we use Android API "ASIS" the better)
Programmatically create sub-bitmaps of the big bitmap (=> We have to do all the work programmatically)
For instance I have tried creating an ImageView of width/height of 40dp, and setting its drawable as a ClipDrawable displaying only the part of the bitmap I wanted, but it did not go well :
the clipped part did not fill the parent when using Gravity.CENTER when creating the ClipDrawable.
The whole "big" PNG was displayed when using Gravity.FILL.
Furthermore this solution is feasable with a simple sprite sheet (for example 2*2 sprites), but I do not think it is possible when using something like a 4*4 sprite sheet. I think ClipDrawable is not meant for such a use.
Isn't there something "clean and easy" like in OpenGL where you can set a texture Id, and set the texture coordinates to display only part of the texture? Considering my researchs I think the best solution is to manually split the big bitmap with Bitmap.createBitmap, when I'd rather ask before starting something like that.
PS : I consider using SpriteSheets because of OpenGl, although my "menus" are developed using Android APIs, hence using ImageView.
Are you familiar with Canvas at all? I think that is another way that would work for you.
You create one bitmap in memory, then use drawBitmap to draw just the current chunk you want.
Here is a snippet from the manual:
public void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
This function ignores the density associated with the bitmap. This is because the source and destination rectangle coordinate spaces are in their respective densities, so must already have the appropriate scaling factor applied.
Parameters
bitmap The bitmap to be drawn
src May be null. The subset of the bitmap to be drawn
dst The rectangle that the bitmap will be scaled/translated to fit into
paint May be null. The paint used to draw the bitmap
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.
I've created an activity that calls a DrawView extends View, I draw on a Canvas something like a binary tree. The canvas size is greater than the screen size, and the onDraw function draws all the canvas, not only the visible part of it. I've added an horizontal and a vertical scrollView, and obviously onDraw is called again and again to refresh the view.
I was wondering if I can draw the canvas on an image (a bitmap or something like that) using that image to show the tree, without the need to recall the onDraw function.
If I can't do this, what can I do in order to get a faster view?
If you're using API 11 or above you can try using the hardware acceleration attribute in the application tag in your manifest.
<application ... android:hardwareAccelerated="true" ...>
Other than that you could consider using another rendering than View, have a look at SurfaceView too.
Generally, I think you shouldn't override View unless you're building a UI component (like a Button), don't quote me on this though.
Technically the canvas draws on a Bitmap. So it's actually the Bitmap that's bigger than the screen. Why are you making the Bitmap bigger than the screen? Bigger Bitmaps = more memory usage - which could slow things down a bit. You only really draw within the clipped bounds of the screen - you may think you're drawing off screen but you're not. Drawing on another Bitmap won't help you - you're already doing that. Without looking at your code you could try:
only draw what changes in the binary tree by calling invalidate(rect) which only repaints what has changed
if you binary tree is meant to be static, just create a png/jpg of it and display that image versus drawing it all yourself
make sure you're not calling invalidate more than you need to