Reputation: 401
I need to be able to programmatically draw on an image, and save that image for later use. Say, draw a line on specific x and y coordinates on the image, save the image, and display it onto a simple view controller. How would I go about doing this in Swift? (Preferably Swift 2, I am still in development and haven't updated my mac to Sierra)
Update: Possibly something to do with converting a UIImage to a CGLayer, drawing on it, and then converting it back to a UIImage.
Upvotes: 34
Views: 51818
Reputation: 683
All you need to do is create and get an Image Context object and access all its powerful drawing methods. You can learn more about the CGContext
object features here.
This function draws a line and a circle on an UIImage and returns the modified image:
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
}
Upvotes: 33
Reputation: 1667
Since iOS 10 you can use the UIGraphicImageRenderer, which has a better syntax and has some great features!
Swift 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)
}
Upvotes: 10
Reputation: 25294
Xcode 9.1, Swift 4
extension 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
}
}
extension 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)
}
}
}
Parent Image:
Child Image:
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)
}
}
Result 1
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)
}
}
Result 2
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)
}
}
Result 3
Don't forget to add Solution code here
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)
}
}
}
Upvotes: 14
Reputation: 451
Updated Answer: Once you get the From and To coordinates, here is how to draw a line in a UIImage with those coordinates. From and To coordinates are in image pixels.
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 }
Upvotes: 6
Reputation: 534914
It's simple:
Make an image graphics context. (Before iOS 10, you would do this by calling UIGraphicsBeginImageContextWithOptions
. In iOS 10 there's another way, UIGraphicsImageRenderer, but you don't have to use it if you don't want to.)
Draw (i.e. copy) the image into the context. (UIImage actually has draw...
methods for this very purpose.)
Draw your line into the context. (There are CGContext functions for this.)
Extract the resulting image from the context. (For example, if you used UIGraphicsBeginImageContextWithOptions
, you would use UIGraphicsGetImageFromCurrentImageContext
.) Then close the context.
Upvotes: 19