在 p5.js 中使用 createGraphics 高效遮罩形状

Efficiently mask shapes using createGraphics in p5.js

我正在尝试在 p5.js 中创建各种形状并用特定的 patterns/drawings 填充它们。每个形状都有一个使用 createGraphics 生成的独特图案。由于我的不同形状无法覆盖我的所有基础 canvas,我正在考虑为我的图案创建更小尺寸的图形以提高性能。例如,如果我的基础 canvas 是 1000 * 1000 像素而我的形状只需要 50 * 50 像素并且我想用矩形图案填充它,我看不出创建 1000 * 1000 像素图案的意义图形并用我的 50 * 50 像素形状对其进行遮蔽。

我正在尝试使用单一形状和图案进行概念验证:

  1. 确定包含我的整个形状的矩形的宽度和高度。请注意,在绘制之前我知道我的形状会是什么样子(绘制顶点的预定点)。
  2. 使用 1 中确定的尺寸创建我的图案图形。
  3. 创建我的形状。
  4. 用3中创建的形状遮盖2中创建的图案图形
  5. 在我的基础中显示结果图像 canvas。

另请注意,形状可以位于我的基地内的任何位置 canvas,并且其位置由用于绘制形状的第一个点确定。该过程必须重复多次才能生成所需的输出。

function setup() {
  
    createCanvas(1000, 1000);
    background(200, 255, 255);
    
    var ptrn;
    var myShape;
    var msk;

    let ptrn_w = 1000;
    let ptrn_h = 1000;
    
    ptrn = createGraphics(ptrn_w, ptrn_h);
    ptrn.background(120, 0, 150);
    ptrn.fill(255, 255, 255);
    ptrn.noStroke();

    for (let i = 0; i < 500; i++){
        let x = random(0, ptrn_w);
        let y = random(0, ptrn_h);
        ptrn.rect(x, y, 20, 5);
    }

    msk = createGraphics(ptrn_w, ptrn_h);
    msk.beginShape();
    msk.vertex(250, 920);
    msk.vertex(300, 15);
    msk.vertex(325, 75);
    msk.vertex(350, 840);
    msk.endShape(CLOSE);
    
    ( myShape = ptrn.get() ).mask( msk.get() );
    
    image(myShape, 0, 0);
    
}

function draw() {  
    
}

上面列出的代码有效,因为 ptrn_wptrn_h 等于基数 canvas 的宽度和高度。但是,我们正在为整个 canvas 生成 patterns/graphics,并且该区域的很大一部分未使用。如果我们生成数百种具有复杂图案的不同形状,我可以看到从性能的角度来看,限制我们生成图案的区域是多么有益。

ptrn_wptrn_h 更改为 ptrn_w = 100ptrn_h = 905 是有问题的,因为蒙版将应用于图案图形之外 ptrn

有没有办法平移 ptrn 的位置,使其与 msk 的位置对齐?如果我们将图像的位置归零,image(myShape, 0, 0) 会不会有问题?

我的另一个想法是在调用 image(myShape, x_pos, y_pos).

时将我的面具的位置归零并重新定位它

实现这种行为的最佳方法是什么?欢迎任何创意。

我认为最有效的方法是使用底层的 CanvasRenderingContext2D globalCompositeOperation。通过将其设置为 'source-in',您可以执行某种反向掩码操作,而无需使用 p5.Image 对象。另外,如以下示例所示,您可以对顶点列表进行一些简单的计算,以确定宽度和高度,并在最小大小的缓冲区上绘制形状。

let vertices = [
  [10, 15],
  [150, 40],
  [100, 120],
  [25, 75]
];

let ptrn;
let myShape;

let shapeTop, shapeLeft;

function setup() {
  createCanvas(windowWidth, windowHeight);

  // Calculate the rage of vertices values
  let shapeBottom, shapeRight;
  for (const [x, y] of vertices) {
    if (shapeTop === undefined || y < shapeTop) {
      shapeTop = y;
    }
    if (shapeBottom === undefined || y > shapeBottom) {
      shapeBottom = y;
    }
    if (shapeLeft === undefined || x < shapeLeft) {
      shapeLeft = x;
    }
    if (shapeRight === undefined || x > shapeRight) {
      shapeRight = x;
    }
  }

  // Determine the minimum width and height to fit the shape
  let w = shapeRight - shapeLeft,
    h = shapeBottom - shapeTop;

  // Generate the pattern
  ptrn = createGraphics(w, h);
  ptrn.background(120, 0, 150);
  ptrn.noStroke();
  for (let i = 0; i < 100; i++) {
    ptrn.fill(random(255), random(255), random(255));
    let x = random(0, w);
    let y = random(0, h);
    ptrn.rect(x, y, 5);
  }

  // Draw the shape 
  myShape = createGraphics(w, h);
  myShape.fill(255);
  myShape.noStroke();
  myShape.beginShape();
  for (const [x, y] of vertices) {
    myShape.vertex(x - shapeLeft, y - shapeTop);
  }
  myShape.endShape(CLOSE);

  // See: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
  // The 'source-in' composite operation will output the drawn content (ptrn)
  // only in place of existing pixels that are not transparent.
  // The output of each pixel is basically: color(r2, g2, b3, min(a1, a2))
  myShape.drawingContext.globalCompositeOperation = 'source-in';
  myShape.image(ptrn, 0, 0)
}

function draw() {
  background(200, 255, 255);
  image(myShape, mouseX + shapeLeft, mouseY + shapeTop);
}
html, body { margin: 0; padding: 0; overflow: hidden }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>