浏览器如何渲染canvas元素的内容

Kev*_*ose 0 html javascript html5-canvas web

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

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

mar*_*rkE 5

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

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

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

pixelArray=[...255,0,0,255...]  // where 255==red, 0==green, 0==blue, alpha==255
Run Code Online (Sandbox Code Playgroud)

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

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

假设您有一个代表假想网格上所有像素的数组:

var width=500;
var height=500;
var pixels=new Array(width*height);
Run Code Online (Sandbox Code Playgroud)

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

var x=200;
var y=30;
var arrayElement=y*width+x;
Run Code Online (Sandbox Code Playgroud)

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

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

setPixel(100,50,{r:255,g:0,b:0,a:255});
Run Code Online (Sandbox Code Playgroud)

您可以获得任何 [x,y] 的像素设置,如下所示:

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

var theColor=getPixel(100,50);
Run Code Online (Sandbox Code Playgroud)

然后你可以设置连接点 [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; }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Html 画布曲线路径

每个弯曲路径元素均由数学方程定义。您可以使用这些方程按一定时间间隔对值进行过采样,从而获得曲线上的每个 [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; 
Run Code Online (Sandbox Code Playgroud)

您必须沿曲线对间隔 (T) 进行过采样,否则可能会丢失该曲线上的 [​​x,y] 点。这是因为与直线不同,曲线公式不会获取均匀间隔的点。下面是一个在 0.00 和 1.00 之间使用 1000 T 航路点进行过采样的示例。

// 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);
}
Run Code Online (Sandbox Code Playgroud)

沿三次贝塞尔曲线获取 [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;
}
Run Code Online (Sandbox Code Playgroud)

沿指定弧度角获取 [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);
}
Run Code Online (Sandbox Code Playgroud)

Html 画布运算符

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

即使是globalCompsiteOperation也相对简单。

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

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

剪裁更加困难,因为您必须确保仅在指定的剪裁区域内应用新的像素值。您可以使用微积分来确定允许的绘图区域,但更简单的是:

  • 您已经有了第一个像素数组(称之为“绘图数组”)
  • 创建第二个像素数组来保存由剪切路径定义的像素(将此第二个数组称为“剪切数组”)
  • 当将新像素应用到绘图数组时,首先参考剪切数组。如果剪切数组像素已设置,则在绘图数组中设置该像素。如果剪切数组像素未设置,则不要更改绘图数组中的该像素。

哇!好吧,这应该可以让您开始...祝您的项目好运!