中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

從iOS的圖片圓角想到渲染

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

圓角是一種很常見的視圖效果,相比于直角,它更加柔和優(yōu)美,易于接受。設(shè)置圓角會(huì)帶來一定的性能損耗,如何提高性能是一個(gè)需要重點(diǎn)討論的話題。

大家常見的圓角代碼 x.layer.cornerRadius = xx; x.clipsToBounds = YES; 這兩行確實(shí)實(shí)現(xiàn)了圓角視覺效果。其實(shí)使用 x.layer.cornerRadius = xx; 已經(jīng)實(shí)現(xiàn)了圓角,只不過在某些控件是不生效的,因?yàn)槟承﹫D層在被切割圓角圖層之上而被顯示出來了。而 x.clipsToBounds = YES; 帶來的后果就是產(chǎn)生 離屏渲染 ?梢允褂胕nstruments中的CoreAnimation工具,打開 Color Offscren-Rednered Yellow 選項(xiàng),可見黃色區(qū)域部分即是離屏渲染部分。

那么離屏渲染會(huì)帶來什么?當(dāng)然后資源損耗,可能產(chǎn)生卡頓。因?yàn)樵趇Phone設(shè)備的硬件資源有差異,當(dāng)離屏渲染不多時(shí),并不是很明顯感覺到它的缺點(diǎn)。

什么是像素

像素,為視頻顯示的基本單位,譯自英文“pixel”,pix是英語單詞picture的常用簡寫,加上英語單詞“元素”element,就得到pixel,故“像素”表示“畫像元素”之意,有時(shí)亦被稱為pel(picture element)。每個(gè)這樣的消息元素不是一個(gè)點(diǎn)或者一個(gè)方塊,而是一個(gè)抽象的取樣。像素是由紅,綠,藍(lán)三種顏色組件構(gòu)成的。因此,位圖數(shù)據(jù)有時(shí)也被叫做 RGB 數(shù)據(jù)。

顯示機(jī)制

一個(gè)像素是如何繪制到屏幕上去的?有很多種方式將一些東西映射到顯示屏上,他們需要調(diào)用不同的框架、許多功能和方法的結(jié)合體。這里我們大概看一下屏幕之后發(fā)生的事情。

圖像想顯示到屏幕上使人肉眼可見都需借助像素的力量。它們密集的排布在手機(jī)屏幕上,將任何圖形通過不同的色值表現(xiàn)出來。計(jì)算機(jī)顯示的流程大致可以描述為將圖像轉(zhuǎn)化為一系列像素點(diǎn)的排列然后打印在屏幕上,由圖像轉(zhuǎn)化為像素點(diǎn)的過程又可以稱之為光柵化,就是從矢量的點(diǎn)線面的描述,變成像素的描述。

回溯歷史,可以從過去的 CRT 顯示器原理說起。CRT 的電子槍按照上面方式,從上到下一行行掃描,掃描完成后顯示器就呈現(xiàn)一幀畫面,隨后電子槍回到初始位置繼續(xù)下一次掃描。為了把顯示器的顯示過程和系統(tǒng)的視頻控制器進(jìn)行同步,顯示器(或者其他硬件)會(huì)用硬件時(shí)鐘產(chǎn)生一系列的定時(shí)信號。當(dāng)電子槍換到新的一行,準(zhǔn)備進(jìn)行掃描時(shí),顯示器會(huì)發(fā)出一個(gè)水平同步信號(horizonal synchronization),簡稱 HSync;而當(dāng)一幀畫面繪制完成后,電子槍回復(fù)到原位,準(zhǔn)備畫下一幀前,顯示器會(huì)發(fā)出一個(gè)垂直同步信號(vertical synchronization),簡稱 VSync。顯示器通常以固定頻率進(jìn)行刷新,這個(gè)刷新率就是 VSync 信號產(chǎn)生的頻率。盡管現(xiàn)在的設(shè)備大都是液晶顯示屏了,但原理仍然沒有變。

關(guān)于卡頓的簡單原理解釋

在 VSync 信號到來后,系統(tǒng)圖形服務(wù)會(huì)通過 CADisplayLink 等機(jī)制通知 App,App 主線程開始在 CPU 中計(jì)算顯示內(nèi)容,比如視圖的創(chuàng)建、布局計(jì)算、圖片解碼、文本繪制等。隨后 CPU 會(huì)將計(jì)算好的內(nèi)容提交到 GPU 去,由 GPU 進(jìn)行變換、合成、渲染。隨后 GPU 會(huì)把渲染結(jié)果提交到幀緩沖區(qū)去,等待下一次 VSync 信號到來時(shí)顯示到屏幕上。由于垂直同步的機(jī)制,如果在一個(gè) VSync 時(shí)間內(nèi),CPU 或者 GPU 沒有完成內(nèi)容提交,則那一幀就會(huì)被丟棄,等待下一次機(jī)會(huì)再顯示,而這時(shí)顯示屏?xí)A糁暗膬?nèi)容不變。這就是界面卡頓的原因。

