iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
今回で、いままでのまとめだ。自信がないが、自分が理解したことを整理してみる。
OS X/iOSの描画システム、Core Graphics (Quartz) フレームワークのデフォルト座標系は、左下が原点で、X軸は右方向、Y軸は上方向が正だ。これをApple Developerサイトの文書『iOS描画および印刷ガイド』内では、LLO (lower-left-origin)と呼んでいる。
iOSのUIKitフレームワークとCore Animationフレームワークは、変換行列(CTM : Current Transformation Matrix)を使ってデフォルト座標系は、左上が原点で、X軸は右方向、Y軸は下方向が正だ。これをApple Developerサイトの文書『iOS描画および印刷ガイド』内では、ULO (upper-left-origin)と呼んでいる。
おそらく、UIKitでは、以下の感じでdrawRect:を呼んでいると考えられる。
UIGraphicsBeginImageContextWithOptions
CGContextRef context = UIGraphicsGetCurrentContext();
[インスタンス drawRect:rect];
UIGraphicsEndImageContext
そして、おそらく、UIGraphicsBeginImageContextWithOptionsでは、以下の感じで、座標系を原点左上 (ULO) に設定していると考えられる。
CGContextTranslateCTM(graphicsContext, 0.0, rect.size.height);
CGContextScaleCTM(graphicsContext, 1.0, -1.0);
UIViewのサブクラスのdrawRect:で描画すれば、他のシステムで慣れた原点左上で悩まなくて済む!と考えると運が悪いと痛い目にあってしまう。
実験してみよう。
上記の画像を、drawRect:内でCore Graphics関数を使って描画してみる。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIImage *image = [UIImage imageNamed:@"upper-left-origin.png"];
CGRect imageRect;
imageRect.origin = CGPointMake(10.0, 10.0);
imageRect.size = image.size;
CGContextDrawImage(context, imageRect, image.CGImage);
}
描画される位置は、左上からの座標だが、画像が反転している!
これをどう考えるかだが、自分はこう理解した。
座標系がどのように変換されるのかは、意識されない。上記の例では、例えば、以下の動作をする。
その為、X軸の下方向が正の座標系だと、上下が逆さまの図が描画される。
こういう訳で、Core Graphicsの関数で、文字列や画像、PDFのような座標の向きを意識する必要がある描画をおこなう場合は、座標系をCoreGraphicsのデフォルト座標系に戻してやる必要がある。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGFloat height = self.bounds.size.height;
CGContextTranslateCTM(context, 0.0, height);
CGContextScaleCTM(context, 1.0, - 1.0);
CGContextRestoreGState(context);
}
ただし、上記の様に、どの位置に描画するのかを指定する場合は、それを考慮しないと、例では画面の上部でなく下部に表示される。
その為、例では描画位置を座標系の違いを考慮して計算してあげる必要がある。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGFloat height = self.bounds.size.height;
CGContextTranslateCTM(context, 0.0, height);
CGContextScaleCTM(context, 1.0, - 1.0);
UIImage *image = [UIImage imageNamed:@"upper-left-origin.png"];
CGRect imageRect;
imageRect.origin = CGPointMake(10.0, self.bounds.size.height - 10.0 - image.size.height);
imageRect.size = image.size;
CGContextDrawImage(context, imageRect, image.CGImage);
CGContextRestoreGState(context);
}
この話、先日のCocoa勉強会で発表したのだが、チームで開発する場合は、どういうポリシーとするのかを決めておかないと、座標系の変換が混ざってしまって、混乱したソースコードになるという指摘を受けた。
例えば、外部インターフェースで使用する座標系は原点左上にして、左下に戻す等は、モジュール内部に隠蔽するとか、面倒なので、全てを左下原点にするとかだ。