Is it possible to extimate a Bitmap's size in memory before actually decoding it? I was running into OutOfMemoryErrors and wouldn't want to try an allocation if there's not enough heap space left. If I set BitmapFactory.Options.justDecodeBounts to true I will get the width and height of the resulting Bitmap:
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, o);
Log.d(getClass().getName(),"Input image size: "+o.outWidth+"x"+o.outHeight);
Is there an easy way to get to the Bitmap's size without having to calculate it from the dimensions?
I was going to work around the issue by multiplying width*height*4 for ARGB_8888 to get the byte count of the resulting image. This is fairly precise but the resulting Bitmap's byte count is a bit higher than that. How would I go about and take this overhead of the Bitmap Object into account? I am not planning to re-use the bitmap so this should hopefully work with KitKat's getAllocationByteCount().
Related
I have read multiple posts like this about memory usage of background image.
my background image is 2048x1365 59KB JPEG; its uncompressed bitmap is 11MB
the background on the view for the particular device would be 480x605, so usage would be 1.1MB (480x605x4)
my app originally uses 12MB without background image
placing the image in drawable-nodpi/ and set it in the layout XML cause the memory usage to 23MB; so exactly base + BMP size
Using BitmapFactory to decode the image (moved to raw/) according to the advice results in 33MB of memory usage. (See codes below.)
Codes to set the background
View view = findViewById(R.id.main_content);
Rect rect = new Rect();
view.getLocalVisibleRect(rect);
BitmapFactory.Options options = new BitmapFactory.Options();
options.outHeight = rect.height();
options.outWidth = rect.width();
options.inScaled = false;
Bitmap backgroundBitmap = BitmapFactory.decodeResource(getResources(), backgroundId, options);
view.setBackgroundDrawable(new BitmapDrawable(getResources(), backgroundBitmap));
What goes wrong? What else can I do to shrink the memory usage?
The trick to getting BitmapFactory to give you a low-memory image is to fill in inSampleSize on the BitmapFactory.Options. This tells BitmapFactory to downsample the image as it loads, giving you a lower-resolution image, but one that is better tuned to whatever use you plan to put it to. You would need to calculate the desired inSampleSize that you want, based on the resolution of the ImageView (or whatever) that you are using the image for.
This sample app demonstrates loading some images out of assets/ with different inSampleSize values.
I have experienced this too but with much smaller images. I found out of that this was happening because I was using the same image size for all screen resolutions. I recommend you have different sizes of the same image and put them in the appropriate folders.
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
I have a PNG image file with 2236x971px dimensions as a resource.
I want to scale it down by a factor of two (to its half). However, when i use this code:
BitmapFactory.Options bo = new BitmapFactory.Options();
bo.inSampleSize = 2;
Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, bo);
the decodedBitmap.getWidth() and decodedBitmap.getHeight() show width: 1677, height:728 → Only 25% reduction in size instead of expected 50%. Why is that so?
I am running the code on API 19.
The reason is that, your resource gets loaded according to your screen metrics. Put your image in the drawable-nodpi Folder or open an input stream to your resource and decode that input stream.
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++)
In my android application I am working on canvas bitmaps (actually it is SCanvas from Samsung SPen SDK, but it does not matter), as a rule 90-95% of area of such bitmaps is transparent, so I expected to have not too large bitmap size in KB. But seems like bitmap size (in KB) does not depend on whether it is a simple background or complex picture, so for example if I have two images (sorry, I am a new user and I can't post any images):
1) empty frame (1000x700 px, background is transparent, color border)
2) full frame (1000x700 px, background is transparent, color border, a lot of text inside)
the size of both bitmaps are about 1.3MB.
But if I convert these bitmaps in byte arrays, the size of the first array is about 11 times less than the size of the second array.
I have to store a lot of such images as BLOBs in DB and display them as ImageView bitmaps.
Question 1: if I need to display 20 ImageView objects with such 95% transparent images, is there any way to not use 20 * 1.3MB at the same time? To me it seems like it should be only 1 alpha layer + 20 "data" layers.
Question 2: is there any way to reduce size (in KB) of an image with transparency without losing too much quality? The only way I saw is to decode the image byte array with inSampleSize = 2 and then create a scaled bitmap to keep the original image dimensions, something like:
originalBitmap.compress(Bitmap.CompressFormat.PNG, 0, stream);
byte[] bitmapBytes = stream.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, options);
int bWidth = options.outWidth;
int bHeight = options.outHeight;
options.inSampleSize = 2;
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
options.inJustDecodeBounds = false;
Bitmap scaledBitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, options);
Bitmap finalBitmap = Bitmap.createScaledBitmap(scaledBitmap, bWidth, bHeight, true);
But the finalBitmap quality is unacceptable, no matter what options I used (inPreferredConfig, inDither).
Any other thoughts about the optimal way to handle the images with about 90% of simple transparent background and 10% of one-color "data"?
No matter what format you use, it will always end up as raw uncompressed (decoded) data before displaying, if it weren't decoded, you couldn't see it on the display. If you're worried about memory, try not to have all the images decoded all the time, just decode the one you have to show, and always release the others (don't keep a reference to them).