Rough.js

Create graphics with a hand-drawn, sketchy, appearance

Home View on GitHub Twitter Sponsor this project
Star Fork

Release v4.0.0 (January 13th, 2020)

This is the first release of Rough.js in about 10 months.

It may not seem like a big release, but it is significant in a couple of ways. Firstly, this is the first time since the library's inception, the core rendering algorithms have been modified. Secondly, for a change, I have removed something from the library in addition to adding some functionality.

Random number seeding

Many users had asked for this feature. Rough.js computes shapes by generating a lot of random offsets. As a result, every render creates a unique hand-drawn looking shape. There are cases, however, where this is not the desired behavior and the application may want to persist the shape even at different sizes.

The API now allows the user to pass in a seed value which is used to generate a predictable sequence of random numbers. Providing the seed is optional.

const seed = rough.newSeed();
rc.rectangle(10, 10, 200, 200, { fill: 'red', seed });
rc.rectangle(240, 10, 250, 250, { fill: 'blue', seed });

Seeded square sample

In this image above, notice the two squares have the same outlines, even though they are rendered separately with different sizes and fill colors.

New Hachure Algorithm

Rough.js can a fill a shape with hachure lines. These lines are also used in creating other fill styles like cross-hatch, zigzag, etc. The old algorithm was adapted from Handy's processing code with minor changes. The code was a bit convoluted, and it did not handle concave shapes very well at some hachure angles, creating cases like these:

Concave polygon

The new algorithm is similar in nature and is based on the Scan-line rasterization algorithm. To create hachure lines, the scan lines are incremented in steps based on the specified hachureGap. This algorithm, however, is designed for horizontal scan-lines.

To achieve various hachure angles, I first rotate the shape itself by the hachureAngle. Then I calculate the horizontal scan-lines for the rotated shape. The computed lines are then rotated back by -hachureAngle.

Concave polygon process

The new algorithm resulted in half the lines of code, and performs about 20% faster.

Dynamic roughness estimation

Part of creating the sketchy look is to create imperfect artifacts that are generated by randomizing certain parameters. Some of these parameters get exagerated based on the shape and the random values that are generated. So a shape with a roughness value of 1 looked more like a roughness value of 3 at a different size. This was particularly noticable when drawing ellipses where the relationship is quadartic. In the image below, notice the circles have the same shape, but the outer circles look more rough.

Concentric circles

The algorithm now auto-adjusts itself based on the size of the shape. Following is the same set of circles generated with auto-adjust

Concentric circles

No-stroke fills

Another feature, often requested was to add ability to draw shapes without the outline. This was achieved by setting the stroke color to transparent. This does the job, but the underlying shape still has the extra instruction to draw the outline. If the sroke is set to none, then the outline is not computed at all.

rc.rectangle(240, 10, 250, 250, { stroke: 'none', fill: 'blue' });

no-stroke square

Why 4.0 and not 3.x?

The main reason for upgrading to a major version was the it is strictly not backwards compatible and the location and names of the bundled resources have changed.

async and web-worker version of rough.js is not built anymore because it can be created by the app using roughjs generator object.

Also, the sunburst style of filling shapes has been removed from the API. It was not popular and had several corner cases where it did not work.