Skip to content

Gaussian blur without border artifacts (CoreImage bug) #308

Description

@mtrezza

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)"

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions