Should I recycle my bitmaps after a resize? - android

I've got a utility method (below) that resizes a bitmap and gives me back a new version. Since I am doing this with quite a few images & I wanted to reduce the chance of running out of memory, I've recycled the bitmap after usage.
This works fine on almost all devices. However, I've notice on the samsung galaxy tab 3 (10 inch) and the note 10.1 (2014) I'm getting the below stack traces:
java.lang.IllegalArgumentException: Cannot draw recycled bitmaps
at android.view.GLES20Canvas.drawBitmap(GLES20Canvas.java:756)
at android.view.GLES20RecordingCanvas.drawBitmap(GLES20RecordingCanvas.java:104)
Below is my resize code:
private static Bitmap resizeBitmap(int newWidth, int newHeight, Bitmap bitmapResize) {
if (bitmapResize == null) {
return null;
}
int width = bitmapResize.getWidth();
int height = bitmapResize.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmapResize,
newWidth, newHeight, true);
//SHOULD I DO THIS???
bitmapResize.recycle();
return resizedBitmap;
}
I have not figured out why almost all devices work, except those 2 (there may be more). The emulator shows no issues either.
It might be worth noting, not all images give me a "Cannot draw recycled bitmaps" error. Only some. But its consistently the same images.
(In case its of use, my app runs on 2.2 upwards)

