Gopi raj
Gopi raj

Reputation: 43

How to draw herringbone pattern on html canvas

I Have to draw Herringbone pattern on canvas and fill with image

some one please help me I am new to canvas 2d drawing. I need to draw mixed tiles with cross pattern (Herringbone)

  var canvas = this.__canvas = new fabric.Canvas('canvas');
  var canvas_objects = canvas._objects;
// create a rectangle with a fill and a different color stroke
var left = 150;
var top = 150;
var x=20;
var y=40;

var rect = new fabric.Rect({
   left: left,
   top: top,
   width: x,
   height: y,
   angle:45,
   fill: 'rgba(255,127,39,1)',
   stroke: 'rgba(34,177,76,1)',
   strokeWidth:0,
    originX:'right',
        originY:'top',
        centeredRotation: false
});
canvas.add(rect);
for(var i=0;i<15;i++){
    var rectangle = fabric.util.object.clone(getLastobject());
 if(i%2==0){
    rectangle.left = rectangle.oCoords.tr.x;
    rectangle.top = rectangle.oCoords.tr.y;
    rectangle.originX='right';
        rectangle.originY='top';
    rectangle.angle =-45;
  }else{
        
      fabric.log('rectangle: ', rectangle.toJSON());
            rectangle.left = rectangle.oCoords.tl.x;
      rectangle.top = rectangle.oCoords.tl.y;
      fabric.log('rectangle: ', rectangle.toJSON());
        rectangle.originX='left';
            rectangle.originY='top';
        rectangle.angle =45;
      
  }
  //rectangle.angle -90;
  canvas.add(rectangle);
}
fabric.log('rectangle: ', canvas.toJSON());
canvas.renderAll();    


function getLastobject(){
    var last = null;
    if(canvas_objects.length !== 0){
        last = canvas_objects[canvas_objects.length -1]; //Get last object   
    }    
  return last;
}

How to draw this pattern in canvas using svg or 2d,3d method. If any third party library that also Ok for me.

I don't know where to start and how to draw this complex pattern.

some one please help me to draw this pattern with rectangle fill with dynamic color on canvas.

Here is a sample of the output I need: (herringbone pattern)

I tried something similar using fabric.js library here is my JSFiddle

Upvotes: 0

Views: 1215

Answers (2)

Blindman67
Blindman67

Reputation: 54026

Trippy disco flooring

To get the pattern you need to draw rectangles one horizontal tiled one space left or right for each row down and the same for the vertical rectangle.

The rectangle has an aspect of width 2 time height.

Drawing the pattern is simple.

Rotating is easy as well the harder part is finding where to draw the tiles for the rotation.

To do that I create a inverse matrix of the rotation (it reverses a rotation). I then apply that rotation to the 4 corners of the canvas 0,0, width,0 width,height and 0,height this gives me 4 points in the rotated space that are at the edges of the canvas.

As I draw the tiles from left to right top to bottom I find the min corners for the top left, and the max corners for the bottom right, expand it out a little so I dont miss any pixels and draw the tiles with a transformation set the the rotation.

As I could not workout what angle you wanted it at the function will draw it at any angle. On is animated, the other is at 60deg clockwise.

Warning demo contains flashing content.

Update The flashing was way to out there, so have made a few changes, now colours are a more pleasing blend and have fixed absolute positions, and have tied the tile origin to the mouse position, clicking the mouse button will cycle through some sizes as well.

