Как обрезать изображение холста по пути, а не по площади - PullRequest
1 голос
/ 02 апреля 2019

Я создаю инструмент рисования, и одна из функций показывает обрезанное изображение нарисованного контура.

Путь, который я нарисовал (изображение)

Например, на рисунке выше, белый контур указывает на то, что я нарисовал, как инструмент рисования.

Обрезанное изображение

А вот обрезанное изображение пути. Если вы посмотрите на изображение, то увидите, что оно обрезает изображение так, как если бы он был закрыт, и поэтому обрезает область изображения, а не путь.

и вот код

function crop({ image, points }) {
  return Observable.create(observer => {
    const { width, height } = getImageSize(image);
    const canvas = document.createElement('canvas') as HTMLCanvasElement;
    const context = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;

    context.beginPath();
    points.forEach(([x, y], idx) => {
      if (idx === 0) {
        context.moveTo(x, y);
      } else {
        context.lineTo(x, y);
      }
    });
    context.clip();

    context.drawImage(image);

    ...etc
}

Функция crop получает points, который состоит [координата x, координата y] [] отрисованного пути.

Есть ли способ показать изображение только тем путем, который я нарисовал?

1 Ответ

2 голосов
/ 02 апреля 2019

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

Контекст Canvas имеет различные параметры компоновки , позволяющие создавать сложные композиции из альфа-значения пикселей.

const ctx = canvas.getContext('2d');
const pathes = [[]];
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';

function begin() {
  canvas.width = this.width;
  canvas.height = this.height;
  ctx.lineWidth = 10;
  addEventListener('mousemove', onmousemove);
  addEventListener('mousedown', onmousedown);
  addEventListener('mouseup', onmouseup);
  anim();
  ctx.fillText("Use your mouse to draw a path", 20,50) 
}

function anim() {
  requestAnimationFrame(anim);
  if(dirty) draw();
  dirty = false;
}

function draw() {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.drawImage(bg, 0, 0);
  ctx.beginPath();
  pathes.forEach(path => {
    if(!path.length) return;
    ctx.moveTo(path[0].x, path[0].y);
    path.forEach(pt => {
      ctx.lineTo(pt.x, pt.y);
    });
  });
  // old drawings will remain on where new drawings will be
  ctx.globalCompositeOperation = 'destination-in';
  ctx.stroke();
  // reset
  ctx.globalCompositeOperation = 'source-over';
}

function onmousemove(evt) {
  if(!down) return;
  const rect = canvas.getBoundingClientRect();
  pathes[pathes.length - 1].push({
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top
  });
  dirty = true;
}
function onmousedown(evt) {
  down = true;
}
function onmouseup(evt) {
  down = false;
  pathes.push([]);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>

Не стесняйтесь смотреть на все варианты компоновки, в разных случаях требуются разные опции, например, если вам нужно нарисовать несколько путей, вы можете предпочесть сначала визуализировать ваши пути, а затем сохранить изображение только там, где вы уже сделали нарисовано с использованием опции source-atop:

const ctx = canvas.getContext('2d');
const pathes = [[]];
pathes[0].lineWidth = (Math.random() * 20) + 0.2;
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';

function begin() {
  canvas.width = this.width;
  canvas.height = this.height;
  addEventListener('mousemove', onmousemove);
  addEventListener('mousedown', onmousedown);
  addEventListener('mouseup', onmouseup);
  anim();
  ctx.fillText("Use your mouse to draw a path", 20,50) 
}

function anim() {
  requestAnimationFrame(anim);
  if(dirty) draw();
  dirty = false;
}

function draw() {
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  pathes.forEach(path => {
    if(!path.length) return;
    ctx.beginPath();
    ctx.lineWidth = path.lineWidth;
    ctx.moveTo(path[0].x, path[0].y);
    path.forEach(pt => {
      ctx.lineTo(pt.x, pt.y);
    });
    ctx.stroke();
  });
  // new drawings will appear on where old drawings were
  ctx.globalCompositeOperation = 'source-atop';
  ctx.drawImage(bg, 0, 0);
  
  // reset
  ctx.globalCompositeOperation = 'source-over';
}

function onmousemove(evt) {
  if(!down) return;
  const rect = canvas.getBoundingClientRect();
  pathes[pathes.length - 1].push({
    x: evt.clientX - rect.left,
    y: evt.clientY - rect.top
  });
  dirty = true;
}
function onmousedown(evt) {
  down = true;
}
function onmouseup(evt) {
  down = false;
  const path = [];
  path.lineWidth = (Math.random() * 18) + 2;
  pathes.push(path);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>

И помните, что у вас могут быть полотна, которые вы не добавите в документ, которые вы можете использовать в качестве слоев для создания действительно сложных композиций. (drawImage() принимает в качестве источника).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...