Drawing App in swift, slow performance & low FPS - ios

I am trying to create a drawing app , using this code to draw lines but it is giving a very slow performance
func drawLineNew(_ prevPoint1 : CGPoint,_ prevPoint2 : CGPoint ,_ currentPoint : CGPoint){
UIGraphicsBeginImageContextWithOptions(ImageView.frame.size, false, 0.0)
let context = UIGraphicsGetCurrentContext()
ImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
var mid1 = CGPoint.init(x: (prevPoint1.x + prevPoint2.x)*0.5, y: (prevPoint1.y + prevPoint2.y)*0.5)
var mid2 = CGPoint.init(x: ((currentPoint.x) + prevPoint1.x)*0.5, y: ((currentPoint.y) + prevPoint1.y)*0.5)
context?.move(to: prevPoint2)
context?.addCurve(to: mid2, control1: mid1, control2: prevPoint1)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: red, green: green, blue: blue, alpha: opacity)
context?.setBlendMode(CGBlendMode.normal )
//context?.setShouldAntialias(true)
context?.strokePath()
ImageView.image = UIGraphicsGetImageFromCurrentImageContext()
ImageView.alpha = 1.0
self.prevPoint1 = mid2
//context?.clear(CGRect.init(origin: CGPoint.init(x: 0, y: 0), size: ImageView.frame.size))
UIGraphicsEndImageContext()
lastPoint = currentPoint
}

The performance is slow when you draw too much lines. Consider optimizing the performance by not drawing all lines depending on the size of your drawing area and which distance a pixel is representing on the displayed scale.

Related

Image in UIImage resize when draw on it

When I load a image to my UIImageView and would like draw on it. Then will change the ratio of the image. Can somebody tell me what is here the wrong?
Thanks
Before drawing
When you drawing
func drawLines(fromPoint:CGPoint,toPoint:CGPoint) {
UIGraphicsBeginImageContext(self.view.frame.size)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushSize)
context?.setStrokeColor(UIColor(red: red, green: green, blue: blue, alpha: opacityValue).cgColor)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}

What would be equal in Swift to draw path/rect with a specific color