CPU 和 GPU 不論哪個(gè)阻礙了顯示流程,都會(huì)造成掉幀現(xiàn)象。所以開發(fā)時(shí),也需要分別對 CPU 和 GPU 壓力進(jìn)行評估和優(yōu)化。

渲染機(jī)制

當(dāng)像素映射到屏幕上的時(shí)候,后臺發(fā)生了很多事情。但一旦它們顯示到屏幕上,每一個(gè)像素均由三個(gè)顏色組件構(gòu)成:紅,綠,藍(lán)。三個(gè)獨(dú)立的顏色單元會(huì)根據(jù)給定的顏色顯示到一個(gè)像素上。在 iPhoneSE 的顯示器上有1,136×640=727,040個(gè)像素,因此有2,181,120個(gè)顏色單元。在一些Retina屏幕上,這一數(shù)字將達(dá)到百萬以上。所有的圖形堆棧一起工作以確保每次正確的顯示。當(dāng)你滾動(dòng)整個(gè)屏幕的時(shí)候,數(shù)以百萬計(jì)的顏色單元必須以每秒60次的速度刷新,這就是一個(gè)很大的工作量。

簡單來說,iOS的顯示機(jī)制大致如此:

Display 的上一層便是圖形處理單元 GPU,GPU 是一個(gè)專門為圖形高并發(fā)計(jì)算而量身定做的處理單元。這也是為什么它能同時(shí)更新所有的像素,并呈現(xiàn)到顯示器上。它的并發(fā)本性讓它能高效的將不同紋理合成起來。所以,開發(fā)中我們應(yīng)該盡量讓CPU負(fù)責(zé)主線程的UI調(diào)動(dòng),把圖形顯示相關(guān)的工作交給GPU來處理。

GPU Driver 是直接和 GPU 交流的代碼塊。不同的GPU是不同的性能怪獸,但是驅(qū)動(dòng)使它們在下一個(gè)層級上顯示的更為統(tǒng)一,典型的下一層級有 OpenGL/OpenGL ES.

OpenGL(Open Graphics Library) 是一個(gè)提供了 2D 和 3D 圖形渲染的 API。GPU 是一塊非常特殊的硬件,OpenGL 和 GPU 密切的工作以提高GPU的能力,并實(shí)現(xiàn)硬件加速渲染。

OpenGL 之上擴(kuò)展出很多東西。在 iOS 上,幾乎所有的東西都是通過 Core Animation 繪制出來,然而在 OS X 上,繞過 Core Animation 直接使用 Core Graphics 繪制的情況并不少見。對于一些專門的應(yīng)用,尤其是游戲,程序可能直接和 OpenGL/OpenGL ES 交流。

需要強(qiáng)調(diào)的是,GPU 是一個(gè)非常強(qiáng)大的圖形硬件,并且在顯示像素方面起著核心作用。它連接到 CPU。從硬件上講兩者之間存在某種類型的總線,并且有像 OpenGL,Core Animation 和 Core Graphics 這樣的框架來在 GPU 和 CPU 之間精心安排數(shù)據(jù)的傳輸。為了將像素顯示到屏幕上,一些處理將在 CPU 上進(jìn)行。然后數(shù)據(jù)將會(huì)傳送到 GPU,最終像素顯示到屏幕上。

正如上圖顯示,GPU 需要將每一個(gè) frame 的紋理(位圖)合成在一起(一秒60次)。每一個(gè)紋理會(huì)占用 VRAM(video RAM),所以需要給 GPU 同時(shí)保持紋理的數(shù)量做一個(gè)限制。GPU 在合成方面非常高效,但是某些合成任務(wù)卻比其他更復(fù)雜,并且 GPU在 16.7ms(1/60s)內(nèi)能做的工作也是有限的。

另外一個(gè)問題就是將數(shù)據(jù)傳輸?shù)?GPU 上。為了讓 GPU 訪問數(shù)據(jù),需要將數(shù)據(jù)從 RAM 移動(dòng)到 VRAM 上。這就是提及到的上傳數(shù)據(jù)到 GPU。這些看起來貌似微不足道,但是一些大型的紋理卻會(huì)非常耗時(shí)。

