ios iPhone 流畅的草图绘制算法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5076622/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:58:29  来源:igfitidea点击:

iPhone smooth sketch drawing algorithm

iosiphonedrawingquartz-graphics

提问by Suwitcha Sugthana

I am working on a sketching app on the iPhone. I got it working but not pretty as seen here enter image description here

我正在 iPhone 上开发一个素描应用程序。我让它工作了,但不像这里看到的那样漂亮 在此处输入图片说明

And I am looking for any suggestion to smooth the drawing Basically, what I did is when user places a finger on the screen I called

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

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

then I collect a single touch in an array with

然后我在一个数组中收集一个触摸

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

and when the user lefts a finger from the screen, I called

当用户从屏幕上离开一个手指时,我打电话

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

then I draw all the points in the array using

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

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

And now I want to improve the drawing tobe more like "Sketch Book" App enter image description here

现在我想将绘图改进为更像“素描本”应用程序 在此处输入图片说明

I think there is something to do with signal processing algorithm to rearrange all the points in the array but I am not sure. Any Help would be much appreciated.

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

Thankz in advance :)

提前谢谢:)

采纳答案by Brad Larson

The easiest way to smooth a curve like this is to use a Bezier curve instead of straight line segments. For the math behind this, see this article(pointed to in this answer), which describes how to calculate the curves required to smooth a curve that passes through multiple points.

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

I believe that the Core Plot frameworknow has the ability to smooth the curves of plots, so you could look at the code used there to implement this kind of smoothing.

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

There's no magic to any of this, as these smoothing routines are fast and relatively easy to implement.

这一切都没有什么神奇之处,因为这些平滑程序速度快且相对容易实现。

回答by kyoji

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

}

回答by alexburtnik

I really love the topic. Thanks for all the implementations, espesially Krzysztof Zab?ocki and Yu-Sen Han. I have modified the version of Yu-Sen Han in order to change line thickness depending on the speed of panning (in fact the distance between last touches). Also I've implemented dot drawing (for touchBegan and touchEnded locations being close to each other) Here is the result: enter image description here

我真的很喜欢这个话题。感谢所有的实现,特别是 Krzysztof Zab?ocki 和 Yu-Sen Han。我修改了 Yu-Sen Han 的版本,以便根据平移速度(实际上是最后一次触摸之间的距离)改变线条粗细。我还实现了点绘制(对于彼此靠近的 touchBegan 和 touchEnded 位置)这是结果: 在此处输入图片说明

To define the line thickness I've chosen such a function of distance:

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

(Don't ask me why... I just though it suits well, but I'm sure you can find a better one)

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

enter image description here

在此处输入图片说明

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

One more hint. To be sure the thickness is changing smoothly, I've bounded it depending on the thickness of the previous segment and a custom coef:

再一个提示。为了确保厚度平滑变化,我根据前一段的厚度和自定义系数对其进行了限制:

self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);

回答by Lars Blumberg

I translated kyoji's answerinto Swift, as a reusable subclass of UIImageView. The subclass TouchDrawImageViewallows the user to draw on an image view with her finger.

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

Once you've added this TouchDrawImageViewclass to your project, make sure to open your storyboard and

将此类添加TouchDrawImageView到项目后,请确保打开故事板并

  1. select TouchDrawImageViewas the "Custom Class" of your image view
  2. check "User Interaction Enabled" property of your image view
  1. 选择TouchDrawImageView作为图像视图的“自定义类”
  2. 检查图像视图的“启用用户交互”属性

Here's the code of TouchDrawImageView.swift:

这里的代码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)
    }
}

回答by Suwitcha Sugthana

Thankz for the input.I update my quest here because I need the space for it.

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

I look up both corePlot and Bezier curve solutions that you suggested with little success.

我查找了您建议的 corePlot 和 Bezier 曲线解决方案,但收效甚微。

For the corePlot I am able to get the graph plot from an array of int but can't find anything related to curve smoothing.BTW Here I am using CPScatterPlot with some random number.

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

enter image description here

在此处输入图片说明

as for Bezier curve, My quest lead me to hereIt is something to do with Spline implementation in iOS

至于贝塞尔曲线,我的追求将我带到这里这与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);

and the result I get is:

我得到的结果是:

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

I am not really sure how to go from there. So I am stuck on that front as well :(

我不太确定如何从那里开始。所以我也被困在这方面:(

I did look up GLPaint, as a last resource. It uses OpenGLES and use a "soft dot" sprite to plot the points in the array. I know it's more like avoiding the problem rather than fixing it. But I guess I'l share my findings here anyway.

我确实查找了 GLPaint,作为最后的资源。它使用 OpenGLES 并使用“软点”精灵来绘制数组中的点。我知道这更像是避免问题而不是解决问题。但我想无论如何我都会在这里分享我的发现。

The Black is GLPaint and the white one is the old method. And the last one is the drawing from "Sketch Book" app just to compare

黑色是 GLPaint,白色是旧方法。最后一张是来自“Sketch Book”应用程序的绘图只是为了比较

enter image description hereenter image description hereenter image description here

在此处输入图片说明在此处输入图片说明在此处输入图片说明

I am still trying to get this done right, any further suggestion are most welcome.

我仍在努力把这件事做好,欢迎任何进一步的建议。

回答by Olle

To get rid of the silly dot in the GLPaint code.

去掉 GLPaint 代码中的傻点。

Change in

在某一方面的变化

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

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

I know that this isn't the solution for our problem, but it's something.

我知道这不是我们问题的解决方案,但它是一些东西。