in Android (in Java) I could do something like this (here I tried to draw Triangle figure which is filled with different colors, first half with red color and second with green, also black stroke around Triangle figure:
#Override
public void onDraw(Canvas canvas) {
....
canvas.clipPath(pathBoundary); // triangle figure path
canvas.drawPath(pathBoundary, paintBlack); // draw black stroke around figure
canvas.drawRect(rectRed, paintRed); // fill first half with red color
canvas.drawRect(rectGreen, paintGreen); // fill second half with green color
in iOS I just learned how to draw Triangle figure with only one color, would like to also draw with two colors (like in Android)
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.60)
context.fillPath()
context.clip()
... how to draw path with specific color?
... how to draw rect with specific color?
You can do just like you do on Android: stroke the triangle path, set it as clipping path, stroke a rectangle in red, stroke a rectangle in green.
UIGraphicsBeginImageContext(CGSize(width: 200, height: 200))
let context = UIGraphicsGetCurrentContext()!
context.beginPath()
context.move(to: CGPoint(x: 100, y: 0))
context.addLine(to: CGPoint(x: 0, y: 200))
context.addLine(to: CGPoint(x: 200, y: 200))
context.closePath()
let path = context.path!
context.setStrokeColor(UIColor.black.cgColor)
context.strokePath()
context.addPath(path)
context.clip()
context.setFillColor(UIColor.red.cgColor)
context.fill(CGRect(x: 0, y: 0, width: 200, height: 150))
context.setFillColor(UIColor.green.cgColor)
context.fill(CGRect(x: 0, y: 150, width: 200, height: 50))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
You can run the code above in a playground. In the right column hover over the last line (the one for image), click on the eye icon and you'll see a preview of the image that was drawn.
The way you do it is basically by drawing two different paths. Each with a different fill color. Here is an example:
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
let r = sqrt(pow(leftVertex.x - topVertex.x, 2) + pow(leftVertex.y - topVertex.y, 2))
let leftAngle = atan(rect.maxY/rect.midX)
context.beginPath()
context.move(to: leftVertex)
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: topVertex)
context.closePath()
context.setFillColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1)
context.fillPath()
let smallLeftVertex = CGPoint(x: r/2*cos(leftAngle), y: rect.midY)
context.beginPath()
context.move(to: smallLeftVertex)
context.addLine(to: topVertex)
context.addLine(to: CGPoint(x: rect.maxX - smallLeftVertex.x, y: rect.midY))
context.closePath()
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1)
context.fillPath()
}
There is some math to evaluate which would be the vertices of the inner (smaller) triangle.
Here is the result:
If you want you can try it in a playground:
//: Playground - noun: a place where people can play
import UIKit
class TriangleView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
let r = sqrt(pow(leftVertex.x - topVertex.x, 2) + pow(leftVertex.y - topVertex.y, 2))
let leftAngle = atan(rect.maxY/rect.midX)
context.beginPath()
context.move(to: leftVertex)
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: topVertex)
context.closePath()
context.setFillColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1)
context.fillPath()
let smallLeftVertex = CGPoint(x: r/2*cos(leftAngle), y: rect.midY)
context.beginPath()
context.move(to: smallLeftVertex)
context.addLine(to: topVertex)
context.addLine(to: CGPoint(x: rect.maxX - smallLeftVertex.x, y: rect.midY))
context.closePath()
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1)
context.fillPath()
}
}
let triangle = TriangleView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
triangle.backgroundColor = .white
**
Edit:
**
You can also avoid to draw two overlapping triangles by using the clip feature of CGContext. In this case the code would look like this:
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
let leftVertex = CGPoint(x: rect.minX, y: rect.maxY)
let topVertex = CGPoint(x: rect.midX, y: rect.minY)
var path = CGMutablePath()
path.move(to: leftVertex)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: topVertex)
path.closeSubpath()
context.addPath(path)
context.clip()
context.setFillColor(red: 1, green: 0, blue: 0, alpha: 1)
context.fill(CGRect(x: 0, y: 0, width: rect.width, height: rect.height/2))
context.setFillColor(red: 0, green: 1, blue: 0, alpha: 1)
context.fill(CGRect(x: 0, y: rect.midY, width: rect.width, height: rect.height/2))
}
In the first part of the code we create the triangle, we extract the path just drown in the context and we add it as clipping path. Then we can fill the two rectangles with the two different colours.
Hope this answers your question.

How to draw lines using UIView?

I can draw lines using CGContext. How to draw lines with the same coordinates using UIView? I want to move lines in the future, so that I need to use UIView.
func DrawLine()
{
UIGraphicsBeginImageContext(self.view.frame.size)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height))
let context = UIGraphicsGetCurrentContext()
let lineWidth : CGFloat = 5
context?.move(to: CGPoint(x: 100, y: 100))
context?.addLine(to: CGPoint(x: self.view.frame.width - 100, y: 100))
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(lineWidth)
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.strokePath()
context?.move(to: CGPoint(x: self.view.frame.width/2, y: 100))
context?.addLine(to: CGPoint(x: self.view.frame.width/2, y: 700))
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
override func viewDidLoad()
{
super.viewDidLoad()
DrawLine()
}
Add a CAShapeLayer to a UIView
let shapeLayer = CAShapeLayer()
view.layer.addSublayer(shapeLayer)
set the shapelayer path to the lines
shapeLayer.path = // some CGPath you create
See: https://stackoverflow.com/a/40556796/3937 for how to make a CGPath with lines.

Extracting UI Image View coordinates

