如何在 html canvas 上绘制人字形图案

How to draw herringbone pattern on html canvas

我必须在 canvas 上绘制人字形图案并填充图像

有人请帮助我我是 canvas 二维绘图的新手。 我需要绘制带有交叉图案(人字形)的混合瓷砖

  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;
}

如何使用 svg 或 2d、3d 方法在 canvas 中绘制此图案。如果任何第三方图书馆也适合我。

我不知道从哪里开始以及如何绘制这个复杂的图案。

有人请帮我在 canvas 上用动态颜色绘制矩形填充图案。

这是我需要的输出示例:(人字形图案)

我使用 fabric.js library here is my JSFiddle

尝试了类似的操作

迷幻迪斯科地板

要获得图案,您需要为每一行向左或向右绘制一个水平平铺一个 space 矩形,垂直矩形也是如此。

矩形的纵横比是宽度乘以高度。

画图很简单

旋转很容易,难的是找到在哪里绘制瓷砖进行旋转。

为此,我创建了一个旋转的逆矩阵(它反转旋转)。然后我将该旋转应用到 canvas 0,0width,0 width,height0,height 的 4 个角,这在旋转的 [=58= 中给了我 4 个点] 位于 canvas.

的边缘

当我从左到右从上到下绘制图块时,我找到了左上角的最小角和右下角的最大角,将其展开一点,这样我就不会错过任何像素并绘制带有变换的图块设置了旋转。

因为我无法锻炼你想要的角度,所以函数会以任何角度绘制它。一个是动画,另一个是顺时针60度。

警告演示包含闪烁内容。

更新 闪烁已经过时了,所以做了一些更改,现在颜色混合得更令人愉悦并且具有固定的绝对位置,并且绑定了图块原点到鼠标位置,单击鼠标按钮也会循环显示一些尺寸。

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>

60 度的非动画版本

        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>

解决这个问题的最佳方法是检查模式并分析其对称性及其重复方式。

你可以从几个方面来看待这个问题。例如,您可以将图案旋转 45 度,使拼贴成为普通的正交矩形。但是让我们看看它是怎么回事。我假设您对 45 度瓷砖感到满意。

就像瓷砖本身一样,图案的比例为 2:1。如果我们水平和垂直重复这个图案,我们可以用完成的图案填充 canvas。

我们可以看到有五个图块与我们的图案块重叠。然而,我们不需要在绘制每个图案块时都绘制它们。我们可以利用块重复的特点,我们可以把一些瓦片的绘制留到后面的行和列。

假设我们正在从左到右、从上到下绘制图案块。我们需要至少绘制哪些图块,以确保完全绘制此图案块(考虑到相邻的图案块)?

由于我们将从左上角开始(向右和向下移动),我们需要绘制图块 2。这是因为该图块不会被我们下方的方块或下方的方块绘制在我们的右边。这同样适用于图块 3。 事实证明,这两个就是我们需要为每个模式块绘制的全部内容。当我们下面的图案块分别绘制其图块 2 和 3 时,将绘制图块 1 和 4。当我们东南方的图案块绘制他们的瓷砖 1 时,将绘制瓷砖 5。

我们只需要记住,我们可能需要在右侧和底部绘制额外的一列,以确保完全绘制行尾和列尾模式块.

最后要解决的问题是我们的模式块有多大。

我们称瓷砖的短边为a,长边为b。我们知道b = 2 * a。我们可以计算出,使用毕达哥拉斯定理,模式块的高度将为:

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

我们可以看到的图案块的宽度将是w = 2 * h

现在我们已经确定了如何绘制图案,让我们来实现我们的算法。

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>