Swift - 为什么我的橡皮擦工具像素化了

Swift - why is my Eraser tool pixilated

关于在 CoreGraphics 中创建橡皮擦工具有很多问题。我找不到与 "pixilated".

匹配的

情况是这样的。我正在玩一个简单的绘图项目。钢笔工具工作正常。橡皮擦工具非常像素化。这是我的意思的屏幕截图:

这是我正在使用的绘图代码(已更新):

 //  DrawingView
//  
//
//  Created by David DelMonte on 12/9/16.
//  Copyright © 2016 David DelMonte. All rights reserved.
//


import UIKit


public protocol DrawingViewDelegate {
    func didBeginDrawing(view: DrawingView)
    func isDrawing(view: DrawingView)
    func didFinishDrawing(view: DrawingView)
    func didCancelDrawing(view: DrawingView)
}



open class DrawingView: UIView {

    //initial settings
    public var lineColor: UIColor = UIColor.black
    public var lineWidth: CGFloat = 10.0
    public var lineOpacity: CGFloat = 1.0
    //public var lineBlendMode: CGBlendMode = .normal

    //used for zoom actions
    public var drawingEnabled: Bool = true

    public var delegate: DrawingViewDelegate?

    private var currentPoint: CGPoint = CGPoint()
    private var previousPoint: CGPoint = CGPoint()
    private var previousPreviousPoint: CGPoint = CGPoint()

    private var pathArray: [Line] = []
    private var redoArray: [Line] = []

    var toolType: Int = 0

    let π = CGFloat(M_PI)
    private let forceSensitivity: CGFloat = 4.0


    private struct Line {
        var path: CGMutablePath
        var color: UIColor
        var width: CGFloat
        var opacity: CGFloat
        //var blendMode: CGBlendMode

        init(path : CGMutablePath, color: UIColor, width: CGFloat, opacity: CGFloat) {
            self.path = path
            self.color = color
            self.width = width
            self.opacity = opacity
            //self.blendMode = blendMode
        }
    }

