如何保持线条的边缘像素不是半透明的? [英] How can I keep the edge pixels of lines from being semi-transparent?

查看:121
本文介绍了如何保持线条的边缘像素不是半透明的?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个HTML画布,它的像素宽度和高度都是32x。然而,当我在上面画线时,我注意到线的边缘像素是半透明地绘制的(正好是半透明的)。有没有办法阻止它?



这幅图,红线是从一点到另一点的一条线。我希望所有的块都是黑色或#FF0000红色。



N.B。我已经使用canvas.translate()来正确对齐像素,并且正在使用此解决方案post> 来渲染扩展像素的离散块。

解决方案

问题背景



Canvas使用抗锯齿功能使图画看起来更加平滑,这就是为什么它会在这里和那里填充半透明像素(请参阅这个解释)。



平滑(又名插值)可以关闭,但仅限于图像(顾名思义, ctx.imageSmoothingEnabled = false )。



解决方案



为此,需要实施线渲染器。但是,典型的线条算法仅支持1像素宽的线。这包括Bresenham以及EFLA(由 Po-Han Lin 提供的极快速线算法),后者比Bresenham。



对于厚度超过1个像素的线条,您需要找到切线角度,然后沿着主线渲染每个线段。

我提供了两种实现,我在某种程度上进行了优化。他们都不需要访问位图本身,只需提供上下文。

您需要记住的唯一事情是使用 fillStyle (和 fill() )而不是 strokeStyle (和 stroke())来设置它的颜色。您可以在填充前生成多行文本,这通常比填充每个线段要快,只要它们使用相同的颜色即可。可选地,您可以使用图像数据并在其中设置像素直接使用,但是速度较慢,并且需要使用CORS以便在使用图像的情况下使用(如果首选的话,使用带有Uint32视图的位图)还有一些特殊的技巧来加速这种方法,但这里不讨论它们。 >

EFLA(极快线算法)



这个算法是用来绘制连续多边形线的地方,最后一点没有设置。但在下面的实现中,我们手动设置它,因此它可以用于单线段。



访问上面的链接网站以获得更深入的解释以及许可证)。



只要确保输入值是整数值:

  function lineEFLA(ctx,x1,y1,x2,y2){

var dlt,mul,yl = false,i,
sl = y2 - y1,
ll = x 2 - x 1,
lls = ll>> 31,
sls = sl>> 31; ((sl ^ sls) - sls>(ll ^ lls) - lls){
sl ^ = ll;


ll ^ = sl;
sl ^ = ll;
yl = true;
}

dlt = ll< 0? -1:1;
mul =(ll === 0)? sl:sl / ll;

if(yl){
x1 + = 0.5; //(i = 0; i!== ll; i + = dlt)setPixel((x1 + i * mul)| 0,y1 + i)预设为舍入
;
}
else {
y1 + = 0.5;
for(i = 0; i!== ll; i + = dlt)setPixel(x1 + i,(y1 + i * mul)| 0);
}
setPixel(x2,y2); //设置最后像素

函数setPixel(x,y){ctx.rect(x,y,1,1)}
}



Bresenham



这是传统线路算法,在过去许多应用程序和计算机中使用需要渲染一条简单的线。



