Picasso: Resize with keep aspect ratio

Created on 2 Sep 2013  路  20Comments  路  Source: square/picasso

I'd like to be able to resize an image by keeping a ratio aspect. Right now it is "complex" to do with a new Target and we are losing some great "build in" feature of Picasso.

One example would be for a resize on width:

Picasso.with(context).load(url).resize(400, -1).into(imageView);

Thanks

Most helpful comment

I'm just confirming that this worked for me. I have the imageView xml set to centerInside then i just specify a width, in my case it is the width of the screen, and then it maintains the aspect ratio. I just pass in 0 for the height.

Picasso.with(context).load(url).resize(width, 0).into(imageView);

All 20 comments

You could use a custom transformation to achieve the effect

private Transformation cropPosterTransformation = new Transformation() {

    @Override public Bitmap transform(Bitmap source) {
      int targetWidth = ...;
      double aspectRatio = (double) source.getHeight() / (double) source.getWidth();
      int targetHeight = (int) (targetWidth * 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 "cropPosterTransformation" + desiredWidth;
    }
  };

Thanks for the reply. We can also do it with a transform but it remains "complex" and requires to write "code".
My request is to make this simple which is the philosophy of this framework.

Thanks for feedback, we will consider adding an API for this. In the meantime what @f2prateek posted above should do.

This is exactly what centerInside() does.

Unless you're trying to resize an allow the bitmap to dictate the size in your layouts. Strange, but I've seen stranger.

  • The goal is to have my imageView to have a specific size by keeping aspect ratio. You only know the size (and ratio) when you fetch (remotely) the image.
  • In my case the image fetched remotely are smaller than the display area and I need increasing it display area based on the width (could be height). Unfortunately, I'm not using the transform method by @f2prateek because my image got degraded (due to the upscale I guess) and I'm using a layoutParms solution instead when I go the size information (but doing so I'm loosing some picasso features and optimization):
Picasso.with(getActivity()).load(story.image_url).into(new Target() {
        @Override
        public void onBitmapLoaded(Bitmap bitmap, LoadedFrom loadedFrom) {

            //whatever algorithm here to compute size
            float ratio = (float) bitmap.getHeight() / (float) bitmap.getWidth();
            float heightFloat = ((float) targetWidth) * ratio;

            final android.view.ViewGroup.MarginLayoutParams layoutParams = (MarginLayoutParams) imageView.getLayoutParams();

            layoutParams.height = (int) heightFloat;
            layoutParams.width = (int) targetWidth;
            imageView.setLayoutParams(layoutParams);
            imageView.setImageBitmap(bitmap);
        }

        @Override
        public void onBitmapFailed() {}
    });

As a sidenote, do not use anonymous Target classes as they might get gc'ed by the time download/decode has completed.

Thanks for this reply @dnkoutso.

Thumbor does this by allowing zero as the dimension you wish to scale with regard to aspect ratio. I'm inclined to follow suit.

I have been using the solution that @f2prateek proposed (I want to scale down the huge camera images to a maximum of 800 pixels on the _longest_ dimention), but unfortunately Crashlytics is telling me that some people are having OutOfMemory errors when Picasso does this.

    @Override
    public Bitmap transform(Bitmap source)
    {
        boolean isLandscape = source.getWidth() > source.getHeight();

        int newWidth, newHeight;
        if (isLandscape)
        {
            newWidth = MAX_DIMENSION_FOR_UPLOAD;
            newHeight = Math.round(((float) newWidth / source.getWidth()) * source.getHeight());
        } else
        {
            newHeight = MAX_DIMENSION_FOR_UPLOAD;
            newWidth = Math.round(((float) newHeight / source.getHeight()) * source.getWidth());
        }

        Bitmap result = Bitmap.createScaledBitmap(source, newWidth, newHeight, false);

        if (result != source)
            source.recycle();

        return result;
    }

Some high end devices take massive pictures (8MP cameras these days..), so I'm wondering if this proposed resize() change will alleviate this...

My above transformation sometimes crashes on high end devices with the following:

Fatal Exception: java.lang.OutOfMemoryError

android.graphics.Bitmap.nativeCreate (Bitmap.java)
android.graphics.Bitmap.createBitmap (Bitmap.java:812)
com.squareup.picasso.BitmapHunter.transformResult (BitmapHunter.java:455)
com.squareup.picasso.BitmapHunter.hunt (BitmapHunter.java:154)
com.squareup.picasso.RequestCreator.get (RequestCreator.java:279)

I too would love to not have to worry about writing my own custom transformation to perform what is effectively a resize(). :)

800 pixels on the longest dimention

Use .resize(800, 800).centerInside() for now.

Oh whoa, that works? I always figured that would distort the aspect ratio.

Does this solution work with .get()? I'm using .get() because I'm uploading the images - not placing them in an ImageView.

Both .centerInside() and .centerCrop() preserve aspect ratio during a resize.

And yes, it works with .get().

Thanks for the tip! I might point out that the Javadoc for centerInside doesn't explicitly mention that it preserves the aspect ratio, while the Javadoc for centerCrop _does_, hence my confusion.

Final question - is it more efficient (memory-wise) to use .resize(MAX_SIZE_LENGTH, MAX_SIZE_LENGTH).centerInside() compared to a transformation which achieves the same goal, such as the one I posted above?

is it more efficient (memory-wise) to use resize().centerInside() compared to a transformation.

Sometimes depending on the source image. If the image is massive, Picasso will downsample it while decoding since it knows the destination size. For example, in your 8MP image world Picasso decodes the full image before handing it to the transformation whereas when using resize we downsample the image to be smaller while decoding to be much smaller and then do a tiny resize to the final size.

Makes perfect sense. This is actually what I was doing myself (downsample during load, then resize after) before I decided to stop re-inventing the wheel and start using Picasso. Thanks for your help! You are a gentleman and a scholar.

@nicolaslauquin Your way is perfect but the code in ( onBitmapLoaded() method ) didn't call at all , why ??????

@MinaMohsen This might not be the place for asking such support in this thread. I have no idea about your problem but I would check what happen on the onBitmapFailed callback.

Closing.

I'm just confirming that this worked for me. I have the imageView xml set to centerInside then i just specify a width, in my case it is the width of the screen, and then it maintains the aspect ratio. I just pass in 0 for the height.

Picasso.with(context).load(url).resize(width, 0).into(imageView);

Was this page helpful?
0 / 5 - 0 ratings