Thursday, 26 May 2011

Understanding the Image Reflection - Gradient

This is my second post about image reflection. In our first post we discussed about basic principles of image inversion. We discussed translation, scaling and bitmap context to understand image inversion. Today we will discuss about gray scale images and gradients. These concepts are important not only to understand reflection but these could also be used in other scenarios. Our main goal is to understand image reflection which is implemented in Reflection sample provided by Apple. All examples which we are going to discussed today can be downloaded from github repository. If you are looking for complete example of Reflection which covers image inversion, gradient and masking then I would refer you to my post about image masking.

Gray Scale images:
As we know pixels are basic elements of digital image. Each pixel contains number of values which determine the type of image. There are mainly three types of images: black & white, gray scale and color. Black & white images are also called binary images as each pixel has a value either 0 or 1. The next complex type is a gray scale image where each pixel stores value of gray level or gray scale. For example if an image is 8-bit grayscale then each pixel can contain value between 0 to 256 inclusive.
The gray scale images are black & white images but with shades of gray. We can also think shades of gray as color intensities where 0 means black and 1 means white. There are several uses of gray scale images in digital image processing specially in medical image analysis. One common example which we have all come across is X-rays. Here is an example of how we can create a gray scale image from a color image:
UIImage* createGrayScaleImage(UIImage* originalImage){
 //create gray device colorspace.
 CGColorSpaceRef space = CGColorSpaceCreateDeviceGray();
 //create 8-bit bimap context without alpha channel.
 CGContextRef bitmapContext = CGBitmapContextCreate(NULL, originalImage.size.width, originalImage.size.height, 8, 0, space, kCGImageAlphaNone);
 //Draw image.
 CGRect bounds = CGRectMake(0.0, 0.0, originalImage.size.width, originalImage.size.height);
 CGContextDrawImage(bitmapContext, bounds, originalImage.CGImage);
 //Get image from bimap context.
 CGImageRef grayScaleImage = CGBitmapContextCreateImage(bitmapContext);
 //image is inverted. UIImage inverts orientation while converting CGImage to UIImage. 
 UIImage* image = [UIImage imageWithCGImage:grayScaleImage];
 return image;
In this function first we receive color image as a parameter. We convert this image into gray scale image. However it is not necessary that we need to have an image before we can draw gray scale image. We can draw gray scale image using different draw functions without loading any image.  After loading image we create a gray scale color space. We use this color space to create a bitmap context of the size of original image. We are not using alpha channel in our context so we set it to kCGImageAlphaNone. Next we draw original image into gray scale bitmap context. We get the handle to this image by calling CGBitmapContextCreateImage. If you remember we discussed in our last post that the images drawn in bitmap context are in different orientation than UIView and CALayer. We use UIImage wrapper to adjust the orientation of newly created image. Here is the output of our function.

Figure 1
As we mentioned earlier gray scale image represents intensity of color at each pixel. If we compare our new gray scale image with original color image we will notice that the body of bus which is red in original image and tyres which are green have different gray shades in gray scale image. Similarly smoke remains white as it has maximum intensity where as transparent background becomes black as it has no intensity in original image. So how does it help us to achieve image reflection? Basically in order to produce fade effect in image reflection, we will create a gray scale gradient and mask it with the original image to produce fade effect. We will discuss gradient in next section but we will discuss masking in our next post.

In Vector Algebra gradient is used to determine the direction and magnitude i.e vector field from scalar quantity. In terms of images, image gradient defines the variation and direction of color intensity at each point or region. We can illustrate it by simply drawing a gradient in image editing tool such as Gimp.
Figure 2: (a) Rectangle (b) Rectangle after gradient

Figure 2 (a) shows rounded rectangle filled with black color. Figure 2(b) shows the effect of gradient on this rectangle. We can visually see that the intensity of color increases as it moves towards lower end of the rectangle and vice versa. Therefore in this case gradient is either from top to bottom or from bottom to top of the rectangle. There are different types of gradients. In iOS there are two types of gradient functions available. One is Linear gradient and the other is Radial gradient.

Linear Gradient
In a linear gradient color levels or intensities vary along an axis between two end points. The color levels or intensities perpendicular to that axis remains same. For example if we apply a gradient from top to bottom of a rectangle as shown in figure 2(b) then it varies along y-axis but it remains same along x-axis. In two dimensional coordinate system, linear gradient can be applied from top to bottom (y-axis), bottom to top(-y axis), left to right (x-axis) or right to left (- x axis). We will now see how it is implemented in code. The output of our code will be as follows:
Figure 3: Linear Gradient (a) Rounded Rectangle (b) Rounded Rectangle after gradient.
Fig 3(a) shows rounded rectangle and fig 3(b) shows same rounded rectangle after being applied with gradient. If you notice after gradient fig 3(b) rounded rectangle gives a 3D look similar to iOS buttons and controls.
//Draw gradient.
-(void)drawGradientInRect:(CGRect)rect context:(CGContextRef)context{
 CGGradientRef gradient;
 //start and end points of gradient.
 CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
 CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
 //locations of color defined in components.
 CGFloat locations[2] = {0.0, 1.0};
 //color components array. In this case color is same i.e white but we are changing the alpha value.
 CGFloat components[8] = { 1.0, 1.0, 1.0, 0.0,
  1.0, 1.0, 1.0, 0.7};
 //RGB color space.
 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
 //create gradient.
 gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
 //draw gradient.
 CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
In this function we receive CGRect and CGContextRef as parameters. This function assumes that it needs to draw gradient in the provided CGRect with CGContextRef. First we find out the start and end point of area where we want to apply gradient. We are applying gradient in direction from top to bottom of the rectangle. In general we can also apply gradient from bottom to top, left to right or right to left. In order to apply top to bottom gradient we first determine the top and bottom mid-points of the rectangle. After that we define the location of color components. Location is CGFloat value between 0 to 1. By location we mean how we want to apply colors in our specified area. We have defined two colors in the array of color components. First is white with alpha value 0.0 and second is also white color with alpha value 0.7. When we draw gradient the first white color is assigned to location 0 (top rectangle point) and second white color is assigned to location 1 (bottom rectangle point). Next we create gradient by providing color space, color components and locations. Now everything is ready to go and we apply gradient. Gradient starts from top mid point with color white and alpha value 0.0 and gradually increases the alpha value in area between two locations and stops at bottom mid point with alpha value 0.7.

Radial Gradient
The concept of Radial gradient is similar to Linear gradient. In Radial gradient color levels or intensities vary radially between two ends i.e gradient fill varies in circular fashion. In Linear gradient we define gradient area using two linear points i.e start and end points. In Radial gradient we generally define gradient area using two circles i.e inner and outer circles. The direction of color intensity or level either goes towards centre point or away from centre point as show in figure: 4.
Figure 4 Radial Gradient (a) Direction towards centre point. (b) Direction away from centre point.
Now we will see how we can implement Radial gradient in iOS. Our example output would be as shown in figure 5. 

Figure 5: Radial gradient with different parameters. (a) Gradient with No Option (b) Gradient with kCGGradientDrawsBeforeStartLocation (c) Gradient with kCGGradientDrawsAfterEndLocation
This is how we generated our examples:
- (void)drawRect:(CGRect)rect {
 //radial gradient centre point.
 CGPoint startCenter, endCenter;
 startCenter = endCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
 //radial gradient radius;
 CGFloat startRadius = 0.0;
 CGFloat endRadius = CGRectGetMidX(rect) > CGRectGetMidY(rect) ? CGRectGetMidX(rect): CGRectGetMidY(rect);
 //gradient locations.
 CGFloat locations[2] = {0.0, 1.0};
 //gradient color components.
 //black color
 CGFloat components[8] = {0.0,0.0,0.0,0.3,
  0.0, 0.0, 0.0, 0.7};
 //Drawing code.
 CGContextRef context = UIGraphicsGetCurrentContext();
 //Get RGB color space
 CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
 //create gradient.
 CGGradientRef gradient = CGGradientCreateWithColorComponents(space, components, locations, 2);
 //draw gradient.
 CGContextDrawRadialGradient(context, gradient, startCenter, startRadius,endCenter,endRadius, options);
First we determine centre points of inner and outer circles. We have set centre points of both inner and outer circles to the centre of UIView. Next we calculate the radii of both circles. We have set the radius of inner circle to 0 which means we want gradient to start from centre point. The radius of outer circle is based on either width or height of view which ever is bigger. By choosing width or height of UIView as a radius of outer circle we fill gradient in whole UIView. As the centre points of circles are in the centre of view therefore midpoint of view would cover whole view. Next three lines are similar as Linear gradient i.e location, color components and creating the color components. CGContextDrawRadialGradient is also nearly similar other than that we provide radii and an option parameter. Option parameter tells the Quartz system where to stop the gradient fill. Figure 5(a) is drawn with no option i.e 0. Figure 5(b) is drawn using kCGGradientDrawsBeforeStartLocation option. This option fills gradient in the area which is beyond starting point of inner circle. However we do not notice any difference between circle 1 and circle 2 in Figure 5. It is due to the fact that our our inner radius is 0 therefore their is no area beyond starting point. If we had set radius greater than 0 then we would have observed difference between Figure 5(a) and 5(b). The second option of gradient is kCGGradientDrawsAfterEndLocation. This option fills the area beyond end point or outer circle. In figure 5(c) third rectangle is drawn using this option. We can see that due to this option we get rectangle shape instead of circles as in Figure 5(a) and 5(b).

You might wonder where can we use Radial gradient. Although we might not have noticed but it is frequently used in iOS framework. For example when alert messages or notifications are shown to users the background screen or area is filled with Radial gradient. This helps to attract user attention towards message box. We can implement similar effect by filling Radial gradient in full view as shown in figure 6.
Figure 6: View with gradient.
As we can see gradient is helping us to focus our attention towards centre point where intensity is maximum on this screen.

In our next post we will discuss masking and conclude image reflection example. Please feel free to provide any feedback or comments.

1 comment: