Nuke: Gaussian blur without border artifacts (CoreImage bug)
Issue
Gaussian blur CIFilter naturally creates gray artifacts at the borders of the output image.
A common use case when blurring an image would be avoiding these artifacts.
Proposed solution
Add a new filter that extends the edge pixels indefinitely and crop the image to its original extend after blurring.
/// A Gaussian blur filter that clamps to extent to avoid the gray border artefact.
struct GaussianBlurClampedToExtent: ImageProcessing, Hashable, CustomStringConvertible {
private let radius: Int
/// Initializes the receiver with a blur radius.
public init(radius: Int = 8) {
self.radius = radius
}
/// Applies `CIGaussianBlur` filter to the image.
public func process(image: Image, context: ImageProcessingContext?) -> Image? {
// Get CI image
let ciImageOptional: CoreImage.CIImage? = {
if let image = image.ciImage {
return image
}
if let image = image.cgImage {
return CoreImage.CIImage(cgImage: image)
}
return nil
}()
// Ensure CI image was retrieved
guard let ciImage = ciImageOptional else { return nil }
// Remember original image extent
let extent = ciImage.extent
// Create image with infinitely extended border pixels to prevent gray edges from blur filter
let inputImage: CIImage = ciImage.clampedToExtent()
// Create blur filter
let filter = CIFilter(name: "CIGaussianBlur", parameters: [kCIInputRadiusKey: radius, kCIInputImageKey: inputImage])
// Get filtered image
guard let filteredImage = filter?.outputImage else { return nil }
// Get default context shared between all Core Image filters.
let context = CIContext(options: [.priorityRequestLow: true])
// Create CI image cropped to original extent
guard let imageRef: CGImage = context.createCGImage(filteredImage, from: extent) else { return nil }
return UIImage(cgImage: imageRef, scale: image.scale, orientation: image.imageOrientation)
}
public var identifier: String {
return "com.github.kean/nuke/gaussian_blur_clamped_to_extent?radius=\(radius)"
}
public var hashableIdentifier: AnyHashable {
return self
}
public var description: String {
return "GaussianBlurClampedToExtend(radius: \(radius))"
}
}
Alternative solutions
Extend existing GaussianBlur filter with a Boolean clampToExtend option:
"com.github.kean/nuke/gaussian_blur?radius=\(radius)&clampToExtend=\(clampToExtend)"
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 17 (7 by maintainers)
This is not a bug, the method is not correct, gaussian loose image border proportionally to gaussian radius natively! Library is buggy, you need to crop the result, it can be done with CIAffineClamp:
Cool, makes sense. I will appreciate a pull request. I’m using this processor myself, so the help is welcome. If not, I can test and merge it later myself. Thanks.
Since it appears to be a Core Image bug, I’m going to close it. If you have other idea, please let me know.
For the blur matrix of
CIGaussianBlurmy assumption was that radius defined inkCIInputRadiusKeyis the number of surrounding pixels taken into account. So that would lead to an x nmatrix wheren = kCIInputRadiusKey * 2 + 1.But that does not seem to be the case. The extent change of
CIImageactually shows thatkCIInputRadiusKey=50changes an image of (origin, size)(0.0, 0.0, 100.0, 100.0)to(-150.0, -150.0, 400.0, 400.0). A radius of 100 creates an image of(-300.0, -300.0, 700.0, 700.0).So for
CIGaussianBlurit seems thatn = kCIInputRadiusKey * 3 * 2 + 1, but I don’t know where the factor 3 comes from. The references I found online for Gaussian Blur all seem to indicate that the “radius” is usually the number of pixels in all directions from the center of the pixel to calculate.Anyway, instead of
clampedToExtent()I will extend the image bykCIInputRadiusKey * 3:After the new app release I will update here if that fixed the crashes.
@kean Unfortunately not.
I think looking for an alternative to
clampedToExtent()for solving the gray frame problem would be a workaround. Maybe it would be enough to manually expend the pixels in each direction fornpixels, then blur, then cut off the extension.The challenge would be to find a correct
ndepending on the image size. The larger the image, the larger the gray border becomes. So mayben = image.width / 2would be enough, or maybe it only needs a 1/4 of the image width. That’s something to empirically find out I guess, unless you have more insight into how and why the gray border the created with Gaussian blur?I filed a bug report with Apple. An Apple engineer suggested that the recursive stack trace is likely an indication that the indefinitely extended edges may cause the crash in CIImage. I’ll update here.