javascript – How to draw this specified image in html 5 canvas using for loops?-ThrowExceptions

Exception or error:

I want to create some patterns in HTML 5 canvas using for loops.
i am not able to create appropriate patterns as specified in picture. also i am struggling with end points x,y coordinate in for loops.

enter image description here

<html>
    <body>
        <style>
            *{
                margin: 0px;
            }
            body{
                background-color: aqua;
            }
            canvas
            {
                background-color: white;
            }
        </style>
        <canvas id="mycanvas" width="5000" height="5000"></canvas>
        <script>
var canvas = document.getElementById('mycanvas');
var context = canvas.getContext('2d');
           
                for(i=0; i<1000;i=i+10){
                    
                    context.moveTo(i, i*50);
                   context.bezierCurveTo(i*10,i*10,0,100,i,0);
                    context.stroke();   
                }
                
            
    
        </script>
    </body>
</html>
How to solve:

The question is vague as the constraints of the problem are not defined. This answer illustrates a solution in steps. Defining functions and data structures that solve sub parts of my interpretation of the problem.

If you are wanting an instant cut and paste solution, you will not get that without rigorously defining the constraints.

This answer requires that you have a basic understanding of JavaScript and Programming and will not help you solve the problem if you do not have this experience.

Repeat random paths.

This solution will repeat a arbitrary path across the canvas.

The Path

Define the path as a set of named function and their arguments.

As at this point we don’t know the size of the canvas, the coordinate data should be easily scale-able to fit any sized canvas. To do that the arguments must be coordinate pairs and should fit a canvas that is 1 by 1 units in size (this ignore the aspect of the canvas).

const path = [
    ["moveTo", [1.1, -0.1]],
    ["bezierCurveTo", [0.75, 1.1, 0.5, 0.25,  0.5, 0.5]],
    ["bezierCurveTo", [0.75, 0.65, 0.25, 0.75,  -0.1, 1.1]],
];

Note you could also use a Path2D object to define the path, but this introduces some problems regarding the extent of the path. Something that is needed if we are to cover the canvas.

Render a Path

Now a function that renders the path.

It needs be able to

  • know what 2D context to draw on.
  • move the path to a position relative to the top left of the canvas.
  • scale the path to fit the canvas without changing the aspect
  • set the width of the stroke in pixels
  • set the stroke style.

.

function drawFittedPath(ctx, path, x, y, lineWidth, style) {
    const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
    ctx.setTransform(scale, 0, 0, scale, x, y);
    ctx.beginPath();
    for(const subPath of path) { ctx[subPathp[0](...subPath[1]) }
    ctx.strokeStyle = style;
    ctx.lineWidth = lineWidth;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.stroke();
}

Where to render

As the path needs to be renders to cover the whole canvas we need to know where relative to the top left of the canvas the path will be visible. To do this we can create a query function that takes the path and a 2D context and returns information in regard to the query.

To avoid repeating code there is a second function within that extracts the x, or y coordinates from the defined path data.

function pathQuery(ctx, path, query) {
    const pathCoords2Array = (offset, stride = 2) => {
        const arr = [];
        for(const subPath of path) {
            let i = offset;
            while (i < subPath[1].length) { 
                arr.push(subPath[1][i]);
                i += stride;
            }
        }
        return arr;
    }
    query = query.toLowerCase();
    const xCoords = pathCoords2Array(0, 2);
    const yCoords = pathCoords2Array(1, 2);
    const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
    const result = {x:0,y:0};
    if (query.includes("left")) { result.x = -Math.max(...xCoords) * scale; }
    else { result.x = ctx.canvas.width - Math.min(...xCoords) * scale; }
    if (query.includes("top")) { result.y = -Math.max(...yCoords) * scale; }
    else { result.y = ctx.canvas.height - Math.min(...yCoords) * scale; }
    return result;
}

Filling the canvas

Now with all the functionality in place we can add the final part that renders the path to fill the canvas.

In this case we need to define the spacing between paths. For simplicity we can define that as the number of paths we want to see and calculate the x and y steps needed to do that.

function fillCanvasWithPath(ctx, path, count, lineWidth, style) {
    const topLeft = pathQuery(ctx, path, "topLeft");
    const botRight = pathQuery(ctx, path, "bottomRight");
    const xStep = ctx.canvas.width / count;
    const yStep = ctx.canvas.height / count;
    var x = topLeft.x - lineWidth / 2;
    var y = topLeft.y - lineWidth / 2;
    while (x < botRight.x + lineWidth && y < botRight.y + lineWidth) {
        drawFittedPath(ctx, path, x, y, lineWidth, style);
        x += xStep;
        y += yStep;
    }
}

Example

const path = [
    ["moveTo", [1.1, -0.1]],
    ["bezierCurveTo", [0.75, 0.1, 0.5, 0.15,  0.5, 0.3]],
    ["bezierCurveTo", [0.5, 0.65, 0.05, 0.2,  0.2, 0.6]],
    ["bezierCurveTo", [0.35, 0.9, 0.0, 0.75,  -0.1, 1.1]],
];

const ctx = canvas.getContext("2d");
ctx.lineJoin = "round";
fillCanvasWithPath(ctx, path,50, 6, "#000");


function drawFittedPath(ctx, path, x, y, lineWidth, style) {
    const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
    ctx.setTransform(scale, 0, 0, scale, x, y);
    ctx.beginPath();
    for(const subPath of path) { ctx[subPath[0]](...subPath[1]) }
    ctx.strokeStyle = style;
    ctx.lineWidth = lineWidth;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.stroke();
}

function pathQuery(ctx, path, query) {
    const pathCoords2Array = (offset, stride = 2) => {
        const arr = [];
        for(const subPath of path) {
            let i = offset;
            while (i < subPath[1].length) { 
                arr.push(subPath[1][i]);
                i += stride;
            }
        }
        return arr;
    }
    query = query.toLowerCase();
    const xCoords = pathCoords2Array(0, 2);
    const yCoords = pathCoords2Array(1, 2);
    const scale = Math.max(ctx.canvas.width, ctx.canvas.height);
    const result = {x:0,y:0};
    if (query.includes("left")) { result.x = -Math.max(...xCoords) * scale; }
    else { result.x = ctx.canvas.width - Math.min(...xCoords) * scale; }
    if (query.includes("top")) { result.y = -Math.max(...yCoords) * scale; }
    else { result.y = ctx.canvas.height - Math.min(...yCoords) * scale; }
    return result;
}

function fillCanvasWithPath(ctx, path, count, lineWidth, style) {
    const topLeft = pathQuery(ctx, path, "topLeft");
    const botRight = pathQuery(ctx, path, "bottomRight");
    const xStep = ctx.canvas.width / count;
    const yStep = ctx.canvas.height / count;
    var x = topLeft.x - lineWidth / 2;
    var y = topLeft.y - lineWidth / 2;
    while (x < botRight.x + lineWidth && y < botRight.y + lineWidth) {
        drawFittedPath(ctx, path, x, y, lineWidth, style);
        x += xStep;
        y += yStep;
    }
}
    
canvas {
  border: 2px solid #666;
}
<canvas id="canvas" width="1024" height="1024"></canvas>

Problems

Path types

The example requires that the path is queried for extent. It assumes that the path is created from coordinate pairs. However some paths can be created with more information than just coordinates, eg ctx.arc The extent of such paths is not that simple and requires code to calculate the real extent from the data available. This is beyond the scope of the basic question and this answer.

Render state

This solution makes assumptions in regard to the 2D context state. Some states will effect the extent of each path. The assumption is that the 2D context is in the default state.

Spacing

In the example image you provided the paths are equally spaced at all points along the path. As the example solution uses an arbitrary path this restraint is not always possible, in fact for a random path it is highly improbable that this condition can be met.

The solution is that you must be very careful with the path you define. Or you can ignore the constraint.

The path

  • needs to consider the aspect of the image
  • can not be closed
  • must be long enough to cover the canvas

If you want each path to have all points along it to be equal distance apart then you need to use a different method that creates a custom path for each position across the canvas. This is again beyond the scope of a simple answer.

Leave a Reply

Your email address will not be published. Required fields are marked *