Ares Li
Ares Li

Reputation: 613

How to add a blur mask with a custom shape above a dynamic camera view in Swift?

I am using Swift to develop an iOS application with a camera feature, and it requires a blur layer above the camera view with a hole in the middle like the image shown below.

I have tried several methods, but none of them added the blur effect without covering the hole. I didn't find working solutions after spending time on Google as well.

Can anyone provide some hints on how can I only blur the non-transperant part of a png image that is attached to a image view?

Methods that I have tried:

  1. Use the built-in iOS 8 blur effect

    let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Dark)
    let blurEffectView = UIVisualEffectView(effect: blurEffect)
    maskImage.addSubview(blurEffectView)
    
  2. Use the "CIGaussianBlur" Filter

    var imageToBlur = CIImage(image: image)
    var blurfilter = CIFilter(name: "CIGaussianBlur")
    blurfilter.setValue(imageToBlur, forKey: "inputImage")
    var resultImage = blurfilter.valueForKey("outputImage") as! CIImage
    var blurredImage = UIImage(CIImage: resultImage)
    self.maskImage.image = blurredImage
    

The visual effect that I want to have:

enter image description here

Upvotes: 5

Views: 5674

Answers (2)

Coder ACJHP
Coder ACJHP

Reputation: 2194

Easiest way to apply blur to out of some circular region in image we can applying Gaussian blur and gradient masks. Here Apple has a post on it.

Basically common steps to do this:

  1. Create a mask filter using CIRadialGradient (adjust your circle center here)

    guard let radialMask = CIFilter(name:"CIRadialGradient") else {
       return nil
    }
    
    let h = inputImage.extent.size.height
    let w = inputImage.extent.size.width
    
    // Adjust your circular hole position here
    let imageCenter = CIVector(x:0.55 * w, y:0.6 * h)
    radialMask.setValue(imageCenter, forKey:kCIInputCenterKey)
    // Small fraction of the image's dimension (high sharp)
    radialMask.setValue(0.2 * h, forKey:"inputRadius0")
    // Large fraction of the image's dimension (lower sharp)
    radialMask.setValue(0.3 * h, forKey:"inputRadius1")
    radialMask.setValue(CIColor(red:0, green:1, blue:0, alpha:0), 
                        forKey:"inputColor0")
    radialMask.setValue(CIColor(red:0, green:1, blue:0, alpha:1), 
                        forKey:"inputColor1")
    
  2. Create CIMaskedVariableBlur to mask blured area

    guard let maskedVariableBlur = CIFilter(name:"CIMaskedVariableBlur") else {
       return nil
    }
    // inputImage:- Your original image
    maskedVariableBlur.setValue(inputImage, forKey: kCIInputImageKey)
    // inputRadiusKey:- Right degree of blur desired
    maskedVariableBlur.setValue(10, forKey: kCIInputRadiusKey)
    // here we will use result of radialMask filter result image
    maskedVariableBlur.setValue(radialMask.outputImage, forKey: "inputMask")
    // Get result image
    let selectivelyFocusedCIImage = maskedVariableBlur.outputImage
    // Convert your result image to UIImage
    let resultImage = UIImage(ciImage: selectivelyFocusedCIImage)
    

Extra:- You can use UIPanGestureRecognizer to move your circle hole

1- Create gesture recognizer:

lazy var panGesture: UIPanGestureRecognizer = {
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleDrag))
    return panGesture
}()

2- Add gesture recognizer to your image view

self.imageView.isUserInteractionEnabled = true
self.imageView.addGestureRecognizer(panGesture)

3- Handle your gesture

var initialCenter = CGPoint()
@objc fileprivate func handleDrag(_ gestureRecognizer: UIPanGestureRecognizer) {
  guard gestureRecognizer.view != nil else {return}

  let translation = gestureRecognizer.translation(in: imageView.superview)

  if gestureRecognizer.state == .began {
      // Save the view's original position.
      self.initialCenter = CGPoint(x: centerVector.x, y: centerVector.y)
  }
  // Update the position for the .began, .changed, and .ended states
  if gestureRecognizer.state != .cancelled {
      // Add the X and Y translation to the view's original position.
      let newCenter = CGPoint(x: initialCenter.x + translation.x, y: initialCenter.y + -translation.y)
    
    
      self.imageCenter = CIVector.init(cgPoint: newCenter)
      // Then apply your filter on gestureRecognizer.state == .end
    
  } else {
      // On cancellation, return the piece to its original location.
      self.imageCenter = CIVector(x: initialCenter.x, y: initialCenter.y)
  }
}

Upvotes: 1

Chris Conover
Chris Conover

Reputation: 9039

I've done this by creating an overlay with a layer with said blurred image, and then using a mask that is built from a path that goes all the way around the extents, and then jumps to the cutout hole, going the opposite direction with the winding fill rule.

Refer to documetation here:

https://developer.apple.com/library/ios/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_paths/dq_paths.html

There are many documented examples of this though, here is one: drawing with clear color on UIView (cutting a hole) in static method

Upvotes: 2

Related Questions