Как обрезать изображение холста по пути, а не по области

#javascript #svg #canvas

#javascript #svg #холст

Вопрос:

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

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

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

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

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

и вот код

 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:

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

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

 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_(26908986301).jpg/320px-Serene_Sunset_(26908986301).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_(26908986301).jpg/320px-Serene_Sunset_(26908986301).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() принимает <canvas> в качестве источника).

Комментарии:

1. Ваш ответ мне очень помог. Я на один шаг далек от того, что я хочу реализовать. Последний вопрос, есть ли способ создать несколько рисунков «назначения»? Я нарисовал 3 отдельных рисунка, которые имеют разную ширину линии (которая не связана с ними для выполнения 3 отдельных context.beginPath), но ничего не остается, потому что ну, ‘destination-in’ сохраняет содержимое холста, где новая форма и существующее содержимое перекрываются, и поскольку я каждый раз генерирую «3»новых» и «разделенных» фигуры, они рисуются после каждого обработанного содержимого.

2. Если все они имеют одинаковый фон, то сделайте это наоборот, используя source-atop : нарисуйте свои контуры поверх исходного кода, а затем нарисуйте изображение только там, где вы уже нарисовали, используя source-atop . Я отредактирую свой ответ.

3. ты мой герой, все заработало после выполнения того, что ты мне сказал 🙂 большое спасибо

4. могу ли я задать вам быстрый, связанный с этим вопрос о Canvas и SVG? Каковы возможные способы рисования объекта на SVG, который получен по типу изображения. Например, я получил изображение простого черного объекта в форме пончика на белом фоне. Я рисую это изображение на холсте и получаю все координаты пикселей, цвет которых равен #000000. И с этими координатами я рисую ломаную линию в SVG. есть ли какие-либо лучшие способы добиться этого? Я бы, конечно, хотел услышать ваше мнение.

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