2011年4月19日

iOS SDK 認識 Quartz 2D (0)

Hi everyone,今天要來介紹iOS SDK的Quartz 2D,Quartz 2D可以用來繪製向量圖像、播放圖片、合成圖片以及操作字型甚至試產生pdf文件,功能可以說是非常的強大,在遊戲製作上也是熱門的建構方式;關於更詳細的介紹可以參考 Apple 提供的 Quartz 2D 的 Guide,這篇文章會講述到如何繪製一個矩形,並且讓矩形移動。
我會將閱讀文章的人視作對 c / obj-c 以及 iOS有一定的認識,因此在obj-c/c的一些使用內容將不會有太多的解釋。另外會以AS3為對照的語言,作一些相關的引用跟對照。

Quartz 2D是關連 UIView 這個類別的內部資料結構,主要是在一個 graphics context (圖形內文)上做繪製;graphics context 我們可以將他視作為 Canvas ,只要有使用到 Quartz 2D API 都會需要使用到他;有關Quartz 2D的API呼叫都會帶有一個CG開頭的字首,使用的時候可以注意一下。

使用 Quartz 2D 需要遵循下面的演算程序:
  • Step 1:取得得目前的圖像內文,並將其保存起來
  • Step 2:取得目前轉換矩陣,將矩陣倒置後存到圖像內文中
  • Step 3:開始繪製矩形
  • Step 4:恢復保存的內文
接下來我們先建立一個專案Sample1,類型為 View-base Application 。接著建立一個新的Class,命名為Quartz2DView;接著將 Quartz2DView.m 的 drawRect:(CGRect) rect 改成下面的內容:
-(void) drawRect:(CGRect) rect{
    //(step 1)
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextSaveGState(context); 
    //(step 2)
    CGAffineTransform t0 = CGContextGetCTM( context );
    t0 = CGAffineTransformInvert(t0);
    CGContextConcatCTM(context, t0);
    //(step 3)
    CGContextBeginPath(context);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);// CGContextSetRGBFillColor(CGContextRef ,r,g,h,alpha)
    CGContextAddRect( context , CGRectMake( 0, 0,100, 100));//CGRectMake(CGFloat x,CGFloat y,CGFloat width,CGFloat height)
    CGContextClosePath(context);
    CGContextDrawPath(context,kCGPathFill);
    //(step 4)
    CGContextRestoreGState(context);
}
  • Step1,我先取得當前UI的圖像內文,這時候我們就取得的可以用來畫圖的 Canvas (畫布),並且將context保存下來;CGContextSaveGState 保存的狀態 在這 有詳細的參考,大家請自行觀看就不細說。
  • Step2,取得目前的轉至矩陣,並將矩陣反轉之後,在存儲回context內(CTM=current transformation matrix),這個動作,會讓你的Quartz 2D的物件座標系轉換,後面會搭配圖解說。
  • Step3,進行繪製的動作,其中CGContextBeginPath是每次開始繪製前必須被呼叫的method,而結束可能會有CGContextClosePath或是CGContextClip來結束,並搭配CGContextDrawPath or CGContextDrawImage來繪製圖形。
  • Step4,最後透過CGContextRestoreGState來恢復內文。
接著將我們寫好的Quartz2DView加到畫面上,在專案的Sample1Controller.h加上程式碼:
#import "Quartz2DView.h"
@interface Sample1ViewController : UIViewController {
    Quartz2DView *quartzView;
}
Controller.m的loadView加上程式碼:
-(void) loadView
{
    [super loadView];
    quartzView = [[Quartz2DView alloc] initWithFrame:[self.view frame]];
    [self.view addSubview:quartzView];
}
執行後的畫面呈現如下:
依照上圖,你會注意到,我們繪製的方塊在畫面的左下角,以左下角為原點;這跟原本的以左上為原點的座標系不同,因為我們在Step2的地方,將原本的轉至矩陣改變了,除了改變座標外,還可以變形、選轉跟縮放;這個轉至矩陣,其實很像我們在寫AS3 DisplayObject.trasform的martix
這邊需要注意的是,整體的座標系並沒有改變,只有在Quartz2DView中 drawRect method中 被畫出來的矩形座標系被改變

