WebKit源碼分析系列之(一) html5 canvas 如今html5概念炒的很是火熱,,其中不乏標(biāo)志性的tags,其中video算一個(gè),,把Adobe憋的不行,,收購PhoneGap等系列的活動(dòng)以應(yīng)對html5之強(qiáng)勢 兩大不開源的公司Apple Adobe誰都不服氣,以目前的勢頭看Apple走的更加深遠(yuǎn)些,,閑話少說,,我們說說今天的tag主角canvas 我們知道flash開發(fā)的時(shí)候,有專門畫點(diǎn)畫線的函數(shù),,有這樣的接口對于開發(fā)小游戲,,是相當(dāng)方便,而且還可以制作出很炫的視覺效果,,下面的幾個(gè)鏈接 就是canvas應(yīng)用 http://media./ambilight/ http://www./zz/201206/135837.html canvas這么牛,,倒是如何實(shí)現(xiàn)的那? 從樣式上看canvas也只是一個(gè)普通的html標(biāo)簽(在webkit上還沒有專門擴(kuò)展過html那,,找個(gè)時(shí)間實(shí)踐下,,哈哈) 下面我們看一個(gè)簡單的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è)簡單的分析 <canvas id="myCanvas">your browser does not support the canvas tag </canvas> 典型的html語言的語法 這個(gè)辦法的顯示其實(shí)很簡單,就是如果是不支持的tags,,那么“your browser does not support the canvas tag”就是作為字符串進(jìn)行處理了,, 如果是支持該tags的情況下,,這一段文字是不會去處理的,這個(gè)方式通常作為判斷瀏覽器是否支持某個(gè)標(biāo)簽,,例如在優(yōu)酷或者土豆網(wǎng)站對瀏覽器的支持 <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í)例對象 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)入底層的分析流程: 對于最基礎(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; } 代碼很多我們只是分析針對2D的情況 OwnPtr<CanvasRenderingContext> m_context; m_context = adoptPtr(new CanvasRenderingContext2D(this, document()->inQuirksMode(), usesDashbardCompatibilityMode)); 這個(gè)操作是我們要拿到畫布的句柄,,實(shí)際上所有的操作都是圍繞這個(gè)函數(shù)展開的 我們具體追蹤一下CanvasRenderingContext類型實(shí)例對象如何進(jì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ì)下面的層次會讓腦細(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; } } 終于找到了針對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ù)雜)以后會做更加詳細(xì)的分析 |
|