Quartz 2D高級繪圖技巧

本文目錄

1.前言
2.Quartz 2D 概述
3.基于圖形上下文繪圖
4.基于路徑的繪圖
5.Transform(Translate、Rotate、Scale)
6.繪制陰影
7.透明度圖層
8.漸變色
9.重復繪圖
10.核心圖層繪圖
11.消除鋸齒渲染

前言

Quartz 2DCore GraphicsQuartzCore、Core Animation這幾個東西經常分不清,看到都是一臉懵逼!??!

  • Quartz 2D 就是 Core Graphics, 框架中包含的類都是以CG開頭, 是一個先進的二維繪圖引擎,所以被叫做Quartz 2D
  • QuartzCore、Core Animation也是同一個東西、框架中的類以CA開頭主要是對控件的layer層進行操作。比如layer的創建與layer的動畫

Quartz 2D

屏幕快照 2017-11-29 18.01.22.png

QuartzCore

屏幕快照 2017-11-29 18.03.05.png

至此在使用類名以CG開頭Quartz 2D之前必須明白Quartz 2D主要是做繪圖工作
而類名以CA開頭QuartzCore主要是對layer層的操作來實現一些動畫等

一、Quartz 2D概述

Quartz 2D需要了解的幾個知識點

1.CGContextRef
2.Opaque Data Types
3.Memory Management

1.CGContextRef 圖形上下文:

CGContextRef是一個很重要的東西,可以說Quartz 2D的繪圖功能都是要基于CGContextRef來完成。CGContextRef它是Quartz 2D使用繪制圖像的輸出裝置,所述信息諸如PDF文件,位圖,或者在顯示器上的窗口, Quartz 2D的所有對象都被繪制或包含在圖形上下文中,您可以將圖形上下文視為一個繪圖目標,沒有這個繪圖目標Quartz 2D就不能完成繪圖工作。

2.Opaque Data Types 其他常用的繪圖對象

Quartz 2DAPI中除CGContextRef外提供的用于繪圖的對象包含

  • CGPathRef用于矢量圖形來創建您填充或描邊的路徑
  • CGImageRef用于根據您提供的示例數據來表示位圖圖像和位圖圖像蒙版
  • CGLayerRef 用于表示可用于重復繪圖(例如用于背景或圖案)和用于離屏繪制的繪圖層。
  • CGPatternRef用于重復繪制
  • CGShadingRef、CGGradientRef用于繪制漸變
  • CGFunctionRef用于定義采用任意數量的浮點參數的回調函數,在為陰影創建漸變時使用此數據類型
  • CGFontRef用來繪制文字
  • CGPDFDictionaryRef ...它提供訪問PDF的元數據,PDF文檔創建,查看和轉換
3.Memory Management內存管理

Quartz使用Core Foundation內存管理模型,其中的對象是引用計數的。創建時,Core Foundation對象以引用計數1開始。可以通過調用保留該對象的函數來增加引用計數,并通過調用釋放該對象的函數來減少引用計數。當引用計數遞減到0時,該對象被釋放。
如果您CreatCopy對象,則您擁有該對象,因此您必須將其Release。也就是說,一般情況下,如果您從名稱中使用CreatCopy函數獲得對象,則必須在完成時Release該對象。否則,會導致內存泄漏。

二、基于圖形上下文基本圖形繪制

根據需求不同選擇不同的圖形上下文,以下是五種輸出:
1.用于界面顯示(Window),這是我們80%的需求
2.離屏繪圖(Layer),CGLayerRefCGBitmapContext好太多
3.位圖(Bitmap)
4.PDF
5.Printer

image.png

圖形上下文繪圖的API都在CGContext

框架中的上下文類.png

來看看我們常用的API

2.1.設置繪畫的一些屬性(線條屬性、繪畫透明度、疊加模式等)
 /* 設置繪制直線、邊框時的線條寬度*/
 void CGContextSetLineWidth(CGContextRef__nullable c, CGFloat width)
 
 /* 設置線段端點的繪制形狀,支持三個枚舉項*/
 void CGContextSetLineCap(CGContextRef__nullable c, CGLineCap cap)
 typedef CF_ENUM(int32_t, CGLineCap) {
     kCGLineCapButt, //該屬性值指定不繪制端點,線條結尾處直接結束。這是默認值。
     kCGLineCapRound,//該屬性值指定繪制圓形端點,線條結尾處繪制一個直徑為線條寬度的半圓。
     kCGLineCapSquare//該屬性值指定繪制方形端點。線條結尾處繪制半個邊長為線條寬度的正方形。需要說明的是,這種形狀的端點與“butt”形狀的端點十分相似,只是采用這種形式的端點的線條略長一點而已
 };

 /* 設置線條連接點的樣式,支持三個枚舉項 */
 void CGContextSetLineJoin(CGContextRef__nullable c, CGLineJoin join)
 typedef CF_ENUM(int32_t, CGLineJoin) {
     kCGLineJoinMiter, // 這是默認的屬性值。該方格的連接點形狀
     kCGLineJoinRound, // 稍微圓角, 該方格的連接點形狀
     kCGLineJoinBevel  // 斜角,該方格的連接點形狀
 };
 
 
 /*當把連接點風格設為meter風格時,該方法用于控制銳角箭頭的長度*/
 void CGContextSetMiterLimit(CGContextRef__nullable c, CGFloat limit)
 
 /*  Linedash pattern(虛線模式)允許我們沿著描邊繪制虛線。我們通過在CGContextSetLineDash結構體中指定虛線數組和虛線相位來控制虛線的大小及位置。
  其中lengths屬性指定了虛線段的長度,該值是在繪制片斷與未繪制片斷之間交替。phase屬性指定虛線模式的起始點。*/
 void CGContextSetLineDash(CGContextRef__nullable c, CGFloat phase,const CGFloat *__nullable lengths, size_t count)
 
 /* 設置彎曲的路徑中的圖形上下文的準確性。*/
 void CGContextSetFlatness(CGContextRef__nullable c, CGFloat flatness)
 
 /* 設置全局透明度 */
 void CGContextSetAlpha(CGContextRef__nullable c, CGFloat alpha)
 
 /*設置CGContextRef的疊加模式。Quartz 2D支持多種疊加模 */
