art
generative
fxhash
the Beauty of Generative Watercolor Art: A Blend of Code and Color

the Beauty of Generative Watercolor Art: A Blend of Code and Color

written by reyrove

06 Oct 2024100 EDITIONS
1 TEZ

The Canvas Setup: Creating the Space for Art

The first step in our artistic journey is setting up the canvas and defining our workspace.

const e = Math.min(innerWidth, innerHeight);
const canvas = { w: e, h: e };

Next, we initialize arrays for the brushstrokes, colors, and dreamy elements. We also define variables that control the number of brushstrokes and elements in the artwork.

let brushColors = [];
let brushStrokes = [];
let paperTexture;
let dreamyElements = [];
let backgroundColors = [];
const numBrushStrokes = 60;
const numColors = 30;
const numDreamyElements = 20;

These variables will help us populate the artwork with the right number of elements and variations.

Color Initialization: Building the Palette

The watercolor effect relies heavily on a soft, harmonious palette. To achieve this, we generate random colors within a pleasant range.

function initializeColors() {
    for (let i = 0; i < numColors; i++) {
        brushColors.push(color($fx.rand() * 205 + 50, $fx.rand() * 205 + 50, $fx.rand() * 205 + 50, $fx.rand() * 100 + 50));
    }
}

Creating the Background: Subtle Layering

A good watercolor piece often begins with a soft gradient or layered background. Here, we divide the canvas into segments, each filled with a different color.

function createBackground() {
    for (let i = 0; i < numColors; i++) {
        backgroundColors.push(color($fx.rand() * 205 + 50, $fx.rand() * 205 + 50, $fx.rand() * 205 + 50, 255));
    }
}

In the drawBackground function, we apply these colors in rectangular segments.

function drawBackground() {
    let segmentHeight = canvas.h / numColors;
    for (let i = 0; i < numColors; i++) {
        fill(backgroundColors[i]);
        rect(0, i * segmentHeight, canvas.w, segmentHeight);
    }
}

Brushstrokes: The Heart of Watercolor

Next, we simulate brushstrokes by placing large, semi-transparent ellipses with feathered edges.

function createBrushStrokes() {
    for (let i = 0; i < numBrushStrokes; i++) {
        let x = $fx.rand() * canvas.w;
        let y = $fx.rand() * canvas.h;
        let size = $fx.rand() * (canvas.w / 3 - canvas.w / 8) + canvas.w / 8;
        let colorIndex = floor($fx.rand() * brushColors.length);

        brushStrokes.push({
            x: x,
            y: y,
            color: brushColors[colorIndex],
            size: size
        });
    }
}

In drawWatercolorStroke, we draw the brushstrokes with layered ellipses, gradually decreasing in size and transparency to mimic the real-life effects of watercolor.

function drawWatercolorStroke(x, y, col, size) {
    push();
    translate(x, y);
    rotate($fx.rand() * TWO_PI);
    noStroke();

    let baseAlpha = col._getAlpha();
    for (let i = 0; i < 7; i++) {
        fill(col);
        ellipse(0, 0, size * ($fx.rand() * 0.2 + 0.9), size * ($fx.rand() * 0.6 + 0.7));
        size *= 0.85;
        col.setAlpha(baseAlpha * (1 - i / 7));
    }

    drawingContext.filter = 'blur(2px)';
    ellipse(0, 0, size, size);
    drawingContext.filter = 'none';

    drawInkBleed(x, y, col, size * 1.5);
    pop();
}

Dreamy Elements: Adding Ethereal Features

In this artwork, we add glowing orbs to evoke a dreamy, magical atmosphere. These orbs are softly colored and have glowing edges.

function createDreamyElements() {
    for (let i = 0; i < numDreamyElements; i++) {
        let x = $fx.rand() * canvas.w;
        let y = $fx.rand() * canvas.h;
        let size = $fx.rand() * (canvas.w / 5 - canvas.w / 10) + canvas.w / 10;
        let col = color($fx.rand() * 105 + 150, $fx.rand() * 105 + 150, $fx.rand() * 105 + 150, $fx.rand() * 50 + 50);
        let glow = $fx.rand() * 20 + 10;

        dreamyElements.push({ x, y, size, col, glow });
    }
}
function drawGlowingOrb(x, y, size, col, glow) {
    push();
    translate(x, y);
    
    drawingContext.shadowBlur = glow;
    drawingContext.shadowColor = col;
    
    noStroke();
    fill(col);
    ellipse(0, 0, size, size);
    
    drawingContext.shadowBlur = 0;
    pop();
}

Finishing Touches: Simulating Paper Texture

Lastly, we apply a subtle paper texture to give the digital artwork a real-world feel.

function createPaperTexture() {
    paperTexture = createGraphics(canvas.w, canvas.h);
    paperTexture.noiseDetail(2, 0.5);
    for (let x = 0; x < canvas.w; x++) {
        for (let y = 0; y < canvas.h; y++) {
            let alpha = paperTexture.noise(x * 0.02, y * 0.02) * 50 + 100;
            paperTexture.stroke(255, alpha);
            paperTexture.point(x, y);
        }
    }
}

By blending the paper texture on top of the artwork, we mimic the natural graininess of real watercolor paper.

function applyPaperTexture() {
    blendMode(OVERLAY);
    image(paperTexture, 0, 0, canvas.w, canvas.h);
    blendMode(BLEND);
}

Final Thoughts

This code allows us to generate watercolor-inspired generative art, blending algorithmic precision with the randomness and fluidity of nature. The soft transitions, ethereal elements, and textured background come together to create a piece that feels both digitally crafted and artistically expressive. for full code, click here.

Try it yourself: Load the code into your editor, experiment with different brushstrokes, colors, and dreamy elements, and see how your unique piece of generative art unfolds!

With flowing hues, gentle strokes, and boundless imagination by Frostbond Coders 🎨✨

stay ahead with our newsletter

receive news on exclusive drops, releases, product updates, and more

feedback