    override public init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.clear
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.backgroundColor = UIColor.clear
    }

    override open func draw(_ rect: CGRect) {
        let context : CGContext = UIGraphicsGetCurrentContext()!

        for line in pathArray {
            context.setLineWidth(line.width)
            context.setAlpha(line.opacity)
            context.setLineCap(.round)

            switch toolType {
            case 0: //pen

                context.setStrokeColor(line.color.cgColor)
                context.addPath(line.path)
                context.setBlendMode(.normal)

                break

            case 1: //eraser

                context.setStrokeColor(UIColor.clear.cgColor)
                context.addPath(line.path)
                context.setBlendMode(.clear)

                break

            case 3: //multiply

                context.setStrokeColor(line.color.cgColor)
                context.addPath(line.path)
                context.setBlendMode(.multiply)

                break

            default:
                break
            }

            context.beginTransparencyLayer(auxiliaryInfo: nil)
            context.strokePath()
            context.endTransparencyLayer()
        }
    }




    override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard drawingEnabled == true else {
            return
        }

        self.delegate?.didBeginDrawing(view: self)
        if let touch = touches.first as UITouch! {
            //setTouchPoints(touch, view: self)
            previousPoint = touch.previousLocation(in: self)
            previousPreviousPoint = touch.previousLocation(in: self)
            currentPoint = touch.location(in: self)

            let newLine = Line(path: CGMutablePath(), color: self.lineColor, width: self.lineWidth, opacity: self.lineOpacity)
            newLine.path.addPath(createNewPath())
            pathArray.append(newLine)
        }
    }

    override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard drawingEnabled == true else {
            return
        }

        self.delegate?.isDrawing(view: self)
        if let touch = touches.first as UITouch! {
            //updateTouchPoints(touch, view: self)
            previousPreviousPoint = previousPoint
            previousPoint = touch.previousLocation(in: self)
            currentPoint = touch.location(in: self)

            let newLine = createNewPath()
            if let currentPath = pathArray.last {
                currentPath.path.addPath(newLine)
            }
        }
    }

    override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard drawingEnabled == true else {
            return
        }


    }

    override open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard drawingEnabled == true else {
            return
        }


    }



    public func canUndo() -> Bool {
        if pathArray.count > 0 {return true}
        return false
    }

    public func canRedo() -> Bool {
        return redoArray.count > 0
    }


    public func undo() {
        if pathArray.count > 0 {

            redoArray.append(pathArray.last!)
            pathArray.removeLast()
        }

        setNeedsDisplay()
    }

    public func redo() {
        if redoArray.count > 0 {
            pathArray.append(redoArray.last!)
            redoArray.removeLast()
        }
        setNeedsDisplay()
    }

    public func clearCanvas() {
        pathArray = []
        setNeedsDisplay()
    }



    private func createNewPath() -> CGMutablePath {
        //print(#function)
        let midPoints = getMidPoints()
        let subPath = createSubPath(midPoints.0, mid2: midPoints.1)
        let newPath = addSubPathToPath(subPath)
        return newPath
    }

    private func calculateMidPoint(_ p1 : CGPoint, p2 : CGPoint) -> CGPoint {
        //print(#function)
        return CGPoint(x: (p1.x + p2.x) * 0.5, y: (p1.y + p2.y) * 0.5);
    }

    private func getMidPoints() -> (CGPoint,  CGPoint) {
        //print(#function)
        let mid1 : CGPoint = calculateMidPoint(previousPoint, p2: previousPreviousPoint)
        let mid2 : CGPoint = calculateMidPoint(currentPoint, p2: previousPoint)
        return (mid1, mid2)
    }

    private func createSubPath(_ mid1: CGPoint, mid2: CGPoint) -> CGMutablePath {
        //print(#function)
        let subpath : CGMutablePath = CGMutablePath()
        subpath.move(to: CGPoint(x: mid1.x, y: mid1.y))
        subpath.addQuadCurve(to: CGPoint(x: mid2.x, y: mid2.y), control: CGPoint(x: previousPoint.x, y: previousPoint.y))
        return subpath
    }

    private func addSubPathToPath(_ subpath: CGMutablePath) -> CGMutablePath {
        //print(#function)
        let bounds : CGRect = subpath.boundingBox

        let drawBox : CGRect = bounds.insetBy(dx: -0.54 * lineWidth, dy: -0.54 * lineWidth)
        self.setNeedsDisplay(drawBox)
        return subpath
    }
}

更新:

我注意到每个橡皮擦都是方形的。请查看第二张图片以更详细地显示:

然后我按照 Pranal Jaiswal 的建议重写了一些代码:

