Picasso — Image Rotation and Transformation

If you read all previous blog posts, you gained some comprehensive knowledge on how to load and handle images with Picasso. So far, we've always left the images untouched (except resizing and scaling to make it fit better). This week's blog post is all about manipulating the input image.

Picasso Series Overview

Image Rotation

Before we go to the advanced image transformations, there is one neat option you might need regularly: image rotation. Picasso has built-in support for rotating images and then displaying them. There are two options: simple and complex rotation.

Simple Rotation

The simple rotation call looks like this: rotate(float degrees). This simply rotates the image by the degrees you passed as parameter. A value between >0 and <360 degrees makes the most sense (0 and 360 leaving the image untouched). Let's look at a code example:

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .rotate(90f)
    .into(imageViewSimpleRotate);

This would rotate the image by 90 degrees.

Complex Rotation

By default, the rotation center ("pivot point") is at 0, 0. Sometimes you might need to rotate the image around a specific pivot point, which is not the standard rotation center. You can do this as well with the following call rotate(float degrees, float pivotX, float pivotY). The extended version would now look like this:

Picasso  
    .with(context)
    .load(R.drawable.floorplan)
    .rotate(45f, 200f, 100f)
    .into(imageViewComplexRotate);
     

Transformation

Rotation is just a tiny portion of possible image manipulation techniques. Picasso is agnostic enough to allow any image manipulation with the generic Transformation interface. You can implement a Transformation with implementing one major method: transform(android.graphics.Bitmap source). This method takes a bitmap, and returns the transformed one.

After you implemented your custom transformation, you can simply set it with transform(Transformation transformation) on your Picasso request. This will cause the image to be transformed before it'll be displayed.

Example #1: Blurring an Image

We've covered blurring of a single image (independent of Picasso) in a previous blog post. We optimized the code by getting inspired from two similar solutions (1, 2). The class would extend Transformation and implement the necessary methods:

public class BlurTransformation implements Transformation {

    RenderScript rs;

    public BlurTransformation(Context context) {
        super();
        rs = RenderScript.create(context);
    }

    @Override
    public Bitmap transform(Bitmap bitmap) {
        // Create another bitmap that will hold the results of the filter.
        Bitmap blurredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);

        // Allocate memory for Renderscript to work with
        Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
        Allocation output = Allocation.createTyped(rs, input.getType());

        // Load up an instance of the specific script that we want to use.
        ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setInput(input);

        // Set the blur radius
        script.setRadius(10);

        // Start the ScriptIntrinisicBlur
        script.forEach(output);

        // Copy the output to the blurred bitmap
        output.copyTo(blurredBitmap);

        bitmap.recycle();

        return blurredBitmap;
    }

    @Override
    public String key() {
        return "blur";
    }
}

Once again, adding the transformation to the Picasso request is super simple:

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .transform(new BlurTransformation(context))
    .into(imageViewTransformationBlur);

This will blur the image before it'll be displayed in the target ImageView.

Example #2: Blurring and Gray-Scaling an Image

Picasso also allows the parameter to be a list of Transformations: transform(List<? extends Transformation> transformations). That means you can apply a chain of transformations to the image.

Additionally to the blurring in the previous section, we've adding the gray-scaling transformation from the official Picasso sample. The implementation of the gray-scaling looks like this:

public class GrayscaleTransformation implements Transformation {

    private final Picasso picasso;

    public GrayscaleTransformation(Picasso picasso) {
        this.picasso = picasso;
    }

    @Override
    public Bitmap transform(Bitmap source) {
        Bitmap result = createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
        Bitmap noise;
        try {
            noise = picasso.load(R.drawable.noise).get();
        } catch (IOException e) {
            throw new RuntimeException("Failed to apply transformation! Missing resource.");
        }

        BitmapShader shader = new BitmapShader(noise, REPEAT, REPEAT);

        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.setSaturation(0);
        ColorMatrixColorFilter filter = new ColorMatrixColorFilter(colorMatrix);

        Paint paint = new Paint(ANTI_ALIAS_FLAG);
        paint.setColorFilter(filter);

        Canvas canvas = new Canvas(result);
        canvas.drawBitmap(source, 0, 0, paint);

        paint.setColorFilter(null);
        paint.setShader(shader);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));

        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

        source.recycle();
        noise.recycle();

        return result;
    }

    @Override
    public String key() {
        return "grayscaleTransformation()";
    }
}

Adding more than one transformation to a Picasso request is possible by building a List and passing it as a parameter:

List<Transformation> transformations = new ArrayList<>();

transformations.add(new GrayscaleTransformation(Picasso.with(context)));  
transformations.add(new BlurTransformation(context));

Picasso  
    .with(context)
    .load(UsageExampleListViewAdapter.eatFoodyImages[0])
    .transform(transformations)
    .into(imageViewTransformationsMultiple);

The transformations should give you enough tools to change the image according to your needs. There are two more facts you should know before implementing a custom Transformation:

  • just return the original, when no transformation required
  • when creating a new bitmap, call .recycle() on the old input bitmap.

Outlook

Transformations can be very powerful. They enable you do compute any possible image manipulation and let you apply it to the image with a single line of code. This is the peak of Picasso's extensive functionality. If you made it until here, you should know all important Picasso functionalities regarding images.

Next week, we'll take a closer look at the caching component of Picasso.

Explore the Library

Find interesting tutorials and solutions for your problems.