2013-02-17 40 views
1

我想创建一个表示“弯曲”或“臃肿”方形像这样的NSBezierPath:NSBezierPath:创建一个“弯曲”方

enter image description here

我挣扎拿出正确的数学得到这个形状是完全正确的。我已经浏览了整个互联网,但谷歌搜索这个主题主要是“这里是如何绘制圆角”,这不是我所需要的。

任何人都可以指向我可以用来放置这些曲线控制点的公式吗?谢谢!

+1

你试过跟踪该图像中的矢量图形程序?例如,[PaintCode](http://www.paintcodeapp.com)或[Opacity](http://likethought.com/opacity/)可让您绘制形状,然后导出为Objective-C代码。即使你不这样做,只要玩一个贝塞尔路径工具,应该给你一些关于控制点如何工作的直觉。 – 2013-02-17 06:23:28

+0

是的,试试* PaintCode *! – Jay 2013-02-17 06:46:46

+0

谢谢你们;我听说过这些应用程序,但从来没有尝试过。他们肯定是昂贵的! – Bryan 2013-02-17 10:02:47

回答

4

好的;经过多次试验和错误,我得到了一些有效的方法。下面的代码将绘制出一个像这样的形状。 (该图像使用以下值的#define语句中的代码):

#define SIDE_DEFLECTION 18.0f 
#define CORNER_RAD 18.0f 
#define KAPPA 0.55238f 
#define CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION .10 
#define SIDE_CENTER_CP_OFFSET 0.60 

这些值,给定500×500一个象素图,将绘制该形状:

enter image description here

绘图代码

在我的情况下,我在NSButtonCell子类中绘制这个形状。因为我知道这个单元格的框架永远不会在我的应用中调整大小,所以我可以做一些优化。具体来说,我将NSBezierPath存储为iVar,所以我不必每次都通过-drawImage重新创建它...此外,我将NSShadow和NSColor存储为iVar,因此不必重新创建它们。

如果您要在调整大小的视图中绘制此形状,您需要稍微调整一下代码。如果方形的尺寸急剧变化,那么现在需要手动调整#define语句的值(否则,拐角看起来并不准确,侧弯曲线过渡到圆角曲线等几乎没有缩进)

下面的代码被配置为在52x52像素的正方形中绘制相同的形状。一般的方法是设置你想要的“侧偏转”和“角半径”,然后调整两个“偏移”定义语句(这是百分比),直到角落看起来很完美。 “kappa”的价值永远不会改变---一个来自我发现的数学论文。

这里是最优化的绘图代码:

#import "LPProjectIconButtonCell.h" 


#define SIDE_DEFLECTION 1.0f 
#define CORNER_RAD 4.0f 

// Distance (%) control points should be from curve start/end to form perfectly circular rounded corners 
#define KAPPA 0.55238f 

// Percentage offset (from perfectly circular rounded corner location) that the corner control points use to 
// compensate for the fact that our sides are rounded. Without this, we get a rough transition between the 
// curve of the side and the start of the corner curve 
#define CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION .85 

// As the curve approaches each side-center point, this is the percentage of the distance between the side endpoints 
// and the side centerpoint where the control point for approaching the centerpoint is located. You are not expected 
// to understand the preceeding sentence. 
#define SIDE_CENTER_CP_OFFSET 0.60 



@implementation LPProjectIconButtonCell 

- (void) awakeFromNib 
{ 
    _shadow = [[NSShadow alloc] init]; 
    [_shadow setShadowBlurRadius:1.0f]; 
    [_shadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0f alpha:0.2f]]; 
    [_shadow setShadowOffset:NSMakeSize(0.0f, -1.0f)]; 

    _borderColor = [[NSColor colorWithCalibratedWhite:0.13 alpha:1.0f] retain]; 
} 

- (void) dealloc 
{ 
    [_path release]; 
    _path = nil; 

    [_shadow release]; 
    _shadow = nil; 

    [_borderColor release]; 
    _borderColor = nil; 

    [super dealloc]; 
} 


- (void) drawImage:(NSImage *)image withFrame:(NSRect)frame inView:(NSView *)controlView 
{ 
    NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; 

    // The path never changes because this view never resizes. So we'll save it to be efficient 
    if (!_path) 
    { 
     NSRect rect = NSInsetRect(frame, 2.0f, 2.0f); 

     // Create the primary points -- 3 per side 
     NSPoint TCenter = NSMakePoint(rect.size.width/2.0f, rect.origin.y); 
     NSPoint TLeft = NSMakePoint(rect.origin.x + CORNER_RAD + SIDE_DEFLECTION, rect.origin.y + SIDE_DEFLECTION); 
     NSPoint TRight = NSMakePoint(rect.origin.x + rect.size.width - (CORNER_RAD + SIDE_DEFLECTION), rect.origin.y + SIDE_DEFLECTION); 

     NSPoint LTop = NSMakePoint(rect.origin.x + SIDE_DEFLECTION, rect.origin.y + CORNER_RAD + SIDE_DEFLECTION); 
     NSPoint LCenter = NSMakePoint(rect.origin.x, rect.size.height/2.0f); 
     NSPoint LBottom = NSMakePoint(rect.origin.x + SIDE_DEFLECTION, rect.origin.y + rect.size.height - CORNER_RAD - SIDE_DEFLECTION); 

     NSPoint BLeft = NSMakePoint(TLeft.x, rect.origin.y + rect.size.height - SIDE_DEFLECTION); 
     NSPoint BCenter = NSMakePoint(TCenter.x, rect.origin.y + rect.size.height); 
     NSPoint BRight = NSMakePoint(TRight.x, BLeft.y); 

     NSPoint RTop = NSMakePoint(rect.origin.x + rect.size.width - SIDE_DEFLECTION, LTop.y); 
     NSPoint RCenter = NSMakePoint(rect.origin.x + rect.size.width, LCenter.y); 
     NSPoint RBottom = NSMakePoint(RTop.x, LBottom.y); 


     // Create corner control points for rounded corners 
     // We don't want them to be perfectly circular, because our sides are curved. So we adjust them slightly to compensate for that. 
     NSPoint CP_TLeft = NSMakePoint(TLeft.x - (TLeft.x - LTop.x) * KAPPA, TLeft.y + SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION); 
     NSPoint CP_LTop = NSMakePoint(LTop.x + SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION, LTop.y - (LTop.y - TLeft.y) * KAPPA); 

     NSPoint CP_LBottom = NSMakePoint(LBottom.x + SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION, LBottom.y + (BLeft.y - LBottom.y) * KAPPA); 
     NSPoint CP_BLeft = NSMakePoint(BLeft.x - (BLeft.x - LBottom.x) * KAPPA, BLeft.y - SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION); 

     NSPoint CP_BRight = NSMakePoint(BRight.x + (RBottom.x - BRight.x) * KAPPA, BRight.y - SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION); 
     NSPoint CP_RBottom = NSMakePoint(RBottom.x - SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION, RBottom.y + (BRight.y - RBottom.y) * KAPPA); 

     NSPoint CP_RTop = NSMakePoint(RTop.x - SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION, RTop.y - (RTop.y - TRight.y) * KAPPA); 
     NSPoint CP_TRight = NSMakePoint(TRight.x + (RTop.x - TRight.x) * KAPPA, TRight.y + SIDE_DEFLECTION * CORNER_CP_OFFSET_FOR_SIDE_DEFLECTION); 


     // Create control points for the rounded sides. (The "duplicate" control points are here in case I ever tweak this in the future.) 
     NSPoint CP_DepartingTCenterForTLeft = NSMakePoint(TCenter.x - (TCenter.x - TLeft.x) * SIDE_CENTER_CP_OFFSET, TCenter.y); 
     NSPoint CP_ApproachingTLeft = TLeft; 

     NSPoint CP_DepartingLTopForLCenter = LTop; 
     NSPoint CP_ApproachingLCenterFromLTop = NSMakePoint(LCenter.x, LCenter.y - (LCenter.y - LTop.y) * SIDE_CENTER_CP_OFFSET); 

     NSPoint CP_DepartingLCenterForLBottom = NSMakePoint(LCenter.x, LCenter.y + (LBottom.y - LCenter.y) * SIDE_CENTER_CP_OFFSET); 
     NSPoint CP_ApproachingLBottom = LBottom; 

     NSPoint CP_DepartingBLeftForBCenter = BLeft; 
     NSPoint CP_ApproachingBCenter = NSMakePoint(BCenter.x - (BCenter.x - BLeft.x) * SIDE_CENTER_CP_OFFSET, BCenter.y); 

     NSPoint CP_DepartingBCenterForBRight = NSMakePoint(BCenter.x + (BRight.x - BCenter.x) * SIDE_CENTER_CP_OFFSET, BCenter.y); 
     NSPoint CP_ApproachingBRight = BRight; 

     NSPoint CP_DepartingRBottomForRCenter = RBottom; 
     NSPoint CP_ApproachingRCenterFromRBottom = NSMakePoint(RCenter.x, RCenter.y + (RBottom.y - RCenter.y) * SIDE_CENTER_CP_OFFSET); 

     NSPoint CP_DepartingRCenterForRTop = NSMakePoint(RCenter.x, RCenter.y - (RCenter.y - RTop.y) * SIDE_CENTER_CP_OFFSET); 
     NSPoint CP_ApproachingRTopFromRCenter = RTop; 

     NSPoint CP_DepartingTRightForTCenter = TRight; 
     NSPoint CP_ApproachingTCenterFromTRight = NSMakePoint(TCenter.x + (TRight.x - TCenter.x) * SIDE_CENTER_CP_OFFSET, TCenter.y); 


     // Draw the bloody square 
     NSBezierPath *p = [[NSBezierPath alloc] init]; 

     [p moveToPoint:TCenter]; 
     [p curveToPoint:TLeft controlPoint1:CP_DepartingTCenterForTLeft controlPoint2:CP_ApproachingTLeft]; 
     [p curveToPoint:LTop controlPoint1:CP_TLeft controlPoint2:CP_LTop]; 
     [p curveToPoint:LCenter controlPoint1:CP_DepartingLTopForLCenter controlPoint2:CP_ApproachingLCenterFromLTop]; 
     [p curveToPoint:LBottom controlPoint1:CP_DepartingLCenterForLBottom controlPoint2:CP_ApproachingLBottom]; 
     [p curveToPoint:BLeft controlPoint1:CP_LBottom controlPoint2:CP_BLeft]; 
     [p curveToPoint:BCenter controlPoint1:CP_DepartingBLeftForBCenter controlPoint2:CP_ApproachingBCenter]; 
     [p curveToPoint:BRight controlPoint1:CP_DepartingBCenterForBRight controlPoint2:CP_ApproachingBRight]; 
     [p curveToPoint:RBottom controlPoint1:CP_BRight controlPoint2:CP_RBottom]; 
     [p curveToPoint:RCenter controlPoint1:CP_DepartingRBottomForRCenter controlPoint2:CP_ApproachingRCenterFromRBottom]; 
     [p curveToPoint:RTop controlPoint1:CP_DepartingRCenterForRTop controlPoint2:CP_ApproachingRTopFromRCenter]; 
     [p curveToPoint:TRight controlPoint1:CP_RTop controlPoint2:CP_TRight]; 
     [p curveToPoint:TCenter controlPoint1:CP_DepartingTRightForTCenter controlPoint2:CP_ApproachingTCenterFromTRight]; 

     [p closePath]; 

     _path = p; 
    } 

    // We want a slightly white drop shadow on the stroke and fill, giving our square some sense of depth. 
    [_shadow set]; 

    [[NSColor blackColor] set]; 
    [_path fill]; 


    // Clip to the bezier path and draw a fill image inside of it. 
    [currentContext saveGraphicsState]; 

     [_path addClip]; 
     [image drawInRect:frame fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0f respectFlipped:YES hints:nil]; 

     if (self.isHighlighted) 
     { 
      // If we're clicked, draw a 50% black overlay to show that 
      NSColor *overColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.5]; 
      [overColor set]; 
      [_path fill]; 
     } 

    [currentContext restoreGraphicsState]; 


    // Stroke the square to create a nice border with a drop shadow at top and bottom. 
    [_borderColor set]; 
    [_path stroke]; 
} 

@end