p5.brush

repository·main·Indexed 20 days ago

https://github.com/acamposuribe/p5.brush

A library for p5.js that adds natural, organic drawing tools including pencils, charcoal, markers, watercolor fills, and hatch patterns. Designed for generative art and high-resolution printing, it supports both a p5-integrated build (requiring p5.js 2.x and WEBGL) and a standalone WebGL2 build. Features include custom brush creation, vector fields, rectangular clipping, and support for p5 instance mode.

Tokens
19.2K
Snippets
86
Records
102
Agent score
71%

What's inside p5.brush

  1. Use transforms and coordinate systems

    main

    The standalone build manages its own transform stack. The WebGL canvas origin is at the center of the canvas. To use top-left coordinates, you should shift the origin at the start of each frame.

    Transform Methods

    • brush.push(): Saves current transform matrix and brush state (stroke, fill, hatch).
    • brush.pop(): Restores the last saved state.
    • brush.translate(x, y)
    • brush.rotate(angle): Angle follows the current brush.angleMode().
    • brush.scale(x, y?): Omit y to scale uniformly.
    brush.push();
    brush.translate(-W / 2, -H / 2); // Shift origin to top-left
    // ... draw ...
    brush.pop();
    brush.push();
    brush.translate(-W / 2, -H / 2);
    brush.pop();
  2. Compare p5 build vs Standalone build

    main

    p5.brush.js is available in two distinct builds. Choose the one that matches your project requirements:

    p5 build

    • File: dist/p5.brush.js
    • Requirement: Requires p5.js 2.x and a WEBGL canvas.
    • Canvas Setup: Use standard p5 createCanvas(w, h, WEBGL).
    • Transforms: Uses p5's native push/pop, translate, rotate, and scale.
    • Angle Mode: Follows p5's angleMode().
    • Seeding: Uses p5's randomSeed() and noiseSeed().
    • Rendering: Frame flushing is automatic.
    • Clearing: Use p5's background() to clear the canvas.

    Standalone build

    • File: dist/brush.js
    • Requirement: No p5.js required; requires a WebGL2 compatible browser.
    • Canvas Setup: Use brush.createCanvas(w, h).
    • Transforms: Uses brush.push/pop, brush.translate, etc.
    • Angle Mode: Controlled via brush.angleMode(brush.DEGREES | brush.RADIANS).
    • Seeding: Uses brush.seed() and brush.noiseSeed().
    • Rendering: Requires calling brush.render() at the end of each frame.
    • Clearing: Use brush.clear(color?) to clear the canvas.
  3. How p5.brush drawing model works

    main

    p5.brush follows the standard p5.js drawing model: you first configure the drawing state, then execute the drawing commands.

    The Three Layers of p5.brush

    1. Style: Configure how marks look using state functions like brush.set(), brush.stroke(), brush.fill(), and brush.hatch(). Use brush.noStroke(), brush.noFill(), brush.noHatch(), or brush.noWash() to disable specific styles.
    2. Geometry: Draw the actual shapes using brush.line(), brush.rect(), brush.circle(), brush.arc(), brush.beginShape(), and brush.polygon().
    3. Advanced Control: Use vector fields (via brush.field()), custom brushes, clipping, and buffers for complex procedural effects.
  4. Create custom tip brushes

    main

    In the standalone build, custom tip functions receive a minimal 2D-canvas-backed surface. This surface is designed to be portable between the p5 and standalone builds.

    Coordinate Space

    The tip surface is 500×500 px internally, but the user-facing coordinate space is 100×100 units with the origin at the center. Draw within roughly ±50 units.

    Important Constraints

    • Rotation: _m.rotate() always uses radians and ignores brush.angleMode().
    • Colors: Use grayscale numbers (0–255) or CSS color strings. p5.Color objects are not supported.
    • Darkness: Darker fills result in higher opacity; lighter/white fills result in transparency.

    Available Methods on the tip surface (_m)

    • push() / pop()
    • translate(x, y)
    • scale(x, y?)
    • rotate(angle) (Always radians)
    • fill(value) / noFill()
    • stroke(value) / noStroke()
    • strokeWeight(value)
    • rect(x, y, w, h)
    • circle(x, y, diameter)
    • ellipse(x, y, w, h)
    • line(x1, y1, x2, y2)
    • beginShape() / vertex(x, y) / endShape(close?)
    • loadPixels() / updatePixels() / pixels
    brush.add('diamond', {
      type: 'custom',
      tip: (_m) => {
        _m.rotate(Math.PI / 4); // Radians
        _m.rect(-1.5, -1.5, 3, 3);
      },
    });
    brush.add('diamond', {
      type: 'custom',
      tip: (_m) => {
        _m.rotate(Math.PI / 4);
        _m.rect(-1.5, -1.5, 3, 3);
      },
    });
  5. Understand brush state management and p5 integration

    main

    p5.brush is designed to integrate seamlessly with p5.js state management.

    Automatic State Handling

    • No manual push/pop needed: You no longer need to call brush.push(), brush.pop(), brush.translate(), brush.rotate(), or brush.scale(). The library automatically hooks into p5's push() and pop(). Brush settings (stroke, fill, hatch) are saved and restored alongside p5's own state.
    • Transformation Inheritance: p5's translate(), rotate(), and scale() calls are automatically inherited by all brush strokes and fills.

    Angle Modes

    • Public brush APIs that accept angles inherit the current p5 angleMode() (defaulting to radians).
    • Exception: brush.addField(name, fn, { angleMode }) allows custom field generators to specify how returned angles should be interpreted before they are stored internally in degrees.
  6. Understand the host/runtime split in p5.brush

    main

    p5.brush is designed with a split between the host environment and the runtime. This architecture allows the core drawing logic to remain decoupled from specific rendering engines.

    • p5/: Contains the current integration specifically for the p5.js runtime.
    • standalone/: A reserved space for future hosts that do not depend on p5.js.

    When building or extending p5.brush, core drawing logic should avoid direct p5 assumptions and instead depend on services provided by the active adapter.

  7. Reuse geometry with brush.Plot and brush.Polygon

    main

    Drawing primitives return geometry objects (brush.Plot or brush.Polygon). You can store these to apply effects like hatching, massing, or filling at a later time or different position without re-calculating the path.

    • brush.Plot is returned by: arc(), spline(), and endShape().
    • brush.Polygon is returned by: polygon().
    • [plot, x, y] is returned by: circle().
    // 1. Draw and store geometry
    const frame = brush.polygon([[50,50],[350,50],[350,350],[50,350]]);
    
    // 2. Configure effects
    brush.hatch(8, Math.PI / 4);
    brush.mass("pastel", "#4b6cb7", { strength: 0.8 });
    
    // 3. Apply effects to the stored geometry
    frame.hatch();
    frame.mass();
  8. Install the p5.brush standalone build

    main

    The standalone build (dist/brush.js or dist/brush.esm.js) runs independently of p5.js and requires a WebGL2-capable browser.

    Script tag (UMD)

    Download dist/brush.js and include it in your HTML. This exposes a global brush object.

    <script src="path_to/brush.js"></script>

    ESM module via npm

    npm install p5.brush
    import * as brush from 'p5.brush/standalone';

    ESM module via local file

    import * as brush from './dist/brush.esm.js';
    import * as brush from 'p5.brush/standalone';
  9. Set up a canvas for drawing

    main

    You can initialize the drawing target using two methods. Both require a WebGL2-capable canvas.

    Using brush.createCanvas()

    The simplest method. It creates a <canvas> element, appends it to the DOM, and automatically loads it as the draw target.

    Options:

    • pixelDensity: Backing resolution multiplier (default 1). Use window.devicePixelRatio for HiDPI screens.
    • parent: CSS selector string or DOM element to append the canvas to.
    • id: id attribute for the created <canvas>.
    brush.createCanvas(800, 600, {
      parent: '#sketch-container',
      pixelDensity: window.devicePixelRatio,
    });

    Using brush.load()

    If you create your own HTMLCanvasElement or OffscreenCanvas, use brush.load(canvas) to register it. You can call this again at runtime to switch between multiple canvases.

    const canvas = document.createElement('canvas');
    canvas.width  = 800;
    canvas.height = 600;
    document.body.appendChild(canvas);
    
    brush.load(canvas);
    brush.createCanvas(800, 600, {
      parent: '#sketch-container',
      pixelDensity: window.devicePixelRatio,
    });
  10. Quick Start: Drawing with p5.brush

    main

    To start drawing immediately with the p5 build:

    1. Create a WEBGL canvas.
    2. Scale brushes if using built-in ones: brush.scaleBrushes(factor).
    3. Select a brush: brush.set(name, color, weight).
    4. Apply fills or hatches: brush.fill(color, alpha) or brush.hatch(density, orientation).
    5. Draw primitives: brush.line(), brush.rect(), brush.circle(), or brush.polygon().
    function setup() {
      createCanvas(700, 410, WEBGL);
      background("#f6f1e8");
      brush.scaleBrushes(3);
    
      brush.set("HB", "#2f2a26", 1.4);
      brush.line(-220, -80, 180, 40);
    
      brush.fill("#d7c3a3", 120);
      brush.noStroke();
      brush.circle(120, 20, 70);
    
      brush.set("rotring", "#1f4b99", 0.8);
      brush.noFill();
      brush.hatch(7, 35);
      brush.rect(-140, 40, 120, 90, "center");
    }
  11. Use p5.brush in p5 instance mode

    main

    If you are using p5 in instance mode (e.g., when using ES modules or mixing libraries), you must tell p5.brush which instance to use. Call brush.instance(p) inside your sketch function before setup and draw. This allows you to use brush.* methods directly without the p. prefix.

    const sketch = (p) => {
      // Tell p5.brush which p5 instance to use
      brush.instance(p);
    
      p.setup = () => {
        // Canvas must be in WEBGL mode — brush initializes automatically
        p.createCanvas(700, 410, p.WEBGL);
      };
    
      p.draw = () => {
        p.background(240);
        brush.set("HB", "#333", 1);
        brush.line(100, 100, 400, 300);
      };
    };
    
    new p5(sketch);