const ctx = canvas.getContext("2d");
const colours = []
for(let i = 0; i < 1; i += 1/80){
    colours.push(`hsl(${Math.floor(i * 360)},${Math.floor((Math.sin(i * Math.PI *4)+1) * 50)}%,${Math.floor(Math.sin(i * Math.PI *8)* 25 + 50)}%)`)
}
const sizes = [0.04,0.08,0.1,0.2];
var currentSize = 0;
const origin = {x : canvas.width / 2, y : canvas.height / 2};
var size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
function drawPattern(size,origin,ang){
    const xAx = Math.cos(ang);  // define the direction of xAxis
    const xAy = Math.sin(ang);    
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.setTransform(xAx,xAy,-xAy,xAx,origin.x,origin.y);
    function getExtent(xAx,xAy,origin){
        const im = [1,0,0,1]; // inverse matrix
        const dot = xAx *  xAx + xAy * xAy;
        im[0] =  xAx / dot;
        im[1] = -xAy / dot;
        im[2] = xAy / dot;
        im[3] = xAx / dot;
        const toWorld = (x,y) => {
            var point = {};
            var xx = x - origin.x;     
            var yy = y - origin.y;     
            point.x = xx * im[0] + yy * im[2]; 
            point.y = xx * im[1] + yy * im[3];
            return point;
        }
        return [
            toWorld(0,0),
            toWorld(canvas.width,0),
            toWorld(canvas.width,canvas.height),
            toWorld(0,canvas.height),
        ]
    }
    const corners = getExtent(xAx,xAy,origin);
    var startX = Math.min(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
    var endX = Math.max(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
    var startY = Math.min(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
    var endY = Math.max(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
    
    startX = Math.floor(startX / size) - 2;
    endX = Math.floor(endX / size) + 2;
    startY = Math.floor(startY / size) - 2;
    endY = Math.floor(endY / size) + 2;
                
    // draw the pattern        
    ctx.lineWidth = size * 0.1;
    ctx.lineJoin = "round";
    ctx.strokeStyle = "black";
    var colourIndex = 0;
    for(var y = startY; y <endY; y+=1){
        for(var x = startX; x <endX; x+=1){
            if((x + y) % 4 === 0){
                colourIndex = Math.floor(Math.abs(Math.sin(x)*size  + Math.sin(y) * 20));
                ctx.fillStyle = colours[(colourIndex++)% colours.length];
                ctx.fillRect(x * size,y * size,size * 2,size);
                ctx.strokeRect(x * size,y * size,size * 2,size);
                x += 2;
                ctx.fillStyle = colours[(colourIndex++)% colours.length];
                ctx.fillRect(x * size,y * size, size, size * 2);
                ctx.strokeRect(x * size,y * size, size, size * 2);
                x += 1;
            }
    
        }
    }
}

// Animate it all 
var update = true; // flag to indecate something needs updating
function mainLoop(time){
    // if window size has changed update canvas to new size
    if(canvas.width !== innerWidth || canvas.height !== innerHeight || update){
        canvas.width = innerWidth;
        canvas.height = innerHeight    
        origin.x = canvas.width / 2;
        origin.y = canvas.height / 2;
        size = Math.min(canvas.width, canvas.height) * sizes[currentSize % sizes.length];
        update = false;
    }
    if(mouse.buttonRaw !== 0){
        mouse.buttonRaw = 0;
        currentSize  += 1;
        update = true;
    }
    // draw the patter
    drawPattern(size,mouse,time/2000);
    requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);

mouse = (function () {
    function preventDefault(e) { e.preventDefault() }
    var m; // alias for mouse
    var mouse = {
        x : 0, y : 0, // mouse position
        buttonRaw : 0,
        over : false,                        // true if mouse over the element
        buttonOnMasks : [0b1, 0b10, 0b100],  // mouse button on masks
        buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
        bounds : null,
        eventNames : "mousemove,mousedown,mouseup,mouseout,mouseover".split(","),
        event(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left - scrollX;
            m.y = e.pageY - m.bounds.top - scrollY;
            if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
            else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
            else if (t === "mouseout") { m.over = false }
            else if (t === "mouseover") { m.over = true }
            e.preventDefault();
        },
        start(element) {
            if (m.element !== undefined) { m.remove() }
            m.element = element === undefined ? document : element;
            m.eventNames.forEach(name =>  document.addEventListener(name, mouse.event) );
            document.addEventListener("contextmenu", preventDefault, false);
        },
    }
    m = mouse;
    return mouse;
})();
mouse.start(canvas);
canvas {
   position : absolute;
   top : 0px;
   left : 0px;
}
<canvas id=canvas></canvas>

Un-animated version at 60Deg

        const ctx = canvas.getContext("2d");
        const colours = ["red","green","yellow","orange","blue","cyan","magenta"]
        const origin = {x : canvas.width / 2, y : canvas.height / 2};
        var size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
        function drawPattern(size,origin,ang){
            const xAx = Math.cos(ang);  // define the direction of xAxis
            const xAy = Math.sin(ang);    
            ctx.setTransform(1,0,0,1,0,0);
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.setTransform(xAx,xAy,-xAy,xAx,origin.x,origin.y);
            function getExtent(xAx,xAy,origin){
                const im = [1,0,0,1]; // inverse matrix
                const dot = xAx *  xAx + xAy * xAy;
                im[0] =  xAx / dot;
                im[1] = -xAy / dot;
                im[2] = xAy / dot;
                im[3] = xAx / dot;
                const toWorld = (x,y) => {
                    var point = {};
                    var xx = x - origin.x;     
                    var yy = y - origin.y;     
                    point.x = xx * im[0] + yy * im[2]; 
                    point.y = xx * im[1] + yy * im[3];
                    return point;
                }
                return [
                    toWorld(0,0),
                    toWorld(canvas.width,0),
                    toWorld(canvas.width,canvas.height),
                    toWorld(0,canvas.height),
                ]
            }
            const corners = getExtent(xAx,xAy,origin);
            var startX = Math.min(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
            var endX = Math.max(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
            var startY = Math.min(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
            var endY = Math.max(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
            
            startX = Math.floor(startX / size) - 4;
            endX = Math.floor(endX / size) + 4;
            startY = Math.floor(startY / size) - 4;
            endY = Math.floor(endY / size) + 4;
                        
            // draw the pattern        
            ctx.lineWidth = 5;
            ctx.lineJoin = "round";
            ctx.strokeStyle = "black";
            for(var y = startY; y <endY; y+=1){
                for(var x = startX; x <endX; x+=1){
                    ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
                    if((x + y) % 4 === 0){
                        ctx.fillRect(x * size,y * size,size * 2,size);
                        ctx.strokeRect(x * size,y * size,size * 2,size);
                        x += 2;
                        ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];        
                        ctx.fillRect(x * size,y * size, size, size * 2);
                        ctx.strokeRect(x * size,y * size, size, size * 2);
                        x += 1;
                    }
            
                }
            }
        }


        canvas.width = innerWidth;
        canvas.height = innerHeight    
        origin.x = canvas.width / 2;
        origin.y = canvas.height / 2;
        size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
        drawPattern(size,origin,Math.PI / 3);
canvas {
   position : absolute;
   top : 0px;
   left : 0px;
}
<canvas id=canvas></canvas>

Upvotes: 4

Paul LeBeau
Paul LeBeau

Reputation: 101800

The best way to approach this is to examine the pattern and analyse its symmetry and how it repeats.

You can look at this several ways. For example, you could rotate the patter 45 degrees so that the tiles are plain orthogonal rectangles. But let's just look at it how it is. I am going to assume you are happy with it with 45deg tiles.

enter image description here

Like the tiles themselves, it turns out the pattern has a 2:1 ratio. If we repeat this pattern horizontally and vertically, we can fill the canvas with the completed pattern.

We can see there are five tiles that overlap with our pattern block. However we don't need to draw them all when we draw each pattern block. We can take advantage of the fact that blocks are repeated, and we can leave the drawing of some tiles to later rows and columns.

Let's assume we are drawing the pattern blocks from left to right and top to bottom. Which tiles do we need to draw, at a minimum, to ensure this pattern block gets completely drawn (taking into account adjacent pattern blocks)?

Since we will be starting at the top left (and moving right and downwards), we'll need to draw tile 2. That's because that tile won't get drawn by either the block below us, or the block to the right of us. The same applies to tile 3. It turns out those two are all we'll need to draw for each pattern block. Tile 1 and 4 will be drawn when the pattern block below us draws their tile 2 and 3 respectively. Tile 5 will be drawn when the pattern block to the south-east of us draws their tile 1.

We just need to remember that we may need to draw an extra column on the right-hand side, and at the bottom, to ensure those end-of-row and end-of-column pattern blocks get completely drawn.

The last thing to work out is how big our pattern blocks are.

Let's call the short side of the tile a and the long side b. We know that b = 2 * a. And we can work out, using Pythagoras Theorem, that the height of the pattern block will be:

h = sqrt(a^2 + a^2)
  = sqrt(2 * a^2)
  = sqrt(2) * a

The width of the pattern block we can see will be w = 2 * h.

Now that we've worked out how to draw the pattern, let's implement our algorithm.

const a = 60;
const b = 120;

const h = 50 * Math.sqrt(2);
const w = h * 2;
const h2 = h / 2;  // How far tile 1 sticks out to the left of the pattern block

// Set of colours for the tiles
const colours = ["red","cornsilk","black","limegreen","deepskyblue",
                 "mediumorchid", "lightgrey", "grey"]

const canvas = document.getElementById("herringbone");
const ctx = canvas.getContext("2d");

// Set a universal stroke colour and width
ctx.strokeStyle = "black";
ctx.lineWidth = 4;

// Loop through the pattern block rows
for (var y=0; y < (canvas.height + h); y+=h)
{
  // Loop through the pattern block columns
  for (var x=0; x < (canvas.width + w); x+=w)
  {
     // Draw tile "2"
     // I'm just going to draw a path for simplicity, rather than
     // worrying about drawing a rectangle with rotation and translates
     ctx.beginPath();
     ctx.moveTo(x - h2, y - h2);
     ctx.lineTo(x, y - h);
     ctx.lineTo(x + h, y);
     ctx.lineTo(x + h2, y + h2);
     ctx.closePath();
     ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
     ctx.fill();
     ctx.stroke();

     // Draw tile "3"
     ctx.beginPath();
     ctx.moveTo(x + h2, y + h2);
     ctx.lineTo(x + w - h2, y - h2);
     ctx.lineTo(x + w, y);
     ctx.lineTo(x + h, y + h);
     ctx.closePath();
     ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
     ctx.fill();
     ctx.stroke();
   }
}
<canvas id="herringbone" width="500" height="400"></canvas>

Upvotes: 1

Related Questions