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

时间:2013-07-23 07:56:07

标签: javascript canvas

我写了a script,它拍的是这样的图片(通常黑色是alpha):

font

...并添加您喜欢的任何颜色的边框:

fonts with borders

然而,它不是很快。创建边框图层需要大约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
            }
        }
    }
}

基本上我想知道:有人知道一种不需要逐像素操作的替代方法吗?或者是否可以对上述逻辑进行重大优化?

我应该提到_getNonAlphaPixels的执行时间可以忽略不计。 _getBorderPixels的执行时间仅为总时间的17%。

修改

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

谢谢肯。

1 个答案:

答案 0 :(得分:5)

您可以通过多种方式完成此操作。

技术1

一种是使用内置的strokeText函数绘制文本的轮廓。设置lineWidth将确定边框的粗细。但是,结果并不总是令人满意:

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

结果:

Demo 1

TEXT WITH BORDER DEMO 1

文本和画布目前在子像素级别上并不那么精确,这与使用字体提示(或者更确切地说不使用),消除锯齿和其他方面有关。

技术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);

结果好多了,如下所示:

demo 2

TEXT WITH BORDER DEMO 2

技术3

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

为了改善这一点,我们至少可以通过将边框文本作为图像缓存一次来减少绘制文本的时间,并使用它来绘制边框,然后将最终文本绘制在顶部。

这里octx表示一个离屏画布上下文(c它自己的离屏画布),我们用它来绘制我们将用于边框的文本。然后我们将fillText替换为drawImage。请注意,我们将 baseline 设置为top,以便更容易控制文本最终的位置。

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);

图像结果与上一个相同:

demo 3

TEXT WITH BORDER DEMO 3

技术4

请注意,如果你想要更厚的边框,你可能想要考虑实际进行圆形绘制 - 字面意思 - 使用cos / sin等。原因是因为在更高的偏移处,边框将开始分离:

enter image description here

您可以改为使用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);
}

请注意,在这种情况下,您可能需要绘制两个回合,因为它们不具有实心的小点(例如,参见i上的点)。

enter image description here

这个演示中有点粗糙,但是举个例子。您可以通过为细分设置不同的阈值进行微调,也可以添加一个“内圆”,其中文本包含的内容就像这里的小细节(i)。

TEXT WITH BORDER DEMO 4

结果

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

  • 字体几何本身(包括字体提示)。
  • 文本呈现及其优化的浏览器实现
  • CPU
  • 硬件加速

例如,在没有硬件加速的基于Atom单核的计算机上,我在Firefox(Aurora)中获得了16ms的演示2和3(有时是文本版本的双倍)。

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

在慢速计算机上,sin / cos方法大约需要8到11毫秒(几次达到5毫秒 - JSFiddle不是测试性能的最佳位置)。

我目前无法访问其他硬件进行测试(此处的边距非常小,我不确定JavaScript是否可以选择它,我相信Firefox尤其如此) )但与使用手动像素操作相比,至少在任何情况下都会有很大的增长。