Javascript 如何提高在画布中非alpha像素周围手动添加边框的速度

Javascript 如何提高在画布中非alpha像素周围手动添加边框的速度,javascript,canvas,Javascript,Canvas,我写过这样的图片(通常黑色是alpha): …并添加任意颜色的边框: 但是它不是很快。创建边框层作为这个小字体的画布大约需要130毫秒。更大的字体需要更长的时间 逻辑很简单: /* This is more or less psuedo-code. */ // Blank image data where I will put the border. var newData = newContext.getImageData(0, 0, canvas.width, canvas.heigh

我写过这样的图片(通常黑色是alpha):

…并添加任意颜色的边框:

但是它不是很快。创建边框层作为这个小字体的画布大约需要130毫秒。更大的字体需要更长的时间

逻辑很简单:

/* This is more or less psuedo-code. */

// Blank image data where I will put the border.
var newData = newContext.getImageData(0, 0, canvas.width, canvas.height);

// The image I will be analyzing.
var oldData = oldContext.getImageData(0, 0, this.data.width, this.data.height);

// Loop through every pixel in oldData and remember where non-alpha pixels are.
var fontPixels = this._getNonAlphaPixels(oldData);

// Loop through relevant pixels, remember neighboring pixels, and add border.
for (var px in fontPixels) {
    for (var py in fontPixels[px]) {

        var borderPixels = this._getBorderPixels(px, py);
        for (var bx in borderPixels) {
            for (var by in borderPixels[bx]) {

                if (typeof fontPixels[bx] !== 'undefined' && 
                    typeof fontPixels[bx][by] !== 'undefined') 
                {
                    continue; // Do not draw borders inside of font.
                }

                newData.data[((newData.width * by) + bx) * 4] = color.red;
                newData.data[((newData.width * by) + bx) * 4 + 1] = color.green;
                newData.data[((newData.width * by) + bx) * 4 + 2] = color.blue;
                newData.data[((newData.width * by) + bx) * 4 + 3] = 255; //alpha
            }
        }
    }
}
基本上我想知道:有人知道一种不需要逐像素操作的替代方法吗?或者,是否可以对上述逻辑进行重大优化

我应该提到,
\u getNonAlphaPixels
的执行时间可以忽略不计。而
\u getBorderPixels
的执行时间仅为总时间的17%

编辑

下面选择的答案非常有效。我的解决方案和下面的解决方案之间唯一的显著区别是,每当绘制文本时,我都会绘制一个图像(而不是字体)


谢谢你,肯。

你可以用几种方法来做到这一点

技术1 一种是使用内置的
strokeText
函数绘制文本的轮廓。设置
lineWidth
将确定边框的厚度。然而,结果并不总是令人满意的:

ctx.strokeStyle = color;
ctx.font = font;
ctx.lineWidth = 2;
ctx.strokeText(txt, x, y);
结果:

文本和画布目前在亚像素级别上不够精确,这与字体暗示的使用方式(或者更确切地说是未使用)、抗锯齿和其他方面有关

技术2 在任何情况下,您都可以通过手动在“圆圈”中绘制文本来创建边框,从而获得更好的结果:

var thick = 2;

ctx.fillStyle = color;
ctx.font = font;

ctx.fillText(txt, x - thick, y - thick);
ctx.fillText(txt, x, y - thick);
ctx.fillText(txt, x + thick, y - thick);
ctx.fillText(txt, x + thick, y);
ctx.fillText(txt, x + thick, y + thick);
ctx.fillText(txt, x, y + thick);
ctx.fillText(txt, x - thick, y + thick);
ctx.fillText(txt, x - thick, y);

ctx.fillStyle = '#fff';
ctx.fillText(txt, x, y);
如图所示,结果要好得多:

技术3 最后一种技术的缺点是,我们要求canvas渲染文本9次-这是浪费时间-理论上。。。(见结果)

为了改善这一点,我们至少可以将绘制文本的次数减少到两次,方法是将边框文本缓存一次作为图像,并使用它绘制边框,然后在顶部绘制最终文本

这里,
octx
表示屏幕外画布上下文(
c
屏幕外画布本身),我们将在其中绘制用于边框的文本。然后,我们将圆形的
fillText
替换为
drawImage
。请注意,我们将“基线”设置为“顶部”,以便更容易地控制文本的结束位置

octx.textBaseline = ctx.textBaseline = 'top';
octx.fillStyle = color;
octx.font = ctx.font = font;
octx.fillText(txt, 0, 0);

ctx.drawImage(c, x - thick, y - thick);
ctx.drawImage(c, x, y - thick);
ctx.drawImage(c, x + thick, y - thick);
ctx.drawImage(c, x + thick, y);
ctx.drawImage(c, x + thick, y + thick);
ctx.drawImage(c, x, y + thick);
ctx.drawImage(c, x - thick, y + thick);
ctx.drawImage(c, x - thick, y);

