1

I want to create a striped pattern with HTML5 canvas, where the thickness of lines for the striped pattern should be configurable using the property lineWidth.

After I read this answer, I understood that for coord x,y from moveTo()/lineTo(), I need to add like 2.5 for the ctx.lineWidth =5 or maybe create a formula based on thickness like this example. But I can't figure out how to change the values of those coordinates so the pattern remains striped like on the right, not random like in left

Below is my code. How should I calculate the coordonates x,y?

function createStrippedPattern(color) {
  const pattern = document.createElement('canvas');
  // create a 10x10 px canvas for the pattern's base shape
  pattern.width = 10;
  pattern.height = 10;

  // get the context for drawing
  const context = pattern.getContext('2d');
  context.strokeStyle = color;
  context.lineWidth = 5;

  // draw 1st line of the shape
  context.beginPath();
  context.moveTo(2, 0);
  context.lineTo(10, 8);
  context.stroke();

  // draw 2st line of the shape
  context.beginPath();
  context.moveTo(0, 8);
  context.lineTo(2, 10);

  context.stroke();

  return context.createPattern(pattern, 'repeat');
};

function fillWithPattern(targetCanvas, patternCanvas) {
  const ctx = targetCanvas.getContext('2d', {
    antialias: false,
    depth: false
  });
  const width = targetCanvas.width;
  const height = targetCanvas.height;
  ctx.fillStyle = patternCanvas;
  ctx.fillRect(0, 0, width, height);
  return targetCanvas;
}

fillWithPattern(
  document.getElementById("targetCanvas"),
  createStrippedPattern("red")
);
<canvas id="targetCanvas" width=30 height=30></canvas>
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Oxy
  • 13
  • 1
  • 6

1 Answers1

3

Code logic problems

The size of the pattern needs to match the slope of the line. That size must be expanded to allow for a set spacing between the lines.

Your code has a fixed size that does not match the slope of either of the lines you draw.

The lines you draw are both in different directions. You will never get them to create a repeatable pattern.

The code you have given is too ambiguous for me to understand what you wish to achieve thus the example adds some constraints that considers my best guess at your requirements.

Tileable striped pattern

The function in the example below creates a striped repeatable (tilded) pattern.

The function createStripedPattern(lineWidth, spacing, slope, color) requires 4 arguments.

  • lineWidth width of the line to draw

  • spacing distance between lines. Eg if lineWidth is 5 and spacing is 10 then the space between the lines is the same width as the line.

  • slope The slope of the line eg 45 degree slope is 1. I have only tested value >= 1 and am not sure if it will work below 1.

    Nor have I tested very large slopes. The point of the example is to show how to draw the line on the pattern to repeat without holes.

  • color Color of line to draw.

The function works by creating a canvas that will fit the constraints given by the arguments. It then draws a line from the top left to bottom right corners. This leaves a gap in the repeating pattern at the top right and bottom left corners.

To fill the missing pixels two more lines are drawn. One through the top right corner and the other through the bottom left.

Note you could also just copy the canvas onto itself (offset to the corners) to fill the missing corner pixels. For pixel art type patterns this may be preferable.

Note that canvas sizes are integer values and lines are rendered at sub pixel accuracy. For very small input values there will be artifact as the relative error between the canvas (integer) pixel size and required (floating point) size grows larger

Example

The example contains the function to create the pattern as outlined above and then renders some examples.

The first canvas has inset patterns with each pattern increasing the line width will keeping the spacing and slope constant.

The second canvas just fills with a fixed lineWidth as 4, spacing as 8 and a slope of 3

function createAARotatedPattern(lineWidth, spacing, ang, color) {
    const can = document.createElement('canvas');
    const w = can.width = 2; 
    const h = can.height = spacing;
    const ctx = can.getContext('2d'); 
    ctx.fillStyle = color; 
    ctx.fillRect(0, 0, 2, lineWidth);
   
    const pat = ctx.createPattern(can, 'repeat');
    const xAx = Math.cos(ang);
    const xAy = Math.sin(ang);
    pat.setTransform(new DOMMatrix([xAx, xAy, -xAy, xAx, 0, 0]));
    return pat;
}
function createStripedPattern(lineWidth, spacing, slope, color) {
    const can = document.createElement('canvas');
    const len = Math.hypot(1, slope);
    
    const w = can.width = 1 / len + spacing + 0.5 | 0; // round to nearest pixel              
    const h = can.height = slope / len + spacing * slope + 0.5 | 0; 

    const ctx = can.getContext('2d'); 
    ctx.strokeStyle = color; 
    ctx.lineWidth = lineWidth;
    ctx.beginPath();

    // Line through top left and bottom right corners
    ctx.moveTo(0, 0);
    ctx.lineTo(w, h);
    // Line through top right corner to add missing pixels
    ctx.moveTo(0, -h);
    ctx.lineTo(w * 2, h);
    // Line through bottom left corner to add missing pixels
    ctx.moveTo(-w, 0);
    ctx.lineTo(w, h * 2);
    
    ctx.stroke();
    return ctx.createPattern(can, 'repeat');
  };

  function fillWithPattern(canvas, pattern, inset = 0) {
    const ctx = canvas.getContext('2d');
    ctx.clearRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
    ctx.fillStyle = pattern;
    ctx.fillRect(inset, inset, canvas.width - inset * 2, canvas.height - inset * 2);
    return canvas;
  }

  fillWithPattern(targetCanvas, createStripedPattern(2, 6, 2, "#000"));
  fillWithPattern(targetCanvas, createStripedPattern(3, 6, 2, "#000"), 50); 
  fillWithPattern(targetCanvas, createStripedPattern(4, 6, 2, "#000"), 100);  
  fillWithPattern(targetCanvas1, createStripedPattern(4, 8, 3, "#000"));
  var y = 0;
  var ang = 0;
  const ctx = targetCanvas2.getContext('2d');
  while (y < targetCanvas2.height) {
      ctx.fillStyle = createAARotatedPattern(2, 5, ang, "#000");
      ctx.fillRect(0, y, targetCanvas2.width, 34);
      y += 40;
      ang += 2 * Math.PI / (targetCanvas2.height / 40);
  }

  
  
  
<canvas id="targetCanvas" width="300" height="300"></canvas>
<canvas id="targetCanvas1" width="300" height="300"></canvas>
<canvas id="targetCanvas2" width="300" height="600"></canvas>

Update

The above example now includes a second method createAARotatedPattern(lineWidth, spacing, ang, color) that uses the pattern transform. ang replaces slope from the original function and represents the angle of the pattern in radians.

It works by drawing the pattern aligned to the x axis and then rotates the pattern via a DOMMatrix.

It will create a pattern at any angle, though personally the quality can at times be less than the first method.

The example has a 3 canvas with strips showing the pattern drawn at various angles. (Note you do not have to recreate the pattern to change the angle)

Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • thx for your answer. But what happens in case of horizontal lines pattern, changing the angle of lines from 45 to 180 degrees? Unfortunately, I can see how it can be used anymore the const slope. – Oxy Sep 28 '22 at 15:48