void CGContextSetBlendMode(CGContextRef __nullable c, CGBlendMode mode)
2.2顏色填充
 /** Primitive color functions. **/
    
    /*
     使用指定顏色來設置該CGContextRef的填充顏色*/
    void CGContextSetFillColorWithColor(CGContextRef__nullable c,
                                        CGColorRef __nullable color)
    
    /*
     使用指定顏色來設置該CGContextRef的線條顏色*/
    void CGContextSetStrokeColorWithColor(CGContextRef__nullable c,
                                          CGColorRef __nullable color)
    
    /** Color space functions. **/
    
    /* 顏色空間填充 */
    void CGContextSetFillColorSpace(CGContextRef__nullable c, CGColorSpaceRef__nullable space)
    
    /* 設置線框顏色空間 */
    
    void CGContextSetStrokeColorSpace(CGContextRef__nullable c,
                                      CGColorSpaceRef __nullable space)
    
    /** Color functions. **/
    
    /* 設置填充顏色空間 CGFloat redColor[4] = {1.0,0,0,1.0};*/
    void CGContextSetFillColor(CGContextRef__nullable c,const CGFloat *__nullable components(redColor))
    
    /* 設置畫筆顏色 CGFloat redColor[4] = {1.0,0,0,1.0};*/
    void CGContextSetStrokeColor(CGContextRef__nullable c,const CGFloat *__nullable components(redColor))
    
    /** Pattern functions. **/
    
    /*
     設置該CGContextRef使用位圖填充*/
    void CGContextSetFillPattern(CGContextRef__nullable c, CGPatternRef__nullable pattern,const CGFloat * __nullable components)
    
    /*
     設置該CGContextRef使用位圖繪制線條、邊框*/
    void CGContextSetStrokePattern(CGContextRef__nullable c, CGPatternRef__nullable pattern,const CGFloat * __nullable components)
    
    /*
     設置該CGContextRef采用位圖填充的相位*/
    void CGContextSetPatternPhase(CGContextRef__nullable c, CGSize phase)
    
    /** Color convenience functions. **/
    
    /*
     使用灰色來設置該CGContextRef的填充顏色*/
    void CGContextSetGrayFillColor(CGContextRef__nullable c,
                                   CGFloat gray, CGFloat alpha)
    
    /*
     使用灰色來設置該CGContextRef的線條顏色*/
    void CGContextSetGrayStrokeColor(CGContextRef__nullable c,
                                     CGFloat gray, CGFloat alpha)
    
    /*
     使用RGB顏色模式來設置該CGContextRef的填充顏色*/
    void CGContextSetRGBFillColor(CGContextRef__nullable c, CGFloat red,
                                  CGFloat green, CGFloat blue, CGFloat alpha)
    
    /* 設置畫筆顏色 */
    void CGContextSetRGBStrokeColor(CGContextRef__nullable c,
                                    CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
    
    /*
     使用CMYK顏色模式來設置該CGContextRef的填充顏色*/
    void CGContextSetCMYKFillColor(CGContextRef__nullable c,
                                   CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
    
    /*
     使用CMYK顏色模式來設置該CGContextRef的線條顏色*/
    void CGContextSetCMYKStrokeColor(CGContextRef__nullable c,
                                     CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
    
2.3 圖型API(直線、曲線、圓、圓弧)
/* 開始創建路徑. */
 void CGContextBeginPath(CGContextRef__nullable c)
 
 /* 開始一個新的子路徑點 */
 void CGContextMoveToPoint(CGContextRef__nullable c,
                           CGFloat x, CGFloat y)
 
 /* 添加一條直線段從當前指向的(x,y)。 */
 void CGContextAddLineToPoint(CGContextRef__nullable c,
                              CGFloat x, CGFloat y)
 
 /**
  *  從當前添加一個三次Bezier曲線
  *  @param cp1x 控制點1 x坐標
  *  @param cp1y 控制點1 y坐標
  *  @param cp2x 控制點2 x坐標
  *  @param cp2y 控制點2 y坐標
  *  @param x    直線的終點 x坐標
  *  @param y    直線的終點 y坐標
  */
 void CGContextAddCurveToPoint(CGContextRef__nullable c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
 
 /**
  *  從當前添加一個二次Bezier曲線
  *  @param cpx 控制點 x坐標
  *  @param cpy 控制點 y坐標
  *  @param x   直線的終點 x坐標
  *  @param y   直線的終點 y坐標
  */
 void CGContextAddQuadCurveToPoint(CGContextRef__nullable c, CGFloat cpx, CGFloat cpy,CGFloat x,CGFloat y)
 
 /* 關閉當前上下文的子路徑,且當前點和起點連接起來 */
 void CGContextClosePath(CGContextRef__nullable c)
 
 /** Path construction convenience functions. **/
 
 /* 添加一個矩形路徑 */
 void CGContextAddRect(CGContextRef__nullable c, CGRect rect)
 
 /* 添加多個矩形路徑 */
 void CGContextAddRects(CGContextRef__nullable c,
                        const CGRect * __nullable rects, size_t count)
 
 /* 添加多條直線路徑*/
 void CGContextAddLines(CGContextRef__nullable c,
                        const CGPoint * __nullable points, size_t count)
 
 /* 根據一個矩形,繪制橢圓(圓形 */
 void CGContextAddEllipseInRect(CGContextRef__nullable c, CGRect rect)
 
 /**
  *  添加弧形對象
  *  @param x          中心點x坐標
  *  @param y          中心點y坐標
  *  @param radius     半徑
  *  @param startAngle 起始弧度
  *  @param endAngle   終止弧度
  *  @param clockwise  是否逆時針繪制,0則順時針繪制
  */
 void CGContextAddArc(CGContextRef__nullable c, CGFloat x,CGFloat y,CGFloat radius,CGFloat startAngle,CGFloat endAngle,int clockwise)
 
 
 /* 這個函數使用一個序列的三次貝塞爾曲線創建一個弧
  原理:首先畫兩條線,這兩條線分別是 current point to (x1,y1)和(x1,y1) to (x2,y2).這樣就是出現一個以(x1,y1)為頂點的兩條射線,然后定義半徑長度,這個半徑是垂直于兩條射線的,這樣就能決定一個圓了,如果當前點和第一個切點的弧(起點)是不平等的,那么會添加一條直線段從當前指向第一個切點?;〉慕K點成為新的當前點的路徑。*/
 
 void CGContextAddArcToPoint(CGContextRef__nullable c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)
 
 /*添加路徑到圖形上下文 */
 
 void CGContextAddPath(CGContextRef__nullable c, CGPathRef__nullable path)
2.4 繪制到上下文中(Fill、Stroke、EOFill、Clear)
/** Path drawing functions. **/
 
 typedef CF_ENUM (int32_t, CGPathDrawingMode) {
     kCGPathFill,//只有填充(非零纏繞數填充),不繪制邊框  如圖1
     kCGPathEOFill,//奇偶規則填充(多條路徑交叉時,奇數交叉填充,偶交叉不填充)如圖2
     kCGPathStroke,        // 只有邊框  如圖3
     kCGPathFillStroke,    // 既有邊框又有填充  如圖4
     kCGPathEOFillStroke   // 奇偶填充并繪制邊框  如圖5
 };
 /*
  使用指定模式繪制當前CGContextRef中所包含的路徑。CGPathDrawingMode 屬性如上*/
 
 void CGContextDrawPath(CGContextRef__nullable c, CGPathDrawingMode mode)
 
 /** Path drawing convenience functions. **/
 
 /*
  填充該路徑包圍的區域*/
 void CGContextFillPath(CGContextRef__nullable c)
 
 /*
  使用奇偶規則來填充該路徑包圍的區域。奇偶規則指:如果某個點被路徑包圍了奇數次,系統繪制該點;如果被路徑包圍了偶數次,系統不繪制*/
 void CGContextEOFillPath(CGContextRef__nullable c)
 
 /*
  使用當前 CGContextRef設置的線寬繪制路徑*/
 void CGContextStrokePath(CGContextRef__nullable c)
 
 /*
  填充rect代表的矩形*/
 void CGContextFillRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  填充多個矩形
  */
 void CGContextFillRects(CGContextRef__nullable c,
                         const CGRect * __nullable rects, size_t count)
 
 /*
  使用當前 CGContextRef設置的線寬繪制矩形框*/
 void CGContextStrokeRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  使用指定線寬繪制矩形框*/
 void CGContextStrokeRectWithWidth(CGContextRef__nullable c,
                                   CGRect rect, CGFloat width)
 
 /*
  擦除指定矩形區域上繪制的圖形*/
 void CGContextClearRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  填充rect矩形的內切橢圓區域*/
 void CGContextFillEllipseInRect(CGContextRef__nullable c,
                                 CGRect rect)
 
 /*
  使用當前 CGContextRef設置的線寬繪制rect矩形的內切橢圓*/
 void CGContextStrokeEllipseInRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  CGContextBeginPath(context);
  for (k = 0; k < count; k += 2) {
  CGContextMoveToPoint(context, s[k].x, s[k].y);
  CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
  }
  CGContextStrokePath(context);
  使用當前 CGContextRef設置的線寬繪制多條線段。該方法需要傳入2N個CGPoint組成的數組,其中1、2個點組成第一條線段,3、4個點組成第2條線段,以此類推*/
 void CGContextStrokeLineSegments(CGContextRef__nullable c,
                                  const CGPoint * __nullable points, size_t count)
 

示例:繪制帶虛線的橢圓

Quartz 2D的繪圖都是需要圖形上下文、以下是基于界面顯示的繪圖上下文獲取

1.UIView及子類上下文獲取
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
}
2.CALayer 上下文獲取
- (void)drawInContext:(CGContextRef)ctx{}
效果:
image.png
代碼:
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextAddArc(context, self.center.x, self.center.y, 80, M_PI, 2*M_PI, NO);
    CGFloat lengths[2] = {2,5};
    CGContextSetLineDash(context, 0, lengths, 2);
    CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
    CGContextSetLineWidth(context, 10);
    CGContextStrokePath(context);
}

三、基于路徑的繪圖

為什么要使用路徑繪畫,CGContextRefAPI已經滿足了我們的繪畫需求?
如過我們不想輕易的丟掉或者重復繪制相同的路徑,那么我們就應該使用路徑繪畫,在畫相同的路徑時可以再畫圖,就是使用CGPathRef保存一個路徑。

路徑CGPathRefCoreGraphics框架中的位置

image.png

CGPathRef常用的API

3.1 創建空的路徑或者拷貝路徑
/* 創建一個可變的空Path */    
CGMutablePathRef  CGPathCreateMutable(void)

/* Copy后得到一個不可變Path */    
CGPathRef __nullable CGPathCreateCopy(CGPathRef cg_nullable path) 

/* Copy后得到一個可變Path */
CGMutablePathRef __nullable CGPathCreateMutableCopy(CGPathRef cg_nullable path)

/* Copy一個Path帶形變參數 */
CGPathRef __nullable CGPathCreateCopyByTransformingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform)
 
/* Copy一個Path帶形變參數 */
CGMutablePathRef __nullable CGPathCreateMutableCopyByTransformingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform)
3.2創建帶非空(圓、矩形、?。┞窂?/h5>
// 創建一個矩形的路徑
CGPathRef  CGPathCreateWithRect(CGRect rect,const CGAffineTransform * __nullable transform)
  
// 創建一個橢圓路徑
CGPathRef  CGPathCreateWithEllipseInRect(CGRect rect, const CGAffineTransform * __nullable transform)
   
// 創建一個圓路徑
CGPathRef  CGPathCreateWithRoundedRect(CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight, const CGAffineTransform * __nullable transform)
3.3 往路徑中添加(線、圓、矩形、?。┖虲ontext中添加圖形一樣
// 移動到某點
void CGPathMoveToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
    
// 畫一條直線
void CGPathAddLineToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
   
// 畫一個圓 
void CGPathAddRoundedRect(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight)
    
// 畫一個二次Bezier曲線
void CGPathAddQuadCurveToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform *__nullable m, CGFloat cpx, CGFloat cpy,
                                             CGFloat x, CGFloat y)
// 畫一個三次Bezier曲線  
void CGPathAddCurveToPoint(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
    
// 換一個矩形
void CGPathAddRect(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGRect rect)
    
// 畫多個矩形
void CGPathAddRects(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, const CGRect * __nullable rects, size_t count)

// 畫多條線
void CGPathAddLines(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, const CGPoint * __nullable points,  size_t count)
    
// 畫一個橢圓
void CGPathAddEllipseInRect(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGRect rect)
  
// 畫弧    
void CGPathAddArc(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, bool clockwise)
    
// 三次貝塞爾曲線創建一個弧
void CGPathAddArcToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)

3.4 Path相關屬性設置
// 設置虛線
CGPathRef __nullable CGPathCreateCopyByDashingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGFloat phase, const CGFloat * __nullable lengths, size_t count)
    
// 設置路徑線寬、路徑頭尾樣式、路徑交接處樣式、連接處銳角箭頭的長度
CGPathRef __nullable CGPathCreateCopyByStrokingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGFloat lineWidth, CGLineCap lineCap,CGLineJoin lineJoin, CGFloat miterLimit)

/* Line join styles. */
typedef CF_ENUM(int32_t, CGLineJoin) {
    kCGLineJoinMiter,
    kCGLineJoinRound,
    kCGLineJoinBevel
};
    
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
    kCGLineCapButt,
    kCGLineCapRound,
    kCGLineCapSquare
};
3.5 Path內存管理
CGPathRef cg_nullable CGPathRetain(CGPathRef cg_nullable path)
 
void CGPathRelease(CGPathRef cg_nullable path)
 
void CGPathCloseSubpath(CGMutablePathRef cg_nullable path)
3.6 添加一個Path到另外一個Path上
// 添加Path2到Path1上
void CGPathAddPath(CGMutablePathRef cg_nullable path1, const CGAffineTransform * __nullable m, CGPathRef cg_nullable path2)

示例:

效果
image.png

代碼中path2,path3都是對path1的重用,這就體現了使用path的優勢,不用在繪制相同的路徑

Code:
CGContextRef context = UIGraphicsGetCurrentContext();
    // 創建路徑1,2,3
    // path1
    CGMutablePathRef path1 = CGPathCreateMutable();
    CGPoint points[3] = {
        CGPointMake(100, 100),
        CGPointMake(70, 130),
        CGPointMake(130, 130)
    };
    CGPathAddLines(path1, nil, points, 3);
    
    // path2
    CGAffineTransform transform[1] = {
        CGAffineTransformTranslate(CGAffineTransformIdentity, 30, 30)
    };
    CGPathRef path2 = CGPathCreateMutableCopyByTransformingPath(path1, transform);
    
    // path3
    CGAffineTransform transform2[1] = {
        CGAffineTransformTranslate(CGAffineTransformIdentity, -30, 30)
    };
    CGPathRef path3 = CGPathCreateMutableCopyByTransformingPath(path1, transform2);
    
    // 填充路徑
    CGContextAddPath(context, path1);
    CGContextSetRGBFillColor(context, 1, 1, 0, 1);
    CGContextFillPath(context);
    
    CGContextAddPath(context, path2);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextFillPath(context);
    
    CGContextAddPath(context, path3);
    CGContextSetRGBFillColor(context, 1, 0, 1, 1);
    CGContextFillPath(context);
    
    // 釋放路徑
    CGPathRelease(path1);
    CGPathRelease(path2);
    CGPathRelease(path3);

四、Transform(Translate、Rotate、Scale)

形變,UIView及子類可以直接使用,這個在開發中經常使用,就不再描述了。

五、繪制陰影

都封裝咋了CALayer中,開發中也經常使用,不再描述。

六、透明度圖層

先用兩個圖例來說明什么透明度圖層的作用

圖1:非透明度圖層.png
圖2:透明度圖層.png

透明度層由被組合以產生復合圖形的兩個或更多的物體,生成的復合體被視為單個對象。當您想要將效果應用于一組對象時,透明度圖層非常有用,例如應用于圖2中三個圓圈的陰影。

繪畫到透明圖層需要三個步驟:

1.調用函數CGContextBeginTransparencyLayer
2.在透明圖層中繪制要合成的項目
3.調用函數CGContextEndTransparencyLayer

代碼:
 CGContextRef context = UIGraphicsGetCurrentContext();
 // 陰影代碼不能寫在步驟2中
 CGSize shadowOffset = CGSizeMake (-20, -5);
 CGContextSetShadow (context, shadowOffset, 10);
 
 // 步驟1
 CGContextBeginTransparencyLayer (context, NULL);
 
 // 步驟2
 CGContextSetRGBFillColor (context, 0, 0.5, 1, 1);
 CGContextFillEllipseInRect(context, CGRectMake(100, 100, 150, 150));
 CGContextSetRGBFillColor (context, 0, 1, 0.5, 1);
 CGContextFillEllipseInRect(context, CGRectMake(40, 160, 150, 150));
 CGContextSetRGBFillColor (context, 1, 1, 0, 1);
 CGContextFillEllipseInRect(context, CGRectMake(160, 160, 150, 150));
 
 // 步驟3
 CGContextEndTransparencyLayer (context);
效果:
image.png

七、漸變色

漸變分為兩種漸變方式:
1.線性漸變:通過兩個點組成的這條線,來做線性漸變
2.徑向漸變:通過第一個點及半徑和第一個點及半徑組成的兩個圓來做徑向漸變

先來看看CGGradientRef如何實現漸變

7.1 CGGradientRef線性漸變

步驟:
1.獲取上下文
2.獲取漸變對象
3.繪制漸變

API:

1.獲取上下文

CGContextRef context = UIGraphicsGetCurrentContext();

2.獲取漸變對象

/**
 獲取漸變對象

 @param space 設置顏色空間,iOS中使用 CGColorSpaceCreateDeviceRGB()獲取顏色空間
 @param components 顏色組成,CGFloat的數組,一組顏色包含rgba 4個值,如果設置3組顏色那么CGFloat components[12]定義12長度
 @param locations 每組顏色的位置,定義長度有顏色組成決定,3組顏色 CGFloat locations[3]定義長度3
 @param count 顏色組成的長度,比如3
 */
CGGradientRef CGGradientCreateWithColorComponents(CGColorSpaceRef cg_nullable space,
                                                    const CGFloat * cg_nullable components,
                                                    const CGFloat * __nullable locations,
                                                    size_t count);

3.繪制線性漸變API

/**
 繪制線性漸變

 @param c 上下文 : UIGraphicsGetCurrentContext()獲取
 @param gradient 漸變對象:CGGradientCreateWithColorComponents(...)API獲取
 @param startPoint 漸變開始點 :CGPoint
 @param endPoint 漸變結束點 :CGPoint
 @param options 繪制的方式 枚舉:CGGradientDrawingOptions 
 */
CGContextDrawLinearGradient(CGContextRef  _Nullable c,
                                CGGradientRef  _Nullable gradient,
                                CGPoint startPoint, CGPoint endPoint,
                                CGGradientDrawingOptions options)

4.繪制方式

/**
 繪制方式

 @param kCGGradientDrawsBeforeStartLocation 在開始點之前繪制使用開始點繪制顏色
 @param kCGGradientDrawsAfterEndLocation 結束點之后使用結束點顏色繼續繪制
*/
typedef CF_OPTIONS (uint32_t, CGGradientDrawingOptions) {
  kCGGradientDrawsBeforeStartLocation = (1 << 0),
  kCGGradientDrawsAfterEndLocation = (1 << 1)
};
Code
 // 1.獲取上下文
 CGContextRef myContext = UIGraphicsGetCurrentContext();
 // 2.獲取顏色空間
 CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
 // 3.顏色組成
 CGFloat compoents[12]={
     248.0/255.0,86.0/255.0,86.0/255.0,1,
     249.0/255.0,127.0/255.0,127.0/255.0,0.7,
     249.0/255.0,200.0/255.0,200.0/255.0,0.5,
 };
 // 4.顏色位置
 CGFloat locations[3]={0,0.5,1.0};   
 // 5.漸變對象
 CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
 // 6.繪制線性漸變
 CGContextDrawLinearGradient(myContext, gradient, CGPointMake(100, 100), CGPointMake(200, 200), kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);
 // 7.釋放顏色空間
 CGColorSpaceRelease(colorSpace);
效果

1.kCGGradientDrawsBeforeStartLocation繪制


kCGGradientDrawsBeforeStartLocation.png

2.kCGGradientDrawsAfterEndLocation繪制


kCGGradientDrawsAfterEndLocation.png

3.kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation繪制


image.png
應用:橫向線性漸變導航欄

1.code

- (void)drawRect:(CGRect)rect {
   // 1.獲取上下文
   CGContextRef myContext = UIGraphicsGetCurrentContext();
   // 2.獲取顏色空間
   CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
   // 3.顏色組成
   CGFloat compoents[8]={
       248.0/255.0,86.0/255.0,86.0/255.0,1,
       249.0/255.0,200.0/255.0,200.0/255.0,0.5,
   };
   // 4.顏色位置
   CGFloat locations[2]={0,1.0};
   // 5.漸變對象
   CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
   // 6.繪制線性漸變
   CGContextDrawLinearGradient(myContext, gradient, CGPointMake(0, self.frame.size.height/2), CGPointMake(self.frame.size.width, self.frame.size.height/2), kCGGradientDrawsAfterEndLocation);
   // 7.釋放顏色空間
   CGColorSpaceRelease(colorSpace);
}

2.效果圖


image.png
7.2 CGGradientRef徑向漸變

與線性漸變繪制步驟相同

API
/**
 繪制徑向漸變

 @param c 上下文 : UIGraphicsGetCurrentContext()獲取
 @param gradient 漸變對象:CGGradientCreateWithColorComponents(...)API獲取
 @param startCenter 開始中心點
 @param startRadius 開始半徑
 @param endCenter 結束中心點
 @param endRadius 結束半徑 
 @param options 繪制方式 枚舉:CGGradientDrawingOptions
 */
void CGContextDrawRadialGradient(CGContextRef cg_nullable c,
                                 CGGradientRef cg_nullable gradient, CGPoint startCenter, CGFloat startRadius,
                                 CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options);
Case 1:

PointA(200,200)半徑為20
PointB(200,400)半徑為40
組成的徑向漸變

code
- (void)drawRect:(CGRect)rect {
    // 1.獲取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.獲取顏色空間
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.顏色組成
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,1,
    };
    // 4.顏色位置
    CGFloat locations[3]={0,0.5,1.0};
    // 5.漸變對象
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
    // 6.繪制徑向漸變
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),20, CGPointMake(200, 400), 40, kCGGradientDrawsBeforeStartLocation);
    // 7.釋放顏色空間
    CGColorSpaceRelease(colorSpace);
}
  1. kCGGradientDrawsBeforeStartLocation繪制


    image.png

2.kCGGradientDrawsAfterEndLocation繪制


image.png

3.kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation繪制


image.png
漸變區域理解
  • 從PointA到PointB投影之間的區域為漸變區域
  • 從PointB到PointA投影,PointA之外的區域使用開始點顏色繪制
  • 從PointA到PointB投影,PointB之外的區域使用結束點顏色繪制

這個理解對繪制圓形徑向漸變非常有用

例子:圓形漸變,加深對漸變區域的理解
Case 2:

PointA(200,200)半徑為20
PointB(200,200)半徑為40
組成的徑向漸變

code
    // 1-5,7步驟與之前相同
    // 6.繪制徑向漸變
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),20, CGPointMake(200, 200), 40, kCGGradientDrawsAfterEndLocation);
效果
  1. kCGGradientDrawsBeforeStartLocation繪制


    image.png
  2. kCGGradientDrawsAfterEndLocation繪制


    image.png
  3. kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation繪制

image.png
Case 2:

PointA(170,170)半徑為0
PointB(200,200)半徑為60
組成的徑向漸變

code
    // 1-5,7步驟與之前相同
    // 6.繪制徑向漸變
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(170, 170),0, CGPointMake(200, 200), 60, kCGGradientDrawsBeforeStartLocation);
效果
image.png

這個效果是不是立體感很強

Case 3: 雷達效果實現

PointA(200,200)半徑為2
PointB(200,200)半徑為變化
組成的徑向漸變

code
{
    CGFloat _alpha;
    CGFloat _radiu;
}
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        _alpha = 1;
        _radiu = 2;
        CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    }
    return self;
}

- (void)update{
    if (_radiu > 12) {
        _radiu = 2;
        _alpha = 1;
    }
    _radiu += 0.1;
    _alpha -= 0.02;
    if (_alpha <= 0) {
        _alpha = 0;
    }
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
    // 1.獲取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.獲取顏色空間
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.顏色組成
    CGFloat compoents[12] = {
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,_alpha,
    };
    // 4.顏色位置
    CGFloat locations[2] = {0,1.0};
    // 5.漸變對象
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
    // 6.繪制徑向漸變
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),2, CGPointMake(200, 200), _radiu, kCGGradientDrawsBeforeStartLocation);
    // 7.釋放顏色空間
    CGColorSpaceRelease(colorSpace);
}

效果圖
111.gif
Case 3: 特定的區域繪制漸變色

PointA(200,200)半徑為2
PointB(200,200)半徑為變化
組成的徑向三角形漸變

Code
// 1.獲取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.獲取顏色空間
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.顏色組成
    CGFloat compoents[12] = {
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,0.5,
    };
    // 4.顏色位置
    CGFloat locations[2] = {0,1.0};
    // 5.漸變對象
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
    // 6.繪圖
    CGContextMoveToPoint(myContext ,self.center.x, self.center.y - 100);
    CGContextAddLineToPoint(myContext, self.center.x - 100, self.center.y + 100);
    CGContextAddLineToPoint(myContext, self.center.x + 100, self.center.y + 100);
    CGContextClosePath(myContext);
    CGContextClip(myContext);
    // 7.繪制徑向漸變
    CGContextDrawRadialGradient(myContext, gradient, self.center,0, self.center, 200, kCGGradientDrawsBeforeStartLocation);
    // 8.釋放顏色空間
    CGColorSpaceRelease(colorSpace);
效果
image.png

八、重復繪圖

CGPatternRef繪制操作可以重復地繪制到一個圖形上下文上,當使用CGPatternRef進行繪制時,Quartz會將頁面劃分為一組CGPatternRef單元格,每個單元格都是CGPatternRef圖像的大小,并使用您提供的回調繪制每個單元格
如圖:

Pattern.png

重復繪圖API

填充:

/**
 填充繪制
 @param c 上下文
 @param pattern 單元圖形
 @param components 顏色組成
 */
void CGContextSetFillPattern(CGContextRef cg_nullable c,
                             CGPatternRef cg_nullable pattern,
                             const CGFloat * cg_nullable components)

描邊繪制:

/**
描邊繪制
@param c 上下文
@param pattern 單元圖形
@param components 顏色組成
*/
void CGContextSetStrokePattern(CGContextRef cg_nullable c,
                              CGPatternRef cg_nullable pattern,
                              const CGFloat * cg_nullable components)

上下文和顏色組成我們都知道,pattern 單元圖形的生成需要下面的API

CGPatternRef pattern獲取
/**
 @param info 是一個指針,指向我們要傳遞給繪制回調函數的數據
 @param bounds 指定模式單元格的大小
 @param matrix 指定模式矩陣,它將模式坐標系統映射到圖形上下文的默認坐標系統
 @param xStep 水平間距
 @param yStep 豎直間距
 @param tiling 枚舉 平鋪模式
 @param isColored 模式單元格是著色模式(true)還是模板模式(false)
 @param callbacks 是一個指向CGPatternCallbacks結構體的指針
 @return CGPatternRef對象
 */
CGPatternRef __nullable CGPatternCreate(void * __nullable info,
                                        CGRect bounds,
                                        CGAffineTransform matrix,
                                        CGFloat xStep,
                                        CGFloat yStep,
                                        CGPatternTiling tiling,
                                        bool isColored,
                                        const CGPatternCallbacks * cg_nullable callbacks)

以上的參數我們唯一不知道的是CGPatternCallbacks * callbacks由來

CGPatternCallbacks * callbacks獲取
struct CGPatternCallbacks {
    // 版本設置為0
    unsigned int version; 
    // 單元格繪制回調函數
    CGPatternDrawPatternCallback __nullable drawPattern;
    // 釋放Pattern是回調,可以設置為NULL
    CGPatternReleaseInfoCallback __nullable releaseInfo;
};
CGPatternDrawPatternCallback drawPattern的獲取

單元格繪制回調函數是類似于下面這種格式的一個回調函數

typedef void (*CGPatternDrawPatternCallback) (
                        void *info,
                        CGContextRef context
);
  1. info: 一個指向模式相關數據的指針。這個參數是可選的,可以傳遞NULL。傳遞給回調的數據與后面創建模式的數據是一樣的。
  2. context: 繪制模式單元格的圖形上下文
完整示例
static const CGFloat PATTERN_MARGIN_H = 30;
static const CGFloat PATTERN_MARGIN_V = 30;
static const CGSize PATTERN_SIZE = {10, 10};
// 繪制單元格
void DrawColoredPattern(void *info, CGContextRef context){
    CGContextSetRGBFillColor (context, 0, 0, 1, 0.5);
    CGContextFillRect (context, (CGRect){0,0, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, 1, 0, 0, 0.5);
    CGContextFillRect (context, (CGRect){0,10, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, 0, 1, 0, 0.5);
    CGContextFillRect (context, (CGRect){10,0, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, .5, 0, .5, 0.5);
    CGContextFillRect (context, (CGRect){10,10, PATTERN_SIZE});
}


- (void)drawRect:(CGRect)rect {
    CGFloat alpha = 1;
    // 1.上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.顏色空間填充
    CGContextSetFillColorSpace (myContext, CGColorSpaceCreatePattern (NULL));
    // 3.回調
    static const CGPatternCallbacks callbacks = {0,&DrawColoredPattern,NULL};
    // 4.pattern對象
    CGPatternRef pattern = CGPatternCreate (NULL,
                               self.frame,
                               CGAffineTransformIdentity,
                               PATTERN_MARGIN_H,
                               PATTERN_MARGIN_V,
                               kCGPatternTilingConstantSpacing,
                               true,
                               &callbacks);
    // 5.pattern對象填充
    CGContextSetFillPattern (myContext, pattern, &alpha);
    // 6.繪制到指定rect
    CGContextFillRect (myContext, self.frame);
    
    // 7.內存釋放
    CGPatternRelease (pattern);
}

效果圖

image.png

圖中:
1為單元格區域
2為豎向間距
3為橫向間距

九、核心圖層繪圖CGLayerRef

如果說CGPatternRef是重復有規律的繪制圖形,那么CGLayerRef?就是繪制無規律的重復圖形

9.1CGLayerRef適合于以下幾種情況:
  1. 高質量離屏渲染,以繪制我們想重用的圖形。例如,我們可能要建立一個場景并重用相同的背景。將背景場景繪制于一個層上,然后在需要的時候再繪制層。一個額外的好處是我們不需要知道顏色空間或其它設備依賴的信息來繪制層。
  2. 重復繪制。例如,我們可能想創建一個由相同元素反復繪制而組成的模式。將元素繪制到一個層中,然后重復繪制這個層,如下圖所示。任何我們重復繪制的Quartz對象,包括CGPath, CGShading和CGPDFPage對象,都可以通過將其繪制到CGLayer來優化性能。注意一個層不僅僅是用于離屏繪制;我們也可以將其用于那些不是面向屏幕的圖形上下文,如PDF圖形上下文。
  3. 緩存。雖然我們可以將層用于此目的,但通常不需要這樣做,因為Quartz Compositor已經做了此事。如果我們必須繪制一個緩存,則使用層來代替位圖圖形上下文。
    CGLayerRef.png
9.2使用CGLayerRef來繪制

我們需要按照如下幾個步驟來使用層對象進行繪制:

  1. 創建一個使用已存在的圖形上下文初始化的CGLayerRef對象
  2. CGLayerRef對象獲取圖形上下文
  3. 繪制圖形到CGLayer圖形上下文
  4. CGLayerRef對象繪制到目標圖形上下文
API
// 1.通過context創建CGLayer對象
CGContextRef context = UIGraphicsGetCurrentContext();
CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(100, 100), NULL);
// 2.為layer對象獲取圖形上下文
CGContextRef layerContext = CGLayerGetContext(layer);
// 3.繪制圖形到layerContext
CGContextAddRect(layerContext, CGRectMake(0, 0, 50, 50));
// 4.將layer繪制到目標圖形上下文
CGContextDrawLayerAtPoint(context, CGPointZero, layer);
示例
- (void)drawRect:(CGRect)rect {
    CGRect starBGRect = CGRectMake(0, 40, 110, 110);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 1.藍色背景
    CGContextSaveGState(context);
    CGContextSetRGBFillColor(context, 0, 0, 1, 1);
    CGContextFillRect(context, starBGRect);
    CGContextRestoreGState(context);
    
    // 2.畫三角形
    CGContextSaveGState(context);
    // 2.1獲取三角形CGLayerRef和三角形的CGContextRef
    CGLayerRef starLayer = CGLayerCreateWithContext(context, starBGRect.size, NULL);
    CGContextRef starContext = CGLayerGetContext(starLayer);
    
    // 2.2在三角形的CGContextRef中畫圖形(三角形)
    CGContextMoveToPoint(starContext, 5, 0);
    CGContextAddLineToPoint(starContext, 0, 10);
    CGContextAddLineToPoint(starContext, 10, 10);
    CGContextClosePath(starContext);
    CGContextSetRGBFillColor(starContext, 1, 0, 0, 1);
    CGContextFillPath(starContext);
    
    // 2.3將三角形的CGLayerRef畫到上下文中(Layer在上下文中變換位置繪制,即在不同位置繪制相同圖形)
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 40+10*i), starLayer);
    }
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 150-10*(i+1)), starLayer);
    }
    
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 90), starLayer);
    }
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(50, 40+10*i), starLayer);
    }
    
    CGContextRestoreGState(context);
}
效果
image.png

十、消除鋸齒渲染

位圖Graphics Context支持反鋸齒,這一操作是人為的較正在位圖中繪制文本或形狀時產生的鋸齒邊緣。當位圖的分辯率明顯低于人眼的分辯率時就會產生鋸齒。為了使位圖中的對象顯得平滑,Quartz使用不同的顏色來填充形狀周邊的像素。通過這種方式來混合顏色,使形狀看起來更平滑。如圖2-4顯示的效果。我們可以通過調用CGContextSetShouldAntialias來關閉位圖Graphics Context的反鋸齒效果。反鋸齒設置是圖形狀態的一部分。

可以調用函數CGContextSetAllowsAntialiasing來控制一個特定Graphics Context是否支持反鋸齒;false表示不支持。該設置不是圖形狀態的一部分。當上下文及圖形狀態設置為true時,Quartz執行反鋸齒。
[圖片上傳失敗...(image-9662c5-1513655208127)]

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容