Create a JavaScript module that generates a resembling Generativemasks.

Generativemasks example image.

'Generativemasks' is the interesting NFT art that creates a mask with different colors and patterns every reload.

The code of 'Generativemasks' was published as open-source. Anyone can make derivatives freely under the terms of the CC BY-NC-SA 3.0 license.

I was interested in it so I made a derivative.

👉 この記事は日本語でも読めます。


Then, I realized that there were some problems when I used the original code directly.

  • Canvas size affects the mask shape.
  • 'randomSeed()' influences the whole code.
  • etc.

So I wrote a JavaScript module. It generates a mask and it returns the result as 'p5.Graphics'. You can designate the mask size and the color palette to draw!

It settles a matter caused by using the original code directly, and you can make a derivative easily.

Let me show you this JavaScript module and the example application. It must be fun to make some derivatives with this!

 

What I made.

I wrote a code that freely makes 'Generativemasks' like below.

  • Size changeable.
  • Can assign original color palette(compatible with coolers URL style).
  • Set a background color or transparent.
  • Set a shadow effect or not.
  • Set a texture effect or not.

The name of this module is 'Garg'. 'Garg' stands for 'Generates A Resembling Generativemasks'.

'Garg' can generate much the same as Generativemasks shape and pattern. But it is not exactly the same that is because I modified the mask generation part of the original code.

They said 'You can use the original code if you want the original shape'.

 

The example application.

I made a web application called 'Generativemasks to Your Taste' to demonstrate the functions of 'Garg'. You can make your favorite size and color mask with it.

The appearance of web application called 'Generativemasks to Your Taste'.
'Generativemasks to Your Taste' on OpenProcessing.

The settings below will generate the mask much the same as the original Generativemasks.

  • Size = 1600
  • Color palette : none
  • Background color : on
  • Shadow : on
  • Texture : on

 

Yet another application.

Collaborate with the 'p5.pattern' made by Mr. SYM.

The 'p5.pattern' is a 'p5.js' pattern generate library made by Mr.SYM(@hyappy717). I tweaked the 'Garg' to draw the Generativemasks pattern with 'p5.pattern' and I made 'GargPattern'. This is the example web application using 'GargPattern'.

The appearance of web application called 'Generativemasks meets p5.pattern'.
'Generativemasks meets p5.pattern' on OpenProcessing.

 

This was just the reason to make the 'Garg' module.

It makes Generativemasks to the abstract art. I applied the Node-Garden technique in the article 'The node-garden technique : application section.' to the image of Generativemasks.

The appearance of web application called 'Generativemasks Abstractor'.
'Generativemasks Abstractor' on OpenProcessing.

You'll get an image like this with low-level abstraction.

The example result image of web application called 'Generativemasks Abstractor'.

 

How to use the 'Garg'.







It's an example p5.js code to draw a mask on the canvas. The mask id = 37, size = 480x480, shadow effect on, texture effect off, background-color off.


const gg = new Garg(true, false, false);
const mask = gg.createMask(37, 480);
image(mask, 0, 0);
mask.remove();

The example mask image that was generated by 'Garg'.

 

Constructor

new Garg(s, t, b)

Parameters

s boolean : true Shdow effect on
t boolean : true Texture effect on
b boolean : true Background-color on, false Tranaparent background

 

Check the color palette

Method

chkRgbStrings(s)

Parameters

s String : Color palette

Description

It checks the syntax of the color palette. It returns true when passing the check, nor it returns false.

The color palette must be RGB, 'RRGGBB-RRGGBB-RRGGBB' form and it needs at least three colors. For your bonus, it is compatible with the 'coolers' URL form.
ex. https://coolors.co/000000-14213d-fca311-e5e5e5-ffffff

 

Create the mask

Method

createMask(i, s)

Parameters

i Number : Generativemasks ID to create. The original id is between 0 to 9999. 'Garg' accepts over 10000.
s Number : The size of the image to create. It returns a square image so it is s x s pixels image.

Description

It create mask and it returns as 'p5.Graphics'. Please 'remove() properly the 'p5.Graphics' when it becomes unneeded'.

 

Apply color palette

Method

setPalette(p)

Parameters

p String : Color palette

Description

It applies the given color palette. To make the mask with this color palette, you need to run this method before 'createMask()'.

See 'chkRgbStrings()' description for the syntax of the color palette.

 

Apply random color palette

Method

setRandomPalette()

Parameters

none

Description

It applies the randomly chosen color palette from the palettes inside 'Garg'

 

The code of 'Garg'.

The latest version 1.0a is here. I hide the code because it is so long.


/* 
 * Garg : Generates A Resembling Generativemasks.
 * 
 * Version : 0.1a
 * Auther  : deconbatch (https://www.deconbatch.com/)
 * Revise  : tetunori   
 * License : Creative Commons Attribution Non-Commercial Share Alike license.
 *
 * Usage :
 *         const gg = new Garg(shadow, texture, bgColor);
 *         gg.setPalette(cPalette);
 *         const mask = gg.createMask(maskID, maskSize);
 *         image(mask, 0, 0);
 *         mask.remove();
 *
 * Change log :
 *   2021.04.Dec
 *     Add remove procedure to internal graphics. Add remove() method to the Garg class.
 *   2021.16.Oct
 *     Forked from sketch.js of 2021.Aug.27 version on https://github.com/Generativemasks/generativemasks.github.io
 *     The author of the original Generativemasks is Shunsuke Takawo (https://generativemasks.on.fleek.co/).
 *
 */

class Garg {

    inst; // this instance
    cSize; // canvas size
    sRadius; // mask shape radius
    needShadow; // apply shadow or not
    needTexture; // apply texture or not
    needBackdrop; // paint background or not
    palette; // selected palette

    constructor(_shadow, _texture, _back) {

	// define this instance canvas
	this.inst = createGraphics(0, 0);
	this.inst.colorMode(RGB, 255);

	// it must draw a mask on 1600x1600 canvas
	this.cSize = 1600;
	const offset = this.cSize / 10;
	this.sRadius = (this.cSize - offset * 2) * 3 / 4;

	// switches
	this.needShadow = _shadow;
	this.needTexture = _texture;
	this.needBackdrop = _back;

	// set random palette
	this.setRandomPalette();

    }


