【问题标题】:Complete Solution for Drawing 1 Pixel Line on HTML5 Canvas在 HTML5 Canvas 上绘制 1 像素线的完整解决方案
【发布时间】:2019-07-21 11:41:56
【问题描述】:

在 HTML5 画布上画 1 像素线总是有问题的。(参考http://jsbin.com/voqubexu/1/edit?js,output

绘制垂直/水平线的方法是x+0.5,y+0.5(参考Canvas line behaviour when 0 < lineWidth < 1)。 要在全球范围内执行此操作,ctx.translate(0.5, 0.5); 将是一个好主意。

但是,当涉及到对角线时,这种方法不起作用。它总是给出一个 2 像素的线。有没有办法阻止这种浏览器行为?如果没有,是否有可以解决此问题的软件包?

【问题讨论】:

  • 该小提琴中的代码行为是(0.5,0.5)偏移量和lineWidth=1, alpha=0.5,不是您想要的吗?对我来说,这在各个角度看起来都非常完美。
  • 您垃圾箱中的最后一个样本似乎很好。您确定您不是在尝试修复渲染过程的物理伪影吗?
  • 当您放大屏幕截图时,您可以看到线条实际上比想象的最小粗。它只是更亮,在高分辨率屏幕上看起来更薄。请注意,垂直和水平边也更轻以保持相同......它们根本不是黑色的。在图像 2 上可以看到正确的垂直和水平黑度......我想,如果没有任何帮助,那么必须直接使用自己的算法绘制像素。当然这会很慢......所以是的,需要解决方案。
  • 0.5 并不总是像素的实际大小。你应该得到像素比并除以它。

标签: javascript html5-canvas line


【解决方案1】:

您所指的“更宽”行来自浏览器自动完成的抗锯齿。

抗锯齿用于显示视觉上不那么锯齿状的线条。

没有逐个像素地绘制,目前无法禁用浏览器绘制的抗锯齿。

您可以使用 Bresenham 的线条算法通过设置单个像素来绘制线条。当然,设置单个像素会导致性能下降。

这是示例代码和演示:http://jsfiddle.net/m1erickson/3j7hpng0/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

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

    var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
    var data=imgData.data;

    bline(50,50,250,250);
    ctx.putImageData(imgData,0,0);

    function setPixel(x,y){
        var n=(y*canvas.width+x)*4;
        data[n]=255;
        data[n+1]=0;
        data[n+2]=0;
        data[n+3]=255;
    }

    // Refer to: http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript
    function bline(x0, y0, x1, y1) {
      var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
      var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1; 
      var err = (dx>dy ? dx : -dy)/2;        
      while (true) {
        setPixel(x0,y0);
        if (x0 === x1 && y0 === y1) break;
        var e2 = err;
        if (e2 > -dx) { err -= dy; x0 += sx; }
        if (e2 < dy) { err += dx; y0 += sy; }
      }
    }

}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

【讨论】:

  • 非常感谢您的回答。我想这是在他们改变标准之前的唯一方法。
【解决方案2】:

对我来说,只有结合不同的“像素完美”技术才能帮助归档结果:

  1. 获取并按像素比例缩放画布:

    pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio

  2. 在调整大小时缩放画布(避免画布默认拉伸缩放)。

  3. 将 lineWidth 与 pixelRatio 相乘以找到合适的“真实”像素线粗细:

    context.lineWidth = 厚度 * pixelRatio;

  4. 检查线的粗细是奇数还是偶数。将一半的 pixelRatio 添加到奇数粗细值的行位置。

    x = x + pixelRatio/2;

奇数行将放置在像素的中间。上面的那行是用来移动一点的。

  1. 使用图像渲染:像素化;

function getPixelRatio(context) {
  dpr = window.devicePixelRatio || 1,
    bsr = context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio || 1;

  return dpr / bsr;
}


var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
var pixelRatio = getPixelRatio(context);
var initialWidth = canvas.clientWidth * pixelRatio;
var initialHeight = canvas.clientHeight * pixelRatio;


window.addEventListener('resize', function(args) {
  rescale();
  redraw();
}, false);

function rescale() {
  var width = initialWidth * pixelRatio;
  var height = initialHeight * pixelRatio;
  if (width != context.canvas.width)
    context.canvas.width = width;
  if (height != context.canvas.height)
    context.canvas.height = height;

  context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}

function pixelPerfectLine(x1, y1, x2, y2) {

  context.save();
  context.beginPath();
  thickness = 1;
  // Multiple your stroke thickness  by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;

  context.strokeStyle = "Black";
  context.moveTo(getSharpPixel(thickness, x1), getSharpPixel(thickness, y1));
  context.lineTo(getSharpPixel(thickness, x2), getSharpPixel(thickness, y2));
  context.stroke();
  context.restore();
}

function pixelPerfectRectangle(x, y, w, h, thickness, useDash) {
  context.save();
  // Pixel perfect rectange:
  context.beginPath();

  // Multiple your stroke thickness by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;
  context.strokeStyle = "Red";
  if (useDash) {
    context.setLineDash([4]);
  }
  // use sharp x,y and integer w,h!
  context.strokeRect(
    getSharpPixel(thickness, x),
    getSharpPixel(thickness, y),
    Math.floor(w),
    Math.floor(h));
  context.restore();
}

function redraw() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  
  pixelPerfectLine(50,50,250,250);
  pixelPerfectLine(120,0,120,250);
  pixelPerfectLine(122,0,122,250);
  pixelPerfectRectangle(10, 11, 200.3, 43.2, 1, false);
  pixelPerfectRectangle(41, 42, 150.3, 43.2, 1, true);
  pixelPerfectRectangle(102, 100, 150.3, 243.2, 2, true);
}

function getSharpPixel(thickness, pos) {

  if (thickness % 2 == 0) {
    return pos;
  }
  return pos + pixelRatio / 2;

}

rescale();
redraw();
canvas {
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  width: 100vh;
  height: 100vh;
}
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

Resize 事件不会在 snipped 中触发,因此您可以在 github 上尝试文件

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-02
    • 2017-12-21
    • 2011-08-28
    • 1970-01-01
    • 2019-02-28
    • 1970-01-01
    • 2013-09-26
    • 1970-01-01
    相关资源
    最近更新 更多