Wednesday, 16 December 2009

Android, Reflections with Bitmaps


Looking to add some polish to you're applications. Maybe enough to see a bit of a reflection? ;)  Lots of applications use the effect of creating a reflection of the original image, one of those being the coverflow view on the iphone. It's a nice bit of presentation polish to add to your UI, when you know how is not difficult to implement. In Android you'll need to make use of a number of classess in the graphics package such as Canvas, Matrix, Bitmap and a few others.
So without further ado, here's the source code of how to create an Image with a reflection
package com.example.reflection;


import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import android.graphics.Shader.TileMode;
import android.os.Bundle;
import android.view.WindowManager.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class Reflection extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        //The gap we want between the reflection and the original image
        final int reflectionGap = 4;
        
        //Get you bit map from drawable folder
        Bitmap originalImage = BitmapFactory.decodeResource(getResources(), 
                R.drawable.killers_day_and_age);
        
        int width = originalImage.getWidth();
        int height = originalImage.getHeight();
        
       
        //This will not scale but will flip on the Y axis
        Matrix matrix = new Matrix();
        matrix.preScale(1, -1);
        
        //Create a Bitmap with the flip matix applied to it.
        //We only want the bottom half of the image
        Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height/2, width, height/2, matrix, false);
        
            
        //Create a new bitmap with same width but taller to fit reflection
        Bitmap bitmapWithReflection = Bitmap.createBitmap(width 
          , (height + height/2), Config.ARGB_8888);
      
       //Create a new Canvas with the bitmap that's big enough for
       //the image plus gap plus reflection
       Canvas canvas = new Canvas(bitmapWithReflection);
       //Draw in the original image
       canvas.drawBitmap(originalImage, 0, 0, null);
       //Draw in the gap
       Paint deafaultPaint = new Paint();
       canvas.drawRect(0, height, width, height + reflectionGap, deafaultPaint);
       //Draw in the reflection
       canvas.drawBitmap(reflectionImage,0, height + reflectionGap, null);
       
       //Create a shader that is a linear gradient that covers the reflection
       Paint paint = new Paint(); 
       LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0, 
         bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, 
         TileMode.CLAMP); 
       //Set the paint to use this shader (linear gradient)
       paint.setShader(shader); 
       //Set the Transfer mode to be porter duff and destination in
       paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); 
       //Draw a rectangle using the paint with our linear gradient
       canvas.drawRect(0, height, width, 
         bitmapWithReflection.getHeight() + reflectionGap, paint); 
       
       //Create an Image view and add our bitmap with reflection to it
       ImageView imageView = new ImageView(this);
       imageView.setImageBitmap(bitmapWithReflection);
       
       //Add the image to a linear layout and display it
       LinearLayout linLayout = new LinearLayout(this); 
       linLayout.addView(imageView, 
               new LinearLayout.LayoutParams( 
                           LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT 
                     ) 
             ); 
             
        // set LinearLayout as ContentView 
        setContentView(linLayout); 
    }
}
I've added plenty of comments in here, more than I would normally, so I think most of the code is fairly self explanitory. But just in case here's the basic steps that we need to create an image with a reflection.

  1. First thing is to load the bitmap in using the Bitmap factory. I'm loading in a killers album cover, but you'll need to change this to load in your own jpg , png whatever..
  2. We then create a new Matrix class which we are going to use to perform a transformation on our original image. Remember the matrix class is used to perform transformations on bitmaps such as translate (i.e move it) , scale (change the size) and rotate. In this case we use scale, but we are not actually change the size of the image, since we specify a scale of 1. The interesting point here is that the y scale is negative, which takes all y co-ordinates in our bit map and makes them negative. This has the effect of flipping and image through it's Y axis.
  3. Once we have our matrix we pass it to createBitmap to create a flipped bitmap of the original image. Notice that we also specify that this new Bitmap should only be the bottom half of the original image.
  4. So now we have two Bitmaps, our original and our reflection. What I want to do now is join these together in one Bitmap. To do this I create a new Bitmap that is big enoguh to contain both the original bitmaps. I called this bitmapWithReflection. When this is first created it is just empty.
  5. Next step is to create a canvas and give it the bitmapWithReflection bitmap.
  6. So next we draw in the orignal bitmap using canvasDraw.
  7. Then add a small gap between the images using drawRect.
  8. Then add the reflection, again using drawBitmap.
  9. We now have the fully formed Bitmap but to make the reflection look convincing we need to fade out the bottom half of the image. To do this we need to apply an alpha Linear gradient. The alpha value for bits determines how opaque or transparent they are.
  10. So we create a linear gradient that is the size of the reflection part of the image. Add give it an alpha gradient ranging from 0x70 to 0x00.
  11. We then set up a Paint object to have the Linear Gardient and also a transfer Mode of Porter Duff with the Porter Duff mode set to Mode.DST_IN. The Poter Duff mode allows us to merge images together according to a set of rules, Porter Duff supports different rules which define the resulting output of the two merged images. For more information see here.
  12. Once we have our paint object setup we draw a rectangle over the reflection part of the image using the Paint. Becuase we have set the XferMode and shader to linear gradient this will give us the fade out effect we want for our reflection
  13. Last we just add our new Bitmap to an Imageview and then put this is a Linear layout.
So that's it, pretty straight forward really. Sometimes it's the little things that can help your UI look and feel professional. Finally here's an example of using these reflections in the Gallery Component: