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.
Related
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.
When I draw a line slowly, I believe it detects it as multiple lines instead of one, so it darkens the line too much.
I’m following this tutorial https://www.raywenderlich.com/5895-uikit-drawing-tutorial-how-to-make-a-simple-drawing-app
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
swiped = false
lastPoint = touch.location(in: view)
}
func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint) {
//1
UIGraphicsBeginImageContext(view.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
tempImageView.image?.draw(in: view.bounds)
//2
context.move(to: fromPoint)
context.addLine(to: toPoint)
//3
context.setLineCap(.round)
context.setBlendMode(.normal)
context.setLineWidth(brushWidth)
context.setStrokeColor(color.cgColor)
context.setStrokeColor(UIColor(red: red,
green: green,
blue: blue,
alpha: opacity).cgColor)
//4
context.strokePath()
//5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
//6
swiped = true
let currentPoint = touch.location(in: view)
drawLine(from: lastPoint, to: currentPoint)
//7
lastPoint = currentPoint
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
//draw a single point
drawLine(from: lastPoint, to: lastPoint)
}
//merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.draw(in: view.bounds, blendMode: .normal, alpha: 1.0)
tempImageView?.image?.draw(in: view.bounds, blendMode: .normal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
When I draw over it, it is hard to see that it darkens the point where they intersect. I would like it to be more noticeable. In this picture, opacity is set to 0.5.
The areas that have dots on the lines is where I drew really quickly. Where it is solid is where I drew the lines slowly. I would like for all lines to look like the quickly-drawn lines — minus the dots of course.
You can change the blend mode of draw line context to .copy for get the same color
From
//3
context.setBlendMode(.normal)
to
//3
context.setBlendMode(.copy)
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()
}
I need to draw lines on UIImage (image load from file system) and save it, but I have a problem. When I trying to save context to file, saved image have size equal imageView size (If I set "tempImageView.frame.size" to UIGraphicsBeginImageContextWithOptions and draw(in:)). If I change "size" in code and set tempImageView.image?.size saved image size is OK, but drawing is not work good (lines draw with offset, and lines view badly)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.swiped = false
if let touch = touches.first {
lastPoint = touch.location(in: self.view)
print(lastPoint)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions((tempImageView.image?.size)!, false, scale)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(origin: CGPoint.zero, size: (tempImageView.image?.size)!))
//let context = UIGraphicsGetCurrentContext()
print("context size = \(context)")
print("fromPoint = \(fromPoint), toPoint = \(toPoint)")
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(5)
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1).cgColor)
context?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
self.newImage = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext()!)
UIGraphicsEndImageContext()
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
swiped = true
if let touch = touches.first {
let currentPoint = touch.location(in: self.view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
print(lastPoint)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !swiped {
drawLineFrom(fromPoint: lastPoint, toPoint: lastPoint)
}
}
I want to let the User to draw a rectangle on UIImageView
I added two variables for first an last touch locations
I added this function:-
func draw(from: CGPoint, to: CGPoint) {
UIGraphicsBeginImageContext(imageView.frame.size)
context = UIGraphicsGetCurrentContext()
context?.setStrokeColor(UIColor(red: 0, green: 0, blue: 0, alpha: 1.0).cgColor)
context?.setLineWidth(5.0)
let currentRect = CGRect(x: from.x,
y: from.y,
width: to.x - from.x,
height: to.y - from.y)
context?.addRect(currentRect)
context?.drawPath(using: .stroke)
context?.strokePath()
imageView.image?.draw(in: self.imageView.frame)
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
I add the method to (touchMoved) it draws many rectangles
I add the method to (touchEnded) it draws one, but it does not appear when the user move the touch
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
firstTouchLocation = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self.view)
draw(from: firstTouchLocation, to: lastTouchLocation)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
lastTouchLocation = touch.location(in: self.view)
draw(from: firstTouchLocation, to: lastTouchLocation)
}
}
I want to let the user extend the rectangle when touchMoved and draw when the touchEnded.
You are replacing your image with a new image composed of the previous image plus a rectangle drawn over it. Rather than drawing the image from the image view, draw the original image.
Alternatively, you could render the the rectangle as a shape layer and just update that shape layer's path:
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
private let shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
_shapeLayer.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
_shapeLayer.lineWidth = 3
return _shapeLayer
}()
private var startPoint: CGPoint!
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.addSublayer(shapeLayer)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startPoint = touches.first?.location(in: imageView)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point: CGPoint
if let predictedTouch = event?.predictedTouches(for: touch)?.last {
point = predictedTouch.location(in: imageView)
} else {
point = touch.location(in: imageView)
}
updatePath(from: startPoint, to: point)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let startPoint = startPoint, let touch = touches.first else { return }
let point = touch.location(in: imageView)
updatePath(from: startPoint, to: point)
imageView.image = imageView.snapshot(afterScreenUpdates: true)
shapeLayer.path = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
shapeLayer.path = nil
}
private func updatePath(from startPoint: CGPoint, to point: CGPoint) {
let size = CGSize(width: point.x - startPoint.x, height: point.y - startPoint.y)
let rect = CGRect(origin: startPoint, size: size)
shapeLayer.path = UIBezierPath(rect: rect).cgPath
}
}
Where:
extension UIView {
func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
This is not only simpler, but more efficient, too.
That yields:
By the way, I might suggest using predictive touches in touchesMoved. On a device (not the simulator) that can yield a slightly more responsive UI.