该算法详细解释

 函数lineBresenham(ctx,x1,y1,x2, y2){

if(x1 === x2){//特殊情况,垂直线
ctx.rect(x1,Math.min(y1,y2),1,Math。 abs(y2-y1)+ 1);
return;
}

if(y1 === y2){//特殊情况,水平线
ctx.rect(Math.min(x1,x2),y1,Math。 abs(x2-x1)+ 1,1);
return;
}

var dx = Math.abs(x2-x1),sx = x1 < x2? 1:-1,
dy = Math.abs(y2-y1),sy = y1 < y2? 1:-1,
err =(dx> dy?dx:-dy)* 0.5; (!0){
ctx.rect(x1,y1,1,1);


if(x1 === x2&& y1 === y2)break;
var e2 = err;
if(e2> -dx){err - = dy; x1 + = sx;如果(e2 , y1 + = sy; }




包含缩放的实时演示



fillRect(0,0,ctx.canvas.width,ctx.canvas.height); // bg colorctx.scale(20,20); // scalectx.fillStyle =#f00; //在这种情况下线的颜色lineEFLA(ctx,0,0,17,20); // algo 1lineBresenham(ctx,3,0,20,20); // algo 2ctx.fill(); //填充rects,使用beginPath()为下一个功能线EFLA(ctx,x1,y1,x2,y2){/ * x1 | = 0; //确保值是整数值x2 | = 0; y1 | = 0; y2 | = 0; * / var dlt,mul,sl = y2-y1,ll = x2-x1,yl = false,lls = ll>> 31,sls = sl>> 31,我;如果((sl ^ sls)-sls>(ll ^ lls)-lls){sl ^ = ll; ll ^ = sl; sl ^ = ll; yl = true; } dlt = ll< 0? -1:1; mul =(ll === 0)? sl:sl / ll;如果(yl){x1 + = 0.5;对于(i = 0; i!== ll; i + = dlt)setPixel((x1 + i * mul)| 0,y1 + i); } else {y1 + = 0.5;对于(i = 0; i!== ll; i + = dlt)setPixel(x1 + i,(y1 + i * mul)| 0); } setPixel(x2,y2); //设置最后一个像素函数setPixel(x,y){ctx.rect(x,y,1,1)}} function lineBresenham(ctx,x1,y1,x2,y2){if(x1 === x2){ //特殊情况,垂直线ctx.rect(x1,Math.min(y1,y2),1,Math.abs(y2-y1)+1);返回;如果(y1 === y2){//特殊情况,水平线ctx.rect(Math.min(x1,x2),y1,Math.abs(x2-x1)+1,1);返回; } var dx = Math.abs(x2-x1),sx = x1 < x2? 1:-1,dy = Math.abs(y2-y1),sy = y1 < y2? 1:-1,err =(dx> dy?dx:-dy)* 0.5; while(!0){ctx.rect(x1,y1,1,1);如果(x1 === x2&& y1 === y2)break; var e2 = err; if(e2> -dx){err - = dy; x1 + = sx; }如果(e2

< canvas width = 400 height = 400> ;< / canvas>

这些实现可以通过对垂直和水平线使用单个矩形()来进一步优化(显示为Bresenham,而不是EFLA)。 setPixel()用于灵活性(f.ex.它可以被重写为设置一个位图像素等)。


I am working with an HTML canvas blown up 32x its pixel width and height. When I draw lines on it, however, I notice that the edge pixels of lines are drawn semi-transparently (exactly half transparent). Is there a way to stop this?

In this picture, the red line is a single line from one point to the other. I would like all of the blocks to be either black or #FF0000 red.

N.B. I am already using canvas.translate() to align pixels properly and am using the solution in this post to render the expanded pixels in discrete blocks.

解决方案

Problem background

Canvas uses anti-aliasing to make drawings appear more smooth which is why it fills in semi-transparent pixels here and there (see this explanation for how that works).

Smoothing (aka interpolation) can be turned off, but only for images (ctx.imageSmoothingEnabled = false, as the name implies).

Solutions

For this a "line renderer" needs to be implemented. However, the typical line algorithms only supports lines of 1 pixel in width. This includes Bresenham as well as the EFLA (Extremely Fast Line Algorithm by Po-Han Lin), latter is faster than Bresenham.

For lines thicker than 1 pixel you would need to find tangent angle and then render each segment along the main line.

I provide both implementations below which I have optimized to some degree. None of them require access to the bitmap itself, just supply the context.

The only thing you need to remember is to use fillStyle (and fill()) instead of strokeStyle (and stroke()) to set its color. You can generate several lines before filling, which is generally faster than filling each line segment, provided they use the same color.

Optionally you could use image data and set pixels there directly, but that is slower and requires CORS in case you use images (use such a bitmap with a Uint32 view if this is preferred. There are also special tricks to speed up this approach, but they are not addressed here).

EFLA (Extremely Fast Line Algorithm)

This algorithm is intended where you want to draw continuous polygon lines, ie. the last point is not set. But in the following implementation we set it manually so it can be used for single line segments.

Visit the linked site above for a more in-depth explanation of it (as well as for license).

Just make sure input values are integer values:

function lineEFLA(ctx, x1, y1, x2, y2) {

    var dlt, mul, yl = false, i,
        sl = y2 - y1,
        ll = x2 - x1,
        lls = ll >> 31,
        sls = sl >> 31;

    if ((sl ^ sls) - sls > (ll ^ lls) - lls) {
        sl ^= ll;
        ll ^= sl;
        sl ^= ll;
        yl = true;
    }

    dlt = ll < 0 ? -1 : 1;
    mul = (ll === 0) ? sl : sl / ll;

    if (yl) {
        x1 += 0.5;  // preset for rounding
        for (i = 0; i !== ll; i += dlt) setPixel((x1 + i * mul)|0, y1 + i);
    }
    else {
        y1 += 0.5;
        for (i = 0; i !== ll; i += dlt) setPixel(x1 + i, (y1 + i * mul)|0);
    }
    setPixel(x2, y2);   // sets last pixel

    function setPixel(x, y) {ctx.rect(x, y, 1, 1)}
}

Bresenham

This is a classic line algorithm used in many applications and computers in the old days where a simple line needed to be rendered.

The algorithm is explained more in detail here.

function lineBresenham(ctx, x1, y1, x2, y2) {

    if (x1 === x2) {  // special case, vertical line
        ctx.rect(x1, Math.min(y1, y2), 1, Math.abs(y2 - y1) + 1);
        return;
    }

    if (y1 === y2) {  // special case, horizontal line
        ctx.rect(Math.min(x1, x2), y1, Math.abs(x2 - x1) + 1, 1);
        return;
    }

    var dx = Math.abs(x2 - x1), sx = x1 < x2 ? 1 : -1,
        dy = Math.abs(y2 - y1), sy = y1 < y2 ? 1 : -1,
        err = (dx > dy ? dx : -dy) * 0.5;

    while(!0) {
        ctx.rect(x1, y1, 1, 1);
        if (x1 === x2 && y1 === y2) break;
        var e2 = err;
        if (e2 > -dx) { err -= dy; x1 += sx; }
        if (e2 < dy)  { err += dx; y1 += sy; }
    }
}

Live demo including zoom

var ctx = document.querySelector("canvas").getContext("2d");

ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); // bg color
ctx.scale(20, 20); // scale

ctx.fillStyle = "#f00"; // color for line in this case

lineEFLA(ctx, 0, 0, 17, 20); // algo 1
lineBresenham(ctx, 3, 0, 20, 20); // algo 2

ctx.fill(); // fill the rects, use beginPath() for next

function lineEFLA(ctx, x1, y1, x2, y2) {

  /* x1 |= 0; // make sure values are integer values
   x2 |= 0;
   y1 |= 0;
   y2 |= 0;*/

  var dlt,
    mul,
    sl = y2 - y1,
    ll = x2 - x1,
    yl = false,
    lls = ll >> 31,
    sls = sl >> 31,
    i;

  if ((sl ^ sls) - sls > (ll ^ lls) - lls) {
    sl ^= ll;
    ll ^= sl;
    sl ^= ll;
    yl = true;
  }

  dlt = ll < 0 ? -1 : 1;
  mul = (ll === 0) ? sl : sl / ll;

  if (yl) {
    x1 += 0.5;
    for (i = 0; i !== ll; i += dlt)
      setPixel((x1 + i * mul) | 0, y1 + i);
  } else {
    y1 += 0.5;
    for (i = 0; i !== ll; i += dlt)
      setPixel(x1 + i, (y1 + i * mul) | 0);
  }
  setPixel(x2, y2); // sets last pixel

  function setPixel(x, y) {
    ctx.rect(x, y, 1, 1)
  }
}

function lineBresenham(ctx, x1, y1, x2, y2) {

  if (x1 === x2) { // special case, vertical line
    ctx.rect(x1, Math.min(y1, y2), 1, Math.abs(y2 - y1) + 1);
    return;
  }

  if (y1 === y2) { // special case, horizontal line
    ctx.rect(Math.min(x1, x2), y1, Math.abs(x2 - x1) + 1, 1);
    return;
  }

  var dx = Math.abs(x2 - x1),
    sx = x1 < x2 ? 1 : -1,
    dy = Math.abs(y2 - y1),
    sy = y1 < y2 ? 1 : -1,
    err = (dx > dy ? dx : -dy) * 0.5;

  while (!0) {
    ctx.rect(x1, y1, 1, 1);
    if (x1 === x2 && y1 === y2) break;
    var e2 = err;
    if (e2 > -dx) {
      err -= dy;
      x1 += sx;
    }
    if (e2 < dy) {
      err += dx;
      y1 += sy;
    }
  }
}

<canvas width=400 height=400></canvas>

Tip: These implementations can be optimized further by using a single rect() for vertical and horizontal lines (shown for Bresenham, not for EFLA). The setPixel() is for flexibility (f.ex. it can be rewritten to set a bitmap pixel instead etc.).

这篇关于如何保持线条的边缘像素不是半透明的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