1

我编写了一个脚本,它采用这样的图像(通常黑色是 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
            }
        }
    }
}

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

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

编辑

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

谢谢肯。

4

1 回答 1

5

您可以通过多种方式做到这一点。

技术1

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

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

结果是:

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

结果要好得多,如下所示:

演示 2

TEXT WITH BORDER DEMO 2

技巧3

最后一种技术的缺点是我们要求画布将文本渲染 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);

图像结果将与上一个相同:

演示 3

TEXT WITH BORDER DEMO 3

技巧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);
}

请注意,在这种情况下,您可能需要画两圈,因为小点不会有一个实心中心(例如,参见 i 上的点)。

在此处输入图像描述

在这个演示中有点粗糙,但为了举例。您可以通过为段设置不同的阈值来进行微调,并添加一个“内圈”,其中文本包含此处的小细节 ( i)。

TEXT WITH BORDER DEMO 4

结果

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

  • 字体几何本身(包括字体提示)。
  • 文本渲染的浏览器实现及其优化
  • 中央处理器
  • 硬件加速

例如,在没有硬件加速的基于 Atom 单核的计算机上,Firefox (Aurora) 中的演示 2 和 3 的时间均为 16 毫秒(有时文本版本的两倍)。

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

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

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

于 2013-07-23T12:49:45.560 回答