Core Graphics實(shí)現(xiàn)各種藝術(shù)字效果

1.初覽

在正式開(kāi)始前先簡(jiǎn)單介紹下CoreGraphics擒悬,我會(huì)把CoreGraphics分解成概念和套路兩個(gè)部分模她,具體框架設(shè)計(jì)思路和API用法不會(huì)涉及,可移步參考Quartz 2D Programming Guide

  • 概念
    概念好比武學(xué)中的內(nèi)功心法懂牧,理解了這些侈净,才能發(fā)揮出招式真正的威力,CoreGraphics中比較核心的概念是Graphics Contexts僧凤,這將是本文引入的唯一的一個(gè)概念畜侦。
  • Graphics Contexts(圖形上下文)
    Context是個(gè)比較抽象的東西,它不僅僅是一個(gè)可以繪制的圖層拼弃,還包含為當(dāng)前圖層設(shè)置的參數(shù)夏伊,如陰影,線條粗細(xì)吻氧,繪制模式等∮搅可以類(lèi)比成一個(gè)新建的Photoshop圖層以及當(dāng)前筆觸盯孙,顏色等配置。對(duì)于移動(dòng)平臺(tái)祟滴,有三種常見(jiàn)的Context
    1.View Graphics Context: 由UIView自動(dòng)創(chuàng)建振惰,你重寫(xiě)UIView drawRect方法時(shí),你的內(nèi)容會(huì)畫(huà)在這個(gè)上下文上垄懂。
    2.Bitmap Graphics Context: 繪制在該上下文的內(nèi)容會(huì)以點(diǎn)陣形式存儲(chǔ)在一塊內(nèi)存中骑晶。簡(jiǎn)單說(shuō),就是為圖片開(kāi)辟一塊內(nèi)存草慧,然后在里面畫(huà)東西桶蛔,上下文幫你把圖片內(nèi)存抽象成一個(gè)Context(圖層)了。
    3.PDF Graphics Context:顧名思義漫谷,跟PDF文件相關(guān)仔雷,本文不會(huì)涉及。
  • 套路
    就是慣用套路,這相當(dāng)于武學(xué)中的招式碟婆。我一直有個(gè)疑問(wèn)电抚,如果一門(mén)武學(xué)沒(méi)有招式,怎么判斷這是哪個(gè)門(mén)子的武學(xué)竖共? CoreGraphics里面就有相關(guān)的招式蝙叛,這里將帶入幾個(gè)具備代表性的招式,以后看到別人用CoreGraphics寫(xiě)的看似無(wú)招的代碼公给,其實(shí)仔細(xì)品讀后會(huì)發(fā)現(xiàn)所謂的無(wú)招只是沒(méi)有固定套路借帘,招式還是在的。

第一招:拿取當(dāng)前Graphics Context妓布。

CGContextRef context = UIGraphicsGetCurrentContext();

通常是起始招式姻蚓,接下來(lái)一般會(huì)用來(lái)為上下文設(shè)置參數(shù)比如說(shuō)設(shè)置畫(huà)線時(shí)的寬度CGContextSetLineWidth(context, 1),把上下文內(nèi)容截取成一個(gè)位圖CGBitmapContextCreateImage(context)等等匣沼。

第二招:開(kāi)辟Bitmap Graphics Context

UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0);
draw something ...
UIGraphicsEndImageContext();

注意上下構(gòu)成一套組合狰挡,是開(kāi)啟然后關(guān)閉一個(gè)Bitmap Graphics Context,在這區(qū)域內(nèi)的所有繪制操作都是對(duì)于該Bitmap Context的释涛,在代碼塊里面使用第一招UIGraphicsGetCurrentContext()拿到的就是這個(gè)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...

這招又是一個(gè)組合塊,會(huì)產(chǎn)生什么效果呢唇撬?舉個(gè)栗子它匕,你正在描繪一個(gè)人物的頭部畫(huà)像,畫(huà)頭發(fā)=>畫(huà)裝飾物=>修飾頭發(fā)=>修飾裝飾物...這樣需要來(lái)回切換著畫(huà)筆狀態(tài)窖认,實(shí)際過(guò)程中會(huì)有很多參數(shù)需要配置豫柬,這個(gè)招式讓我們能保存恢復(fù)某些狀態(tài)。當(dāng)你使用CGContextSaveGState后接下來(lái)你更改畫(huà)筆狀態(tài)扑浸,畫(huà)完后再使用CGContextRestoreGState可以將狀態(tài)恢復(fù)到使用Save方法之前烧给。關(guān)于哪些狀態(tài)可以保存,請(qǐng)參考CGContextSaveGState Discussion部分

最后一招:扭轉(zhuǎn)乾坤???

CGContextConcatCTM(context, CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(rect)));

這是你熟悉又陌生的線性變換操作喝噪,因?yàn)镃ore Graphics(原點(diǎn)左下角础嫡,y軸向上為正)使用的坐標(biāo)系和UIKit(原點(diǎn)左上角,y軸向下為正)的坐標(biāo)系是不一樣的酝惧,在重寫(xiě)UIView drawRect的時(shí)候直接畫(huà)上去的內(nèi)容是一個(gè)相對(duì)x軸的鏡像榴鼎。因此需要做一次線性變換來(lái)得到正確的方位,該操作是將y變成-1*y 然后沿y軸平移晚唇,平移距離為CGRectGetHeight(rect)巫财。

好了招式都介紹完了。

2.繪制

接下來(lái)要把學(xué)到的內(nèi)容用于實(shí)戰(zhàn)了缺亮,希望通過(guò)實(shí)戰(zhàn)演練翁涤,大家能加深理解桥言,逐漸達(dá)到無(wú)招的狀態(tài)。代碼長(zhǎng)度不一葵礼,只列出關(guān)鍵部分号阿,文底有全套實(shí)現(xiàn)地址。

  • 發(fā)光

效果圖:

glow.gif

實(shí)現(xiàn):

//為文字設(shè)置陰影
CGContextSetShadowWithColor(context, CGSizeZero, self.glowSize, self.glowColor.CGColor);

第一招拿到context后鸳粉,這個(gè)效果的核心代碼就只有1句了扔涧,我都不好意思做解釋。你甚至可以直接修改UILabel自帶的陰影屬性來(lái)達(dá)到這個(gè)效果届谈。

  • 描邊

效果圖:

stroke.gif

實(shí)現(xiàn):

//設(shè)置邊線寬度
CGContextSetLineWidth(context, self.outlineWidth);
//設(shè)置線條轉(zhuǎn)角樣式
CGContextSetLineJoin(context, kCGLineJoinRound);
//設(shè)置繪圖模式為描線
CGContextSetTextDrawingMode(context, kCGTextStroke);

這個(gè)效果的核心只有3行枯夜,通過(guò)第一招拿到context,然后配置context艰山。接下來(lái)調(diào)用super的draw方法湖雹,這時(shí)候就畫(huà)了字的描邊。如果要如圖一樣的黑色填充曙搬,把DrawingMode改成Fill的模式摔吏,再調(diào)用一遍super的draw方法即可。

  • 漸變

效果圖:

gradient.gif

實(shí)現(xiàn):

//>>>第一部分
//第二招開(kāi)始
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();

//>>>第二部分
//第一招纵装,此時(shí)是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);

這個(gè)效果大體分為3個(gè)部分诗箍,第一部分把原來(lái)的字形畫(huà)在一張圖片里用作mask。第二部分使用mask裁剪當(dāng)前view的context挽唉。第三部分在當(dāng)前view的context上繪制一個(gè)線性漸變滤祖。除線性漸變外,還有徑向漸變瓶籽,具體可以參考結(jié)尾的Git代碼氨距。

  • 鏤空

效果圖:

hollow.gif

實(shí)現(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)建一個(gè)mask,這里得到的是一個(gè)反向的遮罩
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);

該實(shí)現(xiàn)跟漸變大體相似棘劣,不過(guò)這里創(chuàng)建了一個(gè)反向的mask,把字體區(qū)域給鏤空了楞遏。然后其他部分填充為一個(gè)純色茬暇,當(dāng)然也可以用圖片填充這個(gè)區(qū)域。還有一種鏤空的實(shí)現(xiàn)是通過(guò)修改BlendMode剔除像素寡喝,這里不贅述了糙俗。

  • 3D

效果圖:

3d.gif

實(shí)現(xiàn):

