1.初覽
在正式開始前先簡單介紹下CoreGraphics,我會把CoreGraphics分解成概念和套路兩個部分,具體框架設(shè)計思路和API用法不會涉及,可移步參考Quartz 2D Programming Guide
- 概念
概念好比武學(xué)中的內(nèi)功心法,理解了這些,才能發(fā)揮出招式真正的威力,CoreGraphics中比較核心的概念是Graphics Contexts,這將是本文引入的唯一的一個概念。
- Graphics Contexts(圖形上下文)
Context是個比較抽象的東西,它不僅僅是一個可以繪制的圖層,還包含為當(dāng)前圖層設(shè)置的參數(shù),如陰影,線條粗細(xì),繪制模式等??梢灶惐瘸梢粋€新建的Photoshop圖層以及當(dāng)前筆觸,顏色等配置。對于移動平臺,有三種常見的Context
1.View Graphics Context: 由UIView自動創(chuàng)建,你重寫UIView drawRect方法時,你的內(nèi)容會畫在這個上下文上。
2.Bitmap Graphics Context: 繪制在該上下文的內(nèi)容會以點(diǎn)陣形式存儲在一塊內(nèi)存中。簡單說,就是為圖片開辟一塊內(nèi)存,然后在里面畫東西,上下文幫你把圖片內(nèi)存抽象成一個Context(圖層)了。
3.PDF Graphics Context:顧名思義,跟PDF文件相關(guān),本文不會涉及。
- 套路
就是慣用套路,這相當(dāng)于武學(xué)中的招式。我一直有個疑問,如果一門武學(xué)沒有招式,怎么判斷這是哪個門子的武學(xué)? CoreGraphics里面就有相關(guān)的招式,這里將帶入幾個具備代表性的招式,以后看到別人用CoreGraphics寫的看似無招的代碼,其實仔細(xì)品讀后會發(fā)現(xiàn)所謂的無招只是沒有固定套路,招式還是在的。
第一招:拿取當(dāng)前Graphics Context。
CGContextRef context = UIGraphicsGetCurrentContext();
通常是起始招式,接下來一般會用來為上下文設(shè)置參數(shù)比如說設(shè)置畫線時的寬度CGContextSetLineWidth(context, 1),把上下文內(nèi)容截取成一個位圖CGBitmapContextCreateImage(context)等等。
第二招:開辟Bitmap Graphics Context
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
draw something ...
UIGraphicsEndImageContext();
注意上下構(gòu)成一套組合,是開啟然后關(guān)閉一個Bitmap Graphics Context,在這區(qū)域內(nèi)的所有繪制操作都是對于該Bitmap Context的,在代碼塊里面使用第一招UIGraphicsGetCurrentContext()拿到的就是這個Bitmap Graphics Context。
第三招: 保存和恢復(fù)當(dāng)前Context狀態(tài)
Set line width 5, black hair color ...
Draw hair...
CGContextSaveGState(context);//save line width 5, black color
Set line width 8, red color...
Draw hair ornaments...
CGContextRestoreGState(context);//restore line width 5, black color
Continue to draw hair...
這招又是一個組合塊,會產(chǎn)生什么效果呢?舉個栗子,你正在描繪一個人物的頭部畫像,畫頭發(fā)=>畫裝飾物=>修飾頭發(fā)=>修飾裝飾物...這樣需要來回切換著畫筆狀態(tài),實際過程中會有很多參數(shù)需要配置,這個招式讓我們能保存恢復(fù)某些狀態(tài)。當(dāng)你使用CGContextSaveGState后接下來你更改畫筆狀態(tài),畫完后再使用CGContextRestoreGState可以將狀態(tài)恢復(fù)到使用Save方法之前。關(guān)于哪些狀態(tài)可以保存,請參考CGContextSaveGState Discussion部分
最后一招:扭轉(zhuǎn)乾坤???
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
這是你熟悉又陌生的線性變換操作,因為Core Graphics(原點(diǎn)左下角,y軸向上為正)使用的坐標(biāo)系和UIKit(原點(diǎn)左上角,y軸向下為正)的坐標(biāo)系是不一樣的,在重寫UIView drawRect的時候直接畫上去的內(nèi)容是一個相對x軸的鏡像。因此需要做一次線性變換來得到正確的方位,該操作是將y變成-1*y 然后沿y軸平移,平移距離為CGRectGetHeight(rect)。
好了招式都介紹完了。
2.繪制
接下來要把學(xué)到的內(nèi)容用于實戰(zhàn)了,希望通過實戰(zhàn)演練,大家能加深理解,逐漸達(dá)到無招的狀態(tài)。代碼長度不一,只列出關(guān)鍵部分,文底有全套實現(xiàn)地址。
-
發(fā)光
效果圖:

