沿着贝塞尔曲线路径放置图像 [英] Place images along a bezier path

查看:124
本文介绍了沿着贝塞尔曲线路径放置图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有谁知道如何沿着bezier路径放置图像?我可以很好地编写路径并沿着路径设置精灵动画,但我想让路径成为一系列箭头而不是虚线。我假设必须有一种方法在路径上添加箭头图像但无法找到它。这些路径都是弯曲的:

Does anyone know how to place images along a bezier path? I can write the path fine and animate a sprite along the path but I want to make the path a series of arrows instead of dotted lines. I assumed there must be a way to add arrow images all along the path but can't find it. Also the paths are all curved:

UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:startingPoint];
[path addCurveToPoint:endPoint controlPoint1:controlPoint1 controlPoint2:controlPoint2];


推荐答案

我想你想要这样的东西:

I guess you want something like this:

您可以找到我的完整演示应用程序项目在这个github存储库中

You can find my complete demo app project in this github repository.

无论如何,这是一个有趣的小问题。

Anyway, this is an interesting little problem.

你需要在路径上生成一个点数组,我假设你希望它们是等距的。生成这一点并非易事。

You need to generate an array of points along the path, and I assume you want them to be equally spaced. Generating this points isn't trivial.

幸运的是,Core Graphics包含了一个可以为你完成的功能,但是哪一个并不明显。该函数是 CGPathCreateCopyByDashingPath

Fortunately, Core Graphics contains a function that will do it for you, but it's not obvious which one. The function is CGPathCreateCopyByDashingPath.

首先,让我们做一个 UIBezierPath 创建虚线副本的类别:

First, let's make a UIBezierPath category that creates a dashed copy:

#import <UIKit/UIKit.h>

@interface UIBezierPath (Rob_dash)

- (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase;

@end



UIBezierPath + Rob_dash.m



UIBezierPath+Rob_dash.m

#import "UIBezierPath+Rob_dash.h"

@implementation UIBezierPath (Rob_dash)

- (instancetype)Rob_dashedPathWithPattern:(NSArray *)pattern phase:(CGFloat)phase {
    CGFloat lengths[pattern.count];
    size_t i = 0;
    for (NSNumber *number in pattern) {
        lengths[i++] = number.doubleValue;
    }
    CGPathRef dashedCGPath = CGPathCreateCopyByDashingPath(self.CGPath, NULL, phase, lengths, pattern.count);
    UIBezierPath *dashedPath = [self.class bezierPathWithCGPath:dashedCGPath];
    CGPathRelease(dashedCGPath);
    return dashedPath;
}

@end

一旦我们有一个虚线路径,我们需要枚举路径的元素(单个命令,如 moveToPoint: addLineToPoint:,依此类推)。唯一的方法是使用另一个核心图形函数 CGPathApply 。让我们编写另一个 UIBezierPath 类别,它使用块来简化它。这个有点长:

Once we have a dashed path, we need to enumerate the elements of the path (the individual commands like moveToPoint:, addLineToPoint:, and so on). The only way to do that is using another Core Graphics function CGPathApply. Let's write another UIBezierPath category that uses blocks to make it easier. This one's a bit longer:

#import <UIKit/UIKit.h>

typedef void (^Rob_UIBezierPath_moveBlock)(CGPoint destination);
typedef void (^Rob_UIBezierPath_lineBlock)(CGPoint destination);
typedef void (^Rob_UIBezierPath_quadBlock)(CGPoint control, CGPoint destination);
typedef void (^Rob_UIBezierPath_cubicBlock)(CGPoint control0, CGPoint control1, CGPoint destination);
typedef void (^Rob_UIBezierPath_closeBlock)(void);

@interface UIBezierPath (Rob_forEach)

- (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock;

@end



UIBezierPath + Rob_forEach.m



UIBezierPath+Rob_forEach.m

#import "UIBezierPath+Rob_forEach.h"

struct ForEachBlocks {
    __unsafe_unretained Rob_UIBezierPath_moveBlock moveBlock;
    __unsafe_unretained Rob_UIBezierPath_lineBlock lineBlock;
    __unsafe_unretained Rob_UIBezierPath_quadBlock quadBlock;
    __unsafe_unretained Rob_UIBezierPath_cubicBlock cubicBlock;
    __unsafe_unretained Rob_UIBezierPath_closeBlock closeBlock;
};

static void applyBlockToPathElement(void *info, const CGPathElement *element) {
    struct ForEachBlocks *blocks = info;
    switch (element->type) {
        case kCGPathElementMoveToPoint:
            if (blocks->moveBlock != nil) {
                blocks->moveBlock(element->points[0]);
            }
            break;
        case kCGPathElementAddLineToPoint:
            if (blocks->lineBlock != nil) {
                blocks->lineBlock(element->points[0]);
            }
            break;
        case kCGPathElementAddQuadCurveToPoint:
            if (blocks->quadBlock) {
                blocks->quadBlock(element->points[0], element->points[1]);
            }
            break;
        case kCGPathElementAddCurveToPoint:
            if (blocks->cubicBlock) {
                blocks->cubicBlock(element->points[0], element->points[1], element->points[2]);
            }
            break;
        case kCGPathElementCloseSubpath:
            if (blocks->closeBlock) {
                blocks->closeBlock();
            }
            break;
    }
}

@implementation UIBezierPath (Rob_forEach)

- (void)Rob_forEachMove:(Rob_UIBezierPath_moveBlock)moveBlock line:(Rob_UIBezierPath_lineBlock)lineBlock quad:(Rob_UIBezierPath_quadBlock)quadBlock cubic:(Rob_UIBezierPath_cubicBlock)cubicBlock close:(Rob_UIBezierPath_closeBlock)closeBlock {
    struct ForEachBlocks blocks = {
        .moveBlock = moveBlock,
        .lineBlock = lineBlock,
        .quadBlock = quadBlock,
        .cubicBlock = cubicBlock,
        .closeBlock = closeBlock
    };
    CGPathApply(self.CGPath, &blocks, applyBlockToPathElement);
}

@end

好的,现在我们想要将这两个类别一起使用以划分路径,然后沿着破折号行走并在每个破折号的末尾发出点。请注意,破折号可能包含多个连续的线/曲线段。我们需要注意移动命令才能知道破折号何时结束。另外,要以正确的角度绘制每个箭头,我们需要知道每个点处的曲线的切线,因此我们也将计算它作为单位矢量。在直线段的情况下,切线矢量平行于线段。在曲线的情况下,紧接曲线终点之前的控制点确定端点处的切线。

OK, now we want to use these two categories together to dash the path, then walk along the dashes and emit the point at the end of each dash. Note that a dash might consist of multiple contiguous line/curve segments. We need to watch for move commands to know when a dash ends. Also, to draw each arrow at the correct angle, we need to know the tangent of the curve at each point, so we'll compute that also, as a unit vector. In the case of a straight line segment, the tangent vector is parallel to the line segment. In the case of curves, the control point immediately prior to the endpoint of the curve determines the tangent at the endpoint.

#import <UIKit/UIKit.h>

@interface UIBezierPath (Rob_points)

- (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint point, CGVector vector))block;

@end



UIBezierPath + Rob_points.m



UIBezierPath+Rob_points.m

#import "UIBezierPath+Rob_points.h"
#import "UIBezierPath+Rob_dash.h"
#import "UIBezierPath+Rob_forEach.h"
#import <tgmath.h>

static CGVector vectorFromPointToPoint(CGPoint tail, CGPoint head) {
    CGFloat length = hypot(head.x - tail.x, head.y - tail.y);
    return CGVectorMake((head.x - tail.x) / length, (head.y - tail.y) / length);
}

@implementation UIBezierPath (Rob_points)

- (void)Rob_forEachPointAtInterval:(CGFloat)interval perform:(void (^)(CGPoint, CGVector))block {
    UIBezierPath *dashedPath = [self Rob_dashedPathWithPattern:@[ @(interval * 0.5), @(interval * 0.5) ] phase:0];
    __block BOOL hasPendingSegment = NO;
    __block CGPoint pendingControlPoint;
    __block CGPoint pendingPoint;
    [dashedPath Rob_forEachMove:^(CGPoint destination) {
        if (hasPendingSegment) {
            block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint));
            hasPendingSegment = NO;
        }
        pendingPoint = destination;
    } line:^(CGPoint destination) {
        pendingControlPoint = pendingPoint;
        pendingPoint = destination;
        hasPendingSegment = YES;
    } quad:^(CGPoint control, CGPoint destination) {
        pendingControlPoint = control;
        pendingPoint = destination;
        hasPendingSegment = YES;
    } cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) {
        pendingControlPoint = control1;
        pendingPoint = destination;
        hasPendingSegment = YES;
    } close:nil];
    if (hasPendingSegment) {
        block(pendingPoint, vectorFromPointToPoint(pendingControlPoint, pendingPoint));
    }
}

@end

现在我们可以在路径上找到点,并在每个点找到单位切线向量。让我们在 drawRect中创建一个使用此功能的自定义视图:

Now we can find points along a path, and the unit tangent vector at each point. Let's make a custom view that uses this ability in drawRect::

#import <UIKit/UIKit.h>

@interface ArrowView : UIView

@property (nonatomic) CGFloat interval;

@end



ArrowView.m



ArrowView.m

#import "ArrowView.h"
#import "UIBezierPath+Rob_figureEight.h"
#import "UIBezierPath+Rob_points.h"

@implementation ArrowView

- (void)setInterval:(CGFloat)interval {
    _interval = interval;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    UIImage *arrow = [UIImage imageNamed:@"right233.png"];
    UIBezierPath *path = [UIBezierPath Rob_figureEightInRect:CGRectInset(self.bounds, 40, 40)];
//    [path stroke];
    [path Rob_forEachPointAtInterval:self.interval perform:^(CGPoint point, CGVector vector) {
        CGContextRef gc = UIGraphicsGetCurrentContext();
        CGContextSaveGState(gc); {
            CGContextTranslateCTM(gc, point.x, point.y);
            CGContextConcatCTM(gc, CGAffineTransformMake(vector.dx, vector.dy, -vector.dy, vector.dx, 0, 0));
            CGContextTranslateCTM(gc, -0.5 * arrow.size.width, -0.5 * arrow.size.height);
//            UIRectFrame((CGRect){ CGPointZero, arrow.size });
            [arrow drawAtPoint:CGPointZero];
        } CGContextRestoreGState(gc);
    }];
}

@end

这就是它的全部内容,如果你想沿路径绘制箭头图像。

That's all there is to it, if you want to draw arrow images along a path.

我的演示应用程序库中有一点奖励。如果你回到第一次提交,我也实现了一个不同的解决方案:一个采用路径并箭头化它的类别,在每个子路径的末尾放置一个箭头。如果你将它与破折号结合起来(正如我在该项目版本中所做的那样),你会沿着路径获得箭头。但它最终看起来并不像使用箭头图像那么好。

There's a little bonus in my demo app repository. If you go back to the first commit, I implemented a different solution also: a category that takes a path and "arrowizes" it, putting an arrowhead at the end of each subpath. If you combine that with dashing (as I did in that version of the project), you get arrows along the path. But it ended up not looking as nice as using arrow images.

这篇关于沿着贝塞尔曲线路径放置图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