Blurring iOS UIView

MAY 26, 2013

I've been looking for a way to blurring current UIView without huge latency, but there had been no lack so far. Today, I finally found a solution: Stack Blur.

UPDATE For iOS 7, please check this post. It's a lot easier now.

Blurring a screen in iPhone apps is very popular: Twitter app, Yahoo Weather app... but there is no built-in functionality in UIKit that we can use to realize such view transitions. One possible approach is to 1) capture current screen, 2) apply blur to it, 3) add a blurred image to UIImageView and then current view. The problem is that existing blur functionality in Core Image is very slow. It might be ok if you are using it for image editing app, but is not acceptable UX if blur is a part of UI transition/animation.

Approach 1: CALayer Rasterization

The first approach I found was to use rasterization in CALayer. The benefit of this is that we don't need to capture current UIView because CALayer rasterizes it for us. The code is something like this:

...
CALayer layer = [currentView layer];
[layer setShouldRasterize:YES];
[layer setRasterizationScale:0.5];
...

This is fast. However, the result image is not really blur. The quality of blurring is not acceptable.

Approach 2: Core Image CIFilter

Another approach is to go after Core Image and its filters. Since iOS 6.0, Core Image supports Gaussian Blur in filters. OS X has more blur techniques (e.g. Motion Blur, Zoom Blur...), but at least we don't have to create blur from scratch any more. One thing I need to note about CIGaussianBlur is that it creates a larger image than original and a blank frame around it. If we stick an output image into the same bound as original screen, we'll get smaller blur. In order to fix this, we need to combine CIGaussianBlur with CIAffineClamp which extends image edges, and potentially with CICrop as well.

Sample code is like this:

...
//Get a screen capture from the current view.
UIGraphicsBeginImageContext(currentView.bounds.size);
[currentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
    
//Blur the image
CIImage *blurImg = [CIImage imageWithCGImage:viewImg.CGImage];

CGAffineTransform transform = CGAffineTransformIdentity;
CIFilter *clampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
[clampFilter setValue:blurImg forKey:@"inputImage"];
[clampFilter setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];
    
CIFilter *gaussianBlurFilter = [CIFilter filterWithName: @"CIGaussianBlur"];
[gaussianBlurFilter setValue:clampFilter.outputImage forKey: @"inputImage"];
[gaussianBlurFilter setValue:[NSNumber numberWithFloat:5.0f] forKey:@"inputRadius"];
    
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef cgImg = [context createCGImage:gaussianBlurFilter.outputImage fromRect:[blurImg extent]];
UIImage *outputImg = [UIImage imageWithCGImage:cgImg];
    
//Add UIImageView to current view.
UIImageView *imgView = [[UIImageView alloc] initWithFrame:currentView.bounds];
imgView.image = outputImg;
[currentView addSubview:imgView];
...

Now, we get much better blur result, but it's very slow.

Approach 3: GPUImage

Then, I found this open source: GPUImage. I'm not sure if Core Image just utilizes CPU alone, but GPUImage utilizes GPU (OpenGL ES 2.0) for image and video manipulations. I thought this might be too far, and the library was little big for me, unless I can just take a GPUImageFastBlurFilter piece out of it. Therefore, I passed on.

Approach 4: Stack Blur

After all these different approaches, I finally found a promising solution in terms of simplicity, blur quality and performance. It's not gaussian blur, but a hybrid of gaussian and box blurs in order to boost computation efficiency. I love it. It's not GPU or memory that solved the problem but Math! Here is the iOS implementation of Stack Blur. It's a category in Objective-C, which extends the existing class functionality, in this case UIImage. Once we add this category to the project, the usage is very simple:

...
UIImage *blurImg = [originalImg stackBlur:radius];
...

We still need to capture a screen and put a blurred image back to a view layer, but it's fast and good quality! And the solution is simple and neat! Thanks!