許多UIView的子類曲梗,如一個UIButton
或一個UILabel
,它們知道怎么繪制自己妓忍。遲早虏两,你也將想要做一些自己的繪制。你可以事先準(zhǔn)備好您的繪圖作為一個圖像文件世剖。您可以用代碼繪制一張圖片在應(yīng)用程序運行中定罢。您可以在`UIView`子類,例如一個UIImageVie
或一個UIButton
中顯示在你的圖像旁瘫。一個純粹的UIView
有關(guān)于繪畫的一切祖凫,繪圖很大程度上取決于你;你的代碼決定view
畫什么,你的界面就是什么酬凳。
Images and Image Views
基本的UIKit的圖像類是UIImage
蝙场。UIImage
可以讀取磁盤上的文件,因此粱年,如果圖像不需要動態(tài)創(chuàng)建售滤,一種簡單的方式就是在應(yīng)用程序運行前提供一個圖片文件放到程序包資源中。系統(tǒng)知道如何處理標(biāo)準(zhǔn)圖像文件類型,如TIFF完箩,JPEG赐俗,GIF和PNG;當(dāng)圖像文件將被包含在你的應(yīng)用程序包,你應(yīng)該優(yōu)先提供PNG
格式的圖片,因為系統(tǒng)對PNG
格式有種有特殊的親和力弊知。還可以以其他方式獲取圖像數(shù)據(jù)阻逮,例如通過下載,并轉(zhuǎn)變成一個UIImage
秩彤。相反叔扼,你也可以用代碼構(gòu)造一個UIImage
并顯示在你的界面或保存到磁盤。
Image Files
可以通過UIImage
的初始化init(named:)
方法來獲得app bundle
中的圖片文件漫雷。這個方法在兩個地方查找圖片:
-
Asset catalog
我們在
asset catalog
中查找與name
名稱相同的圖片瓜富。該名稱是區(qū)分大小寫的。 -
Top level of app bundle
我們在
app bundle
的最上層尋找與name
名稱相同的圖片降盹。該名稱是區(qū)分大小寫的与柑,并應(yīng)包括文件擴展名;如果它不包括文件擴展名,則假定.PNG蓄坏。
當(dāng)調(diào)用 init(named:)
時价捧,asset catalog
在 app bundle top level
之前搜索圖片文件。如果有多個asset catalog
涡戳,它們都會進(jìn)行搜索结蟋,但搜索順序是不確定的并且不能指定搜索順序,所以應(yīng)該避免圖像具有相同的名稱渔彰。
關(guān)于init(named:)
的一個好處是圖像數(shù)據(jù)可能會緩存在內(nèi)存中嵌屎,,如果你以后再通過調(diào)用init(named:)
訪問為相同的圖像胳岂,緩存中的數(shù)據(jù)可以立即编整√蛳。或者乳丰,你可以使用init(contentsOfFile:)
直接從 app bundle
里面讀取圖像數(shù)據(jù)而不緩存,但是需要提供一個路徑字符串作為參數(shù); 你可以通過NSBundle.mainBundle()
獲取到app's bundle
内贮,然后通過NSBundle
的實列方法产园,例如pathForResource:ofType
獲取文件的路徑。
獲取app bundle
內(nèi)資源的方法夜郁,如 init(named:)
和pathFor- Resource:ofType:
什燕,都會識別實際的資源文件的后綴。在double-resolution
屏幕上,當(dāng)通過文件名在 app bundle
內(nèi)獲取圖片時竞端,具有相同圖片名并且含有@2x
的圖片將自動使用屎即,與由此生成的UIImage
的scale
屬性值為2.0
。同樣,如果文件具有相同的名稱并且有 @3x
技俐,它擴展將會用于iPhone 6 Plus
,并且 scale
屬性值為 3.0
乘陪。
通過這種方式,您的應(yīng)用程序可以包含一個圖像文件在在不同分辨率下的多個版本雕擂。由于scale
屬性啡邑,圖像的高分辨率版本和單分辨率的版本繪制的大小相同。因此井赌,在高分辨率的屏幕上谤逼,代碼不用修改就能工作,但圖片看起來清晰仇穗。
同樣流部,具有相同名稱的由~ipad
擴展的文件當(dāng)app
在ipad
上運行時會被自動使用 。你可以使用這種方式在universal app
中根據(jù)不同的設(shè)備 iPhone
或者 iPad
提供不同的圖像(這對任何在bundle
中的資源都是有效的而不只是圖片)
asset catalog
的一個好處是你可以忘記所有關(guān)于這些后綴名約定仪缸。asset catalog
知道在一個image set
內(nèi)何時使用備用圖像贵涵,不是根據(jù)它的名字,而是根據(jù)它在catalog
中的位置恰画。把single-
,double-
,triple-resolution
分辨率的圖片放到標(biāo)記著1x
,2x
,3x
的位置上宾茂。對于不同的iPad
版的圖像,檢查iPhone
和iPad
的圖像集的屬性檢查器,不同的圖片位置將會出現(xiàn)在asset catalog
中拴还。
Asset catalog
也可以區(qū)分圖像在不同size class
下的版本跨晴。在圖像集的屬性檢查器中,使用寬度和高度的彈出菜單來指定要區(qū)分哪個size class
片林。如果我們把運行著app
的iPhone旋轉(zhuǎn)到橫向端盆,如果有既有的圖片集中的Any height
和Compact height
圖像都可以使用的話,會優(yōu)先使用Compact height
版本的圖像费封。這些功能是實時的在app
運行期間;如果應(yīng)用程序從橫向旋轉(zhuǎn)為縱向焕妙,Any height
會自動替換掉Compact height
的圖片,如果圖片集中的2中圖像都可以使用的話弓摘。
Asset catalog
這種神奇的能力是通過trait collections
和UIImageAsset
類來實現(xiàn)的焚鹊。當(dāng)圖像通過初始化init(named:)
從asset catalog
中獲取時,它的imageAsset
屬性是一個UIImageAsset
韧献。在圖像集所有的圖像都可以通過UIImageAsset
獲取;每個圖像都有trait collection
末患,你可以訪問圖像的imageAsset
的屬性和 imageWithTraitCollection:
從相同的圖像集中得到特定trait collection
的圖片。
一個內(nèi)置的顯示圖像的interface object
能自動的識別trait collection
;它接收traitCollectionDidChange
:消息并相應(yīng)地作出響應(yīng)锤窑。我們可以通過構(gòu)造一個有image
屬性的UIView
來實現(xiàn)這個功能:
class MyView: UIView {
var image: UIView!
override func traitCollectionDidChange(previous: UITraitCollection?) {
self.setNeedDisplay()
}
override func drawRect() {
if var im = self.image {
if let asset = self.image.imageAsset {
let tc = self.traitCollection
im = asset.imageWithTraitCollection(tc)
}
im.drawAtPoint(CGPointZero)
}
}
}
此外璧针,你的代碼也可以將圖像合并到一個UIImageAsset
- 代碼相當(dāng)于一個asset catalog
中的image set
,但是并沒有asset catalog
渊啰。因此探橱,你可以實時的創(chuàng)建圖像申屹,或者在app bundle
的外部獲取圖像,并且自動配置當(dāng)iPhone
在portrait ori‐ entation
時使用前一個圖像,當(dāng)iPhone
在 landscape orientation
時使用另一個圖像:
let tcdisp = UITraitCollection(displayScale:UIScreen.mainScreen().scale)
let tcphone = UITraitCollection(userInterfaceIdiom: .Phone)
let tcreg = UITraitCollection(verticalSizeClass: .Regular)
let tc1 = UITraitCollection(traitsFromCollections: [tcdisp, tcphone, tcreg])
let tccom = UITraitCollection(verticalSizeClass: .Compact)
let tc2 = UITraitCollection(traitsFromCollections: [tcdisp, tcphone, tccom])
let moods = UIImageAsset()
let frowney = UIImage(named:"frowney")!
let smiley = UIImage(named:"smiley")!
moods.registerImage(frowney, withTraitCollection: tc1)
moods.registerImage(smiley, withTraitCollection: tc2)
之后隧膏,如果把frowney
放到用戶界面 - 例如独柑,一個UIImageView
的image
屬性 - ,當(dāng)app
改變方向時,它將和smiley
交替顯示∷街玻可喜的是忌栅,即使是沒有永久引用frowney
,smiley
曲稼,或UIImageAsset
(moods
)索绪,這都會自動發(fā)生。原因是贫悄,frowney
和smiley
由系統(tǒng)緩存(因為調(diào)用init(named:)
)瑞驱,他們各自保持一個它們自己關(guān)聯(lián)的UIImageAsset
的強引用。
通過init(named:inBundle:compatibleWith- TraitCollection:)
從app bundle
或者asset catalog
獲取圖像時可以指定一個目標(biāo)trait collection
窄坦。bundle
參數(shù)經(jīng)常為nil
唤反,這表示app‘s main bundle
。
Image Views
許多內(nèi)置的CocoaCocoa interface objects
接受一個UIImage
做為自己的一部分去繪制;例如鸭津,一個UIButton
能夠顯示圖像彤侍,UINavigationBar
或者UITabBar
可以有一個背景圖像。當(dāng)你只是想一個圖像出現(xiàn)在你的界面上逆趋,你可能把它交給一個圖像視圖 - UIImageView
- 它的作用就是顯示圖像盏阶。
nib
編輯器在這方面提供了一些快捷方式:一個interface object
的屬性檢查器中名斟,會有一個彈出菜單魄眉,其中包含項目中的所有圖像,這些圖像也會在Media library
中顯示(Command-Option- Control-4
)岩梳。Media library
的圖像往往可以拖動到畫布上的interface object
上顯示,如果只是拖動Media library
里的圖像到空白的view
脾歇,它被轉(zhuǎn)換成顯示該圖像的一個UIImageView
藕各。
一個UIImageView
實際上可以有兩幅圖片,其中一個分配給自己的image
屬性和另一個分配給其highlightedImage
屬性;UIImageView
的highlighted
屬性值決定在任何給定的時間顯示哪幅圖。和button
一樣,UIImageView
只會在用戶點擊它的時候高亮顯示绢慢。不過胰舆,在某些特定的情況下UIImageView
會對它周圍的高亮作出回應(yīng);例如在table view cell
中,當(dāng)cell
高亮的時候倦零,ImageView
會顯示它的highlighted image
。
UIImageView
也是UIView
诞帐,因此它可以有圖像屬性外也可以有背景顏色停蕉,它可以有一個alpha(透明度)值慧起,等等。圖像可能有透明的區(qū)域灿意,也可以是任何形狀缤剧,UIImageView
都會顯示它;沒有背景顏色的UIImageView
不會顯示在界面中汗销,除非它的image
屬性不為空弛针,圖像僅僅只是顯示在界面中,用戶不會意識到它是在一個矩形框中付材。即沒有背景顏色,image
屬性也沒有值的UIImageView
是不可見的富寿,所以你可以以一個空的UIImageView
開始,并隨后在代碼中指定image
屬性。你可以指定一個新的圖像來代替舊的变勇,或者設(shè)置image
屬性的值為nil
來移除UIImageView
的圖像。
UIImageView
如何繪制自己的圖像取決于其contentMode
屬性(UIViewContentMode
)的設(shè)置链患。 (該contentMode
屬性是從UIView
繼承的)例如,.ScaleToFill
意味著圖像的寬度和高度都設(shè)置為視圖的寬度和高度贸毕,從而完全填充視圖即使這會改變圖像的長寬比;.Center
居中繪制圖像而且不改變t圖像的大小。理解contentMode
最好的方式是在nib
中為UIImageView
分配一個小圖像击蹲,然后在屬性檢查器中,切換不同的mode
类咧,觀察圖像如何繪制自身。
你還應(yīng)該注意UIImageView
的clipsToBounds
屬性;如果是false
值戳,即使它的圖像比image view
更大,或者圖像沒有被contentMode
按比例縮小赴捞,那么圖像可以延伸超出image view
本身全部顯示。
當(dāng)在代碼中創(chuàng)建UIImageView
,你可以充分利用便利構(gòu)造器的優(yōu)勢然评,init(image:)
(或者 init(image:highlightedImage:)
).默認(rèn)的contentMode
是.ScaleToFill
,但圖像初始時是不縮放的,而是調(diào)整view
自身的大小去匹配圖像亿眠。你仍然可能需要把UIImageView
放到它的父視圖上正確位置。在下面這個例子中竟趾,我把火星圖片在應(yīng)用程序界面中心:
let iv = UIImageView(image: UIImage(named: "Mars")) //asset catalog
mainView.addSubview(iv)
iv.center = iv.superview!.bounds.center
iv.frame.makeIntegralInPlace()
為一個已經(jīng)存在的UIImageView
指定圖片時,UIImageView
的大小如何改變?nèi)Q于它是否使用autolayout
屎飘。如果沒有使用autolayout
,或者它的大小被約束完全限定,那么UIImageView
的大小不發(fā)生變化押桃。但在autolayout
下,除非其他約束阻止波丰,新的圖像的尺寸會變?yōu)?code>image view的intrinsicContentSize
,因此imageview
將變?yōu)閳D像的大小。
image view
會自動從圖像的alignmentRectInsets
獲得其alignmentRectInsets
先馆。因此,如果你打算使用自動布局來調(diào)整image view
對齊到其他對象仿野,你可以將相應(yīng)的alignmentRectInsets
設(shè)置到將要顯示的圖像上,那么image view
會正確的顯示劣针。要實現(xiàn)這個功能,通過在原始圖像上調(diào)用imageWithAlignmentRectInsets
派生出新的圖像辣苏。
從理論上講煌张,你應(yīng)該能夠在
Asset catalog
中設(shè)置圖像的alignmentRectInsets
链嘀。但是當(dāng)寫這篇文章時怀泊,此功能無法正常工作。
Resizable Images
在界面中某些地方可能需要可以調(diào)整大小的圖像;例如,在作為一個滑塊或進(jìn)度條視圖的軌道的自定義圖像必須能夠調(diào)整大小忠藤,以便它可以填充任何長度的空間。還有經(jīng)常需要通過平鋪或拉伸現(xiàn)有圖像填充背景的其他情形瓜贾。
通過在正常的圖像上調(diào)用resizableImageWithCapInsets:resizingMode:
方法來創(chuàng)建動態(tài)伸縮的圖像。capInsets
:參數(shù)是一個UIEdgeInsets
胃夏,其分量代表向內(nèi)到圖像的邊緣的距離(可以理解為內(nèi)邊距)仰禀。在一個比圖像大的context
中,可調(diào)整大小的圖像有2種表現(xiàn)方式,這取決于resizingMode
:的值(UIImageResizingMode
):
-
.Tile
變化的區(qū)域的內(nèi)部圖片是平鋪(重復(fù));每一條邊是由非變化區(qū)域的相應(yīng)邊緣矩形組成的。相對于變化區(qū)域的四個角落的繪制不變周瞎。 -
.Stretch
變化區(qū)域的內(nèi)部被拉伸一次以填充;每一條邊是由非變化區(qū)域的相應(yīng)邊緣矩形組成的退盯。相對于變化區(qū)域的四個角落的繪制不變囤攀。
在下面的例子中,假設(shè)self.iv
是一個絕對高度和寬度(因此它不會用它的圖像的大小來設(shè)置自己的大序蛳巍)而且contentMode
屬性為.ScaleToFill
(圖像會有伸縮的行為)的UIImageView
。首先,我會說明怎么平鋪整個圖像;注意椒楣,capInsets
:的值是UIEdgeInsetsZero
:
let mars = UIImage(named: "Mars")!
let marsTiled = mars.resizableImageWithCapInsets(UIEdgeInsetsZero, resizingMode: .Tile)
self.iv.image = marsTiled
然后,改變上面代碼種capInsets
參數(shù)的值來重新平鋪上面的圖片:
let marsTiled = mars.resizableImageWithCapInsets(
UIEdgeInsetsMake(
mars.size.height / 4.0,
mars.size.width / 4.0,
mars.size.height / 4.0,
mars.size.width / 4.0
), resizingMode: .Tile)
然后來說明拉伸毛俏,從改變上面代碼的resizingMode
開始:
let marsTiled = mars.resizableImageWithCapInsets(
UIEdgeInsetsMake(
mars.size.height / 4.0,
mars.size.width / 4.0,
mars.size.height / 4.0,
mars.size.width / 4.0
), resizingMode: .Stretch)
效果如下:
一個常見的延伸策略是讓幾乎一半的原始圖像作為cap inset
免绿,只留下中心的一兩個像素來填充空白區(qū)域:
let marsTiled = mars.resizableImageWithCapInsets(
UIEdgeInsetsMake(
mars.size.height / 2.0 - 1,
mars.size.width / 2.0 - 1,
mars.size.height / 2.0 -1,
mars.size.width / 2.0 - 1
), resizingMode: .Stretch)
效果如下
你也應(yīng)該嘗試不同的contentMode
設(shè)置迹卢。在上的例子中誊垢,如果該圖像視圖的contentMode
是.ScaleAspectFill
,并且如果圖像視圖的clipsToBounds
為true
芋肠,我們會得到一種漸變的效果,因為拉神完成的圖象的頂部和底部已經(jīng)超出image view
的邊界而不會被繪制出來。
效果如下:
你可以通過項目的asset catalog
而不是代碼來配置一個可調(diào)整大小的圖像。經(jīng)常出現(xiàn)的情況是:一個特定的圖像將在您的應(yīng)用中主要被用來作為一個可調(diào)整大小的圖像彤叉,并且總是具有同樣的capInsets
和resizingMode
,所以很有必要只配置此圖像一次,而不是重復(fù)寫相同的代碼兔辅。即使圖像在asset catalog
中配置為可調(diào)整大小,它也可以做為一個正常的圖片出現(xiàn)在你的界面中, 例如,你可以把它分配給根據(jù)圖像大小調(diào)整自身大小的image view
,或者是不會壓縮或者拉伸自己圖像的image view
褐澎。
要在asset catalog
中配置一個可調(diào)整大小的圖像,首先選擇圖像,然后在Slicing section
的屬性檢查器中,更改Slices
彈出菜單為Horizontal
,Vertical
寺枉,或者是Horizontal and Vertical
。當(dāng)你執(zhí)行此操作時催式,會有更多的界面出現(xiàn)供你配置參數(shù)。您可以在另外的彈出菜單中配置resizingMode
。也可以用數(shù)字,或單擊畫布右下角的Show Slicing
生年,這都會在畫布上顯示配置好的圖像桌粉。圖形編輯器是可縮放的霜大,所以你可以放大到你覺得舒服的大小。
這個功能實際上比resizableImageWithCapInsetsresizingMode:
更加強大.它可以讓你從平鋪或拉伸區(qū)域分別指定結(jié)束的caps
,而剩下的部分將會被切掉。如下圖所示:
在上圖中左上角,右上角士骤,左下角和右下角的暗色區(qū)域?qū)⒃瓨永L制。窄帶將被拉長添忘,頂部中心的小矩形將被拉伸以填充大部分的區(qū)域。但是圖像的其余部分若锁,被紗布幕覆蓋的中央大片區(qū)域搁骑,將被完全省略。結(jié)果如下圖:
Image Rendering Mode
iOS應(yīng)用的用戶界面在某些地方會自動將圖像作為一個透明遮罩(transparency mask
)又固,也被稱為template
。這意味著圖像的每個像素的透明度(alpha
)會起作用口予,而顏色值將被忽略娄周。在屏幕上顯示的圖像是由圖像的透明度值和一個純色(tint color
)混合而成。tab bar item
的圖片顯示就是這種方式沪停。
圖像被怎樣顯示是通過圖像的只讀renderingMode
的屬性控制的;只能通過在圖像上調(diào)用imageWithRenderingMode:
來生成一個新的圖像來更改rendering mode
值.渲染模式的值(UIImageRenderingMode)為:
- .Automatic
- .AlwaysOriginal
- .AlwaysTemplate
默認(rèn)值是.Automatic
煤辨。這意味在某些被限制的上下文中它被用作透明遮罩(transparency mask
)外,大部分情況都會被原樣繪制出來木张。
通過設(shè)置renderingMod
屬性众辨,你可以強制正常繪制圖像,即使在一個把它當(dāng)作一個透明遮罩(transparency mask
)的上下文中舷礼。相反的:你可以強制繪制圖像為透明度遮罩(transparency mask
)鹃彻,即使在正常的上下文中。
為了實現(xiàn)這個功能妻献,iOS
給每個UIView
一個tintColor
屬性蛛株,這將對它包含的任何模板圖像進(jìn)行著色。此外育拨,tintColor
默認(rèn)是從應(yīng)用程序的最上面window
一直貫穿整個視圖層級而繼承的谨履。因此,更改應(yīng)用程序的主window
的tintColor
可能是你僅有的能對window
所做的改變之一;否則熬丧,你的應(yīng)用程序?qū)⒉捎孟到y(tǒng)的藍(lán)色的tint color
笋粟。 (如果你使用的是main storyboard
,可以在文件檢查器中設(shè)置Global Tint color
的顏色析蝴。)可以設(shè)置個別view
的tint color
, 這會被他的子視圖繼承害捕。下圖顯示了一個應(yīng)用中主window
的tintcolor
是紅色的,顯示相同的背景圖片的兩個按鈕闷畸,一個正常渲染尝盼,另一個以模板方式渲染:
在asset catalog
中可以設(shè)置圖像的渲染模式。在asset catalog
中選擇圖片腾啥,在屬性檢查器中使用彈出的Render
菜單設(shè)置渲染模式為默認(rèn)值(.Automatic
)东涡,原始圖像(.AlwaysOriginal
)冯吓,或模版圖像(.AlwaysTemplate
) 。這是一個很好的辦法疮跑,這使你每次使用相同的渲染模式設(shè)置圖片時少寫很多代碼组贺。而且,當(dāng)調(diào)用init(named:)
祖娘,會返回已經(jīng)設(shè)置好渲染模式的圖片失尖。
Reversible Images
新的iOS9
系統(tǒng)中,當(dāng)你的app是在一個本地語言是從右到左的系統(tǒng)上運行的時候渐苏,整個用戶界面會自動改變翻轉(zhuǎn)(改變方向)掀潮。一般情況下,這不會影響到你的圖片琼富。runtime
會假定你不想反轉(zhuǎn)圖片當(dāng)用戶界面反轉(zhuǎn)的時候仪吧,因此其默認(rèn)行為是讓他們保持原樣。
不過鞠眉,你可能希望圖片隨著系統(tǒng)一起反轉(zhuǎn)薯鼠。例如,可能你已經(jīng)繪制好一個當(dāng)用戶點擊按鈕后指向新界面出現(xiàn)的方向的箭頭械蹋。如果按鈕作用是在導(dǎo)航界面進(jìn)入一個新的視圖控制器出皇,在從左往右的系統(tǒng)圖片箭頭方向指向右邊,但是在從右往左的系統(tǒng)上圖片箭頭方向應(yīng)該指向左邊哗戈。此圖像在應(yīng)用程序的界面上具有方向的信息;因此它需要水平翻轉(zhuǎn)當(dāng)界面反轉(zhuǎn)時郊艘。
通過調(diào)用圖像的imageFlippedForRightToLeftLayoutDirection
,并在界面使用新生成的圖像來達(dá)到上面的效果唯咬。在左到右系統(tǒng)中纱注,正常的圖像將被使用;而在右到左的系統(tǒng)上時,將會自動創(chuàng)建并使用對應(yīng)的相反的圖片副渴。您可以重寫此行為奈附,即使圖像是可反轉(zhuǎn)的。對于UIView
煮剧,例如UIImageView
,通過設(shè)置view
的semanticContentAttribute
防止圖片鏡像将鸵。
不幸的是勉盅,在asset catalog
中還沒有辦法指定圖片是可反轉(zhuǎn)的。因此顶掉,在界面中出現(xiàn)的圖片 - 例如,在nib
中配置的UIImageView
- 你必須使用代碼去控制草娜。在下面這個例子中,我在視圖控制器的viewDidLoad
方法中取出圖像視圖(self.iv
)的圖片痒筒,并用它自身的可反轉(zhuǎn)版本替換它自己:
override func viewDidLoad() {
super.viewDidLoad()
self.iv.image = self.iv.image?.imageFlippedForRightToLeftLayoutDirection()
}
Graphics Contexts
你可能想使用代碼繪制一些圖像宰闰,而不是僅僅使用已經(jīng)存在的圖片文件茬贵。要實現(xiàn)這一點,你需要一個圖形上下文(graphics context
)移袍。
圖形上下文就是你可以繪制圖像的地方解藻。反之,你不能用代碼繪制葡盗,除非你有一個圖形上下文螟左。有幾種方法可以得到一個圖形上下文;我將主要介紹兩種已經(jīng)被我的經(jīng)驗證明是最常見的方式:
-
You create an image context
UIGraphicsBeginImageContextWithOptions
函數(shù)會創(chuàng)建一個適合用于繪制圖像的上下文。你可以繪制到此上下文并生成圖像觅够。當(dāng)這樣做之后胶背,調(diào)用UIGraphicsGetImageFromCurrentImageContext
把上下文變成一個UIImage
,然后調(diào)用UIGraphicsEndImageContext
清除上下文〈龋現(xiàn)在你有了一個圖片钳吟,你可以讓它顯示到界面上或者繪制到一些其他的圖形上下文或者保存為一個文件。 -
Cocoa hands you a graphics context
你子類化UIView
然后實現(xiàn)drawRect:
方法.當(dāng)你的drawRect:
方法被調(diào)用的時候窘拯,Cocoa
同時會為你創(chuàng)建一個圖形上下文砸抛,并要求你在里面繪制;不管你畫什么UIView
都會顯示出來。
這種情況有一種輕微的變體是你繼承CALayer
并在layer
的delegate
里面實現(xiàn)drawInContext:
,或?qū)崿F(xiàn)drawLayer:inContext
树枫。
此外直焙,在任何時刻圖形上下文要么是或不是當(dāng)前的圖形上下文:
- UIGraphicsBeginImageContextWithOptions不僅創(chuàng)造了一個圖形上下文,這也使得上下文成為當(dāng)前的圖形上下文砂轻。
- 當(dāng)
drawRect:
被調(diào)用時奔誓,UIView
的圖形上下文已經(jīng)是當(dāng)前的圖形上下文。 - 有一個
context:
參數(shù)的回調(diào)函數(shù)并不會使任何上下文變成當(dāng)前的圖形上下文;相反搔涝,該參數(shù)是一個想要你在里面繪制的圖形上下文的引用厨喂,但是只有它稱為當(dāng)前的圖形上下文你才能在它里面繪制,是否這么做取決于你庄呈。
初學(xué)者對于繪制感到困惑的原因是有兩套關(guān)于繪制圖像的工具蜕煌,但是它們對于繪制圖像時的context
的處理方式一點都不相同。一組需要一個當(dāng)前上下文;另一組只是需要一個上下文:
-
UIKit
很多Cocoa
類知道如何繪制它們自己;其中包括UIImage
诬留,NSString
(繪制文本)斜纪,UIBezierPath
(用于繪制形狀),和UIColor
文兑。有些類提供能力有限但是方便的方法;有些類則非常強大盒刚。在許多情況下,UIKit
會滿足你所以的需求绿贞。
你只能繪制到當(dāng)前圖形上下文中當(dāng)你使用UIKit
時因块。所以,如果你在一個UIGraphicsBeginImageContextWithOptions
或drawRect:
情況下籍铁,你可以直接使用UIKit
便利的方法;而且會有一個當(dāng)前的圖形上下文涡上,它就是你想在里面繪制的上下文趾断。另外,如果你有一個context:
的參數(shù)吩愧,而且你還想使用UIKit
便利的方法芋酌,你只能把上下文變成當(dāng)前的圖形上下文;通過調(diào)用UIGraphicsPushContext
做到這一點(并確保用UIGraphicsPopContext
來還原)。 -
Core Graphics
這是一個完整的繪圖API耻警。Core Graphics
隔嫡,通常被稱為Quartz
,或Quartz 2D
,是iOS繪圖系統(tǒng)的基礎(chǔ) - UIKit的繪制是建立在它之上 - 所以它是低層次的甘穿,包含很多C函數(shù)腮恩。
使用Core Graphics
繪制時,你必須在每個函數(shù)調(diào)用中明確的指定將要繪制的圖形上下文温兼。如果你你有一個context:
參數(shù)秸滴,這可能是你想要繪制的圖形上下文。在UIKit
中你不需要一個context
的引用募判,但是在Core Graphic
中你需要一個context
的引用荡含。既然你在當(dāng)前的圖形上下文里面繪制,通過調(diào)用UIGraphicsGetCurrentContext
來得到當(dāng)前圖形上下文的引用届垫。
你不必分開使用
UIKit
或Core graphics
误债。相反寝蹈,你可以在同一個代碼塊中使用UIKit和Core graphics
操作相同的圖形上下文黔州。他們只是控制圖形上下文繪制的兩種不同方式。
因此项贺,在一個圖形上下文中我們有兩套工具和三種方法林螃,總共六種方式來繪圖横漏。在下面的例子中赴肚,我將說明這六種方法。首先,我將通過繼承UIView然后實現(xiàn)drawRect:方法,在UIKit為我們準(zhǔn)備好的當(dāng)前圖形上下文中畫一個藍(lán)色圓圈:
override func drawRect(rect: GGRect) {
let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
UIColor.blueColor().setFill()
p.fill()
}
下面我將會用Core graphics
實現(xiàn)相同的功能,但是得先獲得當(dāng)前圖形上下文得引用:
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
CGContextFillPath(context)
}
接下來我會實現(xiàn)一個UIView子類的drawLayer:inContext:方法刻像。在這個例子中,會有一個context的引用,但是它并不是當(dāng)前的圖形上下文蹂季,所以為了使用UIKit父能,我得先讓它成為當(dāng)前得圖形上下文:
override func drawLayer(layer: CALayer, inContext context: CGContext) {
UIGraphicsPushContext(context)
let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
UIColor.blueColor().setFill()
p.fill()
UIGraphicsPopContext()
}
然后我引用以下傳進(jìn)來得context就可以在drawLayer:inContext
中使用Core graphics
:
override func drawLayer(layer, inContext context: CGContext) {
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
CGContextFillPath(context)
}
最后,我將做一個藍(lán)色圓圈得圖片。我們可以在任何時候(我們不需要等待某些特定方法被調(diào)用)跪者,并在任何類(我們不需要繼承UIView)中實現(xiàn)此功能棵帽。由此產(chǎn)生的UIImage
適用于可以使用UIImage
的任何地方。例如铅搓,你可以把它賦值給UIImageView
的image
屬性星掰,從而導(dǎo)致圖像出現(xiàn)在屏幕上像街±杼模或者你可以將其保存為一個文件。
首先我會使用UIKit來繪制我的圖片:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
let p = UIBezierPath(ovalInRect: CGRectMake(0, 0, 100, 100))
UIColor.blueColor().setFill()
p.fill()
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
下面是使用Core Graphics
實現(xiàn)相同的功能:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0)
let context = UIGraphicsGetCurrentContext()!
CGContextAddEllipseInRect(context, CGRectMake(0, 0, 100, 100))
CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)
CGContextFillPath(context)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
你可能想知道UIGraphicsBeginImageContextWithOptions
各項參數(shù)的意義畴栖。第一個參數(shù)很明顯是要創(chuàng)建圖片的尺寸随静。第二個參數(shù)聲明圖像是不是應(yīng)該透明;如果這個參數(shù)為true
而不是false
,繪制的圖片將有一個黑色的背景吗讶,這并不是我想要的效果燎猛。第三個參數(shù)指定圖像的縮放系數(shù);通過傳遞0,告訴系統(tǒng)根據(jù)主屏幕來設(shè)置圖片的縮放系數(shù)照皆,所以我的圖片在單分辨率和高分辨率的設(shè)備上都會顯示很好重绷。
UIImage Drawing
UIImage
提供繪制自身到當(dāng)前的圖形上下文的方法。我們知道如何獲得UIImage
膜毁,我們知道如何獲得圖形上下文并使其成為當(dāng)前的圖形上下文昭卓,所以我們可以嘗試使用這些方法來繪制圖片。
下面瘟滨,我會做出一張由兩張火星圖片并排組成的新圖片:
let mars = UIImage(named: "Mars")!
let size = mars.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 2, size.height * 2), false, 0.0)
mars.drawAtPoint(CGPointMake(0, 0))
mars.drawAtPoint(CGPointMake(size.width, 0))
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
效果如下:
在上面的例子中圖片的縮放控制的很好候醒。如果有原始的火星圖像的多個分辨率版本,系統(tǒng)會根據(jù)當(dāng)前設(shè)備選擇正確的圖片杂瘸,并指定正確的scale
值倒淫。調(diào)用UIGraphicsBeginImageContextWithOptions
函數(shù)的第三個參數(shù)為0
,因此在圖像上下文中繪制的圖片也會有正確的scale
值胧沫。而且從UIGraphicsGetImageFromCurrentImageContext
得到的圖像也會有正確的scale
值昌简。因此,上面的代碼生成的圖片在當(dāng)前設(shè)備上看起來顯示效果很好绒怨,無論它的屏幕分辨率是多少纯赎。
UIImage
的其他一些方法,可以讓你在需要繪制的矩形中指定縮放系數(shù)南蹂,并指定和矩形中已經(jīng)存在的東西的混合模式犬金。為了說明這一點,我將創(chuàng)建一個兩倍大的火星圖片顯示在另一個火星圖片的中心,使用.Multiply
混合模式:
let mars = UIImage(named: "Mars")!
let size = mras.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 2, size.height * 2), false, 0)
mars.drawInRect(CGRectMake(0, 0, size.width * 2, size.height * 2))
mars.drawInRect(CGRectMake(sizw.width / 2, size.height / 2, size.width, size.height),
blendMode: .Multiply,
alpha: 1.0)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
效果如下圖:
UIImage
的繪制方法沒有辦法指定原始矩形-你要提取原始圖像的某已小塊區(qū)域晚顷。您可以通過創(chuàng)建一個較小的圖形上下文然后把圖片的指定區(qū)域繪制到里面峰伙。例如,為了獲得火星的右半部分的圖像该默,你可以創(chuàng)建一個只有火星圖像寬度一半的圖形上下文瞳氓,然后左移繪制火星,這樣火星圖片只有右半部分在圖形上下文中栓袖。這樣做沒有什么壞處匣摘,這個一種非常標(biāo)準(zhǔn)的策略來獲取原始圖片的某部分區(qū)域。代碼如下:
let mars = UIImage(named: "Mars")!
let size = mars.size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width / 2.0, size.height), false, 0.0)
mars.drawAtPoint(CGPointMake(-size.width / 2.0, 0))
let im = UIGraphicsGetImageFromCurrenImageContext()
UIGraphicsEndImageContext()
效果如下圖:
CGImage Drawing
UIImage
的Core graphics
的版本是CGImage
裹刮。他們很容易相互轉(zhuǎn)換:UIImage
有一個訪問其Quartz
圖像數(shù)據(jù)的CGImage
屬性音榜,你可以在CGImage
上調(diào)用init(CGImage:)
或更容易配置的init(CGImage:scale:orientation:)
方法來得到一個UIImage。
CGImage
允許你直接從原始的UIImage
的某個矩形區(qū)域創(chuàng)建一個新的圖片捧弃,而UIImage
不能這么做赠叼。(CGImage
還有其他UIImage
不具備的能力;例如,您可以為CGImage
應(yīng)用一個圖片遮罩(image mask
)违霞。)我會將火星圖片分為兩半然后分開繪制來說明:
let mars = UIImage(named: "Mars")!
let marsCG = mars.CGImage
let size = mars.size
let marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0, 0, size.width / 2.0, size.height))
let marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(size.width / 2.0, 0, size.width / 2.0, size.height))
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width * 1.5, size.height * 1.5), false, 0)
let context = UIGraphicsGetCurrentContext()!
CGContextDrawImage(context, CGRectMake(0, 0, size.width / 2, size.height), marsLeft)
CGContextDrawImage(context, CGRectMake(sizw.width, 0, size.width / 2.0, size.height), marsRight)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
效果如下:
但是嘴办,上面例子有個問題:繪圖是上下顛倒的!代碼沒有對它執(zhí)行翻轉(zhuǎn);但是它是上下鏡像的葛家,或使用專業(yè)的技術(shù)術(shù)語:flipped
户辞。當(dāng)你創(chuàng)建一個CGImage
,然后用CGContextDrawImage
繪制它的時候就會出現(xiàn)這種情況癞谒,這是由于原始的上下文和目標(biāo)上下文的坐標(biāo)系統(tǒng)不匹配造成的底燎。
有很多種方式來修復(fù)不同坐標(biāo)系統(tǒng)之間的不匹配。其中一種是繪制CGImage
到一個中間的UIImage
然后從中間UIImage中提取出另一個CGImage弹砚。下面的例子通過一個函數(shù)來實現(xiàn)這個功能:
func flip(im: CGImage) -> CGImage {
let size = CGSizeMake(
CGFloat(CGImageGetWidth(im),
CGFloat(CGImageGetHeight(im))))
UIGraphicsBeginImageContextWithOptions(size, false, 0)
let context = UIGraphicsGetCurrentContext()!
CGContextDrawImage(context,
CGRectMake(0, 0, size.width, size.height),
im)
let result = UIGraphicsGetImageFromCurrentImageContext().CGImage
UIGraphicsEndImageContext()
return result!
}
使用上面那個便利的函數(shù)双仍,我們可以修復(fù)上面例子中使用CGContextDrawImage
繪制兩半火星的正確方法了:
CGContextDrawImage(context,
CGRectMake(0, 0, size.width / 2.0, size.height),
flip(marsLeft!))
CGContextDrawImage(context,
CGRectMake(size.width, 0, size.width / 2.0, sizw.height),
flip(marsRight!))
但是,我們還有一個問題:一個高分辨率的設(shè)備上桌吃,如果我們的圖片有一個高分辨率的版本朱沃,那么繪制就會出錯。原因是我們使用UIImage
的init(named:)
方法獲取的火星圖片茅诱,在高分辨率的設(shè)備上它返回一個更大尺寸的火星圖像逗物,通過設(shè)置圖片的scale
屬性來匹配原始火星圖片的大小。但CGImage
并沒有scale
屬性瑟俭,而且也不知道圖片的尺寸增加了翎卓!因此,在一個高分辨率的設(shè)備上摆寄,我們從火星圖片中提取的mars.CGImage
比mars.size
的尺寸要大失暴,并且之后所有的計算都是錯誤的坯门。
處理CGImage
最好的解決辦法是,把它封裝為一個UIImage
并繪制這個UIImage
而不是直接繪制CGImage
逗扒。從CGImage
生成UIImage
時可以調(diào)用init(CGImage:scale:orientation:)
來避免圖片的縮放古戴。而且直接繪制UIImage
而不是CGImage
也避免翻轉(zhuǎn)問題!下面是不使用flip
函數(shù)來處理翻轉(zhuǎn)和縮放的方法:
let mars = UIImage(named: "Mars")!
let size = mars.size
let marsCG = mars.CGImage
let sizeCG = CGSizeMake(
CGFloat(CGImageGetWidth(marsCG),
CGFloat(CGImageGetHeight(marsCG))))
let marsLeft = CGImageCreateWithImageInRect(
marsCG,
CGRectMake(0, 0, sizeCG.width / 2, sizeCG.height))
let marsRight = CGImageCreateWithImageInRect(
marsCG,
CGRectMake(sizeCG.width / 2.0, 0, sizeCG.width / 2.0, sizeCG.height))
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(size.width * 1.5, size.height),
false,
0.0)
//instead of calling flip, pass through UIImage
UIImage(CGImage: marsLeft!,
scale: mars.scale,
orientation: mars.imageOrientation)
.drawAtPoint(CGPointMake(0, 0))
UIImage(CGImage: marsRight!,
scale: mars.scale,
orientation: mars.imageOrientation)
.drawAtPoint(CGPointMake(sizw.width, 0))
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
翻轉(zhuǎn)發(fā)生的原因 |
---|
意外翻轉(zhuǎn)發(fā)生的原因是Core graphics 移植于OS X 矩肩,OS X 的坐標(biāo)系的原點默認(rèn)情況下位于左下角和y軸增長方向是向上的现恼,而iOS上的原點默認(rèn)情況下在位于左上角并且y軸正方向是向下的。在大多數(shù)繪圖的情況下蛮拔,因為圖形上下文的坐標(biāo)系統(tǒng)被自動調(diào)整述暂,所以這不會有什么問題。因此在iOS中使用Core Graphics 繪制時上下文中的坐標(biāo)系統(tǒng)的原點在左上角,這和你期望的是一樣的建炫。但是,創(chuàng)建和繪制CGImage 暴露了兩個坐標(biāo)世界之間的不兼容 |
另一個解決方案是疼蛾,繪制CGImage
之前對圖形上下文做一個變換(transform
),直接翻轉(zhuǎn)上下文的內(nèi)部坐標(biāo)系統(tǒng)肛跌。這是非常有效的,但是如果已經(jīng)存在其他變換察郁,這會非逞苌鳎混亂。
Snapshots
整個視圖 -- 從任何一個單一的按鈕到你整個界面包含視圖整個層次結(jié)構(gòu)--可通過調(diào)用UIView
的實例方法drawViewHierarchyInRect:afterScreenUpdates:
可以被繪制到當(dāng)前的圖形上下文中. (此方法比CALayer
的renderInContext:
速度快很多;不過皮钠,renderInContext:
確實很方便)稳捆。繪制的結(jié)果是原始視圖的快照(snapshot
):它看起來像原來的視圖,但它本質(zhì)上只是原來視圖的一個位圖圖像麦轰。
獲得視圖的快照更快的方法是使用的UIView
(或UIScreen
)的實例方法snapshotViewAfterScreenUpdates:
.結(jié)果是一個UIView
乔夯,而不是一個UIImage
;這很像一個只知道如何繪制一個圖像--即快照--的UIImageView
。這樣的快照視圖通常會被用作擴大其邊界和拉伸其圖像款侵。如果你想拉伸快照讓它表現(xiàn)得像一個可調(diào)整大小的圖像末荐,調(diào)用resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:
。
Snapshots
非常有用因為IOS
界面的動態(tài)性質(zhì)新锈。例如甲脏,你可以在真正的視圖上面放置一個快照從而隱藏真正的視圖上正在發(fā)生的事情,或者在動畫中移動快照而不是真正的視圖妹笆。
下面是我的應(yīng)用程序中的一個例子块请。這是一個紙牌游戲,它的視圖都是紙牌拳缠。我想用動畫把所有的紙牌從屏幕里面移動到屏幕外面墩新。但我不想移動那些視圖本身!他們需要留在原地脊凰,繪制以后出現(xiàn)的紙牌抖棘。所以我為每個紙牌視圖做了一個快照視圖;然后茂腥,我隱藏真正的紙牌視圖,而把快照視圖放在它們的位置上切省,然后用動畫移除這些快照最岗。代碼如下:
for v in views {
let snapshot = v.snapshotViewAfterScreenUpdates(false)
let snap = MySnapBehavior(item: snapshot,
snapToPoint: CGPointMake(
self.anim.referenceView!.bounds.midX,
-self.anim.referenceView!.bounds.height))
self.snaps.append(snapshot)
snapshot.frame = v.frame
v.hidden = ture
self.anim.referenceView!.addSubview(snapshot)
self.anim.addBehavior(snap)
}
CIFilter and CIImage
CIFilter
和CIImage
中的CI
代表Core Image
,通過數(shù)學(xué)變換轉(zhuǎn)化圖像的技術(shù)朝捆。Core Image
首先在桌面(OS X
)環(huán)境中使用般渡,并且當(dāng)它被遷移到iOS
系統(tǒng)時,一些在桌面上可用的濾鏡無法在IOS
上使用(大概是因為對于移動設(shè)備來說這么數(shù)學(xué)運算太過于復(fù)雜)芙盘。多年來越來越多的OS X
濾鏡被添加到iOS
上驯用,而現(xiàn)在在新的iOS9
系統(tǒng)中,兩者沒有差別:所有OS X
濾鏡在iOS
中都是可用的儒老,并且兩個平臺都具有幾乎相同的API 蝴乔。
一個CIFilter就是一個濾鏡⊥苑可用的濾鏡可以分為幾大類:
-
Patterns and gradients
這些濾鏡創(chuàng)建的CIImage
可以與其他的CIImage
薇正,諸如單一的顏色,顏色盤,條紋囚衔,或梯度組合挖腰。 -
Compositing
這些濾鏡使用圖像處理軟件如Photoshop的混合模式來組合圖片。 -
Color
這些濾鏡調(diào)整或改變圖像的顏色练湿。因此猴仑,你可以改變圖像的飽和度,色調(diào)肥哎,亮度辽俗,對比度,伽瑪值和白平衡贤姆,曝光榆苞,陰影和高光,等等霞捡。 -
Geometric
這些濾鏡對圖片執(zhí)行基本的幾何變換坐漏,如縮放,旋轉(zhuǎn)和裁剪碧信。 -
Transformation
這些濾鏡對圖片進(jìn)行變形赊琳,模糊,或風(fēng)格化砰碴。 -
Transition
這些濾鏡提供了一個圖像和另一個之間轉(zhuǎn)換的幀;通過順序請求幀躏筏,可以設(shè)置過渡動畫效果。 -
Special purpose
這些濾鏡執(zhí)行高度專業(yè)化的操作呈枉,如人臉檢測和生成的QR碼趁尼。
CIFilter
的基本用法相當(dāng)簡單︰
- 您可以通過提供濾鏡的字符串名稱來指定想使用的濾鏡;想知道都有哪些名字埃碱,查閱
Core Image Filter Reference
,或調(diào)用CIFilter
的類方法filterNamesInCategories:
并提供一個nil
參數(shù)酥泞。 - 每個濾鏡具有少量的鍵值對確定其行為砚殿。您可以通過代碼了解全部的鍵,但通常你會參考文檔芝囤。對于你感興趣的每個鍵似炎,你提供的一個鍵 - 值對。在提供值中悯姊,數(shù)字必須被包裝成
NSNumber
羡藐,并且其他有用的類,如CIVector
和CIColor
悯许,其使用很容易理解氯析。
CIFilter
鍵中輸入的圖像或被濾鏡操作的圖片必須是CIImage
活箕“吡唬可以從CGImage
中獲取CIImage
通過調(diào)用init(CGImage:)
或者從UIImage
中獲取CIImage
通過調(diào)用init(image:)
念祭。
不要嘗試直接從
UIImage
的CIImage
屬性獲取CIImage
。此屬性不會把UIImage
轉(zhuǎn)換成CIImage
启上!它僅僅只是指向支持(back
)UIImage
的CIImage
,如果這個UIImage
真的是被CIImage
支持(back
)的;但是有可能你的圖片不是由CIImage
支持的店印,而是由一個CGImage
冈在。
然而你可以從濾鏡的輸出中獲取CIImage--這意味著濾鏡可以鏈?zhǔn)秸{(diào)用。
有三種方式來描述和使用濾鏡:
- 通過
CIFilter
的init(name:)
創(chuàng)建濾鏡按摘。通過重復(fù)調(diào)用setValue:forKey:
方法或者通過調(diào)用setValuesForKeysWith- Dictionary:
來為濾鏡添加鍵值對包券。通過濾鏡的outputImage
獲取CIImage
。 - 通過調(diào)用
CIFilter
的init(name:withInputParameters:)
并提供鍵值對創(chuàng)建濾鏡炫贤。通過濾鏡的outputImage
獲取CIImage
溅固。 - 如果
CIFilter
需要輸入圖像而且你已經(jīng)有了一個CIImage
,可以通過調(diào)用CIImage
實例方法imageByApplyingFilter:withInputParameters:
并提供濾鏡和鍵值對兰珍,然后獲取輸出的CIImage
侍郭。
當(dāng)你構(gòu)建一個濾鏡鏈時,實際上什么都沒有發(fā)生掠河。唯一的密集計算會發(fā)生在最后亮元,當(dāng)你變換濾鏡鏈輸出的CIImage
為位圖圖形時。這就是所謂的圖像渲染唠摹。有兩種主要方式的方式實現(xiàn)這一點:
-
With a CIContext
通過調(diào)用init(options:)
創(chuàng)建一個CIContext
爆捞。然后把最后的CIImage
作為第一個參數(shù)傳遞給createCGImage:fromRect:
去處理。這會渲染圖像勾拉。這種方式有點輕微棘手的事情是煮甥,CIImage
不具有frame
或bounds
;它只有一個extent
盗温。你會經(jīng)常使用它作為createCGImage:fromRect:
的第二個參數(shù).最終輸出的CGImage
可以隨便使用,例如在應(yīng)用程序的顯示成肘,或者轉(zhuǎn)換為一個UIImage
卖局,或者進(jìn)行進(jìn)一步的繪制。
這種方法具有一個優(yōu)點是當(dāng)渲染發(fā)生時你有全部的控制權(quán)艇劫。但是要注意:創(chuàng)建一個CIContext
是非常昂貴的吼驶!最好的方式是,事先只創(chuàng)建CIContext
一次--整個app
中--店煞, 并在每次渲染時重用它蟹演。 -
With a UIImage
在最后的CIImage
上調(diào)用的init(CIImage:)
或init(CIImage:scale:orientation:)
直接創(chuàng)建一個UIImage
。然后顷蟀,可以把UIImage
繪制到某些圖形上下文中酒请。在繪制發(fā)生的時候,圖像就被渲染了鸣个。
蘋果聲稱羞反,你可以簡單地調(diào)用
init(CIImage:)
創(chuàng)建一個UIImage然后把它賦值給UIImageView的image屬性,那么UIImageView會渲染這個圖像。根據(jù)我的經(jīng)驗囤萤,這是不正確的昼窗。為了渲染它你必須顯式的繪制它。
為了說明這一點涛舍,我會用我自己的一張普通的照片創(chuàng)建一個圓形的帶陰影效果的圖片澄惊。我們會先從圖片中導(dǎo)出CIImage。我們對圖片運用一個白色到黑色的徑向漸變的濾鏡富雅。然后掸驱,我們使用第二個濾鏡,這個濾鏡把上面的逕向漸變當(dāng)做一個遮罩來把我的照片和一個透明背景色混合:其中徑向漸變?yōu)榘咨u變的內(nèi)半徑)的部分没佑,只會看到我的照片毕贼,徑向漸變是黑色(漸變的外半徑)的部分,我們只會看到透明的顏色蛤奢,在它們中間的環(huán)形漸變區(qū)域內(nèi)圖片漸漸消失鬼癣。下面的代碼用兩種方式來設(shè)置濾鏡:
let moi = UIImage(named: "Moi")!
let moiCI = CIImage(image: moi)!
let moiextent = moiCI.extent
let center = CIVector(x: moiextent.width / 2.0, y: moiextent.height / 2.0)
let smallerDimension = min(moiextent.width, moiextent.height)
let largerDimension = max(moiextent.width, moiextent.height)
//first filter
let grad = CIFilter(name: "CIRadialGradient")!
grad.setValue(center, forKey: "inputCenter")
grad.setValue(smallerDimension / 2.0 * 0.85, forKey: "inputRadius0")
grad.setValue(largerDimension / 2.0, forKey: "inputRadius1")
let gradimage = grad.outputImage!
//second filter
let blendImage = moiCI.imageByApplyingFilter("CIBlendWithMask", withInputParameters: ["inputMaskImage": gradImage])
現(xiàn)在我們在濾鏡鏈的最后得到了最終的CIImage
(blendimage
);注意:此時處理器還沒有進(jìn)行任何渲染。現(xiàn)在我們想要生成最終的位圖并顯示它远剩。例如,我們可以通過UIImageView
來顯示它扣溺。有兩種不同的方法可以做到這一點。我們可以通過調(diào)用CIContext(options:nil)
把CIImage
傳給我們已經(jīng)事先準(zhǔn)備好的CIContext
(self.context
)來創(chuàng)建一個CGImage
:
let moiCG = self.context.createCGImage(blendImage, fromRect: moiextent)
self.iv.image = UIImage(CGImage: moiCG)
另外瓜晤,我們可以把濾鏡鏈最后輸出的CIImage
作為一個UIImage
然后把它繪制成一個位圖:
UIGraphicsBeginImageContextWithOptions(moiextent.size, false, 0)
UIImage(CIImage: blendImage).drawInRect(moiextent)
let im = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.iv.image = im
效果如下:
通過繼承CIFilter
可以把濾鏡鏈封裝成一個自定義濾鏡锥余。子類只需要重寫outputImage
屬性(以及可能的其他方法,如setdefaults
)痢掠,并使用額外的屬性使之成為鍵值編碼兼容的對于任何輸入的鍵.驱犹。下面是我們的陰影濾鏡一個簡單的CIFilter子類嘲恍,其中一個輸入的鍵是指定輸入的圖像,另一個輸入鍵是調(diào)整漸變的小半徑的百分比:
class MyVignetterFilter: CIFilter {
var inputImage: CIImage?
var inputPercentage: NSNumber? = 1.0
override var outputImage: CIImage? {
return self.makeOutputImage()
}
private func makeOutputImage() -> CIImage? {
guard let inputImage = self.inputImage
else {return nil}
guard let inputPercentage = self.inputPercentage
else {return nil}
let extent = inputImage.extent
let grad = CIFilter(name: "CIRadialGradient")!
let center = CIVector(x: extent.width / 2.0, y: extent.height / 2.0)
let smallerDimension = min(extent.width, extent.height)
let largerDimension = max(extent.width, extent.height)
grad.setValue(center, forKey: "inputCenter")
grad.setValue(smallerDimension / 2.0 * CGFloat(inputPercentage), forKey: "inputRadius0")
grad.setValue(largerDimension / 2.0, forKey: "inputRadius1")
let blend = CIFilter(name: "CIBlendWithMask")!
blend.setValue(inputImage, forKey: "inputImage")
blend.setValue(grad.outputImage, forKey: "inputMaskImage")
return blend.outputImage
}
}
下面是如何使用我們的CIFilter
子類并顯示其輸出的一個例子:
let vig = MyVignetterFilter()
let moiCI = CIImage(image: UIImage(named: "Moi")!)!
vig.setValuesForKeysWithDictionary([
"inputImage": moiCI,
"inputPercentage": 0.7
])
let outim = vig.outputImage!
let outimCG = self.context.createCGImage(outim, formRect: outim.extent)
self.iv.image = UIImage(CGImage: outimCG)
Blur and Vibrancy Views
iOS上的某些視圖雄驹,例如導(dǎo)航欄和控制中心會顯示一種半透明的模糊過渡效果佃牛。iOS
提供了UIVisualEffectView
類來幫助你實現(xiàn)這種效果。你可以把其他視圖放到UIVisualEffectView
前面医舆,但是任何子視圖應(yīng)該放在contentView
里面俘侠。通過設(shè)置contentView
的backgroundColor
來實現(xiàn)模糊的效果。
使用init(effect:)
來創(chuàng)建UIVisualEffectView
并使用它;effect:
參數(shù)是一個UIVisualEffect子類的實例:
-
UIBlurEffect
調(diào)用init(style:)
來創(chuàng)建一個UIBlurEffect
;style
參數(shù)(UIBlurEffectStyle
)是.Dark
蔬将,.Light
和.ExtraLight
爷速。(.ExtraLight
特別適合于一小塊界面,例如一個導(dǎo)航欄或工具欄霞怀。):
let fuzzy = UIVisualEffectView(effect: (UIBlurEffect(style: .Light)))
-
UIVibrancyEffect
調(diào)用init(forBlurEffect:)
來初始化UIVibrancyEffect
惫东。Vibrancy
讓視圖和它地下的模糊效果相協(xié)調(diào)。這樣做的目的是毙石,高亮效果視圖應(yīng)該在一個模糊效果視圖的前面廉沮,一般在單一的UIView
的contentView
里面添加一個高亮效果;告訴高亮效果的下面是什么模糊效果,它們會自己協(xié)調(diào)徐矩。您可以獲取的視圖的模糊效果作為其effect
屬性滞时,但是這是一個UIVisualEffec
t類,所以你必須要轉(zhuǎn)換為UIBlurEffect
才能把它傳遞給init(forBlurEffect:)
。
下面是一個模糊效果并覆蓋整個視圖的視圖滤灯,而且包含一個含有UILabel的高亮視圖:
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .ExtraLight))
blur.frame = mainview.bounds
blur.autoresizingMasks = [.Flexiblewidth, .FlexibleHeight]
let vib = UIVisualEffectView(effect: UIVibrancyEffect(forBlurEffect: blur.effect as! UIBlurEffect))
let lab = UILabel()
lab.text = "Hello, world!"
lab.sizeToFit()
vib.frame = lab.frame
vib.frame = lab.frame
vib.contentView.addSubview(lab)
vib.center = CGPointMake(blur.bounds.midX, blur.bounds.midY)
vib.autoresizingMask = [.FlexibleToMargin, .FlexibleBottomMargin, .FlexibleLeftMargin, .FlexibleRightMargin]
blur.contextView.addSubview(vib)
mainview.addSubview(blur)
效果如下圖:
蘋果似乎認(rèn)為高亮使視圖與模糊底層結(jié)合時更加清晰漂洋,但我不這么認(rèn)為。高亮的視圖的顏色和會模糊的顏色綜合但這會是高亮的視圖更加不清晰力喷。上圖中使用.Dark
或.ExtraLight
模糊效果時label
看起來還可以,但是使用.Light
就很難看清了演训。
在UIVisualEffectView.h頭文件中有很多值得研究的額外信息弟孟。例如,為了高亮圖片視圖必須使用模版(template
)圖片样悟。
在nib編輯器中都可以直接使用模糊和高亮視圖拂募。
==未完待續(xù)