浏览器如何呈现canvas元素的内容

时间:2014-10-14 18:02:11

标签: javascript html5 web html5-canvas

浏览器如何呈现html画布的内容?像context.lineTo(x,y)这样的东西。必须有一些组件将所有函数调用转换为像素数据。我真正想知道的是,是否有某种方法可以获得这些像素数据,而无需在某处实际渲染它。

我想知道我们是否可以运行某种独立的javascript引擎(v8)并将javascript代码传递给它并获取像素数据作为输出。

2 个答案:

答案 0 :(得分:3)

浏览器(或图形卡)只是应用数学算法来设置像素。

结果存储在Html Canvas维护的支持数组中,以保持每个像素的状态。

每个画布像素由画布'像素数组中的一组4个元素表示。 4像素组是R,G,B,A值。因此,单个红色像素将由像素阵列中的这4个元素组表示:

pixelArray=[...255,0,0,255...]  // where 255==red, 0==green, 0==blue, alpha==255

您无法访问浏览器的内部像素设置算法,但您可以运行相同的算法来设置适当的像素。

以下是如何做到这一点的概述:

假设您有一个表示虚构网格上所有像素的数组:

var width=500;
var height=500;
var pixels=new Array(width*height);

您可以获取与任何像素相对应的数组元素:

var x=200;
var y=30;
var arrayElement=y*width+x;

您可以像这样设置数组像素:

function setPixel(x,y,colorObject){
    pixels[y*width+x]=colorObject;
}

setPixel(100,50,{r:255,g:0,b:0,a:255});

您可以像这样获取任何[x,y]的像素设置:

function getPixel(x,y){
    return(pixels[y*width+x]);
}

var theColor=getPixel(100,50);

然后您可以设置连接点[x0,y0]到[x1,y1]的线段上的像素,如下所示:

// Refer to: http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript

function bline(x0, y0, x1, y1) {
  var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
  var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1; 
  var err = (dx>dy ? dx : -dy)/2;        
  while (true) {
    setPixel(x0,y0);
    if (x0 === x1 && y0 === y1) break;
    var e2 = err;
    if (e2 > -dx) { err -= dy; x0 += sx; }
    if (e2 < dy) { err += dx; y0 += sy; }
  }
}

其他Html Canvas Path元素具有类似的算法,可以计算需要沿路径设置的每个[x,y]像素。

Html Canvas曲线路径

每个弯曲的Path元素由数学方程定义。通过使用这些方程式间隔过采样值,您可以沿着曲线获得每个[x,y]。

例如,以间隔(T)的获取[x,y]沿着二次曲线的公式如下。 T是介于0.00和1.00之间的值,其中T == 0.00位于曲线的起点,T == 1.00位于曲线的末端。

// Get [x,y] at interval T along a Quadratic Curve
var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; 
var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; 

您必须沿曲线过采样间隔(T),否则您可能会错失沿该曲线的[x,y]点。这是因为与线不同,曲线公式不能获取均匀间隔的点。这是一个过采样的示例,其中1000 T路点在0.00和1.00之间。

// Oversample T along a quadratic curve to be sure to catch all [x,y]
// Some pixels will harmlessly be set more than once, 
// but it's likely that all desired pixels will be set
for(var i=0;i<1000;i++){
    var T=i/1000;
    var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x; 
    var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y; 
    setPixel(x,y,color);
}

沿着三次贝塞尔曲线获取[x,y]点的公式如下:

// De Casteljau's algorithm which calculates points along a cubic Bezier curve
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
    var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
    var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
    return({x:x,y:y});
}

// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T
    + (3 * b + T * (-6 * b + b * 3 * T)) * T
    + (c * 3 - c * 3 * T) * t2
    + d * t3;
}

以指定弧度角沿弧<获取[x,y]点的公式如下:

// Trigonometry to calculate [x,y] at a specified angle
function get ArcXYatRadianAngle(centerX,centerY,radius,radianAngle){
    var x=centerX+radius*Math.cos(radianAngle);
    var y=centerY+radius*Math.sin(radianAngle);
}