    /*
     * setPalette : sets color palette from "rrggbb-rrggbb-rrggbb-rrggbb-rrggbb" style parameter.
     */
    setPalette(_rgbStrings) {

	const rgbStr = _rgbStrings.replace(/.*\//g, ""); // can use coolors url directly

	if (this.chkRgbStrings(rgbStr)) {
	    this.palette = this.getPalette(rgbStr);
	} else {
	    // if _rgbStrings was invalid set random palette
	    this.setRandomPalette();
	}
    }


    /*
     * setRandomPalette : sets random color palette.
     */
    setRandomPalette() {

	const defaultRGBs = [
	    "202c39-283845-b8b08d-f2d492-f29559",
	    "1f2041-4b3f72-ffc857-119da4-19647e",
	    "2f4858-33658a-86bbd8-f6ae2d-f26419",
	    "ffac81-ff928b-fec3a6-efe9ae-cdeac0",
	    "f79256-fbd1a2-7dcfb6-00b2ca-1d4e89",
	    "e27396-ea9ab2-efcfe3-eaf2d7-b3dee2",
	    "966b9d-c98686-f2b880-fff4ec-e7cfbc",
	    "50514f-f25f5c-ffe066-247ba0-70c1b3",
	    "177e89-084c61-db3a34-ffc857-323031",
	    "390099-9e0059-ff0054-ff5400-ffbd00",
	    "0d3b66-faf0ca-f4d35e-ee964b-f95738",
	    "177e89-084c61-db3a34-ffc857-323031",
	    "780000-c1121f-fdf0d5-003049-669bbc",
	    "eae4e9-fff1e6-fde2e4-fad2e1-e2ece9-bee1e6-f0efeb-dfe7fd-cddafd",
	    "f94144-f3722c-f8961e-f9c74f-90be6d-43aa8b-577590",
	    "555b6e-89b0ae-bee3db-faf9f9-ffd6ba",
	    "9b5de5-f15bb5-fee440-00bbf9-00f5d4",
	    "ef476f-ffd166-06d6a0-118ab2-073b4c",
	    "006466-065a60-0b525b-144552-1b3a4b-212f45-272640-312244-3e1f47-4d194d",
	    "f94144-f3722c-f8961e-f9844a-f9c74f-90be6d-43aa8b-4d908e-577590-277da1",
	    "f6bd60-f7ede2-f5cac3-84a59d-f28482",
	    "0081a7-00afb9-fdfcdc-fed9b7-f07167",
	    "f4f1de-e07a5f-3d405b-81b29a-f2cc8f",
	    "50514f-f25f5c-ffe066-247ba0-70c1b3",
	    "001219-005f73-0a9396-94d2bd-e9d8a6-ee9b00-ca6702-bb3e03-ae2012-9b2226",
	    "ef476f-ffd166-06d6a0-118ab2-073b4c",
	    "fec5bb-fcd5ce-fae1dd-f8edeb-e8e8e4-d8e2dc-ece4db-ffe5d9-ffd7ba-fec89a",
	    "e63946-f1faee-a8dadc-457b9d-1d3557",
	    "264653-2a9d8f-e9c46a-f4a261-e76f51",
	];

	// this random must be free from setting the randomSeed
	this.palette = this.getPalette(this.inst.random(defaultRGBs));

    }


    /*
     * getPalette : returns color palette.
     * rgbStrings must be "rrggbb-rrggbb-rrggbb" style, at least 3 colors.
     */
    getPalette(_rgbStrings) {

	const arr = _rgbStrings.split("-");
	for (let i = 0; i < arr.length; i++) {
	    arr[i] = this.inst.color("#" + arr[i]);
	}

	// this shuffle must be free from setting the randomSeed
	return this.inst.shuffle(arr, true);
    }


    /*
     * chkRgbStrings : returns RGB strings check result.
     * true : check OK, false : error
     */
    chkRgbStrings(_rgbStrings) {

	const rgbStr = _rgbStrings.replace(/.*\//g, ""); // can use coolors url directly
	const checker = new RegExp(/^(([0-9]|[a-fA-F]){6}-){2,}([0-9]|[a-fA-F]){6}$/);
	if (checker.test(rgbStr)) {
	    return true;
	} else {
	    return false;
	}
    }


    /*
     * createMask : returns _size resized Generativemasks (ID = _id) on p5.Graphics.
     */
    createMask(_id, _size) {
	// set color palette
	const c = this.palette[0];
	const shifted = this.palette.shift(); // without this, affect pattern

	// define mask canvas
	const maskCv = createGraphics(_size, _size);
	maskCv.pixelDensity(1);
	maskCv.colorMode(RGB, 255);
	maskCv.angleMode(DEGREES);
	maskCv.clear(); // transparent background
	maskCv.fill(c);
	maskCv.stroke(c);
	maskCv.strokeWeight(30);
	maskCv.scale(_size / this.cSize); // resize

	// set mask id
	maskCv.randomSeed(_id);
	maskCv.noiseSeed(_id);

	// draw shape and pattern
	const nScale = maskCv.random(60, 200); // must be 60 - 200
	this.drawShape(this.cSize / 2, this.cSize / 2, this.sRadius, nScale, maskCv);

	// repair palette
	this.palette.push(shifted);

	// return resized canvas
	const sizedPg = createGraphics(_size, _size);
	if (this.needBackdrop) {
	    sizedPg.background(this.inst.random(this.palette));
	} else {
	    sizedPg.clear();
	}
	if (this.needShadow) {
	    sizedPg.drawingContext.shadowColor = this.inst.color(0, 128);
	    sizedPg.drawingContext.shadowBlur = _size / 20;
	    sizedPg.drawingContext.shadowOffsetY = _size / 40;
	}
	sizedPg.image(maskCv, 0, 0);
	maskCv.remove();
	if (this.needTexture) {
	    sizedPg.scale(_size / this.cSize); // resize
	    const texture = this.getTexture(this.cSize);
	    sizedPg.image(texture, 0, 0);
	    texture.remove();
	}

	return sizedPg;
    }


    /*
     * drawShape : clip the shape on the center of p5.Graphics g.
     */
    drawShape(cx, cy, r, nPhase, target) {

	const vertexPV = new Array();

	// calculate the shape
	let minX = this.cSize;
	let maxX = -this.cSize;
	let minY = this.cSize;
	let maxY = -this.cSize;
	for (let angle = 0; angle < 360; angle += 1) {
	    let nr = map(target.noise(cx, cy, (angle - 180) / nPhase), 0, 1, (r * 1) / 8, r);
	    nr = constrain(nr, 0, this.cSize / 2);
	    let x = target.cos(angle) * nr;
	    let y = target.sin(angle) * nr;
	    vertexPV.push(createVector(x, y));
	    minX = min(minX, x);
	    maxX = max(maxX, x);
	    minY = min(minY, y);
	    maxY = max(maxY, y);
	}

	// draw shape on the center of canvas
	const divX = lerp(minX, maxX, 0.5);
	const divY = lerp(minY, maxY, 0.5);
	target.push();
	//	target.translate(cx, cy, r);
	target.translate(cx, cy);
	target.rotate(90);
	target.beginShape();
	for (let p of vertexPV) {
	    vertex(p.x - divX, p.y - divY);
	}
	target.endShape(CLOSE);
	target.pop();

	// clip shape
	target.drawingContext.clip();

	this.drawGraphic(-divY, -divX, this.cSize, this.cSize, this.palette, target);

    }


    /*
     * drawGraphic : draw graphics on p5.Graphics target.
     */
    drawGraphic(x, y, w, h, colors, target) {
	let g = createGraphics(w / 2, h);
	g.angleMode(DEGREES);
	g.translate(x, y);
	let gx = 0;
	let gy = 0;
	let gxStep, gyStep;

	if (target.random() > 0.5) {
	    while (gy < g.height) {
		gyStep = target.random(g.height / 100, g.height / 5);
		if (gy + gyStep > g.height || g.height - (gy + gyStep) < g.height / 20) {
		    gyStep = g.height - gy;
		}
		gx = 0;
		while (gx < g.width) {
		    gxStep = gyStep;
		    if (gx + gxStep > g.width || g.width - (gx + gxStep) < g.width / 10) {
			gxStep = g.width - gx;
		    }
		    // g.ellipse(gx+gxStep/2,gy+gyStep/2,gxStep,gyStep);
		    this.drawPattern(g, gx, gy, gxStep, gyStep, colors, target);
		    gx += gxStep;
		}
		gy += gyStep;
	    }
	} else {
	    while (gx < g.width) {
		gxStep = target.random(g.width / 100, g.width / 5);
		if (gx + gxStep > g.width || g.width - (gx + gxStep) < g.width / 20) {
		    gxStep = g.width - gx;
		}
		gy = 0;
		while (gy < g.height) {
		    gyStep = gxStep;
		    if (gy + gyStep > g.height || g.height - (gy + gyStep) < g.height / 10) {
			gyStep = g.height - gy;
		    }
		    // g.ellipse(gx+gxStep/2,gy+gyStep/2,gxStep,gyStep);
		    this.drawPattern(g, gx, gy, gxStep, gyStep, colors, target);
		    gy += gyStep;
		}
		gx += gxStep;
	    }
	}

	target.push();
	//	target.translate(x + w / 2, y + h / 2);
	target.translate(w / 2, h / 2);
	target.imageMode(CENTER);
	target.scale(1, 1);
	target.image(g, -g.width / 2, 0);
	target.scale(-1, 1);
	target.image(g, -g.width / 2, 0);
	target.pop();

	g.remove();
    }


    /*
     * drawPattern : draw patterns on p5.Graphics g.
     */
    drawPattern(g, x, y, w, h, colors, target) {
	let rotate_num = (int(target.random(4)) * 360) / 4;
	g.push();
	g.translate(x + w / 2, y + h / 2);
	g.rotate(rotate_num);
	if (rotate_num % 180 == 90) {
	    let tmp = w;
	    w = h;
	    h = tmp;
	}
	g.translate(-w / 2, -h / 2);
	if (this.needShadow) {
	    g.drawingContext.shadowColor = this.inst.color(0, 84);
	    g.drawingContext.shadowBlur = max(w, h) / 5;
	}
	let sep = int(target.random(1, 6));

	let c = -1,
	    pc = -1;
	g.stroke(0, (20 / 100) * 255);

	switch (int(target.random(8))) {
	case 0:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.scale(i);
		g.strokeWeight(1 / i);
		g.fill(c);
		g.arc(0, 0, w * 2, h * 2, 0, 90);
		g.pop();
	    }
	    break;
	case 1:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.fill(c);

		g.push();
		g.translate(w / 2, 0);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.arc(0, 0, w, h, 0, 180);
		g.pop();

		g.push();
		g.translate(w / 2, h);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.arc(0, 0, w, h, 0 + 180, 180 + 180);
		g.pop();
		g.pop();
	    }
	    break;
	case 2:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.fill(c);

		g.push();
		g.scale(i);
		g.strokeWeight(1 / i);
		g.arc(0, 0, w * sqrt(2), h * sqrt(2), 0, 90);
		g.pop();

		g.push();
		g.translate(w, h);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.arc(0, 0, w * sqrt(2), h * sqrt(2), 0 + 180, 90 + 180);
		g.pop();

		g.pop();
	    }
	    break;
	case 3:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.translate(w / 2, h / 2);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.fill(c);
		g.ellipse(0, 0, w, h);
		g.pop();
	    }
	    break;
	case 4:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.scale(i);
		g.strokeWeight(1 / i);
		g.fill(c);
		g.triangle(0, 0, w, 0, 0, h);
		g.pop();
	    }
	    break;
	case 5:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.fill(c);

		g.push();
		g.translate(w / 2, 0);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.triangle(-w / 2, 0, w / 2, 0, 0, h / 2);
		g.pop();

		g.push();
		g.translate(w / 2, h);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.triangle(-w / 2, 0, w / 2, 0, 0, -h / 2);
		g.pop();
		g.pop();
	    }
	    break;
	case 6:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.fill(c);

		g.push();
		g.scale(i);
		g.strokeWeight(1 / i);
		g.triangle(0, 0, w * sqrt(2), 0, 0, h * sqrt(2));
		g.pop();

		g.push();
		g.translate(w, h);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.arc(0, 0, -w * sqrt(2), 0, 0, -h * sqrt(2));
		g.pop();

		g.pop();
	    }
	    break;
	case 7:
	    for (let i = 1; i > 0; i -= 1 / sep) {
		while (pc == c) {
		    c = target.random(colors);
		}
		pc = c;
		g.push();
		g.translate(w / 2, h / 2);
		g.rotate(45);
		g.scale(i);
		g.strokeWeight(1 / i);
		g.fill(c);
		g.rectMode(CENTER);
		g.square(0, 0, sqrt(sq(w) + sq(h)));
		g.pop();
	    }
	    break;
	}
	g.pop();
    }


    /*
     * getTexture : returns texture image on _size x _size p5.Graphics.
     */
    getTexture(_size) {
	const tex = createGraphics(_size, _size);
	tex.colorMode(HSB, 360, 100, 100, 100);
	tex.angleMode(DEGREES);

	tex.strokeWeight(0.1);
	for (let x = 0; x < _size; x += 20) {
	    for (let y = 0; y < _size; y += 20) {
		let angle = tex.random(75, 105);
		let d = _size / 3; //10;
		tex.stroke(0, 0, 0, tex.random(20));
		tex.line(
		    x + tex.cos(angle) * d,
		    y + tex.sin(angle) * d,
		    x + tex.cos(angle + 180) * d,
		    y + tex.sin(angle + 180) * d
		);
	    }
	}

	return tex;
    }

}