override open func draw(_ rect: CGRect) {
        print(#function)
        let context : CGContext = UIGraphicsGetCurrentContext()!

        if isEraserSelected {
            for line in undoArray {
                //context.beginTransparencyLayer(auxiliaryInfo: nil)
                context.setLineWidth(line.width)
                context.addPath(line.path)
                context.setStrokeColor(UIColor.clear.cgColor)
                context.setBlendMode(.clear)
                context.setAlpha(line.opacity)
                context.setLineCap(.round)
                context.strokePath()

            }
        } else {
            for line in undoArray {
                context.setLineWidth(line.width)
                context.setLineCap(.round)
                context.addPath(line.path)
                context.setStrokeColor(line.color.cgColor)
                context.setBlendMode(.normal)
                context.setAlpha(line.opacity)
                context.strokePath()
            }

        }
    }

I'm still getting the same result. I'd appreciate any more help.

我不能完全看你的代码但是我在 Swift 2.3 前一段时间做了类似的事情(我知道你正在看 swift 3 但现在这是版本我有)。

这是绘图 class 的工作原理。

import Foundation
import UIKit
import QuartzCore

class PRSignatureView: UIView

{

var drawingColor:CGColorRef = UIColor.blackColor().CGColor //Col
var drawingThickness:CGFloat = 0.5
var drawingAlpha:CGFloat = 1.0

var isEraserSelected: Bool

private var currentPoint:CGPoint?
private var previousPoint1:CGPoint?
private var previousPoint2:CGPoint?

private var path:CGMutablePathRef = CGPathCreateMutable()

var image:UIImage?

required init?(coder aDecoder: NSCoder) {
    //self.backgroundColor = UIColor.clearColor()
    self.isEraserSelected = false
    super.init(coder: aDecoder)
    self.backgroundColor = UIColor.clearColor()
}

override func drawRect(rect: CGRect)
{
    self.isEraserSelected ? self.eraseMode() : self.drawingMode()
}

private func drawingMode()
{
    if (self.image != nil)
    {
        self.image!.drawInRect(self.bounds)
    }
    let context:CGContextRef = UIGraphicsGetCurrentContext()!
    CGContextAddPath(context, path)
    CGContextSetLineCap(context, CGLineCap.Round)
    CGContextSetLineWidth(context, self.drawingThickness)
    CGContextSetStrokeColorWithColor(context, drawingColor)
    CGContextSetBlendMode(context, CGBlendMode.Normal)
    CGContextSetAlpha(context, self.drawingAlpha)
    CGContextStrokePath(context);
}

private func eraseMode()
{
    if (self.image != nil)
    {
        self.image!.drawInRect(self.bounds)
    }
    let context:CGContextRef = UIGraphicsGetCurrentContext()!
    CGContextSaveGState(context)
    CGContextAddPath(context, path);
    CGContextSetLineCap(context, CGLineCap.Round)
    CGContextSetLineWidth(context, self.drawingThickness)
    CGContextSetBlendMode(context, CGBlendMode.Clear)
    CGContextStrokePath(context)
    CGContextRestoreGState(context)
}




private func midPoint (p1:CGPoint, p2:CGPoint)->CGPoint
{
    return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5)
}

private func finishDrawing()
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0);
    drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
    self.image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

func clearSignature()
{
    path = CGPathCreateMutable()
    self.image = nil;
    self.setNeedsDisplay();
}

// MARK: - Touch Delegates
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    path = CGPathCreateMutable()
    let touch = touches.first!
    previousPoint1 = touch.previousLocationInView(self)
    currentPoint = touch.locationInView(self)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    previousPoint2 = previousPoint1
    previousPoint1 = touch.previousLocationInView(self)
    currentPoint = touch.locationInView(self)
    
    let mid1 = midPoint(previousPoint2!, p2: previousPoint1!)
    let mid2 = midPoint(currentPoint!, p2: previousPoint1!)
    
    let subpath:CGMutablePathRef = CGPathCreateMutable()
    CGPathMoveToPoint(subpath, nil, mid1.x, mid1.y)
    CGPathAddQuadCurveToPoint(subpath, nil, previousPoint1!.x, previousPoint1!.y, mid2.x, mid2.y)
    CGPathAddPath(path, nil, subpath);
    self.setNeedsDisplay()
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    self.touchesMoved(touches, withEvent: event)
    self.finishDrawing()
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
    self.touchesMoved(touches!, withEvent: event)
    self.finishDrawing()
}

}

Source Code for test app I created using the above code

编辑:根据要求将几行代码转换为 swift 3

subpath.move(to: CGPoint(x: mid1.x, y: mid1.y))
subpath.addQuadCurve(to:CGPoint(x: mid2.x, y: mid2.y) , control: CGPoint(x: previousPoint1!.x, y: previousPoint1!.y))
path.addPath(subpath)

编辑:回应更新后的问题

这是更新后的图纸Class,肯定能解决问题。 https://drive.google.com/file/d/0B5nqEBSJjCriTU5oRXd5c2hRV28/view?usp=sharing

