如何避免"抗锯齿" HTML5 Canvas中的效果

时间:2016-11-30 04:21:56

标签: html html5 canvas html5-canvas

为什么以下代码会将行的宽度更改为2像素而不是最终为1?

var canvas = document.getElementById('c');
var context = canvas.getContext('2d');

function clear() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}

function draw() {
  context.save();
  context.beginPath();
  context.scale(zoom, zoom);
  context.moveTo(100, 50);
  context.lineTo(100, 100);
  context.restore();
  context.stroke();
}

var zoom = 1.0;

$('#c').mousewheel(function(e) {
  if (e.originalEvent.deltaY < 0) {
    zoom *= 1.1;
  } else {
    zoom /= 1.1;
  }
  clear();
  draw();
});

draw();

你可以在这里试试 - https://jsfiddle.net/818j0646/

只是尝试放大/缩小,你会注意到我在说什么:

enter image description here

我该如何避免这种行为?我需要我的线总是保持1个宽度,没有这样的&#34;抗锯齿&#34;效果。

1 个答案:

答案 0 :(得分:2)

您可以添加相对于缩放的行宽支持(只需确保在笔划后应用 >或在绘制任何内容之前返回所有设置):

restore()

<强> Modified fiddle

如果你想保留大约1个像素而不管比例,你可以反转线宽公式:

var lineWidth = 1;                       // line width

function draw() {
  context.save();
  context.beginPath();
  context.scale(zoom, zoom);
  context.lineWidth = zoom * lineWidth;  // line-width * zoom
  context.moveTo(100, 50);
  context.lineTo(100, 100);
  context.stroke();
  context.restore();                     // restore last
}

<强> Result

但是,在某些尺度上会出现影响抗锯齿处理的小舍入误差。

解决方案

避免此问题的唯一真正方法是手动将矩阵应用于表示线的点,使值成为整数,然后使用Bresenham或IMO将这些值的结果呈现为一行,更好更快{{ 3}}算法和via context.lineWidth = 1 / (zoom * lineWidth); ,逐像素,最后将其推送到位图。当然,您可以将所有这些包装成单个函数。

这也意味着您需要跟踪矩阵。在较新的浏览器中,您可以使用ImageData并尽快使用currentTransform来获取当前转换,或者您可以使用自定义矩阵解决方案来实现跨浏览器和向后兼容性(这里有很多,这里是{{3 }})。

目前无法为光栅化到画布的矢量关闭消除锯齿功能。

演示

我没有在这个快速演示中实现边界检查,但这也是你需要实现的,所以线条不会缠绕(即只有当x> = 0&amp; &amp; x&lt; width etc. ..每像素。)

&#13;
&#13;
getTransform()
&#13;
window.onload = function() {
  
var canvas = document.getElementById('c');
var context = canvas.getContext('2d');
var matrix = new Matrix();
var zoom = 1.0;

function clear() {
	context.clearRect(0, 0, canvas.width, canvas.height);
}

function draw() {
  matrix.reset();  // replaces save/restore
  matrix.scale(zoom, zoom);

  // manually draw line via matrix and EFLA
  line(context, 100, 50, 100, 100);
}

// custom line function
function line(context, x1, y1, x2, y2) {

  // instead of transforming context, apply matrix to points:
  var p1 = matrix.applyToPoint(x1, y1);
  var p2 = matrix.applyToPoint(x2, y2);

  // create a bitmap (for demo), or obtain an existing one (getImageData)
  var idata = context.createImageData(canvas.width, canvas.height);
  var data32 = new Uint32Array(idata.data.buffer);
  
  _line(data32, p1.x|0, p1.y|0, p2.x|0, p2.y|0, canvas.width);
  context.putImageData(idata, 0, 0);
}

// EFLA line algorithm
function _line(data, x1, y1, x2, y2, w) {

	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(data, (x1 + i * mul)|0, y1 + i, w)
	}
	else {
		y1 += 0.5;
		for (i = 0; i !== ll; i += dlt)
			setPixel(data, x1 + i, (y1 + i * mul)|0, w)
	}
}

// Set a pixel (black for demo)
function setPixel(data, x, y, w) {data[y * w + x] = 0xff000000}

$('#c').mousewheel(function(e) {
  e.preventDefault();
  if (e.originalEvent.deltaY < 0) {
    zoom *= 1.1;
  } else {
    zoom /= 1.1;
  }
  clear();
  draw();
});


draw();
  };
&#13;
body {overflow:hidden}
&#13;
&#13;
&#13;

<强> EFLA