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:


15 comments:

  1. This is great stuff. I don't see many comments on your posts, and I'm not sure why. Perhaps a "how-to" blog's posts don't warrant much discussion? Either way, your blog has been tremendously helpful to me. It's very informative and easy to understand, and best of all, sheds light on how to do some really cool stuff with Android that I may otherwise never figure out. So thank you, and keep up the good work!

    ReplyDelete
  2. Thanks for taking the time to comment it's really great to hear that people find what I've written is useful and informative.

    ReplyDelete
  3. Thanks, this is a lot of help on the project i'm working on.

    ReplyDelete
  4. WOW....speachless

    ReplyDelete
  5. Thanks Neil Davies for u r post. It will be helpful for me if u post how to save the final bitmap as image.

    ReplyDelete
  6. Just a suggestion to improve memory usage, you could use this Canvas method:
    drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)

    this way, you avoid allocating memory for the second bitmap. I don't think that the impact on cpu usage is high, as all android phones have 2d acceleration built-in for the screen effects.

    ReplyDelete
  7. Thanks helpful suggestions are always very welcome

    ReplyDelete
  8. Excellent tip!
    But do i see some bad artefacts in starsailor reflection?
    Is that a screenshot or application artefact ?
    Just wondering because i got the same atrefact which can be clearly seen on a simple alpha gradient fade out effect :(
    Any ideas on the cause or workaround ?

    Anyways great reflection techique, thanks for sharing

    ReplyDelete
  9. "Just a suggestion to improve memory usage, you could use this Canvas method:

    drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)"

    That actually doesn't work. I think there is a bug in Android that prevents that method from working properly when scaling is applied. If I comment out the matrix.preScale() line it will draw the bitmap, but not upside down. However, leaving the preScale() in there causes it to not draw anything.

    It was a good suggestion if Android actually worked as expected, otherwise you're just wasting your time. Just stick to the code posted in the blog post, and it will work.

    ReplyDelete
  10. Great! Thanks very much for posting this! Could someone clairify the line:
    paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
    Sorry for my ignorance but what is Xfermode?

    ReplyDelete
  11. This looks really useful. Any pointers on doing this from a starting point of a canvas as opposed to a bitmap? i.e. I've drawn some stuff onto a canvas (obtained from surfaceHolder.lockCanvas()) and now I want to create a reflection of what I drew. I can't find any way of getting the bitmap from a canvas...

    ReplyDelete
  12. Great stuff. Exactly what I was looking for .-)

    ReplyDelete
  13. I am sorry, I totally understand your code, but when I read further on alpha blending, I'm very confused.... I know I have next to no knowledge on computer graphic, but I really want to know more about it, because this is the second time I've encountered "Alpha blending". The first time is when I wrote a Live-wallpaper. What book would you recommend me reading to start off? Thank you !

    ReplyDelete
  14. Great post. I used the techniques you outlined to create a custom View which will apply this reflection effect to any set of child Views. Would be interested to here your thoughts. http://tomtheguvnor.wordpress.com/2012/01/19/creating-a-reusable-fading-reflection-view-effect-in-android/

    ReplyDelete
  15. andreas stuetz10 July 2013 12:47

    Tjank you neal!
    Greets from austria

    ReplyDelete