How do I animate in/out a gaussian blur effect in iOS?

14,188

Solution 1

With iOS 9 and above, you can animate the effect on visual effect views:

[UIView animateWithDuration:0.3 animations: ^ {
    visualEffectView.effect = blurEffect;
} completion:nil];

This will achieve the expected effect of animating the blur radius.


I think if you animate a crossfade from non-blurred view -> blurred view, it will be pleasing enough. You need to animate the display of the blur view on top of the non-blurred view.

Let's assume blurView is the view containing the blur effect.

Here are two examples of how to accomplish an animated transition:

[UIView transitionWithView:self.view duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations: ^ {
    [self.view addSubview:blurView];
} completion:nil];

Here I assume blurView has already been set up, and just transition the adding as subview in an animation.

You can also achieve this in a different way:

blurView.alpha = 0.0f;
[UIView animateWithDuration:0.3 animations: ^ {
    blurView.alpha = 1.0f;
} completion:nil];

Here, I make the blur view transparent and animate its appearance. Notice that using this method will not work with hidden, so you want to use the alpha property.


If you wish to control the blur amount interactively, here is an example on how to achieve:

First create an image of the view you want to blur:

UIGraphicsBeginImageContextWithOptions(nonBlurredView.bounds.size, NO, self.view.window.screen.scale);
[nonBlurredView drawViewHierarchyInRect:nonBlurredView.bounds afterScreenUpdates:NO];
UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();

Keep this image. You only want to redraw in a similar fashion if the view has been changed. Otherwise, you should reuse this image, as it is expensive to draw the hierarchy.

Now, when you need to blur, use the following code:

CIImage *imageToBlur = [CIImage imageWithCGImage:snapshotImage.CGImage];    
CIFilter *gaussianBlurFilter = [CIFilter filterWithName: @"CIGaussianBlur"]; 
[gaussianBlurFilter setValue:imageToBlur forKey: @"inputImage"]; 
[gaussianBlurFilter setValue:@10 forKey: @"inputRadius"]; //Here you input the blur radius - the larger, the 
CIImage *resultImage = [gaussianBlurFilter valueForKey: @"outputImage"]; 
UIImage *endImage = [[UIImage alloc] initWithCIImage:resultImage];

The inputRadius input gives sets up the amount of blur performed. If you animate this, you will achieve an interactive feel.

But I still think the former method is much easier and will feel as good to the user.

Solution 2

This is easy on OS X, where one view can CIFilter an effect onto the view behind it, and where the processor is powerful and fast. On iOS, however, there are no view-compositing filters, and blurring takes some time: you can't do it repeatedly, live, as fast as the animation's frames. Therefore some form of compromise is needed.

If you know far enough in advance what it is that you want to blur, however, you can prepare a series of images using a gradation of blurring: no blurring, a little blurring, a little more blurring, and so on. Then you assemble the images and "play" them as the "frames" of an animated UIImageView.

That, however, is not what I would actually advise. What I would do is prepare the blur image and the non-blurred image and use a CIDissolveTransition to dissolve from the non-blurred to the blurred image. No, it isn't the same as gradually applying the blur, but it is computationally inexpensive and is the best "dissolve" effect in the toolbox.

Solution 3

I rarely suggest off the shelf solutions for coding problems. However, in this case, I would whole heartedly recommend LiveFrost. It's a really solid UIView subclass focused exclusively on replicating the popular iOS 7 gaussian blur effect.

I would also encourage you to read the author's blog regarding his design decisions for this class. I have done a lot of research specifically in this topic since the release of iOS 7, and I have literally ZERO COMPLAINTS about this code.

It even has blur animation out of the box! Good luck to you :)

Solution 4

I've developed a small project that uses GPUImage for live blurring with variable blur radius and framerate (MSLiveBlur) - this sounds like exactly what you need.

In the sample app I have a slider that increases / decreases the blur level in response to user action, as you mentioned in your question. If you wanted to animate it without user interaction, you could make a timer that slowly increases the blur radius until you reach the final value. You wouldn't be able to use CoreAnimation with this solution though.

[[MSLiveBlurView sharedInstance] setBlurInterval:0.2];
[[MSLiveBlurView sharedInstance] blurRect:someView.frame];

// Count x from 0 to your final radius
[MSLiveBlurView sharedInstance].blurRadius = x;

Solution 5

Here's a function that you can use to animate a blur with precomputed images. You say you're familiar with GPUImage so i used it (but it can works too with simple blur algorithm).

With this you can animate real blur in real time with a cost of 1-2% CPU

// configuration
let imageCount: Int = 10
let blurMax: Float = 40.0
// keep images in memory
var currentIdx: Int = 0
var imgs: [UIImage] = []

