johnbraum
johnbraum

Reputation: 296

Canvas - Create random flakes on image

Can anyone give me a hint how I can create something like that with javascript:

flakes

The requirement is that I can set the density of the flakes. and add up to 5 different colors.

I do know how to create a canvas and put pixels in there, but I don't know how to create the "flakes".

Is there a way to create random shapes like this?

Upvotes: 1

Views: 505

Answers (2)

Blindman67
Blindman67

Reputation: 54099

You can tessellate a simple shape and draw it at some random point.

The example below will create a 3 sided point, testate it randomly to a detail level of about 2 pixels and then add it to a path.

Then the path is filled with a color and another set of shapes are added.

function testate(amp, points) {
    const p = [];
    var i = points.length - 2, x1, y1, x2, y2;
    p.push(x1 = points[i++]);
    p.push(y1 = points[i]);
    i = 0;
    while (i < points.length) {
        x2 = points[i++];
        y2 = points[i++];
        const dx = x2 - x1;
        const dy = y2 - y1;
        const r = (Math.random() - 0.5) * 2 * amp;
        p.push(x1 + dx / 2 - dy * r);
        p.push(y1 + dy / 2 + dx * r);
        p.push(x1 = x2);
        p.push(y1 = y2);
    }
    return p;
}

function drawFlake(ctx, size, x, y, noise) {
    const a = Math.random() * Math.PI;
    var points = [];
    const step = Math.PI * (2/3);
    var i = 0;
    while (i < 3) {
        const r = (Math.random() * size + size) / 2;
        points.push(Math.cos(a + i * step) * r);
        points.push(Math.sin(a + i * step) * r);
        i++;
    }
    while (size > 2) {
        points =  testate(noise, points);
        size >>= 1;
    }
    i = 0;
    ctx.setTransform(1,0,0,1,x,y);
    ctx.moveTo(points[i++], points[i++]);
    while (i < points.length) {
        ctx.lineTo(points[i++], points[i++]);
    }
}
function drawRandomFlakes(ctx, count, col, min, max, noise) {
    ctx.fillStyle = col;
    ctx.beginPath();
    while (count-- > 0) {
        const x = Math.random() * ctx.canvas.width;
        const y = Math.random() * ctx.canvas.height;
        const size = min + Math.random() * (max- min);
        drawFlake(ctx, size, x, y, noise);
    }
    ctx.fill();
}

const ctx = canvas.getContext("2d");
canvas.addEventListener("click",drawFlakes);
drawFlakes();
function drawFlakes(){ 
    ctx.setTransform(1,0,0,1,0,0);
    ctx.fillStyle = "#341";
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
    const noise = Math.random() * 0.3 + 0.3;
    drawRandomFlakes(ctx, 500, "#572", 5, 10, noise)
    drawRandomFlakes(ctx, 200, "#421", 10, 15, noise)
    drawRandomFlakes(ctx, 25, "#257", 15, 30, noise)
}
body { background: #341 }
div {
   position: absolute;
   top: 20px;
   left: 20px;
   color: white;
}   
<canvas id="canvas" width = "600" height = "512"></canvas>
<div>Click to redraw</div>

Upvotes: 3

Jannes Carpentier
Jannes Carpentier

Reputation: 1898

You'll need a certain noise algorithm.

In this example I used Perlin noise, but you can use any noise algorithm that fits your needs. By using Perlin noise we can define a blob as an area where the noise value is above a certain threshold.

I used a library that I found here and based my code on the sample code. The minified code is just a small portion of it (I cut out simplex and perlin 3D).

LICENSE

You can tweek it by changing the following parameters

Math.abs(noise.perlin2(x / 25, y / 25))

Changing the 25 to a higher value will zoom in, lower will zoom out

if (value > 0.4){

Changing the 0.4 to a lower value will increase blob size, higher will decrease blob size.

!function(n){var t=n.noise={};function e(n,t,e){this.x=n,this.y=t,this.z=e}e.prototype.dot2=function(n,t){return this.x*n+this.y*t},e.prototype.dot3=function(n,t,e){return this.x*n+this.y*t+this.z*e};var r=[new e(1,1,0),new e(-1,1,0),new e(1,-1,0),new e(-1,-1,0),new e(1,0,1),new e(-1,0,1),new e(1,0,-1),new e(-1,0,-1),new e(0,1,1),new e(0,-1,1),new e(0,1,-1),new e(0,-1,-1)],o=[151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180],i=new Array(512),w=new Array(512);function u(n){return n*n*n*(n*(6*n-15)+10)}function f(n,t,e){return(1-e)*n+e*t}t.seed=function(n){n>0&&n<1&&(n*=65536),(n=Math.floor(n))<256&&(n|=n<<8);for(var t=0;t<256;t++){var e;e=1&t?o[t]^255&n:o[t]^n>>8&255,i[t]=i[t+256]=e,w[t]=w[t+256]=r[e%12]}},t.seed(0),t.perlin2=function(n,t){var e=Math.floor(n),r=Math.floor(t);n-=e,t-=r;var o=w[(e&=255)+i[r&=255]].dot2(n,t),h=w[e+i[r+1]].dot2(n,t-1),s=w[e+1+i[r]].dot2(n-1,t),a=w[e+1+i[r+1]].dot2(n-1,t-1),c=u(n);return f(f(o,s,c),f(h,a,c),u(t))}}(this);

const c = document.getElementById("canvas");
const cc = c.getContext("2d");

noise.seed(Math.random());

let image = cc.createImageData(canvas.width, canvas.height);
let data = image.data;

for (let x = 0; x < c.width; x++){
    for (let y = 0; y < c.height; y++){
        const value = Math.abs(noise.perlin2(x / 25, y / 25));
        const cell = (x + y * c.width) * 4;
        if (value > 0.4){
            data[cell] = 256;
            data[cell + 1] = 0;
            data[cell + 2] = 0;
            data[cell + 3] = 256;
        }
        else {
            data[cell] = 0;
            data[cell + 1] = 0;
            data[cell + 2] = 0;
            data[cell + 3] = 0;
        }
    }
}

cc.putImageData(image, 0, 0);
<canvas id="canvas" width=500 height=500></canvas>

Upvotes: 2

Related Questions