ASCII MagicDocs

Recipe format

A recipe is the look with no image. It is a delta against the defaults, serialised to a short string.

The shape

A recipe codetext
recipe:v1:eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0

Shared as a link instead, the same payload rides as ?r= with no prefix:

text
https://www.ascii-magic.com/app?r=eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0

A typical recipe is 120 to 280 characters, because it only carries what you changed.

Decoding one by hand

  1. Strip the recipe:v1: prefix. From a link, take the r parameter instead.
  2. Right-pad with = until the length is a multiple of 4. The encoder strips padding.
  3. Base64 decode.
  4. Parse as JSON.
bash
printf '%s' "$payload" | base64 -d | python3 -m json.tool

What comes back is the delta. Anything absent is at its default. It always carries v: 1.

What is inside

Two parts. Top-level keys are engine settings that differ from their defaults: renderMode, fontSize, charSet, coverage, the backdrop keys, brightness, contrast, invert, plus a per-engine block for whichever style you used, plus lights, colour, animation, dither and depth.

A pfx object carries post effects, and it is a per-property delta rather than a whole effect block. So a recipe that only raised film grain carries {"pfx":{"filmGrain":{"intensity":70}}} and nothing else.

Decodedjson
{
  "v": 1,
  "renderMode": "halftone",
  "fontSize": 14,
  "htAngle": 45,
  "pfx": { "filmGrain": { "enabled": true, "intensity": 70 } }
}

Applying one

Applying is a full reset then layer, not a patch. Every key goes back to its default first, then the recipe is layered on top. So two recipes applied in sequence do not compound.

If a recipe names a Pro style and your account cannot render it, the style falls back to the free default and the rest of the recipe still applies. You get a toast saying so rather than a silent downgrade.

Recipes inside PNGs

Every PNG the app exports carries its own recipe in a tEXt chunk with the keyword asciimagicRecipe. Other image tools ignore unrecognised chunks, so this is invisible everywhere else.

To read one by hand, find that chunk and take everything after the NUL separator. It is a normal recipe code. Dropping such a PNG back into the editor loads the look automatically.

Two gotchas

Non-Latin-1 characters break encoding. The encoder uses btoa, so a text overlay or a custom character set containing a character above U+00FF will throw. No shipped preset contains one.

The base64 is not URL-safe. Padding is stripped but + and / are not translated. In practice the payload is ASCII JSON so this is rare, but a custom character set containing ?, > or ~ can produce a +, which a query parser reads as a space. If a link misbehaves, paste the recipe:v1: code instead, which is parsed with a regex and is unaffected.

A worked decode

Take the payload from a shared link and read it, with no tooling beyond a shell:

Decode a recipebash
payload='eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0'
# right-pad to a multiple of 4, the encoder strips padding
pad=$(( (4 - ${#payload} % 4) % 4 ))
printf '%s%s' "$payload" "$(printf '=%.0s' $(seq 1 $pad))" \
  | base64 -d | python3 -m json.tool
What comes backjson
{
    "v": 1,
    "renderMode": "dither"
}

That is a complete recipe. It says the mode is dither and nothing else differs from the defaults, which is why it is so short. A recipe with a tuned look and a few effects runs 120 to 280 characters.

Why it is a delta

Encoding the full state would make every recipe roughly the same size, dominated by values nobody changed, and would freeze today's defaults into every link ever shared. A delta means a recipe carries intent rather than state: it says what you changed, and everything else follows whatever the engine currently considers normal.

The trade is that a recipe is interpreted against the defaults of the build that opens it. A shipped default changing is therefore a real compatibility event, and v exists so that a future format change can be detected rather than silently misread.

What a recipe does not carry

  • Your image. A recipe is the look with no source. Opening one loads a demo so there is something to see it on.
  • Anything at its default. If a key is absent, it is not "unset", it is normal.
  • Export settings. Format and scale are decisions at export time, not part of the look.
  • Your account or tier. Opening a recipe that uses a Pro style on a free account shows you the lock, not the render.

Through the MCP server

Two tools handle the format directly, which makes an agent able to read and write looks rather than only produce them:

ToolDirectionUse
decode_recipecode to settingsRead a shared look before changing it. Also the most reliable way to learn a mode's real parameter names, since a decode names every key it set.
encode_recipesettings to codeHand a look back to the user as a link they can open in the editor.
stylize_image with recipecode to a renderReproduce exactly. A recipe is never dressed, so this is the only call whose output is deterministic.

Where recipes turn up

PlaceForm
The Recipes button in the editorA recipe:v1: code to copy.
A share linkThe same payload as ?r=, with no prefix.
An exported PNGWritten into a tEXt chunk, so the file remembers how it was made.
A projectThe recipe plus the source it was made on.
MCPEither tool above, or the recipe argument on a render.

Last updated