最終,CPU 開始運(yùn)行程序。你可能會(huì)讓 CPU 從 bundle 加載一張 PNG 的圖片并且解壓它。這所有的事情都在 CPU 上進(jìn)行。然后當(dāng)你需要顯示解壓縮后的圖片時(shí),它需要以某種方式上傳到 GPU。一些看似平凡的,比如顯示文本,對 CPU 來說卻是一件非常復(fù)雜的事情,這會(huì)促使 Core Text 和 Core Graphics 框架更緊密的集成來根據(jù)文本生成一個(gè)位圖。一旦準(zhǔn)備好,它將會(huì)被作為一個(gè)紋理上傳到 GPU 并準(zhǔn)備顯示出來。當(dāng)你滾動(dòng)或者在屏幕上移動(dòng)文本時(shí),同樣的紋理能夠被復(fù)用,CPU 只需簡單的告訴 GPU 新的位置就行了,所以 GPU 就可以重用存在的紋理了。CPU 并不需要重新渲染文本,并且位圖也不需要重新上傳到 GPU。

在圖形世界中,合成是一個(gè)描述不同位圖如何放到一起來創(chuàng)建你最終在屏幕上看到圖像的過程。屏幕上一切事物皆紋理。一個(gè)紋理就是一個(gè)包含 RGBA 值的長方形,比如,每一個(gè)像素里面都包含紅、綠、藍(lán)和透明度的值。在 Core Animation 世界中這就相當(dāng)于一個(gè) CALayer。

每一個(gè) layer 是一個(gè)紋理,所有的紋理都以某種方式堆疊在彼此的頂部。對于屏幕上的每一個(gè)像素,GPU 需要算出怎么混合這些紋理來得到像素 RGB 的值。這就是合成。

如果我們所擁有的是一個(gè)和屏幕大小一樣并且和屏幕像素對齊的單一紋理,那么屏幕上每一個(gè)像素相當(dāng)于紋理中的一個(gè)像素,紋理的最后一個(gè)像素也就是屏幕的最后一個(gè)像素。

如果我們有第二個(gè)紋理放在第一個(gè)紋理之上,然后GPU將會(huì)把第二個(gè)紋理合成到第一個(gè)紋理中。有很多種不同的合成方法,但是如果我們假定兩個(gè)紋理的像素對齊,并且使用正常的混合模式,我們便可以用公式來計(jì)算每一個(gè)像素: R = S + D * ( 1 – Sa )
結(jié)果的顏色是源色彩(頂端紋理)+目標(biāo)顏色(低一層的紋理)*(1-源顏色的透明度)。在這個(gè)公式中所有的顏色都假定已經(jīng)預(yù)先乘以了它們的透明度。

接著我們進(jìn)行第二個(gè)假定,兩個(gè)紋理都完全不透明,比如 alpha=1。如果目標(biāo)紋理(低一層的紋理)是藍(lán)色(RGB=0,0,1),并且源紋理(頂層的紋理)顏色是紅色(RGB=1,0,0),因?yàn)?Sa 為1,所以結(jié)果為: R = S
結(jié)果是源顏色的紅色。這正是我們所期待的(紅色覆蓋了藍(lán)色)。如果源顏色層為50%的透明,比如 alpha=0.5,既然 alpha 組成部分需要預(yù)先乘進(jìn) RGB 的值中,那么 S 的 RGB 值為(0.5, 0, 0),公式看起來便會(huì)像這樣:

                       0.5   0               0.5
R = S + D * (1 - Sa) = 0   + 0 * (1 - 0.5) = 0
                       0     1               0.5

我們最終得到RGB值為(0.5, 0, 0.5),是一個(gè)紫色。這正是我們所期望將透明紅色合成到藍(lán)色背景上所得到的。

記住我們剛剛只是將紋理中的一個(gè)像素合成到另一個(gè)紋理的像素上。當(dāng)兩個(gè)紋理覆蓋在一起的時(shí)候,GPU需要為所有像素做這種操作。正如你所知道的一樣,許多程序都有很多層,因此所有的紋理都需要合成到一起。盡管GPU是一塊高度優(yōu)化的硬件來做這種事情,但這還是會(huì)讓它非常忙碌。

為何圖片縮放會(huì)增加GPU工作量

當(dāng)所有的像素是對齊的時(shí)候我們得到相對簡單的計(jì)算公式。每當(dāng) GPU 需要計(jì)算出屏幕上一個(gè)像素是什么顏色的時(shí)候,它只需要考慮在這個(gè)像素之上的所有 layer 中對應(yīng)的單個(gè)像素,并把這些像素合并到一起;蛘,如果最頂層的紋理是不透明的(即圖層樹的最底層),這時(shí)候 GPU 就可以簡單的拷貝它的像素到屏幕上。

