學習了jxt1234and2010的大作,試着用自己的理解去分析skia draw bitmap的過程,在這裏感謝jxt1234and2010。
1.Draw bitmap api
這裏主要講一下在SkCanvas中,有哪些draw bitmap 的API可用,以及各自的作用是什麼。
draw bitmap的api有以下幾種:
- drawBitmap:將bitmap畫到x,y的位置(這本身是一個平移,需要和SkCanvas中的矩陣狀態疊加)。
- drawBitmapRect 和 drawBitmapRectToRect:將源圖src矩陣部分,畫到目標dst區域去。最後一個flags是AndroidL上爲了gpu繪製效果而加上去的,在CPU繪製中不需要關注。
- drawSprite:無視SkCanvas的矩陣狀態,將bitmap平移到x,y的位置。
- drawBitmapMatrix:繪製的bitmap帶有matrix的矩形變換,需要和SkCanvas的矩形變換疊加。
- drawRect:這個是最通用的方法,多用於需要加入額外效果的場景,比如需要繪製重複紋理。關於Tile的兩個參數就是OpenGL紋理貼圖中水平垂直方向上的邊界處理模式。
根據以下測試代碼來直觀看一下這些api的不同:
<pre name="code" class="cpp">SkBitmap src;
SkImageDecoder::DecodeFile("E:/git/skia/Skia_VS2010/skia/out/2.png", &src);
/*各種繪製圖片方法使用示例*/
{
canvas->drawBitmap(src, 0, 0, NULL); //在目的設備的(0,0)位置繪製解碼出的圖片bitmap
}
{
canvas->drawSprite(src, 400, 400, NULL); //簡易模式下,平移到目的設備的(400,400)處繪製解碼後的圖片
}
{
canvas->drawBitmap(src, 0, 0, NULL);
SkRect dstR;
dstR.set(60,60,110,110);
SkRect srcR;
srcR.set(0,0,50,50);
canvas->drawBitmapRectToRect(src, &srcR, dstR, NULL); //將源區域內的bitmap繪製到目的區域
}
{
SkMatrix m;
m.setScale(2.3,0.9);
canvas->drawBitmapMatrix(src, m, NULL); //按照m矩陣繪製bitmap
}
{
SkRect dstRect;
dstRect.set(100,100,920,480); //繪製區域
SkPaint paint;
SkMatrix m;
m.setScale(2.3,0.9);
SkShader* shader = SkShader::CreateBitmapShader(src, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &m); //重複貼圖
paint.setShader(shader);
SkSafeUnref(shader);
canvas->drawRect(dstRect, paint);
}
2.Draw bitmap flow
上面的圖中列出了draw bitmap api的繪製流程,可以看出drawBitmap基本可以分爲兩路:drawSprite和drawRect,即簡易繪製模式和繪製矩形區域。
下面借鑑這個流程來分析一下源碼。(實際源碼中我們在drawBitmap中還可以看到有第三條flow,drawBitmapAsMask。如果源bitmap屬於kAlpha_8_SkColorType類型纔會走這條flow,在這裏暫時忽略掉)
(1)drawSprite簡易模式
進入簡易模式有兩種方式:
a.直接去調用drawSprite;
b.調用drawBitmap時,如果bitmap的colorType不是kAlpha_8_SkColorType,同時對於bitmap的matrix只有平移操作,此時就會進入簡易模式的分支。
由於兩種方式的行爲類似,我們選擇從a方式源碼入手:
<pre name="code" class="cpp">void SkDraw::drawSprite(const SkBitmap& bitmap, int x, int y,
const SkPaint& origPaint) const {
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fRC->isEmpty() ||
bitmap.width() == 0 || bitmap.height() == 0 ||
bitmap.colorType() == kUnknown_SkColorType) {
return;
}
SkIRect bounds;
bounds.set(x, y, x + bitmap.width(), y + bitmap.height()); //更新繪製範圍
if (fRC->quickReject(bounds)) {
return; // nothing to draw
}
SkPaint paint(origPaint);
paint.setStyle(SkPaint::kFill_Style);
if (NULL == paint.getColorFilter() && clipHandlesSprite(*fRC, x, y, bitmap)) {
SkTBlitterAllocator allocator;
// blitter will be owned by the allocator.
SkBlitter* blitter = SkBlitter::ChooseSprite(*fBitmap, paint, bitmap,
x, y, &allocator);
if (blitter) {
if (fBounder && !fBounder->doIRect(bounds)) {
return;
}
SkScan::FillIRect(bounds, *fRC, blitter);
return;
}
}
SkMatrix matrix;
SkRect r;
// get a scalar version of our rect
r.set(bounds);
// create shader with offset
matrix.setTranslate(r.fLeft, r.fTop);
SkAutoBitmapShaderInstall install(bitmap, paint, &matrix);
const SkPaint& shaderPaint = install.paintWithShader();
SkDraw draw(*this);
matrix.reset();
draw.fMatrix = &matrix;
// call ourself with a rect
// is this OK if paint has a rasterizer?
draw.drawRect(r, shaderPaint);
}
clipHandlesSprite(*fRC, x, y, bitmap)) 這個函數的作用是判斷當前剪裁區域是否可以執行簡易繪製模式。
它首先判斷當前剪裁區域需不需要考慮抗鋸齒:
如果不需要抗鋸齒,則執行簡易繪製模式;
如果需要抗鋸齒,則要判斷剪裁區域是否包含了繪製區域:
如果包含繪製區域,則執行簡易繪製模式;
如果不包含繪製區域,則執行drawRect方式。
SkCanvas的任何渲染都必須在裁剪區域之內,因此如果圖像跨越了裁剪區域邊界而且裁剪區域需要考慮抗鋸齒,在邊界上需要做特殊處理。
接下來就是使用SkBlitter::ChooseSprite()函數去創建簡易模式下的blitter,它是根據源bitmap和目的設備的colorType進行選擇的。
如果目的設備是kRGB_565_SkColorType,則需要使用SkSpriteBlitter::ChooseD16()工廠函數去創建具體類型的blitter,如果:
源bitmap是kN32_SkColorType,則創建Sprite_D16_S32_BlitRowProc類型的blitter(變量名中的D和S是dst和src);
源bitmap是kARGB_4444_SkColorType,且:
目的設備的alpha是不透明的,則創建Sprite_D16_S4444_Opaque;
目的設備的alpha是透明的,則創建Sprite_D16_S4444_Blend;
源bitmap是kRGB_565_SkColorType,且:
目的設備的alpha是不透明的,則創建Sprite_D16_S16_Opaque;
目的設備的alpha是透明的,則創建Sprite_D16_S16_Blend;
源bitmap是kIndex_8_SkColorType,且:
如果源bitmap是不透明的,且:
目的設備的alpha是不透明的,則創建Sprite_D16_SIndex8_Opaque;
目的設備的alpha是透明的,則創建Sprite_D16_SIndex8_Blend;
如果源bitmap是透明的,且:
目的設備的alpha是不透明的,則創建Sprite_D16_SIndex8A_Opaque;
目的設備的alpha是透明的,則創建Sprite_D16_SIndex8A_Blend。
如果目的設備是kN32_SkColorType,,則需要使用SkSpriteBlitter::ChooseD32()工廠函數去創建具體類型的blitter,過程與ChooseD16類似。
再下面就是SkScan::FillIRect(bounds, *fRC, blitter),掃描待繪製區域,然後使用所選的blitter渲染每個繪製區域。
<pre name="code" class="cpp">void SkScan::FillIRect(const SkIRect& r, const SkRasterClip& clip,
SkBlitter* blitter) {
if (clip.isEmpty() || r.isEmpty()) {
return;
}
if (clip.isBW()) { //不需要抗鋸齒
FillIRect(r, &clip.bwRgn(), blitter);
return;
}
/*需要抗鋸齒*/
SkAAClipBlitterWrapper wrapper(clip, blitter);
FillIRect(r, &wrapper.getRgn(), wrapper.getBlitter()); }
void SkScan::FillIRect(const SkIRect& r, const SkRegion* clip,
SkBlitter* blitter) {
if (!r.isEmpty()) { //判斷繪製區域不是空的
if (clip) { //判斷剪裁區域不爲空
if (clip->isRect()) { //剪裁區域是一個單獨的矩形
const SkIRect& clipBounds = clip->getBounds();
if (clipBounds.contains(r)) { //如果剪裁矩形邊界包含繪製矩形,則直接渲染繪製矩形區域
blitrect(blitter, r);
} else { //如果剪裁矩形邊界不完全包含繪製矩形,則只渲染與剪裁區域相交的矩形區域
SkIRect rr = r;
if (rr.intersect(clipBounds)) {
blitrect(blitter, rr);
}
}
} else { //走到這裏說明剪裁區域是由一個矩形序列組成,需要循環渲染每一個矩形
SkRegion::Cliperator cliper(*clip, r);
const SkIRect& rr = cliper.rect();
while (!cliper.done()) {
blitrect(blitter, rr);
cliper.next();
}
}
} else { //走到這說明沒有剪裁區域,則直接渲染繪製區域
blitrect(blitter, r);
}
}
}
最後一步就是使用blitter將帶渲染區域渲染到目的設備上。 拿子類Sprite_D32_S32A_XferFilter中blitRect的實現來解釋實際blitter做了些什麼:
virtual void blitRect(int x, int y, int width, int height) {
SkASSERT(width > 0 && height > 0);
uint32_t* SK_RESTRICT dst = fDevice->getAddr32(x, y); //找到目的設備的像素地址
const uint32_t* SK_RESTRICT src = fSource->getAddr32(x - fLeft,
y - fTop); //找到源bitmap的像素地址
size_t dstRB = fDevice->rowBytes();
size_t srcRB = fSource->rowBytes();
SkColorFilter* colorFilter = fColorFilter;
SkXfermode* xfermode = fXfermode;
do {
const SkPMColor* tmp = src;
/*如果有colorFilter,則需要對源bitmap進行顏色過濾處理*/
if (NULL != colorFilter) {
colorFilter->filterSpan(src, width, fBuffer);
tmp = fBuffer;
}
/*爲目的設備計算每個像素混合處理之後的值,然後把得到的一行像素搬移到目的設備上;*/
if (NULL != xfermode) {
xfermode->xfer32(dst, tmp, width, NULL);
} else {
fProc32(dst, tmp, width, fAlpha);
}
dst = (uint32_t* SK_RESTRICT)((char*)dst + dstRB);
src = (const uint32_t* SK_RESTRICT)((const char*)src + srcRB);
} while (--height != 0);
}
在blitRect()函數中,主要做了三個工作:
1.在paint中設置的colorFilter在這裏完成處理;
2.在paint中設置的XferMode也在這裏進行圖像混合;圖像混合時,如果設置了XferMode,則在圖像混合時不使用alpha;如果沒有設置XferMode,則使用源bitmap的Alpha去處理混合。
3.處理完的bitmap搬移到目的設備上。
整個簡易模式flow流程如下:
(2)drawRect
如果繪製時需要使用paint加入各種渲染效果,就要使用drawRect。進入drawRect flow的方式有兩種:
1.直接調用;
2.調用drawBitmap或者drawSprite時不滿足簡易模式的條件。
我們根據上面演示的例子來解釋。
{
SkRect dstRect;
dstRect.set(100,100,920,480); //繪製區域
SkPaint paint;
SkMatrix m;
m.setScale(2.3,0.9); //設置仿射變換矩陣的縮放元素
SkShader* shader = SkShader::CreateBitmapShader(src, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &m); //重複貼圖
paint.setShader(shader);
SkSafeUnref(shader);
canvas->drawRect(dstRect, paint);
}
代碼中先設置了一個(100,100,920,480)矩形繪製區域,然後設置仿射變換矩陣的縮放比例(2.3,0.9),最後爲paint添加了一個shader,shader設置爲重複模式。
shader中的平鋪模式有三種:CLAMP(越界時限制在邊界)、REPEAT(越界時從開頭取起)、MIRROR(越界時取樣方向倒轉去取)。
設置完shader後就開始drawRect。
void SkDraw::drawRect(const SkRect& rect, const SkPaint& paint) const {
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fRC->isEmpty()) {
return;
}
SkPoint strokeSize;
RectType rtype = ComputeRectType(paint, *fMatrix, &strokeSize);
if (kPath_RectType == rtype) {
SkPath tmp;
tmp.addRect(rect);
tmp.setFillType(SkPath::kWinding_FillType);
this->drawPath(tmp, paint, NULL, true);
return;
}
const SkMatrix& matrix = *fMatrix;
SkRect devRect;
// transform rect into devRect
matrix.mapPoints(rect_points(devRect), rect_points(rect), 2);
devRect.sort();
if (fBounder && !fBounder->doRect(devRect, paint)) {
return;
}
// look for the quick exit, before we build a blitter
SkIRect ir;
devRect.roundOut(&ir);
if (paint.getStyle() != SkPaint::kFill_Style) {
// extra space for hairlines
ir.inset(-1, -1);
}
if (fRC->quickReject(ir)) {
return;
}
SkDeviceLooper looper(*fBitmap, *fRC, ir, paint.isAntiAlias());
while (looper.next()) {
SkRect localDevRect;
looper.mapRect(&localDevRect, devRect);
SkMatrix localMatrix;
looper.mapMatrix(&localMatrix, matrix);
SkAutoBlitterChoose blitterStorage(looper.getBitmap(), localMatrix,
paint);
const SkRasterClip& clip = looper.getRC();
SkBlitter* blitter = blitterStorage.get();
// we want to "fill" if we are kFill or kStrokeAndFill, since in the latter
// case we are also hairline (if we've gotten to here), which devolves to
// effectively just kFill
switch (rtype) {
case kFill_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiFillRect(localDevRect, clip, blitter);
} else {
SkScan::FillRect(localDevRect, clip, blitter);
}
break;
case kStroke_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiFrameRect(localDevRect, strokeSize, clip, blitter);
} else {
SkScan::FrameRect(localDevRect, strokeSize, clip, blitter);
}
break;
case kHair_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiHairRect(localDevRect, clip, blitter);
} else {
SkScan::HairRect(localDevRect, clip, blitter);
}
break;
default:
SkDEBUGFAIL("bad rtype");
}
}
}
drawRect的剪裁區域如果爲空則不會繼續繪製。如果不是空的話,drawRect會首先計算rect類型,skia中的rect類型有四種:
enum RectType {
kHair_RectType, //無線寬stroke
kFill_RectType, //矩形填充
kStroke_RectType,//矩形輪廓
kPath_RectType //path
};
對於kPath_RectType屬於drawPath範圍,以後再解釋,其它三種屬於drawRect。
判斷完rect類型之後會把繪製矩形區域進行仿射變換,映射到目的設備的矩形區域中。
接下來會定義一個SkDeviceLooper 對象,根據SkDeviceLooper .h中的註釋:
Helper class to manage "tiling" a large coordinate space into managable
chunks, where managable means areas that are <= some max critical coordinate size.
大概的意思是,這個類用來把比較大的座標空間分成一些“易處理"的數據塊,這些數據塊都不大於一個最大值。這個最大值根據是否
採用抗鋸齒來判斷是多少,並把由這個最大值構成的矩形區域叫做delta區域:
enum Delta {
kBW_Delta = 1 << 14, // 16K, gives room to spare for fixedpoint
kAA_Delta = kBW_Delta >> 2 // supersample 4x
};
SkDeviceLooper 的構造函數中會去記錄繪製區域與剪切區域的相交區域:
如果剪切區域爲空,或者相交區域爲空,標記爲kDone_State;
如果相交區域不爲空,且相交區域在delta區域內,則標記爲kSimple_State;否則標爲kComplex_State。
SkDeviceLooper的next()函數會根據源bitmap的這三種狀態,爲looper對象返回一個有效的managable chunks。
然後在while()每一次循環中,被處理的對象爲每一個chunk。
後面的就要爲源bitmap的每一個子集(即chunk)使用SkBlitter::Choose()去選擇blitter,根據Canvas綁定的Bitmap像素模式和paint屬性去選擇blitter。
繪製圖片時paint有Shader(SkBitmapProcShader),因此是選的是帶Shader的Blitter,比如適應ARGB格式的 SkARGB32_Shader_Blitter。得到blitter之後就需要根據不同的rect類型進行渲染操作。
類似drawSprit,在渲染之前需要掃描待繪製矩形區域,這裏會根據不同的rect類型去選擇不同的函數:
switch (rtype) {
case kFill_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiFillRect(localDevRect, clip, blitter);
} else {
SkScan::FillRect(localDevRect, clip, blitter);
}
break;
case kStroke_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiFrameRect(localDevRect, strokeSize, clip, blitter);
} else {
SkScan::FrameRect(localDevRect, strokeSize, clip, blitter);
}
break;
case kHair_RectType:
if (paint.isAntiAlias()) {
SkScan::AntiHairRect(localDevRect, clip, blitter);
} else {
SkScan::HairRect(localDevRect, clip, blitter);
}
break;
default:
SkDEBUGFAIL("bad rtype");
}
每種rect類型這裏都會判斷是否考慮抗鋸齒,並採用不同的函數實際去處理。這裏只討論kFill_RectType。
如果不考慮抗鋸齒的話,後面的流程與drawSprite這部分過程類似。
如果考慮抗鋸齒,需要在blit之前處理抗鋸齒,基本方法就是對浮點的座標,按其離整數的偏離度給一個alpha權重,將顏色乘以此權重(減淡顏色)畫上去。
SkScan中在繪製矩形時,先用blitV繪製左右邊界,再用blitAntiH繪製上下邊界,中間大塊的不需要考慮抗鋸齒,因而用blitRect。
後面blit的過程可以參考skia圖片繪製的實現(2) 和我後來補充的另一篇skia bitmap shader。
整個flow:
fill rect flow: