Search code examples
javascripthtmlcanvasdesign-patternsfabricjs

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


Solution

  • 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>