版權聲明:本文爲博主原創文章,轉載請註明出處:http://blog.csdn.net/jxt1234and2010
而且,若自己實現一個2D引擎,這塊內容是很具有參考意義的,用OpenGL的話,圖像採樣等都很少關注了,對對座標就好。但菱角、圓弧、曲線等如何繪製仍然是一個難題,這時就可以參考Skia中drawPath的實現。
由於涉及較多的圖形學知識,本章就不講相關公式了,只講講基本的流程。
一、SkPath類
在之前的圖像繪製並沒有介紹SkBitmap,因爲SkBitmap相對而言比較容易理解,網上文章也多。但這次的SkPath不同,研究它怎麼用是需要一點精力的,因此在這裏先做介紹。
1、SkPath結構
去除成員函數之後,我們看到SkPath包括這幾個成員,註釋中補充了說明:
- class SK_API SkPath {
- //SkPath中的主要內容,SkAutoTUnref是自解引用,之所以這麼設計,是爲了複製SkPath時,省去份量較多的點複製(只複製引用)。
- //由一系列線段組成
- SkAutoTUnref<SkPathRef> fPathRef;
- int fLastMoveToIndex;
- uint8_t fFillType;//如下四種類型之一
- /*enum FillType {
- kWinding_FillType,//繪製所有線段包圍成的區域
- kEvenOdd_FillType,//繪製被所有線段包圍奇數次的區域)
- kInverseWinding_FillType,//kWinding_FillType取反,即繪製不在該區域的點
- kInverseEvenOdd_FillType//第二種type取反
- }*/
- mutable uint8_t fConvexity;//凹凸性,臨時計算
- mutable uint8_t fDirection;//方向,順時針/逆時針,臨時計算
- #ifdef SK_BUILD_FOR_ANDROID
- const SkPath* fSourcePath;//Hwui中使用,暫不關注
- #endif
- };
關於 fFillType中 kWinding_FillType和 kEvenOdd_FillType的區別,可看SkPath::contains。這是判斷點是否在不規則幾何體內的經典代碼(),很有參考意義。
SkPathRef的內容如下:
- class SkPathRef
- {
- private:
- mutable SkRect fBounds;//邊界,臨時計算
- uint8_t fSegmentMask;//表示這個Path含有哪些種類的形狀
- mutable uint8_t fBoundsIsDirty;//緩存fBounds使用,表示 fBounds是否需要重新計算
- mutable SkBool8 fIsFinite; // only meaningful if bounds are valid
- mutable SkBool8 fIsOval;
- /*skia不使用stl庫而採用的一套容器方案,具體不細說,可看下 SkPath::Iter 的實現*/
- SkPoint* fPoints; // points to begining of the allocation
- uint8_t* fVerbs; // points just past the end of the allocation (verbs grow backwards)
- int fVerbCnt;
- int fPointCnt;
- size_t fFreeSpace; // redundant but saves computation
- SkTDArray<SkScalar> fConicWeights;
- mutable uint32_t fGenerationID;
- };
2、SkPath的主要類型:
kMove_Verb:表示需要移動起點
kLine_Verb:直線
kQuad_Verb:二次曲線
kConic_Verb:圓錐曲線
kCubic_Verb:三次曲線
kClose_Verb:表閉合到某點
kDone_Verb:表結束
3、drawPath使用實例
- #include "SkPath.h"
- #include "SkCanvas.h"
- #include "SkBitmap.h"
- int main()
- {
- SkBitmap dst;
- dst.allocN32Pixels(1000, 1000);
- SkCanvas c(dst);
- SkPath path;
- /*一個三角形*/
- path.moveTo(300,0);
- path.lineTo(400,100);
- path.lineTo(200,100);
- path.close();
- /*橢圓*/
- SkRect oval;
- oval.set(0, 0, 500, 600);
- path.addOval(oval);
- c.drawPath(path);
- return 1;
- }
二、drawPath流程
1、基本流程
2、填充算法說明
我們跟進最重要的函數 sk_fill_path,如下爲代碼:
- void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,
- int start_y, int stop_y, int shiftEdgesUp,
- const SkRegion& clipRgn) {
- SkASSERT(&path && blitter);
- SkEdgeBuilder builder;
- int count = builder.build(path, clipRect, shiftEdgesUp);
- SkEdge** list = builder.edgeList();
- if (count < 2) {
- if (path.isInverseFillType()) {
- /*
- * Since we are in inverse-fill, our caller has already drawn above
- * our top (start_y) and will draw below our bottom (stop_y). Thus
- * we need to restrict our drawing to the intersection of the clip
- * and those two limits.
- */
- SkIRect rect = clipRgn.getBounds();
- if (rect.fTop < start_y) {
- rect.fTop = start_y;
- }
- if (rect.fBottom > stop_y) {
- rect.fBottom = stop_y;
- }
- if (!rect.isEmpty()) {
- blitter->blitRect(rect.fLeft << shiftEdgesUp,
- rect.fTop << shiftEdgesUp,
- rect.width() << shiftEdgesUp,
- rect.height() << shiftEdgesUp);
- }
- }
- return;
- }
- SkEdge headEdge, tailEdge, *last;
- // this returns the first and last edge after they're sorted into a dlink list
- SkEdge* edge = sort_edges(list, count, &last);
- headEdge.fPrev = NULL;
- headEdge.fNext = edge;
- headEdge.fFirstY = kEDGE_HEAD_Y;
- headEdge.fX = SK_MinS32;
- edge->fPrev = &headEdge;
- tailEdge.fPrev = last;
- tailEdge.fNext = NULL;
- tailEdge.fFirstY = kEDGE_TAIL_Y;
- last->fNext = &tailEdge;
- // now edge is the head of the sorted linklist
- start_y <<= shiftEdgesUp;
- stop_y <<= shiftEdgesUp;
- if (clipRect && start_y < clipRect->fTop) {
- start_y = clipRect->fTop;
- }
- if (clipRect && stop_y > clipRect->fBottom) {
- stop_y = clipRect->fBottom;
- }
- InverseBlitter ib;
- PrePostProc proc = NULL;
- if (path.isInverseFillType()) {
- ib.setBlitter(blitter, clipRgn.getBounds(), shiftEdgesUp);
- blitter = &ib;
- proc = PrePostInverseBlitterProc;
- }
- if (path.isConvex() && (NULL == proc)) {
- walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, NULL);
- } else {
- walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc);
- }
- }
不考慮 Inverse 的情況,主要就是兩步:
(1)生成一系列邊:SkEdge
(2)遍歷渲染各邊所圍出來的區域
凸集的渲染比較簡單,因爲可以保證,任意兩條邊+閉合線所圍成區域一定需要渲染:
(1)取初始的兩條邊,分別爲:左和右。
(2)渲染左右邊+閉合邊所圍成的區域(一般爲三角,當兩邊平行時取矩形)
(3)迭代刷新左右兩邊(如果是曲線需要刷新多次)
- static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,
- SkBlitter* blitter, int start_y, int stop_y,
- PrePostProc proc) {
- validate_sort(prevHead->fNext);
- SkEdge* leftE = prevHead->fNext;
- SkEdge* riteE = leftE->fNext;
- SkEdge* currE = riteE->fNext;
- #if 0
- int local_top = leftE->fFirstY;
- SkASSERT(local_top == riteE->fFirstY);
- #else
- // our edge choppers for curves can result in the initial edges
- // not lining up, so we take the max.
- int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);
- #endif
- SkASSERT(local_top >= start_y);
- for (;;) {
- SkASSERT(leftE->fFirstY <= stop_y);
- SkASSERT(riteE->fFirstY <= stop_y);
- if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&
- leftE->fDX > riteE->fDX)) {
- SkTSwap(leftE, riteE);
- }
- int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);
- local_bot = SkMin32(local_bot, stop_y - 1);
- SkASSERT(local_top <= local_bot);
- SkFixed left = leftE->fX;
- SkFixed dLeft = leftE->fDX;
- SkFixed rite = riteE->fX;
- SkFixed dRite = riteE->fDX;
- int count = local_bot - local_top;
- SkASSERT(count >= 0);
- if (0 == (dLeft | dRite)) {
- int L = SkFixedRoundToInt(left);
- int R = SkFixedRoundToInt(rite);
- if (L < R) {
- count += 1;
- blitter->blitRect(L, local_top, R - L, count);
- left += count * dLeft;
- rite += count * dRite;
- }
- local_top = local_bot + 1;
- } else {
- do {
- int L = SkFixedRoundToInt(left);
- int R = SkFixedRoundToInt(rite);
- if (L < R) {
- blitter->blitH(L, local_top, R - L);
- }
- left += dLeft;
- rite += dRite;
- local_top += 1;
- } while (--count >= 0);
- }
- leftE->fX = left;
- riteE->fX = rite;
- if (update_edge(leftE, local_bot)) {
- if (currE->fFirstY >= stop_y) {
- break;
- }
- leftE = currE;
- currE = currE->fNext;
- }
- if (update_edge(riteE, local_bot)) {
- if (currE->fFirstY >= stop_y) {
- break;
- }
- riteE = currE;
- currE = currE->fNext;
- }
- SkASSERT(leftE);
- SkASSERT(riteE);
- // check our bottom clip
- SkASSERT(local_top == local_bot + 1);
- if (local_top >= stop_y) {
- break;
- }
- }
- }
凹集或者判斷不了凹凸性就比較複雜,需要一條線一條線去渲染,每次渲染還得判斷奇偶性:
代碼如下,不分析了:
- static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType,
- SkBlitter* blitter, int start_y, int stop_y,
- PrePostProc proc) {
- validate_sort(prevHead->fNext);
- int curr_y = start_y;
- // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
- int windingMask = (fillType & 1) ? 1 : -1;
- for (;;) {
- int w = 0;
- int left SK_INIT_TO_AVOID_WARNING;
- bool in_interval = false;
- SkEdge* currE = prevHead->fNext;
- SkFixed prevX = prevHead->fX;
- validate_edges_for_y(currE, curr_y);
- if (proc) {
- proc(blitter, curr_y, PREPOST_START); // pre-proc
- }
- while (currE->fFirstY <= curr_y) {
- SkASSERT(currE->fLastY >= curr_y);
- int x = SkFixedRoundToInt(currE->fX);
- w += currE->fWinding;
- if ((w & windingMask) == 0) { // we finished an interval
- SkASSERT(in_interval);
- int width = x - left;
- SkASSERT(width >= 0);
- if (width)
- blitter->blitH(left, curr_y, width);
- in_interval = false;
- } else if (!in_interval) {
- left = x;
- in_interval = true;
- }
- SkEdge* next = currE->fNext;
- SkFixed newX;
- if (currE->fLastY == curr_y) { // are we done with this edge?
- if (currE->fCurveCount < 0) {
- if (((SkCubicEdge*)currE)->updateCubic()) {
- SkASSERT(currE->fFirstY == curr_y + 1);
- newX = currE->fX;
- goto NEXT_X;
- }
- } else if (currE->fCurveCount > 0) {
- if (((SkQuadraticEdge*)currE)->updateQuadratic()) {
- newX = currE->fX;
- goto NEXT_X;
- }
- }
- remove_edge(currE);
- } else {
- SkASSERT(currE->fLastY > curr_y);
- newX = currE->fX + currE->fDX;
- currE->fX = newX;
- NEXT_X:
- if (newX < prevX) { // ripple currE backwards until it is x-sorted
- backward_insert_edge_based_on_x(currE SkPARAM(curr_y));
- } else {
- prevX = newX;
- }
- }
- currE = next;
- SkASSERT(currE);
- }
- if (proc) {
- proc(blitter, curr_y, PREPOST_END); // post-proc
- }
- curr_y += 1;
- if (curr_y >= stop_y) {
- break;
- }
- // now currE points to the first edge with a Yint larger than curr_y
- insert_new_edges(currE, curr_y);
- }
- }
3、描線流程
個人認爲較簡單,就不介紹了。
三、總結
drawPath是繪製所有不規則形體的函數,帶入Bitmap的Shader,可以製作不規則形體的圖片。對於凸集,Skia的渲染主要也是切成三角片後渲染,和OpenGL類似。而對於凹集,則是掃描線了。渲染的實現和繪製圖片一樣,構建Blitter,調用Blitter的blit函數族渲染。