How to use Core Image APIs to blur a image looks just like the `UIVisualView` effect?
Asked Answered
D

3

5

The first image below is the original image.
The second image is a blurred image implemented using Core Image API.
The third image is a blurred image implemented using UIVisualView.

It's obvious that the Core Image blurred the image and shrunk it. The bigger radius resulted in a wider white border.

Question: How to use core image make a visual effect just like the UIVisualView effect.

At least, how to use Core Image to blur an image without the white border.

func blur(image: UIImage, withRadius radius: Float) -> UIImage {
    let context = CIContext(options: nil)
    let image = CIImage(image: image)

    let filter = CIFilter(name: "CIGaussianBlur")
    filter?.setValue(image, forKey: kCIInputImageKey)
    filter?.setValue(radius, forKey: "inputRadius")
    let result = filter?.outputImage
    return UIImage(cgImage: context.createCGImage(result!, from: (result?.extent)!)!)
}

The third image is

func addVisualEffectView() {
    let effectView = UIVisualEffectView(effect: UIBlurEffect(style:.light))
    effectView.frame = originalImageView.bounds // originalImageView is the ImageView represents the original image
    originalImageView.addSubview(effectView)
}

enter image description here enter image description here

enter image description here

Deluge answered 19/12, 2017 at 4:0 Comment(2)
You have your answer. Now, if you want a better explanation about what's happening beneath things, I recommend "Core Image for Swift" - particularly Chapter 3.1. It explains why you have a white border and how to correct it - basically, it's a convolution filter and the edges are... well, edges.Monkeypot
@dfd Thanks, man. I will check it.Deluge
E
1

You can create an UIImageView and an UIViewVisualEffectView above the image view, then render it to an image, see my approach:

extension UIImage
{
    var glass: UIImage?
    {
        let area = CGRect(x: 0, y: 0, width: size.width, height: size.height)
    
        let mainView = UIView(frame: area)
    
        let imageView = UIImageView(image: self)
        imageView.frame = area
    
        let blurEffect = UIBlurEffect(style: .light)
        let blurredEffectView = UIVisualEffectView(effect: blurEffect)
        blurredEffectView.frame = area
    
        mainView.addSubview(imageView)
        mainView.addSubview(blurredEffectView)
    
        let renderer = UIGraphicsImageRenderer(size: size)
        let blurImage = renderer.image { _ in
            mainView.drawHierarchy(in: area, afterScreenUpdates: true)
        }
        return blurImage
    }
}
Equitable answered 4/3, 2022 at 7:23 Comment(0)
C
6

Using this I have got the blurry effect like visual effect view.

func blurredImage(with sourceImage: UIImage) -> UIImage {
    //  Create our blurred image
    let context = CIContext(options: nil)
    let inputImage = CIImage(cgImage: sourceImage.cgImage as! CGImage)
    //  Setting up Gaussian Blur
    var filter = CIFilter(name: "CIGaussianBlur")
    filter?.setValue(inputImage, forKey: kCIInputImageKey)
    filter?.setValue(50.0, forKey: "inputRadius")
    let result = filter?.value(forKey: kCIOutputImageKey) as? CIImage

   /*  CIGaussianBlur has a tendency to shrink the image a little, this ensures it matches 
    *  up exactly to the bounds of our original image */

    let cgImage = context.createCGImage(result ?? CIImage(), from: inputImage.extent)
    let retVal = UIImage(cgImage: cgImage!)
    return retVal
}

CIContext. All of the processing of a core image is done in a CIContext. This is somewhat similar to a Core Graphics or OpenGL context.

Calling createCGImage(from:) on the context with the supplied CIImage will return a new CGImage instance.

This tutorial will help to understand:

Core Image Tutorial: Getting Started

Here is the output:

enter image description here

Cogitable answered 19/12, 2017 at 5:57 Comment(4)
It would help everyone if you add some explanation as to why CIContext.createCGImage(from:) is key to this. In fact, it'd be nice to do more than provide code! We're here to help, not just provide code. Other than that, great answer.Monkeypot
@Faysal thank you! And dfd. Thanks both of you. This problem has confused me for a little bit long time.Deluge
@dfd Could this be much more similar to the UIVisualEffect? No matter what the radius is, the blurred image still looks a little bit different from the UIVisualEffect.Deluge
It probably is. Remember, you are using a view created by Apple versus something you are using a lower-level API to closely reproduce. I see eight CICategoryBlur filters, which do not include those simple ones you could write yourself. Your question, BTW, never spoke about "recreating" that blur - just about why you ended up with a white border. :-)Monkeypot
L
4

The answers that do not make use of clampedToExtent() or a similar effect will not compute the correct gaussian filter on the border, even if they crop the blurred image to make most of the white border disappear. Note that the answers based on UIVisualEffectView from UIKit will transparently apply such a similar effect.

This is because the only way to compute correctly the border is to create an image of infinite extent by repeating pixel colors from the edges of the original image, just before applying the filter. You need to save the CIImage extent before calling clampedToExtent(), to be able to apply the blur filter only on the initial extent of the input CIImage.

Simply said, when using Core Graphics and Core Image to blur an image, you need to make use of clampedToExtent(). But when using UIKit or SwiftUI, this is automatically done for you.

// cgImage is the input image you want to apply the gaussian filter to
var ci_image = CIImage(cgImage: cg_image)
// Save the width and height
let ci_image_ext = ci_image.extent
// Create an infinite extent by repeating pixel colors from the edges
ci_image = ci_image.clampedToExtent()
// Create you drawing context
let ci_context = CIContext()
// Create and configure you gaussian filter
let blur = CIFilter(name: "CIGaussianBlur")
blur?.setValue(ci_image, forKey: kCIInputImageKey)
blur?.setValue(blur_radius, forKey: kCIInputRadiusKey)
let blurred_image = blur?.outputImage
// Apply the gaussian filter to the part of the input image
let new_cg_image = ci_context.createCGImage(blurred_image!, from: ci_image_ext)
// new_cg_image is the blurred output image
Ladle answered 29/12, 2022 at 13:47 Comment(0)
E
1

You can create an UIImageView and an UIViewVisualEffectView above the image view, then render it to an image, see my approach:

extension UIImage
{
    var glass: UIImage?
    {
        let area = CGRect(x: 0, y: 0, width: size.width, height: size.height)
    
        let mainView = UIView(frame: area)
    
        let imageView = UIImageView(image: self)
        imageView.frame = area
    
        let blurEffect = UIBlurEffect(style: .light)
        let blurredEffectView = UIVisualEffectView(effect: blurEffect)
        blurredEffectView.frame = area
    
        mainView.addSubview(imageView)
        mainView.addSubview(blurredEffectView)
    
        let renderer = UIGraphicsImageRenderer(size: size)
        let blurImage = renderer.image { _ in
            mainView.drawHierarchy(in: area, afterScreenUpdates: true)
        }
        return blurImage
    }
}
Equitable answered 4/3, 2022 at 7:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.