画布:使用渐变渐变(模拟角度渐变)绘制大量元素

时间:2014-03-24 22:43:38

标签: javascript optimization canvas gradient

这个项目http://biduleohm.free.fr/ledohm/(对不起,用户界面是法语但代码是英文)我需要一个角度渐变,但它不存在于原生中,所以我用线性渐变实现了它在一条线上,我画线越来越长,形成一个三角形。结果图形正常,但速度不是很好(125个三角形为1850毫秒)。它位于[Répartition]选项卡中,如果其中一个输入上有一个keyup事件,它会重绘,不要害怕明显的慢,我限制为每2000毫秒最多重绘一次。

在我对整个三角形使用简单的线性渐变之前(但这与现实不符)并且速度还可以,它会在不到一秒的时间内绘制出数千个三角形。使用了这个功能:

drawFrontLightForColor : function(x, y, w, h, color) {
    var x2 = x - w;
    var x3 = x + w;
    var gradient = Distri.frontCanvas.createLinearGradient(x2, y, x3, y);
    gradient.addColorStop(0,   'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
    gradient.addColorStop(0.5, 'rgba(' + color + ', ' + (color == Distri.lightColors.cw ? Distri.lightCenterAlphaCw : Distri.lightCenterAlphaOther) + ')');
    gradient.addColorStop(1,   'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
    Distri.frontCanvas.fillStyle = gradient;
    Distri.frontCanvas.beginPath();
    Distri.frontCanvas.moveTo(x, y);
    Distri.frontCanvas.lineTo(x2, (y + h));
    Distri.frontCanvas.lineTo(x3, (y + h));
    Distri.frontCanvas.lineTo(x, y);
    Distri.frontCanvas.fill();
    Distri.frontCanvas.closePath();
},

然后我切换到这个功能:

drawFrontLightForColor : function(x, y, w, h, centerColor, edgeColor) {
    var ratio = w / h;
    var tmpY;
    var tmpW;
    var x2;
    var x3;
    var gradient;
    Distri.frontCanvas.lineWidth = 1;
    for (var tmpH = 0; tmpH < h; tmpH++) {
        tmpY = y + tmpH;
        tmpW = Math.round(tmpH * ratio);
        x2 = x - tmpW;
        x3 = x + tmpW;
        gradient = Distri.frontCanvas.createLinearGradient(x2, tmpY, x3, tmpY);
        gradient.addColorStop(0, edgeColor);
        gradient.addColorStop(0.5, centerColor);
        gradient.addColorStop(1, edgeColor);
        Distri.frontCanvas.beginPath();
        Distri.frontCanvas.moveTo(x2, tmpY);
        Distri.frontCanvas.lineTo(x, tmpY);
        Distri.frontCanvas.lineTo(x3, tmpY);
        Distri.frontCanvas.strokeStyle = gradient;
        Distri.frontCanvas.stroke();
        Distri.frontCanvas.closePath();
    }
},

您可以找到整个来源here

我不能将beginPath,stroke,closePath放在循环之外,因为每次迭代都会改变渐变(我已尝试但是它使用了每一行的最后一个渐变(具有讽刺意味的是,它与第一个功能...)这是可以理解的,但不是我想要的。)

我接受任何建议(包括重做整个功能并修改他的调用者以外包一些代码)来提高速度,比如5倍(理想情况下更多)。

2 个答案:

答案 0 :(得分:0)

我认为你从一开始就采取了错误的方式:当做了如此多的颜色变化时,你最好在像素级别进行操作。
所以是的,可以使用webgl像素着色器,但是你必须努力让所有平台上的样板运行正常(或者让lib为你做这个)。
无论如何,这里有一个完美的解决方案,并且足够快(几毫秒):使用原始像素数据,使用相关功能逐个更新它们,然后绘制结果。

这样做的步骤是:
- 创建一个与画布大小相同的缓冲区 - 迭代它的像素,跟踪点的x,y - 规范化坐标,使其符合您的空间&#39; - 计算所有数据中标准化(x,y)的值 - 从该值中写出一种颜色(在我的例子中,我选择灰度) - 将整个缓冲区绘制到画布上。

我做了一个jsfiddle,这里有4个数据点的结果:

enter image description here

小提琴在这里: http://jsfiddle.net/gamealchemist/KsM9c/3/

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var width = canvas.width,
    height = canvas.height;

// builds an image for the target canvas
function buildImage(targetCanvas, valueForXY, someData) {
    var width = targetCanvas.width;
    var height = targetCanvas.height;
    var tempImg = ctx.createImageData(width, height);
    var buffer = tempImg.data;
    var offset = 0;
    var xy = [0, 0];
    function normalizeXY(xy) {
        xy[0] = xy[0] / width ;
        xy[1] = xy[1] / height;
    }
    for (var y = 0; y < height; y++)
    for (var x = 0; x < width; x++, offset += 4) {
        xy[0] = x; xy[1]=y;
        normalizeXY(xy);
        var val = Math.floor(valueForXY(xy, someData) * 255);
        buffer[offset] = val;
        buffer[offset + 1] = val;
        buffer[offset + 2] = val;
        buffer[offset + 3] = 255;
    }
    ctx.putImageData(tempImg, 0, 0);
}


// return normalized (0->1) value for x,y and
//      provided data.
//    xy is a 2 elements array
function someValueForXY(xy, someData) {
    var res = 0;
    for (var i = 0; i < someData.length; i++) {
        var thisData = someData[i];
        var dist = Math.pow(sq(thisData[0] - xy[0]) + sq(thisData[1] - xy[1]), -0.55);
        localRes = 0.04 * dist;
        res += localRes;
    }
    if (res > 1) res = 1;
    return res;
}

var someData = [
    [0.6, 0.2],
    [0.35, 0.8],
    [0.2, 0.5],
    [0.6, 0.75]
];

buildImage(canvas, someValueForXY, someData);

// ------------------------
function sq(x) {
    return x * x
}

答案 1 :(得分:0)

事实上,GameAlchemist的解决方案并不快,或者我做错了什么。我只为顶视图实现了这个算法,因为前视图要复杂得多。

对于120个灯,顶视图使用旧代码需要100-105毫秒,使用此代码需要1650-1700毫秒(而且它在新代码中仍然缺少一些东西,例如颜色):

drawTopLightForColor_ : function(canvasW, canvasD, rampX, rampY, rampZ, ledsArrays, color) {
    function sq(x) {
        return x * x;
    }
    var tmpImg = Distri.topCanvasCtx.createImageData(canvasW, canvasD);
    var rawData = tmpImg.data;
    var ledsArray = ledsArrays[color];
    var len = ledsArray.length;
    var i = 0;
    for (var y = 0; y < canvasD; y++) {
        for (var x = 0; x < canvasW; x++, i += 4) {
            var intensity = 0;
            for (var j = 0; j < len; j++) {
                intensity += 2 * Math.pow(
                    sq((rampX + ledsArray[j].x) - x) +
                    sq((rampZ + ledsArray[j].y) - y),
                    -0.5
                );
            }
            if (intensity > 1) {
                intensity = 1;
            }
            intensity = Math.round(intensity * 255);
            rawData[i] = intensity;
            rawData[i + 1] = intensity;
            rawData[i + 2] = intensity;
            rawData[i + 3] = 255;
        }
    }
    Distri.topCanvasCtx.putImageData(tmpImg, 0, 0);
},

我做错了吗?