Flippin' Dots

Post Metadata

The Starbucks Reserve Roastery near me abruptly closed. This news was a big bummer, but not necessarily because of the coffee quality. They had this huge split flap sign whose pleasant waterfall patter washed over the cafe when its message changed, and served as lovely background noise when you were bringing your tourist family around to shop for souvenirs or nibbling on a croissant sandwich and a reading group paper.

Large black split-flap sign with white lettering, with the words "Starbucks Reserve Roastery & Tasting Room" printed above the mechanism. The board is mounted near the ceiling on a wood partition. The text displayed by the split flap reads (backslashes denote line breaks): Today Roasting \ Gravitas Blend vintage 2018 \ Notes: Gravitas returns to the roastery for the fourth year in a row \ in the spirit of the original blend we reminded ourselves of the \ experience we wanted our partners and customers to have. \ This bold cup offers notes of brandied cherry and bramble berry."

Image source: https://commons.wikimedia.org/wiki/File:Starbucks_Reserve_Roastery_interior_21.jpg

I’m not the only one who enjoys these kinds of novel, physical, analog displays. Companies like Oat Foundry and Breakfast Studio are well-known for their custom installations that push the boundaries of what can be done artistically with these technologies. Hackers around the world have refurbished, upgraded, and hacked on existing displays, as well as created prototypes of novel display technology. Even Adobe has a research department working on Primrose, a flexible smart-window material type of display with an eye towards applications in everything from fashion to building-scale architecture.

Left: A person stands in front of a blue-and-silver flip-dot display with their hand splayed. The display reflects the shape of the person's head and hand; outside of their silhouette, the dots are flipped to the variegated blue side, and inside the silhouette, the dots are flipped to the silver side. Middle: A 3/4 view of a "picture flap" display, where each split-flap unit has a printed photo on it. The unit is mid-flip, with most flaps partway fallen down. The previous picture is a grid of boxes with the text "MONOGRAM THE PERFECT GIFT" and the next picture is a side-by-side of three watches. Right: A side-by-side of a woman wearing a dress. The dress is sleeveless and has a shift cut. It is made of a material that looks like gray scales. In both images, the scales have darkened to create distinct patterns. On the left, a sequence of waves, with some of the scales serving as midtone "aliasing" pixels. On the right, a floral pattern with a belt appears.
Left to right: Flip dot mirror by Breakfast, Picture flaps by Oat Foundry, Primrose dress by Adobe.

But what exactly do we enjoy about these displays? Is it pure nostalgia, a yearning for a simpler time? A gentle visual and auditory experience in the age of blaring digital screens and speakers? The idea that even after decades of power loss, they will continue to shout on their last known message? Personally, I enjoy the constrained nature of these displays. It’s often said that really interesting art is born from working against constraints. Just take a look at colour cycling (I think these are some of the coolest demos on the internet):

A pixel art image of a three-tier waterfall between two cliff faces, nestled behind elegant buildings constructed into the sides of the rock, and a curved bridge crossing the chasm. Green jungle foliage flanks the scene. The water is animated as falling down in cascades.

The idea is born out of indexed graphics: rather than recording the full colour data per pixel, which uses a ton of memory, we could save all the colours we’re going to use as a palette and only record an index into it. But if you wanted to update your image (say for animating), it’d still be really costly to load all new indices. What if instead you just swapped out the palette you were indexing from? So to animate a sunrise, you might start with a palette of dark blues, then move through oranges and reds, and then reach sky blue to get daytime. My point is, these kinds of solutions for constrained problems create pieces with a distinct character, and we embrace that look for its charm. We love the medium for what it is!

So what about, say, designing flip dot content? What does it look like? Well, for starters, it’s not like you can just use a file that was designed for the average computer monitor with millions of dots – at 8.9mm diameter per dot, the flipdot equivalent of a 4k-resolution screen would take up at least 1345 inches x 757 inches. That’s 37 x 21 yards, which is like, two semitrucks long by a bowling lane tall, for you Americans out there. Also, what might be the bigger problem is that the colour palettes tend to be extremely limited. Trying to display a full-colour video is going to be really difficult if you’re only choosing from one of two colours (in the case of flip dots) or need to spend a long and variable amount of time updating the pixel (in the case of split flaps). So if you have anything in mind other than Bad Apple, you’re probably pretty stuck.

