iPhone平滑的草图绘制算法

时间:2011-02-22 09:56:32

标签: ios iphone drawing quartz-graphics

我正在开发iPhone上的草图应用程序。 我得到了它的工作,但不是在这里看到的漂亮 enter image description here

我正在寻找任何平滑绘图的建议 基本上,我所做的是当用户将手指放在我称之为

的屏幕上时
- (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);

现在我想改进绘图更像是“Sketch Book”App enter image description here

我认为与信号处理算法有关,要重新排列阵列中的所有点,但我不确定。任何帮助将非常感激。

提前谢谢:)

6 个答案:

答案 0 :(得分:56)

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();

}

答案 1 :(得分:22)

像这样平滑曲线的最简单方法是使用贝塞尔曲线而不是直线段。有关此背后的数学,请参阅this article(在this answer中指出),其中介绍了如何计算平滑通过多个点的曲线所需的曲线。

我相信Core Plot framework现在能够平滑曲线的曲线,所以你可以看看那里用来实现这种平滑的代码。

这些都没有任何魔力,因为这些平滑程序快速且相对容易实现。

答案 2 :(得分:15)

我真的很喜欢这个话题。感谢所有的实施,特别是KrzysztofZabłocki和Yu-Sen Han。 我修改了Yu-Sen Han的版本,以便根据平移速度(实际上是最后一次触摸之间的距离)来改变线条粗细。我也实现了点画(对于touchBegan和touchEnded位置彼此接近) 结果如下: enter image description here

要定义线条粗细,我选择了这样一个距离函数:

(不要问我为什么......我只是觉得它很合适,但我相信你能找到更好的一个)

enter image description here

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);

答案 3 :(得分:5)

我将kyoji's answer翻译成Swift,作为UIImageView的可重用子类。子类TouchDrawImageView允许用户用手指在图像视图上绘图。

将此TouchDrawImageView课程添加到项目后,请务必打开故事板并

  1. 选择TouchDrawImageView作为&#34;自定义类&#34;您的图片视图
  2. 检查&#34;启用用户交互&#34;图像视图的属性
  3. 这里是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)
        }
    }
    

答案 4 :(得分:3)

感谢输入。我在这里更新我的任务,因为我需要空间。

我查看了您提出的corePlot和Bezier曲线解决方案,但收效甚微。

对于corePlot,我能够从int数组中获取图形图,但找不到与曲线平滑相关的任何内容.BTW这里我使用CPScatterPlot和一些随机数。

enter image description here

至于Bezier曲线,我的任务引导我here这与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”应用程序的绘图,只是为了比较

enter image description here enter image description here enter image description here

我仍在努力做到这一点,任何进一步的建议都是最受欢迎的。

答案 5 :(得分:1)

摆脱GLPaint代码中的愚蠢点。

改变

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

这个功能

//Ä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//

我知道这不是我们问题的解决方案,但它确实存在。