天天看點

Skia深入分析3——skia圖檔繪制的實作(1)

此篇講skia繪制圖檔的流程,在下一篇講圖像采樣原理、混合和抖動技術

1、api用法

(1)drawbitmap

void drawbitmap(const skbitmap& bitmap, skscalar left, skscalar top, const skpaint* paint = null);

将bitmap畫到x,y的位置(這本身是一個平移,需要和skcanvas中的矩陣狀态疊加)。

(2)drawbitmaprect 和 drawbitmaprecttorect

void drawbitmaprect(const skbitmap& bitmap, const skrect& dst, const skpaint* paint = null);

void drawbitmaprecttorect(const skbitmap& bitmap, const skrect* src, const skrect& dst, const skpaint* paint, drawbitmaprectflags flags);

将源圖src矩陣部分,畫到目标dst區域去。

最後一個flags是androidl上為了gpu繪制效果而加上去的,在cpu繪制中不需要關注。

(3)drawsprite

void drawsprite(const skbitmap& bitmap, int x, int y, const skpaint* paint);

無視skcanvas的矩陣狀态,将bitmap平移到x,y的位置。

(4)drawbitmapmatrix

void drawbitmapmatrix(const skbitmap& bitmap, const skmatrix& matrix, const skpaint* paint);

繪制的bitmap帶有matrix的矩形變換,需要和skcanvas的矩形變換疊加。

(5)drawrect

void drawrect(const skrect& r, const skpaint& paint);

這個是最通用的方法,多用于需要加入額外效果的場景,比如需要繪制重複紋理。關于tile的兩個參數就是opengl紋理貼圖中水準垂直方向上的邊界處理模式。

由這種用法,大家不難類推到非矩形圖像繪制的方法,比如畫圓角矩形圖示、把方圖檔裁剪成一個圓等。

下面是一個demo程式

2、流程解析

Skia深入分析3——skia圖檔繪制的實作(1)

(1)skcanvas兩重循環調到skbitmapdevice,進而調到skdraw

在skdraw中,drawbitmap的渲染函數統一為:

void skdraw::drawbitmap(const skbitmap& bitmap, const skmatrix& prematrix, const skpaint& origpaint) const;

(2)sprite簡易模式

在滿足如下條件時,走進sprite簡易模式。

代碼見 external/skia/src/core/skdraw.cpp drawbitmap 函數

a、(bitmap.colortype() != kalpha_8_skcolortype && just_translate(matrix, bitmap))

kalpha_8_skcolortype 的圖像隻有一個通道alpha,按 drawmask 方式處理,将paint中的顔色按圖像的alpha預乘,疊加到目标區域上。

just_translate表示matrix為一個平移矩陣,這時不涉及旋轉縮放,bitmap的像素點和skcanvas綁定的dstbitmap的像素點此時存在連續的一一對齊關系。

b、cliphandlessprite(*frc, ix, iy, bitmap))

這個條件是指目前skcanvas的裁剪區域不需要考慮抗鋸齒或者完全包含了bitmap的渲染區域。skcanvas的任何渲染都必須在裁剪區域之内,是以如果圖像跨越了裁剪區域邊界而且裁剪區域需要考慮抗鋸齒,在邊界上需要做特殊處理。

注:裁剪區域的設定api

void skcanvas::cliprect(const skrect& rect, skregion::op op, bool doaa)

doaa即是否在r的邊界非整數時考慮抗鋸齒。

滿足條件,建立skspriteblitter,由skscan::fillirect按每個裁剪區域調用skspriteblitter的blitrect。

這種情況下可以直接做顔色轉換和透明度合成渲染過去,不需要做抗鋸齒和圖像插值,也就不需要走取樣——混合流程,性能是最高的。

滿足條件後通過choosesprite去選一個skspriteblitter

詳細代碼見 external/skia/src/core/skblitter_sprite.cpp 中的 choosesprite 函數。

這函數實際上很多場景都沒覆寫到,是以很可能是選不到的,這時就開始轉回drawrect流程。

(3)建立bitmapshader

在  skautobitmapshaderinstall install(bitmap, paint); 這一句代碼中,為paint建立了bitmapshader:

        fpaint.setshader(createbitmapshader(src, skshader::kclamp_tilemode,

                                            skshader::kclamp_tilemode,

                                            localmatrix, &fallocator));

然後就可以使用drawrect畫圖像了。

(4)drawrect

不能使用skspriteblitter的場景,走drawrect通用流程。

這裡有非常多的分支,隻講繪制實矩形的情形。

通過 skautoblitterchoose -> skblitter::choose,根據canvas綁定的bitmap像素模式,paint屬性去選擇blitter。

繪制圖檔時paint有shader(skbitmapprocshader),是以是選的是帶shader的blitter,比如适應argb格式的 skargb32_shader_blitter

(5)skscan

在skscan中,對每一個裁剪區域,将其與繪制的rect求交,然後渲染這個相交區域。此外,在需要時做抗鋸齒。

做抗鋸齒的基本方法就是對浮點的坐标,按其離整數的偏離度給一個alpha權重,将顔色乘以此權重(減淡顔色)畫上去。

skscan中在繪制矩形時,先用blitv繪制左右邊界,再用blitantih繪制上下邊界,中間大塊的不需要考慮抗鋸齒,因而用blitrect。

(6)blitrect

這一步先通過 shader的shadespan方法取對應位置的像素,再将此像素通過skblitrow的proc疊加上去。

如果不需要考慮混合模式,可以跳過proc。

參考代碼:external/skia/src/core/skblitter_argb32.cpp 中的blitrect

(7)shadespan

這裡隻考慮 skbitmapprocshader 的shadespan,這主要是圖像采樣的方法。詳細代碼見 external/skia/src/core/skbitmapprocshader.cpp

對每一個目标點,先通過 matrixproc 取出需要參考的源圖像素,然後用sampleproc将這些像素合成為一個像素值。(和opengl裡面的texture2d函數原理很類似)。

若存在 shaderproc(做線性插值時,上面的步驟是可以優化的,完全可以取出一群像素一起做插值計算),以shaderproc代替上面的兩步流程,起性能優化作用。

3、skblitter接口解析

(1)blith

virtual void blith(int x, int y, int width);

從x,y坐标開始,渲染一行width個像素

(2)blitv

virtual void blitv(int x, int y, int height, skalpha alpha);

從x,y開始,渲染一列height個像素,按alpha值對顔色做減淡處理

(3)blitantih

virtual void blitantih(int x, int y, const skalpha antialias[], const int16_t runs[]);

如流程圖所标示的,這個函數的用來渲染上下邊界,作抗鋸齒處理。

(4)blitrect

virtual void blitrect(int x, int y, int width, int height);

繪制矩形區域,這個地方就不需要考慮任何的幾何變換、抗鋸齒等因素了。

(5)blitmask

virtual void blitmask(const skmask& mask, const skirect& clip);

主要繪制文字時使用,以一個顔色乘上mash中的透明度,疊加。