Cocoa環境で幅1pxの一本の線を描くために

Cocoa環境で一本の水平ラインを描こうと考えて、最初に思い付いたのはベジェ曲線NSBezierPathを利用する方法だった。しかし、ベジェ曲線NSBezierPathの特性をちゃんと理解できていないため、幅1pxの線を引くことに苦労してしまった。以下は一本の線を引く過程で自分が理解したことのメモ。

setDefaultLineWidth:1を指定しても2pxの線が描画される場合がある

- (void)drawRect:(NSRect)rect {
	NSRect testRect = NSMakeRect(50,50,100,100);
	[[NSColor greenColor] set];

	[NSBezierPath setDefaultLineWidth:1];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(50,120) toPoint:NSMakePoint(150,120)];

	[NSBezierPath setDefaultLineWidth:2];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(50,100) toPoint:NSMakePoint(150,100)];

	[[NSColor brownColor] set];
        NSFrameRect(testRect);
}

NSViewのサブクラスを定義して、drawRectメソッドに上記コードを書いて実行してみると、以下のような結果になる。

  • setDefaultLineWidth:1とsetDefaultLineWidth:2が同じ2pxの幅になっている。(周囲の茶色の枠線が幅1px)
  • setDefaultLineWidth:1よりsetDefaultLineWidth:2の色が若干濃い。(ちょっと分かり難いかもしれないが)

ベジェ曲線NSBezierPathとNSFrameRectの違い

ライン幅20の四角形をそれぞれの方法で描いてみた。

ベジェ曲線NSBezierPathで描画
- (void)drawRect:(NSRect)rect {
	NSRect testRect = NSMakeRect(50,50,100,100);

	[[NSColor whiteColor] set];
	[NSBezierPath setDefaultLineWidth:20];
	[NSBezierPath strokeRect:testRect];

	[[NSColor brownColor] set];
        NSFrameRect(testRect);
}


NSFrameRectで描画
- (void)drawRect:(NSRect)rect {
	NSRect testRect = NSMakeRect(50,50,100,100);

	[[NSColor whiteColor] set];
	NSFrameRectWithWidth(testRect, 20);

	[[NSColor brownColor] set];
        NSFrameRect(testRect);
}


  • ベジェ曲線NSBezierPathは、指定した四角形のラインを中心に、両側に指定した幅の1/2(10px)ずつの領域を確保して、20px幅の四角形を描画している。
  • 一方、NSFrameRectは、指定した四角形の範囲に収まるような20px幅の四角形を描画している。

ベジェ曲線NSBezierPathが描画するときの気持ち

以上のことから考えて、1pxの水平ラインをNSBezierPathで描画しようとすると、以下のような現象が発生していると考えられる。

  • 非常に単純な以下のスクリーンを想像して...
(y軸)
2┌┬┬┬┐
1├┼┼┼┤
0└┴┴┴┘
 0 1 2 3 4(x軸)
[NSBezierPath setDefaultLineWidth:1];
[NSBezierPath strokeLineFromPoint:NSMakePoint(0,1) toPoint:NSMakePoint(4,1)];
  • 数学的に見て「y = 1」の水平ラインを中心に、両側に0.5px領域を確保して、幅1pxのラインを描画しようと試みる。
  • MacBookの画面上で、0.5px分だけ画素を表示することは不可能なので、アンチエイリアス処理によって表示色を薄めて、擬似的に表現しようとする。
  • その結果、2px幅のラインが、色が薄くなって表示されていたのだ...。

幅1pxの線を描く方法

  • NSFrameRectを使う。
  • NSBezierPathを利用する場合は、中心となる線を0.5pxずらして指定する。
- (void)drawRect:(NSRect)rect {
	NSRect testRect = NSMakeRect(50,50,100,100);

	[[NSColor greenColor] set];

	NSFrameRect(NSMakeRect(50,120,100,1));

	[NSBezierPath setDefaultLineWidth:1];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(50,100.5) toPoint:NSMakePoint(150,100.5)];

	[[NSColor brownColor] set];
        NSFrameRect(testRect);
}


一本の線を引くのも奥が深い...。思い返せば、初めての書道で書いたのも漢数字の「一」だったような気がする。(筆の入りと止めを理解する第一歩)最もシンプルな一本の水平ラインの描き方を理解することはとても重要なのであった。