// func that create the precomputed images with GPUImage (call it in the viewDidLoad for example)
func initBlur() {
    let blur = GaussianBlur()
    let myImage: UIImage = UIImage(named: "your_image")!
    let pictureInput = PictureInput(image: myImage)
    let pictureOutput = PictureOutput()
    pictureOutput.onlyCaptureNextFrame = false

    pictureOutput.imageAvailableCallback = { image in
        self.imgs.append(image)
    }

    pictureInput --> blur --> pictureOutput

    for i in 0...imageCount {
        blur.blurRadiusInPixels = (Float(i) / Float(imageCount)) * blurMax
        pictureInput.processImage(synchronously: true)
    }
}

// function that return the correct image from the image set with a blur percentage from a value between 0 and 1 where 0 = no blur and 1 full blurred.
func getBlurredImage(value: Float) -> UIImage? {
    // return nil if there isn't precompiled images
    if imgs.count == 0 {
        return nil
    }

    // get the desired blurred image index
    let idx = Int(value * Float(imageCount - 1))
    // if the index changed, check the correctness of the index
    if currentIdx != idx {
        if idx < 0 {
            currentIdx = 0
        }
        else if idx >= imgs.count {
            currentIdx = imageCount - 1
        }
        else {
            currentIdx = idx
        }
    }
    return imgs[currentIdx]
}

And that's it, next you can use it for example with a timer or in a scrollViewDidScroll function like:

imageView.image = getBlurredImage(value: yOffset / height)
Share:
14,188

Related videos on Youtube

Admin
Author by

Admin

Updated on February 28, 2020

Comments

  • Admin
    Admin over 4 years

    For the whole iOS 7 feel, I want to apply a blur effect to a specific portion of the screen to obfuscate it, but I don't want to just throw the blur on instantly, I want to animate it in and animate it out so the user almost sees the blur effect being applied.

    Almost as if in Photoshop you changed the gaussian blur value bit by bit from 0 to 10, instead of 0 to 10 in one go.

    I've tried a few solutions to this, the most popular suggestion being to simply put the blurred view on top of a non-blurred view, and then lower the alpha value of the blurred view.

    This works okay, but not very eye pleasing as there's no transition, it's just an overlay. Example:

    enter image description here

    What would be a better way to achieve such an effect? I'm familiar with GPUImage, but not sure how to accomplish it with that.

    It'd also be great if I could control what percentage of the blur it is at, so it could be applied interactively from the user. (e.g.: user drags halfway, the blur is half applied, etc.)

    • Léo Natan
      Léo Natan about 10 years
      How do you create the blur view and how do you add it to the view hierarchy? Please post some code.
    • silver_belt
      silver_belt about 10 years
      I've gone the route of blurred view over the original, and animated the alpha on the blurred view. It looks and performs very well, so I'd recommend it unless you find a good reason not to do it.
    • Totumus Maximus
      Totumus Maximus about 10 years
      @aloha64 have you tried adjusting the 'RNFrostedSidebar'? github.com/rnystrom/RNFrostedSidebar
  • Sam
    Sam about 10 years
    This is a solid answer, but if you're seeking close to real time performance CIFilters will not cut it. You can get away with faking it in most places, but if you absolutely must have real time blur with animation (lucky you!) then you'll need to step down to the low level Accelerate framework and execute the box blur yourself.
  • Sam
    Sam about 10 years
    Also little trick: You can save some work on the blur by loading the UIImage as a JPG with quality level 1.
  • Léo Natan
    Léo Natan about 10 years
    @Sam Thanks for suggestions. I hate Apple for not providing their backdrop layer. In most cases of blur, I just use a toolbar with dark style and tint it to what I need. What the author describes in his blog is exactly what Apple does internally. They even have more room for optimizations because they have raw access to the QuartzCore scene and render buffer.
  • joakim
    joakim over 9 years
    I like your template of 1) get an image of the view , 2) apply the blur and 3) animate it on top of another view. It's how I approach blurring too. I'd like to add that you could use the Accelerate framework for the blur to avoid the use of CIFilter. developer.apple.com/library/ios/samplecode/UIImageEffects. Oh and of course as of iOS 8 we can now use UIVisualEffectView and friends
  • Trev14
    Trev14 over 5 years
    As someone who pays close attention to Apple's UI, I wouldn't crossfade in a blur on top since it feels cheap and doesn't give the best illusion of blurring the screen's current contents. Apple wouldn't do it :)
  • Léo Natan
    Léo Natan over 5 years
    @Trev14 this answer is old and I have already provided a solution that doesn’t cross fade for modern iOS.