I'm getting an image (.png) from SQLiteDatabase and using this code to decode the bytearray into a bitmap:
Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDither = true;
options.inScaled = true;
options.inDensity = 240;
options.inTargetDensity = metrics.densityDpi;
Bitmap bmp = BitmapFactory.decodeStream(new ByteArrayInputStream(imageAsBytes), null, options);
As you can see, image (3) should be like (2), but it doesn't.
1) = Image with no scale (metrics.densityDpi = 240);
2) = same .png above, but compiled in res/drawable;
3) = Image with down scale (with metrics.densityDpi = 120);
I also tried options.inDither = false;, but I see no difference.
So what's wrong with my code?
There a few other things I would try:
Load the png with no scale, when you come to draw the Image (either from within an ImageView or directly onto the canvas) set a Matrix to scale the image
Alternatively, load the image in the required density and try drawing the Bitmap directly to the canvas with a Paint object. After instantiating your Paint, enable Bitmap filtering (this will increase the image quality)
setFilterBitmap(true)
Finally, you could always load the Bitmap (density independent) and resize the Bitmap manually using Bitmap.createScaledBitmap, make sure you set the third paramenter to true (this enabled bitmap filtering for increased quality). Below is an example of scaling a bitmap where 100 is the desired size:
Bitmap.createScaledBitmap ( original_bitmap, 100, 100, true);
Briefly, the best quality downscaling algorithm consists of 2 steps:
downscale using BitmapFactory.Options::inSampleSize->BitmapFactory.decodeResource() as close as possible to the resolution that you need but not less than it
get to the exact resolution by downscaling a little bit using Canvas::drawBitmap()
Here is detailed explanation how SonyMobile resolved this task: http://developer.sonymobile.com/2011/06/27/how-to-scale-images-for-your-android-application/
Here is the source code of SonyMobile scale utils: http://developer.sonymobile.com/downloads/code-example-module/image-scaling-code-example-for-android/
Related
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.
Can the code below cause OutOfMemory ? I think it allows to exceed the application memory limit.
Matrix matrix = new Matrix();
matrix.postRotate(orientation);
image = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), matrix, true);
What is the best way to rotate an image in Android ?
Allocate it in a service in a new process to get more heap?
#CommonsWare said in this link [1] that many developers think that more heap is a solution for inefficient coding.
This question indicates large heap too [2].
Is there a simpler solution ?
[1] Can you start an IntentService on a separate process?
[2] How to avoid OutOfMemory ex while rotating the image?
The short answer is, Yes, this code may cause OutOfMemory. I don't think that there is a simpler solution than increasing app heap size. I believe that #CommonsWare is right, and often OutOfMemory is an indication of wrong programming. But there are some situations when you need, ehm, huge memory. Rotation of a huge image is definitely one of such situations.
You can use native code (NDK) instead of asking for increased heap size, but this is definitely not easier. And it will still needs lots of memory, so there is no advantage in going for C++ (except that it works on 2.3).
If you wish to use an NDK based solution, I've created one here, and i've made a github project here .
this will avoid OOM by putting the data into the native C "world", recycle the old data, and return the result back, after rotation.
it doesn't require any downsampling.
OutOfMemoryException thrown when your bitmap is to large to load in memory.
Here I am giving you one solution.
BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap=BitmapFactory.decodeStream(is,null,options);
Use inSampleSize attribute of BitmapFactory.Options class.
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.
//decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=70;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
Here:
image = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), matrix, true);
code operates two bitmaps, the first image is a Bitmap you previosly loaded/decoded into RAM to work with - to rotate actually. And the second one will be created by Bitmap.createBitmap() no matter you store the result into the same variable. Anyway at this line you need bitmap x2 RAM and this causes the OOM for sure (speaking of device's camera biggest possible photos).
I think that using NDK is the best solution here.
Please check my very same question here for additional possible solution (MappedByteBuffer) and it has links to NDK/JNI solutions as well.
In my app, the bitmap is drawn as if the color is some lower quality type. If i load up the background image using the gallery app, it loads just fine and does not look like it's super low quality. The code i am using to load and draw my images is simple:
//Code for initializing the Bitmap
Bitmap bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.none), (int) (canvas.getWidth() * compression), (int) (canvas.getHeight() * compression), true);
//...
//Code for drawing this Bitmap
canvas.drawBitmap(bitmap, null, new RectF(0, 0, canvas.getWidth(), canvas.getHeight()), null);
If nothing in the code tells you what is wrong, i made an image comparing what the image actually looks like on a computer or other image viewer, and what it looks like in the app.
question is somewhat similar to Bad image quality after resizing/scaling bitmap
try disabling scaling, resize in an offscreen bitmap and make sure that Bitmap is 32 bits (ARGB888):
Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap source = BitmapFactory.decodeResource(a.getResources(), path, options);
another good and complete answer about image scaling/processing can be found at Quality problems when resizing an image at runtime
I have.png image file stored as a resource in my android application.
In my code, i am allocationg new Bitmap instance from that image as follow:
Bitmap img = BitmapFactory.decodeResource(getResources(), R.drawable.imgName);
But when I read the image dimensions from the Bitmap object using getWight() and getHeight() methods,
int width = img.getWidth();
int height = img.getHeight();
I am getting different results from the original image... Can some one explain me what am I missing, and how can I retreive the image size?
(My project is complied with android 2.2 - API 8)
Edit:
Ok - found out how to get the real dimensions:
setting inJustDecodeBounds property of the BitmapFactory.Options class to true as follow:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.imgName, options);
width = options.outWidth;
height = options.outHeight;
The problem now is that the decoder returns null when we send Options argument, so I need to decode again like I did before (without Options argument...) to retrieve Bitmap instance -bizarre, isnt it?
To get exact resource image use:
BitmapFactory.Options o = new Options();
o.inScaled = false;
Bitmap watermark = BitmapFactory.decodeResource(context.getResources(), id, o);
This turns off the automatic screen density scaling.
Update:
I'm sure you realized this by now, but inJustDecodeBounds does just that, it finds the dimensions. You will not get an image. That option is generally for doing custom scaling. You end up calling decodeResource twice, the second time setting:
options.inJustDecodeBounds = false;
and making any adjustments to the options based on your:
width = options.outWidth;
height = options.outHeight;
Android scales your image for different densities (in a way for different screen resolutions and sizes). Place a separate copy of your image in drawable-ldpi, drawable-hdpi,drawable-xhdpi , drawable folders.
I am downloading images from web and i use Gallery widget to display the images.
If the downloaded image size is huge, my application crashes with the below log.
"E/GraphicsJNI( 3378): VM won't let us allocate 5591040 bytes"
I want to scale down the downloaded image size only when the image size is more to an extent that it will crash the app. I have written the code to scale down the image size but i am not sure how to find the bitmap size so i can decide on whether to scale or not
BitmapFactory.Options o = new BitmapFactory.Options();
o.inSampleSize = 2;
Bitmap bit = BitmapFactory.decodeStream(inputStream,null,o);
Bitmap scaled = Bitmap.createScaledBitmap(bit, 200, 200, true);
bit.recycle();
return scaled;
To get bitmap dimensions, you can simply use:
To get height -> bitmap.getHeight()
To get width -> bitmap.getWidth()
Use inJustDecodeBounds field of BitmapFactory.Options to get bitmap dimensions.