iPhone平滑草图绘制algorithm
我正在研究iPhone上的素描应用程序。 我得到它的工作,但没有看到这里漂亮
而我正在寻找任何build议来平滑绘图基本上,我所做的是当用户将手指放在我所称的屏幕上时
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
然后我用数组收集一个单一的触摸
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
当用户从屏幕上移出一个手指时,我打电话
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
然后我使用数组绘制所有点
NSMutableArray *points = [collectedArray points]; CGPoint firstPoint; [[points objectAtIndex:0] getValue:&firstPoint]; CGContextMoveToPoint(context, firstPoint.x, firstPoint.y); CGContextSetLineCap(context, kCGLineCapRound); CGContextSetLineJoin(context, kCGLineJoinRound); for (int i=1; i < [points count]; i++) { NSValue *value = [points objectAtIndex:i]; CGPoint point; [value getValue:&point]; CGContextAddLineToPoint(context, point.x, point.y); } CGContextStrokePath(context); UIGraphicsPushContext(context);
现在我想改善绘图,更像“素描书”应用程序
我认为有一些信号处理algorithm来重新排列arrays中的所有点,但我不确定。 任何帮助将非常感激。
Thankz提前:)
像这样平滑曲线的最简单方法是使用贝塞尔曲线而不是直线段。 对于这个背后的math,请参阅这篇文章 (在这个答案中指出),它描述了如何计算所需的曲线来平滑通过多个点的曲线。
我相信Core Plot框架现在能够平滑绘制曲线,所以你可以看看那里使用的代码来实现这种平滑。
这些都不是什么奇迹,因为这些平滑的例程很快且相对容易实现。
CGPoint midPoint(CGPoint p1, CGPoint p2) { return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; previousPoint1 = [touch previousLocationInView:self]; previousPoint2 = [touch previousLocationInView:self]; currentPoint = [touch locationInView:self]; } -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; previousPoint2 = previousPoint1; previousPoint1 = [touch previousLocationInView:self]; currentPoint = [touch locationInView:self]; // calculate mid point CGPoint mid1 = midPoint(previousPoint1, previousPoint2); CGPoint mid2 = midPoint(currentPoint, previousPoint1); UIGraphicsBeginImageContext(self.imageView.frame.size); CGContextRef context = UIGraphicsGetCurrentContext(); [self.imageView.image drawInRect:CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)]; CGContextMoveToPoint(context, mid1.x, mid1.y); // Use QuadCurve is the key CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y); CGContextSetLineCap(context, kCGLineCapRound); CGContextSetLineWidth(context, 2.0); CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); CGContextStrokePath(context); self.imageView.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); }
我真的很喜欢这个话题。 感谢所有的实现,特别是KrzysztofZabłocki和Yu-Sen Han。 我已经修改了Yu-Sen Han的版本,以便根据平移的速度(实际上是最后一次触摸之间的距离)来改变线的粗细。 此外,我已经实施了点绘图(对于touchBegan和touchEnded位置彼此接近)结果如下:
为了定义线条的粗细,我select了这样一个距离函数:
(不要问我为什么…我只是虽然适合,但我相信你可以find一个更好的)
CGFloat dist = distance(previousPoint1, currentPoint); CGFloat newWidth = 4*(atan(-dist/15+1) + M_PI/2)+2;
还有一个提示。 为了确保厚度变化平稳,我已经根据前一个段的厚度和一个自定义系数来限定厚度:
self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);
Thankz的input。我在这里更新我的任务,因为我需要它的空间。
我查看了你build议的corePlot和Bezier曲线解决scheme。
对于corePlot,我可以从int数组中得到graphics图,但是找不到与曲线平滑有关的任何东西。BTW这里我使用CPScatterPlot和一些随机数。
至于Bezier曲线,我的任务将我带到这里。这与iOS中Spline实现有关
CatmullRomSpline *myC = [[CatmullRomSpline alloc] initAtPoint:CGPointMake(1.0, 1.0)]; [myC addPoint:CGPointMake(1.0, 1.5)]; [myC addPoint:CGPointMake(1.0, 1.15)]; [myC addPoint:CGPointMake(1.0, 1.25)]; [myC addPoint:CGPointMake(1.0, 1.23)]; [myC addPoint:CGPointMake(1.0, 1.24)]; [myC addPoint:CGPointMake(1.0, 1.26)]; NSLog(@"xxppxx %@",[myC asPointArray]); NSLog(@"xxppxx2 %@",myC.curves);
我得到的结果是:
2011-02-24 14:45:53.915 DVA[10041:40b] xxppxx ( "NSPoint: {1, 1}", "NSPoint: {1, 1.26}" ) 2011-02-24 14:45:53.942 DVA[10041:40b] xxppxx2 ( "QuadraticBezierCurve: 0x59eea70" )
我真的不知道该怎么走。 所以我卡在前面以及:(
我查了一下GLPaint,作为最后一个资源。 它使用OpenGLES并使用“软点”精灵来绘制数组中的点。 我知道这更像是避免这个问题而不是解决问题。 但是我想无论如何我都会在这里分享我的发现。
黑色是GLPaint,白色是旧的方法。 最后一个是“Sketch Book”应用程序的绘图,仅供比较
我仍然试图做到这一点,任何进一步的build议是最受欢迎的。
为了摆脱GLPaint代码中的愚蠢的点。
在某一方面的变化
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
这个function
//Ändrat av OLLE /* // Convert touch point from UIView referential to OpenGL one (upside-down flip) if (firstTouch) { firstTouch = NO; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; } else { location = [touch locationInView:self]; location.y = bounds.size.height - location.y; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; } */ location = [touch locationInView:self]; location.y = bounds.size.height - location.y; previousLocation = [touch previousLocationInView:self]; previousLocation.y = bounds.size.height - previousLocation.y; //Ändrat av OLLE//
我知道这不是我们问题的解决scheme,但是它是一些东西。
这里是在Swift中实现的kyoji的答案 ,作为UIImageView
一个可重用的子类。 TouchDrawImageView
子类允许用户用手指绘制图像视图。
一旦你添加了这个TouchDrawImageView
类到你的项目中,一定要打开你的故事板和
- select
TouchDrawImageView
作为图像视图的“自定义类” - 检查图像视图的“用户交互已启用”属性
这里是TouchDrawImageView.swift
的代码:
import UIKit class TouchDrawImageView: UIImageView { var previousPoint1 = CGPoint() override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } previousPoint1 = touch.previousLocation(in: self) } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let previousPoint2 = previousPoint1 previousPoint1 = touch.previousLocation(in: self) let currentPoint = touch.location(in: self) // calculate mid point let mid1 = midPoint(p1: previousPoint1, p2: previousPoint2) let mid2 = midPoint(p1: currentPoint, p2: previousPoint1) UIGraphicsBeginImageContext(self.frame.size) guard let context = UIGraphicsGetCurrentContext() else { return } if let image = self.image { image.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)) } context.move(to: mid1) context.addQuadCurve(to: mid2, control: previousPoint1) context.setLineCap(.round) context.setLineWidth(2.0) context.setStrokeColor(red: 1.0, green: 0, blue: 0, alpha: 1.0) context.strokePath() self.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } func midPoint(p1: CGPoint, p2: CGPoint) -> CGPoint { return CGPoint(x: (p1.x + p2.x) / 2.0, y: (p1.y + p2.y) / 2.0) } }