Skia深入分析4——skia路徑繪製的實現

Skia路徑繪製代碼分析
路徑繪製儘管使用頻率相對於圖像繪製、文本繪製低,但卻是非常重要的一個基本特性。所有不規則圖形(橢圓、圓角矩形、三角形、簡單的文字),最後都避不開路徑繪製。
而且,若自己實現一個2D引擎,這塊內容是很具有參考意義的,用OpenGL的話,圖像採樣等都很少關注了,對對座標就好。但菱角、圓弧、曲線等如何繪製仍然是一個難題,這時就可以參考Skia中drawPath的實現。
由於涉及較多的圖形學知識,本章就不講相關公式了,只講講基本的流程。
一、SkPath類
在之前的圖像繪製並沒有介紹SkBitmap,因爲SkBitmap相對而言比較容易理解,網上文章也多。但這次的SkPath不同,研究它怎麼用是需要一點精力的,因此在這裏先做介紹。
1、SkPath結構
去除成員函數之後,我們看到SkPath包括這幾個成員,註釋中補充了說明:
[cpp] view plain copy
  1. class SK_API SkPath {  
  2.     //SkPath中的主要內容,SkAutoTUnref是自解引用,之所以這麼設計,是爲了複製SkPath時,省去份量較多的點複製(只複製引用)。  
  3.     //由一系列線段組成  
  4.     SkAutoTUnref<SkPathRef> fPathRef;  
  5.   
  6.   
  7.     int                 fLastMoveToIndex;  
  8.     uint8_t             fFillType;//如下四種類型之一  
  9.     /*enum FillType { 
  10.         kWinding_FillType,//繪製所有線段包圍成的區域 
  11.         kEvenOdd_FillType,//繪製被所有線段包圍奇數次的區域) 
  12.         kInverseWinding_FillType,//kWinding_FillType取反,即繪製不在該區域的點 
  13.         kInverseEvenOdd_FillType//第二種type取反 
  14.         }*/  
  15.     mutable uint8_t     fConvexity;//凹凸性,臨時計算  
  16.     mutable uint8_t     fDirection;//方向,順時針/逆時針,臨時計算  
  17. #ifdef SK_BUILD_FOR_ANDROID  
  18.     const SkPath*       fSourcePath;//Hwui中使用,暫不關注  
  19. #endif  
  20. };  


關於 fFillType中 kWinding_FillType和 kEvenOdd_FillType的區別,可看SkPath::contains。這是判斷點是否在不規則幾何體內的經典代碼(),很有參考意義。


SkPathRef的內容如下:
[cpp] view plain copy
  1. class SkPathRef  
  2. {  
  3. private:  
  4.     mutable SkRect      fBounds;//邊界,臨時計算  
  5.     uint8_t             fSegmentMask;//表示這個Path含有哪些種類的形狀  
  6.     mutable uint8_t     fBoundsIsDirty;//緩存fBounds使用,表示 fBounds是否需要重新計算  
  7.     mutable SkBool8     fIsFinite;    // only meaningful if bounds are valid  
  8.     mutable SkBool8     fIsOval;  
  9.   
  10.   
  11.     /*skia不使用stl庫而採用的一套容器方案,具體不細說,可看下 SkPath::Iter 的實現*/  
  12.     SkPoint*            fPoints; // points to begining of the allocation  
  13.     uint8_t*            fVerbs; // points just past the end of the allocation (verbs grow backwards)  
  14.     int                 fVerbCnt;  
  15.     int                 fPointCnt;  
  16.     size_t              fFreeSpace; // redundant but saves computation  
  17.   
  18.   
  19.   
  20.   
  21.     SkTDArray<SkScalar> fConicWeights;  
  22.     mutable uint32_t    fGenerationID;  
  23. };  


2、SkPath的主要類型:

kMove_Verb:表示需要移動起點
kLine_Verb:直線
kQuad_Verb:二次曲線
kConic_Verb:圓錐曲線
kCubic_Verb:三次曲線
kClose_Verb:表閉合到某點
kDone_Verb:表結束


3、drawPath使用實例
[cpp] view plain copy
  1. #include "SkPath.h"  
  2. #include "SkCanvas.h"  
  3. #include "SkBitmap.h"  
  4.   
  5. int main()  
  6. {  
  7.     SkBitmap dst;  
  8.     dst.allocN32Pixels(1000, 1000);  
  9.     SkCanvas c(dst);  
  10.     SkPath path;  
  11.     /*一個三角形*/  
  12.     path.moveTo(300,0);  
  13.     path.lineTo(400,100);  
  14.     path.lineTo(200,100);  
  15.     path.close();  
  16.     /*橢圓*/  
  17.     SkRect oval;  
  18.     oval.set(0, 0, 500, 600);  
  19.     path.addOval(oval);  
  20.   
  21.     c.drawPath(path);  
  22.     return 1;  
  23. }  


二、drawPath流程
1、基本流程


2、填充算法說明
我們跟進最重要的函數 sk_fill_path,如下爲代碼:
[cpp] view plain copy
  1. void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,  
  2.                   int start_y, int stop_y, int shiftEdgesUp,  
  3.                   const SkRegion& clipRgn) {  
  4.     SkASSERT(&path && blitter);  
  5.   
  6.     SkEdgeBuilder   builder;  
  7.   
  8.     int count = builder.build(path, clipRect, shiftEdgesUp);  
  9.     SkEdge**    list = builder.edgeList();  
  10.   
  11.     if (count < 2) {  
  12.         if (path.isInverseFillType()) {  
  13.             /* 
  14.              *  Since we are in inverse-fill, our caller has already drawn above 
  15.              *  our top (start_y) and will draw below our bottom (stop_y). Thus 
  16.              *  we need to restrict our drawing to the intersection of the clip 
  17.              *  and those two limits. 
  18.              */  
  19.             SkIRect rect = clipRgn.getBounds();  
  20.             if (rect.fTop < start_y) {  
  21.                 rect.fTop = start_y;  
  22.             }  
  23.             if (rect.fBottom > stop_y) {  
  24.                 rect.fBottom = stop_y;  
  25.             }  
  26.             if (!rect.isEmpty()) {  
  27.                 blitter->blitRect(rect.fLeft << shiftEdgesUp,  
  28.                                   rect.fTop << shiftEdgesUp,  
  29.                                   rect.width() << shiftEdgesUp,  
  30.                                   rect.height() << shiftEdgesUp);  
  31.             }  
  32.         }  
  33.   
  34.         return;  
  35.     }  
  36.   
  37.     SkEdge headEdge, tailEdge, *last;  
  38.     // this returns the first and last edge after they're sorted into a dlink list  
  39.     SkEdge* edge = sort_edges(list, count, &last);  
  40.   
  41.     headEdge.fPrev = NULL;  
  42.     headEdge.fNext = edge;  
  43.     headEdge.fFirstY = kEDGE_HEAD_Y;  
  44.     headEdge.fX = SK_MinS32;  
  45.     edge->fPrev = &headEdge;  
  46.   
  47.     tailEdge.fPrev = last;  
  48.     tailEdge.fNext = NULL;  
  49.     tailEdge.fFirstY = kEDGE_TAIL_Y;  
  50.     last->fNext = &tailEdge;  
  51.   
  52.     // now edge is the head of the sorted linklist  
  53.   
  54.     start_y <<= shiftEdgesUp;  
  55.     stop_y <<= shiftEdgesUp;  
  56.     if (clipRect && start_y < clipRect->fTop) {  
  57.         start_y = clipRect->fTop;  
  58.     }  
  59.     if (clipRect && stop_y > clipRect->fBottom) {  
  60.         stop_y = clipRect->fBottom;  
  61.     }  
  62.   
  63.     InverseBlitter  ib;  
  64.     PrePostProc     proc = NULL;  
  65.   
  66.     if (path.isInverseFillType()) {  
  67.         ib.setBlitter(blitter, clipRgn.getBounds(), shiftEdgesUp);  
  68.         blitter = &ib;  
  69.         proc = PrePostInverseBlitterProc;  
  70.     }  
  71.   
  72.     if (path.isConvex() && (NULL == proc)) {  
  73.         walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, NULL);  
  74.     } else {  
  75.         walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc);  
  76.     }  
  77. }  


