Creating Organic, Hand-Drawn Rectangles in Processing & p5.js

Trying to make some hand-drawn canvas with Processing/p5.js.

Are you tired of the sterile, perfect geometry that often comes with creative coding? Sometimes, adding a "human touch" can completely transform the mood of your work. In this post, I’ll explore how to create organic, hand-drawn-style rectangles, inspired by a brilliant technique shared by miku-san.


[Japanese version / 日本語版はこちら]


Applying this texture can give your digital art a classic, painterly feel—much like the primed undercoating of a physical masterpiece.

A Veil, a Printed Image 1891 Odilon Redon

A Veil, a Printed Image 1891 Odilon Redon French | The Metropolitan Museum of Art | Open Access

 

Technique: Building Rectangles from Loose Lines

The logic is simple: instead of using a single rect() function, we construct the shape using four independent lines. By introducing slight randomness to the path of each line, we achieve that unmistakable hand-drawn look.

Four sides of a rectangle

Functions like vertex() in Processing and p5.js allow us to draw custom lines.


And also filled rectangles.

 

Implementation: Processing (Java)

We can use bezierVertex() to add subtle, organic curves to each side of our rectangle. Here is an implementation that uses a "twist ratio" to control the distortion.

bezierVertex() | Reference / Processing.org

Here is the example code of filled rectangle.


/**
 * Hand-drawn canvas.
 * ref. https://twitter.com/BaroqueEngine/status/1580529748115353602
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * Processing 3.5.3
 * created 2022.10.17
 */

void setup(){
  size(640, 800);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();

  float margin = 120.0;
    
  background(0.0, 0.0, 90.0, 100.0);
  
  pushMatrix();
  translate(margin * 0.5, margin * 0.5);
  noStroke();
  fill(40.0, 15.0, 80.0, 100.0);
  handDrawnRect(width - margin, height - margin, 0.05);
  popMatrix();

}

/**
 * handDrawnRect : draw hand-drawn rectangle
 * _w, _h : rectangle width, height
 * _t     : twist ratio
 */
void handDrawnRect(float _w, float _h, float _t) {
  beginShape();
  twistedVertex(0.0, 0.0, _w, 0.0, _t); // upper
  twistedVertex(_w, 0.0, _w, _h, _t);   // right
  twistedVertex(_w, _h, 0.0, _h, _t);   // bottom
  twistedVertex(0.0, _h, 0.0, 0.0, _t); // left
  endShape();
}

/**
 * handDrawnRect : draw background
 * _sx, _sy : start point
 * _ex, _ey : end point
 * _t       : twist ratio
 */
void twistedVertex(float _sx, float _sy, float _ex, float _ey, float _t) {
  float dLen = dist(_sx, _sy, _ex, _ey) * _t;
  float secL = random(0.2, 0.4);
  float trdL = secL * 2.0;

  vertex(_sx, _sy);
  bezierVertex(
               lerp(_sx, _ex, secL) + random(-1.0, 1.0) * dLen,
               lerp(_sy, _ey, secL) + random(-1.0, 1.0) * dLen,
               lerp(_sx, _ex, trdL) + random(-1.0, 1.0) * dLen,
               lerp(_sy, _ey, trdL) + random(-1.0, 1.0) * dLen,
               _ex,
               _ey
               );
}

 

Implementation: p5.js (JavaScript)

For those working on the web, here is the same logic translated for p5.js.

bezierVertex() | reference | p5.js

The example code here is the same as the example code of Processing. I wrote this for your convenience.


/**
 * Hand-drawn canvas.
 * ref. https://twitter.com/BaroqueEngine/status/1580529748115353602
 * 
 * @author @deconbatch
 * @version 0.1
 * p5.js 1.1.3
 * created 2022.10.17
 */

function setup() {
  createCanvas(640, 800);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();
  noLoop();

  const margin = 120.0;
    
  background(0, 0, 90, 100);
  
  push();
  translate(margin * 0.5, margin * 0.5);
  noStroke();
  fill(40, 15, 80, 100);
  handDrawnRect(width - margin, height - margin, 0.03);
  pop();
}

/**
 * handDrawnRect : draw hand-drawn rectangle
 * _w, _h : rectangle width, height
 * _t     : twist ratio
 */
function handDrawnRect(_w, _h, _t) {
  beginShape();
  twistedVertex(0.0, 0.0, _w, 0.0, _t); // upper
  twistedVertex(_w, 0.0, _w, _h, _t);   // right
  twistedVertex(_w, _h, 0.0, _h, _t);   // bottom
  twistedVertex(0.0, _h, 0.0, 0.0, _t); // left
  endShape();
}

/**
 * handDrawnRect : draw background
 * _sx, _sy : start point
 * _ex, _ey : end point
 * _t       : twist ratio
 */
function twistedVertex(_sx, _sy, _ex, _ey, _t) {
  const dLen = dist(_sx, _sy, _ex, _ey) * _t;
  const secL = random(0.2, 0.4);
  const trdL = secL * 2.0;

  vertex(_sx, _sy);
  bezierVertex(
               lerp(_sx, _ex, secL) + random(-1.0, 1.0) * dLen,
               lerp(_sy, _ey, secL) + random(-1.0, 1.0) * dLen,
               lerp(_sx, _ex, trdL) + random(-1.0, 1.0) * dLen,
               lerp(_sy, _ey, trdL) + random(-1.0, 1.0) * dLen,
               _ex,
               _ey
              );
}


I've also included a quick look at how you might use the p5.scribble library for a different textured effect.


function setup() {
  createCanvas(640, 800);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();
  noLoop();

  const margin = 60.0;
  
  background(0, 0, 90, 100);

  const sb = new Scribble();
  const xc = [margin, width - margin,
              width - margin, margin]
  const yc = [margin, margin,
              height - margin, height - margin]
  stroke(40, 15, 80, 100);
  strokeWeight(6);
  sb.scribbleFilling(xc, yc, 5, -30);
}

Rectangle drawn by p5.scribble

 

The Importance of Anchor Points

A common pitfall when adding randomness is losing the shape's structural integrity. To ensure the four corners meet perfectly, I keep the start and end coordinates of each side fixed, only applying random() offsets to the internal Bezier control points.

If you randomize the vertices themselves, the shape begins to look like a tangled, single-stroke scribble rather than a deliberate rectangle.

Someone forced to draw a rectangle with a single stroke

You can achieve a more controlled look by drawing each line individually within its own beginShape() and endShape() block.

Rectangle with four hand-drawn lines.

Tips: The use of lerp() helps position the Bezier control points at 20% and 40% along the edge, ensuring the distortion feels natural rather than chaotic.

 

Embracing Imperfection

Now that we can generate hand-drawn style canvases, the possibilities for what to create on them are endless.

I said before 'It may bring us a way of expression like the undercoating of existing artwork'. My goal isn't to imitate the old masters, but to find a unique "digital-organic" expression. By seeking out these small imperfections, we can add a layer of flavor and soul to our code-driven artwork.

Example artwork
Example artwork

 

Next Post Previous Post
No Comment
Add Comment
comment url