ios 绘制平滑曲线 - 需要的方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8702696/
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
Drawing Smooth Curves - Methods Needed
提问by BDGapps
How do you smooth a set of points in an iOS drawing app WHILE MOVING? I have tried UIBezierpaths but all I get are jagged ends where they intersect, when I just shift the points 1,2,3,4 - 2,3,4,5. I have heard of spline curves and all the other types. I am quite new to iPhone programming and do not understand how to program it in my quartz drawing app. A solid example would be greatly appreciated, I have spent weeks running in circles and I can never seem to find any iOS code for this task. Most of the posts just link to a java simulation or pages on wikipedia about curve fitting which does nothing for me. Also I do not want to switch to openGL ES. I hope someone can finally provide code to answer this circulating question.
如何在移动时平滑 iOS 绘图应用程序中的一组点?我试过 UIBezierpaths 但我得到的只是它们相交的锯齿状末端,当我只是移动点 1,2,3,4 - 2,3,4,5 时。我听说过样条曲线和所有其他类型。我对 iPhone 编程很陌生,不知道如何在我的石英绘图应用程序中进行编程。一个可靠的例子将不胜感激,我花了数周的时间在圈子里跑,我似乎永远找不到这个任务的任何 iOS 代码。大多数帖子只是链接到 Java 模拟或维基百科上关于曲线拟合的页面,这对我没有任何帮助。我也不想切换到 openGL ES。我希望有人最终可以提供代码来回答这个循环问题。
This was my code for the UIBezierPath which left edges at intersection///
这是我的 UIBezierPath 代码,它在交点处留有边缘//
UPDATED TO AN ANSWER BELOW
更新到下面的答案
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]
- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity
{
NSMutableArray *points = [(NSMutableArray*)[self pointsOrdered] mutableCopy];
if (points.count < 4) return [self bezierPath];
// Add control points to make the math make sense
[points insertObject:[points objectAtIndex:0] atIndex:0];
[points addObject:[points lastObject]];
UIBezierPath *smoothedPath = [self bezierPath];
[smoothedPath removeAllPoints];
[smoothedPath moveToPoint:POINT(0)];
for (NSUInteger index = 1; index < points.count - 2; index++)
{
CGPoint p0 = POINT(index - 1);
CGPoint p1 = POINT(index);
CGPoint p2 = POINT(index + 1);
CGPoint p3 = POINT(index + 2);
// now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
for (int i = 1; i < granularity; i++)
{
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;
CGPoint pi; // intermediate point
pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
[smoothedPath addLineToPoint:pi];
}
// Now add p2
[smoothedPath addLineToPoint:p2];
}
// finish by adding the last point
[smoothedPath addLineToPoint:POINT(points.count - 1)];
return smoothedPath;
}
- (PVPoint *)pointAppendingCGPoint:(CGPoint)CGPoint
{
PVPoint *newPoint = [[PVPoint alloc] initInsertingIntoManagedObjectContext:[self managedObjectContext]];
[newPoint setCGPoint:CGPoint];
[newPoint setOrder:[NSNumber numberWithUnsignedInteger:[[self points] count]]];
[[self mutableSetValueForKey:@"points"] addObject:newPoint];
[(NSMutableArray *)[self pointsOrdered] addObject:newPoint];
[[self bezierPath] addLineToPoint:CGPoint];
return [newPoint autorelease];
if ([self bezierPath] && [pointsOrdered count] > 3)
{
PVPoint *control1 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 2];
PVPoint *control2 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 1];
[bezierPath moveToPoint:[[pointsOrdered objectAtIndex:[pointsOrdered count] - 3] CGPoint]];
[[self bezierPath] addCurveToPoint:CGPoint controlPoint1:[control1 CGPoint] controlPoint2:[control2 CGPoint]];
}
}
- (BOOL)isComplete { return [[self points] count] > 1; }
- (UIBezierPath *)bezierPath
{
if (!bezierPath)
{
bezierPath = [UIBezierPath bezierPath];
for (NSUInteger p = 0; p < [[self points] count]; p++)
{
if (!p) [bezierPath moveToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]];
else [bezierPath addLineToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]];
}
[bezierPath retain];
}
return bezierPath;
}
- (CGPathRef)CGPath
{
return [[self bezierPath] CGPath];
}
回答by Joshua Weinberg
I just implemented something similar in a project I am working on. My solution was to use a Catmull-Rom spline instead of using Bezier splines. These provide a very smooth curve THROUGH a set a points rather then a bezier spline 'around' points.
我刚刚在我正在从事的项目中实现了类似的东西。我的解决方案是使用 Catmull-Rom 样条而不是使用 Bezier 样条。这些通过一组点而不是“围绕”点的贝塞尔样条提供了非常平滑的曲线。
// Based on code from Erica Sadun
#import "UIBezierPath+Smoothing.h"
void getPointsFromBezier(void *info, const CGPathElement *element);
NSArray *pointsFromBezierPath(UIBezierPath *bpath);
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]
#define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]
@implementation UIBezierPath (Smoothing)
// Get points from Bezier Curve
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
// Retrieve the path element type and its points
CGPathElementType type = element->type;
CGPoint *points = element->points;
// Add the points if they're available (per type)
if (type != kCGPathElementCloseSubpath)
{
[bezierPoints addObject:VALUE(0)];
if ((type != kCGPathElementAddLineToPoint) &&
(type != kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(1)];
}
if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}
NSArray *pointsFromBezierPath(UIBezierPath *bpath)
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier);
return points;
}
- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity;
{
NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy];
if (points.count < 4) return [self copy];
// Add control points to make the math make sense
[points insertObject:[points objectAtIndex:0] atIndex:0];
[points addObject:[points lastObject]];
UIBezierPath *smoothedPath = [self copy];
[smoothedPath removeAllPoints];
[smoothedPath moveToPoint:POINT(0)];
for (NSUInteger index = 1; index < points.count - 2; index++)
{
CGPoint p0 = POINT(index - 1);
CGPoint p1 = POINT(index);
CGPoint p2 = POINT(index + 1);
CGPoint p3 = POINT(index + 2);
// now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
for (int i = 1; i < granularity; i++)
{
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;
CGPoint pi; // intermediate point
pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
[smoothedPath addLineToPoint:pi];
}
// Now add p2
[smoothedPath addLineToPoint:p2];
}
// finish by adding the last point
[smoothedPath addLineToPoint:POINT(points.count - 1)];
return smoothedPath;
}
@end
The original Catmull-Rom implementation is based on some code from Erica Sadun in one of her books, I modified it slightly to allow for a full smoothed curve. This is implemented as a category on UIBezierPath and worked out very well for me.
最初的 Catmull-Rom 实现基于 Erica Sadun 在她的一本书中的一些代码,我稍微修改了它以允许完全平滑的曲线。这是作为 UIBezierPath 上的一个类别实现的,对我来说效果很好。
回答by user1244109
@Rakesh is absolutely right - you dont need to use Catmull-Rom algorithm if you just want a curved line. And the link he suggested does exacly that. So here's an addition to his answer.
@Rakesh 是绝对正确的 - 如果您只想要一条曲线,则不需要使用 Catmull-Rom 算法。他建议的链接正是这样做的。所以这是对他的回答的补充。
The code bellow does NOTuse Catmull-Rom algorithm & granularity, but draws a quad-curved line (control points are calculated for you). This is essentially what's done in the ios freehand drawing tutorialsuggested by Rakesh, but in a standalone method that you can drop anywhere (or in a UIBezierPath category) and get a quad-curved spline out of the box.
下面的代码不使用 Catmull-Rom 算法和粒度,而是绘制了一条四边形曲线(为您计算控制点)。这基本上是在Rakesh 建议的ios 手绘教程中完成的,但是在一个独立的方法中,您可以将其放置在任何地方(或在 UIBezierPath 类别中)并获得开箱即用的四曲线样条。
You do need to have an array of CGPoint
's wrapped in NSValue
's
您确实需要将CGPoint
's数组包裹在NSValue
's 中
+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
UIBezierPath *path = [UIBezierPath bezierPath];
NSValue *value = points[0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:p1];
if (points.count == 2) {
value = points[1];
CGPoint p2 = [value CGPointValue];
[path addLineToPoint:p2];
return path;
}
for (NSUInteger i = 1; i < points.count; i++) {
value = points[i];
CGPoint p2 = [value CGPointValue];
CGPoint midPoint = midPointForPoints(p1, p2);
[path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
[path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];
p1 = p2;
}
return path;
}
static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
CGPoint controlPoint = midPointForPoints(p1, p2);
CGFloat diffY = abs(p2.y - controlPoint.y);
if (p1.y < p2.y)
controlPoint.y += diffY;
else if (p1.y > p2.y)
controlPoint.y -= diffY;
return controlPoint;
}
Here's the result:
结果如下:
回答by colinta
Some good answers here, though I think they are either way off (user1244109's answer only supports horizontal tangents, not useful for generic curves), or overly complicated (sorry Catmull-Rom fans).
这里有一些很好的答案,尽管我认为它们要么偏离(user1244109 的答案仅支持水平切线,对通用曲线没有用),要么过于复杂(对不起 Catmull-Rom 粉丝)。
I implemented this in a much simpler way, using Quad bezier curves. These need a start point, an end point, and a control point. The natural thing to do might beto use the touch points as the start & end points. Don't do this!There are no appropriate control points to use. Instead, try this idea: use the touch points as control points, and the midpointsas the start/end points. You're guaranteed to have proper tangents this way, and the code is stupid simple. Here's the algorithm:
我使用 Quad bezier 曲线以更简单的方式实现了这一点。这些需要一个起点、一个终点和一个控制点。自然的做法可能是使用接触点作为起点和终点。 不要这样做!没有合适的控制点可供使用。相反,试试这个想法:使用接触点作为控制点,使用中点作为起点/终点。您可以保证以这种方式获得适当的切线,并且代码非常简单。这是算法:
- The "touch down" point is the start of the path, and store
location
inprevPoint
. - For every dragged location, calculate
midPoint
, the point betweencurrentPoint
andprevPoint
.- If this is the first dragged location, add
currentPoint
as a line segment. - For all points in the future, add a quad curve that terminatesat the
midPoint
, and use theprevPoint
as the control point. This will create a segment that gently curves from the previous point to the current point.
- If this is the first dragged location, add
- Store
currentPoint
inprevPoint
, and repeat #2 until dragging ends. - Add the final point as another straight segment, to finish up the path.
- “着陆”点是路径的起点,并存储
location
在prevPoint
. - 对于每个拖动的位置,计算和
midPoint
之间的点。currentPoint
prevPoint
- 如果这是第一个拖动位置,则添加
currentPoint
为线段。 - 对于未来的所有点,添加终止于 的四边形曲线
midPoint
,并将prevPoint
用作控制点。这将创建一个从前一个点到当前点轻轻弯曲的线段。
- 如果这是第一个拖动位置,则添加
- 存储
currentPoint
在 中prevPoint
,然后重复 #2 直到拖动结束。 - 添加最后一点作为另一个直线段,以完成路径。
This results in very good looking curves, because using the midPoints guarantees that the curve is a smooth tangent at the end points (see attached photo).
这会产生非常好看的曲线,因为使用 midPoints 可以保证曲线在端点处是平滑的切线(见附图)。
Swift code looks like this:
Swift 代码如下所示:
var bezierPath = UIBezierPath()
var prevPoint: CGPoint?
var isFirst = true
override func touchesBegan(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
let location = touchesSet.first!.locationInView(self)
bezierPath.removeAllPoints()
bezierPath.moveToPoint(location)
prevPoint = location
}
override func touchesMoved(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
let location = touchesSet.first!.locationInView(self)
if let prevPoint = prevPoint {
let midPoint = CGPoint(
x: (location.x + prevPoint.x) / 2,
y: (location.y + prevPoint.y) / 2,
)
if isFirst {
bezierPath.addLineToPoint(midPoint)
else {
bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
}
isFirst = false
}
prevPoint = location
}
override func touchesEnded(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
let location = touchesSet.first!.locationInView(self)
bezierPath.addLineToPoint(location)
}
Or, if you have an array of points and want to construct the UIBezierPath
in one shot:
或者,如果您有一组点并想UIBezierPath
一次性构建:
var points: [CGPoint] = [...]
var bezierPath = UIBezierPath()
var prevPoint: CGPoint?
var isFirst = true
// obv, there are lots of ways of doing this. let's
// please refrain from yak shaving in the comments
for point in points {
if let prevPoint = prevPoint {
let midPoint = CGPoint(
x: (point.x + prevPoint.x) / 2,
y: (point.y + prevPoint.y) / 2,
)
if isFirst {
bezierPath.addLineToPoint(midPoint)
}
else {
bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
}
isFirst = false
}
else {
bezierPath.moveToPoint(point)
}
prevPoint = point
}
if let prevPoint = prevPoint {
bezierPath.addLineToPoint(prevPoint)
}
Here are my notes:
以下是我的笔记:
回答by Caleb
The key to getting two bezier curves to join smoothly is that the relevant control points and the start/end points on the curves must be collinear. Think of the control point and the endpoint as forming a line that's tangent to the curve at the endpoint. If one curve starts at the same point where another ends, and if they both have the same tangent line at that point, the curve will be smooth. Here's a bit of code to illustrate:
使两条贝塞尔曲线平滑连接的关键是相关控制点和曲线上的起点/终点必须共线。将控制点和端点视为形成一条与端点处的曲线相切的线。如果一条曲线的起点与另一条曲线的终点相同,并且它们在该点的切线相同,则曲线将是平滑的。下面是一些代码来说明:
- (void)drawRect:(CGRect)rect
{
#define commonY 117
CGPoint point1 = CGPointMake(20, 20);
CGPoint point2 = CGPointMake(100, commonY);
CGPoint point3 = CGPointMake(200, 50);
CGPoint controlPoint1 = CGPointMake(50, 60);
CGPoint controlPoint2 = CGPointMake(20, commonY);
CGPoint controlPoint3 = CGPointMake(200, commonY);
CGPoint controlPoint4 = CGPointMake(250, 75);
UIBezierPath *path1 = [UIBezierPath bezierPath];
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path1 setLineWidth:3.0];
[path1 moveToPoint:point1];
[path1 addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2];
[[UIColor blueColor] set];
[path1 stroke];
[path2 setLineWidth:3.0];
[path2 moveToPoint:point2];
[path2 addCurveToPoint:point3 controlPoint1:controlPoint3 controlPoint2:controlPoint4];
[[UIColor orangeColor] set];
[path2 stroke];
}
Notice that path1
ends at point2
, path2
starts at point2
, and control points 2 and 3 share the same Y-value, commonY
, with point2
. You can change any of the values in the code as you like; as long as those three points all fall on the same line, the two paths will join smoothly. (In the code above, the line is y = commonY
. The line doesn't have to be parallel to the X axis; it's just easier to see that the points are collinear that way.)
注意,path1
端部在point2
,path2
开始于point2
,和控制点2和3共享相同的Y值,commonY
与point2
。您可以根据需要更改代码中的任何值;只要这三个点都在同一条线上,两条路径就会顺利连接起来。(在上面的代码中,线是y = commonY
。线不必平行于 X 轴;这样更容易看出点共线。)
Here's the image that the code above draws:
这是上面的代码绘制的图像:
After looking at your code, the reason that your curve is jagged is that you're thinking of control points as points on the curve. In a bezier curve, the control points are usually not on the curve. Since you're taking the control points from the curve, the control points and the point of intersection are notcollinear, and the paths therefore don't join smoothly.
查看您的代码后,您的曲线呈锯齿状的原因是您将控制点视为曲线上的点。在贝塞尔曲线中,控制点通常不在曲线上。由于您从曲线中获取控制点,因此控制点和交点不共线,因此路径不会平滑连接。
回答by user1129629
We need to observe some thing before applying any algorithm on captured points.
在对捕获的点应用任何算法之前,我们需要观察一些事情。
- Generally UIKit does not give the points at equal distance.
- We need to calculate the intermediate points in between two CGPoints[ Which has captured with Touch moved method]
- 通常 UIKit 不会给出等距的点。
- 我们需要计算两个 CGPoints 之间的中间点 [ 用触摸移动方法捕获的]
Now to get smooth line, there are so many ways.
现在要获得流畅的线条,有很多方法。
Some times we can achieve the by applying second degree polynomial or third degree polynomial or catmullRomSpline algorithms
有时我们可以通过应用二次多项式或三次多项式或 catmullRomSpline 算法来实现
- (float)findDistance:(CGPoint)point lineA:(CGPoint)lineA lineB:(CGPoint)lineB
{
CGPoint v1 = CGPointMake(lineB.x - lineA.x, lineB.y - lineA.y);
CGPoint v2 = CGPointMake(point.x - lineA.x, point.y - lineA.y);
float lenV1 = sqrt(v1.x * v1.x + v1.y * v1.y);
float lenV2 = sqrt(v2.x * v2.x + v2.y * v2.y);
float angle = acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2));
return sin(angle) * lenV2;
}
- (NSArray *)douglasPeucker:(NSArray *)points epsilon:(float)epsilon
{
int count = [points count];
if(count < 3) {
return points;
}
//Find the point with the maximum distance
float dmax = 0;
int index = 0;
for(int i = 1; i < count - 1; i++) {
CGPoint point = [[points objectAtIndex:i] CGPointValue];
CGPoint lineA = [[points objectAtIndex:0] CGPointValue];
CGPoint lineB = [[points objectAtIndex:count - 1] CGPointValue];
float d = [self findDistance:point lineA:lineA lineB:lineB];
if(d > dmax) {
index = i;
dmax = d;
}
}
//If max distance is greater than epsilon, recursively simplify
NSArray *resultList;
if(dmax > epsilon) {
NSArray *recResults1 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(0, index + 1)] epsilon:epsilon];
NSArray *recResults2 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(index, count - index)] epsilon:epsilon];
NSMutableArray *tmpList = [NSMutableArray arrayWithArray:recResults1];
[tmpList removeLastObject];
[tmpList addObjectsFromArray:recResults2];
resultList = tmpList;
} else {
resultList = [NSArray arrayWithObjects:[points objectAtIndex:0], [points objectAtIndex:count - 1],nil];
}
return resultList;
}
- (NSArray *)catmullRomSplineAlgorithmOnPoints:(NSArray *)points segments:(int)segments
{
int count = [points count];
if(count < 4) {
return points;
}
float b[segments][4];
{
// precompute interpolation parameters
float t = 0.0f;
float dt = 1.0f/(float)segments;
for (int i = 0; i < segments; i++, t+=dt) {
float tt = t*t;
float ttt = tt * t;
b[i][0] = 0.5f * (-ttt + 2.0f*tt - t);
b[i][1] = 0.5f * (3.0f*ttt -5.0f*tt +2.0f);
b[i][2] = 0.5f * (-3.0f*ttt + 4.0f*tt + t);
b[i][3] = 0.5f * (ttt - tt);
}
}
NSMutableArray *resultArray = [NSMutableArray array];
{
int i = 0; // first control point
[resultArray addObject:[points objectAtIndex:0]];
for (int j = 1; j < segments; j++) {
CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue];
float px = (b[j][0]+b[j][1])*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x;
float py = (b[j][0]+b[j][1])*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y;
[resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
}
}
for (int i = 1; i < count-2; i++) {
// the first interpolated point is always the original control point
[resultArray addObject:[points objectAtIndex:i]];
for (int j = 1; j < segments; j++) {
CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue];
CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue];
float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x;
float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y;
[resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
}
}
{
int i = count-2; // second to last control point
[resultArray addObject:[points objectAtIndex:i]];
for (int j = 1; j < segments; j++) {
CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue];
CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + (b[j][2]+b[j][3])*pointIp1.x;
float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + (b[j][2]+b[j][3])*pointIp1.y;
[resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
}
}
// the very last interpolated point is the last control point
[resultArray addObject:[points objectAtIndex:(count - 1)]];
return resultArray;
}
回答by Amogh Talpallikar
For achieving this we need to use this method. BezierSplinethe code is in C# to generate arrays of control points for a bezier spline. I converted this code to Objective C and it works brilliantly for me.
为了实现这一点,我们需要使用这种方法。 BezierSpline代码在 C# 中,用于为贝塞尔样条生成控制点数组。我将此代码转换为目标 C,它对我来说非常有效。
To convert the code from C# to Objective C. understand the C# code line by line, even if you dont know C#, u must be knowing C++/Java ?
要将代码从 C# 转换为 Objective C。逐行理解 C# 代码,即使您不了解 C#,您也必须了解 C++/Java 吗?
While converting:
转换时:
Replace Point struct used here with CGPoint.
Replace Point array with NSMutableArray and store NSvalues wrapping CGPoints in it.
Replace all double arrays with NSMutableArrays and store NSNumber wrapping double in it.
use objectAtIndex: method in case of subscript for accessing array elements.
use replaceObjectAtIndex:withObject: to store objects at specific index.
Remember that NSMutableArray is a linkedList and what C# uses are dynamic arrays so they already have existing indices. In your case, in a NSMutableArray if it is empty, you cant store objects at random indices as the C# code does. they at times in this C# code, populate index 1 before index 0 and they can do so as index 1 exists. in NSMutabelArrays here, index 1 should be there if u want to call replaceObject on it. so before storing anything make a method that will add n NSNull objects in the NSMutableArray.
将此处使用的 Point 结构替换为 CGPoint。
用 NSMutableArray 替换 Point 数组,并在其中存储包含 CGPoints 的 NSvalues。
用 NSMutableArrays 替换所有 double 数组并将 NSNumber 包装在其中存储。
在访问数组元素的下标的情况下使用 objectAtIndex: 方法。
使用 replaceObjectAtIndex:withObject: 将对象存储在特定索引处。
请记住,NSMutableArray 是一个链表,而 C# 使用的是动态数组,因此它们已经具有现有索引。在您的情况下,在 NSMutableArray 中,如果它为空,则不能像 C# 代码那样以随机索引存储对象。他们有时在这个 C# 代码中,在索引 0 之前填充索引 1,并且他们可以在索引 1 存在时这样做。在此处的 NSMutabelArrays 中,如果您想在其上调用 replaceObject,则索引 1 应该在那里。所以在存储任何东西之前,创建一个将在 NSMutableArray 中添加 n 个 NSNull 对象的方法。
ALSO :
还 :
well this logic has a static method that will accept an array of points and give you two arrays:-
好吧,这个逻辑有一个静态方法,它将接受一个点数组并为您提供两个数组:-
array of first control points.
array of second control points.
第一个控制点的数组。
第二个控制点的数组。
These arrays will hold first and second control point for each curve between two points you pass in the first array.
这些数组将保存您在第一个数组中传递的两点之间的每条曲线的第一个和第二个控制点。
In my case, I already had all the points and I could draw curve through them.
就我而言,我已经有了所有的点,我可以通过它们绘制曲线。
In you case while drawing, you will need to some how supply a set of points through which you want a smooth curve to pass.
在您绘制的情况下,您需要了解如何提供一组您希望平滑曲线通过的点。
and refresh by calling setNeedsDisplay and draw the spline which is nothing but UIBezierPath between two adjacent points in the first array. and taking control points from both the control point arrays index wise.
并通过调用 setNeedsDisplay 刷新并绘制样条线,它只是第一个数组中两个相邻点之间的 UIBezierPath 。并从两个控制点数组索引中获取控制点。
Problem in your case is that, its difficult to understand while moving what all critical points to take.
在您的情况下,问题在于,在移动所有关键点时很难理解。
What you can do is: Simply while moving the finger keep drawing straight lines between previous and current point. Lines will be so small that it wont be visible to naked eye that they are small small straight lines unless you zoom in.
您可以做的是:只需在移动手指的同时,继续在前一个点和当前点之间绘制直线。线条会非常小以至于肉眼看不到它们是很小的小直线,除非你放大。
UPDATE
更新
Anyone interested in an Objective C implementation of the link above can refer to
任何对上述链接的 Objective C 实现感兴趣的人都可以参考
thisGitHub repo.
这个GitHub 仓库。
I wrote it sometime back and it doesn't support ARC, but you can easily edit it and remove few release and autorelease calls and get it working with ARC.
我在某个时候写过它,它不支持 ARC,但是您可以轻松编辑它并删除一些 release 和 autorelease 调用并使其与 ARC 一起使用。
This one just generates two arrays of control points for a set of points which one wants to join using bezier spline.
这个只是为一组想要使用贝塞尔样条连接的点生成两个控制点数组。
回答by Rakesh
Dont need to write this much of code.
不需要写这么多代码。
Just refer to the ios freehand drawing tutorial; it really smoothen the drawing, also cache mechanism is there so that performance does not go down even when you keep drawing continuously.
参考ios徒手画教程即可;它确实使绘图变得平滑,还有缓存机制,因此即使您继续绘图,性能也不会下降。
回答by Mike Zriel
Here is the code in Swift 4/5
这是 Swift 4/5 中的代码
func quadCurvedPathWithPoint(points: [CGPoint] ) -> UIBezierPath {
let path = UIBezierPath()
if points.count > 1 {
var prevPoint:CGPoint?
for (index, point) in points.enumerated() {
if index == 0 {
path.move(to: point)
} else {
if index == 1 {
path.addLine(to: point)
}
if prevPoint != nil {
let midPoint = self.midPointForPoints(from: prevPoint!, to: point)
path.addQuadCurve(to: midPoint, controlPoint: controlPointForPoints(from: midPoint, to: prevPoint!))
path.addQuadCurve(to: point, controlPoint: controlPointForPoints(from: midPoint, to: point))
}
}
prevPoint = point
}
}
return path
}
func midPointForPoints(from p1:CGPoint, to p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2)
}
func controlPointForPoints(from p1:CGPoint,to p2:CGPoint) -> CGPoint {
var controlPoint = midPointForPoints(from:p1, to: p2)
let diffY = abs(p2.y - controlPoint.y)
if p1.y < p2.y {
controlPoint.y = controlPoint.y + diffY
} else if ( p1.y > p2.y ) {
controlPoint.y = controlPoint.y - diffY
}
return controlPoint
}
回答by d512
I found a pretty nice tutorialthat describes a slight modification to Bezier curve drawing that does tend to smooth out the edges pretty nicely. It's essentially what Caleb is referring to above about putting the joining end points on the same line as the control points. It's one of the best tutorials (on anything) that I've read in a while. And it comes with a fully working Xcode project.
我发现了一个非常好的教程,它描述了对贝塞尔曲线绘制的轻微修改,它确实可以很好地平滑边缘。这基本上就是 Caleb 在上面提到的关于将连接端点与控制点放在同一条线上的内容。这是我读过的最好的教程之一(关于任何东西)。它带有一个完全可用的 Xcode 项目。
回答by GeneCode
I tried all of the above, but can't make it work. One of the answer yield a broken result for me even. Upon searching more I found this: https://github.com/sam-keene/uiBezierPath-hermite-curve. I did not write this code, but I implemented it and it works really really well. Just copy the UIBezierPath+Interpolation.m/h and CGPointExtension.m/h. Then you use it like this:
我尝试了上述所有方法,但无法使其正常工作。其中一个答案甚至对我来说产生了一个破碎的结果。在搜索更多后,我发现了这个:https: //github.com/sam-keene/uiBezierPath-hermite-curve。这段代码不是我写的,但我实现了它,它运行得非常好。只需复制 UIBezierPath+Interpolation.m/h 和 CGPointExtension.m/h。然后你像这样使用它:
UIBezierPath *path = [UIBezierPath interpolateCGPointsWithHermite:arrayPoints closed:YES];
It is really a robust and neat solution overall.
总体而言,这确实是一个强大而简洁的解决方案。