Fig Tree Brushes

Source

Little ducklings were walking
then they fell
and they died.

— Rosie, 3

One of the unexpected discoveries from playing with Fig was how much fun I had designing, messing with (and breaking!) brushes. I thought I'd share how they work, how they don't work and why I will keep many of the bugs collected during my escapade in pixel land.

First, we have three types of brushes in Fig:

  1. normal → draw a (slightly noisy) rectangle
  2. drawable → draw the shape of the brush on a 9x9 matrix
  3. code → control your brush tixy.land style!

Regular

Regular brushes are just rectangles with occasional holes, Edam cheese style (or Gouda, depending on how fast you draw):

Drawn brushes

Draw the shape of your brush on a tiny 9x9 grid based editor. That's all!


No, I didn't mix x and y coords, you're sideways! (and Jonathan is a school.)

The original reason I added that little editor was to have a simple debugging/prototyping tool. What I particularly like about it:

I suspect this might not be as interesting to others as it has been to me. I still remember the feeling of excitement when I tried the brush editor in Paint Shop Pro.

The UX was fun to handle as well, partially because it's more complex than it seems on the surface. Some things to consider:

(This is also one of the reasons I enjoy typography: when it works, it becomes almost invisible.)

Some of the interactions I tested:

Code brushes

Feels like this entire project has been a 4-week long stream of consciousness. The code brush is an extension of the regular drawable brush but it's controlled via a tixy.land inspired JS snippet:

(i: int, x: int, y: int) → boolean

Let's break this down:

We run ixy for each pixel of the brush grid, for each iteration. Every time the function returns true we draw a pixel, otherwise we leave it empty.

Since i can be used as a poor man's replacement for time, we can use ixy to create animated brushes:

We can create looped/cyclical animations by using trigonometry, e.g. Math.sin.

I'm often impressed by the work of people who are capable of writing complex shader code. I like to think of this brush editor as a simple, me-sized shader toy. Having said that, let's see if we can hack or break it!

Prototyping new brushes using aye aye AI

tixy.land caps the number of code characters at 32. We don't have this limitation, but to create more complex behaviours we'll still need:

  1. a place to store variables, e.g. computed temporary state
  2. someone with a better knowledge of maths than yours truly
  3. someone who will bother to write the code

1. can be solved with IIFEs (immediately-invoked function expressions):

(() => { ... })()

// can be used as:

const ixy = (i, x, y) => (
	() => {
		// use i, x, y here
		let someComputedPropertyUsingXandY = ...
		return someOtherComputedProperty
	}
)()

2. can be offloaded to Claude!

I'm using some basic prompt vibing™ tricks here: chain of thought prompting, providing examples.

None of this song and dance is really needed, as providing two examples seemed to be enough to give me useful results: thread on Xitter.

Here's the prompt:

(see My default Coding Assistant System prompt for context)

implement the function animate (time, x, y) => void

the function:
- operates on a grid of 9x9 pixels
- returns either true or false for each pixel
- is implemented in Javascript

Example implementations:

- random spray/noise: `return Math.random < 1`
- diagonal line: `return x === y`
Now: implement the body of this function that will render a circle (9x9):

- wrap the result in an IIFE and pass these arguments to it: (i, x, y)

Result:

The woods are lovely dark and deep, so these are the bugs I'd like to keep:

Procreate is a wonderful piece of software. The faster, messier my brush strokes are, the better they look (thanks for their stabilisation and motion filtering algos). Every line is smooth, dynamic, drawn by a (hand-held) hand. This is useful and valuable as it allows more people to express themselves in ways that were previously impossible or very difficult. I used Procreate and MS Paint as a reference when looking for UX patterns.

(Yes, nothing can replace patience and discipline when it comes to drawing. I know that because I have low supplies of either.)

Fig doesn't even draw lines, which results in a bunch of small glitches. Every frame (however often it happens, usually between 30 and 60 fps) we sample the brush and drop it on the screen. This means that in order to draw strokes you'll need to draw more slowly. This means that if you draw really fast you can simulate brush spacing. And, finally, this also means that the brush dynamics will vary slightly between devices, just like it does when I switch from my pastel paper to vellum (or a crappy notebook.)

I tried to fix it and it looked too pretty. I'm keeping it.

That's all for today, see you tomorrow!

Want to receive my work as I publish it? Subscribe here.

a giant foot-shaped snail with a house on its back. the house is still in construction, with a big crane towering above it The image is a stylized black-and-white illustration. In the lower left corner, there is a small, cozy-looking house with smoke rising from its chimney. The smoke, however, does not dissipate into the air but instead forms a dark, looming cloud. Within the cloud, the silhouette of a large, menacing face is visible, with its eyes and nose peeking through the darkness. The creature, perhaps a cat, appears to be watching over the house ominously, creating a sense of foreboding or unease.