當(dāng)一個(gè) layer 上所有的像素和屏幕上的像素完美的對應(yīng)整齊,那這個(gè) layer 就是像素對齊的。主要有兩個(gè)原因可能會(huì)造成不對齊。第一個(gè)便是滾動(dòng),當(dāng)一個(gè)紋理上下滾動(dòng)的時(shí)候,紋理的像素便不會(huì)和屏幕的像素排列對齊。另一個(gè)原因便是當(dāng)紋理的起點(diǎn)不在一個(gè)像素的邊界上。

在這兩種情況下,GPU 需要再做額外的計(jì)算。它需要將源紋理上多個(gè)像素混合起來,生成一個(gè)用來合成的值。當(dāng)所有的像素都是對齊的時(shí)候,GPU 只剩下很少的工作要做。

Core Animation 工具和模擬器有一個(gè) Color Misaligned Images 選項(xiàng),當(dāng)這些在你的 CALayer 實(shí)例中發(fā)生的時(shí)候,這個(gè)功能便可向你展示。

關(guān)于iOS設(shè)備的一些尺寸限制可以看這里: iOSRes

離屏渲染

On-Screen Rendering意為當(dāng)前屏幕渲染,指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)中進(jìn)行。

Off-Screen Rendering意為離屏渲染,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開辟一個(gè)緩沖區(qū)進(jìn)行渲染操作。

當(dāng)圖層屬性的混合體被指定為在未預(yù)合成之前不能直接在屏幕中繪制時(shí),屏幕外渲染就被喚起了。屏幕外渲染并不意味著軟件繪制,但是它意味著圖層必須在被顯示之前在一個(gè)屏幕外上下文中被渲染(不論CPU還是GPU)。

離屏渲染可以被 Core Animation 自動(dòng)觸發(fā),或者被應(yīng)用程序強(qiáng)制觸發(fā)。屏幕外的渲染會(huì)合并/渲染圖層樹的一部分到一個(gè)新的緩沖區(qū),然后該緩沖區(qū)被渲染到屏幕上。

特殊的“離屏渲染”:CPU渲染

如果我們重寫了drawRect方法,并且使用任何Core Graphics的技術(shù)進(jìn)行了繪制操作,就涉及到了CPU渲染。

整個(gè)渲染過程由CPU在App內(nèi)同步地完成,渲染得到的bitmap最后再交由GPU用于顯示。

離屏渲染的體現(xiàn)

相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的,主要體現(xiàn)在兩個(gè)方面:

  • 1 創(chuàng)建新緩沖區(qū)
    要想進(jìn)行離屏渲染,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。
  • 2 上下文切換
    離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕。而上下文環(huán)境的切換是要付出很大代價(jià)的。

觸發(fā)離屏渲染

1、drawRect

2、layer.shouldRasterize = true;

3、有mask或者是陰影(layer.masksToBounds, layer.shadow*);

3.1) shouldRasterize(光柵化)

3.2) masks(遮罩)

3.3) shadows(陰影)

3.4) edge antialiasing(抗鋸齒)

3.5) group opacity(不透明)

4、Text(UILabel, CATextLayer, Core Text, etc)…

注:layer.cornerRadius,layer.borderWidth,layer.borderColor并不會(huì)Offscreen Render,因?yàn)檫@些不需要加入Mask。

圓角優(yōu)化

前面說了那么多,這里就給上實(shí)際可行方案。圓角的優(yōu)化目前考慮兩方面:一是,從圖片入手,將圖片切割成指定圓角樣式。二是,使用貝塞爾曲線,利用CALayer層繪制指定圓角樣式的mask遮蓋View。

UIImage切割:

UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);

CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2.0) {
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
    CGContextSaveGState(context);
    [path addClip];
    CGContextDrawImage(context, rect, self.CGImage);
    CGContextRestoreGState(context);
}

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
image = [image dd_imageByCornerRadius:radius borderedColor:borderColor borderWidth:borderWidth corners:corners];
UIGraphicsEndImageContext();

圖片繪制:

UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
[self drawAtPoint:CGPointZero];
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGFloat strokeInset = borderWidth / 2.0;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
path.lineWidth = borderWidth;
[borderColor setStroke];
[path stroke];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

具體源碼可以轉(zhuǎn)至github進(jìn)行star DDCornerRadius 歡迎issue。

 

來自:http://chars.tech/2017/07/03/ios-corner-radius/

 

標(biāo)簽: isp 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:三個(gè)案例看Nginx配置安全

下一篇:9個(gè)最佳的大數(shù)據(jù)處理編程語言