天天看點

iOS開發之Quartz2D

1、         quartz2d概述及作用

quartz2d的api是純c語言的,quartz2d的api來自于core graphics架構。

資料類型和函數基本都以cg作為字首,比如:

cgcontextref

cgpathref

cgcontextstrokepath(ctx);

……

quartz 2d是一個二維繪圖引擎,同時支援ios和mac系統。

quartz 2d能完成的工作:

繪制圖形 : 線條\三角形\矩形\圓\弧等;

繪制文字;

繪制\生成圖檔(圖像);

讀取\生成pdf;

截圖\裁剪圖檔;

自定義ui控件;

… …

2、quartz2d在ios開發中的價值

為了便于搭建美觀的ui界面,ios提供了uikit架構,裡面有各種各樣的ui控件,比如:

uilabel:顯示文字;

uiimageview:顯示圖檔;

uibutton:同時顯示圖檔和文字(能點選);

利用uikit架構提供的控件,拼拼湊湊,能搭建和現實一些簡單、常見的ui界面。

但是,有些ui界面極其複雜、而且比較個性化,用普通的ui控件無法實作,這時可以利用quartz2d技術将控件内部的結構畫出來,自定義控件的樣子。其實,ios中大部分控件的内容都是通過quartz2d畫出來的。是以,quartz2d在ios開發中很重要的一個價值是:自定義view(自定義ui控件)。

3、圖形上下文

圖形上下文(graphics context):是一個cgcontextref類型的資料。

圖形上下文的作用:

(1)儲存繪圖資訊、繪圖狀态

(2)決定繪制的輸出目标(繪制到什麼地方去?)

(輸出目标可以是pdf檔案、bitmap或者顯示器的視窗上)

相同的一套繪圖序列,指定不同的graphics context,就可将相同的圖像繪制到不同的目标上。

quartz2d提供了以下幾種類型的graphics context:

(1)bitmap graphics context

(2)pdf graphics context

(3)window graphics context

(4)layer graphics context

(5)printer graphics context

4、自定義view

如何利用quartz2d自定義view?(自定義ui控件)如何利用quartz2d繪制東西到view上?

首先,得有圖形上下文,因為它能儲存繪圖資訊,并且決定着繪制到什麼地方去。

其次,那個圖形上下文必須跟view相關聯,才能将内容繪制到view上面。

自定義view的步驟:

(1)建立一個類,繼承自uiview

(2)實作- (void)drawrect:(cgrect)rect方法,然後在這個方法中

(a)取得跟目前view相關聯的圖形上下文

例如:cgcontextref ctx = uigraphicsgetcurrentcontext();

(a) 繪制相應的圖形内容

  例如:畫1/4圓

    cgcontextmovetopoint(ctx, 100, 100);

    cgcontextaddlinetopoint(ctx, 100, 150);

    cgcontextaddarc(ctx, 100, 100, 50, -m_pi_2, m_pi, 1);

    cgcontextclosepath(ctx);

    [[uicolor redcolor] set];

(b)利用圖形上下文将繪制的所有内容渲染顯示到view上面

  例如:cgcontextfillpath(ctx);

5、drawrect

為什麼要實作drawrect:方法才能繪圖到view上?

因為在drawrect:方法中才能取得跟view相關聯的圖形上下文。

drawrect:方法在什麼時候被調用?

(1)當view第一次顯示到螢幕上時(被加到uiwindow上顯示出來)。

(2)調用view的setneedsdisplay或者setneedsdisplayinrect:時。

setneedsdisplay常被調用來重新整理view界面。

繪圖順序:

iOS開發之Quartz2D

3、         drawrect:中取得的上下文

在drawrect:方法中取得上下文後,就可以繪制東西到view上。view内部有個layer(圖層)屬性,drawrect:方法中取得的是一個layer graphics context,是以,繪制的東西其實是繪制到view的layer上去了。view之是以能顯示東西,完全是因為它内部的layer。

4、         quartz2d繪圖的代碼步驟

第一步:獲得圖形上下文:

cgcontextref ctx = uigraphicsgetcurrentcontext();

第二步:拼接路徑(下面代碼是搞一條線段):

cgcontextmovetopoint(ctx, 10, 10);

cgcontextaddlinetopoint(ctx, 100, 100);

第三步:繪制路徑:

cgcontextstrokepath(ctx); // cgcontextfillpath(ctx);

7、常用拼接路徑函數

建立一個起點

void cgcontextmovetopoint(cgcontextref c, cgfloat x, cgfloat y)

添加新的線段到某個點

void cgcontextaddlinetopoint(cgcontextref c, cgfloat x, cgfloat y)

添加一個矩形

void cgcontextaddrect(cgcontextref c, cgrect rect)