I've managed to find the solution to my issue. It turns out the original image may be passed back as an optimisation, if the width/height of the resize match the original image.
I imagine on some devices my computations resulted in me trying to resize an image to its existing size. When i recycled the "old" bitmap, i was recyling the resized one too.
The solution is to change my code to say
if (bitmapResize!=resizedBitmap )
bitmapResize.recycle();
I found my issue covered in this conversation ( which i didn't find in my initial searching for the issue)
https://groups.google.com/forum/#!topic/android-developers/M6njPbo3U0c

I had a similar issue with on of ma games and from my experience you are doing the right thing. its the right place to recycle the old bitmap and will prevent OutOfMemoryExceptions, keep in mind that this instance of Bitmap will not be usable anymore.

You may simply use input bitmap reference as output. This way your input bitmap will be overwritten and you won't need to recycle it.
Kind of:
private static Bitmap resizeBitmap(int newWidth, int newHeight, Bitmap bitmapResize) {
if (bitmapResize == null) {
return null;
}
int width = bitmapResize.getWidth();
int height = bitmapResize.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
return Bitmap.createScaledBitmap(bitmapResize,
newWidth, newHeight, true);
}

Related

How to pixelize a bitmap in android

I have been searching but i did not find a question about how to pixelize a bitmap in android
Example
Note that i dont mean blur
You could just try and resize that image to a smaller version (and then back up if you need it at the same size as the original for some reason)
{
Bitmap original = ...;
//Calculate proportional size, or make the method accept just a factor of scale.
Bitmap small = getResigetResizedBitmap(original, smallWidth, smallHeight);
Bitmap pixelated = getResigetResizedBitmap(small, normalWidth, normalHeight);
//Recycle small, recycle original if no longer needed.
}
public Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// "RECREATE" THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(
bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
The code is from here
The simplest way to pixelate the image would be to scale image down using "nearest neighbour" algorithm, and then scale up, using the same algorithm.
Filtering over the image trying to find an average takes much more time, but does not actually give any improvements in result quality, after all you do intentionally want your image distorted.

High resolution background image gives performance issues in Android

I want to have a background image that scales to fit any screen size in Android. The image is static and doesn't need to scroll. I made the image at 4K resolution to cover what is a likely resolution to exist on tablets in the next 2-3 years (2560 x 1600 already exist). The image is a JPG with a 137KB file size. Similar resolution images seem to work fine in Android web browsers. Why am I getting a lot of slow down in Android (on Samsung Galaxy S3, which should have plenty of CPU/RAM to handle an image like this)? I don't feel like I am doing anything out of the ordinary.
This loads the image in the XML layout. The image is currently stored in drawable-nodpi.
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/logo_background"
android:scaleType="centerCrop" />
Making different size images for each category of screen resolution is difficult as I cannot find information on what the current maximum resolution for a device in each category is only a minimum.
I want to use the same background image again and again between a variety of fragments. Is there a way to have the image resized once to the width of the screen (preferably asynchronously) and then load that resized image each time? Could this be done with Picasso?
Please don't give answers like "of course larger images result in performance issues" or link me to Google's Supporting Different Densities. This is a real issue that is going to become more of an issue as screen resolutions continue to increase. I am amazed that handling and resizing large images is not already optimised in the ImageView class, which makes me think I am doing something wrong.
The problem is that what you are trying to do is not relying on the SDK. By having one image and having to change the image on runtime, you are causing more work to be done on the UI thread in onDraw().
Of course you would be able to create a Bitmap for a specific size, but why do such complicated work when you can rely on the SDK?
Currently there are a bunch of different folders that you can use in order to get what you are looking for, and then in the future you can get a 4k image put into a specific folder. Things like this might work:
drawable-xhdpi
drawable-xxhdpi
drawable-xlarge-xhdpi - May not be specific enough for what you are trying to accomplish
drawable-sw600dp - This allows you to specify a folder for an image where the screen width is greater than 600dp. This qualifier will probably be helpful for your case, in the future where you will be using 4k images.
You dont even need Picasso mate.Here you get the screen size:
LinearLayout layout = (LinearLayout)findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = layout.getMeasuredWidth();
int height = layout.getMeasuredHeight();
}
});
And here you resize your image with your new dimensions:
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth){
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
Using a matrix to resize is relatively fast. Although user1090347s answer would be best practice.
The problem is that android uses Bitmap to render images to canvas. It is like BMP image format for me. So, you have no gain from JPG format, cuz all information lost from jpg conversion are lost forever and you will end up will fullsize bitmap anyway. The problem with big resolution is that, you have to address few bytes for every pixel, no conversion applied! In particular, smaller devices have lower memory class as bigger ones. So, you have to handle the image resolution based on device screen size and memory class.
You can properly convert your background bitmap at runtime with these helper functions:
public void getScreenSizePixels(Resources resources, int widthHeightInPixels[/*2*/])
{
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
double screenWidthInPixels = (double)config.screenWidthDp * dm.density;
double screenHeightInPixels = screenWidthInPixels * dm.heightPixels / dm.widthPixels;
widthHeightInPixels[0] = (int)(screenWidthInPixels + .5);
widthHeightInPixels[1] = (int)(screenHeightInPixels + .5);
}
--
public static Bitmap getBitmap(byte[] imageAsBytes, int reqWidth, int reqHeight) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(
imageAsBytes,
0,
imageAsBytes.length,
opt);
int width = opt.outWidth;
int height = opt.outHeight;
int scale = 1;
while (reqWidth < (width / scale) || reqHeight < (height / scale)) {
scale++;
}
//bitmap.recycle();
opt.inJustDecodeBounds = false;
opt.inSampleSize = scale;
Bitmap bitmap = BitmapFactory.decodeByteArray(
imageAsBytes,
0,
imageAsBytes.length,
opt);
return bitmap;
}

How to resize image with custom size

