Рисование четких линий 1px на холсте HTML5 при использовании преобразований - PullRequest
0 голосов
/ 13 ноября 2018

Я рисую много 1px линий на элементе HTML5 canvas в моем коде.Код чертежа выглядит примерно так: переменная 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();

Одна проблема с этим кодом заключается в том, что линии размером 1 пиксель размыты, потому что я рисую за границами пикселей.Я пытался адаптировать код для привязки линий к ближайшим пикселям, как показано ниже:

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, чтобы получитьчеткие линии.Но я не уверен, как этого добиться, когда весь холст преобразуется, и я не рисую в окончательной системе координат.Поскольку мои попытки исправить это до сих пор не увенчались успехом, похоже, я что-то здесь упускаю или совершаю какую-то другую ошибку в пути.

Как рисовать четкие линии в 1 пиксель на холсте при преобразовании всего моегохолст с помощью setTransform?Как именно мне нужно округлить координаты, чтобы привязать результирующие строки к пикселям?

1 Ответ

0 голосов
/ 14 ноября 2018

Поскольку кажется, что ваше преобразование не имеет свойства перекоса или поворота, возможно, проще всего будет не преобразовывать ваш контекст, а скорее масштабировать и перевести все координаты.

В настоящее время вы устанавливаете для lineWidth значение 1 / zoom, учитывая, насколько хороши компитеры с точностью по математике, вам будет трудно закончить рисование идеального штриха в 1 пиксель с этим, только несколько значений увеличения будут, и если вы захотите чтобы ограничить ваш масштаб до этих значений, вы получите прерывистый зум.

Вместо этого всегда сохраняйте 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...