iPhone平滑的草图绘制算法

Suw*_*ana 60 iphone drawing quartz-graphics ios

我正在开发iPhone上的草图应用程序.我得到了它的工作,但不是在这里看到的漂亮 在此输入图像描述

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

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
Run Code Online (Sandbox Code Playgroud)

然后我收集一个阵列中的单个触摸

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
Run Code Online (Sandbox Code Playgroud)

当用户从屏幕上拿出一根手指时,我打了个电话

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
Run Code Online (Sandbox Code Playgroud)

然后我用数字绘制数组中的所有点

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);
Run Code Online (Sandbox Code Playgroud)

而现在我想改进绘图更像是"Sketch Book"App 在此输入图像描述

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

提前谢谢:)

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

}
Run Code Online (Sandbox Code Playgroud)

  • 这里基于此解决方案的示例项目:https://github.com/brightredchilli/Free-Sketching (5认同)

Bra*_*son 22

像这样平滑曲线的最简单方法是使用贝塞尔曲线而不是直线段.对于这背后的数学,请参阅本文(在本答案中指出),该文章描述了如何计算平滑通过多个点的曲线所需的曲线.

我相信Core Plot框架现在能够平滑曲线的曲线,因此您可以查看用于实现这种平滑的代码.

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

  • 我想指出链接中的贝塞尔曲线文章和其中共享的代码不是交互式草图数据.这种曲线拟合算法要求您在运行算法之前知道所有点. (2认同)

ale*_*nik 15

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

为了定义线条厚度,我选择了这样一个距离函数:

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

在此输入图像描述

CGFloat dist = distance(previousPoint1, currentPoint);
CGFloat newWidth = 4*(atan(-dist/15+1) + M_PI/2)+2;
Run Code Online (Sandbox Code Playgroud)

还有一点提示.为了确保厚度平滑变化,我根据前一段的厚度和自定义系数限制它:

self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);
Run Code Online (Sandbox Code Playgroud)


Lar*_*erg 5

我将kyoji的答案翻译成Swift,作为的可重用子类UIImageView。该子类TouchDrawImageView允许用户用手指在图像视图上进行绘制。

将此类添加TouchDrawImageView到项目后,请确保打开情节提要,然后

  1. 选择TouchDrawImageView作为图像视图的“自定义类”
  2. 检查图像视图的“已启用用户交互”属性

这是的代码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)
    }
}
Run Code Online (Sandbox Code Playgroud)