how to add colored border to uiimage in swift

后端 未结 5 1615
礼貌的吻别
礼貌的吻别 2020-12-31 15:59

It is pretty easy to add border to UIImageView, using layers (borderWidth, borderColor etc.). Is there any possibility to add border to image, not to image view? Does somebo

相关标签:
5条回答
  • 2020-12-31 16:27

    Strongly inspired by @herme5, refactored into more compact Swift 5/iOS12+ code as follows (fixed vertical flip issue as well):

    public extension UIImage {
    
        /**
        Returns the flat colorized version of the image, or self when something was wrong
    
        - Parameters:
            - color: The colors to user. By defaut, uses the ``UIColor.white`
    
        - Returns: the flat colorized version of the image, or the self if something was wrong
        */
        func colorized(with color: UIColor = .white) -> UIImage {
            UIGraphicsBeginImageContextWithOptions(size, false, scale)
    
            defer {
                UIGraphicsEndImageContext()
            }
    
            guard let context = UIGraphicsGetCurrentContext(), let cgImage = cgImage else { return self }
    
    
            let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
    
            color.setFill()
            context.translateBy(x: 0, y: size.height)
            context.scaleBy(x: 1.0, y: -1.0)
            context.clip(to: rect, mask: cgImage)
            context.fill(rect)
    
            guard let colored = UIGraphicsGetImageFromCurrentImageContext() else { return self }
    
            return colored
        }
    
        /**
        Returns the stroked version of the fransparent image with the given stroke color and the thickness.
    
        - Parameters:
            - color: The colors to user. By defaut, uses the ``UIColor.white`
            - thickness: the thickness of the border. Default to `2`
            - quality: The number of degrees (out of 360): the smaller the best, but the slower. Defaults to `10`.
    
        - Returns: the stroked version of the image, or self if something was wrong
        */
    
        func stroked(with color: UIColor = .white, thickness: CGFloat = 2, quality: CGFloat = 10) -> UIImage {
    
            guard let cgImage = cgImage else { return self }
    
            // Colorize the stroke image to reflect border color
            let strokeImage = colorized(with: color)
    
            guard let strokeCGImage = strokeImage.cgImage else { return self }
    
            /// Rendering quality of the stroke
            let step = quality == 0 ? 10 : abs(quality)
    
            let oldRect = CGRect(x: thickness, y: thickness, width: size.width, height: size.height).integral
            let newSize = CGSize(width: size.width + 2 * thickness, height: size.height + 2 * thickness)
            let translationVector = CGPoint(x: thickness, y: 0)
    
    
            UIGraphicsBeginImageContextWithOptions(newSize, false, scale)
    
            guard let context = UIGraphicsGetCurrentContext() else { return self }
    
            defer {
                UIGraphicsEndImageContext()
            }
            context.translateBy(x: 0, y: newSize.height)
            context.scaleBy(x: 1.0, y: -1.0)
            context.interpolationQuality = .high
    
            for angle: CGFloat in stride(from: 0, to: 360, by: step) {
                let vector = translationVector.rotated(around: .zero, byDegrees: angle)
                let transform = CGAffineTransform(translationX: vector.x, y: vector.y)
    
                context.concatenate(transform)
    
                context.draw(strokeCGImage, in: oldRect)
    
                let resetTransform = CGAffineTransform(translationX: -vector.x, y: -vector.y)
                context.concatenate(resetTransform)
            }
    
            context.draw(cgImage, in: oldRect)
    
            guard let stroked = UIGraphicsGetImageFromCurrentImageContext() else { return self }
    
            return stroked
        }
    }
    
    
    extension CGPoint {
        /** 
        Rotates the point from the center `origin` by `byDegrees` degrees along the Z axis.
    
        - Parameters:
            - origin: The center of he rotation;
            - byDegrees: Amount of degrees to rotate around the Z axis.
    
        - Returns: The rotated point.
        */
        func rotated(around origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
            let dx = x - origin.x
            let dy = y - origin.y
            let radius = sqrt(dx * dx + dy * dy)
            let azimuth = atan2(dy, dx) // in radians
            let newAzimuth = azimuth + byDegrees * .pi / 180.0 // to radians
            let x = origin.x + radius * cos(newAzimuth)
            let y = origin.y + radius * sin(newAzimuth)
            return CGPoint(x: x, y: y)
        }
    }
    

    0 讨论(0)
  • 2020-12-31 16:27

    Use this simple extension for UIImage

    extension UIImage {
    
        func outline() -> UIImage? {
    
            UIGraphicsBeginImageContext(size)
            let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
            self.draw(in: rect, blendMode: .normal, alpha: 1.0)
            let context = UIGraphicsGetCurrentContext()
            context?.setStrokeColor(red: 1.0, green: 0.5, blue: 1.0, alpha: 1.0)
            context?.setLineWidth(5.0)
            context?.stroke(rect)
            let newImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
    
            return newImage
    
        }
    
    }
    

    It will give you an image with pink border.

    0 讨论(0)
  • 2020-12-31 16:34

    Here is a UIImage extension I wrote in Swift 4. As IOSDealBreaker said this is all about image processing, and some particular cases may occur. You should have a png image with a transparent background, and manage the size if larger than the original.

    • First get a colorised "shade" version of your image.
    • Then draw and redraw the shade image all around a given origin point (In our case around (0,0) at a distance that is the border thickness)
    • Draw your source image at the origin point so that it appears on the foreground.
    • You may have to enlarge your image if the borders go out of the original rect.

    My method uses a lot of util methods and class extensions. Here is some maths to rotate a vector (which is actually a point) around another point: Rotating a CGPoint around another CGPoint

    extension CGPoint {
        func rotated(around origin: CGPoint, byDegrees: CGFloat) -> CGPoint {
            let dx = self.x - origin.x
            let dy = self.y - origin.y
            let radius = sqrt(dx * dx + dy * dy)
            let azimuth = atan2(dy, dx) // in radians
            let newAzimuth = azimuth + (byDegrees * CGFloat.pi / 180.0) // convert it to radians
            let x = origin.x + radius * cos(newAzimuth)
            let y = origin.y + radius * sin(newAzimuth)
            return CGPoint(x: x, y: y)
        }
    }
    

    I wrote my custom CIFilter to colorise an image which have a transparent background: Colorize a UIImage in Swift

    class ColorFilter: CIFilter {
        var inputImage: CIImage?
        var inputColor: CIColor?
        private let kernel: CIColorKernel = {
            let kernelString =
            """
            kernel vec4 colorize(__sample pixel, vec4 color) {
                pixel.rgb = pixel.a * color.rgb;
                pixel.a *= color.a;
                return pixel;
            }
            """
            return CIColorKernel(source: kernelString)!
        }()
    
        override var outputImage: CIImage? {
            guard let inputImage = inputImage, let inputColor = inputColor else { return nil }
            let inputs = [inputImage, inputColor] as [Any]
            return kernel.apply(extent: inputImage.extent, arguments: inputs)
        }
    }
    
    extension UIImage {
        func colorized(with color: UIColor) -> UIImage {
            guard let cgInput = self.cgImage else {
                return self
            }
            let colorFilter = ColorFilter()
            colorFilter.inputImage = CIImage(cgImage: cgInput)
            colorFilter.inputColor = CIColor(color: color)
    
            if let ciOutputImage = colorFilter.outputImage {
                let context = CIContext(options: nil)
                let cgImg = context.createCGImage(ciOutputImage, from: ciOutputImage.extent)
                return UIImage(cgImage: cgImg!, scale: self.scale, orientation: self.imageOrientation).alpha(color.rgba.alpha).withRenderingMode(self.renderingMode)
            } else {
                return self
            }
        }
    

    At this point you should have everything to make this work:

    extension UIImage {
        func stroked(with color: UIColor, size: CGFloat) -> UIImage {
            let strokeImage = self.colorized(with: color)
            let oldRect = CGRect(x: size, y: size, width: self.size.width, height: self.size.height).integral
            let newSize = CGSize(width: self.size.width + (2*size), height: self.size.height + (2*size))
            let translationVector = CGPoint(x: size, y: 0)
    
            UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
            if let context = UIGraphicsGetCurrentContext() {
                context.interpolationQuality = .high
    
                let step = 10 // reduce the step to increase quality
                for angle in stride(from: 0, to: 360, by: step) {
                    let vector = translationVector.rotated(around: .zero, byDegrees: CGFloat(angle))
                    let transform = CGAffineTransform(translationX: vector.x, y: vector.y)
                    context.concatenate(transform)
                    context.draw(strokeImage.cgImage!, in: oldRect)
                    let resetTransform = CGAffineTransform(translationX: -vector.x, y: -vector.y)
                    context.concatenate(resetTransform)
                }
                context.draw(self.cgImage!, in: oldRect)
    
                let newImage = UIImage(cgImage: context.makeImage()!, scale: self.scale, orientation: self.imageOrientation)
                UIGraphicsEndImageContext()
    
                return newImage.withRenderingMode(self.renderingMode)
            }
            UIGraphicsEndImageContext()
            return self
        }
    }
    
    0 讨论(0)
  • 2020-12-31 16:43

    Here is the way how you can achieve that:

    Add below extension to your code:

    extension UIImage {
        func imageWithBorder(width: CGFloat, color: UIColor) -> UIImage? {
            let square = CGSize(width: min(size.width, size.height) + width * 2, height: min(size.width, size.height) + width * 2)
            let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: square))
            imageView.contentMode = .center
            imageView.image = self
            imageView.layer.borderWidth = width
            imageView.layer.borderColor = color.cgColor
            UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
            guard let context = UIGraphicsGetCurrentContext() else { return nil }
            imageView.layer.render(in: context)
            let result = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return result
        }
    }
    

    And you can use it this way:

    let imgOriginal = UIImage.init(named: "Richie_Rich")
    img.image = imgOriginal?.imageWithBorder(width: 2, color: UIColor.blue)
    

    And result will be:

    Here is the original post which is with Round corners but I removed that code because you did't ask for it.

    0 讨论(0)
  • 2020-12-31 16:45

    Borders to the images belongs to image processing area of iOS. It's not easy as borders for a UIView, It's pretty deep but if you're willing to go the distance, here is a library and a hint for the journey https://github.com/BradLarson/GPUImage

    try using GPUImageThresholdEdgeDetectionFilter

    or try OpenCV https://docs.opencv.org/2.4/doc/tutorials/ios/image_manipulation/image_manipulation.html

    0 讨论(0)
提交回复
热议问题