WebKit源碼分析系列之(一) html5 canvas
如今html5概念炒的很是火熱,其中不乏標(biāo)志性的tags,其中video算一個(gè),把Adobe憋的不行,收購PhoneGap等系列的活動(dòng)以應(yīng)對(duì)html5之強(qiáng)勢(shì)
兩大不開源的公司Apple Adobe誰都不服氣,以目前的勢(shì)頭看Apple走的更加深遠(yuǎn)些,閑話少說,我們說說今天的tag主角canvas
我們知道flash開發(fā)的時(shí)候,有專門畫點(diǎn)畫線的函數(shù),有這樣的接口對(duì)于開發(fā)小游戲,是相當(dāng)方便,而且還可以制作出很炫的視覺效果,下面的幾個(gè)鏈接
就是canvas應(yīng)用
http://media.chikuyonok.ru/ambilight/
http://www.2cto.com/zz/201206/135837.html
canvas這么牛,倒是如何實(shí)現(xiàn)的那?
從樣式上看canvas也只是一個(gè)普通的html標(biāo)簽(在webkit上還沒有專門擴(kuò)展過html那,找個(gè)時(shí)間實(shí)踐下,哈哈)
下面我們看一個(gè)簡(jiǎn)單的canvas應(yīng)用舉例:
<!DOCTYPE HTML>
<html>
<body>
<canvas id="myCanvas">your browser does not support the canvas tag </canvas>
<script type="text/javascript">
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
ctx.fillStyle='#FF0000';
ctx.fillRect(0,0,80,100);
</script>
</body>
</html>
我們做個(gè)簡(jiǎn)單的分析
<canvas id="myCanvas">your browser does not support the canvas tag </canvas>
典型的html語言的語法
這個(gè)辦法的顯示其實(shí)很簡(jiǎn)單,就是如果是不支持的tags,那么“your browser does not support the canvas tag”就是作為字符串進(jìn)行處理了,
如果是支持該tags的情況下,這一段文字是不會(huì)去處理的,這個(gè)方式通常作為判斷瀏覽器是否支持某個(gè)標(biāo)簽,例如在優(yōu)酷或者土豆網(wǎng)站對(duì)瀏覽器的支持
<video src="video.ogg">
<object data="videoplayer.swf" type="application/x-shockwave-flash">
<param name="movie" value="video.swf"/>
</object>
</video>
就是說在支持video的狀態(tài)下,使用video標(biāo)簽播放,在不支持狀態(tài)下,使用Adobe flash
好了繼續(xù)分析網(wǎng)頁內(nèi)容
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
第一句很好理解,就是取到canvas實(shí)例對(duì)象
canvas.getContext('2d')
這句話得到畫板句柄,根據(jù)傳入的類型進(jìn)行返回不同實(shí)例,普通的操作是2D方式,如果涉及到3D操作傳入experimental-webgl或者webkit-3d參數(shù),這樣在網(wǎng)頁上就可以執(zhí)行3D的動(dòng)作
ctx.fillStyle='#FF0000';
ctx.fillRect(0,0,80,100);
這兩句話就是具體繪制操作,畫個(gè)紅色矩形
不要小瞧這兩句話,意味著在網(wǎng)頁里面可以隨便進(jìn)行畫點(diǎn)畫線,一些絢麗操作的通道就開啟了
上層邏輯的分析,基本上就是這種情況了。
下面進(jìn)入底層的分析流程:
對(duì)于最基礎(chǔ)的HTMLCanvasElement類的實(shí)例化,就先不做過多的分析了,直接分析底層
canvas.getContext('2d');這個(gè)函數(shù)的實(shí)現(xiàn)流程
CanvasRenderingContext* HTMLCanvasElement::getContext(const String& type, CanvasContextAttributes* attrs)
{
// A Canvas can either be "2D" or "webgl" but never both. If you request a 2D canvas and the existing
// context is already 2D, just return that. If the existing context is WebGL, then destroy it
// before creating a new 2D context. Vice versa when requesting a WebGL canvas. Requesting a
// context with any other type string will destroy any existing context.
// FIXME - The code depends on the context not going away once created, to prevent JS from
// seeing a dangling pointer. So for now we will disallow the context from being changed
// once it is created.
if (type == "2d") {
if (m_context && !m_context->is2d())
return 0;
if (!m_context) {
bool usesDashbardCompatibilityMode = false;
#if ENABLE(DASHBOARD_SUPPORT)
if (Settings* settings = document()->settings())
usesDashbardCompatibilityMode = settings->usesDashboardBackwardCompatibilityMode();
#endif
m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode));
#if USE(IOSURFACE_CANVAS_BACKING_STORE) || (ENABLE(ACCELERATED_2D_CANVAS) && USE(ACCELERATED_COMPOSITING))
if (m_context) {
// Need to make sure a RenderLayer and compositing layer get created for the Canvas
setNeedsStyleRecalc(SyntheticStyleChange);
}
#endif
}
return m_context.get();
}
#if ENABLE(WEBGL)
Settings* settings = document()->settings();
if (settings && settings->webGLEnabled()
#if !PLATFORM(CHROMIUM) && !PLATFORM(GTK)
&& settings->acceleratedCompositingEnabled()
#endif
) {
// Accept the legacy "webkit-3d" name as well as the provisional "experimental-webgl" name.
// Once ratified, we will also accept "webgl" as the context name.
if ((type == "webkit-3d") ||
(type == "experimental-webgl")) {
if (m_context && !m_context->is3d())
return 0;
if (!m_context) {
m_context = WebGLRenderingContext::create(this, static_cast<WebGLContextAttributes*>(attrs));
if (m_context) {
// Need to make sure a RenderLayer and compositing layer get created for the Canvas
setNeedsStyleRecalc(SyntheticStyleChange);
}
}
return m_context.get();
}
}
#else
UNUSED_PARAM(attrs);
#endif
return 0;
}
代碼很多我們只是分析針對(duì)2D的情況
OwnPtr<CanvasRenderingContext> m_context;
m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode));
這個(gè)操作是我們要拿到畫布的句柄,實(shí)際上所有的操作都是圍繞這個(gè)函數(shù)展開的
我們具體追蹤一下CanvasRenderingContext類型實(shí)例對(duì)象如何進(jìn)行簡(jiǎn)單的畫點(diǎn)畫線操作
我們先不去看這個(gè)m_context如何去實(shí)現(xiàn),我們先看看如何使用的過程
ctx.fillRect(0,0,80,100);看看這個(gè)函數(shù)內(nèi)部包的什么我們感興趣的寶貝
下面是在c++層函數(shù)實(shí)現(xiàn)
void CanvasRenderingContext2D::fillRect(float x, float y, float width, float height)
{
if (!validateRectForCanvas(x, y, width, height))
return;
GraphicsContext* c = drawingContext();
if (!c)
return;
if (!state().m_invertibleCTM)
return;
// from the HTML5 Canvas spec:
// If x0 = x1 and y0 = y1, then the linear gradient must paint nothing
// If x0 = x1 and y0 = y1 and r0 = r1, then the radial gradient must paint nothing
Gradient* gradient = c->fillGradient();
if (gradient && gradient->isZeroSize())
return;
FloatRect rect(x, y, width, height);
c->fillRect(rect);
didDraw(rect);
}
這個(gè)函數(shù)我們只關(guān)心三行
GraphicsContext* c = drawingContext();
c->fillRect(rect);
didDraw(rect);
先看第一行,GraphicsContext* c到底是誰給的?
層層分解,估計(jì)下面的層次會(huì)讓腦細(xì)胞累死一部分
GraphicsContext* CanvasRenderingContext2D::drawingContext() const
{
return canvas()->drawingContext();
}
看看drawingContext()怎么實(shí)現(xiàn)
GraphicsContext* HTMLCanvasElement::drawingContext() const
{
return buffer() ? m_imageBuffer->context() : 0;
}
buffer()函數(shù)實(shí)現(xiàn)
ImageBuffer* HTMLCanvasElement::buffer() const
{
if (!m_hasCreatedImageBuffer)
createImageBuffer();
return m_imageBuffer.get();
}
createImageBuffer()函數(shù)實(shí)現(xiàn)
void HTMLCanvasElement::createImageBuffer() const
{
ASSERT(!m_imageBuffer);
m_hasCreatedImageBuffer = true;
FloatSize unscaledSize(width(), height());
IntSize size = convertLogicalToDevice(unscaledSize);
if (!size.width() || !size.height())
return;
#if USE(IOSURFACE_CANVAS_BACKING_STORE)
if (document()->settings()->canvasUsesAcceleratedDrawing())
m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Accelerated);
else
m_imageBuffer = ImageBuffer::create(size, ColorSpaceDeviceRGB, Unaccelerated);
#else
m_imageBuffer = ImageBuffer::create(size);
#endif
// The convertLogicalToDevice MaxCanvasArea check should prevent common cases
// where ImageBuffer::create() returns 0, however we could still be low on memory.
if (!m_imageBuffer)
return;
m_imageBuffer->context()->scale(FloatSize(size.width() / unscaledSize.width(), size.height() / unscaledSize.height()));
m_imageBuffer->context()->setShadowsIgnoreTransforms(true);
m_imageBuffer->context()->setImageInterpolationQuality(DefaultInterpolationQuality);
#if USE(JSC)
JSC::JSLock lock(JSC::SilenceAssertionsOnly);
scriptExecutionContext()->globalData()->heap.reportExtraMemoryCost(m_imageBuffer->dataSize());
#endif
}
ImageBuffer::create(size) 函數(shù)實(shí)現(xiàn)
static PassOwnPtr<ImageBuffer> create(const IntSize& size, ColorSpace colorSpace = ColorSpaceDeviceRGB, RenderingMode renderingMode = Unaccelerated)
{
bool success = false;
OwnPtr<ImageBuffer> buf(new ImageBuffer(size, colorSpace, renderingMode, success));
if (success)
return buf.release();
return 0;
}
ImageBuffer構(gòu)造函數(shù)實(shí)現(xiàn)
ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace, RenderingMode, bool& success)
: m_data(size)
, m_size(size)
{
// GraphicsContext creates a 32bpp SkBitmap, so 4 bytes per pixel.
if (!PlatformBridge::canSatisfyMemoryAllocation(size.width() * size.height() * 4))
success = false;
else {
m_context.set(GraphicsContext::createOffscreenContext(size.width(), size.height()));
success = true;
}
}
終于找到了針對(duì)canvas的GraphicsContext,哈哈看看函數(shù)里面的操作,哈哈熟悉的skia操作
GraphicsContext* GraphicsContext::createOffscreenContext(int width, int height)
{
PlatformGraphicsContext* pgc = new PlatformGraphicsContext();
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
bitmap.allocPixels();
bitmap.eraseColor(0);
pgc->mCanvas->setBitmapDevice(bitmap);
GraphicsContext* ctx = new GraphicsContext(pgc);
return ctx;
}
追蹤到這里實(shí)際上已經(jīng)接近尾聲了,畫筆的老祖宗都已經(jīng)找到,具體的操作都是在祖先的基礎(chǔ)上完成
我們用比較輕松的心態(tài)看下具體的繪制操作
void GraphicsContext::fillRect(const FloatRect& rect)
{
SkPaint paint;
m_data->setupPaintFill(&paint);
extactShader(&paint,
m_state.fillPattern.get(),
m_state.fillGradient.get());
GC2CANVAS(this)->drawRect(rect, paint);
}
GC2CANVAS這個(gè)是什么玩意,看一下
#define GC2CANVAS(ctx) (ctx)->m_data->getPlatformGfxCtx()->mCanvas
哈哈,canvas,原來搞到祖先頭上了,
GC2CANVAS(this)->drawRect(rect, paint);這句話實(shí)際上就是調(diào)用skia的canvas進(jìn)行繪制矩形操作,
繪制操作完畢之后就進(jìn)行具體展現(xiàn)到屏幕上,
void HTMLCanvasElement::didDraw(const FloatRect& rect)
{
m_copiedImage.clear(); // Clear our image snapshot if we have one.
if (RenderBox* ro = renderBox()) {
FloatRect destRect = ro->contentBoxRect();
FloatRect r = mapRect(rect, FloatRect(0, 0, size().width(), size().height()), destRect);
r.intersect(destRect);
if (r.isEmpty() || m_dirtyRect.contains(r))
return;
m_dirtyRect.unite(r);
ro->repaintRectangle(enclosingIntRect(m_dirtyRect));
}
HashSet<CanvasObserver*>::iterator end = m_observers.end();
for (HashSet<CanvasObserver*>::iterator it = m_observers.begin(); it != end; ++it)
(*it)->canvasChanged(this, rect);
}
函數(shù)就是做的這個(gè)事情,因?yàn)樗械牟僮鞫际窃跒g覽器上操作,因此最后還是要觸發(fā),webkit的顯示動(dòng)作(repaintRectangle)
今天講述的都是一些框架性的,至于具體如何觸發(fā)webkit的操作以及具體繪制過程(相當(dāng)復(fù)雜)以后會(huì)做更加詳細(xì)的分析