Here is the diff between version 1.0 and 1.0a. Please use this as a patch of your derivatives of the 'Garg'.


4c4
<  * Version : 0.1
---
>  * Version : 0.1a
5a6
>  * Revise  : tetunori   
11c12,14
<  *         image(gg.createMask(maskID, maskSize), 0, 0);
---
>  *         const mask = gg.createMask(maskID, maskSize);
>  *         image(mask, 0, 0);
>  *         mask.remove();
13a17,18
>  *   2021.04.Dec
>  *     Add remove procedure to internal graphics. Add remove() method to the Garg class.
185a191
>       maskCv.remove();
188c194,196
<           sizedPg.image(this.getTexture(this.cSize), 0, 0);
---
>           const texture = this.getTexture(this.cSize);
>           sizedPg.image(texture, 0, 0);
>           texture.remove();
190d197
<       return sizedPg;
191a199
>       return sizedPg;
298a307,308
> 
>       g.remove();

 

Acknowledgement.

I've had a fun time making 'Garg'. I would like to thank you all for your effort to publish the Generativemasks code and the originator of Generativemasks takawo shunsuke (@takawo).

It's my great pleasure to see you make something with 'Garg'. If you find bugs or something please let me know via Twitter.

Mr. Tetsunori NAKAYAMA (@tetunori_lego) kindly send me a patch to fix the memory leak problem. Thank you Nakayama san!

To tell the truth, it's my first time making a JavaScript module, and also I wonder I can call it a 'module'.

 

Next Post Previous Post
No Comment
Add Comment
comment url