Html Canvas Operators

设置 globalAlpha 就像将任何像素的alpha元素设置为0到255之间的所需值(完全透明和完全不透明之间)一样简单。

即使 globalCompsiteOperation 也相对简单。

  • 绘制一个新的[x,y]像素值以应用于像素阵列。
  • 从像素阵列中读取现有像素值。
  • 使用以下选项应用所需的合成效果:
    • 保留现有像素值
    • 使用新像素值覆盖
    • 将像素的alpha值设置为0(使其透明)

提示:这种将现有像素与替换像素进行比较的方法也可用于将混合滤镜应用于像素! ; - )

剪切比较困难,因为您必须确保只在指定的剪切区域内应用新的像素值。您可以使用微积分来确定允许的绘图区域,但更直接的是:

  • 您已经拥有第一个像素阵列(称之为“绘图阵列”)
  • 创建第二个像素数组以保存剪切路径定义的像素(将第二个数组称为“剪切数组”)
  • 将新像素应用于绘图数组时,请先查阅剪裁数组。如果裁剪数组像素是SET,则设置绘图数组中的像素。如果裁剪数组像素是UNSET,则不要改变绘图数组中的那个像素。

呼!好吧,这应该让你开始...祝你的项目好运!

答案 1 :(得分:2)

  

我真正想知道的是,是否有某种方法可以获得这个像素   数据没有实际呈现在某个地方。

原则上没有,除了使用画布(技术上是位图)来光栅化浏览器中的矢量数据(路径)之外,没有办法获取像素数据。

浏览器通常使用底层图形核心系统(即DirectX等)进行光栅化。我们无法通过浏览器访问它(除非您修改它,否则也无法访问V8 / node.js)。这个子系统将处理线,圆,弧和椭圆到像素的所有光栅化,以及填充多边形等等,但不是通过构建点位置数组而是仅仅“走”一个斜率(简单的当然 - 填充使用不同的方法,但基于行扫描。)

对于线条,这意味着当前像素将仅例如相对于前一像素临时注册,对于圆形/椭圆形,像素通常在此处和那里被镜像。但实际上,在光栅化发生后,没有可以提取的绝对或相对像素位置的配准。

如果您使用的是每个像素的位置的数组,那么您需要实现自己的某种算法解决方案。但除非你因为一个非常特殊的原因需要它,否则我无法理解为什么你不会使用画布来为你做更快的事情,或者直接在向量空间中工作,只在需要时光栅化它们。

你可以使用JavaScript自己使用各种线算法实现这一点,以及你有什么。请参阅markE对首发的出色回答。我也做了Retro-Canvas一段时间,您可以查看JavaScript中使用的算法。它们也进行了优化。

除此之外,如果您想要画布的灵活性/兼容性,则需要实现2D matrix的旋转,缩放和翻译。

您需要注意的一件事是,通过这种方法,您将消除抗锯齿,这对于平滑的结果非常重要。如果你需要,你必须在顶部添加这些东西(你也可以尝试将你的内部线条与在画布上绘制的内容线同步,但它们需要完全相同,可能在那个阶段可能会让你陷入麻烦Linux,Mac,Windows等使用不同的图形子系统,结果可能有微小的变化),并要求您注册具有不同颜色/ alpha值的其他像素。

部分资源:

这些是任何典型多边形形状的基本构建块(弧是圆的一部分,所以我没有包含它,但是存在优化的方法)。

您还可以使用Catmull / Rom和cardinal splines以及Bezier,它通常实现为2. order(二次,一个控制点)和3. order(两个控制点),如果你想要能够制作更平滑的形状,字体轮廓等等。

另一种可能的方法是在黑色或透明背景上以白色在离屏画布上绘制形状,然后扫描每一行并将其找到的每个像素与其在阵列中的位置和相对颜色(到alpha)相对应。但是,这不会给你连续的行,只是每个像素的位置,无论它们是什么......