簡體   English   中英

p5.js:如何在不改變背景或使用外部 canvas 的情況下在 canvas 形狀上打孔?

[英]p5.js: How to punch a hole into a canvas shape, without changing the background or using an external canvas?

我正試圖找出一種方法在一個東西上打孔,但如果沒有這個洞也穿過背景中的任何東西。

  1. 這個洞是由幾個任意形狀組成的,並不是我可以用來剪輯的簡單路徑。
  2. 該孔僅穿過前景形狀,而不是一直打入背景(背景應保持原樣)。

我想出了一種使用外部上下文執行此操作的方法,然后將其引入。我的問題:有沒有辦法在我的默認 canvas 上執行此操作,並避免外部上下文可能引起的並發症(額外的 memory,顏色差異ETC)?

這是一個工作(p5.js)示例,它使用了一個新的上下文:

 function setup() { createCanvas(600,600); background(255, 0, 0); noStroke(); } function draw() { //blue: stuff in the background that should not change fill ("blue"); rect (20,20,500,500); //draw on external canvas pg = createGraphics(600,600); //yellow+green foreground shapes pg.fill("green"); pg.rect(100, 100, 200, 200); pg.fill("yellow"); pg.rect(80, 80, 100, 300); //punch a hole in the shapes pg.fill(0, 0, 255); pg.blendMode(REMOVE); pg.circle(140, 140, 150); pg.circle(180, 180, 150); //bring in the external canvas with punched shapes image(pg, 0, 0); noLoop(); }
 html, body { margin: 0; padding: 0; } canvas { display: block; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>

如果沒有您已經發現的技術,就沒有簡單或內置的方法可以做到這一點。 唯一的選擇是實現boolean 幾何運算,例如任意形狀和樣條的減法和相交。 這將允許您制作任意貝塞爾樣條曲線來表示多個復雜形狀的組合,然后直接繪制它們。 這種方法在中風方面的行為與去除方法不同。

僅供參考,p5js erase()noErase()中還有一對方法與blendMode(REMOVE)方法具有相似的行為。 我不認為有任何技術上的好處,但使用它們而不是混合模式可能更慣用。

我同意,正如 Paul(+1) 所提到的,使用多個 p5.Graphics 實例(你稱之為外部上下文)是最直接/可讀的方法。

您可以顯式使用p5.Imagemask() ,但是涉及的操作很少,並且可讀性會降低一些。 這是一個例子:

 function setup() { createCanvas(600,600); background(255, 0, 0); noStroke(); } function draw() { //blue: stuff in the background that should not change fill ("blue"); rect (20,20,500,500); //draw on external canvas pg = createGraphics(600,600); //yellow+green foreground shapes pg.fill("green"); pg.rect(100, 100, 200, 200); pg.fill("yellow"); pg.rect(80, 80, 100, 300); //punch a hole in the shapes let msk = createGraphics(600, 600); msk.background(0); msk.erase(); msk.noStroke(); msk.circle(140, 140, 150); msk.circle(180, 180, 150); let mskImage = msk.get(); pgImage = pg.get(); pgImage.mask(mskImage); image(pgImage, 0, 0); noLoop(); }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

一個(非常)hacky 的解決方法是用一個 canvas 做同樣的事情。 這將使圓圈內的區域完全透明,使它們顯示為藍色,只需將背景元素置於藍色后面:

 function setup() { createCanvas(600,600); background(255, 0, 0); noStroke(); } function draw() { //blue: stuff in the background that should not change fill ("blue"); rect (20,20,500,500); //draw on external canvas // pg = createGraphics(600,600); //yellow+green foreground shapes fill("green"); rect(100, 100, 200, 200); fill("yellow"); rect(80, 80, 100, 300); //punch a hole in the shapes fill(0, 0, 255); blendMode(REMOVE); circle(140, 140, 150); circle(180, 180, 150); //bring in the external canvas with punched shapes // image(pg, 0, 0); noLoop(); }
 body{ /* make the HTML background match the canvas blue */ background-color: #00F; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

不過,這可能不夠靈活。

現在,假設您的前景由黃色和綠色形狀組成,而背景是藍色,另一種選擇是手動訪問pixels[]數組並更新像素值。 在您的示例中,掩碼是圓形的,因此您可以檢查:

  • 當前像素到圓心的距離小於圓的半徑:這意味着像素在圓內
  • 此外,如果圓圈內的顏色是前景色(例如,在您的情況下為綠色或黃色),如果兩個條件都匹配,那么您可以用背景色替換此像素(在您的情況下為藍色)

這是一個例子:

 function setup() { createCanvas(600,600); pixelDensity(1); background(255, 0, 0); noStroke(); } function draw() { //blue: stuff in the background that should not change fill ("blue"); rect (20,20,500,500); //draw on external canvas //yellow+green foreground shapes fill("green"); rect(100, 100, 200, 200); fill("yellow"); rect(80, 80, 100, 300); //punch a hole in the shapes fill(0, 0, 255); // make pixels available for reading loadPixels(); // apply each circle "mask" / bg color replacement // yellow, green, bg blue to replace fg with circleMask(140, 140, 150, [255, 255, 0], [0, 0x80, 0], [0, 0, 255]); circleMask(180, 180, 150, [255, 255, 0], [0, 0x80, 0], [0, 0, 255]); // once all "masks" are applied, updatePixels(); noLoop(); } function circleMask(x, y, radius, fg1, fg2, bg){ // total number of pixels let np = width * height; let np4 = np*4; //for each pixel (i = canvas pixel index (taking r,g,b,a order into account) // id4 is a quarter of "i" for(let i = 0, id4 =0; i < np4; i+=4, id4++){ // compute x from pixel index let px = id4 % width; // compute y from pixel index let py = id4 / width; // if we're within the circle if(dist(px, py, x, y) < radius / 2){ // if we've found foreground colours to make transparent // ([0][1][2] = r, g, b) if((pixels[i] == fg1[0] || pixels[i] == fg2[0]) && (pixels[i+1] == fg1[1] || pixels[i+1] == fg2[1]) && (pixels[i+2] == fg1[2] || pixels[i+2] == fg2[2])){ // "mask" => replace fg colour matching pixel with bg pixel pixels[i] = bg[0]; pixels[i+1] = bg[1]; pixels[i+2] = bg[2]; } } } }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

這里有幾點需要注意。 pixels[]set(x, y, clr)快,但這意味着你需要記住一些細節:

  • 在訪問pixels[]之前調用loadPixels()以讀取/填充數組
  • 進行所需的所有像素更改(在這種情況下,圓圈“蒙版”/圓圈內的像素顏色替換)
  • pixels[]更新調用updatePixels()

還注意到執行需要一些時間。 可能會有一些速度改進,例如僅在檢查距離之前迭代圓的邊界框內的像素並檢查平方距離而不是dist() ,但這也會降低代碼的可讀性。

最后,參考 Paul 的 boolean 幾何操作的想法,也許您可以通過使用beginContour() / endContour()從矩形“切割”圓形區域來偽造其中的一些。 如果您需要對裁剪形狀進行較低級別的控制,則可以使用原始裁剪函數,因為您可以通過drawingContext CanvasRenderingContext2D 不幸的是,我沒有時間提供詳細的示例。 希望上面的想法和參考可以幫助您找出最適合您的選項(或組合或選項)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM