【问题标题】:how to add colored border to uiimage in swift如何在swift中为uiimage添加彩色边框
【发布时间】:2018-06-02 16:07:27
【问题描述】:

使用图层(borderWidth、borderColor 等)向 UIImageView 添加边框非常容易。是否有可能为图像添加边框,而不是图像视图?有人知道吗?

更新:

我尝试遵循以下建议并使用extension。谢谢你,但我没有得到想要的结果。这是我的代码。怎么了?

import UIKit
    class ViewController: UIViewController {

    var imageView: UIImageView!
    var sizeW = CGFloat()
    var sizeH = CGFloat()

    override func viewDidLoad() {
        super.viewDidLoad()

        sizeW = view.frame.width
        sizeH = view.frame.height

        setImage()
    }

    func setImage(){

        //add image view
        imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: sizeW/2, height: sizeH/2))
        imageView.center = view.center
        imageView.tintColor = UIColor.orange
        imageView.contentMode = UIViewContentMode.scaleAspectFit

        let imgOriginal = UIImage(named: "plum")!.withRenderingMode(.alwaysTemplate)
        let borderImage = imgOriginal.imageWithBorder(width: 2, color: UIColor.blue)
        imageView.image = borderImage

        view.addSubview(imageView)

    }



}


    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
    }
}

带有红色边框的第二张图片或多或少是我需要的:

【问题讨论】:

  • 你到底需要什么?
  • 我需要一个红色边框,比如说,在李子本身周围而不是在图像视图周围有一个borderWidth 3的红色边框。我怎么能得到它?
  • 我添加了第二张照片,效果或多或少。
  • 任何解决方案@roman

标签: ios swift image image-processing border


【解决方案1】:

受到@herme5 的强烈启发,重构为更紧凑的 Swift 5/iOS12+ 代码如下(也修复了垂直翻转问题):

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

【讨论】:

  • 您忘记包含 CGPoint.rotated 函数。
  • 当然。我想尝试使用您的代码来勾勒国旗,但顶部的轮廓不见了。基本上,在调用stroked 之后,UIImage 在底部和侧面都有一个轮廓。关于为什么会发生这种情况的任何线索?我在香草 UIImageView 上使用它,其标志网址取自维基百科。
  • 如果有人正在寻找更简单的东西(矩形UIImage/简单边框)these answers 可能就足够了。
  • 这比你有用多了!但是,这一行: context.translateBy(x: 0, y: size.height) 应该是: context.translateBy(x: 0, y: newSize.height)
  • 很好的解决方案,我建议进行编辑,以修复翻译后的居中问题。请批准
【解决方案2】:

这是我在 Swift 4 中编写的 UIImage 扩展。正如 IOSDealBreaker 所说,这完全是关于图像处理的,可能会出现一些特殊情况。您应该有一个透明背景的 png 图片,如果大于原始大小,请管理大小。

  • 首先获取图像的彩色“阴影”版本。
  • 然后在给定的原点周围绘制并重绘阴影图像(在我们的例子中,在(0,0) 周围,距离为边框厚度)
  • 在原点绘制源图像,使其出现在前景中。
  • 如果边框超出原始矩形,您可能需要放大图片。

我的方法使用了很多 util 方法和类扩展。以下是围绕另一个点旋转矢量(实际上是一个点)的一些数学运算: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)
    }
}

我编写了自定义 CIFilter 来为具有透明背景的图像着色: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
        }
    }

此时,您应该具备完成这项工作的所有条件:

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
    }
}

【讨论】:

  • 如果我们手动移除背景边缘不平滑。你能建议任何关于平滑边界的技术吗?
  • @Ahmed,简短的回答:像 Gimp 或 Photoshop 这样的照片编辑软件。我不认为所有照片都存在完美的算法(尤其是逼真的,有风景的)。
  • @Ahmed 尽管如此,对于相对简单的图像,我有这种方法(例如:具有统一背景的单色徽标拼写错误卡通绘图) Gimp 有一个“擦除”颜色通道的过滤器(例如:每个白色像素将变得完全透明)。看看这个问题:stackoverflow.com/questions/50216613/… 你可以重写一个着色器函数,这样:像素 RGB 越接近你想要擦除的颜色,alpha 越少。
  • @Ahmed “像素 RGB 越接近您要擦除的颜色,像素越透明”。我还不知道如何在算法中对其数学表示进行建模。
【解决方案3】:

为 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

    }

}

它会给你一个带有粉红色边框的图像。

【讨论】:

  • 我尝试了您的代码:let image = UIImage(named: "plum")!.outline() 并且仍然获得图像视图周围的边框,而不是图像本身!
  • 它围绕图像而不是图像视图。做一件事,让你的 imageview 更大,并保持它的内容模式属性设置为宽高比,并给它一个背景颜色。您将在图像周围设置边框,而不是在 imageView 周围。
  • 您好 Arun,我正在尝试遵循您的建议,但结果不是正确的边界。您能否提供一个简短的 func 代码,而不仅仅是扩展名?
【解决方案4】:

图像的边框属于 iOS 的图像处理区域。作为 UIView 的边界并不容易,它很深,但如果你愿意走远,这里有一个图书馆和旅程的提示 https://github.com/BradLarson/GPUImage

尝试使用 GPUImageThresholdEdgeDetectionFilter

或试试 OpenCV https://docs.opencv.org/2.4/doc/tutorials/ios/image_manipulation/image_manipulation.html

【讨论】:

    猜你喜欢
    • 2016-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-02
    • 1970-01-01
    • 1970-01-01
    • 2019-11-28
    • 2014-07-11
    相关资源
    最近更新 更多