Freehand creation of HTML objects Canva

I am creating a template in HTML canvas to call.

How would I randomly generate shapes like the ones coded below to form a pattern like the image. I created one version by creating code with drawscript in Illustrator, but it is far from perfect, how could I do the same with a loop?

enter image description here

thank

//triangles
ctx.fillStyle="rgb(75,128,166)";
ctx.beginPath();
ctx.moveTo(824,92);
ctx.lineTo(796,140);
ctx.lineTo(767,92);
ctx.lineTo(824,92);
ctx.fill();

//circles
ctx.fillStyle="rgba(35,121,67,0.8)";
ctx.beginPath();
ctx.moveTo(869,263);
ctx.bezierCurveTo(869,253,861,244,850,244);
ctx.bezierCurveTo(839,244,831,253,831,263);
ctx.bezierCurveTo(831,274,839,283,850,283);
ctx.bezierCurveTo(861,283,869,274,869,263);
ctx.fill();

      

+3


source to share


3 answers


You can make a square grid that is skewed for triangles, then have a random padding as well as a random diagonal subdivision of that rectangle to make it look like a triangle.

For circles, we can use a pseudo-hexagonal system, which means that the circles will be placed in hexagonal positions, just offsetting the circular encirclement and not the actual hexagon.

snap

Triangles

  • First define a simple mesh other than one that overlaps the drawing area (this is one approach, an alternative approach is to wrap the coordinates, but this requires transformation tracking, so brute force in this example)
  • Skew transform to rotate vertical alignment diagonally.
  • Determine coverage, fill cells, which in turn determine if they are separated, if the top, etc.

How to cover with random cells can be done in several ways, below is just one approach. Others can use a fixed grid system and iterate over it using a sweep based step (tracking is required to ensure accuracy). Third to fill the coverage of the cells, then an arbitrary sort of the array to shuffle the cells around.

Circles

A grid will also be used here, but since we're going to pack the vertical space to get closer to the hex grid, we need to compensate for that. The layout will be made taking into account the following factors:



  • vertical distance = diameter x sqrt (3) x 0.5 1)
  • horizontal distance = radius (offset toggles every 2nd row)

( 1) thanks to @Jason and his answer for reminding me of this!)

To compensate for the vertical "packed" circles, since they won't fill the bottom, we use the inverse sqrt (3) * 0.5 ( 1 / (sqrt(3) * 0.5)

).

Final result

Combining both of them into one canvas will result in this:

var canvas = document.querySelector("canvas"),
    ctx = canvas.getContext("2d"),
    w = canvas.width,
    h = canvas.height,
    cellsY = 14,                     // cells Y for triangles
    cellsX = cellsY * 2,             // cells X times two to overlap skew
    cw = w / cellsX * 2,             // cell width and height         
    ch = h / cellsY,
    toggle, cx = 0, cy,              // for circles
    cells = 25,                      // cells for cirles + comp. (see below)
    deltaY = 0.8660254037844386,     // = sqrt(3) * 0.5
    deltaYI = 1 / deltaY,            // inverse deltaY
    grid = new Uint8Array((cells * cells * deltaYI)|0), // circles "booleans"
    i;

// Calc and Render Triangles ---

// main transform: skew
ctx.setTransform(1, 0, 0.51, 1, -cellsX * cw * 0.5, 0);
ctx.fillStyle = "rgb(90, 146, 176)";

// fill random cells based on likely cover:
var cover = 0.67,                    // how much of total area to cover
    biasDiv = 0.6,                   // bias for splitting cell
    biasUpper = 0.5,                 // bias for which part to draw
    count = cellsX * cellsY * cover, // coverage
    tris = [],
    x, y, d, u, overlap;             // generate cells

for (i = 0; i < count; i++) {
  overlap = true;
  while (overlap) { // if we have overlapping cells
    x = (Math.random() * cellsX) | 0;
    y = (Math.random() * cellsY) | 0;
    overlap = hasCell(x, y);
    if (!overlap) {
      d = Math.random() < biasDiv;   // divide cell?
      u = Math.random() < biasUpper; // if divided, use upper part?
      tris.push({
        x: x,
        y: y,
        divide: d,
        upper: u
      })
    }
  }
}