//循環(huán)繪制text到context,每次偏移幾個(gè)像素
[self.text drawInRect:CGRectMake(rect.origin.x + i, rect.origin.y + i, rect.size.width, rect.size.height) withAttributes:attrs];

從動(dòng)畫(huà)中應(yīng)該也可以看出端倪,所謂的3D效果其實(shí)是很多圖層疊加起來(lái)的预鬓,因此真正的核心代碼只有這一句巧骚。但相應(yīng)的為了實(shí)現(xiàn)這種透視感,每一層的顏色和陰影都有些許變化。

  • 涂層

效果圖:

marked.gif

實(shí)現(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];

這個(gè)效果的核心代碼也只有這一句竣蹦,基本思想是用一張筆刷灰度圖,修改該灰度圖的TintColor沧奴,然后繪制在context上痘括,通過(guò)隨機(jī)調(diào)整間隔,就達(dá)到了深淺相間的效果滔吠「倬控制文字和圖片的繪制順序,就形成了上下效果疮绷。

  • 故障

效果圖:

glitch.gif

實(shí)現(xiàn):
這個(gè)效果的實(shí)現(xiàn)沒(méi)有引入什么新的操作翰舌,是一些基本操作的組合。
1.用3中顏色先把文字畫(huà)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];

得到下面的效果

step1.gif

2.為圖片添加切片

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

3.添加隨機(jī)的線段
代碼還有很大優(yōu)化空間椅贱,不列舉了,說(shuō)下基本實(shí)現(xiàn)思路吧

  • 構(gòu)建一個(gè)循環(huán)體
  • 循環(huán)體內(nèi)隨機(jī)生成CGRect
  • 過(guò)濾掉重疊的Rect
step3.gif
  • 材質(zhì)

效果圖:

materia.png

實(shí)現(xiàn):
這不是蒙圖實(shí)現(xiàn)唉韭!這不是蒙圖實(shí)現(xiàn)夜涕!這不是蒙圖實(shí)現(xiàn)!蒙圖很難實(shí)現(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];

通過(guò)使用不同的材質(zhì)球女器,來(lái)顯示不同的材質(zhì)效果,上圖使用的材質(zhì)圖

Golden.png

這個(gè)效果主要用到的是Core Image里面的filter住诸,目的是引入實(shí)現(xiàn)特殊字體的另一個(gè)思路驾胆,通過(guò)第二招將文字變成圖片,然后就可以組合使用Core Image Filter來(lái)實(shí)現(xiàn)更加復(fù)雜的效果贱呐。CoreImageFilterReference

附上代碼地址ArtFontDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末丧诺,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子奄薇,更是在濱河造成了極大的恐慌驳阎,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馁蒂,死亡現(xiàn)場(chǎng)離奇詭異呵晚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)沫屡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)饵隙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沮脖,你說(shuō)我怎么就攤上這事金矛⌒炯保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵驶俊,是天一觀的道長(zhǎng)娶耍。 經(jīng)常有香客問(wèn)我,道長(zhǎng)废睦,這世上最難降的妖魔是什么伺绽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮嗜湃,結(jié)果婚禮上奈应,老公的妹妹穿的比我還像新娘。我一直安慰自己购披,他們只是感情好杖挣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著刚陡,像睡著了一般惩妇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上筐乳,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天歌殃,我揣著相機(jī)與錄音,去河邊找鬼蝙云。 笑死氓皱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的勃刨。 我是一名探鬼主播波材,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼身隐!你這毒婦竟也來(lái)了廷区?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贾铝,失蹤者是張志新(化名)和其女友劉穎隙轻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體垢揩,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡大脉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了水孩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琐驴,死狀恐怖俘种,靈堂內(nèi)的尸體忽然破棺而出秤标,到底是詐尸還是另有隱情,我是刑警寧澤宙刘,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布苍姜,位于F島的核電站,受9級(jí)特大地震影響悬包,放射性物質(zhì)發(fā)生泄漏衙猪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一布近、第九天 我趴在偏房一處隱蔽的房頂上張望垫释。 院中可真熱鬧,春花似錦撑瞧、人聲如沸棵譬。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)订咸。三九已至,卻和暖如春酬诀,著一層夾襖步出監(jiān)牢的瞬間脏嚷,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工瞒御, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留父叙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓葵腹,卻偏偏與公主長(zhǎng)得像高每,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子践宴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容