I'm currently creating a drawing based app, I would like there to be an option for the user to send the co-ordinates to another player.
I'm using swift3 in Xcode 8.1.
I am able to extract a PNG of the image but what I'd like to do is just send the coordinates in order to recreate the image on the other players screen.
I've posted the code to the user's drawing function. I did try to push ' context? ' into an array after ' addLine ' but nothing that looked like coordinates was pushed into the array.
func drawPicture(fromPoint:CGPoint, toPoint:CGPoint) {
UIGraphicsBeginImageContextWithOptions(self.drawPage.bounds.size, false, 0.0)
drawPage.image?.draw(in: CGRect(x: 0, y:0, width:self.drawPage.bounds.width, height:self.drawPage.bounds.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
context?.setBlendMode(CGBlendMode.color)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(5)
context?.setStrokeColor(UIColor(red: 0.26, green: 0.53, blue: 0.96, alpha: 1.0).cgColor)
context?.strokePath()
drawPage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Thanks for any ideas you have :)
I would suggest adding an array in which you would add coordinates while you are drawing and then just send the content of that array wherever you need to.
E.g. something like this:
struct DrawingCoordinate {
var from: CGPoint
var to: CGPoint
init(from: CGPoint, to: CGPoint) {
self.from = from
self.to = to
}
}
var coordinatesArray = [DrawingCoordinate]()
func drawPicture(fromPoint:CGPoint, toPoint:CGPoint) {
UIGraphicsBeginImageContextWithOptions(self.drawPage.bounds.size, false, 0.0)
drawPage.image?.draw(in: CGRect(x: 0, y:0, width:self.drawPage.bounds.width, height:self.drawPage.bounds.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
// add this line to your array
coordinatesArray.append(DrawingCoordinate(from: fromPoint, to: toPoint))
context?.setBlendMode(CGBlendMode.color)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(5)
context?.setStrokeColor(UIColor(red: 0.26, green: 0.53, blue: 0.96, alpha: 1.0).cgColor)
context?.strokePath()
drawPage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Does this make sense to you?

Draw gradient under UIBezierPath

I have drawn a curved line. I want to put a gradient from the view all the way up to the line (so that the gradient curves along with the line.)
edit: here is a photo of what the code below produces:
I know how to draw the line, and I know how to add a regular gradient, but just not the two together. Here is my code:
override func drawRect(rect: CGRect) {
// draw the curved line, this code works just fine.
let path1 = UIBezierPath()
path1.lineWidth = 1.1
UIColor.greenColor().setStroke()
path1.moveToPoint(CGPoint(x: 0, y: bounds.height/2))
path1.addQuadCurveToPoint(CGPoint(x: bounds.width, y: bounds.height/2), controlPoint: CGPoint(x: bounds.width/2, y: (bounds.height * 0.75)))
path1.stroke()
// my attempt to draw the gradient:
let gradient = CAGradientLayer()
gradient.startPoint = CGPoint(x: 1, y: 1)
gradient.endPoint = CGPoint(x: 0, y: 0)
let colors = [UIColor.whiteColor().CGColor, UIColor(red: 0, green: 1, blue: 1, alpha: 0.4).CGColor]
gradient.colors = colors
// the following line is where I need help
gradient.frame = CGRectMake(0, 475, bounds.width, path1.bounds.height)
layer.addSublayer(gradient)
}
what can I set gradient.frame equal to so that it's upper limit is the previously drawn path? Answer in Swift please (i've seen a lot of other questions on this subject but they are all in objective C)
Thanks
I have found the answer.
The following code gave me this:
.
Here is the code:
override func drawRect(rect: CGRect) {
//draw the line of UIBezierPath
let path1 = UIBezierPath()
path1.lineWidth = 1.1
UIColor(white: 1, alpha: 1).setStroke()
path1.moveToPoint(CGPoint(x: 0, y: bounds.height/2))
path1.addQuadCurveToPoint(CGPoint(x: bounds.width, y: bounds.height/2), controlPoint: CGPoint(x: bounds.width/2, y: (bounds.height * 0.65)))
path1.stroke()
// add clipping path. this draws an imaginary line (to create bounds) from the
//ends of the UIBezierPath line down to the bottom of the screen
let clippingPath = path1.copy() as! UIBezierPath
clippingPath.addLineToPoint(CGPoint(x: self.bounds.width, y: self.bounds.height))
clippingPath.addLineToPoint(CGPoint(x: 0, y: bounds.height))
clippingPath.closePath()
clippingPath.addClip()
// create and add the gradient
let colors = [UIColor(red: 0, green: 1, blue: 1, alpha: 0.45).CGColor, UIColor.whiteColor().CGColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let colorLocations:[CGFloat] = [0.0, 1.0]
let gradient = CGGradientCreateWithColors(colorSpace,
colors,
colorLocations)
let context = UIGraphicsGetCurrentContext()
let startPoint = CGPoint(x: 1, y: 1)
let endPoint = CGPoint(x: 1, y: bounds.maxY)
// and lastly, draw the gradient.
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, CGGradientDrawingOptions.DrawsAfterEndLocation)
}

Resources