I need to resize my image with custom size. The image is taken from device camera or gallery, i tired the below code, but the image is stretched, i need the image in square shape with out any stretch.
public Bitmap decodeSampledBitmapFromFile(Bitmap bm, int boundBoxInDp) {
boundBoxInDp=300;
int height = bm.getHeight();
int width = bm.getWidth();
float scaleWidth = ((float) boundBoxInDp) / width;
float scaleHeight = ((float) boundBoxInDp) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
return resizedBitmap;
}
If you already have a bitmap, you could use the following code to resize:
Bitmap originalBitmap = <original initialization>;
Bitmap resizedBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, false);
Or you can use following library for resize image
https://github.com/biokys/cropimage
This won't fit your image in a bounding box (the failure of which is presumably what you're calling "stretch"). It will not handle rectangular bitmaps in your square bounding box, nor will it handle images smaller than the bounding box particularly well. You probably want something like:
public Bitmap decodeSampledBitmapFromFile(Bitmap bm, int boundBoxInDp) {
boundSquareInPx=convertToPixels(300);
int maxDimen = Math.max(bm.getHeight(), bm.getWidth())
float scale = (maxDimen <= boundSquareInPx) ? 1 : boundSquareInPx / (float) maxDimen;
float scaleWidth = scale * bm.getWidth();
float scaleHeight = scale * bm.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, true);
return resizedBitmap;
}
Few notes: if you image is smaller than your bound it won't fit it- obvious modifications do that.
Secondly, dp != px; the Bitmap object returns px, so you're going to have to convert to px from dp (which is well documented elsewhere).
Use postTranslate(...) if you need to center the correspondingly cropped bitmap.
The documentation is here; this is allready the best library I know for resizing in Android- I've never needed anything else, and I've been in the game a while and work with this frequently.
If you need, in my opinion, the best introduction to working with the API efficiently: read the source code to ImageView and Drawable instances; a really worthwhile personal development exercise would be to use the SDK to implement a fading transition drawable that is center cropped, as that's rather annoyingly one of the only things missing from the Android library, and would involve a heck of a lot of the kind of coding you're trying to do above.
NB:
As you'll note, another answerer has pointed out the existence of createScaledBitmap, which is probably much clearer code; I just wanted to point out how what you were doing was basically right and how it could be improved.
Best.
Take a look at https://github.com/coomar2841/image-chooser-library/blob/d27b542d2487132b0150be382f39e9ef95aafe68/src/com/kbeanie/imagechooser/threads/MediaProcessorThread.java.
The method called compressAndSaveImage.

Setting wallpaper

Im trying to set my bitmap image as a wallpaper!
Im currently using this code to get it done.
WallpaperManager wpm = WallpaperManager.getInstance(this);
float minH = wpm.getDesiredMinimumHeight();
float minW = wpm.getDesiredMinimumWidth();
Log.d("seb", minH + " = Min Height");
Log.d("seb", minW + " = Min Width");
targetBitmap = Bitmap.createScaledBitmap(targetBitmap,(int)minW, (int)minH, false);
wpm.setBitmap(targetBitmap);
It works! The phone automatically resizes the bitmap to fit the screen, but no mather how small my bitmap is, it's always cropped horizontally and scaled up.
Does anyone know how to fix this?
(One fix would be to put a black border around and have them cropped instead of the actual picture, though Im guessing there is a better alternative )
EDIT
this is original picture in code.
The following picture is what I mean with cropped when set to wallpaper:
And this will be in the same way even if i resize the image since the system automatically enlarges the picture to fit the whole screen
Ok, so after our discussion in "comments", here is a solution which can work.
Try that:
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
Try it and if you have the same (or another) problem, let me know and we will try to fix it.

How to make an image small rather than its original size?

I am developing an animation app in the android.
i have one big image in application.
The image should start from the center of screen. The image size will be bigger at the initial stage. While moving to the left of the screen, its size should decrease(i.e. scaling should take place). Image should not go back to its original position. It should be placed at the leftside of the screen itself after the animation.
Can anyone please help.
Thanking you.
If I understand conrrectly you want to display an image and then animate it so that it appers to be reducing in size giving an affect that is going into the background.
You need to use ScaleAnimation
http://developer.android.com/reference/android/view/animation/ScaleAnimation.html
A good tutorial regarding android animation can be found at:
http://www.hascode.com/2010/09/playing-around-with-the-android-animation-framework/#Scale_Animations
this function resize the bitmap
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
matrix.postRotate(90);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
resizedBitmap.compress(Bitmap.CompressFormat.PNG, 70, bytes);
return resizedBitmap;
}

Categories

Resources