如何曲线化CGMutablePath?

时间:2014-09-10 23:42:30

标签: ios cocoa-touch graphics

具有以下形状:

enter image description here

我想知道如何让它像这样弯曲:

enter image description here

同样地:

enter image description here

我假设所有的圆/线被打包成一个CGMutablePath,然后应用某种曲线,弧线或四边形曲线,虽然我遇到了麻烦甚至接近复制它。有谁知道怎么做?

1 个答案:

答案 0 :(得分:43)

在第一个示例中,您将从具有多个已关闭子路径的路径开始。显然,您想要扭曲子路径的中心,但是将各个子路径相对于它们的(新)中心保持未扭曲。我将忽略这一点,因为即使没有它,解决方案已经非常复杂了。

所以,让我们考虑如何定义“warp字段”。我们将使用三个控制点:

example with control points

warp使fixedPoint保持不变。它通过旋转缩放将startPoint移动到endPoint,只需插入坐标。

此外,它根据距固定点的距离应用旋转和缩放。而不只是基于简单的欧几里德距离。请注意,我们不希望对图片中“V”形的顶端点应用任何旋转或缩放,即使这些端点距离fixedPoint是可测量的欧几里德距离。我们想测量沿固定点 - > startPoint矢量的距离,并随着距离的增加应用更多的旋转/缩放。

这一切都需要一些相当重的三角学。我不会试图解释细节。我只是将代码转储给你,作为UIBezierPath上的一个类别:

UIBezierPath + Rob_warp.h

#import <UIKit/UIKit.h>

@interface UIBezierPath (Rob_warp)

- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint;

@end

UIBezierPath + Rob_warp.m

请注意,您需要this answer中的Rob_forEach类别。

#import "UIBezierPath+Rob_warp.h"
#import "UIBezierPath+Rob_forEach.h"
#import <tgmath.h>

static CGPoint minus(CGPoint a, CGPoint b) {
    return CGPointMake(a.x - b.x, a.y - b.y);
}

static CGFloat length(CGPoint vector) {
    return hypot(vector.x, vector.y);
}

static CGFloat dotProduct(CGPoint a, CGPoint b) {
    return a.x * b.x + a.y * b.y;
}

static CGFloat crossProductMagnitude(CGPoint a, CGPoint b) {
    return a.x * b.y - a.y * b.x;
}

@implementation UIBezierPath (Rob_warp)

- (UIBezierPath *)Rob_warpedWithFixedPoint:(CGPoint)fixedPoint startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint {

    CGPoint startVector = minus(startPoint, fixedPoint);
    CGFloat startLength = length(startVector);
    CGPoint endVector = minus(endPoint, fixedPoint);
    CGFloat endLength = length(minus(endPoint, fixedPoint));
    CGFloat scale = endLength / startLength;

    CGFloat dx = dotProduct(startVector, endVector);
    CGFloat dy = crossProductMagnitude(startVector, endVector);
    CGFloat radians = atan2(dy, dx);

    CGPoint (^warp)(CGPoint) = ^(CGPoint input){
        CGAffineTransform t = CGAffineTransformMakeTranslation(-fixedPoint.x, -fixedPoint.y);
        CGPoint inputVector = minus(input, fixedPoint);
        CGFloat factor = dotProduct(inputVector, startVector) / (startLength * startLength);
        CGAffineTransform w = CGAffineTransformMakeRotation(radians * factor);
        t = CGAffineTransformConcat(t, w);
        CGFloat factoredScale = pow(scale, factor);
        t = CGAffineTransformConcat(t, CGAffineTransformMakeScale(factoredScale, factoredScale));
        // Note: next line is not the same as CGAffineTransformTranslate!
        t = CGAffineTransformConcat(t, CGAffineTransformMakeTranslation(fixedPoint.x, fixedPoint.y));
        return CGPointApplyAffineTransform(input, t);
    };

    UIBezierPath *copy = [self.class bezierPath];
    [self Rob_forEachMove:^(CGPoint destination) {
        [copy moveToPoint:warp(destination)];
    } line:^(CGPoint destination) {
        [copy addLineToPoint:warp(destination)];
    } quad:^(CGPoint control, CGPoint destination) {
        [copy addQuadCurveToPoint:warp(destination) controlPoint:warp(control)];
    } cubic:^(CGPoint control0, CGPoint control1, CGPoint destination) {
        [copy addCurveToPoint:warp(destination) controlPoint1:warp(control0) controlPoint2:warp(control1)];
    } close:^{
        [copy closePath];
    }];
    return copy;
}

@end

好的,你怎么用这个疯狂的东西?对于示例中类似“V”的路径,您可以这样做:

CGRect rect = path.bounds;
CGPoint fixedPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint startPoint = CGPointMake(fixedPoint.x, CGRectGetMaxY(rect));
path = [path Rob_warpedWithFixedPoint:fixedPoint startPoint:startPoint endPoint:endAnchor];

我将fixedPoint计算为路径边界框顶边的中心,startPoint作为底边的中心。我的测试程序中endAnchor受用户控制。它在模拟器中看起来像这样:

demo with V

气泡型路径如下所示:

demo with bubbles

您可以在此处找到我的测试项目:https://github.com/mayoff/path-warp