In my application, on most devices, I am interested in loading and displaying a bitmap exactly as it exists in the resource file:
options.inScaled = false;
backgroundImage = BitmapFactory.decodeResource(getResources(), R.drawable.demo_frame, options);
On certain (small) devices, I want to reduce the size of the bitmap, so I call the following:
backgroundImage = Bitmap.createScaledBitmap(backgroundImage, Scale(backgroundImage.getWidth()), Scale(backgroundImage.getHeight()), false);
where the Scale function applies a scaling factor to shrink the image. The bitmap returned shows me the desired values in the getWidth() and getHeight() functions, but the image is displayed in its original size, i.e. without the scaling factor. I assume this has something to do with the options.inScaled parameter, but the documentation did not enlighten me. Can someone give a more detailed explanation of the inScaled parameter, and how I can override it in this case? Thanks.
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 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 am trying to work on a small requirement which provides users with an option to resize an image to a given percentage. Lets say 75% is the option. Does that mean I should resize the image size to 75% or the resolution of the image to 75%?
Any thoughts on this?
I wanted to use
Bitmap yourBitmap; resized =
Bitmap.createScaledBitmap(yourBitmap,(int)(yourBitmap.getWidth()*0.75),
(int)(yourBitmap.getHeight()*0.75), true);
For the newWidth and newHeight should I blindly pass calculate like above?
I think
image resolution is 100% like 100x100 or 200x200 or 1000x1000
user want to resize it to 75%
I think scaleX, scaleY
so on the end image must be 75% like 75x75 or 150x150 or 750x750
Within the help of Bitmap.createScaledBitmap you can resize your image even without losing your image quality
Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, wantedWidth, wantedHeight, true);
I hope it will work for you
You can implement as following way.
/* sample code */
Bitmap srcBitmap;
Bitmap destBitmap = Bitmap.createScaledBitmap(srcBitmap, (int)(srcBitmap.getWidth()*0.75), (int)(srcBitmap.getHeight()*0.75), true);
Hopefully...
What you consider to be 75% of an image is up to you. There are several posibilities. A nice variant is to resize to a predetermined size like 1024 for width if landscape and for height if portrait.
All the here suggested techniques can often not be used as they construct a full blown original bitmap first. Often memory for that will not be available.
So scale it down while loading. BitmapFactory will do it for you.
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 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++)