使用转换时在HTML5画布上绘制清晰的1px线

时间:2018-11-13 20:05:05

标签: javascript html canvas html5-canvas

我在代码中的HTML5 canvas元素上绘制了很多1px的线。绘图代码大致如下所示,在这种情况下,transform变量是使用d3-zoom设置的。 instructions.f32是一个Float32Array,其中包含我用于绘制线条的坐标。

 context.setTransform(
    transform.k,
    0,
    0,
    transform.k,
    transform.x,
    transform.y
  );
  context.lineWidth = 1 / transform.k;
  context.beginPath();
  for (let i = from; i < to; ++i) {

    let v1 = instructions.f32[i * 4 + 1];
    let v2 = instructions.f32[i * 4 + 2];

    // execute some moveTo/lineTo commands using v1 and v2 as coordinates
  }
  context.stroke();

此代码的一个问题是1px的线很模糊,因为我绘制的是像素边界。我尝试修改代码以将线对齐到最近的像素,如下所示:

let v1 = (Math.round(instructions.f32[i * 4 + 1] * transform.k) + 0.5) / transform.k;
let v2 = (Math.round(instructions.f32[i * 4 + 2] * transform.k) + 0.5) / transform.k;

但这仍会导致线条模糊,如下图所示(放大图像的屏幕截图):

enter image description here

如果我没有任何转换集,据我了解,我只需要将坐标舍入到最接近的像素并添加0.5即可获得清晰的线条。但是我不确定在整个画布转换后如何实现此目的,并且我没有绘制最终的坐标系。到目前为止,我的纠正尝试都失败了,看来我在这里遗漏了一些东西,或者在途中犯了其他错误。

使用setTransform转换整个画布时,如何在画布中绘制清晰的1px线?我该如何精确地四舍五入坐标才能将生成的线捕捉到像素?

1 个答案:

答案 0 :(得分:0)

由于似乎您的变换没有倾斜或旋转属性,所以最简单的方法可能是不变换上下文,而是缩放并转换所有坐标。

当前,您将lineWidth设置为1 / zoom,考虑到Compiters在Math精度方面的出色表现,您将很难用它绘制一个完美的1px笔画,只有几个zoom值可以,如果您愿意要将缩放限制在这些值,您会得到断断续续的缩放。

相反,始终将lineWidth保持为1px,缩放并转换所有坐标,然后再将它们四舍五入到最近的像素边界。

context.setTransform(1,0,0,1,0,0);
context.lineWidth = 1;
context.beginPath();
for (let i = from; i < to; ++i) {

  let v1 = instructions.f32[i * 4 + 1];
  let v2 = instructions.f32[i * 4 + 2];
  // scale and translate
  v1 = (v1 + transform.x) * transform.k;
  v2 = (v2 + transform.y) * transfrom.k;
  // round
  const r1 = Math.round(v1);
  const r2 = Math.round(v2);
  // to nearest px boundary
  v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
  v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

  // lineTo...
}

const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
const zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoom", zoomed);
const transform = {k: 1, x: 0, y: 0};
const context = canvas.getContext('2d');
d3.select('canvas')
  .call(zoom);
draw();
function draw() {

    context.setTransform(1,0,0,1,0,0);
    context.clearRect(0,0,canvas.width, canvas.height);
    context.lineWidth = 1;
    context.beginPath();
    for (let i = 0; i < pts.length; i+=2) {

      let v1 = pts[i];
      let v2 = pts[i + 1];
      // scale and translate
      v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transform.k;
      // round
      const r1 = Math.round(v1);
      const r2 = Math.round(v2);
      // to nearest px boundary
      v1 = r1 + (0.5 * Math.sign(r1 - v1) || 0.5);
      v2 = r2 + (0.5 * Math.sign(r2 - v2) || 0.5);

      context.lineTo(v1, v2);
    }
    context.stroke();
}
function zoomed() {
  const evt = d3.event;
  transform.k = evt.scale;
  transform.x = evt.translate[0];
  transform.y = evt.translate[1];
  draw();
}
canvas {border: 1px solid}
zoom with mousewheel and pan by dragging<br>
<canvas id="canvas"></canvas>
<script src="//d3js.org/d3.v3.min.js"></script>

但是您可能更喜欢精度较低,但锯齿不平整和简单的地板:

  v1 = (v1 + transform.x) * transform.k;
  v2 = (v2 + transform.y) * transform.k;
  // floor
  v1 = Math.floor(v1) + 0.5;
  v2 = Math.floor(v2) + 0.5;

  // lineTo

const pts = [60, 60, 60, 110, 100,110, 100, 90, 220, 90];
const zoom = d3.behavior.zoom()
    .scaleExtent([1, 10])
    .on("zoom", zoomed);
const transform = {k: 1, x: 0, y: 0};
const context = canvas.getContext('2d');
d3.select('canvas')
  .call(zoom);
draw();
function draw() {

    context.setTransform(1,0,0,1,0,0);
    context.clearRect(0,0,canvas.width, canvas.height);
    context.lineWidth = 1;
    context.beginPath();
    for (let i = 0; i < pts.length; i+=2) {

      let v1 = pts[i];
      let v2 = pts[i + 1];
      // scale and translate
      v1 = (v1 + transform.x) * transform.k;
      v2 = (v2 + transform.y) * transform.k;
      // floor
      v1 = Math.floor(v1) + 0.5;
      v2 = Math.floor(v2) + 0.5;
      context.lineTo(v1, v2);
    }
    context.stroke();
}
function zoomed() {
  const evt = d3.event;
  transform.k = evt.scale;
  transform.x = evt.translate[0];
  transform.y = evt.translate[1];
  draw();
}
canvas {border: 1px solid}
zoom with mousewheel and pan by dragging<br>
<canvas id="canvas"></canvas>
<script src="//d3js.org/d3.v3.min.js"></script>