I am taking a bitmap, creating a new one from the original and then setting the new one to an ImageView, when I recycle the original I get that error, but I am not drawing the original? For more detail read my comments in my code. As you can see I recycle the bitmap that comes in which I do not draw, I always make a new bitmap called tile that I draw.
My Code:
public void tileImage(Bitmap bm){
if(bm==null){
Debug.out("Bitmap is null");
}
else{
Bitmap tile;
float tileWidth = bm.getWidth();
float tileHeight =1024;
//if my bitmap is too wide
if(bm.getWidth()>width){
Debug.out("Bitmap too wide: "+bm.getWidth());
//if this code runs I get no error, if not I get the error
bm = Bitmap.createScaledBitmap(bm,
(int)width,
(int)(bm.getHeight()*(float)(width/tileWidth)),
false
);
}
Debug.out("Bitmap height: "+bm.getHeight()+" adjusted width "+bm.getWidth());
//if my bitmap is too tall
if(bm.getHeight()>tileHeight){
for(int i = 0; tileHeight*i<bm.getHeight(); i++){
image = new ImageView(main);
//make tiles of the body
if((tileHeight*(i+1))<bm.getHeight()){
tile = Bitmap.createBitmap(
bm,
0,
(int)(tileHeight*i),
(int)bm.getWidth(),
(int)(tileHeight)
);
Debug.out("Tiling: "+i);
}
//tile the reaminder
else{
tile = Bitmap.createBitmap(
bm,
0,
(int)(tileHeight*i),
(int)bm.getWidth(),
(int)(bm.getHeight()%tileHeight)
);
Debug.out("Tiling: "+bm.getHeight()%tileHeight+" "+i);
}
image.setImageBitmap(tile);
tiles.addView(image);
}
}
//else its not too tall
else{
image = new ImageView(main);
Debug.out("No tiling");
tile = Bitmap.createBitmap(
bm,
0,
0,
(int)bm.getWidth(),
(int)bm.getHeight()
);
Debug.out("Bitmap too small height: "+bm.getHeight()+" width "+bm.getWidth());
image.setImageBitmap(tile);
tiles.addView(image);
}
}
//this is the trouble maker
bm.recycle();
}
Bitmap.createBitmap(params) Returns an immutable bitmap from the specified subset of the source bitmap. The new bitmap may be the same object as source, or a copy may have been made.
bitmap.recycle() method Frees the native object associated with this bitmap, and clear the reference to the pixel data. This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap.
onDraw() method takes some time to inflate the View. If you are passing the bitmap to draw a view and calling recycle() on the same reference then the bitmap is marked as "dead", meaning it will throw an exception if getPixels() or setPixels() is called, and will draw nothing.
You should call recycle() in onDestroy().
My Code:
public class MainActivity extends Activity {
private Bitmap mBitmap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView image1 = (ImageView) findViewById(R.id.image1);
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
image1.setImageBitmap(mBitmap);
tileImage();
}
private void tileImage() {
ImageView image2 = (ImageView) findViewById(R.id.image2);
Bitmap bm = Bitmap.createBitmap(mBitmap, 0, 0,
(int) mBitmap.getWidth(), (int) mBitmap.getHeight());
image2.setImageBitmap(bm);
}
#Override
protected void onDestroy() {
mBitmap.recycle();
super.onDestroy();
}
}
The answer above is not entirely correct. You should in fact not wait until onDestroy with recycle. Especially if you make heavy use of creating these bitmaps. You might not even get to onDestroy before you get an OOM error. What you should do is test the dimensions you're scaling it to to the dimensions of the bitmap. If they are equal, return the original bitmap and no recycle is necessary. Otherwise, you'll get a new bitmap object and you should immediately call recyle on the original bitmap.
see http://developer.android.com/training/displaying-bitmaps/manage-memory.html
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 can't figure out how to resize a bitmap. Based on posts here Bitmap.createScaledBitmap is the way to do it but it just doesn't work for me. Because of the nature of the view I'm doing everything in onDraw so I don't think I can use a layout or ImageView. Here's what I'm doing:
I have an image of the Empire State Building that's 200x200. I run this code to set it up:
private void setupBitmap() {
int bitmapSize = 100;
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.empirestate_sm);
myBitmap = Bitmap.createScaledBitmap(originalBitmap, bitmapSize, bitmapSize, true);
}
This is is my onDraw:
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(myBitmap, 0, 0, null);
}
And here is the result, note the image is just cropped and not resized:
Just for s&g's if I set the bitmapSize = 200 it looks like this:
Ok I've figured it out. What worked for me is leaving the bitmap as is when you create it. Then when you draw it on the canvas set it to the right size. I'm assuming the createScaledBitmap function is broken??
myCanvas.drawBitmap (landmarkImg, null, new RectF((float)0,(float)0,newWidth,newHeight),null);
You can try using an ImageView:
ImageView iv = (ImageView) findViewById(R.id.imageview);
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.empirestate_sm);
myBitmap = Bitmap.createScaledBitmap(originalBitmap, bitmapSize, bitmapSize, true);
iv.setImageBitmap(myBitmap);
I want to combine many small bitmaps which are contained in ArrayList to one large bitmap.
However, I don't know why the large bitmap is looped. It means it seems to copy only the first element in the array. I tried to draw each small bitmap in the array to test and it works fine, but when I run the loop like the below code, it goes wrong.
In addiditon, when I add the bmp.recycle() and bmp = null, it causes the error "trying to use a recycled bitmap". I don't understand why the error happens.
Can you help me, please, thanks!
public static Bitmap getBitmapForVisibleRegion(WebView webview) {
Bitmap returnedBitmap = null;
webview.setDrawingCacheEnabled(true);
returnedBitmap = Bitmap.createBitmap(webview.getDrawingCache());
webview.setDrawingCacheEnabled(false);
return returnedBitmap;
}
public void CombineBitmap(){
ArrayList<Bitmap> bmps = new ArrayList<Bitmap>();
for (int i = 0; i < webView.getWidth; i+=needToCapture){
bmps.add(getBitmapForVisibleRegion(webView));
webView.scrollBy(needToCapture, 0);
}
Bitmap bigbitmap = Bitmap.createBitmap(largeBitmapWidth, largeBitmapHeight, Bitmap.Config.ARGB_8888);
Canvas bigcanvas = new Canvas(bigbitmap);
Paint paint = new Paint();
int iWidth = 0;
for (int i = 0; i < bmps.size(); i++) {
Bitmap bmp = bmps.get(i);
bigcanvas.drawBitmap(bmp, iWidth , 0, paint);
iWidth +=bmp.getWidth();
bmp.recycle();
bmp=null;
}
}
I finally found out my problem. It's because of my dummy mistake.
I have to use scrollTo instead scrollBy
After I change to scrollTo, everything works fine. This is really an useful experience.
According to the documentation of Bitmap.createBitmap(Bitmap),
Returns an immutable bitmap from the source bitmap. The new bitmap may be the same object as source, or a copy may have been made. It is initialized with the same density as the original bitmap.`
Therefore, replace
returnedBitmap = Bitmap.createBitmap(webview.getDrawingCache());
with
returnedBitmap = webview.getDrawingCache().copy(Config.ARGB_8888, false);
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
I want to merge two images and then save them on the Android SDCard.One is from the camera and one from the resources folder. The problem is that i get this error: Caused by: java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor. Thanks.
Bitmap bottomImage = BitmapFactory.decodeResource(getResources(),R.drawable.blink);
Bitmap topImage = (Bitmap) data.getExtras().get("data");
// As described by Steve Pomeroy in a previous comment,
// use the canvas to combine them.
// Start with the first in the constructor..
Canvas comboImage = new Canvas(bottomImage);
// Then draw the second on top of that
comboImage.drawBitmap(topImage, 0f, 0f, null);
// bottomImage is now a composite of the two.
// To write the file out to the SDCard:
OutputStream os = null;
try {
os = new FileOutputStream("/sdcard/DCIM/Camera/" + "myNewFileName.png");
bottomImage.compress(CompressFormat.PNG, 50, os);
//Bitmap image.compress(CompressFormat.PNG, 50, os);
} catch(IOException e) {
Log.v("error saving","error saving");
e.printStackTrace();
}
Managed to fix it by simply doing this change:
int w = bottomImage.getWidth();
int h = bottomImage.getHeight();
Bitmap new_image = Bitmap.createBitmap(w, h ,bottomImage.getConfig());
The problem now is that it doesn't saves the image. Do you know why?
This will help you =)
Edit: (embed answer from link)
the only static "constructor" for Bitmap returning a mutable one is:
(Class: Bitmap) public static Bitmap createBitmap(int width, int
height, boolean hasAlpha)
Returns: a mutable bitmap with the specified width and height.
So you could work with getPixels/setPixels or like this:
Bitmap bitmapResult = bm.createBitmap(widthOfOld, heightOfOld, hasAlpha);
Canvas c = new Canvas();
c.setDevice(bitmapResult); // drawXY will result on that Bitmap
c.drawBitmap(bitmapOld, left, top, paint);
how to get the drawable from Bitmap: by using the BitmapDrawable-Subclass which extends Drawable, like this:
Bitmap myBitmap = BitmapFactory.decode(path);
Drawable bd = new BitmapDrawable(myBitmap);
The bitmap you are retrieving is immutable, meaning it can't be modified. Although it doesn't specify on the Canvas page that the constructor needs a mutable bitmap, it does.
To create a mutable bitmap, you can use this method.