This may sound very specific... And it kinda is.
I really need to load a 2048x2048 32-bit image on Android 2.3 (specially Nexus One and Xperia Play, where the app in its current form crashes outright with a out of memory error).
Android 2.2 loads it fine, and all androids after 2.3 load it fine too, and some 2.3 devices (usually with huge heaps) also load it fine.
The Xperia Play in particular reports (via OpenGL) that it can load images up to 4096x4096, and using Marmalade SDK I really could load crazy huge images, and many of them, easily using 150mb of memory.
But I cannot figure how to load a 2048x2048 image with Java on that device, it just throws me this error:
05-07 17:41:25.202: E/GraphicsJNI(27847): VM won't let us allocate 30965760 bytes
EDIT: Stop telling me to use lower resolution, I will quote myself:
I really need to load a 2048x2048 32-bit image on Android 2.3
A lower sample image is not 2048x2048, neither is a higher one, or a scaled one, or a mipmap version, or anything else... I NEED TO LOAD THOSE IMAGES THE WAY THEY ARE, PERIOD.
Edit
Since you don't want to scale down the image, which is understandable, you may want to look into reducing the quality/sizeInMbs of the image you are loading to memory.
For instance, you stated you have an image of 2048*2048 which is taking approximately 30 mb, that is pretty big for an image that size.
Take a look into this demo from Romain Guy where he's using images that are 1280*752 and yet are only a few hundred kbs in size. Despite of this small size, the images look really clear and crisp on the working demo.
First of all, loading a 2048*2048 image into a small device screen such as those devices you mentioned is a waste of resources. Here's a link to a fully detailed tutorial on how to scale and load large images efficiently.
Keep in mind that you should try to scale images down when possible, Android Developer site mentions that: "An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling."
Step one, get the image dimens first, by using BitmapFactory.Options:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Step two, figure out a sampling factor: How much to scale the image down
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Finally you can scale it down using:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
30965760 bytes sounds pretty big for a resolution of 2048x2048.
Could it be that the image is 32-bit per channel instead of 8-bit per channel? A common mistake when I am asking people for a 32-bit PNG is that they select the 32bit/channel (128-bit image) mode in a program such as Photoshop.
Red (8-bit) + Green (8-bit) + Blue (8-bit) + Alpha (8-bit) = a 32-bit image.
Take a look using BitmapRegionDecoder to load in only the portions of the bitmap which would be visible (or will be visible soon) at any given time. See: Using BitmapRegionDecoder to load large images
You will have to scale down your bitmap to reduce memory consumption.
I advice you to look at the official documentation about this matter of Loading Large Bitmaps Efficiently.
As an extra advice, do not forget to recycle all of your unsed bitmaps. This will help GC to free that memory faster.
If you need the full resolution for whatever reason (as opposed to the reduced resolution in daniel_c05's answer) and you can't use OpenGL (why not? it's perfect for this),
Split it into tiles
Only load and render a few tiles at a time
If you need efficient zooming, pre-scale the image (mipmap) and tile each level.
But really, you should be using OpenGL if you're working at that scale and want speed.
Related
I'm trying to resize a bitmap using inDensity and inTargetDensity following #colt-mcanlis' instructions explained at 1, 2 and 3.
So far so good, good documentation, great video. The problem is that the resulting sizes for the image makes no sense to me.
For example if I use following values:
srcWidth is 11774px and srcHeight is 6340px
dstWidth is 1440px and dstHeight is 2392px
The code I'm using is:
options.inScaled = true;
options.inSampleSize = 8;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth * 8;
options.inSampleSize;
imageBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.image, options);
And the resulting image has width 70px and height 38px, instead 1440x2393.
I tried without using inSampleSize, and I get a very similar result. Then I assume the problem is with inTargetDensity and inDensity.
I went to the documentation and found the following:
inDensity
int inDensity
The pixel density to use for the bitmap...
As far as I know, to calculate a density I need a width, height and a display size but a display size doesn't make sense to me in this context, since I just want to calculate inDensity and inPixelDensity independent of a display size.
So, what am I doing wrong here ?
I was following Loading Large Bitmaps Efficiently by the book, but was running into the problem that the decoded bitmap ended up having way larger dimensions even than the original image (options.outWidth / options.outHeight).
I noticed that after the "decode bounds" step, inTargetDensity had a larger value than inDensity, and ultimately found that to be the cause of the larger decoded bitmap. Not sure exactly when playing with anything different on this fields would be useful...
But setting options.inTargetDensity = options.inDensity after the "decode bounds" step, worked for having the bitmap be decoded at the expected size (according to the inSampleSize you calculate).
Looking forward to the "more straightforward" API that Romain Guy announced in Google I/O (2018) :D
If you just want to resize an image while decoding, inSampleSize option is enough, but, because the aspect ratio of original and target images are not the same, you can't get the expected result through inSampleSize option directly, you need to do some extra crop operations after resizing. You can refer to the following guide for details:
http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
I want to crop image without getting OutOfMemory exception.
it means i have x, y, width and height of cropped image and want to crop original image without bringing it to memory.
Yes i know that BitmapRegionDecoder is good idea but maybe the cropped image would be too large for bringing it to memory.
In fact i don't want copped bitmap, just want to write cropped image from source file to destination file.
EDIT : I want to save cropped image not just showing it in an ImageView
I want to save it in a new file without losing dimensions
This is the example
in this situation cropped image resolution is 20000x20000 and code below wont work cause of OOM:
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
using inSampleSize to decrease the original picture size is good but the result i save is no longer 20000x20000.
How can i crop the 25000x25000 and save the 20000x20000 part of image in a file?
Simply put, it requires lots of low level programming and optimizations.
as you can see, lots of answers in this region are pointing to generic concepts of bitmap compression, etc which are indeed applicable in most issues but not specifically yours.
Also BitmapRegionDecoder as suggested in answers won’t work well. It sure prevents loading the whole bitmap in RAM but what about the cropped image? after cropping an image it gives you a giant bitmap which no matter what, gives you an OOM.
Because your problem as you described, needs Bitmaps to get written or get read from disk just as they get written or read from memory; something called a BufferedBitmap (or so) which efficiently handles the memory it requires by saving little chunks of a bitmap to disk and using them later, thus, avoiding OOM.
Any other solution which wants to tackle the problem with scaling only do half of the work. why? because cropped image itself can be too big for memory (as you said).
However, solving the problem by scaling isn’t that bad, if you don’t care about the quality of the cropped image compared to the quality user had seen when she was cropping it. that’s what the Google Photos do, it simply reduces the quality of cropped image, very simple!
I haven’t seen any BufferedBitmap classes around (but if there are, it would be awesome). They sure become handy for solving similar problems.
You can check Telegram messaging app which comes with an open-source implementation of image cropping facilities; you guess right, it handles all the similar nasty works with good old C... Hence, we might conclude that a good global solution (or better said, ONE OF THE SEVERAL APPLICABLE SOLUTIONS) appears to be low-level programming to handle disk and memory yourself.
I know my answer failed to give any copy-paste-ish solution to your problem but at least I hope it gave you some ideas my friend.
Did you checked BitmapRegionDecoder? It will extract a rectangle out of the original image.
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options);
mImageView.setImageBitmap(bitmap);
http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html
You can solve this using BitmapFactory. To determinate the original bitmap size without putting it in to memory, do the fallowing:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(..., options);
int originalImageWith = options.outWidth;
int originalImageHeight = options.outHeight;
Now you can use options.inSampleSize
If set to a value > 1, requests the decoder to
subsample the original image, returning a smaller image to save
memory. The sample size is the number of pixels in either dimension
that correspond to a single pixel in the decoded bitmap. For example,
inSampleSize == 4 returns an image that is 1/4 the width/height of the
original, and 1/16 the number of pixels. Any value <= 1 is treated the
same as 1. Note: the decoder uses a final value based on powers of 2,
any other value will be rounded down to the nearest power of 2.
Now it's not a perfect solution but you can do math to find what is the closest factor of 2 that you can use on options.inSampleSize to save memory.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(..., options);
BitmapRegionDecoder is the good way to crop big or large Images, but it's available from API 10 and above.
There is a class called BitmapRegionDecoder which might help you, but it's available from API 10 and above.
If you can't use it :
Many image formats are compressed and therefore require some sort of loading into memory.
You will need to read about the best image format that fits your needs, and then read it by yourself, using only the memory that you need.
a little easier task would be to do it all in JNI, so that even though you will use a lot of memory, at least your app won't get into OOM so soon since it won't be constrained to the max heap size that is imposed on normal apps.
Of course, since android is open source, you can try to use the BitmapRegionDecoder and use it for any device.
Reference :
Crop image without loading into memory
Or you can find some other way on below that might be helpful to you:
Bitmap/Canvas use and the NDK
Since that loading large bitmaps to show on a device screen in android(the correct way) is not a trivial task, I took a look on some tutorials on how to effectively make it. I'm already aware that you need to do the following in order to make a memory efficient image loader method:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; //do that to avoid loading all the information on the heap to decode
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
Acoording to the google tutorial you should make a sampled sized image, until this point I understand, you should make a method like this:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
And Finally you call the method like this:
imageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(), drawable.big_image, 200, 200));
And here is where my doubt lies: How to know the best size values according to the devices's screen size/resolution ? Is there any method that I can embed on my code which returns the optimal screen resolution to load an image without the pixelated effect and yet not too big that would blow up the VM heap ? This is one of the biggest challenges I'm facing on my project right now. I searched this link(and others I don't remember) but I couldn't find the answer I'm looking for.
As a suggestion ,
You can define the approx : width and height of your imageview as a portion of
device width and height in pixels. As ex:
imageview width = 0.8 of device width & imageview height = 0.4 of device height
Then you can calculate the device width and height in pixels. There by you can get the actual required imageview size according to relevant portion.
Then you can pass the calculated imageview width and height in to "decodeSampledBitmapFromResource" method.
I have the following code to create a canvas with a size of 8303 × 5540, but running that code produces a OutOfMemoryException.
scaledBitmap = Bitmap.createBitmap(8303, 5540, Bitmap.Config.ARGB_8888);
How can I resolve this problem?
Setting android:largeHeap="true" in AndroidManifest.xml helped me.
Well.. Creating a bitmap of that size, you would have to allocate about 183MB of memory. That will be a problem on most phones. You could try to set android:largeHeap="true" in your manifest, but still that will not give you enough memory on most phones.
If you are willing to accept a "subsampled" version of your image, and the image data is coming from file, you could take a look at http://developer.android.com/training/displaying-bitmaps/load-bitmap.html for loading subsamples of large images into memory. Basically, you can tell the BitmapFactory to load one out of every X pixels, thereby avoiding the requirement to have all 183MB of image data in memory.
http://codingaffairs.blogspot.com/2016/07/processing-bitmap-and-memory-management.html
Now here are tips which you can follow and can avoid out of memory exception in your Android Application.
Always use inSampleSize
Now what is inSampleSize ?
with the help of inSampleSize you are actually telling the decoder not to grab every pixel in memory, instead sub sample image.
This will cause less number of pixels to be loaded in memory than the original image. you can tell decoder to grab every 4th pixel or every second pixel from original image.
if inSampleSize is 4. decoder will return an Image that is 1/16 the number of pixels in original image.
so how much memory you have saved ? calculate :)
Read Bitmap Dimensions before loading into memory.
How reading bitmap dimensions before loading image into memory can help you avoid out of
memory error ? Let's Learn
use inJustBounds = true
here is technique with the help of which you can get image dimension beore loading it in memory
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Above code snippet will not give us any image/bitmap. it will return null for bitmap Object.
but it will definitely return width and height of that image. which is R.id.myimage.
Now you have width and height of Image. you can scale up or scale down image based on these factors:
ImageView size which will be used to display Image.
Available amount of memory. you can check available amount of memory using ActivityManager and getMemoryClass.
Screen size and density of device.
Use appropriate Bitmap Configuration
Bitmap configurations is color space/color depth of an Image. Default bitmap Configuration in Android is RGB_8888 which is 4 bytes per pixel.
If you use RGB_565 color channel which use 2 Bytes per pixel. half the memory allocation for same resolution :)
Use inBitmap property for recycling purpose.
Do not make static Drawable Object as it cannot be garbage collected.
Request large heap in in manifest file.
Use multiple processes if you are doing lot of image processing(memory intensive task) or use NDK (Native Development using c, c++)
I want to load images in Android, but if the image is just too large I wanted to resize it prior to loading . I learned that one can use the BitmapFactory to get just the size of the image, then one can figure out an appropriate scaling factor and use BitmapFactory to load the image with the required dimensions (per the very competent suggestions I found in this related thread).
So I tried that and it refused to work. I spent the last hour picking through the code trying to figure out why such a simple operation wasn't having any effect whatsoever (the scaling factor was being utterly ignored!)
Then I stumbled upon Android issue 3072... turns out this has been identified as broken for GIF files for well over two years. I realize GIF isn't exactly modern but it's still out there and in wide use (my test set has a lot of them, which is why it seemed uniformly broken until I found that bug report).
My question is, what can I use as an alternative to BitmapFactory that will correctly read and resize a GIF file when a scaling factor is employed without silently ignoring it? Memory constraints preclude loading large images directly for further processing, so this would need to be a streaming solution.
My other question is, how many others run into old, unfixed bugs like this and either don't realize it or worse, rely on the API to accurately perform the requested function and end up with memory leaks and other strange surprises?
I ended up using the solution posted here.
options.inSampleSize = 2;
bitmap = BitmapFactory.decodeStream(new URL(url).openStream(),null, options);
Bitmap scaledBitmap = scaleDown(bitmap, 1280, true);
bitmap = scaledBitmap;
Where scaleDown is:
public static Bitmap scaleDown(Bitmap realImage, float maxImageSize,
boolean filter) {
float ratio = Math.min(
(float) maxImageSize / realImage.getWidth(),
(float) maxImageSize / realImage.getHeight());
int width = Math.round((float) ratio * realImage.getWidth());
int height = Math.round((float) ratio * realImage.getHeight());
Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, width,
height, filter);
return newBitmap;
}