添加一個橢圓

void cgcontextaddellipseinrect(cgcontextref context, cgrect rect)

添加一個圓弧

void cgcontextaddarc(cgcontextref c, cgfloat x, cgfloat y,

  cgfloat radius, cgfloat startangle, cgfloat endangle, int clockwise)

8、常用繪制路徑函數

mode參數決定繪制的模式

void cgcontextdrawpath(cgcontextref c, cgpathdrawingmode mode)

繪制空心路徑

void cgcontextstrokepath(cgcontextref c)       

繪制實心路徑

void cgcontextfillpath(cgcontextref c)

提示:一般以cgcontextdraw、cgcontextstroke、cgcontextfill開頭的函數,都是用來繪制路徑的

其他常用函數:

設定線段寬度:

    cgcontextsetlinewidth(ctx, 10);

設定線段頭尾部的樣式:

    cgcontextsetlinecap(ctx, kcglinecapround);

設定線段轉折點的樣式:

    cgcontextsetlinejoin(ctx, kcglinejoinround);

設定顔色:

cgcontextsetrgbstrokecolor(ctx, 1, 0, 0, 1);

9、圖形上下文棧的操作

将目前的上下文copy一份,儲存到棧頂(那個棧叫做”圖形上下文棧”):

void cgcontextsavegstate(cgcontextref c)

将棧頂的上下文出棧,替換掉目前的上下文(清空之前對于上下文設定):

void cgcontextrestoregstate(cgcontextref c)

10、矩陣操作

利用矩陣操作,能讓繪制到上下文中的所有路徑一起發生變化:

縮放:

void cgcontextscalectm(cgcontextref c, cgfloat sx, cgfloat sy)

旋轉:

void cgcontextrotatectm(cgcontextref c, cgfloat angle)

平移:

void cgcontexttranslatectm(cgcontextref c, cgfloat tx, cgfloat ty)

11、quartz2d的記憶體管理

關于記憶體管理,有以下原則:

(1)使用含有“create”或“copy”的函數建立的對象,使用完後必須釋放,否則将導緻記憶體洩露。

(2)使用不含有“create”或“copy”的函數擷取的對象,則不需要釋放

(3)如果retain了一個對象,不再使用時,需要将其release掉。

(4)可以使用quartz 2d的函數來指定retain和release一個對象。例如,如果建立了一個cgcolorspace對象,則使用函數cgcolorspaceretain和cgcolorspacerelease來retain和release對象。

(5)也可以使用core foundation的cfretain和cfrelease。注意不能傳遞null值給這些函數。

11、    圖檔水印

有時候,在手機用戶端app中也需要用到水印技術,比如,使用者拍完照片後,可以在照片上打個水印,辨別這個圖檔是屬于哪個使用者的

實作方式:

利用quartz2d,将水印(文字、logo)畫到圖檔的右下角

核心代碼:

開啟一個基于位圖的圖形上下文:

void  uigraphicsbeginimagecontextwithoptions(cgsize size, bool opaque, cgfloat scale)

從上下文中取得圖檔(uiimage):

uiimage* uigraphicsgetimagefromcurrentimagecontext();

結束基于位圖的圖形上下文:

void     uigraphicsendimagecontext();

例如:

uiimage *bgimage = [uiimage imagenamed:@"scene"];

    // 上下文 : 基于位圖(bitmap) ,  所有的東西需要繪制到一張新的圖檔上去

    // 1.建立一個基于位圖的上下文(開啟一個基于位圖的上下文)

    // size : 新圖檔的尺寸

    // opaque : yes : 不透明,  no : 透明

    // 這行代碼過後.就相當于常見一張新的bitmap,也就是新的uiimage對象

    uigraphicsbeginimagecontextwithoptions(bgimage.size, no, 0.0);

// 2.畫背景

//特别注意的是使用oc自帶的畫圖方法不用傳上下文,系統會自動檢測并傳入上下文

    [bgimage drawinrect:cgrectmake(0, 0, bgimage.size.width, bgimage.size.height)];

    // 3.畫右下角的水印

    uiimage *waterimage = [uiimage imagenamed:@"logo"];

    cgfloat scale = 0.2;

    cgfloat margin = 5;

    cgfloat waterw = waterimage.size.width * scale;

    cgfloat waterh = waterimage.size.height * scale;

    cgfloat waterx = bgimage.size.width - waterw - margin;

    cgfloat watery = bgimage.size.height - waterh - margin;

    [waterimage drawinrect:cgrectmake(waterx, watery, waterw, waterh)];

    // 4.從上下文中取得制作完畢的uiimage對象

uiimage *newimage =

uigraphicsgetimagefromcurrentimagecontext();

    // 5.結束上下文

    uigraphicsendimagecontext();

    // 6.顯示到uiimageview

    self.iconview.image = newimage;

    // 7.将image對象壓縮為png格式的二進制資料

    nsdata *data = uiimagepngrepresentation(newimage);

    //uiimagejpegrepresentation(<#uiimage *image#>, <#cgfloat compressionquality#>)

    // 8.寫入檔案

nsstring *path =

[[nssearchpathfordirectoriesindomains(nsdocumentdirectory, nsuserdomainmask, yes) lastobject]

stringbyappendingpathcomponent: @"new.png"];

   [data writetofile:path atomically:yes];

13、圖檔裁剪

有時候我們需要把一張普通的圖檔刻意裁剪成圓形,比如把使用者頭像做成圓形的,如下圖:

iOS開發之Quartz2D

核心代碼:

void cgcontextclip(cgcontextref c)

将目前上下所繪制的路徑裁剪出來(超出這個裁剪區域的都不能顯示)

示例代碼1(效果如上圖):

    cgcontextref ctx = uigraphicsgetcurrentcontext();   

    // 畫圓

    cgcontextaddellipseinrect(ctx, cgrectmake(100, 100, 50, 50));

    // 裁剪

    cgcontextclip(ctx);

    cgcontextfillpath(ctx);

    // 顯示圖檔

    uiimage *image = [uiimage imagenamed:@"me"];

[image drawatpoint:cgpointmake(100, 100)];

示例代碼2(産生圖像圓截圖,效果和上面圖檔效果一樣但在圖像周圍加個圓邊框,并儲存到本地):

// 1.加載原圖

    uiimage *oldimage = [uiimage imagenamed:@"me"];

    // 2.開啟上下文

    cgfloat borderw = 2; // 圓環的寬度

    cgfloat imagew = oldimage.size.width + 2 * borderw;

    cgfloat imageh = oldimage.size.height + 2 * borderw;

    cgsize imagesize = cgsizemake(imagew, imageh);

    uigraphicsbeginimagecontextwithoptions(imagesize, no, 0.0);

    // 3.取得目前的上下文

    cgcontextref ctx = uigraphicsgetcurrentcontext();

    // 4.畫邊框(大圓)

    [[uicolor whitecolor] set];

    cgfloat bigradius = imagew * 0.5; // 大圓半徑

    cgfloat centerx = bigradius; // 圓心

    cgfloat centery = bigradius;

    cgcontextaddarc(ctx, centerx, centery, bigradius, 0, m_pi * 2, 0);

    cgcontextfillpath(ctx); // 畫圓

    // 5.小圓

    cgfloat smallradius = bigradius - borderw;

    cgcontextaddarc(ctx, centerx, centery, smallradius, 0, m_pi * 2, 0);

    // 裁剪(後面畫的東西才會受裁剪的影響)

    // 6.畫圖

    [oldimage drawinrect:cgrectmake(borderw, borderw, oldimage.size.width, oldimage.size.height)];

    // 7.取圖

    // 8.結束上下文

    // 9.顯示圖檔

    // 10.寫出檔案

[[nssearchpathfordirectoriesindomains(nsdocumentdirectory,

nsuserdomainmask, yes) lastobject]

stringbyappendingpathcomponent:@"new.png"];

    [data writetofile:path atomically:yes];

14、螢幕截圖

有時候需要截取螢幕上的某一塊内容,比如捕魚達人遊戲:

iOS開發之Quartz2D

- (void)renderincontext:(cgcontextref)ctx;

調用某個view的layer的renderincontext:方法即可

示例代碼:

// 1.開啟上下文

    uigraphicsbeginimagecontextwithoptions(view.frame.size, no, 0.0);

    // 2.将控制器view的layer渲染到上下文

    [view.layer renderincontext:uigraphicsgetcurrentcontext()];

    // 3.取出圖檔

// 4.寫檔案

nsdata *data = uiimagepngrepresentation(newimage);

[data writetofile:path atomically:yes];

uigraphicsendimagecontext();

15、oc中自帶畫圖方法

// 1.獲得目前的觸摸點

    uitouch *touch = [touches anyobject];

    cgpoint startpos = [touch locationinview:touch.view];

    // 2.建立一個新的路徑

    uibezierpath *currenpath = [uibezierpath bezierpath];//也可用alloc init方法建立

    currenpath.linecapstyle = kcglinecapround;

    currenpath.linejoinstyle = kcglinejoinround;

    // 3.設定起點

[currenpath movetopoint:startpos];

//4.連線

[currentpath addlinetopoint:endpos];

判斷一個點是否在一個矩形框内:

cgrectcontainspoint(rect,point);//判斷point這個點是否在rect這個矩形框内

繼續閱讀