It looks like you have a UIImage
and a UIImageView
declared as a property of your controller... and in viewDidLoad()
you're loading that image, something like:
mainImage = UIImage(named: "background")
I'm guessing that, because in your func blurSliderSlides(_ sender: UISlider)
you have this line:
currentFilter!.setValue(CIImage(image: mainImage), forKey: kCIInputImageKey)
and at the end:
backgroundImage.image = processedImage
So, when you add the "shape" subview, set the .image
to the original:
@IBAction func squareButtonTapped(_ sender: Any) {
squareView.removeFromSuperview()
squareView.frame = CGRect(x: backgroundImage.bounds.midX, y: backgroundImage.bounds.midY, width: CGFloat(backgroundImage.frame.size.height * 20 / 100), height: CGFloat(backgroundImage.frame.size.width * 10 / 100))
backgroundImage.addSubview(squareView)
// replace the "blur" image with the original
backgroundImage.image = mainImage
}
Edit - after clarification in comments...
You don't want to think in terms of "adding subviews."
Instead, use two image views... one containing the original image, and another containing the blurred image, overlaid on top. Then use a layer mask (with a "hole cut") on the blurred image view to let the original "show through."
So,

And it can look like this at run-time:



Here is some example code you can try out. It has one slider which controls the "cut-out oval" as a percentage of the image view width:
class BlurMaskVC: UIViewController {
var mainImage: UIImage!
var originalImageView: UIImageView!
var blurredImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// make sure we can load the image
guard let img = UIImage(named: "bkg640x360") else {
fatalError("Could not load image!!!")
}
mainImage = img
originalImageView = UIImageView()
blurredImageView = UIImageView()
originalImageView.image = mainImage
blurredImageView.image = mainImage
originalImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(originalImageView)
blurredImageView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(blurredImageView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain original image view Top / Leading / Trailing
originalImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
originalImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
originalImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
// let's use the image's aspect ratio
originalImageView.heightAnchor.constraint(equalTo: originalImageView.widthAnchor, multiplier: mainImage.size.height / mainImage.size.width),
// constrain blurred image view to match the original image view
// so it's overlaid directly on top
blurredImageView.topAnchor.constraint(equalTo: originalImageView.topAnchor, constant: 0.0),
blurredImageView.leadingAnchor.constraint(equalTo: originalImageView.leadingAnchor, constant: 0.0),
blurredImageView.trailingAnchor.constraint(equalTo: originalImageView.trailingAnchor, constant: 0.0),
blurredImageView.bottomAnchor.constraint(equalTo: originalImageView.bottomAnchor, constant: 0.0),
])
// a slider to set the "percentage" of the image view to "un-blur"
let areaSlider = UISlider()
areaSlider.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(areaSlider)
NSLayoutConstraint.activate([
areaSlider.topAnchor.constraint(equalTo: originalImageView.bottomAnchor, constant: 20.0),
areaSlider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
areaSlider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
])
areaSlider.addTarget(self, action: #selector(updateBlurMask(_:)), for: .valueChanged)
// since this example does not have a "blur" slider,
// let's set the blur to 20
doBlur(20)
}
func doBlur(_ currentValue: Int) {
let context = CIContext(options: nil)
guard let inputImage = CIImage(image: mainImage) else {
fatalError("Could not get CIImage from mainImage!!!")
}
let originalOrientation = mainImage.imageOrientation
let originalScale = mainImage.scale
if let filter = CIFilter(name: "CIGaussianBlur") {
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(currentValue, forKey: kCIInputRadiusKey)
guard let outputImage = filter.outputImage,
let cgImage = context.createCGImage(outputImage, from: inputImage.extent)
else {
fatalError("Could not generate Processed Image!!!")
}
let processedImage = UIImage(cgImage: cgImage, scale: originalScale, orientation: originalOrientation)
blurredImageView.image = processedImage
}
}
@objc func updateBlurMask(_ sender: UISlider) {
let b: CGRect = blurredImageView.bounds
// let's make a "square" rect with the max of blurImageView's width, height
let m: CGFloat = max(b.width, b.height)
let maxR: CGRect = CGRect(x: 0.0, y: 0.0, width: m, height: m)
// use the value of the slider - 0.0. to 1.0
// as a percentage of the width
// to scale the max rect
let v: CGFloat = CGFloat(sender.value)
let tr = CGAffineTransform(scaleX: v, y: v)
var r: CGRect = maxR.applying(tr)
// center it
r.origin.x = (b.width - r.width) * 0.5
r.origin.y = (b.height - r.height) * 0.5
// a path for the full image view rect
let fullPath = UIBezierPath(rect: blurredImageView.bounds)
// a path for the oval in a percentage of the full rect
let pth = UIBezierPath(ovalIn: r)
// append the paths
fullPath.append(pth)
// this "cuts a hole" in the path
fullPath.usesEvenOddFillRule = true
// shape layer to use as a mask
let maskLayer = CAShapeLayer()
// again, we're going to "cut a hole" in the path
maskLayer.fillRule = .evenOdd
// set the path
maskLayer.path = fullPath.cgPath
// can be any opaque color
maskLayer.fillColor = UIColor.white.cgColor
// set the layer mask
blurredImageView.layer.mask = maskLayer
}
}
You should have no trouble using the updateBlurMask()
func as a basis for your other shapes.