如何在 Swift 的 UIImageView 中绘制流畅的线条?
How to draw smooth lines inside an UIImageView with Swift?
下面的代码在 UIImageView 上绘制点之间的直线。绘图渲染速度快,看起来不错,但还可以做得更好,所以我想修改代码以创建平滑的曲线。在过去的六周里,我调查了 Swift 中的许多示例,但都没有成功。
下面代码的哪些部分我必须修改(以及如何修改)才能实现?有人告诉我调用下面的代码片段而不是 CGContextAddLineToPoint() (在下面的 // 2)应该可以解决问题,但我不清楚如何调用它以及它与当前代码的关系。
片段:
func drawQuadLineWithPoints(firstPoint: CGPoint, secondPoint: CGPoint, thirdPoint: CGPoint, inContext: CGContext) {
CGContextMoveToPoint(context, 0.5 * (firstPoint.x + secondPoint.x), 0.5 * (firstPoint.y + secondPoint.y));
CGContextAddQuadCurveToPoint(context, secondPoint.x, secondPoint.y, 0.5 * (secondPoint.x + thirdPoint.x), 0.5 * (secondPoint.y + thirdPoint.y));
}
代码:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
swiped = false
if let touch = touches.anyObject() as? UITouch {
lastPoint = touch.locationInView(self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
// 2
CGContextMoveToPoint(context, fromPoint.x, fromPoint.y)
CGContextAddLineToPoint(context, toPoint.x, toPoint.y)
// 3
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brushWidth)
CGContextSetRGBStrokeColor(context, red, green, blue, 1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
// 4
CGContextStrokePath(context)
// 5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
// 6
swiped = true
if let touch = touches.anyObject() as? UITouch {
let currentPoint = touch.locationInView(view)
drawLineFrom(lastPoint, toPoint: currentPoint)
// 7
lastPoint = currentPoint
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if !swiped {
// draw a single point
drawLineFrom(lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
查看 THIS
的答案
这是对Swift的粗略翻译,我使用:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch{
prevPoint1 = touch.previousLocationInView(self.view)
prevPoint2 = touch.previousLocationInView(self.view)
lastPoint = touch.locationInView(self.view)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch{
let currentPoint = touch.locationInView(view)
prevPoint2 = prevPoint1
prevPoint1 = touch.previousLocationInView(self.view)
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
var mid1 = CGPointMake((prevPoint1.x + prevPoint2.x)*0.5, (prevPoint1.y + prevPoint2.y)*0.5)
var mid2 = CGPointMake((currentPoint.x + prevPoint1.x)*0.5, (currentPoint.y + prevPoint1.y)*0.5)
CGContextMoveToPoint(context, mid1.x, mid1.y)
CGContextAddQuadCurveToPoint(context, prevPoint1.x, prevPoint1.y, mid2.x, mid2.y)
//CGContextAddLineToPoint(context, toPoint.x, toPoint.y)
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brushWidth)
CGContextSetRGBStrokeColor(context, red, green,blue, 1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
CGContextStrokePath(context)
TempImage.image = UIGraphicsGetImageFromCurrentImageContext()
TempImage.alpha = opacity
UIGraphicsEndImageContext()
lastPoint = currentPoint
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
UIGraphicsBeginImageContext(MainImage.frame.size)
MainImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
MainImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
TempImage.image = nil
}
对于Swift3.0
func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
swiped = false
if let touch = touches.first as? UITouch {
lastPoint = touch.location(in: self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
swiped = true
if let touch = touches.first as? UITouch {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
更新 Swift 4
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
var prevPoint1: CGPoint!
var prevPoint2: CGPoint!
var lastPoint:CGPoint!
var width: CGFloat!
var red:CGFloat!
var green:CGFloat!
var blue:CGFloat!
var alpha: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
width = 3.0
red = (0.0/255.0)
green = (0.0/255.0)
blue = (0.0/255.0)
alpha = 1.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
if let touch = touches.first {
prevPoint1 = touch.previousLocation(in:self.view)
prevPoint2 = touch.previousLocation(in:self.view)
lastPoint = touch.location(in:self.view)
}
}
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
if let touch = touches.first {
let currentPoint = touch.location(in: view)
prevPoint2 = prevPoint1
prevPoint1 = touch.previousLocation(in: self.view)
UIGraphicsBeginImageContext(imageView.frame.size)
guard let context = UIGraphicsGetCurrentContext() else {
return
}
context.move(to:prevPoint2)
context.addQuadCurve(to: prevPoint1, control: prevPoint2)
context.setLineCap(.butt)
context.setLineWidth(width)
context.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context.setBlendMode(.normal)
context.strokePath()
imageView.image?.draw(in: CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height), blendMode: .overlay, alpha: 1.0)
imageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
lastPoint = currentPoint
}
}
}
这是我对 SwiftUI 的改编。我坚持所有的要点,这样我就可以 undo/redo,这样我就可以拥有原始的接触点,因为这是一个签名。我没有 need/want 实际图像...
其他描述见代码
import SwiftUI
struct DrawPad: View {
@State private var path = Path()
@State private var allStrokes: [[DragGesture.Value]] = []
@State private var currentStroke: [DragGesture.Value] = []
var body: some View {
path
.stroke(Color.blue, lineWidth: 1.0)
.background(Color(white: 0.95))
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged { value in
addNextPath(from: value)
}
.onEnded { value in
addNextPath(from: value)
// We finished the current stroke. Remove it and start a new one
allStrokes.append(currentStroke)
currentStroke = []
}
)
}
private func addNextPath(from value: DragGesture.Value) {
currentStroke.append(value)
// We need at least 3 points to draw a nice line
guard currentStroke.count >= 3 else { return }
// Get the last 3 points
let first = currentStroke[currentStroke.count - 3]
let second = currentStroke[currentStroke.count - 2]
let third = currentStroke[currentStroke.count - 1]
// Get two mid points. Mid points are just the average of the two points
let firstMid = [first.location, second.location].average
let secondMid = [second.location, third.location].average
// Create the curve from the mid point between first and second to the mid point between second and third
// and the second point is control point. If you draw this out on a piece of paper you will see that
// the second point is the tangent for the first mid point.
path.move(to: firstMid)
path.addQuadCurve(to: secondMid, control: second.location)
}
}
struct DrawPad_Previews: PreviewProvider {
static var previews: some View {
DrawPad()
}
}
extension Array where Element == CGPoint {
var average: CGPoint {
let count = CGFloat(self.count)
let sumX = reduce(0, { [=10=] + .x })
let sumY = reduce(0, { [=10=] + .y })
return CGPoint(x: sumX/count, y: sumY/count)
}
}
下面的代码在 UIImageView 上绘制点之间的直线。绘图渲染速度快,看起来不错,但还可以做得更好,所以我想修改代码以创建平滑的曲线。在过去的六周里,我调查了 Swift 中的许多示例,但都没有成功。
下面代码的哪些部分我必须修改(以及如何修改)才能实现?有人告诉我调用下面的代码片段而不是 CGContextAddLineToPoint() (在下面的 // 2)应该可以解决问题,但我不清楚如何调用它以及它与当前代码的关系。
片段:
func drawQuadLineWithPoints(firstPoint: CGPoint, secondPoint: CGPoint, thirdPoint: CGPoint, inContext: CGContext) {
CGContextMoveToPoint(context, 0.5 * (firstPoint.x + secondPoint.x), 0.5 * (firstPoint.y + secondPoint.y));
CGContextAddQuadCurveToPoint(context, secondPoint.x, secondPoint.y, 0.5 * (secondPoint.x + thirdPoint.x), 0.5 * (secondPoint.y + thirdPoint.y));
}
代码:
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
swiped = false
if let touch = touches.anyObject() as? UITouch {
lastPoint = touch.locationInView(self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
// 1
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
// 2
CGContextMoveToPoint(context, fromPoint.x, fromPoint.y)
CGContextAddLineToPoint(context, toPoint.x, toPoint.y)
// 3
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brushWidth)
CGContextSetRGBStrokeColor(context, red, green, blue, 1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
// 4
CGContextStrokePath(context)
// 5
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
// 6
swiped = true
if let touch = touches.anyObject() as? UITouch {
let currentPoint = touch.locationInView(view)
drawLineFrom(lastPoint, toPoint: currentPoint)
// 7
lastPoint = currentPoint
}
}
override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
if !swiped {
// draw a single point
drawLineFrom(lastPoint, toPoint: lastPoint)
}
// Merge tempImageView into mainImageView
UIGraphicsBeginImageContext(mainImageView.frame.size)
mainImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
tempImageView.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
tempImageView.image = nil
}
查看 THIS
的答案这是对Swift的粗略翻译,我使用:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch{
prevPoint1 = touch.previousLocationInView(self.view)
prevPoint2 = touch.previousLocationInView(self.view)
lastPoint = touch.locationInView(self.view)
}
}
override func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch{
let currentPoint = touch.locationInView(view)
prevPoint2 = prevPoint1
prevPoint1 = touch.previousLocationInView(self.view)
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
var mid1 = CGPointMake((prevPoint1.x + prevPoint2.x)*0.5, (prevPoint1.y + prevPoint2.y)*0.5)
var mid2 = CGPointMake((currentPoint.x + prevPoint1.x)*0.5, (currentPoint.y + prevPoint1.y)*0.5)
CGContextMoveToPoint(context, mid1.x, mid1.y)
CGContextAddQuadCurveToPoint(context, prevPoint1.x, prevPoint1.y, mid2.x, mid2.y)
//CGContextAddLineToPoint(context, toPoint.x, toPoint.y)
CGContextSetLineCap(context, kCGLineCapRound)
CGContextSetLineWidth(context, brushWidth)
CGContextSetRGBStrokeColor(context, red, green,blue, 1.0)
CGContextSetBlendMode(context, kCGBlendModeNormal)
CGContextStrokePath(context)
TempImage.image = UIGraphicsGetImageFromCurrentImageContext()
TempImage.alpha = opacity
UIGraphicsEndImageContext()
lastPoint = currentPoint
}
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
UIGraphicsBeginImageContext(MainImage.frame.size)
MainImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: 1.0)
TempImage.image?.drawInRect(CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height), blendMode: kCGBlendModeNormal, alpha: opacity)
MainImage.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
TempImage.image = nil
}
对于Swift3.0
func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
swiped = false
if let touch = touches.first as? UITouch {
lastPoint = touch.location(in: self.view)
}
}
func drawLineFrom(fromPoint: CGPoint, toPoint: CGPoint) {
UIGraphicsBeginImageContext(view.frame.size)
let context = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.height))
context?.move(to: fromPoint)
context?.addLine(to: toPoint)
context?.setLineCap(CGLineCap.round)
context?.setLineWidth(brushWidth)
context?.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0)
context?.setBlendMode(CGBlendMode.normal)
context?.strokePath()
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.alpha = opacity
UIGraphicsEndImageContext()
}
func touchesMoved(touches: Set<NSObject>, withEvent event: UIEvent) {
swiped = true
if let touch = touches.first as? UITouch {
let currentPoint = touch.location(in: view)
drawLineFrom(fromPoint: lastPoint, toPoint: currentPoint)
lastPoint = currentPoint
}
}
更新 Swift 4
import UIKit class ViewController: UIViewController { @IBOutlet weak var imageView: UIImageView! var prevPoint1: CGPoint! var prevPoint2: CGPoint! var lastPoint:CGPoint! var width: CGFloat! var red:CGFloat! var green:CGFloat! var blue:CGFloat! var alpha: CGFloat! override func viewDidLoad() { super.viewDidLoad() width = 3.0 red = (0.0/255.0) green = (0.0/255.0) blue = (0.0/255.0) alpha = 1.0 } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { prevPoint1 = touch.previousLocation(in:self.view) prevPoint2 = touch.previousLocation(in:self.view) lastPoint = touch.location(in:self.view) } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { let currentPoint = touch.location(in: view) prevPoint2 = prevPoint1 prevPoint1 = touch.previousLocation(in: self.view) UIGraphicsBeginImageContext(imageView.frame.size) guard let context = UIGraphicsGetCurrentContext() else { return } context.move(to:prevPoint2) context.addQuadCurve(to: prevPoint1, control: prevPoint2) context.setLineCap(.butt) context.setLineWidth(width) context.setStrokeColor(red: red, green: green, blue: blue, alpha: 1.0) context.setBlendMode(.normal) context.strokePath() imageView.image?.draw(in: CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height), blendMode: .overlay, alpha: 1.0) imageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() lastPoint = currentPoint } } }
这是我对 SwiftUI 的改编。我坚持所有的要点,这样我就可以 undo/redo,这样我就可以拥有原始的接触点,因为这是一个签名。我没有 need/want 实际图像...
其他描述见代码
import SwiftUI
struct DrawPad: View {
@State private var path = Path()
@State private var allStrokes: [[DragGesture.Value]] = []
@State private var currentStroke: [DragGesture.Value] = []
var body: some View {
path
.stroke(Color.blue, lineWidth: 1.0)
.background(Color(white: 0.95))
.gesture(
DragGesture(minimumDistance: 0.1)
.onChanged { value in
addNextPath(from: value)
}
.onEnded { value in
addNextPath(from: value)
// We finished the current stroke. Remove it and start a new one
allStrokes.append(currentStroke)
currentStroke = []
}
)
}
private func addNextPath(from value: DragGesture.Value) {
currentStroke.append(value)
// We need at least 3 points to draw a nice line
guard currentStroke.count >= 3 else { return }
// Get the last 3 points
let first = currentStroke[currentStroke.count - 3]
let second = currentStroke[currentStroke.count - 2]
let third = currentStroke[currentStroke.count - 1]
// Get two mid points. Mid points are just the average of the two points
let firstMid = [first.location, second.location].average
let secondMid = [second.location, third.location].average
// Create the curve from the mid point between first and second to the mid point between second and third
// and the second point is control point. If you draw this out on a piece of paper you will see that
// the second point is the tangent for the first mid point.
path.move(to: firstMid)
path.addQuadCurve(to: secondMid, control: second.location)
}
}
struct DrawPad_Previews: PreviewProvider {
static var previews: some View {
DrawPad()
}
}
extension Array where Element == CGPoint {
var average: CGPoint {
let count = CGFloat(self.count)
let sumX = reduce(0, { [=10=] + .x })
let sumY = reduce(0, { [=10=] + .y })
return CGPoint(x: sumX/count, y: sumY/count)
}
}