Creating Organic, Hand-Drawn Rectangles in 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.
意図的に雑な線をコードで描く方法を動画でまとめてみた。 pic.twitter.com/ExzdaVkdXg
— miku (@BaroqueEngine) October 13, 2022
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 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.
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);
}
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.
You can achieve a more controlled look by drawing each line individually within its own beginShape() and endShape() block.
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.









