如今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的操作以及具体绘制过程(相当复杂)以后会做更加详细的分析