A short segment of the video "Bad Apple" rendered on 24x32 simulated blue-and-yellow flip-dot screen. The animation shows the camera zooming out from a figure dancing. The figure turns to the side profile and throws an apple. The camera follows the apple falling, which is caught by a witch on a broomstick, and inverts the colours in the scene. The witch flies towards a castle.
(But you can make Bad Apple!)

Summarizing the state of affairs:

  • These displays are ultra low-resolution
  • They have a very limited colour palette
  • There are restrictions on how fast and where pixels can update

But there are also unique properties that we can take advantage of:

  • They make pleasing noises
  • Their dots don’t have to be arranged rectilinearly
  • There is often inherent motion associated with the update that we can use to our perceptual advantage.
  • Dots can have sub-pixel patterns

So then the question is, what kind of system can I build that lets me translate my content for different types of hardware and find that special character? I want to be aware of the constraints like having a low resolution, but I also want to employ effects that might enhance my content. In fact, these effects can help suggest visual content that transcends the lowered resolution; for example, if the wind is blowing a leaf across the screen, it could make sense to have the flip dots move in a path to emphasize the kinetic energy. That would be pretty difficult to do with a digital screen, and also works without a fine level of detail!

Here’s the first attempt at the idea, which I’ll go over at a high level. The central tenet here is getting control over what dots are changing state and when. For now, I’m going to focus on flip dots, as they are the simplest medium we’re using. Consider these two frames that we’re transitioning between:

A completely white image. A black rectangle on a white background.
Green background used for visibility purposes.

We have a bunch of options for how to transition between them. Do we just flip from one frame to the next?

Animation on a flip-dot display showing, in one frame, a yellow background, and in the next frame, the blue rectangle appearing when the dots flip.

Perhaps we want to suggest that the content is being wiped away?

Animation on a flip-dot display showing a yellow background, and then a blue rectangle grows from the bottom-up two rows at a time until the full blue rectangle appears.

Or we want to suggest that it is emerging from some kind of uncertain noise?

Animation on a flip-dot display showing a yellow background, then in the next three frames, noise begins to appear and grow inside the blue rectangle until the rectangle is solid blue.

Maybe we want to sync up that reveal to some kind of rhythm?

Animation on a flip-dot display showing an animation similar to the one directly above, but the first and third frame last twice as long as the second and fourth, making an irregular cadence.

In some ways, the input and output frames are a specification (they cannot change!), but we can play with the way that the dots change to transition between them – and we can think of that as being governed by a schedule. So, our prototype system uses a simple declarative interface to express the schedules of each frame and the pieces within them.

Let’s break down one example. Take this simple example of a golf ball getting hit and flying away. Here is what the program looks like:

