Skip to content

Generators

This content is for v0.4. Switch to the latest version for up-to-date documentation.

A generator is a picture the engine draws from a description, with no media to decode: a color or gradient, a shape layer, a text, or a layout. This page covers the first three; Layout covers the fourth. After reading it you can make backgrounds, motion graphics and titles from JSON alone.

A Generator appears in three places:

  • as the generator of a source with "kind": "generated", which clips show like any other source;
  • as a composition’s generator node;
  • through a composition’s media node that names a generated source.

A generated picture is the size of the canvas. Positions are fractions, (0, 0) top-left to (1, 1) bottom-right, and lengths are fractions of the canvas’s shorter side, so the picture is the same at any render resolution.

A fill or a text never changes over time: a clip animates it with its transform, effects and masks. Shapes and layouts may animate any value, keyed in picture time: the source time the clip selects (see Animation).

{ "type": "fill", "paint": ... } covers the picture with a Paint:

Paint Fields
solid color
linear_gradient start, end, stops, spread (pad, repeat or reflect)
radial_gradient center, radius (a fraction of the shorter side), stops, spread
conic_gradient center, angle_degrees (where offset 0 lies, clockwise from straight up), stops

A gradient has 2 to 8 stops, each an offset in 0..1 and a color. Gradients interpolate premultiplied stops in linear light, so a gradient between two colors passes through their physical mix, and a stop fading to transparent does not darken its neighbour. Wherever a paint is read, a bare color is read as a solid paint.

This project shows the four paints one after another, a second each:

[
{
"id": "solid",
"kind": "generated",
"generator": {
"type": "fill",
"paint": { "type": "solid", "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } }
}
},
{
"id": "linear",
"kind": "generated",
"generator": {
"type": "fill",
"paint": {
"type": "linear_gradient",
"start": { "x": 0, "y": 0.5 },
"end": { "x": 1, "y": 0.5 },
"stops": [
{ "offset": 0, "color": { "red": 0.98, "green": 0.45, "blue": 0.1, "alpha": 1 } },
{ "offset": 1, "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } }
]
}
}
},
{
"id": "radial",
"kind": "generated",
"generator": {
"type": "fill",
"paint": {
"type": "radial_gradient",
"center": { "x": 0.5, "y": 0.5 },
"radius": 0.6,
"stops": [
{ "offset": 0, "color": { "red": 0.98, "green": 0.45, "blue": 0.1, "alpha": 1 } },
{ "offset": 1, "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } }
]
}
}
},
{
"id": "conic",
"kind": "generated",
"generator": {
"type": "fill",
"paint": {
"type": "conic_gradient",
"center": { "x": 0.5, "y": 0.5 },
"stops": [
{ "offset": 0, "color": { "red": 0.98, "green": 0.45, "blue": 0.1, "alpha": 1 } },
{ "offset": 0.5, "color": { "red": 0.17, "green": 0.36, "blue": 0.99, "alpha": 1 } },
{ "offset": 1, "color": { "red": 0.98, "green": 0.45, "blue": 0.1, "alpha": 1 } }
]
}
}
}
]

{ "type": "shapes", "items": [...] } draws an ordered content tree, as an After Effects or Lottie shape layer does. Each ShapeItem is one of:

  • Geometry: rectangle (with corner_radius), ellipse, polystar (a polygon or star), path (Bezier subpaths).
  • Paints: fill paints the inside of the geometry listed before it in its group; stroke outlines it, with width, cap, join, miter_limit and dash.
  • Modifiers: trim keeps part of the geometry before it, by length, from start to end, shifted by offset.
  • group: items drawn together through one transform and opacity.

The tree reads front first: a fill or stroke paints the geometry before it in the same group, nested groups included, and earlier paints draw on top of later ones.

Units follow the picture: a center is a position, a size is per axis (x a fraction of the width, y of the height, so { "x": 0.5, "y": 0.5 } is round only on a square canvas), and radii and stroke widths are fractions of the shorter side.

This star draws itself on with a keyframed trim while it turns:

{
"type": "shapes",
"items": [
{
"type": "group",
"items": [
{
"type": "polystar",
"kind": "star",
"center": { "mode": "constant", "value": { "x": 0.5, "y": 0.5 } },
"points": { "mode": "constant", "value": 5 },
"outer_radius": { "mode": "constant", "value": 0.35 },
"inner_radius": { "mode": "constant", "value": 0.15 },
"rotation_degrees": {
"mode": "keyframes",
"keyframes": [
{ "time": 0, "value": 0, "interpolation": { "type": "linear" } },
{ "time": 360000, "value": 36, "interpolation": { "type": "hold" } }
]
}
},
{
"type": "trim",
"start": { "mode": "constant", "value": 0 },
"end": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": 0,
"interpolation": { "type": "cubic_bezier", "x1": 0.65, "y1": 0, "x2": 0.35, "y2": 1 }
},
{ "time": 240000, "value": 1, "interpolation": { "type": "hold" } }
]
}
},
{
"type": "stroke",
"paint": {
"mode": "constant",
"value": { "type": "solid", "color": { "red": 1, "green": 0.75, "blue": 0.2, "alpha": 1 } }
},
"width": { "mode": "constant", "value": 0.012 },
"join": "round",
"cap": "round"
}
]
}
]
}

The shapes preset moves an ellipse along a keyframed centre:

{ "type": "text", "text": ..., "style": ... } draws text on a transparent picture, laid out and placed by its style as caption cues are. The TextStyle sets:

  • Font: font_family, font_size (a fraction of the canvas’s shorter side, 0.05 by default), font_weight (1 to 1000, matched as CSS matches font-weight), letter_spacing, line_height.
  • Placement: position and anchor (the point of the text placed at position; (0.5, 1), its bottom centre, by default), max_width (the wrapping width, a fraction of the canvas width), align.
  • Look: color (white by default), outline (a band around the glyphs, its width a fraction of the font size, at most 0.5), background (a box behind each line, its padding a fraction of the font size).

The text is a string, where \n starts a line.

A text picture is still. To move it, animate the clip that shows it: its visual opacity, position, scale and rotation_degrees take keyframes like any clip’s, and effects and masks apply as they do to any picture. Here the clip fades the title in while it rises into place:

{
"id": "text-clip",
"source_id": "headline",
"timeline_range": { "start": 0, "end": 360000 },
"source_range": { "start": 0, "end": 360000 },
"visual": {
"opacity": {
"mode": "keyframes",
"keyframes": [
{ "time": 0, "value": 0, "interpolation": { "type": "linear" } },
{ "time": 120000, "value": 1, "interpolation": { "type": "hold" } }
]
},
"position": {
"mode": "keyframes",
"keyframes": [
{
"time": 0,
"value": { "x": 0.5, "y": 0.55 },
"interpolation": { "type": "cubic_bezier", "x1": 0.2, "y1": 0, "x2": 0, "y2": 1 }
},
{ "time": 120000, "value": { "x": 0.5, "y": 0.5 }, "interpolation": { "type": "hold" } }
]
}
}
}

The source is { "type": "text", "text": "A title from JSON alone" } with font_family, a font_size of 0.09, and position and anchor at the centre.