5

I wish to draw lines on my canvas with width that gradually vary from start to end. That is, say the line starts at (0, 0) with width = 1 (equivalently, strokeWeight) and ends at (50, 50) with width = 3, and the width must (linearly) increase from 1 to 3 from start to end.

Any ideas on how to achieve this? Can't scrape it out of the web.

amzon-ex
  • 1,645
  • 1
  • 6
  • 28

2 Answers2

7

enter image description here

The key is to separate the line into for example 30 segments. You draw each segment with increasing strokeWeight(). The more segments you have, the smoother the line will look.

You can use lerp() to find x,y coordinates for points between two ends.

You can use lerp() to find strokeWeight() for lines between two ends.

function setup() {
  createCanvas(200, 200);
  background("black");
  stroke("white");
  gradientLine(0, 0, 50, 50, 1, 3, 30);
  noLoop();
}

function gradientLine(
  start_x,
  start_y,
  end_x,
  end_y,
  start_weight,
  end_weight,
  segments
) {
  let prev_loc_x = start_x;
  let prev_loc_y = start_y;
  for (let i = 1; i <= segments; i++) {
    let cur_loc_x = lerp(start_x, end_x, i / segments);
    let cur_loc_y = lerp(start_y, end_y, i / segments);
    push();
    strokeWeight(lerp(start_weight, end_weight, i / segments));
    line(prev_loc_x, prev_loc_y, cur_loc_x, cur_loc_y);
    pop();
    prev_loc_x = cur_loc_x;
    prev_loc_y = cur_loc_y;
  }
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.0.0/lib/p5.min.js"></script>

EDIT: Also, in case one is working with colours with alpha < 255, such artifacts can appear: enter image description here

This happens because the default capping for strokes is set to ROUND. Setting strokeCap(SQUARE) will fix this problem:

enter image description here

This needs to be set in the push()...pop() block in gradientLine(...) function. (Note: This will make the ends of the line look flat, and that needs more refined work.)

amzon-ex
  • 1,645
  • 1
  • 6
  • 28
ffmaer
  • 751
  • 9
  • 17
  • Fascinating! Thanks for the solution. One question: is `push()` and `pop()` necessary? And if not necessary, is it recommended for some reason (which I don't know)? – amzon-ex Jun 27 '20 at 04:57
  • 1
    hi @amzon-ex. Thanks for accepting the answer! `push()` and `pop()` are good to have because they can prevent `strokeWeight()`'s effect from going outside of the `gradientLine()` function unintentionally. Without `push()` and `pop()`, all lines drawn after calling `gradientLine()` will have the weight of `end_weight` provided for the `gradientLine()`. – ffmaer Jun 27 '20 at 06:46
  • Thanks, got it. Also, I suggested an edit which I thought should be noted. Feel free to scrutinise it. – amzon-ex Jun 27 '20 at 15:04
1

You could also just draw the line "as a trapezoid" using beginShape() / endShape() and transforming each end of the line into two separate points. See the code snippet below:

function setup() {
  createCanvas(400, 400);
  noStroke();
  fill(220);
}

function draw() {
  background(20);
  drawVaryingWidthLine(0, 0, 50, 50, 1, 3);
  drawVaryingWidthLine(80, 20, 200, 140, 1, 5);
  drawVaryingWidthLine(30, 60, 230, 260, 10, 3);
  drawVaryingWidthLine(210, 180, 360, 330, 40, 20);
}


function drawVaryingWidthLine(x1, y1, x2, y2,  startWidth, endWidth) { 
  const halfStartWidth = startWidth / 2
  const halfEndwidth = endWidth / 2
  beginShape();
    vertex(x1 + halfStartWidth, y1 - halfStartWidth);
    vertex(x2 + halfEndwidth, y2 - halfEndwidth);
    vertex(x2 - halfEndwidth, y2 + halfEndwidth);
    vertex(x1 - halfStartWidth, y1 + halfStartWidth);
    vertex(x1 + halfStartWidth, y1 - halfStartWidth);
  endShape();
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/p5.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/addons/p5.sound.min.js"></script>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8" />

  </head>
  <body>
    <script src="sketch.js"></script>
  </body>
</html>

Compared to @ffmaer's solution, this one doesn't have any smoothness / transparency artifacts. I assume that mine is somewhat faster because you only draw one shape per line (though I haven't tested it). However, this version here is currently limited to lines of the angle asked for in the original post (45 degrees downward right). It could be quite easily adapted to arbitrary angles by adjusting the corner positions of the trapezoid.

Edit: now using trapezoids instead of triangles, based on @ffmaer's comment.

Malte
  • 556
  • 5
  • 11
  • 2
    I thought about using a trapezoid, so we can specify both the `start_weight` and the `end_weight` for the line with varying weights. – ffmaer Jun 28 '20 at 11:55