I'm building a game using Android 2.2. The main game Activity uses a custom SurfaceView:
class GameView extends SurfaceView
From what I understand, the onDraw() method requires its own Thread to be executed. That in mind, I am planning to add a background image in onDraw():
canvas.drawBitmap(wallpaper, 0, 0, paint);
paint = new Paint();
But when I execute the game, it becomes very slow. If I comment out the new Paint() line, the game speeds up.
Is there something I am doing wrong, or is there a solution to my problem? For example, is there a way to reduce the number of calls to onDraw()? Or add an XML attribute to my custom SurfaceView class?
Here's the code how I load the drawable images.
public Bitmap loadBitmap(String image) {
Bitmap bitmap = null;
try {
int id = R.drawable.class.getField(image).getInt(new Integer(0));
bitmap = BitmapFactory.decodeResource(context.getResources(), id);
// bitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.RGB_565);
} catch(Exception ex) {
Log.e("loadBitmap", ex.getMessage());
}
return bitmap;
}
Here's the code of the onDraw method.
Unfortunately, I can't post everything.
paint.setColor(Color.BLACK);
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
canvas.drawBitmap(gameLevel.getBitmap(), 0, 0, paint);
// draw object(1) 320x25
// draw object(5) 50x50 each
// draw object(n) 15x15 each, estimate
// draw object(n) 50x50 each
// collision check, draw hit tile on the image sheet
// draw game information using canvas.drawText()
timeLine++;
Thanks in advance!
If the problem is only the "paint = new Paint();" line, why don't you create the Paint object only once? When the class is first created and make it a Class variable. Then just use the object everytime you want.
You could try to load the background as RGB_565 instead of ARGB_8888 in case you haven't already. Otherwise there is not much you can do except switching to OpenGL
EDIT:
Options options = new Options();
options.inDither = false;
options.inJustDecodeBounds = false;
options.inSampleSize = 1;
options.mCancel = false;
options.inPreferredConfig = Config.RGB_565;
bitmap = BitmapFactory.decodeResource(context.getResources(), id, options);
If that doesn't help other reasons may be:
You drawing code is wrong
You scale the background when you draw it
You run it on an emulator
Related
I am working on application that allows users to shade a portion of bitmap when the swipe on it. Then when he finishes swiping I call a method to crop that portion of bitmap and perform some function(but cropped bitmap is not saved anywhere so doubt there is any problem here). On click on imageView I reset the bitmap with original bitmap. There is also a functionality to rotate the bitmap.I have two bitmap objects bill(user actually swipes on it) and billOrg(original bitmap as it is). Below are the methods.
private void drawShade(float left,float top,float right,float bottom){
//this method draws shade on bitmap. Coordinates are sent from onTouchEvent
TAG = "drawShade";
//parseTouchPointsString();
Bitmap tempBitmap = Bitmap.createBitmap(bill.getWidth(), bill.getHeight(), Bitmap.Config.RGB_565);
Log.d(TAG,"bill:"+bill.isMutable()+"tempBit:"+tempBitmap.isMutable()+"");
Canvas tempCanvas = new Canvas(tempBitmap);
tempCanvas.drawBitmap(bill,0,0,null);
tempCanvas.drawRoundRect(new RectF(left,top,right,bottom), 10, 10, shadePaint);
imgView.setImageDrawable(new BitmapDrawable(getResources(), tempBitmap));
if(fingerUp) {
Log.e("fingerUp",fingerUp+"");
bill = tempBitmap;
}
Log.d(TAG,"shade drawn at:"+left+","+top+","+right+","+bottom);
}
Here is method to rotate bitmap :
public void rotateImage(View v){
TAG = "rotateImage";
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap rotatedBitmap = Bitmap.createBitmap(bill , 0, 0, bill.getWidth(), bill.getHeight(), matrix, true);
bill = rotatedBitmap.copy(rotatedBitmap.getConfig(),true);
createScaledBitmap();
billOrg = bill.copy(rotatedBitmap.getConfig(),true);//bill.copy(bill.getConfig(),false);
setImage(bill);
rl.invalidate();
}
Method to reset bitmap on click :
private void resetImageView(boolean saveShade){
//the boolean var here tell whether to keep the shaded portion after reset or not
if(!saveShade) { //app crashes in this if block although I have try-catch.
try {
Canvas canvas = new Canvas(bill);
Log.e("resetImageView", "billOrg:" + billOrg.isMutable() + ",bill:" + bill.isMutable()); //returns true for both bitmap objects
canvas.drawBitmap(billOrg, 0, 0, null);
touchBounds = "";
tv_res.setText("");
rl.invalidate();
setImage(bill);
}catch(Exception ex){
Log.e("resetImage",ex.getMessage());
}
}else{
Canvas canvas = new Canvas(bill);
canvas.drawBitmap(bill, 0, 0, null);
touchBounds = "";
tv_res.setText("");
rl.invalidate();
setImage(bill);
}
}
All is fine except when user first swipes the image --> then rotates the image --> then clicks on bitmap.
All I get is this error: jni_helper.cc:110 Bitmap is of the wrong format: 4. No other exceptions.
As per my knowledge bitmaps usually throw errors if we try to modify a bitmap object which is immutable but I have made it mutable in all places. It prints mutable in logs too. I guess I am doing something wrong while modifying the bitmaps. I know might be confusing for you. I don't know how well I have explained. If you need any clarity kindly ask. I need some help.
Ok so I found out what was causing the problem.
Bitmap tempBitmap = Bitmap.createBitmap(bill.getWidth(), bill.getHeight(), Bitmap.Config.RGB_565);
In the drawShade() method was culprit. I changed the "Bitmap.Config.RGB_565" to bill.getConfig() and it was fixed.
I’m using thquinn’s DraggableGridView and load ~60 images into it. This all works fine. I had all the images needed in my assets, but want to create them at runtime since first of only the Text on the images change which seems redundant and I can reduce the appsize and the second reason is that I need to sometimes change the icons over the Air where adding wouldn’t be the problem but deleting from assets isn’t possible and would use unnecessary space. That briefly to explain my motives here.
So I’ve used the method from this Post to draw text over my Asset png and then convert it into a Bitmap to be able to use them in the LRUcache. This works with a few images but as soon as I try and display all needed Images I get an OutOfMemory error. The Base Images are 200x200 px which I think should also be scaled to the need size depending on screensize and density.
First of, this method doesn’t seem efficient because I create a Bitmap canvas then make a LayerdDrawable which I make into a Bitmap (for caching) again. Not sure, but it just feels like I’m creating to much temp images which clutter up the memory.
And then I’m using a BitmapDrawable which is depreciated. How would this method look without the BitmapDrawable??
Am I going about this the right way in general and How would I make this method efficiently so I don’t get the OOM error?!?
BTW. When I don’t use LRUcache and just return the LayerdDrawable for the GridView the images load fine but I get the Exception after a couple of Orientation changes!
This is the method as I have it atm:
private Bitmap createIcon(Drawable backgroundImage, String text,
int width, int height) {
String key = text.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Bitmap canvasBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
Canvas imageCanvas = new Canvas(canvasBitmap);
Typeface font = Typeface.createFromAsset(getActivity().getAssets(), "myriadpro.ttf");
Paint imagePaint = new Paint();
imagePaint.setTextAlign(Paint.Align.CENTER);
imagePaint.setTextSize(26);//
imagePaint.setTypeface(font);
imagePaint.setAntiAlias(true);
imagePaint.setColor(Color.parseColor("#562b12"));
backgroundImage.draw(imageCanvas);
imageCanvas.drawText(text, (width / 2)+4, (height / 2)-8, imagePaint);
LayerDrawable layerDrawable = new LayerDrawable(
new Drawable[]{backgroundImage, new BitmapDrawable(canvasBitmap)});
int w = layerDrawable.getIntrinsicWidth();
int h = layerDrawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
layerDrawable.draw(canvas);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
}
Update:
I have tried with another method now, which seems better because it’s not using BitmapDrawable. But I still get OOM error. Also it generally doesn’t seem to realy use the cached images, when I change orientation only 1 or 2 images come from the cache.
I also failed to metion before the this is inside a Fragment. Not sure if it matters. But in portrait mode i have only this Fragment and in Landscape there can be another one if the width allows it.
public Bitmap drawTextToBitmap(Context mContext, int resourceId, String mText) {
try {
int memory = (int) (Runtime.getRuntime().freeMemory() / 1024);
Log.d("TRACE", "memory " + memory);
Log.d("TRACE", mText);
String key = mText.toLowerCase();
Bitmap cachedBitmap = getBitmapFromMemCache(key);
if (cachedBitmap != null){
Log.d("TRACE", "is cached");
return cachedBitmap;
}
else{
Resources resources = mContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
bitmap = bitmap.copy(bitmapConfig, true); // OOE error happens here
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.rgb(110,110, 110));
paint.setTextSize((int) (25 * scale));
Rect bounds = new Rect();
paint.getTextBounds(mText, 0, mText.length(), bounds);
int x = (bitmap.getWidth() - bounds.width())/6;
int y = (bitmap.getHeight() + bounds.height())/5;
canvas.drawText(mText, x * scale, y * scale, paint);
addBitmapToMemoryCache(key,bitmap);
return bitmap;
}
} catch (Exception e) {
// TODO: handle exception
return null;
}
}
I worked on an app which needed to constantly hold 3-4 very large images in memory, and I was struggling with a problem very similar to yours. I loaded a bitmap from a byte array, then I needed to copy it to make it mutable, so that I could draw on it. This copy would cause there to be 1 too many bitmaps in memory, and then cause an OOM crash.
Eventually I found a way to load it once, and make it mutable at the same time:
Options options = new Options();
options.inMutable = true;
BitmapFactory.decodeResource(getResources(), id, options);
Use this, instead of copying the bitmap to make it mutable. I'm not sure if you really need the ARGB_8888 configuration, but if you don't, this should at least improve your memory efficiency.
Note: This will only work with Android 3.0 and above. For versions that need to run on 2.x and above, you can use a little reflection magic to see if the "inMutable" field exists in runtime. While this won't help on pre-3.0 devices, it will still provide a good improvement for most devices (and I've also noticed that devices running 2.x tend to have more memory flexibility).
Here's the code:
Options options = new Options();
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap mutableBitmap = null;
try
{
Options.class.getField("inMutable").set(options, Boolean.TRUE);
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBytes;
}
catch (NoSuchFieldException noFieldException)
{
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);
mutableBitmap = decodedBitmap .copy(decodedBitmap .getConfig(), true);
decodedBitmap .recycle();
}
Im writing a widget and I need to download and set a bitmap on the layout. Everything I've tried doesn't seem to work.
I've created a test bitmap now to set on the view, [update] this works.
Bitmap.Config config = Bitmap.Config.ARGB_8888;
Bitmap bitmap = Bitmap.createBitmap(imageActiveWidth, imageHeight, config);
Canvas canvas = new Canvas(bitmap); // Load the Bitmap to the Canvas
Paint paint = new Paint();
paint.setColor(0xFFFFCCFF);
canvas.drawRect(0, 0, imageActiveWidth, imageHeight, paint);
views.setImageViewBitmap(resId, bitmap);
using a resource file does work:
Bitmap placeholderBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.placeholder_medium);
views.setImageViewBitmap(imageSlotId, placeholderBitmap);
However using a downloaded bitmap does not seem work.
(after async task has downloaded bitmap, I have a method setBitmap which is one line:
views.setImageViewBitmap(resId, proxy);
Result - screen is just white, no bitmap
I'm really stumped on how to get this to work, because I need to be able to download bitmaps and set them.
Found a solution. I think its related to this bug:
http://code.google.com/p/android/issues/detail?id=8489
Solved by changing by setBitmap method to the following:
private void setBitmap(RemoteViews views, int resId, Bitmap bitmap){
Bitmap proxy = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(proxy);
c.drawBitmap(bitmap, new Matrix(), null);
views.setImageViewBitmap(resId, proxy);
}
And I needed to call:
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views);
AFTER the bitmaps had been set.
For some reason this wasn't working when I came back to it. My view was an AdapterViewFlipper, so i used the above method with a call to widgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.content); which caused the bitmaps to render.
I get a bitmap from resource using this method and draw it using Canvas.drawBitmap within a class extended from SurfaceView:
private Bitmap getImage(Context context, int imageId) {
TypedValue value = new TypedValue();
context.getResources().openRawResource(imageId, value);
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inTargetDensity = value.density;
return BitmapFactory.decodeResource(context.getResources(), imageId,
opts);
}
This bitmap displays itself without any problem. But when I want to change it's color using the following method and draw this new bitmap, the it will always lose some part of the whole image.
public static Bitmap changeColor(Bitmap bmpOriginal, float degree) {
Bitmap bmp = Bitmap.createBitmap(bmpOriginal.getWidth(),
bmpOriginal.getWidth(), Config.ARGB_8888);
// Set the two bitmaps with the same density.
//But it seems no use now
// try {
// bmp.setDensity(bmpOriginal.getDensity());
// } catch (Exception e) {
// // TODO: handle exception
// Log.v("ImageTools_changeColor", "" + e.toString());
// }
Canvas c = new Canvas(bmp);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setRotate(0, degree);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmp;
}
I tried many search results from the web, but still don't know why.
I think the problem may also come from another source: the resource bitmap itself. It is a 32bit png file 65*161 big and with a size of 1.59KB, not very big. So I get another png and draw with the same method, nothing goes wrong! So these two pngs' links are also given here for you to find the crux of the problem. Thanks a loooot!
================The png causing problem VS The png without problem=============
Hi every am new to this android development.
Currently am developing drawing application with adding stamps/labels to drawn image.so i have done drawing part so now i have to implement adding stamps/labels to that drawn image.
So please help me out this..
Bitmap Rbitmap = Bitmap.createBitmap(bitmap).copy(Config.ARGB_4444, true);
Canvas canvas = new Canvas(Rbitmap);
canvas.drawBitmap(label, -9, Rbitmap.getHeight()-label.getHeight()-10, null);
canvas.save();
return Rbitmap;
Making your question little more specific will help you more.If I understood is correct this piece of code will help you out to draw a bitmap to a drawn canvas.
private Paint green = new Paint();
private int greenx , greeny;
green.setColor(Color.GREEN);
green.setAntiAlias(false);
canvas.drawCircle(greenx,greeny,20,green);
how to add image in this code replace drawcircle with image how ?
You could be a little more specific, i.e posting some code to show what you have to get more specific answers. Anyway, you can draw a bitmap on top of another bitmap by using something like this:
//You will have a Bitmap bottomBmp, a Bitmap topBmp and a Canvas canvas.
//If you are inside an onDraw() method the canvas will be provided to you, otherwise you will have to create it yourself, use a mutable bitmap of the same size as the bottomBmp.
canvas.drawBitmap(bottomBmp, 0, 0, null); //Draw the bottom bitmap in the upper left corner of the canvas without any special paint effects.
canvas.drawBitmap(topBmp, 0, 0, null); //Draw the top bitmap over the bottom bitmap, change the zeroes to offset this bitmap.
Try with this code:
private Bitmap background;
public birdClass(Context context) {
super(context);
background = BitmapFactory.decodeResource(getResources(),R.drawable.splash );
}