解决的问题:

  1. Line 结构未保存关联的工具类型。每当调用 setNeedsDislpay() 时,您都会重绘 pathArray 中的所有对象,并且所有对象都将使用当前选定的工具重绘。我添加了一个新变量 associatedTool 来解决这个问题。
  2. 使用函数 beginTransparencyLayer 会将混合模式设置为 kCGBlendModeNormal。由于这对于与工具类型相关的所有情况都很常见,这导致模式被设置为正常。我已经删除了这两行

//context.beginTransparencyLayer(auxiliaryInfo: nil)

//context.endTransparencyLayer()

试试这个,擦除时没有错误,可以用来画擦除和清屏。您甚至可以增加或减少铅笔和橡皮擦的尺寸。你也可以相应地改变颜色。

希望对您有所帮助.....

导入 UIKit

class DrawingView: UIView {

var lineColor:CGColor = UIColor.black.cgColor 
var lineWidth:CGFloat = 5
var drawingAlpha:CGFloat = 1.0

var isEraserSelected: Bool

private var currentPoint:CGPoint?
private var previousPoint1:CGPoint?
private var previousPoint2:CGPoint?

private var path:CGMutablePath = CGMutablePath()

var image:UIImage?

required init?(coder aDecoder: NSCoder) {
    //self.backgroundColor = UIColor.clearColor()
    self.isEraserSelected = false
    super.init(coder: aDecoder)
    self.backgroundColor = UIColor.clear
}

override func draw(_ rect: CGRect)
{
    self.isEraserSelected ? self.eraseMode() : self.drawingMode()
}

private func drawingMode()
{
    if (self.image != nil)
    {
        self.image!.draw(in: self.bounds)
    }
    let context:CGContext = UIGraphicsGetCurrentContext()!
    context.addPath(path)
    context.setLineCap(CGLineCap.round)
    context.setLineWidth(self.lineWidth)
    context.setStrokeColor(lineColor)
    context.setBlendMode(CGBlendMode.normal)
    context.setAlpha(self.drawingAlpha)
    context.strokePath();
}

private func eraseMode()
{
    if (self.image != nil)
    {
        self.image!.draw(in: self.bounds)
    }
    let context:CGContext = UIGraphicsGetCurrentContext()!
    context.saveGState()
    context.addPath(path);
    context.setLineCap(CGLineCap.round)
    context.setLineWidth(self.lineWidth)
    context.setBlendMode(CGBlendMode.clear)
    context.strokePath()
    context.restoreGState()
}

private func midPoint (p1:CGPoint, p2:CGPoint)->CGPoint
{
    return CGPoint(x: (p1.x + p2.x) * 0.5, y: (p1.y + p2.y) * 0.5);
}

private func finishDrawing()
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0);
    drawHierarchy(in: self.bounds, afterScreenUpdates: true)
    self.image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
}

func clearSignature()
{
    path = CGMutablePath()
    self.image = nil;
    self.setNeedsDisplay();
}

// MARK: - Touch Delegates
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    path = CGMutablePath()
    let touch = touches.first!
    previousPoint1 = touch.previousLocation(in: self)
    currentPoint = touch.location(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first!
    previousPoint2 = previousPoint1
    previousPoint1 = touch.previousLocation(in: self)
    currentPoint = touch.location(in: self)

    let mid1 = midPoint(p1: previousPoint2!, p2: previousPoint1!)
    let mid2 = midPoint(p1: currentPoint!, p2: previousPoint1!)

    let subpath:CGMutablePath = CGMutablePath()
    subpath.move(to: CGPoint(x: mid1.x, y: mid1.y), transform: .identity)
    subpath.addQuadCurve(to: CGPoint(x: mid2.x, y: mid2.y), control: CGPoint(x: (previousPoint1?.x)!, y: (previousPoint1?.y)!))
    path.addPath(subpath, transform: .identity)

    self.setNeedsDisplay()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.touchesMoved(touches, with: event)
    self.finishDrawing()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
    self.touchesMoved(touches!, with: event)
    self.finishDrawing()
}
}