不考慮 Inverse 的情況,主要就是兩步:
(1)生成一系列邊:SkEdge
(2)遍歷渲染各邊所圍出來的區域

凸集的渲染比較簡單,因爲可以保證,任意兩條邊+閉合線所圍成區域一定需要渲染:
(1)取初始的兩條邊,分別爲:左和右。
(2)渲染左右邊+閉合邊所圍成的區域(一般爲三角,當兩邊平行時取矩形)

(3)迭代刷新左右兩邊(如果是曲線需要刷新多次)

[cpp] view plain copy
  1. static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,  
  2.                               SkBlitter* blitter, int start_y, int stop_y,  
  3.                               PrePostProc proc) {  
  4.     validate_sort(prevHead->fNext);  
  5.   
  6.     SkEdge* leftE = prevHead->fNext;  
  7.     SkEdge* riteE = leftE->fNext;  
  8.     SkEdge* currE = riteE->fNext;  
  9.   
  10. #if 0  
  11.     int local_top = leftE->fFirstY;  
  12.     SkASSERT(local_top == riteE->fFirstY);  
  13. #else  
  14.     // our edge choppers for curves can result in the initial edges  
  15.     // not lining up, so we take the max.  
  16.     int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);  
  17. #endif  
  18.     SkASSERT(local_top >= start_y);  
  19.   
  20.     for (;;) {  
  21.         SkASSERT(leftE->fFirstY <= stop_y);  
  22.         SkASSERT(riteE->fFirstY <= stop_y);  
  23.   
  24.         if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&  
  25.                                       leftE->fDX > riteE->fDX)) {  
  26.             SkTSwap(leftE, riteE);  
  27.         }  
  28.   
  29.         int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);  
  30.         local_bot = SkMin32(local_bot, stop_y - 1);  
  31.         SkASSERT(local_top <= local_bot);  
  32.   
  33.         SkFixed left = leftE->fX;  
  34.         SkFixed dLeft = leftE->fDX;  
  35.         SkFixed rite = riteE->fX;  
  36.         SkFixed dRite = riteE->fDX;  
  37.         int count = local_bot - local_top;  
  38.         SkASSERT(count >= 0);  
  39.         if (0 == (dLeft | dRite)) {  
  40.             int L = SkFixedRoundToInt(left);  
  41.             int R = SkFixedRoundToInt(rite);  
  42.             if (L < R) {  
  43.                 count += 1;  
  44.                 blitter->blitRect(L, local_top, R - L, count);  
  45.                 left += count * dLeft;  
  46.                 rite += count * dRite;  
  47.             }  
  48.             local_top = local_bot + 1;  
  49.         } else {  
  50.             do {  
  51.                 int L = SkFixedRoundToInt(left);  
  52.                 int R = SkFixedRoundToInt(rite);  
  53.                 if (L < R) {  
  54.                     blitter->blitH(L, local_top, R - L);  
  55.                 }  
  56.                 left += dLeft;  
  57.                 rite += dRite;  
  58.                 local_top += 1;  
  59.             } while (--count >= 0);  
  60.         }  
  61.   
  62.         leftE->fX = left;  
  63.         riteE->fX = rite;  
  64.   
  65.         if (update_edge(leftE, local_bot)) {  
  66.             if (currE->fFirstY >= stop_y) {  
  67.                 break;  
  68.             }  
  69.             leftE = currE;  
  70.             currE = currE->fNext;  
  71.         }  
  72.         if (update_edge(riteE, local_bot)) {  
  73.             if (currE->fFirstY >= stop_y) {  
  74.                 break;  
  75.             }  
  76.             riteE = currE;  
  77.             currE = currE->fNext;  
  78.         }  
  79.   
  80.         SkASSERT(leftE);  
  81.         SkASSERT(riteE);  
  82.   
  83.         // check our bottom clip  
  84.         SkASSERT(local_top == local_bot + 1);  
  85.         if (local_top >= stop_y) {  
  86.             break;  
  87.         }  
  88.     }  
  89. }  


