I'm using Universal Image Loader to:
Load image synchronously
Fix orientation
Crop center (this is not working)
Do all this with a low memory footprint
I don't want to scale a bitmap by myself if I can do it with Universal Image Loader. The reason I ask this question is because I don't know wether this is possible or not with Universal Image Loader.
I don't want (or need) to use an ImageView to load the bitmap I simply need a bitmap, but crop center it on the fly. Is this possible with UIL? I have gone through the issues on GH, through questions on SO and can't find an example for it.
This is the code:
private Bitmap getBitmap(Uri uri, int width, int height) {
DisplayImageOptions options = new DisplayImageOptions.Builder()
.considerExifParams(true)
.cacheOnDisk(true)
.build();
ImageSize size = new ImageSize(width, height);
return mImageLoader.loadImageSync(uri.toString(), size, options);
}
Thank you all!
I think that that's what BitmapProcessors are for (although I haven't used the lib myself).
Here's what I would do:
// preProcessor gets to work before image gets cached, so cache will contain the cropped
// version
[...Builder...].preProcessor(new BitmapProcessor() {
public Bitmap process (Bitmap src) {
// Roughly assuming that the bitmap is bigger than the needed dimensions
// but you get the idea
int xOffset = (src.getWidth() - width) / 2;
int yOffset = (src.getHeight() - height) / 2;
Bitmap result = Bitmap.createBitmap(src, xOffset, yOffset, width, height);
// not sure whether this is necessary or the calling logic does it
// my guess is it does 'cause it's a simple check [if (src != result) src.recycle();]
// and the lib is rather smart altogether. Check the docs, they probably mention this
src.recycle();
return result;
}
})
Related
Here is my problem: I'm asking my user to choose between different sizes of a same image in order to upload it. My interface shows 4 buttons with each giving informations about the width, the height of the image, and the length of the file.
I managed to produce the four versions of the image calling Bitmap.createScaledBitmap, then compressing those bitmaps to files again.
I'm well aware I need to recycle the bitmap and delete the files when I'm done.
However, when trying to compute a big file, the method createScaledBitmap throws an out of memory exception.
I was told to use the option inJustDecodeBounds to decode the file but it would return a null bitmap.
Does someone have an idea on how to solve this problem?
Here is the code:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.RGB_565;
Bitmap bmp = getScaledBitmap(BitmapFactory.decodeFile(uri, options), ratio);
private Bitmap getScaledBitmap(Bitmap bmp, float ratio) {
int srcWidth = bmp.getWidth();
int srcHeight = bmp.getHeight();
int dstWidth = (int)(srcWidth * ratio);
int dstHeight = (int)(srcHeight * ratio);
Bitmap result = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight, true);
bmp.recycle();
return result;
}
To answer your replies, I don't consider it to be a duplicate. I previously found the thread you linked but it's quite old and it didn't solve my problem.
Anyway, I solved the problem.
I first tried using Glide library which is said to be better for memory management. Though, there was an improvement but it wasn't perfect and the problem occured again.
Using android:largeHeap="true" put an end to my nightmare. This has to be set in the application field of your manifest.
I hope this will help someone else.
I have a high resolution image (2588*1603) in drawable folder. If I use below code (1) to set it for the imageView I do not get OOM exception and the image assigned as expected:
public class MainActivity extends ActionBarActivity{
private ImageView mImageView;
int mImageHeight = 0;
int mImageWidth = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.imageView);
mImageView.setScaleType(ScaleType.FIT_CENTER);
BitmapFactory.Options sizeOption = new BitmapFactory.Options();
sizeOption.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.drawable.a, sizeOption);
mImageHeight = sizeOption.outHeight;
mImageWidth = sizeOption.outWidth;
mImageView.post(new Runnable() {
#Override
public void run() {
try {
BitmapRegionDecoder bmpDecoder = BitmapRegionDecoder
.newInstance(getResources().openRawResource(R.drawable.a),true);
Rect rect = new Rect(0,0,mImageWidth, mImageHeight);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inDensity = getResources().getDisplayMetrics().densityDpi;
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
mImageView.setImageBitmap(bmp);
} catch (NotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
Note that rect size is exactly the same as image size.
But If I use other methods like for example 2 or 3 I get OOM.
2) mImageView.setBackgroundResource(R.drawable.a);
3) Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
mImageView.setImageBitmap(bmp);
What is the difference between 1 and 2,3 ?
(I know how to solve OOM, I just want to know the difference)
This is the source of BitmapRegionDecoder#decodeRegion:
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
checkRecycled("decodeRegion called on recycled region decoder");
if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
|| rect.bottom > getHeight())
throw new IllegalArgumentException("rectangle is not inside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, options);
}
As you can see, it simply calls a native method. I do not understand enough C++ to see whether the method scales the bitmap down (according to your inDensity flag).
The other two methods use the same native method (nativeDecodeAsset) to get the bitmap.
Number 2 caches the drawable and thus needs more memory. After lots of operations (checking if the bitmap is already preloaded or cashed and other things), it calls a native method to get the bitmap. Then, it caches the drawable and sets the background image.
Number 3 is pretty straight forward, it calls a native method after a few operations.
Conclusion: For me, it is hard to say which scenario applies here, but it should be one of these two.
Your first attemp scales the bitmap down (the inDensity flag) and thus needs less memory.
All three methods need more or less the same amount of memory, number 2 and 3 just a little bit more. Your image uses ~16MB RAM, which is the maximum heap size on some phones. Number 1 could be under that limit, while the other two are slightly above the threshold.
I suggest you to debug this problem. In your Manifest, set android:largeHeap="true" to get more memory. Then, run your 3 different attemps and log the heap size and the bytes allocated by the bitmap.
long maxMemory = Runtime.getRuntime().maxMemory();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long freeMemory = maxMemory - usedMemory;
long bitmapSize = bmp.getAllocationByteCount();
This will give you a better overview.
Ok, down to core, single difference between 1 and 2,3 is that 1 doesn't support nine patches and purgeables. So most probably a bit of additional memory allocated for NinePatchPeeker to work during decoding is what triggers OOM in 2 and 3 (since they use same backend). In case of 1, it isn't allocated.
Other from that i don't see any other options. If you look at image data decoding, then tiled decoding uses slightly more memory due to image index, so if it was the case, situation would be opposite: 1 will be throwing OOMs and 2,3 is not.
Too many detail of the picture results the out of memory.
summary: 1 use the scaled bitmap; 2,3 load the full detailed drawable(this results the out of memory) then resize and set it to imageview.
1
Bitmap bmp = bmpDecoder.decodeRegion(rect, options);
the constructor(InputStream is, boolean isShareable) use the stream , which will not exhaust the memory.
use BitmapFactory.Options and BitmapRegionDecoder will scale down the bitmap.
Refer: BitmapRegionDecoder will draw its requested content into the Bitmap provided, clipping if the output content size (post scaling) is larger than the provided Bitmap. The provided Bitmap's width, height, and Bitmap.Config will not be changed
2,3
Drawable d = mContext.getDrawable(mResource);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
there is no scale option, the whole picture will load to memory
Sorry for English.
Maybe help you.
You are not getting OOM exception because of this
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
It is already given here
public Bitmap.Config inPreferredConfig
Added in API level 1
If this is non-null, the decoder will try to decode into this internal configuration. If it is null, or the request cannot be met, the decoder will try to pick the best matching config based on the system's screen depth, and characteristics of the original image such as if it has per-pixel alpha (requiring a config that also does). Image are loaded with the ARGB_8888 config by default.
I have a list of images that I have to download from the server and show them in a listView in android app. They are of varying ratio, and since I would to make each ImageView to match the device's width, I cannot fix their height.
I also know that I could improve the performance by downsizing the image before downloading, just as what Picasso is doing with fit() and resize().
I am using Picasso for my project. The problem is, I cannot use Picasso's fit() and resize() methods, which are methods to downsize image before downloading, if I do not know the height of the imageView beforehand.
So I tried with other approaches. I am wondering if transform would do the downsizing as well?
new Transformation() {
#Override
public Bitmap transform(Bitmap source) {
int targetHeight = deviceWidth;
double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
int targetWidth = (int) (targetHeight / aspectRatio);
Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
if (result != source) {
// Same bitmap is returned if sizes are the same
source.recycle();
}
return result;
}
#Override
public String key() {
return "transformation" + " desiredWidth";
}
Many Thanks.
You cannot "downsize the image before downloading".
Picasso (and everything else) downloads the original image as-is, and only then does calculations to scale the image according to the container's dimensions.
If you don't use neither fit() nor resize(), Picasso will load the image at full resolution: that is, the resolution of the original image.
You can use Thumbor service to crop and resize images before downloading. Square has a nice Java client for it - Pollexor. And special Picasso RequestTransformer for Pollexor. That said, you'll get all the preprocessing extracted to the backend.
I want to have a background image that scales to fit any screen size in Android. The image is static and doesn't need to scroll. I made the image at 4K resolution to cover what is a likely resolution to exist on tablets in the next 2-3 years (2560 x 1600 already exist). The image is a JPG with a 137KB file size. Similar resolution images seem to work fine in Android web browsers. Why am I getting a lot of slow down in Android (on Samsung Galaxy S3, which should have plenty of CPU/RAM to handle an image like this)? I don't feel like I am doing anything out of the ordinary.
This loads the image in the XML layout. The image is currently stored in drawable-nodpi.
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/logo_background"
android:scaleType="centerCrop" />
Making different size images for each category of screen resolution is difficult as I cannot find information on what the current maximum resolution for a device in each category is only a minimum.
I want to use the same background image again and again between a variety of fragments. Is there a way to have the image resized once to the width of the screen (preferably asynchronously) and then load that resized image each time? Could this be done with Picasso?
Please don't give answers like "of course larger images result in performance issues" or link me to Google's Supporting Different Densities. This is a real issue that is going to become more of an issue as screen resolutions continue to increase. I am amazed that handling and resizing large images is not already optimised in the ImageView class, which makes me think I am doing something wrong.
The problem is that what you are trying to do is not relying on the SDK. By having one image and having to change the image on runtime, you are causing more work to be done on the UI thread in onDraw().
Of course you would be able to create a Bitmap for a specific size, but why do such complicated work when you can rely on the SDK?
Currently there are a bunch of different folders that you can use in order to get what you are looking for, and then in the future you can get a 4k image put into a specific folder. Things like this might work:
drawable-xhdpi
drawable-xxhdpi
drawable-xlarge-xhdpi - May not be specific enough for what you are trying to accomplish
drawable-sw600dp - This allows you to specify a folder for an image where the screen width is greater than 600dp. This qualifier will probably be helpful for your case, in the future where you will be using 4k images.
You dont even need Picasso mate.Here you get the screen size:
LinearLayout layout = (LinearLayout)findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int width = layout.getMeasuredWidth();
int height = layout.getMeasuredHeight();
}
});
And here you resize your image with your new dimensions:
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth){
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
Using a matrix to resize is relatively fast. Although user1090347s answer would be best practice.
The problem is that android uses Bitmap to render images to canvas. It is like BMP image format for me. So, you have no gain from JPG format, cuz all information lost from jpg conversion are lost forever and you will end up will fullsize bitmap anyway. The problem with big resolution is that, you have to address few bytes for every pixel, no conversion applied! In particular, smaller devices have lower memory class as bigger ones. So, you have to handle the image resolution based on device screen size and memory class.
You can properly convert your background bitmap at runtime with these helper functions:
public void getScreenSizePixels(Resources resources, int widthHeightInPixels[/*2*/])
{
Configuration config = resources.getConfiguration();
DisplayMetrics dm = resources.getDisplayMetrics();
double screenWidthInPixels = (double)config.screenWidthDp * dm.density;
double screenHeightInPixels = screenWidthInPixels * dm.heightPixels / dm.widthPixels;
widthHeightInPixels[0] = (int)(screenWidthInPixels + .5);
widthHeightInPixels[1] = (int)(screenHeightInPixels + .5);
}
--
public static Bitmap getBitmap(byte[] imageAsBytes, int reqWidth, int reqHeight) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(
imageAsBytes,
0,
imageAsBytes.length,
opt);
int width = opt.outWidth;
int height = opt.outHeight;
int scale = 1;
while (reqWidth < (width / scale) || reqHeight < (height / scale)) {
scale++;
}
//bitmap.recycle();
opt.inJustDecodeBounds = false;
opt.inSampleSize = scale;
Bitmap bitmap = BitmapFactory.decodeByteArray(
imageAsBytes,
0,
imageAsBytes.length,
opt);
return bitmap;
}
I need to display original image in full screen in gallery form. For thumb it will be work perfectly and when I try to display that image in full screen with original source it will not be able to display. In most cases if the image resolution is greater then 2000 then it will display error bitmap too large to be uploaded into a texture android.
I want to prevent this, I have search google but not getting any answer regarding this.
I came across the same problem and came up with a one liner solution for this problem here:
Picasso.with(context).load(new File(path/to/File)).fit().centerCrop().into(imageView);
i just created a if else function to check if the image is bigger than 1M pixels here's the sample code:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
selectedImagePath = getPath(selectedImageUri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeFile(selectedImagePath);
int height = bitmap.getHeight(), width = bitmap.getWidth();
if (height > 1280 && width > 960){
Bitmap imgbitmap = BitmapFactory.decodeFile(selectedImagePath, options);
imageView.setImageBitmap(imgbitmap);
System.out.println("Need to resize");
}else {
imageView.setImageBitmap(bitmap);
System.out.println("WORKS");
}
Google provided a training how to do that. Download the sample from Displaying Bitmaps Efficiently
Take a look to ImageResizer class.
ImageResizer.decodeSampledBitmapFrom* use this method to get downscaled image.
This is the code I used to rectify my problem of fitting an image of size 3120x4196 resolution in an image view of 4096x4096 resolution. Here ImageViewId is the id of the image view created in the main layout and ImageFileLocation is the path of the image which is to be resized.
ImageView imageView=(ImageView)findViewById(R.id.ImageViewId);
Bitmap d=BitmapFactory.decodeFile(ImageFileLcation);
int newHeight = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
Bitmap putImage = Bitmap.createScaledBitmap(d, 512, newHeight, true);
imageView.setImageBitmap(putImage);
You don't need to load the whole image, cause it's too large and probably your phone won't able to show the full bitmap pixels.
You need to scale it first according to your device screen size.
This is the best method that I found and it works pretty good:
Android: Resize a large bitmap file to scaled output file
I found a way to do this without using any external libraries:
if (bitmap.getHeight() > GL10.GL_MAX_TEXTURE_SIZE) {
// this is the case when the bitmap fails to load
float aspect_ratio = ((float)bitmap.getHeight())/((float)bitmap.getWidth());
Bitmap scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0,
(int) ((GL10.GL_MAX_TEXTURE_SIZE*0.9)*aspect_ratio),
(int) (GL10.GL_MAX_TEXTURE_SIZE*0.9));
imageView.setImageBitmap(scaledBitmap);
}
else{
// for bitmaps with dimensions that lie within the limits, load the image normally
if (Build.VERSION.SDK_INT >= 16) {
BitmapDrawable ob = new BitmapDrawable(getResources(), bitmap);
imageView.setBackground(ob);
} else {
imageView.setImageBitmap(bitmap);
}
}
Basically, the maximum image dimensions are a system-imposed limit. The above approach will correctly resize bitmaps that exceed this limit. However, only a portion of the entire image will be loaded. To change the region displayed, you can alter the x and y parameters of the createBitmap() method.
This approach handles bitmaps of any size, including pictures taken with professional cameras.
References:
Android Universal Image Loader.