次のような「湾曲した」または「肥大化した」正方形を表すNSBezierPathを作成したいと思います。
この形を正確に正しくするための正しい数学を考え出すのに苦労しています。私はインターネット全体を見てきましたが、このトピックをグーグルで検索すると、ほとんどの場合「丸みを帯びた角を描く方法」になりますが、これは私が必要としていることではありません。
これらの曲線の制御点を配置するために使用できる数式を誰かに教えてもらえますか?ありがとう!
次のような「湾曲した」または「肥大化した」正方形を表すNSBezierPathを作成したいと思います。
この形を正確に正しくするための正しい数学を考え出すのに苦労しています。私はインターネット全体を見てきましたが、このトピックをグーグルで検索すると、ほとんどの場合「丸みを帯びた角を描く方法」になりますが、これは私が必要としていることではありません。
これらの曲線の制御点を配置するために使用できる数式を誰かに教えてもらえますか?ありがとう!
わかった; 多くの試行錯誤の末、私はうまくいくものを手に入れました。以下のコードは、このような形状を描画します。(この画像は、コードの#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
500x500ピクセルのビューが与えられた場合、これらの値は次の形状を描画します。
私の場合、NSButtonCellサブクラス内にこの図形を描画しています。このセルのフレームがアプリでサイズ変更されないことがわかっているので、いくつかの最適化を行うことができます。具体的には、NSBezierPathをiVarとして保存するため、-drawImageを使用して毎回再作成する必要はありません...さらに、NSShadowとNSColorをiVarとして保存するため、これらも再作成する必要はありません。
サイズを変更するビューでこの形状を描画する場合は、コードを少し調整する必要があります。正方形のサイズが大幅に変更された場合は、#defineステートメントの値を今すぐ手動で調整する必要があります(そうしないと、コーナーがきれいに見えません。サイドカーブが丸みを帯びたコーナーカーブに移行する場所など、くぼみがほとんどありません)。
以下のコードは、52x52ピクセルの正方形に同じ形状を描画するように構成されています。一般的なアプローチは、必要な「側面のたわみ」と「コーナー半径」を設定してから、コーナーが完全に見えるまで2つの「オフセット」定義ステートメント(パーセンテージ)を調整することです。「カッパ」の値は決して変わらないはずです---それは私が見つけた数学の論文から取られています。
最適化された描画コードは次のとおりです。
#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