如果說?SkCanvas
?是畫布,是所有繪圖操作的提供者的話,那么?SkSurface
?就是畫布的容器,我們稱之為表面,它負責管理畫布對應的像素數據。這些像素數據可以是在內存中創建的,也可以是在 GPU 顯存中創建的。
創建一個空白表面
如何創建和使用 Skia 的?SkSurface
?對象,如下代碼所示:
SkImageInfo imgInfo = SkImageInfo::MakeN32Premul(800, 600);
sk_sp<SkSurface> surface = SkSurfaces::Raster(imgInfo);
SkSurfaces::Raster
?方法執行后,應用程序將在內存中分配指定數量的像素。
若需要使用?SkSurface
?對象管理的像素,可以通過如下方法來完成工作:
SkPixmap pixmap;
surface->peekPixels(&pixmap);
auto addr = pixmap.addr();
pixmap.addr()
?用于獲取像素數據的內存地址,一般情況下,開發者不會使用這個地址來改變具體的某個像素的值。
因為內存中存儲的像素數據是被格式化過的,并不是A,R,G,B顏色分量依次排列的數值。像素數據在內存中的排布方式與?SkSurface
?的顏色空間有關。
通過如下代碼,你可以獲取某個具體位置的顏色:
SkColor color = pixmap.getColor(0, 0); //畫布上位置 0,0 的顏色
根據現有像素數據創建表面
如果你已經擁有了像素數據,就可以直接基于你的像素數據創建?SkSurface
?對象,如下代碼所示:
std::vector<SkColor> surfaceMemory;
surfaceMemory.resize(w * h);
SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(info, &surfaceMemory.front(), w * sizeof(SkColor));
上面代碼中使用?std::vector<SkColor>
?來存儲像素數據。
每個像素就是一個?SkColor
?類型的數據(SkColor就是?uint32_t
?)。
像素容器的大小是?w * h
,(二維數組的列數 w 和行數 h ,與窗口的寬和高相同)
SkSurfaces::WrapPixels
?方法基于像素數據創建?SkSurface
?對象。
其中?&surfaceMemory.front()
?用于獲取容器內第一個像素的地址,
w * sizeof(SkColor)
?是每行數據的大小。
同樣道理,如果你有一個?SkBitmap
?對象,就可以根據這個對象的像素數據創建表面:
SkBitmap bitmap;
bitmap.allocN32Pixels(w, h);
sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(bitmap.pixmap());
上述代碼使用?SkBitmap
?對象的?allocN32Pixels
?方法為此對象創建了指定大小的內存空間用于存儲像素數據。
SkBitmap
?對象的?pixmap
?方法負責獲取?SkBitmap
?對象的像素數據的內存地址。
重置表面像素數據
如果你想批量把一個表面的像素數據全部設置為某個顏色值。
就可以用以下兩個辦法達到這個目的:
auto canvas = surface->getCanvas();
canvas->clear(0x66778899); //方法1
canvas->drawColor(0x22FF8899); //方法2
SkPaint paintObj;
canvas->drawPaint(paintObj); //方法3
這都是通過畫布對象?SkCanvas
?完成的。
也可以在源頭設置所有像素的值,如下代碼所示
std::vector<SkColor> surfaceMemory(w * h, 0xffff00); //初始化像素數組時,即設置好像素顏色surfaceMemory.resize(w * h,0xffff00); //更改像素容器大小時,重置整個容器的像素顏色
覆蓋表面像素數據
如果你已經有了一組像素數據,需要把這些像素寫入目標表面,可以通過如下方法完成:
void surfaceWritePixels(SkSurface *surface)
{std::vector<SkColor> srcMem(200 * 200, 0xff00ffff);SkBitmap dstBitmap;dstBitmap.setInfo(SkImageInfo::MakeN32Premul(200, 200));dstBitmap.setPixels(&srcMem.front());surface->writePixels(dstBitmap, 100, 100);
}
在這段代碼中,使用?SkBitmap
?類型包裝了像素數據。
SkBitmap
?對象的?setPixels
?方法會把?srcMem
?管理的像素地址拷貝到?SkBitmap
?對象中(只復制了地址)
SkSurface
?對象的?writePixels
?方法會把?dstBitmap
?對象管理的像素數據復制到自己管理的內存數據中。
復制是從?100,100
?的位置開始的,由于?dstBitmap
?只管理了?200×200
?的像素數據,所以復制工作執行到坐標?300,300
?就結束了。
程序運行的結果如下圖所示:
表面與畫布的互訪
通過?SkSurface
?對象的?getCanvas
?方法得到一個與此表面有關的畫布。
也可以通過?SkCanvas
?對象的?getSurface
?方法得到一個與此畫布有關的表面。
但需要注意的是,SkCanvas
?對象的?getSurface
?方法并不是總能得到與之對應的?SkSurface
?對象。
比如:
auto canvas = SkCanvas::MakeRasterDirect(info, &surfaceMemory.front(), 4 * w);
auto surface = canvas->getSurface();
這個?canvas
?對象是手動創建的,它不依賴任何surface
,它的?getSurface
?方法的返回值就是一個空指針。
雖然這個?canvas
?沒有與之對應的?surface
,可以通過如下方法創建一個:
SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
auto surface = canvas->makeSurface(info);
需要注意的是,這樣做會導致?canvas
?對應的像素數據被復制到?surface
?內(內存占用加倍)
所以要么先創建surface
再通過surface
得到canvas
,要么僅使用canvas
,不使用surface
。
盡量不要通過canvas
去創建surface
。
當一個應用中存在多個surface
的時候,往往需要合并這些surface
,
有很多辦法可以完成這項任務,其中之一就是把一個?surface
?繪制到另一個?canvas
?中,如下代碼所示:
surface->draw(canvas.get(), 0, 0);
這行代碼會把?surface
?管理的像素數據,繪制到?canvas
?畫布上(從坐標?0,0
?位置開始繪制),這就達到了合并兩個?surface
?的效果。