到這邊我們已經認識到該如何繪製一個圖形,並且呈現在我們的畫面上;接下來,我希望我畫出來的矩形可以沿著y軸往上移動,所以我需要一個建立一個類似AS3 ENTER_FRAME的計時器並且希望他以30FPS的速度來更新畫面,在這邊我先使用NSTimer這個類別(有一個更適合開發遊戲的記時器類別 CADisplayLink 以60FPS的速度更新畫面),開始修改Quartz2DView相關的程式碼:
Quartz2DView.h:
@interface Quartz2DView : UIView{
    CGFloat x0;
    CGFloat y0;
    NSTimer *time;
}
-(void) dt:(NSTimer*) theTimer;
Quartz2DView.m 分別修改 initWithFrame、drawRect並且增加dt這個method:
-(id) initWithFrame:(CGRect) frame
{
    self = [super initWithFrame:frame];
    if(self)
    {
        x0 = y0 = 0;
        timer = [NSTimer scheduledTimerWithTimeInterval:1/30.0
                                                 target:self
                                               selector:@selector(dt:)
                                               userInfo:nil
                                                repeats:YES];
    }
}
-(void) dt:(NSTimer*) theTimer
{
    y+=1;
    [self setNeedsDisplay];
}
-(void) drawRect:(CGRect) rect{
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextSaveGState(context); 
    
    CGAffineTransform t0 = CGContextGetCTM( context );
    t0 = CGAffineTransformInvert(t0);
    CGContextConcatCTM(context, t0);
    
    CGContextBeginPath(context);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);// CGContextSetRGBFillColor(CGContextRef ,r,g,h,alpha)
    CGContextAddRect( context , CGRectMake( x, y,100, 100) );
    CGContextClosePath(context);
    CGContextDrawPath(context,kCGPathFill);
    
    CGContextRestoreGState(context);
}
在上面的程式碼中,當我們建立了Quartz2DView的實體的時候會將x,y指定為0,並且建立的一個NSTimer 讓他以30FPS來速度來呼叫dt這個method,在dt這個method中 [self setNeedsDisplay] 是告訴Quartz2DView再去執行(更新)drawRect這個method的動作;最後我們在修改drawRect中的 CGContextAddRect (highlight部分),將代表x,y的地方帶入我們宣告的參數x0,y0,這樣一來,當y0改變的時候,就可以達到讓矩形延著Y由移動的效果。

到此,這篇算是告一段落了;Quartz 2D算是被廣泛運用在照片處理跟遊戲製作上面(有空來研究cocos2d-iphone是不是也是用這個架成的framework),至於大家來找碴Lite,因為那時我還沒有接觸到Quartz 2D,所以是使用一堆UIImageView+UIView來製作;除了這兩種方式可以用來製作遊戲外,還有一個大家耳熟能詳的OpenGL ES,處理的效能又好了許多,希望未來可以介紹給大家。

下一篇希望介紹將可視物件(這次範例中的矩形)包成一個class,讓畫面上可以有多個圖像去動作。

最後希望這篇文章有幫到大家,有任何錯誤或者建議,也請大家不要吝嗇,謝謝。

參考網址:
iOS Developer Library
Quart2D Guide
AS3 Martix Class
範例:Sample1.zip

下一篇相關文章:iOS SDK 認識 Quartz 2D (1)

3 則留言:

  1. cocos2d是based on OpenGL做的,
    與Quart2D不同.

    回覆刪除
    回覆
    1. 喔,原來如此,謝謝你的解答<(_ _)>

      刪除
  2. Quartz2DView.m 分別修改 initWithFrame、drawRect並且增加dt這個method:

    下方 Code 的變數名稱 x, y, x0, y0 是不是有搞錯的地方?

    回覆刪除