iosswiftcore-imagecifiltergaussianblur

Achieve same CIFilter effect on different sizes of same image


I'm building a photo editor and to keep a good performance I filter a small version of the image first and when the user wants to export it, then I filter the higher resolution image.

I'm using CIGaussianBlur filter but I can't achieve same results for different images resolutions.

This is my code:

class ViewController : UIViewController {

var originalImage = UIImage()
var previewImageView = UIImageView()
var previewCIImage: CIImage!
var scaleFactor = CGFloat()

let blurFilter = CIFilter.gaussianBlur()
var blurSlider = UISlider()
var blurRadius = Float()

override func viewDidLoad() {
    previewImageView.image = originalImage.scalePreservingAspectRatio(targetSize: previewImageView.frame.size)
    previewCIImage = CIImage(image: previewImageView.image!)
    
    // Get the scale factor
    scaleFactor = originalImage.getScaleFactor(targetSize: previewImageView.frame.size)
    
    blurSlider.addTarget(self, action: #selector(blurChanged(slider:)), for: .valueChanged)
}

@objc func blurChanged(slider: UISlider) {
    blurRadius = slider.value
    
    let scaledRadius = blurRadius * Float(scaleFactor)
    blurFilter.radius = scaledRadius
    
    MTKView.setNeedsDisplay()
}

func exportFullSizeImage() -> UIImage {
    let inputImage = CIImage(image: originalImage)!
            
    blurFilter.inputImage = inputImage.clampedToExtent()
    
    // Assuming scaleFactor is 1.0 for the unscaled image
    let scaledRadius = blurRadius * 1.0
    blurFilter.radius = scaledRadius

    let output = (blurFilter.outputImage)!
    let outputCGImage = context.createCGImage(output, from: output.extent)
    
    return UIImage(cgImage: outputCGImage!)
}

}

extension UIImage {
func scalePreservingAspectRatio(targetSize: CGSize) -> UIImage {
    let widthRatio = targetSize.width / size.width
    let heightRatio = targetSize.height / size.height
    
    let scaleFactor = min(widthRatio, heightRatio)
            
    let scaledImageSize = CGSize(
        width: size.width * scaleFactor,
        height: size.height * scaleFactor
    )
    
    let renderer = UIGraphicsImageRenderer(
        size: scaledImageSize
    )

    let scaledImage = renderer.image { _ in
        self.draw(in: CGRect(
            origin: .zero,
            size: scaledImageSize
        ))
    }
    return scaledImage
}

func getScaleFactor(targetSize: CGSize) -> CGFloat {
    let widthRatio = targetSize.width / size.width
    let heightRatio = targetSize.height / size.height
    let scaleFactor = min(widthRatio, heightRatio)
    return scaleFactor
}

}

Here's the output of the small version of the image (preview image):

enter image description here

And here's the output of the full size image (unscaled image):

enter image description here

The results are clearly different, the full size/unscaled image has more blur. I need to achieve the same blur effect on both images.

I've found two similar questions: Output of CIFilter has different effect for different sizes of same image and How to achieve same CIFilter effect on multiple sizes of same image

I know the scale factor of the resized image, that's maybe useful to get an answer.


Solution

  • The parameter scaling from the linked answer should work for all images, regardless of their aspect ratio. The important part is that you apply the scale factor to both images, the preview and the export.

    Alternatively, since you have the scale factor of the resized image, you can use that to scale the parameter (instead of using the image size):

    // assuming scaleFactor is 1.0 for the unscaled image
    let scaledRadius = radius * scaleFactor 
    filter.setValue(scaledRadius, forKey: "inputRadius")
    

    Please also note that not every parameter of every filter needs scaling to achieve consistency across different image sizes. Usually, only parameters that describe some kind of effect radius or size need scaling.