凹集或者判斷不了凹凸性就比較複雜,需要一條線一條線去渲染,每次渲染還得判斷奇偶性:

代碼如下,不分析了:

[cpp] view plain copy
  1. static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType,  
  2.                        SkBlitter* blitter, int start_y, int stop_y,  
  3.                        PrePostProc proc) {  
  4.     validate_sort(prevHead->fNext);  
  5.   
  6.     int curr_y = start_y;  
  7.     // returns 1 for evenodd, -1 for winding, regardless of inverse-ness  
  8.     int windingMask = (fillType & 1) ? 1 : -1;  
  9.   
  10.     for (;;) {  
  11.         int     w = 0;  
  12.         int     left SK_INIT_TO_AVOID_WARNING;  
  13.         bool    in_interval = false;  
  14.         SkEdge* currE = prevHead->fNext;  
  15.         SkFixed prevX = prevHead->fX;  
  16.   
  17.         validate_edges_for_y(currE, curr_y);  
  18.   
  19.         if (proc) {  
  20.             proc(blitter, curr_y, PREPOST_START);    // pre-proc  
  21.         }  
  22.   
  23.         while (currE->fFirstY <= curr_y) {  
  24.             SkASSERT(currE->fLastY >= curr_y);  
  25.   
  26.             int x = SkFixedRoundToInt(currE->fX);  
  27.             w += currE->fWinding;  
  28.             if ((w & windingMask) == 0) { // we finished an interval  
  29.                 SkASSERT(in_interval);  
  30.                 int width = x - left;  
  31.                 SkASSERT(width >= 0);  
  32.                 if (width)  
  33.                     blitter->blitH(left, curr_y, width);  
  34.                 in_interval = false;  
  35.             } else if (!in_interval) {  
  36.                 left = x;  
  37.                 in_interval = true;  
  38.             }  
  39.   
  40.             SkEdge* next = currE->fNext;  
  41.             SkFixed newX;  
  42.   
  43.             if (currE->fLastY == curr_y) {    // are we done with this edge?  
  44.                 if (currE->fCurveCount < 0) {  
  45.                     if (((SkCubicEdge*)currE)->updateCubic()) {  
  46.                         SkASSERT(currE->fFirstY == curr_y + 1);  
  47.   
  48.                         newX = currE->fX;  
  49.                         goto NEXT_X;  
  50.                     }  
  51.                 } else if (currE->fCurveCount > 0) {  
  52.                     if (((SkQuadraticEdge*)currE)->updateQuadratic()) {  
  53.                         newX = currE->fX;  
  54.                         goto NEXT_X;  
  55.                     }  
  56.                 }  
  57.                 remove_edge(currE);  
  58.             } else {  
  59.                 SkASSERT(currE->fLastY > curr_y);  
  60.                 newX = currE->fX + currE->fDX;  
  61.                 currE->fX = newX;  
  62.             NEXT_X:  
  63.                 if (newX < prevX) { // ripple currE backwards until it is x-sorted  
  64.                     backward_insert_edge_based_on_x(currE  SkPARAM(curr_y));  
  65.                 } else {  
  66.                     prevX = newX;  
  67.                 }  
  68.             }  
  69.             currE = next;  
  70.             SkASSERT(currE);  
  71.         }  
  72.   
  73.         if (proc) {  
  74.             proc(blitter, curr_y, PREPOST_END);    // post-proc  
  75.         }  
  76.   
  77.         curr_y += 1;  
  78.         if (curr_y >= stop_y) {  
  79.             break;  
  80.         }  
  81.         // now currE points to the first edge with a Yint larger than curr_y  
  82.         insert_new_edges(currE, curr_y);  
  83.     }  
  84. }  

3、描線流程
個人認爲較簡單,就不介紹了。

三、總結
drawPath是繪製所有不規則形體的函數,帶入Bitmap的Shader,可以製作不規則形體的圖片。對於凸集,Skia的渲染主要也是切成三角片後渲染,和OpenGL類似。而對於凹集,則是掃描線了。渲染的實現和繪製圖片一樣,構建Blitter,調用Blitter的blit函數族渲染。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章