当前位置: 代码迷 >> Web前端 >> WebKit源码分析系列之(1) html5 canvas
  详细解决方案

WebKit源码分析系列之(1) html5 canvas

热度:301   发布时间:2012-11-07 09:56:10.0
WebKit源码分析系列之(一) html5 canvas
如今html5概念炒的很是火热,其中不乏标志性的tags,其中video算一个,把Adobe憋的不行,收购PhoneGap等系列的活动以应对html5之强势
两大不开源的公司Apple Adobe谁都不服气,以目前的势头看Apple走的更加深远些,闲话少说,我们说说今天的tag主角canvas
我们知道flash开发的时候,有专门画点画线的函数,有这样的接口对于开发小游戏,是相当方便,而且还可以制作出很炫的视觉效果,下面的几个链接
就是canvas应用
http://media.chikuyonok.ru/ambilight/
http://www.2cto.com/zz/201206/135837.html

canvas这么牛,倒是如何实现的那?
从样式上看canvas也只是一个普通的html标签(在webkit上还没有专门扩展过html那,找个时间实践下,哈哈)
下面我们看一个简单的canvas应用举例:
<!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>
我们做个简单的分析
<canvas id="myCanvas">your browser does not support the canvas tag </canvas>
典型的html语言的语法
这个办法的显示其实很简单,就是如果是不支持的tags,那么“your browser does not support the canvas tag”就是作为字符串进行处理了,
如果是支持该tags的情况下,这一段文字是不会去处理的,这个方式通常作为判断浏览器是否支持某个标签,例如在优酷或者土豆网站对浏览器的支持
<video src="video.ogg">
   <object data="videoplayer.swf" type="application/x-shockwave-flash">
     <param name="movie" value="video.swf"/>
   </object>
</video>
就是说在支持video的状态下,使用video标签播放,在不支持状态下,使用Adobe flash
好了继续分析网页内容
var canvas=document.getElementById('myCanvas');
var ctx=canvas.getContext('2d');
第一句很好理解,就是取到canvas实例对象
canvas.getContext('2d')
这句话得到画板句柄,根据传入的类型进行返回不同实例,普通的操作是2D方式,如果涉及到3D操作传入experimental-webgl或者webkit-3d参数,这样在网页上就可以执行3D的动作
ctx.fillStyle='#FF0000';
ctx.fillRect(0,0,80,100);
这两句话就是具体绘制操作,画个红色矩形
不要小瞧这两句话,意味着在网页里面可以随便进行画点画线,一些绚丽操作的通道就开启了
上层逻辑的分析,基本上就是这种情况了。
下面进入底层的分析流程:
对于最基础的HTMLCanvasElement类的实例化,就先不做过多的分析了,直接分析底层
canvas.getContext('2d');这个函数的实现流程

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));
这个操作是我们要拿到画布的句柄,实际上所有的操作都是围绕这个函数展开的
我们具体追踪一下CanvasRenderingContext类型实例对象如何进行简单的画点画线操作
我们先不去看这个m_context如何去实现,我们先看看如何使用的过程
ctx.fillRect(0,0,80,100);看看这个函数内部包的什么我们感兴趣的宝贝
下面是在c++层函数实现
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);
}
这个函数我们只关心三行
GraphicsContext* c = drawingContext();
c->fillRect(rect);
didDraw(rect);

先看第一行,GraphicsContext* c到底是谁给的?
层层分解,估计下面的层次会让脑细胞累死一部分
GraphicsContext* CanvasRenderingContext2D::drawingContext() const
{
    return canvas()->drawingContext();
}
看看drawingContext()怎么实现
GraphicsContext* HTMLCanvasElement::drawingContext() const
{
    return buffer() ? m_imageBuffer->context() : 0;
}
buffer()函数实现
ImageBuffer* HTMLCanvasElement::buffer() const
{
    if (!m_hasCreatedImageBuffer)
        createImageBuffer();
    return m_imageBuffer.get();
}
createImageBuffer()函数实现
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) 函数实现
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构造函数实现
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,哈哈看看函数里面的操作,哈哈熟悉的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;
}
追踪到这里实际上已经接近尾声了,画笔的老祖宗都已经找到,具体的操作都是在祖先的基础上完成


我们用比较轻松的心态看下具体的绘制操作
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这个是什么玩意,看一下
#define GC2CANVAS(ctx)  (ctx)->m_data->getPlatformGfxCtx()->mCanvas
哈哈,canvas,原来搞到祖先头上了,
GC2CANVAS(this)->drawRect(rect, paint);这句话实际上就是调用skia的canvas进行绘制矩形操作,
绘制操作完毕之后就进行具体展现到屏幕上,
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);
}
函数就是做的这个事情,因为所有的操作都是在浏览器上操作,因此最后还是要触发,webkit的显示动作(repaintRectangle)
今天讲述的都是一些框架性的,至于具体如何触发webkit的操作以及具体绘制过程(相当复杂)以后会做更加详细的分析