Picasso - Image downsizing before downloading for better performance - android

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.

Related

High resolution background image gives performance issues in Android

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;
}

Universal Image Loader: Crop center image loading synchronously (no imageview) (Android, UIL)

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;
}
})

Load large images with Picasso and custom Transform object

I'm getting an Out Of Memory exception using Picasso when loading "large" images (> 1.5MB) from Android Gallery (using startActivityForResult).
I'm using a custom Target object because I need to preprocess the Bitmap when it is ready and also I'm using a custom Transform object to scale the Bitmap.
The problem is that the method public Bitmap transform(Bitmap source) on my Transform object is never called because the Out Of Memory Exception, so I don't get the oportunity to resample the image.
But, if I use the .resize(maxWidth, maxHeight) method, then it loads the image OK. I supossed that the Transform object was for that purpose too, but it seems that the transform method is called after the resize, and, if I don't call resize, then it will end in an Out of Memory..
The problem is that with resize I need to specify both width and height, but I need to scale and keep the aspect ratio.
Consider that the images will be selected from user Gallery, so they can be bigger or smaller, portrait, squared or landscape, etc, so I need my own Transformation object to perform the logic that needs my application.
I found a solution..
In my Transform object I needed to scale the image (keeping aspect ratio) to 1024 x 768 max.
Transform object was never called unless I call .resize(width, height) to resample down the image.
For keeping aspect ratio and using resize I call .centerInside(). This way image will be scaled resample to fit width, height).
The value that I give to .resize(width, height) is Math.ceil(Math.sqrt(1024 * 768)).
This way I'm sure to have an image higher enough for my custom Transform object, and also avoid Out Of Memory exception
Update: Full example
Following this example you will get a image that fits inside MAX_WIDTH and MAX_HEIGHT bounds (keeping aspect ratio)
private static final int MAX_WIDTH = 1024;
private static final int MAX_HEIGHT = 768;
int size = (int) Math.ceil(Math.sqrt(MAX_WIDTH * MAX_HEIGHT));
// Loads given image
Picasso.with(imageView.getContext())
.load(imagePath)
.transform(new BitmapTransform(MAX_WIDTH, MAX_HEIGHT))
.skipMemoryCache()
.resize(size, size)
.centerInside()
.into(imageView);
And this is my custom BitmapTransform class:
import android.graphics.Bitmap;
import com.squareup.picasso.Transformation;
/**
* Transformate the loaded image to avoid OutOfMemoryException
*/
public class BitmapTransform implements Transformation {
private final int maxWidth;
private final int maxHeight;
public BitmapTransform(int maxWidth, int maxHeight) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
#Override
public Bitmap transform(Bitmap source) {
int targetWidth, targetHeight;
double aspectRatio;
if (source.getWidth() > source.getHeight()) {
targetWidth = maxWidth;
aspectRatio = (double) source.getHeight() / (double) source.getWidth();
targetHeight = (int) (targetWidth * aspectRatio);
} else {
targetHeight = maxHeight;
aspectRatio = (double) source.getWidth() / (double) source.getHeight();
targetWidth = (int) (targetHeight * aspectRatio);
}
Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
if (result != source) {
source.recycle();
}
return result;
}
#Override
public String key() {
return maxWidth + "x" + maxHeight;
}
}
There's a very good article on the Android Developer website which helped me alot when I had the same problem with loading large images.
This article explains really well what causes the error and how to solve it. There are also other articles (see menu) to, for example, cache images.

Prevent bitmap too large to be uploaded into a texture android

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.

creating scaled bitmap leads to invalid image

I've created a function that scales a bitmap directly to a specific surface area. The function first gets the width and height of the bitmap and then finds the sample size closest to the required size. Lastly the image is scaled to the exact size. This is the only way I could find to decode a scaled bitmap. The problem is that the bitmap returned from BitmapFactory.createScaledBitmap(src,width,height,filter) always comes back with a width and height of -1. I've already implemented other functions that use the createScaledBitmap() method with out this error and I can not find any reason why creating a scaled bitmap would produce invalid output. I've also found that if I create a copy of the image bitmap that is mutable causes the same error. Thanks
public static Bitmap load_scaled_image( String file_name, int area) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file_name, options);
double ratio = (float)options.outWidth / (float)options.outHeight;
int width, height;
if( options.outWidth > options.outHeight ) {
width = (int)Math.sqrt(area/ratio);
height = (int)(width*ratio);
}
else {
height = (int)Math.sqrt(area/ratio);
width = (int)(height*ratio);
}
BitmapFactory.Options new_options = new BitmapFactory.Options();
new_options.inSampleSize = Math.max( (options.outWidth/width), (options.outHeight/height) );
Bitmap image = BitmapFactory.decodeFile(file_name, new_options);
return Bitmap.createScaledBitmap(image, width, height, true);
}
I added this function to scale large camera images to a specific number of mega pixels. So a typical area passed in would be 1000000 for 1 megapixel. The camera image after being decoded yields a outWidth of 1952 and a outHieght of 3264. I then calculate the ratio this way I can keep the same height to width ratio with the scaled image, in this case the ratio is 0.598... Using the ratio and the new surface area I can find the new width which is 773 and a height of 1293. 773x1293=999489 which is just about 1 megapixel. Next I calculate the sample size for which to decode the new image, in this case the sample size is 4 and the image is decoded to 976x1632. So I'm passing in a width of 773 a height of 1293.
I was having a similar problem (getting -1 for height and width of the scaled bitmap).
Following this stackOverflow thread:
Android how to create runtime thumbnail
I've tried to use the same bitmap twice while calling the function:
imageBitmap = Bitmap.createScaledBitmap(imageBitmap, THUMBNAIL_SIZE,
THUMBNAIL_SIZE, false);
For some reason, this solved my problem, perhaps it would solve yours too.

Categories

Resources