79637180

Date: 2025-05-24 21:24:36
Score: 0.5
Natty:
Report link

I wrote extension to UIImage to create a border around an image that reflects image shape. You can specify width, color and alpha for the original image.

Example work of code

extension UIImage {
    func withOutline(width: CGFloat, color: UIColor, alpha: CGFloat = 1.0) -> UIImage? {
        guard let image = addTransparentPadding(width / 2), let ciImage = CIImage(image: image) else { return nil }
        let context = CIContext(options: nil)
        let expandedExtent = ciImage.extent
        let expandedImage = ciImage
        
        guard let alphaMaskFilter = CIFilter(name: "CIColorMatrix") else { return nil }
        alphaMaskFilter.setValue(expandedImage, forKey: kCIInputImageKey)
        
        alphaMaskFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputRVector")
        alphaMaskFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputGVector")
        alphaMaskFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputBVector")
        
        alphaMaskFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputAVector")
        
        guard let alphaImage = alphaMaskFilter.outputImage else { return nil }
        
        guard let edgeFilter = CIFilter(name: "CIMorphologyGradient") else { return nil }
        edgeFilter.setValue(alphaImage, forKey: kCIInputImageKey)
        edgeFilter.setValue(width, forKey: "inputRadius")
        guard let edgeMaskImage = edgeFilter.outputImage else { return nil }
        
        guard let constantColorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil }
        constantColorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey)
        guard let colorImage = constantColorFilter.outputImage else { return nil }
        
        let coloredEdgeImage = colorImage.cropped(to: expandedExtent)
        
        guard let colorClampFilter = CIFilter(name: "CIColorClamp") else { return nil }
        
        colorClampFilter.setValue(edgeMaskImage, forKey: kCIInputImageKey)
        colorClampFilter.setValue(CIVector(x: 1, y: 1, z: 1, w: 0.0), forKey: "inputMinComponents")
        colorClampFilter.setValue(CIVector(x: 1.0, y: 1.0, z: 1.0, w: 1.0), forKey: "inputMaxComponents")
        
        guard let colorClampImage = colorClampFilter.outputImage else { return nil }
        
        guard let sharpenFilter = CIFilter(name: "CISharpenLuminance") else { return nil }
        sharpenFilter.setValue(colorClampImage, forKey: kCIInputImageKey)
        sharpenFilter.setValue(10.0, forKey: "inputSharpness") // Adjust sharpness level
        sharpenFilter.setValue(10.0, forKey: "inputRadius") // Adjust radius
        guard let shaprenImage = sharpenFilter.outputImage else { return nil }
        
        colorClampFilter.setValue(CIVector(x: 0.0, y: 0.0, z: 0.0, w: 0.0), forKey: "inputMinComponents")
        colorClampFilter.setValue(CIVector(x: 0.0, y: 0.0, z: 0.0, w: 1.0), forKey: "inputMaxComponents")
        colorClampFilter.setValue(expandedImage, forKey: kCIInputImageKey)
        guard let expandedMaskImage = colorClampFilter.outputImage else { return nil }
        
        guard let compositeFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
        compositeFilter.setValue(shaprenImage, forKey: kCIInputBackgroundImageKey)
        compositeFilter.setValue(expandedMaskImage, forKey: kCIInputImageKey)
        guard let maskImage = compositeFilter.outputImage else { return nil }
        
        guard let blendFilter = CIFilter(name: "CIBlendWithMask") else { return nil }
        blendFilter.setValue(coloredEdgeImage, forKey: kCIInputImageKey)
        blendFilter.setValue(maskImage, forKey: kCIInputMaskImageKey)
        guard let outlineImage = blendFilter.outputImage else { return nil }
        
        
        let rgba = [0.0, 0.0, 0.0, alpha]
        guard let colorMatrix = CIFilter(name: "CIColorMatrix") else { return nil }
        colorMatrix.setDefaults()
        colorMatrix.setValue(expandedImage, forKey: kCIInputImageKey)
        colorMatrix.setValue(CIVector(values: rgba.map { CGFloat($0) }, count: 4), forKey: "inputAVector")
        
        guard let alphaImage = colorMatrix.outputImage else { return nil }
        
        compositeFilter.setValue(alphaImage, forKey: kCIInputImageKey)
        compositeFilter.setValue(outlineImage, forKey: kCIInputBackgroundImageKey)
        guard let finalImage = compositeFilter.outputImage else { return nil }
        
        guard let cgImage = context.createCGImage(finalImage, from: expandedExtent) else { return nil }
        return UIImage(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation)
    }
    
    func addTransparentPadding(_ padding: CGFloat) -> UIImage? {
        let newSize = CGSize(width: self.size.width + (2 * padding),
                             height: self.size.height + (2 * padding))
        
        UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        
        // Ensure transparency by setting a clear background
        context.clear(CGRect(origin: .zero, size: newSize))
        
        // Corrected origin positioning
        let origin = CGPoint(x: padding, y: padding)
        self.draw(in: CGRect(origin: origin, size: self.size))
        
        let paddedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return paddedImage?.withRenderingMode(.alwaysOriginal)
    }
}

Usage is simple

imageView.image = myImage.withOutline(width: 5, color: .red, alpha: 1)
Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Yehor Bozko