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.
- 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..
- 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.
- 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.
- 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.
- Next step is to create a canvas and give it the bitmapWithReflection bitmap.
- So next we draw in the orignal bitmap using canvasDraw.
- Then add a small gap between the images using drawRect.
- Then add the reflection, again using drawBitmap.
- 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.
- 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.
- 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.
- 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
- Last we just add our new Bitmap to an Imageview and then put this is a Linear layout.
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!
ReplyDeleteThanks for taking the time to comment it's really great to hear that people find what I've written is useful and informative.
ReplyDeleteThanks, this is a lot of help on the project i'm working on.
ReplyDeleteWOW....speachless
ReplyDeleteThanks Neil Davies for u r post. It will be helpful for me if u post how to save the final bitmap as image.
ReplyDeleteJust a suggestion to improve memory usage, you could use this Canvas method:
ReplyDeletedrawBitmap(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.
Thanks helpful suggestions are always very welcome
ReplyDeleteExcellent tip!
ReplyDeleteBut 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
"Just a suggestion to improve memory usage, you could use this Canvas method:
ReplyDeletedrawBitmap(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.
Great! Thanks very much for posting this! Could someone clairify the line:
ReplyDeletepaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));
Sorry for my ignorance but what is Xfermode?
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...
ReplyDeleteGreat stuff. Exactly what I was looking for .-)
ReplyDeleteI 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 !
ReplyDeleteGreat 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/
ReplyDeleteTjank you neal!
ReplyDeleteGreets from austria
thank you so much for this great tutorial... awesome explanation thanks again.:)
ReplyDelete