【问题标题】:How to draw herringbone pattern on html canvas如何在 html 画布上绘制人字形图案
【发布时间】:2020-08-13 23:13:45
【问题描述】:

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

有人请帮助我,我是画布 2d 绘图的新手。 我需要绘制带有交叉图案的混合瓷砖(人字形)

  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 方法在画布中绘制此图案。如果有任何第三方库对我来说也可以。

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

请有人帮我在画布上用动态颜色的矩形填充绘制这个图案。

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

我使用fabric.js 库尝试了类似的东西,这是我的JSFiddle

【问题讨论】:

    标签: javascript html canvas design-patterns fabricjs


    【解决方案1】:

    Trippy 迪斯科地板

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

    矩形的宽度是高度的 2 倍。

    绘制图案很简单。

    旋转也很容易,更难的部分是找到在哪里绘制用于旋转的图块。

    为此,我创建了一个旋转逆矩阵(它反转了一个旋转)。然后我将该旋转应用于画布的 4 个角 0,0width,0width,height0,height,这在旋转空间中位于画布边缘的 4 个点。

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

    因为我无法在函数中确定你想要的角度以任何角度绘制它。一个是动画,另一个是顺时针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;
    }
    &lt;canvas id=canvas&gt;&lt;/canvas&gt;

    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;
    }
    &lt;canvas id=canvas&gt;&lt;/canvas&gt;

    【讨论】:

    • 虽然这是一个很酷的演示,但它可能对 OP 没有帮助。他只是想学习如何绘制图案,而不是如何制作旋转迪斯科版本。 :)
    • @PaulLeBeau OP 只需要获取函数drawPattern 并调用一次即可停止动画。
    • 我看不出在花时间回答这些复杂问题的同时享受一点乐趣有什么错。学习应该是有趣的,只有脾气暴躁的猫不同意和投反对票。
    • 嗨@Blindman67,您的旋转模式示例非常棒。我有一点怀疑。如何在该图案而不是颜色上绘制图像。填充在矩形内的图像也应该在旋转时移动。
    • @Gopiraj 每个矩形都与旋转轴对齐,因此要绘制图像只需将fillRect 函数替换为水平平铺ctx.drawImage(imageToDraw,x * size,y * size,size * 2,size); 和垂直平铺ctx.drawImage(imageToDraw,x * size,y * size, size, size * 2)。这当然会拉伸图像,但是如果您查看ctx.drawImage,它可以很容易地更改为仅绘制图像的一部分。如果您有问题,请告诉我,我将添加一些代码以包括绘制图像。
    【解决方案2】:

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

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

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

    我们可以看到有五个瓷砖与我们的图案块重叠。但是,当我们绘制每个图案块时,我们不需要将它们全部绘制出来。我们可以利用块重复的事实,我们可以将一些图块的绘制留给后面的行和列。

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

    由于我们将从左上角开始(并向右和向下移动),我们需要绘制图块 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();
       }
    }
    &lt;canvas id="herringbone" width="500" height="400"&gt;&lt;/canvas&gt;

    【讨论】:

    • 感谢@PaulLeBeau,这非常简单易懂。
    • 嗨@PaulLeBeau 如何在矩形而不是颜色上填充图像。在我的原因中,图像出现在多个矩形中。图像应根据矩形旋转。
    • 如何实现像linlk这样的视图,瓷砖的高度和宽度应该可以更改为任意大小。
    • 这有点复杂。您需要针对不同大小的图块调整算法。可以使用drawImage() 绘制木材纹理。此页面上的内容应该可以帮助您入门。祝你好运!
    • 您好,感谢您的帮助。我找到了以任何长度和宽度绘制人字形图案的解决方案。这是我的jsfiddle。现在我尝试在其上随机绘制多个图像。 :)
    猜你喜欢
    • 1970-01-01
    • 2018-01-14
    • 1970-01-01
    • 2018-07-10
    • 1970-01-01
    • 2022-11-12
    • 2014-10-16
    • 2013-01-24
    • 1970-01-01
    相关资源
    最近更新 更多