實現(xiàn):
//為文字設(shè)置陰影
CGContextSetShadowWithColor(context, CGSizeZero, self.glowSize, self.glowColor.CGColor);
第一招拿到context后,這個效果的核心代碼就只有1句了,我都不好意思做解釋。你甚至可以直接修改UILabel自帶的陰影屬性來達(dá)到這個效果。
-
描邊
效果圖:

實現(xiàn):
//設(shè)置邊線寬度
CGContextSetLineWidth(context, self.outlineWidth);
//設(shè)置線條轉(zhuǎn)角樣式
CGContextSetLineJoin(context, kCGLineJoinRound);
//設(shè)置繪圖模式為描線
CGContextSetTextDrawingMode(context, kCGTextStroke);
這個效果的核心只有3行,通過第一招拿到context,然后配置context。接下來調(diào)用super的draw方法,這時候就畫了字的描邊。如果要如圖一樣的黑色填充,把DrawingMode改成Fill的模式,再調(diào)用一遍super的draw方法即可。
-
漸變
效果圖:

實現(xiàn):
//>>>第一部分
//第二招開始
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
//把當(dāng)前內(nèi)容繪制在Bitmap Context上
[super drawTextInRect:rect];
//第一招
CGContextRef context = UIGraphicsGetCurrentContext();
//以當(dāng)前context內(nèi)容生成一張圖片
CGImageRef mask = CGBitmapContextCreateImage(context);
//第二招結(jié)束
UIGraphicsEndImageContext();
//>>>第二部分
//第一招,此時是View Graphics context了
context = UIGraphicsGetCurrentContext();
//最后一招,扭轉(zhuǎn)乾坤
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
//注意是ClipTo,因此只有mask部分能被繪制上內(nèi)容
CGContextClipToMask(context, rect, mask);
//>>>第三部分
//創(chuàng)建漸變
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)color, NULL);
//繪制漸變,漸變只顯示mask部分
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);
這個效果大體分為3個部分,第一部分把原來的字形畫在一張圖片里用作mask。第二部分使用mask裁剪當(dāng)前view的context。第三部分在當(dāng)前view的context上繪制一個線性漸變。除線性漸變外,還有徑向漸變,具體可以參考結(jié)尾的Git代碼。
-
鏤空
效果圖:

實現(xiàn):
//第一部分
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
[super drawTextInRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef image = CGBitmapContextCreateImage(context);
UIGraphicsEndImageContext();
//第二部分
context = UIGraphicsGetCurrentContext();
CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));
//使用第一部分得到的image創(chuàng)建一個mask,這里得到的是一個反向的遮罩
CGImageRef mask = CGImageMaskCreate(CGImageGetWidth(image), CGImageGetHeight(image), CGImageGetBitsPerComponent(image), CGImageGetBitsPerPixel(image), CGImageGetBytesPerRow(image), CGImageGetDataProvider(image), CGImageGetDecode(image), CGImageGetShouldInterpolate(image));
CGContextClipToMask(context, rect, mask);
//第三部分
//設(shè)置為填充色
[self.maskColor set];
//使用顏色填充區(qū)域
CGContextFillRect(context, rect);
該實現(xiàn)跟漸變大體相似,不過這里創(chuàng)建了一個反向的mask,把字體區(qū)域給鏤空了。然后其他部分填充為一個純色,當(dāng)然也可以用圖片填充這個區(qū)域。還有一種鏤空的實現(xiàn)是通過修改BlendMode剔除像素,這里不贅述了。
-
3D
效果圖:

實現(xiàn):
//循環(huán)繪制text到context,每次偏移幾個像素
[self.text drawInRect:CGRectMake(rect.origin.x + i, rect.origin.y + i, rect.size.width, rect.size.height) withAttributes:attrs];
從動畫中應(yīng)該也可以看出端倪,所謂的3D效果其實是很多圖層疊加起來的,因此真正的核心代碼只有這一句。但相應(yīng)的為了實現(xiàn)這種透視感,每一層的顏色和陰影都有些許變化。
-
涂層
效果圖:

實現(xiàn):
//循環(huán)繪制圖片,圖片偏移i = i + randomValue
[self.strokeTexture drawInRect:CGRectMake(i, midY - self.strokeWidth/2.f, self.strokeWidth, self.strokeWidth) blendMode:kCGBlendModeNormal alpha:self.maskAlpha];
這個效果的核心代碼也只有這一句,基本思想是用一張筆刷灰度圖,修改該灰度圖的TintColor,然后繪制在context上,通過隨機(jī)調(diào)整間隔,就達(dá)到了深淺相間的效果??刂莆淖趾蛨D片的繪制順序,就形成了上下效果。
-
故障
效果圖:

實現(xiàn):
這個效果的實現(xiàn)沒有引入什么新的操作,是一些基本操作的組合。
1.用3中顏色先把文字畫3遍
CGRect bottomRect = CGRectMake(rect.origin.x + self.bottomOffset.x, rect.origin.y + self.bottomOffset.y, rect.size.width, rect.size.height);
CGRect middleRect = CGRectMake(rect.origin.x + self.middleOffset.x, rect.origin.y + self.middleOffset.y, rect.size.width, rect.size.height);
self.textColor = self.bottomColor;
[super drawTextInRect:bottomRect];
self.textColor = self.middleColor;
[super drawTextInRect:middleRect];
self.textColor = self.topColor;
[super drawTextInRect:rect];
得到下面的效果

2.為圖片添加切片
//得到切片圖片
CGImageRef sliceRef = CGImageCreateWithImageInRect(contentImage, imageSlice);
//把原上下文切片部分內(nèi)容剔除
CGContextClearRect(context, contentSlice);
//把切片畫到原上下文被剔除部分,左右隨機(jī)平移一定距離
CGContextDrawImage(context, translatedRect, rotateRef);


3.添加隨機(jī)的線段
代碼還有很大優(yōu)化空間,不列舉了,說下基本實現(xiàn)思路吧
- 構(gòu)建一個循環(huán)體
- 循環(huán)體內(nèi)隨機(jī)生成CGRect
- 過濾掉重疊的Rect

-
材質(zhì)
效果圖:

實現(xiàn):
這不是蒙圖實現(xiàn)!這不是蒙圖實現(xiàn)!這不是蒙圖實現(xiàn)!蒙圖很難實現(xiàn)這種有高低落差的光影效果。
//使用CIHeightFieldFromMask生成高低落差圖
CIImage *inputImage = [CIImage imageWithCGImage:imageRef];
CIFilter *filter = [CIFilter filterWithName:@"CIHeightFieldFromMask"];
[filter setValue:inputImage forKey:kCIInputImageKey];
CIImage *outputImage = filter.outputImage;
CGImageRelease(imageRef); imageRef = NULL;
//使用CIShadedMaterial拼接材質(zhì)
CIImage *materia = [CIImage imageWithCGImage:self.materiaImage.CGImage];
CIFilter *filterMaterial = [CIFilter filterWithName:@"CIShadedMaterial"];
[filterMaterial setValue:outputImage forKey:kCIInputImageKey];
[filterMaterial setValue:materia forKey:kCIInputShadingImageKey];
CIImage *finalEffect = filterMaterial.outputImage;
UIImage *finalImage = [UIImage imageWithCIImage:finalEffect];
[finalImage drawInRect:rect];
通過使用不同的材質(zhì)球,來顯示不同的材質(zhì)效果,上圖使用的材質(zhì)圖

這個效果主要用到的是Core Image里面的filter,目的是引入實現(xiàn)特殊字體的另一個思路,通過第二招將文字變成圖片,然后就可以組合使用Core Image Filter來實現(xiàn)更加復(fù)雜的效果。CoreImageFilterReference
附上代碼地址ArtFontDemo