How to compare touch-drawn path with a given bezier path? - ios

I'm developing simple kids alphabets drawing app in iOS. I have created bezier path of a letter with dashed line. If kids draw over the bezier path, I have to detect whether they drawing correctly or not. I'm allowing them to draw only over that path. I don't know to detect whether they are drawing correctly or not and whether they completed it correctly.
// Created layer for path
let font = UIFont.systemFont(ofSize: 350)
var unichars = [UniChar](selectedText.utf16)
var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
if gotGlyphs {
let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!
var inverse = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -cgpath.boundingBox.height - 1)
let path = UIBezierPath(cgPath: cgpath.copy(using: &inverse)!)
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
shapeLayer.lineDashPattern = [7,3]
mainImageView.layer.addSublayer(shapeLayer)
}
//Allowing user to draw only over letter path
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
guard let point = touch?.location(in: self.mainImageView) else {
return
}
if let path = shapeLayer.path, path.contains(point) {
lastPoint = point
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(drawPadView.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImgView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height))
// 2
context?.move(to: toPoint)
context?.addLine(to: toPoint)
// 3
context?.setLineCap(.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(UIColor(displayP3Red: red, green: green, blue: blue, alpha: 1.0).cgColor)
context?.setBlendMode(.normal)
// 4
context?.strokePath()
// 5
tempImgView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImgView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
let touch = touches.first
guard let point = touch?.location(in: self.mainImageView) else {
return
}
if let path = shapeLayer.path, path.contains(point) {
drawLineFrom(fromPoint: lastPoint, toPoint: point)
lastPoint = point
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(drawPadView.frame.size)
mainImageView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height), blendMode: .normal, alpha: opacity)
tempImgView.image?.draw(in: CGRect(x: 0, y: 0, width: drawPadView.frame.size.width, height: drawPadView.frame.size.height), blendMode: .normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Please suggest me how to verify user completed drawing and check whether they drawn correctly or not.

Related

CGContext & CGMutablePath drawing line But it's be reversed

I use CgContext & CGMutablePath to Draw objects but the image that drawed be reversed.
I need Draw Object to CoreMl drawing classification project.
I use Touchs events to draw object
But when I draw to top direction if will be to bottom direction.
I want the line is correct direction
I learn from link
func drawContextLine(from fromPoint: CGPoint, to toPoint:CGPoint) ->CGImage?{
let grayscale = CGColorSpaceCreateDeviceGray()
if contextDraw == nil{
contextDraw = CGContext(
data:nil, width:256, height:256, bitsPerComponent:8, bytesPerRow:0,
space:grayscale, bitmapInfo:CGImageAlphaInfo.none.rawValue)
}
// intermediate_bitmap_context?.setStrokeColor(
// red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contextDraw?.setStrokeColor(UIColor.white.cgColor)
let path = CGMutablePath()
path.move(to: fromPoint)
path.addLine(to: toPoint)
contextDraw?.setLineWidth(5.0)
contextDraw?.beginPath()
contextDraw?.addPath(path)
contextDraw?.strokePath()
guard let imgCg = contextDraw?.makeImage() else {
return nil
}
imgViewPridict.image = UIImage.init(cgImage: imgCg)
return contextDraw?.makeImage()
}
touch begin
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
lastPoint = touch.location(in: self.view)
}
}
touch move
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard touches.first != nil else {
return
}
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
drawLine(from: lastPoint, to: currentPoint)
drawContextLine(from: lastPoint, to: currentPoint)
lastPoint = currentPoint
}
}
Try the code snippet below.
func drawContextLine(from fromPoint: CGPoint, to toPoint:CGPoint) ->CGImage?{
let grayscale = CGColorSpaceCreateDeviceGray()
if contextDraw == nil{
contextDraw = CGContext(
data:nil, width:256, height:256, bitsPerComponent:8, bytesPerRow:0,
space:grayscale, bitmapInfo:CGImageAlphaInfo.none.rawValue)
}
contextDraw?.setStrokeColor(UIColor.white.cgColor)
contextDraw?.saveGState()
contextDraw?.translateBy(x: 0, y: CGFloat(integerLiteral: contextDraw?.height ?? 0))
contextDraw?.scaleBy(x: 1.0, y: -1.0)
contextDraw?.setLineWidth(5.0)
contextDraw?.move(to: CGPoint(x: 100, y: 100))
contextDraw?.addLine(to: CGPoint(x: 150, y: 150))
contextDraw?.strokePath()
contextDraw?.restoreGState()
guard let imgCg = contextDraw?.makeImage() else {
return nil
}
imgViewPridict.image = UIImage.init(cgImage: imgCg)
return contextDraw?.makeImage()
}
You may need to inverse the content drawn on cgContext by applying AffineTransform to it.

iOS: Remove last image in array after touchesEnded has been completed

I am building an iOS drawing application and I am trying to implement a undo button to remove the line drawn by the user. I've created an array of images in a variable:
var images = [UIImage]()
Each time the user swipes and removes the finger from the screen a new image is created in the touchesEnded function. I think I need to remove the last image to get the previous image in an IBAction which will act as a undo button.
Here's a sample of my current code:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first!
guard touch.view != floaty else { return }
swiped = false
if let touch = touches.first{
postBtn.isEnabled = true
postBtn.setImage(UIImage(named: "done_icon_moved"), for: .normal)
dismissBtn.isEnabled = true
undoBtn.isEnabled = true
undoBtn.setImage(UIImage(named: "undo_icon_moved"), for: .normal)
lastPoint = touch.location(in: self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(view.frame.size)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
let context = UIGraphicsGetCurrentContext()
// 2
context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
// 3
context?.setBlendMode(CGBlendMode.normal)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(UIColor(red:red, green:green, blue:blue, alpha: 1.0).cgColor)
// 4
context?.strokePath()
// 5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
// draw a single point
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height),blendMode: CGBlendMode.normal, alpha: 1.0)
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
print("touch ended")
}
Check if image array have objects or not before remove last object.
if images.count != 0 {
images.removeLast()
}

Mask blur with image

So in my swift app I'm allowing the user to paint with there finger on touch. I'm using cgcontext to do this. After the user lifts the finger and the touch ends I am dynamically adding a visualeffect view on top of the shape with the same height and width of the drawn shape.
What i want to do next is use the shape as a mask for visualeffect view. The problem right now is if i try to mask the visualeffect view with the shape. the masked view does not show unless the origin point of the shape is (0,0). Here is a link to how i'm currently attempting to implementing this (https://pastebin.com/JaM9kx4G)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = false
if let touch = touches.first as? UITouch {
if (touch.view == sideView){
return
}
tempPath = UIBezierPath()
lastPoint = touch.location(in: view)
tempPath.move(to:lastPoint)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
tempPath.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
otherImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
// 2
context!.move(to: CGPoint(x:fromPoint.x,y:fromPoint.y))
context!.addLine(to: CGPoint(x:toPoint.x, y:toPoint.y))
// 3
context!.setLineCap(CGLineCap.round)
context!.setLineWidth(brushWidth)
context!.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context!.setBlendMode(CGBlendMode.normal)
// 4
context!.strokePath()
// 5
otherImageView.image = UIGraphicsGetImageFromCurrentImageContext()
otherImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first as? UITouch {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
// 7
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
tempPath.close()
let tempImage = UIImageView(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
UIGraphicsBeginImageContext(tempImage.frame.size)
tempImage.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: 1.0)
otherImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: CGBlendMode.normal, alpha: opacity)
let context = UIGraphicsGetCurrentContext()
let image = UIGraphicsGetImageFromCurrentImageContext()
tempImage.image = image
otherImageView.image = nil
imageView.addSubview(tempImage)
let blur = VisualEffectView()
blur.frame = CGRect(x:tempPath.bounds.origin.x,y:tempPath.bounds.origin.y, width:tempPath.bounds.width, height:tempPath.bounds.height)
blur.blurRadius = 5
blur.layer.mask = tempImage.layer
}

Swift: Draw on image without changing its size (Aspect fill)

