此篇講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、流程解析
(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中的透明度,疊加。