function hasCell(x, y) {
  for (var i = 0, c; c = tris[i++];) {
    if (c.x === x && c.y === y) return true;
  }
  return false;
}

// render
for (i = 0; i < tris.length; i++) renderTri(tris[i]);
ctx.fill();  // fill all sub-paths

function renderTri(t) {
  var x = t.x * cw,                  // convert to abs. position
    y = t.y * ch;
  if (t.divide) {                    // create triangle
    ctx.moveTo(x + cw, y);           // define common diagonal
    ctx.lineTo(x, y + ch);
    t.upper ? ctx.lineTo(x, y) : ctx.lineTo(x + cw, y + ch);
  }
  else {
    ctx.rect(x, y, cw, ch);          // fill complete cell
  }
}

// Calc and Render Circles ---

cover = 0.5,                         // how much of total area to cover
count = Math.ceil(grid.length * cover); // coverage
cw = ch = w / cells;

ctx.setTransform(1,0,0,1,0,0);       // reset transforms
ctx.fillStyle = "rgb(32, 141, 83)";
ctx.globalCompositeOperation = "multiply";  // blend mode instead of alpha

if (ctx.globalCompositeOperation !== "multiply") ctx.globalAlpha = 0.5; // for IE

for (i = 0; i < count; i++) {
  overlap = true;
  while (overlap) {                  // if we have overlapping cells
    x = (Math.random() * cells) | 0;           // x index
    y = (Math.random() * cells * deltaYI) | 0; // calc y index + comp
    overlap = hasCircle(x, y);                 // already has circle?
    if (!overlap) {
      grid[y * cells + x] = 1;                 // set "true"
    }
  }
}

function hasCircle(x, y) {
  return grid[y * cells + x] === 1;
}

// render
ctx.beginPath();
cy = ch * 0.5;                               // start on Y axis
for (y = 0; y < (cells * deltaYI)|0; y++) {  // iterate rows + comp.
  toggle = !(y % 2);                         // toggle x offset
  for (x = 0; x < cells; x++) {              // columns
    if (grid[y * cells + x]) {               // has circle?
      cx = x * cw + (toggle ? cw * 0.5 : 0); // calc x
      ctx.moveTo(cx + cw * 0.5, cy);         // creat sub-path
      ctx.arc(cx, cy, cw * 0.5, 0, 2 * Math.PI); // add arc
      ctx.closePath();                       // close sub-path
    }
  }
  cy += ch * deltaY;                         // add deltaY
}
ctx.fill();                                  // fill all at once
      