timing: [1,1,1,1,1,1,1,1] filepath: /animations/golf-collide${i}.png objects: [#000000 golfstick] [#5fcde4 golfer] [#5b6ee1 ball] golfstick 0 ->* instantaneous ->* golfstick 7 golfer 0 ->* instantaneous ->* golfer 7 ball 4 ->* instantaneous ->* ball 7

And here is the result, rendered in my three.js flipdot simulator:

An animation on a flip-dot display of a small figure holding a golf club up and back in the air, swinging, and hitting a golf ball, and arcing back in the follow-through pose. The golf ball then flies away for two frames.

The first line declares how many frames are in the input and how “long”, generally speaking, to spend on each frame. We can stagger the updates by making some frames take longer. The second line specifies the file path to our input, which is a set of keyframes. I’ve drawn the keyframes for this example myself, and they show a little stick figure hitting a golf ball (no comments on their form please, they are still learning how to golf):

A series of 8 side-by-side pixel art images showing the keyframes in the animation from above, but there is an additional piece of terrain to the left of the golfer. Various parts are colour-coded: the mountain slope terrain is pink, the golfer body is light blue, the golf club is black, and the golf ball is dark blue.

(This input shows a golfer hitting a golf ball. The golfer’s body is light blue, the golf club is black, the golf ball is dark blue, and the mountainous slope backdrop is pink.)

The third line tells us what Objects are in the input, which we use colour to delineate. By Object, I mean a collection of dots and their states (e.g., the golf ball Object is a 2x2 grid of all coloured dots). You can think of a program as constructing a graph of objects and how they change over time, something like this, where circles represent objects and squares represent transitions:

A simple flowchart-style graph. There are three linear graphs going up and down. On the left side are labels "frame 1" through "frame 4", with frame 1 at the top and frame 4 at the bottom. These represent time. Each graph has its own label (from left to right, "mountain", "golfer", and "golf ball"). Graph 1, "mountain", has one oval at "frame 1" and one at "frame 4". An edge from the top oval connects to a rectangle in the middle, which connects to the bottom oval. In the second graph, "golfer", there is one oval at each frame, and an edge connects the oval at frame 1 to a rectangle which is connected to the oval at frame 2, and so on to frame 4. In the last graph, "golf ball", there is one oval at frame 3 and one oval at frame 4, and one rectangle connected by edges between them.

(N.B. the structure of this diagram does not perfectly match the current example)

So the last three lines in the program are compiled into individual graphs. A valid transition graph, such as ball 4 ->* instantaneous ->* ball 7, is given by Selector (-> Transition -> Selector)*. A selector is an Object (taken from the named Objects line) and a frame id (e.g. ball 4). So, if I simply declare ball 4, then the golf ball from frame 4 will appear at frame 4. Any objects not given on these lines do not appear in the animation. Note how the mountain (which was indicated in pink in the input) has disappeared in the final animation!

(N.B. You may be wondering why the example uses ->* instead of ->. ->* is just syntactic sugar that expands the declaration over all frames. So, A 1 ->* t ->* A 3 would expand to A 1 -> t -> A 2 -> t -> A 3.)

Now let’s focus on transitions. The transition is used to compute the object for each new subframe, describing how the object transforms from its state at frame f1 to its state at f2. For example, instantaneous just means “go from f1 to f2 without anything in between”. f1 and f2 need not be sequential; if they are not, the transition will generate objects for all intervening frames and subframes. Thus, the schedule (i.e. when each dot flips) is dictated by the transitions we choose, and we can make a bunch of different schedules by switching out the transitions.

So instead, we could interpolate the path of the golf ball to make it move more smoothly:

golfstick 0 ->* instantaneous ->* golfstick 7 golfer 0 ->* instantaneous ->* golfer 7 ball 4 ->* move ->* ball 7

and we get this:

The same golf animation as above, but instead of just two frames where the golf ball flies away, there are now about 8 frames showing more detail of the golf ball's path.

Or record parts of its path as it moves like so:

golfstick 0 ->* instantaneous ->* golfstick 7 golfer 0 ->* instantaneous ->* golfer 7 ball 4 ->* move ->* ball 7 ball 4 ->* path ->* ball 7

Similar to the animation directly above, but now as the golf ball flies, the last few frames of its previous path are frozen, so that more of the path is being visualized at once.

And that’s the basics of it! Of course, there are plenty of other things you could want to play with that we haven’t covered here. For example, we haven’t really touched on how you might want to manipulate the setup of your display for different types of content –- like engineering the colour layout to allow you to display multicolour videos. (Something like this?)

A short scene from the 2015 live-action Cinderella, where Cinderella is dancing at the ball, rendered on a 270x155 resolution flip-dot display. The colour setup is a checkerboard: half the dots are white on the back and red on the front, and the other half are black on the back and green on the front. The animation is moving very slowly.

Source clip taken from Cinderella (2015). (The costuming is so great…)

Right now, we’re working on adapting this system for new types of hardware, including Primrose, and show how you can generally adapt it, even when you want it to work on Pixel Track, refreshable braille displays, or even marching bands!

Acknowledgements. Thanks to everyone who helped with this work and gave inspiration, especially my mentors at Adobe, Alec Jacobson and TJ Rhodes, and my advisors at UW and Brown, Zachary Tatlock and Adriana Schulz!