是否可以制作带线条背景的 canvas 或不是矩形的 canvas?

Is it possible to make canvas with background with lines or canvas that isn't a rectangle?

我正在努力制作这个https://massmoca.org/event/walldrawing340/

在 Javascript 代码中,使用 p5.js,但我不知道如何用线条填充这些形状。有没有其他的可能性,比如把 canvas 做成圆形之类的,或者我只需要分别做每个形状?

目前我是一个形状一个形状地做,但是做三角形和梯形还是比较粗糙的...

        var sketch = function (p) {
          with(p) {

            let h,
                w,
                space;

            p.setup = function() {
              createCanvas(900, 400);
              h = height / 2;
              w = width / 3;
              space = 10;
              noLoop();
            };
        
            p.draw = function() {
              drawBackground('red', 'blue', 0, 0);
              shape('Circle', 'red', 'blue', 0, 0);
              drawBackground('yellow', 'red', w, 0);
              shape('Square', 'yellow', 'red', w, 0);
              drawBackground('blue', 'yellow', 2 * w, 0);
              shape('Triangle', 'blue', 'red', 2 * w, 0)
              drawBackground('red', 'yellow', 0, h);
              shape('Rectangle', 'red', 'blue', 0, h)
              drawBackground('yellow', 'blue', w, h);
              shape('Trapezoid', 'yellow', 'red', w, h);
              drawBackground('blue', 'red', 2 * w, h);            
            };

            function drawBackground(bColor, lColor, x, y) {
                fill(bColor)
                noStroke();
                rect(x, y, w, h)
                stroke(lColor);
                strokeWeight(1);
                for (let i = 0; i < h / space; i++) {
                    line(0 + x, i * space + y + 10, w + x, i * space + y + 10);
                }

            }
            function shape(shape, bColor, lColor, x, y) {
                fill(bColor)
                noStroke();
                let w1;
                switch (shape) {
                    case 'Circle':
                        circle(x + w / 2, y + h / 2, h - space * 6);
                        stroke(lColor);
                        strokeWeight(1);
                        for (let i = 0; i < w / space; i++) {

                            for (let j = 0; j < h; j++) {
                                pX = i * space + x;
                                pY = 0 + y + j;
                                if (pow(x + w / 2 - pX, 2)
                                    + pow(pY - (y + h / 2), 2) <= pow(h - space * 6 * 2 - 10, 2)) {
                                    point(pX, pY);
                                }

                            }
                        }
                        break;

                    case 'Square':
                        w1 = w - (h - space * 6);
                        rect(x + w1 / 2, y + space * 3, h - space * 6, h - space * 6);
                        stroke(lColor);
                        strokeWeight(1);
                        for (let i = 0; i < 15; i++) {
                            for (let j = 0; j < h - space * 6; j++) {
                                point(x + w1 / 2 + i * space, y + space * 3 + j)
                            }
                        }
                        break;

                    case 'Triangle':
                        w1 = w - (h - space * 6);
                        triangle(x + w1 / 2, h - space * 3 + y, x + w / 2, y + space * 3, x + w1 / 2 + h - space * 6, h - space * 3 + y)
                        for (let i = 0; i < w / space; i++) {

                            for (let j = 0; j < h; j++) {
                                pX = i * space + x;
                                pY = 0 + y + j;
                                if (pow(x + w / 2 - pX, 2)
                                    + pow(pY - (y + h / 2), 2) <= pow(h - space * 6 * 2 - 10, 2)) {
                                    point(pX, pY);
                                }

                            }
                        }
                        break;

                    case 'Rectangle':
                        w1 = w - (h - space * 6) / 2;
                        rect(x + w1 / 2, y + space * 3, (h - space * 6) / 2, h - space * 6)
                        break;

                    case 'Trapezoid':
                        w1 = w - (h - space * 6);
                        quad(x + w1 / 2, h - space * 3 + y, x + w1 / 2 + (h - space * 6) / 4, y + space * 3, x + w1 / 4 + h - space * 6, y + space * 3, x + w1 / 2 + h - space * 6, h - space * 3 + y)
                        break;

                    case 'Parallelogram':
                            w1 = w - (h - space * 6);
                            quad(x + w1 / 4, h - space * 3 + y, x + w1 / 2, y + space * 3, x + w1 / 2 + h - space * 6, y + space * 3, x + w1 / 4 + h - space * 6, h - space * 3 + y)
                            break;
                        break;
                }

            }

          }
        };
        
        let node = document.createElement('div');
        window.document.getElementById('p5-container').appendChild(node);
        new p5(sketch, node);
    body {
      background-color:#efefef;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
    <div id="p5-container"></div>

没有消息,一切正常,我只是想知道我是否必须做这么多艰巨的工作...

我有办法做一些形状,但我不确定其他形状。一种方法是,如果您知道形状轮廓上的每个点的位置,您可以只使用 for 循环并使用 line 或 rect 函数连接顶部和底部的所有其他点。对于正方形和平行四边形这样的形状,这将相对容易,但我不确定可以使用什么函数来获得圆形或梯形的点。

在此处查看更多信息:https://www.openprocessing.org/sketch/745383

你需要线性代数的东西,基本上注意垂直线 starting/ending Y 坐标如何相对于线的 X 坐标变化。当然,在你得到可用的东西之前,还要进行大量的试验。像 :

var w = 600
    h = 600
    sp = 15

var slides = [fcircle, fsquare, ftriangle, ftrapezoid, fparallelogram];

var active = 0;

var ms;

function blines(){
  stroke(0);
  for (var i=0; i < h; i+=sp) {
    line(0,i,w,i);
  }
}

function vertlines(calcline) {
   for (var x=w/2-w/4+sp; x < w/2+w/4; x+=sp) {
       var pnts = calcline(x);
       line(pnts[0],pnts[1],pnts[2],pnts[3]);
   }
}

function fcircle() {
   // cut background
   noStroke();
   circle(w/2, h/2, w/2);
   stroke('red');
   // draw figure lines
   let calc = function (x){
      var sx = x-w/2;
      var sy = h/2;
      var ey = h/2;
      sy += 137*sin(2.5+x/135);
      ey -= 137*sin(2.5+x/135);
      return [x,sy,x,ey];
   }
   vertlines(calc);
}

function fsquare() {
   // cut background
   noStroke();
   quad(w/2-w/4, h/2-h/4, w/2+w/4, h/2-h/4,
        w/2+w/4, h/2+h/4, w/2-w/4, h/2+h/4);  
   stroke('red');
   // draw figure lines
   let calc = function (x){
      return [x,h/2-h/4,x,h/2+h/4];
   }
   vertlines(calc);
}

function ftriangle() {
   // cut background
   noStroke();
   quad(w/2, h/2-h/4, w/2+w/4, h/2+h/4,
        w/2-w/4, h/2+h/4, w/2, h/2-h/4);  
  stroke('red');
   // draw figure lines
   let calc = function (x){
      var inpx = x > w/2 ? w-x : x;
      var ys = h/2+h/4;
      ys += -(0.3*inpx*log(inpx)-220);
      return [x,ys,x,h/2+h/4];
   }
   vertlines(calc);
}

function ftrapezoid() {
   // cut background
   noStroke();
   quad(w/2-w/10, h/2-h/4, w/2+w/10, h/2-h/4,
        w/2+w/4, h/2+h/4, w/2-w/4, h/2+h/4);  
   stroke('red');
   // draw figure lines
   let calc = function (x){
      var inpx = x > w/2 ? w-x : x;
      var ys = h/2+h/4;
      ys += -(0.55*inpx*log(inpx)-420);
      if (x >= w/2-w/10 && x <= w/2+w/10) {
         ys=h/2-h/4;
      }
      return [x,ys,x,h/2+h/4];
   }
   vertlines(calc);
}

function fparallelogram() {
   // cut background
   noStroke();
   quad(w/2-w/10, h/2-h/4, w/2+w/7, h/2-h/4,
        w/2, h/2+h/4, w/2-w/4, h/2+h/4);  
   stroke('red');
   // draw figure lines
   let calc = function (x){
      // guard condition
      if (x > w/2+w/7)
         return [0,0,0,0];
      var inpx = x > w/2 ? w-x : x;
      var ys = h/2+h/4;
      ys += -(0.55*inpx*log(inpx)-420);
      var ye=h/2+h/4
      if (x >= w/2-w/10) {
         ys=h/2-h/4;
      }
      if (x > w/2) {
         ye = h/2+h/4;
         ye += 0.50*inpx*log(inpx)-870;
      }
      return [x,ys,x,ye];
   }
   vertlines(calc);
}

function setup() {
  ms = millis();
  createCanvas(w, h);
}

function draw() {
   if (millis() - ms > 2000) {
      ms = millis();
      active++;
      if (active > slides.length-1)
         active = 0;
   }
   background('#D6EAF8');
   fill('#D6EAF8');
   blines();
   slides[active]();
}

Slideshow DEMO

如果您不需要实际的线坐标(例如用于绘图),我会充分利用 createGraphics() to easily render shapes and lines into (taking advantage of the fact that get() returns a p5.Image) and p5.Image's mask() 函数。

这是一个基本示例:

function setup() {
  createCanvas(600, 300);
  
  let w = 300;
  let h = 150;
  let spacing = 12;
  let strokeWidth = 1;
  
  const BLUE   = color('#005398');
  const YELLOW = color('#f9db44');
  const RED    = color('#dc1215');
  
  bg = getLinesRect(w, h, RED, BLUE, spacing, strokeWidth, true);
  fg = getLinesRect(w, h, RED, YELLOW, spacing, strokeWidth, false);
  mask = getCircleMask(w, h, w * 0.5, h * 0.5, 100, 0);
  
  image(bg, 0, 0);
  image(fg, w, 0);
  // render opaque mask (for visualisation only), mask() requires alpha channel
  image(getCircleMask(w, h, w * 0.5, h * 0.5, 100, 255),0, h);
  
  // apply mask
  fg.mask(mask);
  // render bg + masked fg
  image(bg, w, h);
  image(fg, w, h);
  
  // text labels
  noStroke();
  fill(255);
  text("bg layer", 9, 12);
  text("fg layer", w + 9, 12);
  text("mask", 9, h + 12);
  text("bg + masked fg", w + 9, h + 12);
}

function getLinesRect(w, h, bg, fg, spacing, strokeWidth, isHorizontal){
  let rect = createGraphics(w, h);
  rect.background(bg);
  rect.stroke(fg);
  rect.strokeWeight(strokeWidth);
  
  if(isHorizontal){
    for(let y = 0 ; y < h; y += spacing){
      rect.line(0, y + strokeWidth, w, y + strokeWidth);
    } 
  }else{
    for(let x = 0 ; x < w; x += spacing){
      rect.line(x + strokeWidth, 0, x + strokeWidth, h);
    }
  }
  // convert from p5.Graphics to p5.Image
  return rect.get();
}

function getCircleMask(w, h, cx, cy, cs, opacity){
  let mask = createGraphics(w, h);
  // make background transparent (alpha is used for masking)
  mask.background(0, opacity);
  mask.noStroke();
  mask.fill(255);
  mask.circle(cx, cy, cs);
  // convert p5.Graphics to p5.Image
  return mask.get();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

您可以对其余形状应用相同的逻辑:

function setup() {
  createCanvas(1620, 590);
  
  let compWidth  = 500;
  let compHeight = 250;
  let compSpacing= 30;
  
  let lineWeight = 1.5;
  let lineSpacing = 12;
  
  const BLUE   = color('#005398');
  const YELLOW = color('#f9db44');
  const RED    = color('#dc1215');
  
  // yellow square
  circleMask   = getCircleMask(compWidth, compHeight, compWidth * 0.5, compHeight * 0.5, 210);
  redCircle    = getComposition(compWidth, compHeight, RED, 
                                                    BLUE,
                                                    YELLOW,
                                                    lineSpacing, lineWeight, circleMask);
  
  
  // red box
  boxMask      = getRectMask(compWidth, compHeight, (compWidth - 100) * 0.5, 20, 100, 210);
  redBox       = getComposition(compWidth, compHeight, RED, 
                                                    YELLOW,
                                                    BLUE,
                                                    lineSpacing, lineWeight, boxMask);
  
  
  // yellow square
  squareMask   = getRectMask(compWidth, compHeight, 144, 20, 210, 210);
  yellowSquare = getComposition(compWidth, compHeight, YELLOW, 
                                                    RED,
                                                    BLUE,
                                                    lineSpacing, lineWeight, squareMask);
                                                    
  // yellow trapeze
  trapezeMask   = getQuadMask(compWidth, compHeight, 200, 25, 200 + 115, 25,
                                                     150 + 220, 220, 150, 220);
  yellowTrapeze = getComposition(compWidth, compHeight, YELLOW, 
                                                    BLUE,
                                                    RED,
                                                    lineSpacing, lineWeight, trapezeMask);
                                                    
  // blue triangle
  triangleMask   = getTriangleMask(compWidth, compHeight, compWidth * 0.5, 25,
                                                     150 + 220, 220, 150, 220);
  blueTriangle   = getComposition(compWidth, compHeight, BLUE, 
                                                    YELLOW,
                                                    RED,
                                                    lineSpacing, lineWeight, triangleMask);
                                                    
  // blue parallelogram
  parallelogramMask = getQuadMask(compWidth, compHeight, 200, 25, 200 + 145, 25,
                                                         150 + 145, 220, 150, 220);
  blueParallelogram = getComposition(compWidth, compHeight, BLUE, 
                                                    RED,
                                                    YELLOW,
                                                    lineSpacing, lineWeight, parallelogramMask);
  
  // render compositions
  image(redCircle, compSpacing, compSpacing);
  image(redBox, compSpacing, compSpacing + (compHeight + compSpacing));
  
  
  image(yellowSquare, compSpacing + (compWidth + compSpacing), compSpacing);
  image(yellowTrapeze, compSpacing + (compWidth + compSpacing), compSpacing + (compHeight + compSpacing));
  
  image(blueTriangle, compSpacing + (compWidth + compSpacing) * 2, compSpacing);
  image(blueParallelogram, compSpacing + (compWidth + compSpacing) * 2, compSpacing + (compHeight + compSpacing));
  
}

function getComposition(w, h, bgFill, bgStroke, fgStroke, spacing, strokeWidth, mask){
  let comp = createGraphics(w, h);
  
  bg = getLinesRect(w, h, bgFill, bgStroke, spacing, strokeWidth, true);
  fg = getLinesRect(w, h, bgFill, fgStroke, spacing, strokeWidth, false);
  // apply mask
  fg.mask(mask);
  // render to final output
  comp.image(bg, 0, 0);
  comp.image(fg, 0, 0);
  
  return comp;
}

function getRectMask(w, h, rx, ry, rw, rh){
  let mask = createGraphics(w, h);
  // make background transparent (alpha is used for masking)
  mask.background(0,0);
  mask.noStroke();
  mask.fill(255);
  mask.rect(rx, ry, rw, rh);
  // convert p5.Graphics to p5.Image
  return mask.get();
}

function getCircleMask(w, h, cx, cy, cs){
  let mask = createGraphics(w, h);
  // make background transparent (alpha is used for masking)
  mask.background(0,0);
  mask.noStroke();
  mask.fill(255);
  mask.circle(cx, cy, cs);
  // convert p5.Graphics to p5.Image
  return mask.get();
}

function getQuadMask(w, h, x1, y1, x2, y2, x3, y3, x4, y4){
  let mask = createGraphics(w, h);
  // make background transparent (alpha is used for masking)
  mask.background(0,0);
  mask.noStroke();
  mask.fill(255);
  mask.quad(x1, y1, x2, y2, x3, y3, x4, y4);
  // convert p5.Graphics to p5.Image
  return mask.get();
}

function getTriangleMask(w, h, x1, y1, x2, y2, x3, y3){
  let mask = createGraphics(w, h);
  // make background transparent (alpha is used for masking)
  mask.background(0,0);
  mask.noStroke();
  mask.fill(255);
  mask.triangle(x1, y1, x2, y2, x3, y3);
  // convert p5.Graphics to p5.Image
  return mask.get();
}

function getLinesRect(w, h, bg, fg, spacing, strokeWidth, isHorizontal){
  let rect = createGraphics(w, h);
  rect.background(bg);
  rect.stroke(fg);
  rect.strokeWeight(strokeWidth);
  
  if(isHorizontal){
    for(let y = 0 ; y < h; y += spacing){
      rect.line(0, y + strokeWidth, w, y + strokeWidth);
    } 
  }else{
    for(let x = 0 ; x < w; x += spacing){
      rect.line(x + strokeWidth, 0, x + strokeWidth, h);
    }
  }
  // convert from p5.Graphics to p5.Image
  return rect.get();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

可能矩形和三角形都可以使用 getQuadMask() 充分利用坐标来绘制。

请注意,我只是观察了一些形状,所以它们不会是完美的,但应该很容易调整。请记住,遮罩的放置会影响垂直线的对齐方式。

可能还有其他方法可以获得相同的视觉效果。 例如,将 texture()textureWrap(REPEAT)beginShape()/endShape() 一起使用,为每条线使用像素并在更改方向和颜色之前检查交叉点等

在生成绘图线方面,我会从水平线开始,对凸多边形进行线相交,以确定停止水平线和开始垂直线的位置。 @AgniusVasiliauskas 的回答(+1)适合这种方法。

Freya Holmér 对测试有很好的视觉解释。