This repo is for generative art with PostScript output that can be printed to standard 3 1/8 inch (80 mm) thermal receipt paper. I personally use this Rongta point-of-sale (POS) printer to print my artwork. For example, here is the same banner above printed out:
I like to make my art at art trading card size (2.5 x 3.5 in), so this code is designed around that. This size is conveniently just a little bit narrower than the paper, so it pairs well.
I jokingly refer to my receipt printer as a "paper toaster" since a thermal printer works by heating the thermal paper, darkening it. No ink is used.
I recommend using Docker to run this script for a consistent environment. And also there are OS differences for the optional dependency of GhostScript, so using Docker avoids that headache.
This repo produces two images:
ptrgags/paper-toaster
- Main Python script.ptrgags/post-toast-ghost
- optional post-processing step for converting the PostScript files to other formats.
This container runs the Python code, generating an artwork as a PostScript (.ps) file.
It can be run through Docker with the following command:
docker container run -v /your/output/dir:/workdir --rm -it ptrgags/paper-toaster ARTWORK_ID <args>
Notes:
- For the full list of artworks and arguments, see the CLI Options Reference
- The bind mount, (
-v
flag) is required, as this is the directory that the postscript file will be written to - On Linux if you get permission errors, this is because the Docker container runs with an unprivileged user that does not match your UID. You can override this by adding the flag
--user $(id -u):$(id -g)
The name of this container is a cheeky rhyming abbreviation of "Post-process the output of Paper Toaster using GhostScript".
This takes /workdir/<artwork_id>.ps
(generated with paper-toaster
above)
and generates the following 3 files:
<artwork_id>.pdf
- (Requires GhostScript) a PDF version of the document. I find this to be the easiest to use for printing<artwork_id>_thumbnail.png
- (Requires GhostScript) a PNG version at 100 DPI. I use this for thumbnails in the README and on my website.<artwork_id>_web.png
- (Requires GhostScript) a PNG version at 200 DPI. I use this to make larger screenshots for my website.
docker container run -v /your/path/here:/workdir --rm -it ptrgags/post-toast-ghost <arwork_id>.ps
Notes:
- The bind mount, (
-v
flag) is required and must match the one used forpaper-toaster
From the root of the repo, here is how I build the two Docker images:
# Base image paper-toaster
docker image build -f ./docker/Dockerfile --target paper-toaster -t ptrgags/paper-toaster .
# Ghostscript image converter, post-toast-ghost
docker image build -f ./docker/Dockerfile --target post-toast-ghost -t ptrgags/post-toast-ghost .
I usually run both containers one after the other to iterate quickly. I made a quick Bash script to chain the commands together. It also uses an environment variable to configure hot-reloading of source code for development.
This will create a directory <repo_root>/workdir/
where the output will go.
# Development mode - Each time it runs, this hot-reloads the code
export PAPER_TOASTER_ENV=dev
./docker/run_container.sh ARTWORK_ID <args>
# Production mode - This runs the latest image you have locally
export PAPER_TOASTER_ENV=prod
./docker/run_container.sh ARTWORK_ID <args>
While I recomend using Docker, here are instructions for manual usage for reference.
- Python 3. I currently use Python 3.12. Only the standard library is used.
- (optional) GhostScript for PDF and PNG exports
To make integration with Docker easier, files are always written to the directory
specified by the environment WORK_DIR
. This also lets you set the directory
once instead of having to repeat it every command.
# bash
export WORK_DIR="/path/to/workdir"
in the src
directory, there's a papertoaster
module. Executing this
module will run the script and generate $WORK_DIR/<artwork_id>.ps
cd src
python -m papertoaster ARTWORK_ID <args>
For the full list of artworks and arguments, see the CLI Options Reference
If you want to produce PDF output or PNG images from the PostScript file, you can use GhostScript
Example in Linux:
# Generate a PDF
ps2pdf input.ps
# Generate a PNG image at 300 DPI
gs -o output.png -sDEVICE=png16m -r300 input.ps
In Windows, the usage is very similar, but on Windows the GhostScript command is
not gs
but gswin64c
As with many of my projects, I keep a log of what I worked on over time. This includes notes, experiments, links to resources I used along the way, etc.
You can find it here:
While the entry point is slightly different between Docker and running manually, the arguments are always the same format:
ARTWORK_ID <args>
where ARTWORK_ID is one of the subcommands, see Artworks and the arguments are a combination of Common options and the
There are a few options that can be used with any artwork to control the page size and layout. However, some artworks may be designed for specific configurations, such as one trading-card sized page
Option | Description |
---|---|
--num-cards N |
How many 2.5x3.5 inch trading cards tall will the receipt be. For example, I sometimes print receipts that are 3 cards tall. |
--page-width WIDTH_INCHES |
Override the width of the page to be any size in inches. |
--page-height HEIGHT_INCHES |
Override the height of the page to be any size in inches. |
--landscape |
Make the ouput document landscape rather than portrait. |
--seed SEED |
If provided, random.seed() will be called with this value to make the results reproducible. Otherwise, a seed will be chosen automatically and printed to the console. |
The sections below give a summary of the different artworks and explain the parameters. They are listed in reverse chronological order to feature newest artworks at the top.
Examples marked with 🧪 indicate artistic experiments by messing with the parameters in ways I didn't originally intend.
Examples with a Seed column can be reproduced exactly using the --seed N
global option to set the random seed.
This receipt creates patterns inspired by the Project Euler problem #208: Robot Walks. This script does not solve the puzzle, but it does visualize some of the paths. This script produces two types of paths:
- Looping paths with 5-fold symmetry (most of the time)
- Infinitely repeating paths (sometimes)
There are more possibilities, but these ones have more symmetry.
Parameters:
Parameter | Description |
---|---|
-n/--num-steps N |
How many steps the robot will take. The path will be repeated 5 times as this usually causes the robot to loop back to the start. |
-l/--loop |
If set, the script will keep trying until it finds a path that loops. |
-d/--dots |
If set, draw a dot at each position on the robot's path. |
-p/--path-type {arc, polyline} |
Select between curved arcs and straight polylines. |
-r/--render-type {stroke,fill,eofill} |
Select between stroked, filled, or even-odd filled paths. |
Examples:
This receipt is more of a utility than an artwork. It simply draws a to do list template with checkboxes that you can print out and write on. It's also nice for a shopping list!
Parameters:
Parameter | Description |
---|---|
-s/--row-size |
Size of a row in inches (including padding). Defaults to 1/4 inch |
Examples:
Example | Arguments | Description |
---|---|---|
todo |
Default settings |
This tiling was an artistic way of visualizing flow in a grid.
First, a grid of cells is created. Then, for each edge between cells, a direction across the edge is chosen randomly. Then, for each cell, based on which directions the flow in/out of the tile is going, one of several tiles is chosen.
Randomly placing the tiles would be an easier implementation, but this method ensures that if you follow any path from a source (closed circle) to a sink (open circle), the flow direction will look consistent.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
Examples:
Example | Arguments | Description |
---|---|---|
edge_directions |
Simple example | |
edge_directions -s 0.125 |
Set the square size to 1/8 of an inch |
A colorized version of the older braids
tiling. Obviously, this looks better
on a screen or a color printer rather than the grayscale of a receipt printer.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
-w/--stroke-width WIDTH_POINTS |
The width of a single strand in points (1/72 of an inch) |
-c/--swap-chance CHANCE |
The chance of swapping a braid strand with its neighbor as a number between 0.0 and 1.0 |
-i/--invert-colors |
Invert the colors so the braids stand out |
-g/--groups GROUPS_CSV |
CSV of positive integers the determines groups of strands to weave. E.g. 3,4 means weave the first 3 strands, and the next 4 strands separately. If not specified, all strands are woven. If there are not enough groups for the number of strands, the list of groups will be repeated |
Examples:
I've always liked the look of isometric grids in video games and art, so this artwork makes a mountain-like scene on an isometric grid.
The scene is an illusion. I'm just overlaying a bunch of solid-color parallelograms from back to front to make the scene. This is somewhat inspired by 2D tile-based iso rendering, but much simpler to implement. Of course, it only works well for this carefully curated scene.
To make the mountain shape, I randomly generate heights in each cell such that a cell is equal or taller than any of its neighbors closer to the "camera".
Parameters:
This script generates a random isometric scene each time. There are no parameters.
Examples:
Example | Arguments |
---|---|
iso_grid |
|
iso_grid |
A pattern of strands of rope being woven together in a braid pattern. The crossings are randomly chosen.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
-i/--invert-colors |
If true, invert the color scheme |
Examples:
Example | Arguments | Description |
---|---|---|
braids |
Default settings | |
braids -i |
Inverted colors |
This script is a simple implementation of an elementary cellular automaton.
There are a few differences from the description on Wolfram Mathworld:
- Instead of starting from a single seed point, the first row is randomly generated
- When examining neighbors, this script wraps around the boundary
- The pattern is generated from bottom to top given PostScript's coordinate system
Parameters:
Parameter | Description |
---|---|
RULE |
The rule number from 0 to 255 |
Examples:
Example | Arguments | Description |
---|---|---|
elementary_ca 30 |
Rule 30. This pattern is chaotic and is like a simplified model for the patterns on sea snail shells (see Olivia porphria) | |
elementary_ca 112 |
Rule 112 | |
elementary_ca 161 |
Rule 161 |
Barcodes are ubiquitous, but I didn't know much about how they encode data. So I tried implementing Code 128 barcodes since these can store not just numbers but ASCII characters too.
This receipt works best with the --landscape
global option, and for longer
strings of text, increasing the --num-cards
is necessary.
Also note that barcodes are easier to scan on paper than on a screen, and it works best when the barcode is flat. I'm able to use my Android phone's camera to scan it, but sometimes third-party apps are necessary.
Parameters:
Parameter | Description |
---|---|
TEXT |
An ASCII string to turn into a barcode |
Examples:
Example | Arguments | Description |
---|---|---|
barcode128 --landscape "Paper Toaster" |
Simple example that encodes the text "Paper Toaster" |
Turtle graphics patterns based on the Bridges math art paper "Let the Numbers Do the Walking: Generating Turtle Dances on the Plane from Integer Sequences"
This implementation allows using different sequences of integers:
- The natural numbers
- The square numbers
- The triangle numbers
- The fibonnaci sequence
Parameters:
Parameter | Description |
---|---|
SEQUENCE |
One of natural , square , triangle , fibonacci |
A |
The first integer modulus |
B |
The second integer modulus |
-d/--divisions |
A divisor of 360 degrees used to determine the minimum turn angle. |
-f/--fill |
If set, the path will be filled with odd/even fill |
Examples:
Sometimes I want a large number of dice rolls (e.g. for drawing dice-generated art on paper), but rolling that many dice would be inconvenient (because it would be tedious, noisy, or I don't have dice on hand).
Certainly it's easy to find apps to do dice rolls, but sometimes I want to get away from my phone. So it would be handy to roll many dice up front, then cross off entries from the list as I use them.
This script simply generates a bunch of dice rolls of the form (NdX + M) in standard dice notation and prints them out in a table.
Parameters:
Parameter | Description |
---|---|
N |
(required) How many dice are summed for each roll. For example, 2 would mean roll two dice and add their results |
SIDES |
(required) Each die rolled will have this many sides. |
-m/--modifier MODIFIER |
A constant value to add to the result. |
Examples:
Example | Arguments | Description |
---|---|---|
quiet_dice 1 6 |
Simple example | |
quiet_dice 2 10 |
Example where each roll adds two 10-sided dice. | |
quiet_dice 1 20 -m 2 |
Example with a modifier |
This artwork is based on Hitomezashi embroidery patterns, inspired by this Numberphile video. Hitomezashi is a form of Sashiko embroidery, decorative running stitch patterns that help reinforce clothing.
This script lets you specify two integers to set the bit pattern for the rows and columns (see the video to see how this works). Large numbers with a variety of 1s and 0s work best.
Parameters:
Parameter | Description |
---|---|
ROW_BITS |
An integer representing the bits for the rows (least significant bit first). It can be a decimal number or a binary number prefixed with 0b . Rows are numbered from bottom to top of the document. |
COL_BITS |
An integer representing the bits for the columns (least significant bit first). It can be a decimal number or a binary number prefixed with 0b . Columns are numbered from left to right. |
-o/--odd-even |
If true, the pattern is filled with an odd/even coloring. |
-s/--square-size |
Size of each square in inches. Defaults to a quarter of an inch |
Examples:
Hex grid with some optional gaps between hexagons. It also can render dots instead of lines.
This artwork only supports a single card-length receipt.
Parameters:
Parameter | Description |
---|---|
-s/--side-length SIDE_LENGTH |
The side length of each hexagon in inches. Defaults to 1/4 inch |
-e/--expand-factor EXPAND_FACTOR |
How much to expand/contract the grid for artistic effect |
-d/--dots |
Draw the hexagon vertices, but do not connect them. This can be used to make a hex grid |
Examples:
This was a warm-up exercise, this makes a card with graph paper.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
Examples:
Example | Arguments | Description |
---|---|---|
grid |
Default settings (1/4 inch grid when printed) | |
grid -s 0.125 |
finer 1/8 inch grid |