I'm sorry if this topic has been brought before, but all my searches on the web and google groups did not help me.
I'm currently developing a little game with the Android SDK, and use hi-res bitmaps that I resize accordingly to match the device's resolution (letting the system do it for me is
not "crisp" enough).
I use a SurfaceView, on which I paint in one pass a canvas filling the whole surface. The paint uses setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)) to allow masking.
Beforehand, I retrieve various bitmaps -- which are resized at initialization with createScaledBitmap() and put in a cache -- and I apply the bitmaps with a paint on this canvas, before drawing this canvas on the SurfaceView.
My problem is, whatever I try, whatever paint settings I use (dithering, antialias, etc..), the resized bitmaps are not antialiased and the drawing present jagged edges. I tried everything.
The only little success I had was using inSampleSize to approach the desired scaled size and force a first pass of antialiasing, before invoking createScaledBitmap on the retrieved
hi-res bitmap, but it is not beautiful enough. I just can't allow to create multitudes of pre-sized bitmaps for every combination of resolution. What did I miss ?
Thanks a lot in advance
First when you load your bitmap you make sure that you don't lose any image quality by settings options to argb_8888:
Options options = new Options();
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap pic = BitmapFactory.decodeResource(getResources(), R.id.pic, options);
When you scale the bitmap turn on the filter:
pic = Bitmap.createScaledBitmap(pic, screenW, screenH, true);
However if one streaches the image too much inevitably it degrades in quality.
When you use paint you can improve quality but lose on speed with turning on ditherig and filtering:
Paint paint = new Paint();
paint.setFlags(Paint.DITHER_FLAG);
paint.setFilterBitmap(true);
Finally the entire activity window could be set on argb_4444 instead on argb_8888 (OS < 2.3). You can chage this if you instert this line before setContentView:
getWindow().setFormat(PixelFormat.RGBA_8888);
If it comes down to it, you can manually antialias without too much trouble. Just apply a simple lowpass filter (something like an NxN average) to the pixel data before asking the bitmap object to rescale itself.
you may clear canvas buffer by youself! such as follows:
canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);
Related
I'm extending SurfaceView to create a fullscreen horizontally scrollable background.
My issue is with the quality of the bitmap. When used in the draw-call as
canvas.drawBitmap(bitmap, 0, 0, paint);
It's quality is much poorer than its original. I'm not doing any scaling.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setFilterBitmap(true);
Not sure if the images show it clearly, they're screenshots from the tablet. The good one is an ImageView with its src as the drawable.
The canvas one has this horizontally stripy effect, its very significant in reality. This effect is vertical as well. It's whenever the gradient is slightly changing. As if the canvas' bitmap has much less colors..
I'm sure there's a solution to this, but I could not find any.
Poor quality example
Real quality example
Looks like color quantization. The default color format for SurfaceView is 16-bit RGB_565. Change it to 32-bit with something like this on onCreate():
mSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
(Some examples use PixelFormat.TRANSLUCENT.)
With link to my previous question of Moving Circle on Live Wallpaper. I am moving a circle with new bitmap each time with circle drawn on it on new position i.e. (x,y). But it doesn't seems to me a good way of doing it, so I am thinking that is it possible to remove a circle/bitmap drawn on it in live wallpaper canvas?
if yes then please share some code/link.
If you're hitting performance issues, at this point in your code...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.aquarium, options);
...you are decoding a bitmap every frame. That is expensive and should be avoided.
Instead read in the bitmap once and then use it to draw to your canvas every frame.
As well, you should try not to instantiate anything in the draw loop. Everything you create there has to be Garbage Collected and that will slow things down. Anything that is an object try to instantiate outside of the draw loop. So the other thing you could do to make this perform a little faster is to do this in the constructor:
Paint paint = new Paint();
I am trying to composite a background bitmap from multiple smaller bitmaps. I am using Eclipse with the Android SDK.
I have loaded a (mostly) blank background bitmap (pixel dimensions are 320x480) like so:
Bitmap mBackground = BitmapFactory.decodeResource( res, R.drawable.background );
Then I load a bitmap 'tile' (128x128) the same way.
I have tried multiple ways of drawing the tile onto the bitmap but every single method does not work the way I would like/expect. I want to draw the tile bitmap scaled down to 64x64 and at a specific location (pixel offset) on the background bitmap.
1) Using a Canvas...
Canvas c = new Canvas( mBackground );
c.drawBitmap( mTile, null, new Rect( 10, 10, 73, 73 ), null );
This draws the tile way too big (not fullsize, not 64x64, but somewhere in between, about 66% of original)
2) Using drawables (which basically seems to be the same):
Canvas c = new Canvas( mBackground );
c.translate( 10, 10 );
d.setBounds( 0, 0, 63, 63 );
d.draw( c );
Same result as experiment #1...
The documentation mentions in numerous places (without a concise explanation anywhere) that this has to do with the device independent 'densities' thus I tried the next approach...
3) Using fixed scale resources. I created the /res/drawable-nodpi (I also tried just /res/drawable) folder and moved my resources (background and tile png images) into it. That causes an exception when creating the Canvas from the background Bitmap because an unscaled bitmap apparently becomes immutable and therefore cannot be assigned to a Canvas!
4) I tried using BitmapFactory.Options and setting inScaled to false, and passing those options to both of the decodeResource calls for the background and the tile. This has the exact same effect as #3 (exception thrown).
Nothing has worked and its becoming quite frustrating. I am sure I am just missing some detail that is peculiar to bitmaps when it comes to the different coordinate spaces but I cannot find it anywhere in the documentation or elsewhere (I've tried multiple android development sites to no avail).
Hopefully someone who has done this will see this plea!
Thanks, jason
Canvas.drawBitmap() does not perform scaling afaik.
Since your tile image is originally 128x128 and you want to draw it at 64x64, the simplest trick would be to call decodeResource() with the option to reduce it in size:
BitmapFactory.Options options = new BitmapFactory.Options()
options.inSampleSize = 2; // downscale by 50%
Bitmap mTile = BitmapFactory.decodeResource( res, R.drawable.tile, options );
To do more powerful scaling (e.g. drawing your image at any size) you should wrap it in a BitmapDrawable.
It looks like the densities of your bitmaps are different... There is a similar issue here. You should check the density of each Bitmap you load to see if it's the same problem.
Say I have a somewhat large (i.e. not fit in most phones' memory) bitmap on disk. I want to draw only parts of it on the screen in a way that isn't scaled (i.e. inSampleSize == 1)
Is there a way to load/draw just the part I want given a Rect specifying the area without loading the entire bitmap content?
I'm quite confident this is possible since you can load a really large bitmap file into an ImageView without problems so there must be some sort of a built-in way to handle large bitmaps... and after a few attempts, I've found a solution:
Instead of loading the entire bitmap and manually draw it yourself, load it as a Drawable instead:
InputStream mapInput = getResources().openRawResource(
R.drawable.transit_map);
_map = Drawable.createFromStream(mapInput, "transit_map");
_map.setBounds(0, 0, _mapDimension.width(), _mapDimension.height());
I'm using a resource file but since you can use Drawable.createFromStream to load image from any InputStream, it should works with arbitrary bitmap.
Then, use the Drawable.draw method to draw it onto the desired canvas like so:
int left = -(int) contentOffset.x;
int top = -(int) contentOffset.y;
int right = (int) (zoom * _mapDimension.width() - contentOffset.x);
int bottom = (int) (zoom * _mapDimension.height() - contentOffset.y);
_map.setBounds(left, top, right, bottom);
_map.draw(canvas);
As in the above case, You can also scale and translate the bitmap as well by manipulating the drawable's bounds and only the relevant parts of the bitmap will be loaded and drawn onto the Canvas.
The result is a pinch-zoomable view from just one single 200KB bitmap file. I've also tested this with a 22MB PNG file and it still works without any OutOfMemoryError including when screen orientation changes.
Now it's very relevant: BitmapRegionDecoder.
Note: available since Android SDK 10
It can easily be done by using RapidDecoder.
import rapid.decoder.BitmapDecoder;
Rect bounds = new Rect(10, 20, 30, 40);
Bitmap bitmap = BitmapDecoder.from("your-file.png")
.region(bounds)
.decode();
imageView.setImageBitmap(bitmap);
It supports down to Android 2.2 (API Level 8).
Generally speaking, that isn't possible, particularly since most image formats are compressed, so you don't even know which bytes to read until you've extracted the uncompressed form.
Break your image up into small tiles and load just the tiles you need to cover the region you want to display at runtime. To avoid jittery scrolling, you might also want to preload tiles that are just out of sight (the ones that border the visible tiles) on a background thread.
I want to draw a bitmap on a canvas with bigger size than it is. I can use canvas.drawBitmap(bitmap, null, destRect, null); but that gives a poor quality, as the result is pixelated, if the source image is sightly smaller than the destination rectangle.
How can i draw my bitmap using bilinear or bicubic resampling?
Any help would be appreciated, thanx.
You need to set the FILTER_BITMAP_FLAG flag in your paint.
canvas.drawBitmap(bitmap, matrix, null); //ugly jaggies unless scale is 1:1, but fast
or
Paint paint = new Paint();
paint.setFilterBitmap();
canvas.drawBitmap(bitmap, matrix, paint); // pretty but slower (not too slow, usually)
The same applies to other (syntactic sugar) forms of the drawBitmap method.
There is no documentation anywhere for most of the Android drawing options and neither is there documentation for the underlying native Skia library that does the rendering. I'd be happier if there were. You can search the Skia source code on Google Code Search. Search for Skia and dig into the raw source; that's the only documentation.
FILTER_BITMAP_FLAG doesn't work for downscaling in most of the cases.
Good downscaling algorithm (not nearest neighbor like) consists of just 2 steps (plus calculation of the exact Rect for input/output images crop):
downscale using BitmapFactory.Options::inSampleSize->BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()
Here is detailed explanation how SonyMobile resolved this task: https://web.archive.org/web/20140228024414/http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Here is the source code of SonyMobile scale utils: https://web.archive.org/web/20140227233706/http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/