I have a simple view controller where the entire screen is an imageView. The imageView has its content mode set to UIViewContentMode.scaleAspectFill. I can draw on the picture, but as soon as I do the image shrinks horizontally. This messing up the quality of the image and I'm not sure exactly why it's happening.
I looked at this question, but I didn't understand the only answer. I think the issue is with the way I am drawing and that rect is shrinking the image.
class AnnotateViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var lastPoint = CGPoint.zero
var fromPoint = CGPoint()
var toPoint = CGPoint.zero
var brushWidth: CGFloat = 5.0
var opacity: CGFloat = 1.0
#IBAction func startDrawing(_ sender: Any) {
self.isDrawing = !self.isDrawing
if isDrawing {
self.imageView.isUserInteractionEnabled = true
showColorButtons()
}
else {
self.imageView.isUserInteractionEnabled = false
hideColorButtons()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastPoint = touch.preciseLocation(in: self.imageView)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, false, 0.0)
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: 255, green: 0, blue: 0, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.preciseLocation(in: self.imageView)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
}
UPDATE - SOLUTION
I finally got back to working on this problem. Looking at this question, I was able to use the accepted answer to solve my problem. Below is my updated code:
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, true, 0.0)
let aspect = imageView.image!.size.width / imageView.image!.size.height
let rect: CGRect
if imageView.frame.width / aspect > imageView.frame.height {
let height = imageView.frame.width / aspect
rect = CGRect(x: 0, y: (imageView.frame.height - height) / 2,
width: imageView.frame.width, height: height)
} else {
let width = imageView.frame.height * aspect
rect = CGRect(x: (imageView.frame.width - width) / 2, y: 0,
width: width, height: imageView.frame.height)
}
imageView.image?.draw(in: rect)
let context = UIGraphicsGetCurrentContext()
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: self.red, green: self.green, blue: self.blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
annotatedImage = imageView.image
imageView.alpha = opacity
UIGraphicsEndImageContext()
}
This line:
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: screenSize.width, height: screenSize.height))
Draws the image at the aspect ratio of the screen, but your image is probably not at the same aspect ratio (the ratio of width to height).
I think this might be useful: UIImage Aspect Fit when using drawInRect?

Draw lines in swift 3.0

I used the following code to no avail in Swift 3.0 - it gives me a blank screen on the simulator - I don't know what's happening.
import UIKit
class DrawExample: UIView {
override func draw( _ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context!.setLineWidth(3.0)
context!.setStrokeColor(UIColor.purple.cgColor)
//make and invisible path first then we fill it in
context!.move(to: CGPoint(x: 50, y: 60))
context!.addLine(to: CGPoint(x: 250, y:320))
context!.strokePath()
}
Update your class with below:
import UIKit
class DrawExample: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw( _ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context!.setLineWidth(3.0)
context!.setStrokeColor(UIColor.purple.cgColor)
//make and invisible path first then we fill it in
context!.move(to: CGPoint(x: 50, y: 60))
context!.addLine(to: CGPoint(x: 250, y:320))
context!.strokePath()
}
}
Now make an instance on the view controller and add it to view. Check the below code for it.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let draw = DrawExample(frame: self.view.bounds)
view.addSubview(draw)
}
}
Drawing in Swift 4.1
let lastPoint = CGPoint.zero
let red: CGFloat = 0.0
let green: CGFloat = 0.0
let blue: CGFloat = 0.0
let brushWidth: CGFloat = 10.0
let opacity: CGFloat = 1.0
var isSwiping:Bool!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isSwiping = false
if let touch = touches.first{
lastPoint = touch.location(in: imgViewDraw)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
isSwiping = true;
if let touch = touches.first{
let currentPoint = touch.location(in: imgViewDraw)
UIGraphicsBeginImageContext(self.imgViewDraw.frame.size)
self.imgViewDraw.image?.draw(in: CGRect(x:0, y:0,width:self.imgViewDraw.frame.size.width, height:self.imgViewDraw.frame.size.height))
UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: currentPoint.x, y: currentPoint.y))
UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round)
UIGraphicsGetCurrentContext()?.setLineWidth(self.brushWidth)
UIGraphicsGetCurrentContext()?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
UIGraphicsGetCurrentContext()?.strokePath()
self.imgViewDraw.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
lastPoint = currentPoint
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(!isSwiping) {
// This is a single touch, draw a point
UIGraphicsBeginImageContext(self.imgViewDraw.frame.size)
self.imgViewDraw.image?.draw(in: CGRect(x:0, y:0,width:self.imgViewDraw.frame.size.width, height:self.imgViewDraw.frame.size.height))
UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round)
UIGraphicsGetCurrentContext()?.setLineWidth(self.brushWidth)
UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
UIGraphicsGetCurrentContext()?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
UIGraphicsGetCurrentContext()?.strokePath()
self.imgViewDraw.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}

Resources