I've an image (3648x2736) around 4.19 MB(size in disk) and I wanted to load it in my application but it crashed as it should because of not enough memory. So to avoid these kind of crashes I put a validator before decoding the images(No, I do not want to use inSampleSize to make it smaller).
long maxMemory = Runtime.getRuntime().maxMemory();
long nativeUsage = Debug.getNativeHeapAllocatedSize();
long heapSize = Runtime.getRuntime().totalMemory();
long heapRemaining = Runtime.getRuntime().freeMemory();
long memoryLeft = maxMemory - (heapSize - heapRemaining) - nativeUsage;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
int bitmapSize = options.outHeight * options.outWidth * 4;
if (bitmapSize < memoryLeft)
return BitmapFactory.decodeFile(filePath);
Now one thing I want to make sure is, am I calculating bitmapSize properly ? Because the image file size is only 4.19 MB and memoryLeft was more than 8 MB, yet app crashed. That means it's storing every pixel as 4 bytes(PNGs), right ? then shouldn't it be 3 bytes for jpeg ? or is there something else I need to know ?
Since Bitmap is just a set of uncompressed pixels no matters what format it is - png or jpeg or else. Only factor you should remember is Bitmap.Config which describe color scheme for bitmap. For example Config.RGB_565 will take 2 bytes per pixel (5 bit red, 6 bit green, 5 bit green channel) and Config.ARGB_8888 will take 4 bytes per pixel (8 bits per each channel).
You can set Bitmap.Config while decoding image using BitmapFactory.Options.inPreferredConfig but as I understood from BitmapFactory.Options docs this is not guaranteed.
Related
Newbie question. I have a byte array (of total length 1920 X 1080 X 4), with every 4 bytes holding constant alpha value of 0x80 (~ 50% translucency) and RGB triplet, I would like to convert it to a bitmap using BitmapFactory. BitmapFactory always returns a null bitmap in the code below.
byte[] rgbImage; // allocated and populated with data elsewhere
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.outWidth = 1920;
opts.outHeight = 1080;
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeByteArray(rgbImage, 0, 1920 * 1080 * 4, opts);
if(bitmap == null){
Log.e("TAG","bitmap is null");
}
What am I doing wrong? It seems that since any byte takes values in the range 0..255, even an arbitrary byte array would qualify as a RGB image, provided its dimensions made sense.
BitmapFactory.decodeByteArray doesn't accept unsupported formats, so, ensure that the rgbImage byte data is one of the supported image formats. Follow the link to see which ones:
https://developer.android.com/guide/topics/media/media-formats#image-formats
In addition, althouh not the cause of the failure, outWidth / outHeight are not meant to be populated by you, instead these are populated by BitmapFactory when inJustDecodeBounds is set to True in order to find out an image dimensions without decoding it. Setting them does nothing at all, as these are output parameters. Also note that when setting inJustDecodeBounds to True, the return value will be always Null, as the request becomes for the image dimensions and not the image itself.
Also note that for the length argument in decodeByteArray, you can pass rgbImage.length instead of 1920 * 1080 * 4, if such buffer has the same length.
I know I can use these ways to get the Bitmap's size:
bitmap.getAllocationByteCount(); //API 19
bitmap.getByteCount(); //API 12
bitmap.getRowBytes() * bitmap.getHeight(); //earlier version
But This all need a Bitmap object which mean I need to decode the bitmap into memory before, this may case OOM Exception. So I use this way to get the size before I can get Bitmap object:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(bitmapPath, options);
int picWidth = options.outWidth;
int picHeight = options.outHeight;
int size = 4 * picHeight * picWidth; //Byte
I think it cost 32 bits per pixel because Android decode bitmap use RGB_8888 default.
Is this a correct way or is there a better way to do this ?
You can select the bitmap configuration with BitmapFactory.Options.inPreferredConfig. This will allow you to specify a configuration where you know for sure how many bytes per pixel will be occupied by the Bitmap. I believe RGB_8888 is the default.
You can probably not 100% reliably prevent an OOM on a bitmap decode since you don't have a guarantee for a set amount of contiguous free space in memory for the allocation of the Bitmap. But you can certainly adjust your sample size and config to reduce the load.
Just create a File object with the path to the Bitmap. Then get file.length() to get the file size.
e.g. File bitmap = new File(pathToBitmap);
bitmap.length();
You'll have to put a try/catch clause as required.
I am creating a bitmap and it takes about 11 mb in the heap , though it is of the small size. Well I wanted to know if I can create the bitmap and also sclae it as a same time. The reason I want to do it is , the memory allocation , as If I understand correctly from different bitmap questions which are posted here , and that is
The bitmap allocates the memory as when it is created
So if its , then scaling it again take some process time and also increase the heap size until and unless the garbage collection is not occurred
So what I am doing is
screenHeight = displaymetrics.heightPixels;
screenWidth = displaymetrics.widthPixels;
float aspectRatio = screenWidth / screenHeight;
int modifiedScreenHeight = 400;
int modifiedScreenWidth = (int) (modifiedScreenHeight * aspectRatio);
mBitmap = Bitmap.createBitmap(modifiedScreenWidth, modifiedScreenHeight, Bitmap.Config.ARGB_8888);
So now it is creating the bitmap and allocation the memory , by memory analyzer tool in android studio I can see that it took 11mb in memory.
But I want to minimize them ,I have visited a link and I want to do some more scaling by options as show in this video . but it uses the file to decode such as
BitmapFactory.decodeFile(??,options);
where as I have no file to decode from , I want to decode it from the bitmap I created and to wash away the last created bitmap to clear the memory.
Or if it is possible to set the options when creating it so that we can avoid from extra memory allocation .
Please help.
You can use this using BitmapFactory.Options - specifically, use the options to decode the width / height of the bitmap, then sampleSize to determine how large the generated bitmap will be.
According to your example, you'd like the width/height of the bitmap to be 400 by 400 * aspectRatio. So, first, you'll need to see how large the bitmap needs to be. Do this as so:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(??, options);
int bitmapWidth = options.outWidth;
int bitmapHeight = options.outHeight;
This action will only decode the bitmaps size, without actually allocating memory for the bitmap's pixels. This is good because it's a very quick and light operation which doesn't require much resources and helps you make a more educated decision when loading the bitmap. Now we must use these size to determine how big the generated bitmap will be.
int sampleSize = 1;
while (bitmapWidth / sampleSize > 400 && bitmapHieght / sampleSize > 400 * aspectRatio)
sampleSize *= 2;
sampleSize must be a power of 2 for this to work, and what it will do is determine how many pixels to "skip" when reading the bitmap into memory. This algorithm will set a sample size to a size equal to 1st sample size which will produce a bitmap immediately smaller than the required bounds. You can tweak this if you'd like a slightly different implementation.
Now that you have the sample size, set it with in the options object and load the actual bitmap:
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(??, options);
The generated bitmap will be smaller than the required bounds, thus limiting your memory requirements for creating the bitmap object.
Hope this helps.
I just added support for album art in my Android application and I've encountered a problem where displaying the album art in a layout causes the application memory to spike and the playback service is eventually killed to free up memory. I believe the issue is that I'm adding the extracted album art to the layout without compressing it. This results in a large image having to be cached in memory. The code I'm using to make the Bitmap is:
byte [] blob = mCursor.getBlob(mCursor.getColumnIndexOrThrow(Media.MediaColumns.PICTURE));
if (blob != null) {
return BitmapFactory.decodeByteArray(blob, 0, blob.length);
}
Is it possible to uniformly scale/compress these Bitmaps to reduce their memory footprint. Also, is there a way to compress directly using the byte array (rather then an inputstream).
Try this
Options opt = new Options();
opt.inSampleSize = 2;
if (blob != null) {
return BitmapFactory.decodeByteArray(blob, 0, length, opt)
}
More info about this
public int inSampleSize
Added in API level 1
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 will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. Also, powers of 2 are often faster/easier for the decoder to honor.
I have drawn an image manually on bitmap. But it has shown out of memory error.
I, then reduced the size of the image which shows me the compressed form of the image.
I need the original size of the image to be displayed which is then showing out of memory error. Kindly, help me out in resolving the issue.
I am enclosing the part of the code too.
ImageView iv1 = new ImageView(mcontext);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
BitmapFactory.decodeFile(pageViewManager.getPenToolPath() + pageViewManager.getCurrentPageIndex() + ".jpg");
bmp = BitmapFactory.decodeFile(pageViewManager.getPenToolPath() + pageViewManager.getCurrentPageIndex() + ".jpg", options);
iv1.setImageBitmap(bmp);
The memory space used by an uncompressed picture is easy to calculate: it's the number of pixels*the color depth, the number of pixels itself is height*width. For example, for an image of 1000*800 in 24 bits (3 bytes), the memory size will be:
1000*800*3=2400000 bytes or 2,4 Mb