【问题标题】:How do I draw on an image in Swift?如何在 Swift 中绘制图像?
【发布时间】:2016-10-10 01:13:03
【问题描述】:

我需要能够以编程方式在图像上绘图,并保存该图像以供以后使用。比如说,在图像的特定 x 和 y 坐标上画一条线,保存图像,并将其显示到一个简单的视图控制器上。我将如何在 Swift 中执行此操作? (最好是 Swift 2,我还在开发中,还没有将我的 mac 更新到 Sierra)

更新:可能与将 UIImage 转换为 CGLayer、在其上绘图、然后将其转换回 UIImage 有关。

【问题讨论】:

  • 您将 UIImage 转换为 CGContext,而不是 CGLayer。

标签: ios swift xcode core-graphics


【解决方案1】:

您需要做的就是创建并获取一个 Image Context 对象并访问其所有强大的绘图方法。您可以详细了解CGContext 对象功能here

此函数在 UIImage 上绘制一条线和一个圆并返回修改后的图像:

斯威夫特 4

func drawOnImage(_ image: UIImage) -> UIImage {
     
     // Create a context of the starting image size and set it as the current one
     UIGraphicsBeginImageContext(image.size)
     
     // Draw the starting image in the current context as background       
     image.draw(at: CGPoint.zero)

     // Get the current context
     let context = UIGraphicsGetCurrentContext()!

     // Draw a red line
     context.setLineWidth(2.0)
     context.setStrokeColor(UIColor.red.cgColor)
     context.move(to: CGPoint(x: 100, y: 100))
     context.addLine(to: CGPoint(x: 200, y: 200))
     context.strokePath()
     
     // Draw a transparent green Circle
     context.setStrokeColor(UIColor.green.cgColor)
     context.setAlpha(0.5)
     context.setLineWidth(10.0)
     context.addEllipse(in: CGRect(x: 100, y: 100, width: 100, height: 100))
     context.drawPath(using: .stroke) // or .fillStroke if need filling
     
     // Save the context as a new UIImage
     let myImage = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
     
     // Return modified image
     return myImage
}

【讨论】:

【解决方案2】:

很简单:

  1. 创建图像图形上下文。 (在 iOS 10 之前,您可以通过调用 UIGraphicsBeginImageContextWithOptions 来执行此操作。在 iOS 10 中还有另一种方式,即 UIGraphicsImageRenderer,但如果您不想使用,则不必使用它。)

  2. 将图像绘制(即复制)到上下文中。 (UIImage 实际上有 draw... 用于此目的的方法。)

  3. 在上下文中画线。 (为此有 CGContext 函数。)

  4. 从上下文中提取生成的图像。 (例如,如果您使用UIGraphicsBeginImageContextWithOptions,您将使用UIGraphicsGetImageFromCurrentImageContext。)然后关闭上下文。

【讨论】:

  • 我写了一本可以在线阅读的书,教你如何做到这一点:apeth.com/iOSBook/ch15.html#_graphics_contexts
  • 在 Swift 2 中,其实都是一样的,因为它只是一堆 C 函数调用,而 Swift 只是直接调用它们。 Swift 3 非常不同。
  • 实际上,使用 UIGraphicsBeginImageContextWithOptions 和 UIGraphicsGetImageFromCurrentImageContext 会比使用 UIGraphicsImageRenderer API 消耗更多的内存。
  • @ChrisForever 我现在总是使用图像渲染器,因为它是一个更好的 API,但我没有理由相信存在内存差异;图像上下文是图像上下文,是图像大小的缓冲区。
【解决方案3】:

详情

Xcode 9.1,斯威夫特 4

解决方案

扩展 UIImage

extension UIImage {

    typealias RectCalculationClosure = (_ parentSize: CGSize, _ newImageSize: CGSize)->(CGRect)

    func with(image named: String, rectCalculation: RectCalculationClosure) -> UIImage {
        return with(image: UIImage(named: named), rectCalculation: rectCalculation)
    }

    func with(image: UIImage?, rectCalculation: RectCalculationClosure) -> UIImage {

        if let image = image {
            UIGraphicsBeginImageContext(size)

            draw(in: CGRect(origin: .zero, size: size))
            image.draw(in: rectCalculation(size, image.size))

            let newImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return newImage!
        }
        return self
    }
}

扩展 UIImageView

    extension UIImageView {

    enum ImageAddingMode {
        case changeOriginalImage
        case addSubview
    }

    func drawOnCurrentImage(anotherImage: UIImage?, mode: ImageAddingMode, rectCalculation: UIImage.RectCalculationClosure) {

        guard let image = image else {
            return
        }

        switch mode {
        case .changeOriginalImage:
            self.image = image.with(image: anotherImage, rectCalculation: rectCalculation)

        case .addSubview:
            let newImageView = UIImageView(frame: rectCalculation(frame.size, image.size))
            newImageView.image = anotherImage
            addSubview(newImageView)
        }
    }
}

图片样本

父图像:

儿童形象:


使用示例1

func sample1(imageView: UIImageView) {
    imageView.contentMode = .scaleAspectFit
    imageView.image = UIImage(named: "parent")?.with(image: "child") { parentSize, newImageSize in
        print("parentSize = \(parentSize)")
        print("newImageSize = \(newImageSize)")
        return CGRect(x: 50, y: 50, width: 90, height: 90)
    }
}

结果 1


使用示例2

func sample2(imageView: UIImageView) {
    imageView.contentMode = .scaleAspectFit
    imageView.image = UIImage(named: "parent")
    imageView.drawOnCurrentImage(anotherImage: UIImage(named: "child"), mode: .changeOriginalImage) { parentSize, newImageSize in
        print("parentSize = \(parentSize)")
        print("newImageSize = \(newImageSize)")
        let sideLength:CGFloat = 90
        let indent:CGFloat = 50
        return CGRect(x: parentSize.width-sideLength-indent, y: parentSize.height-sideLength-indent, width: sideLength, height: sideLength)
    }
}

结果 2


使用示例3

func sample3(imageView: UIImageView) {
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBounds = true
    imageView.image = UIImage(named: "parent")
    imageView.drawOnCurrentImage(anotherImage: UIImage(named: "child"), mode: .addSubview) { parentSize, newImageSize in
        print("parentSize = \(parentSize)")
        print("newImageSize = \(newImageSize)")
        let sideLength:CGFloat = 90
        let indent:CGFloat = 15
        return CGRect(x: parentSize.width-sideLength-indent, y: indent, width: sideLength, height: sideLength)
    }
}

结果 3

完整示例代码

别忘了在此处添加解决方案代码

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let imageView = UIImageView(frame: UIScreen.main.bounds)
        view.addSubview(imageView)
        sample1(imageView: imageView)
       // sample2(imageView: imageView)
       // sample3(imageView: imageView)
    }

    func sample1(imageView: UIImageView) {
        imageView.contentMode = .scaleAspectFit
        imageView.image = UIImage(named: "parent")?.with(image: "child") { parentSize, newImageSize in
            print("parentSize = \(parentSize)")
            print("newImageSize = \(newImageSize)")
            return CGRect(x: 50, y: 50, width: 90, height: 90)
        }
    }

    func sample2(imageView: UIImageView) {
        imageView.contentMode = .scaleAspectFit
        imageView.image = UIImage(named: "parent")
        imageView.drawOnCurrentImage(anotherImage: UIImage(named: "child"), mode: .changeOriginalImage) { parentSize, newImageSize in
            print("parentSize = \(parentSize)")
            print("newImageSize = \(newImageSize)")
            let sideLength:CGFloat = 90
            let indent:CGFloat = 50
            return CGRect(x: parentSize.width-sideLength-indent, y: parentSize.height-sideLength-indent, width: sideLength, height: sideLength)
        }
    }

    func sample3(imageView: UIImageView) {
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.image = UIImage(named: "parent")
        imageView.drawOnCurrentImage(anotherImage: UIImage(named: "child"), mode: .addSubview) { parentSize, newImageSize in
            print("parentSize = \(parentSize)")
            print("newImageSize = \(newImageSize)")
            let sideLength:CGFloat = 90
            let indent:CGFloat = 15
            return CGRect(x: parentSize.width-sideLength-indent, y: indent, width: sideLength, height: sideLength)
        }
    }
}

【讨论】:

  • 如何自定义我添加的图像?比如把它的角弄圆……或者广告标签?
  • @NCT127 这是一个难题。这取决于你有什么结果。我认为如果您可以通过代码共享创建单独的问题会更好。我可以帮你。
  • UIImage 的任何解决方案,而不是 UIImageView?
【解决方案4】:

从 iOS 10 开始你可以使用UIGraphicImageRenderer,它有更好的语法和一些很棒的功能!

斯威夫特 4

let renderer = UIGraphicsImageRenderer(size: view.bounds.size)
let image = renderer.image { context in
    // draw your image into your view
    context.cgContext.draw(UIImage(named: "myImage")!.cgImage!, in: view.frame)
    // draw even more...
    context.cgContext.setFillColor(UIColor.red.cgColor)
    context.cgContext.setStrokeColor(UIColor.black.cgColor)
    context.cgContext.setLineWidth(10)
    context.cgContext.addRect(view.frame)
    context.cgContext.drawPath(using: .fillStroke)
}

【讨论】:

    【解决方案5】:

    更新答案:一旦你得到 From 和 To 坐标,这里是如何在 UIImage 中用这些坐标画一条线。 From 和 To 坐标以图像像素为单位。

    func drawLineOnImage(size: CGSize, image: UIImage, from: CGPoint, to: CGPoint) -> UIImage {
    
    // begin a graphics context of sufficient size
    UIGraphicsBeginImageContext(size)
    
    // draw original image into the context
    image.drawAtPoint(CGPointZero)
    
    // get the context for CoreGraphics
    let context = UIGraphicsGetCurrentContext()
    
    // set stroking width and color of the context
    CGContextSetLineWidth(context, 1.0)
    CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
    
    // set stroking from & to coordinates of the context
    CGContextMoveToPoint(context, from.x, from.y)
    CGContextAddLineToPoint(context, to.x, to.y)
    
    // apply the stroke to the context
    CGContextStrokePath(context)
    
    // get the image from the graphics context 
    let resultImage = UIGraphicsGetImageFromCurrentImageContext()
    
    // end the graphics context 
    UIGraphicsEndImageContext()
    
    return resultImage }
    

    【讨论】:

    猜你喜欢
    • 2016-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-10
    • 2019-07-01
    • 2016-06-07
    • 2021-01-27
    相关资源
    最近更新 更多