body {background:#777}
canvas {padding:50px;background: rgb(226, 226, 226)}
      

<canvas width=600 height=600></canvas>
      

Run codeHide result


There is room for refactoring here and the randomization functions are not the best in terms of performance, but it should be enough to get you going. Hope this helps!

+4


source


Tip: Creating circles is easier with context.arc

rather than adding 4 Bezier curves (and 4 Bezier curves do not form a perfect circle, for example arc

).

Adding random circles

If you want more randomized coverage of the circles, you should try adding one circle at a time and make sure that each new attempt does not overlap existing circles.

enter image description here

Here's some sample code and demo that adds as many random circles as needed to cover 40% of the canvas area:



var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var PI2=Math.PI*2;
var radius=10;
var radiusTest=(2*radius)*(2*radius);
var circleCoverageDesired=.40;
var circleCount=parseInt((cw*ch*circleCoverageDesired)/(Math.PI*radius*radius))+1;
var circles=[];

ctx.fillStyle='green';
ctx.globalAlpha=0.25;

addRandomCircles();

function addRandomCircles(){
  // give up after "tries" to avoid unsolvable patterns
  var tries=circleCount*200;
  while(tries>0 && circles.length<circleCount){
    var x=Math.random()*(cw-radius*2)+radius/2;
    var y=Math.random()*(ch-radius*2)+radius/2;
    testRandomCircle(x,y);
    tries--;
  }
}


function testRandomCircle(x,y){
  for(var i=0;i<circles.length;i++){
    var c=circles[i];
    var dx=x-c.x;
    var dy=y-c.y;
    if(dx*dx+dy*dy<=radiusTest){
      return(false);
    }
  }
  var circle={x:x,y:y};
  circles.push(circle);
  ctx.beginPath();
  ctx.arc(x,y,radius,0,PI2);
  ctx.closePath();
  ctx.fill();
  var pct=parseInt((Math.PI*radius*radius*circles.length)/(cw*ch)*100);
  $('#count').text('Added: '+circles.length+' of '+circleCount+' needed circles for '+pct+'% coverage.');
  return(true);
}
      

body{ background-color: ivory; }
#canvas{border:1px solid red;}
      

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id=count>Count</h4>
<canvas id="canvas" width=300 height=300></canvas>
      

Run codeHide result


Adding random triangles

Adding random triangles requires the same constraint as adding random circles. You have to add one new triangle at a time and make sure that each new triangle does not overlap existing triangles.

Testing if the overlap of two polygons (like triangles) can be done using the dividing axis theorem,

A previous Stackoverflow answer by Matthias Buelens showed how the Separating Axis Theorem: javascript polygon intersection could be implemented

+1


source


OK Here's a function that will render this art style on any canvas on the page (with shape size and frequency, and canvas size):

function art(options, canvas) {
    var surface = document.getElementById(canvas),
        context = surface.getContext("2d"),
        row,
        col,
        triangleDirection = 1,
        triangleSize = options.triangle.size,
        circleSize = options.circle.size,
        circleStep = Math.sqrt(3) * circleSize * 2,
        circleOffset = 0;

    function shouldDraw(chances) {
        return Math.random() < chances;
    }

    function drawTriangle(x, y, direction, size, ctx) {
        ctx.fillStyle = options.triangle.color;
        ctx.beginPath();
        ctx.moveTo(x, y - (direction * size));
        ctx.lineTo(x - (direction * size), y + (direction * size));
        ctx.lineTo(x + (direction * size), y + (direction * size));
        ctx.lineTo(x, y - (direction * size));
        ctx.fill();
        ctx.strokeStyle = options.triangle.color;
        ctx.stroke();
    }

    function drawCircle(x, y, size, ctx) {
        //circles
        ctx.fillStyle = options.circle.color;
        ctx.beginPath();
        ctx.arc(x, y, size, 0, 2 * Math.PI, false);
        ctx.fill();
    }

    //Draw Tiangles
    for (col = 1; col < (surface.width / triangleSize); col++) {
        for (row = 1; row < (surface.height / triangleSize); row++) {
            if (shouldDraw(options.triangle.density)) {
                drawTriangle(row * triangleSize, col * triangleSize * 2, triangleDirection, triangleSize, context);
            }
            //Swap direction
            triangleDirection = -1 * triangleDirection;
        }
    }
    //Draw Circles
    for (row = 1; row < (surface.height / circleSize) - 1; row++) {
        for (col = 1; col < (surface.width / circleStep) - 1; col++) {
            if (shouldDraw(options.circle.density)) {
                drawCircle((row * circleSize), (col * circleStep) + circleOffset, circleSize, context);
            }
        }
        //swap offset by row
        if (row % 2 === 0) {
            circleOffset = circleStep / 2;
        } else {
            circleOffset = 0;
        }
    }
}

art({triangle: {size:24, density: 0.7, color: 'rgb(75,128,166)'}, circle: {size: 14, density: 0.2, color: 'rgba(35,121,67,0.8)'}}, 'surface')
      

#surface {
    width: 600px;
    height: 600px;
}
      

<canvas id='surface' height='600' width='600' />
      

Run codeHide result


Here are some highlights:

Drawing a triangle is an oscillating drawing of triangles up and down, so generalizing your triangular code to a function that can draw this based on arguments will make your code more convenient.

The circle pattern wobbles, but this time its line by line and shifts left and right. To figure this out, I needed to clear some underlying geometry. Looking at three circles above each other:

enter image description here

you can see their height moving in steps equal to the radius ( circleSize

in code). However, the distance they are from each other is more complicated, however, when you see it as an equilateral triangle, you can calculate it as the height of that triangle, or:

enter image description here

then you can see that the variables should be located at a distance twice this distance, which, after decreasing the fractions, turns into: Math.sqrt(3) * circleSize * 2

Hope it helps :)

+1


source







All Articles