2013年4月29日月曜日

Quartz 2D (App Kit) コードを Core Animation に対応させる


話題から逸れますが,最近筆者がはまったのでメモも兼ねて掲載します.

御存知の通り,OS X の2DグラフィックスAPIはC言語APIである Core Graphics があり,その上に Objective-C API である App Kit があります.Apple はこの両者をもともと Quartz 2D と呼んでいましたが,徐々に Quartz 2D という呼び方はしなくなってきました.また iOS にはそもそも2DグラフィックスAPIとして Core Graphics しか用意されていないこともあり,Quartz 2D の影はどんどん薄くなってきています.

OS X においても,従来の App Kit を使った描画コードにそのまま Core Animation を被せることはできません.しかしながら,従来の App Kit を使った描画コードは Core Animation の1レイヤーとして共存させることは可能です.

そのやり方を解説しておきましょう.

App Kit を使った描画の場合,常套手段として NSView のサブクラスで drawRect: をオーバーライドしますね?このメソッドの中で NSBezierPath クラスなどの stroke メソッドを呼んだりします.例えばこんな感じでしょう.

MyView.h

@interface MyView: NSView {
  …
}

@end

MyView.m

@implementation MyView {
  …
}

- (void)drawRect: (NSRect)rect {
  [[NSColor whiteColor] set];
  NSRectFill(rect);

  NSBezierPath *path = …;
  ...
  [path stroke];
}

イベントハンドラでは NSView クラスの setNeedsDisplay: メソッドを使って drawRect: を間接的に呼び出します.例えばこんな感じです.

- (void)mouseDown: (NSEvent *)event {
  NSPoint locationInView = [self convertPoint: event.locationInWindow fromView: nil];
  ...
  [self setNeedsDisplay: YES];
}

一方 Core Animation を使っている場合 drawRect: は呼んでもらえません.その代わり,レイヤは自身のドローイング時に drawLayer: inContext: を呼び出します.このメソッドはデリゲートがあればデリゲートのものが使われます.

まずは Core Animation を使った場合の基本パタンからおさらいしましょう.

MyView.m

@implementation MyView {
  CALayer *backgroundLayer;
}

- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self) {
    backgroundLayer = [CALayer layer];
    CGColorRef white = CGColorCreateGenericGray(1.0f, 1.0f);
    backgroundLayer.backgroundColor = white;
    CGColorRelease(white);
    [self setLayer: backgroundLayer];
    [self setWantsLayer: YES];
  }
  return self;
}

こんな感じで Core Animation レイヤを初期化します.

まずは MyView クラスに drawLayer: inContext: メソッドを実装しておきましょう.このメソッドは Core Graphics グラフィックスコンテキストを渡してもらえますので,これを使って App Kit のグラフィックスコンテキストとします.

MyView.m

- (void)drawLayer: (CALayer *)layer inContext: (CGContextRef)context {
  NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext graphicsContextWithGraphicsPort: context flipped: NO];
  [NSGraphicsContext saveGraphicsState];
  [NSGraphicsContext setCurrentContext: nsGraphicsContext];

  // ここに App Kit 時代のコードを書く
  NSBezierPath *path = …;
  …
  [path stroke];

  [NSGraphicsContext restoreGraphicsState];
}

次に,このメソッドが backgroundLayer の描画時に呼ばれないといけません.そこで initWithFrame: に以下のようにコードを追加します.

MyView.m

- (id)initWithFrame: (NSRect)frame {
  self = [super initWithFrame: frame];
  if (self) {
    backgroundLayer = [CALayer layer];
    CGColorRef white = CGColorCreateGenericGray(1.0f, 1.0f);
    backgroundLayer.backgroundColor = white;
    CGColorRelease(white);
    backgroundLayer.delegate = self;
    [self setLayer: backgroundLayer];
    [self setWantsLayer: YES];
  }
  return self;
}

イベントハンドラは次のように書き換えます.

- (void)mouseDown: (NSEvent *)event {
  NSPoint locationInView = [self convertPoint: event.locationInWindow fromView: nil];
  ...
  [backgroundLayer setNeedsDisplay];
}

これで,App Kit 時代のコードが  Core Animation レイヤとして使えるようになりました.

Apple は QuickDraw, QuickTime に続き Quartz 2D も終了させようとしているのかもしれません.でも,Core Animation, Core Image そしてもちろん Core Video は Quartz Core というフレームワークに収められています.しばらく Quartz の名前は残りそうですね.

0 件のコメント:

コメントを投稿