ctx.fillStyle = '#fff';
ctx.fillText(txt, x, y);
图像结果将与前面的相同:

技术4 请注意,如果你想要更厚的边框,你可能会想考虑使用COS/Sin等来实际地画一个圆形图。原因是因为在较高的偏移量下,边界将开始分离:

您可以使用Cos/Sin计算在文字圆圈中绘制文本,而不是添加一组绘图:

function drawBorderText(txt, x, y, font, color) {

    var thick = 7, 
        segments = 4,  /// number of segments to divide the circle in
        angle = 0,     /// start angle
        part,          /// degrees per segment, see below

        i = 0, d2r = Math.PI / 180;

    /// determine how many parts are needed. I just
    /// started with some numbers in this demo.. adjust as needed
    if (thick > 1) segments = 6;
    if (thick > 2) segments = 8;
    if (thick > 4) segments = 12;

    part = 360 / segments;

    ctx.fillStyle = color;
    ctx.font = font;

    /// draw the text in a circle
    for(;i < segments; i++) {
        ctx.fillText(txt, x + thick * Math.cos(angle * d2r),
                          y + thick * Math.sin(angle * d2r));
        angle += part;
    }

    ctx.fillStyle = '#fff';
    ctx.fillText(txt, x, y);
}
函数drawBorderText(txt、x、y、字体、颜色){
var厚度=7,
segments=4,///要分割圆的段数
角度=0,///起始角度
零件,每段///度,见下文
i=0,d2r=Math.PI/180;
///确定需要多少零件。我只是
///从本演示中的一些数字开始。根据需要进行调整
如果(厚度>1)段=6;
如果(厚度>2)段=8;
如果(厚度>4)段=12;
部分=360/段;
ctx.fillStyle=颜色;
ctx.font=font;
///把文本画成一个圆圈
对于(;i<段;i++){
ctx.fillText(txt,x+thick*Math.cos(角度*d2r),
y+thick*Math.sin(角度*d2r));
角度+=零件;
}
ctx.fillStyle='#fff';
ctx.fillText(txt,x,y);
}
请注意,在这种情况下,您可能需要画两轮,因为对于小点,它们没有实心中心(例如,请参见i上的点)

在这个演示中有点粗糙,但举个例子。您可以通过为段设置不同的阈值进行微调,也可以在文本包含类似于此处的小细节(
i
)的地方添加“内圆”

结果 请注意,结果将取决于各种因素:

  • 字体几何体本身(包括字体提示)
  • 文本呈现的浏览器实现及其优化
  • 中央处理器
  • 硬件加速
例如,在一台没有硬件加速的基于Atom单核的计算机上,我在Firefox(Aurora)中的demo 2和demo 3都获得了16ms的速度(有时是文本版本的两倍)

在同一台计算机上的Chrome(Canary)中,基于文本的使用1-3毫秒,而缓存的使用大约5毫秒

sin/cos方法在一台速度较慢的计算机上大约需要8-11毫秒(几次达到5毫秒——JSFIDLE不是测试性能的最佳场所)


我目前无法访问其他硬件进行测试(这里的利润空间非常小,我不确定JavaScript是否能够接受,我认为Firefox尤其如此)但至少在任何情况下,与使用手动像素操作相比,您都会有很大的提高。

您可以通过多种方式实现这一点

技术1 一种是使用内置的
strokeText
函数绘制文本的轮廓。设置
lineWidth
将确定边框的厚度。然而,结果并不总是令人满意的:

ctx.strokeStyle = color;
ctx.font = font;
ctx.lineWidth = 2;
ctx.strokeText(txt, x, y);
结果:

文本和画布目前在亚像素级别上不够精确,这与字体暗示的使用方式(或者更确切地说是未使用)、抗锯齿和其他方面有关

技术2 在任何情况下,您都可以通过手动在“圆圈”中绘制文本来创建边框,从而获得更好的结果:

var thick = 2;

ctx.fillStyle = color;
ctx.font = font;

ctx.fillText(txt, x - thick, y - thick);
ctx.fillText(txt, x, y - thick);
ctx.fillText(txt, x + thick, y - thick);
ctx.fillText(txt, x + thick, y);
ctx.fillText(txt, x + thick, y + thick);
ctx.fillText(txt, x, y + thick);
ctx.fillText(txt, x - thick, y + thick);
ctx.fillText(txt, x - thick, y);

ctx.fillStyle = '#fff';
ctx.fillText(txt, x, y);
如图所示,结果要好得多:

技术3 最后一种技术的缺点是,我们要求画布渲染文本9次-