Transform 2D
Moves, scales, and rotates its input in 2D space. Works on images (GPU sampling), shapes, and points (CPU affine).
Category: Utility Menu path: Utility > Transform2D
Ports
| Port | Type | Direction | Description |
|---|---|---|---|
in | imageRgba16f / shape / points | input | Input to transform. Output type matches input. |
out | imageRgba16f / shape / points | output | Transformed result |
Parameters
| Param | Type | Default | Description |
|---|---|---|---|
positionMode | enum | Pixels | How position is interpreted. See Position mode below. |
positionAnchor | enum | Center | Comp edge/corner the offset is measured from when Position Mode is Pinned. Labeled Pin To in the UI. Hidden in other modes. |
position | vec2 | (0, 0) | Translation. Units depend on Position Mode: pixels-from-comp-center (Pixels), normalized 0–1 from top-left (Normalized), or pixel offset from the chosen Pin To edge (Pinned). |
scale | vec2 | (1, 1) | Scale factor (1 = 100%). Linked by default. |
rotation | scalar | 0 | Rotation in degrees |
anchor | vec2 | (0.5, 0.5) | Pivot point in normalized coordinates. (0,0) = top-left, (0.5,0.5) = center, (1,1) = bottom-right. The frame of reference depends on anchorSpace. |
anchorSpace | enum | Content | What the anchor is normalized to. Content = the input's bounding box (recommended, composes correctly in chains). Comp = the composition. Texture inputs ignore this — anchor is always canvas-normalized for textures. |
Expose Channels
| Port | Type | Overrides |
|---|---|---|
position_in | vec2 | position |
scale_in | vec2 | scale |
rotation_in | scalar | rotation |
anchor_in | vec2 | anchor |
How It Works
For images: applies an inverse affine matrix via GPU sampling. The engine evaluates at a larger canvas size (overscan) to prevent content from being clipped at composition boundaries during transforms.
For shapes and points: applies a CPU affine transform to vertex positions (~1us). This is the standard way to position geometry before rasterization via DrawShape/DrawPoints.
Transform2D is the sole spatial transform node. The Output node handles compositing (opacity + blend mode) but has no spatial transform — use T2D for all positioning.
Position mode
positionMode decides what position means. There is one relative mode and two placement modes:
- Pixels (default) — relative.
positionis an offset in comp pixels from wherever the content currently sits:T(P) = pivot + (P − pivot) × S × R + position. A freshly added T2D is a true identity — it changes nothing until you touch it. Chained T2Ds concatenate: the second works on top of the first (offsets sum, rotation/scale pivot around the current content), exactly like nested parent transforms. For the everyday case — a centered shape or image under a single T2D — the numbers read the same as absolute coordinates, since the starting pivot is comp center. - Normalized — placement.
positionis[0, 1]from top-left to bottom-right, and says where the anchor lands:(0.5, 0.5)= anchor at comp center,(0.5, 0.78)= a typical lower-thirds Y. Designs survive resolution and aspect changes. Placement intentionally overrides upstream translation — that's what placing is. - Pinned — placement. The Pin To picker (
positionAnchor) selects a comp corner or edge (TopLeft, BottomCenter, etc.) andpositionis a pixel offset from that edge in standard screen coords (+X right, +Y down). For "32px from the bottom-right corner with the content's bottom-right corner pinned there", set Pin To = BottomRight,anchor=(1, 1), andposition=(-32, -32). (Distinct from the Anchor Point picker, which sets the rotation/scale pivot in content space.)
In the placement modes, position is where the anchor lands — so anchor=(0, 0.5) plus any position places the content's left-middle exactly there, no half-width math. In Pixels mode the anchor is purely a pivot: moving it never shifts the content, it only changes what scale and rotation revolve around.
Internally the engine resolves every mode to an absolute anchor landing point at the start of T2D evaluation (relative offsets fold in the current pivot), so the downstream pipeline — overscan, matrices, the gizmo — sees one canonical space.
Switch modes freely while iterating — values aren't auto-converted between modes, so the visual position may jump when you change positionMode. Re-key once you're settled on a mode.
Anchor space
anchorSpace controls where the pivot point lives:
- Content (default): Anchor is normalized within the input's AABB. Anchor
(0.5, 0.5)pivots around whatever the input's current bounding box center is. This composes correctly when chaining T2Ds — a second T2D added after the first scales/rotates around the current shape, not around comp center. Exception: geometry that declares a natural center — Polygon's radius center — pivots there instead of the AABB center, so odd-sided polygons don't wobble; other anchor values offset from that point by the content size. - Comp: Anchor is normalized within the composition. Anchor
(0.5, 0.5)pivots around comp center regardless of where the input sits. Useful for spinning or scaling content around a fixed point in comp space (e.g. rotate a whole scene around its top-left corner with anchor(0, 0)).
Rule of thumb: leave it on Content for 99% of cases. Flip to Comp when you specifically want a pivot anchored to the composition rather than the content.
Texture inputs resolve to Comp behavior regardless of the param value — the GPU texture render path doesn't branch on anchorSpace, it always pivots at anchor * compSize. The engine reports this as effectiveAnchorSpace: "Comp" in ResolvedNodeTransform so the gizmo math stays in sync.
Anchor Point picker & snapping
Two ways to place the anchor (rotation/scale pivot) precisely:
- Anchor Point picker — a 9-point grid in the Properties panel (under the Anchor row). Click a cell to snap the anchor to that corner / edge / centre of the content (Top-Left →
(0, 0), Centre →(0.5, 0.5), Bottom-Right →(1, 1)). The picker keeps the content visually in place — in Pixels mode the anchor is a pure pivot so nothing moves by construction (position only compensates when scale/rotation are active); in the placement modespositionauto-compensates. Either way, picking a preset moves the pivot, not the shape. - Anchor handle snapping — dragging the anchor crosshair in the viewport snaps to those same 9 content points within a few pixels; the crosshair turns yellow while latched. Hold Cmd/Ctrl to drag freely past a snap. (Honors the viewport snap toggle.)
Both move only the pivot — the content stays put, exactly like dragging the handle freely.
Positioning T2D relative to Draw nodes
Prefer to do all transforms before Draw nodes. The pattern Shape → T2D → T2D → … → DrawShape → Output composes cleanly: each T2D pivots around the current geometry AABB, the gizmo wraps the shape tightly, and chained T2Ds inherit each other's positioning the way nested parent transforms do.
T2D placed after a Draw node (DrawShape / DrawText / DrawPoints) operates on a canvas-sized texture. That's by design — rasterized output is sized for compositing uniformity. But it means the gizmo bounds wrap the full canvas rather than the visible drawn content, because the engine reports inputBounds as the texture's pixel dimensions. The transform itself works correctly; only the gizmo sizing is loose. Use this pattern when you specifically want to transform the rasterized canvas as a whole (canvas-level effects, compositing multiple drawn layers), not for positioning individual shapes.
(Tracked as a future engine improvement: have rasterizer nodes report their tight content AABB alongside the texture so downstream gizmos can wrap actual pixels.)
Usage Examples
Basic: Position a layer
Every image layer typically has an ImageSource -> Transform2D -> Output chain. T2D controls where the image appears in the composition.
Geometry pipeline
Rectangle -> Transform2D -> DrawShape -> Output. The T2D positions and scales the shape before it's rasterized.
Procedural animation
Connect Time -> Math (multiply by speed) -> T2D's rotation_in expose channel for continuous rotation. Or use Noise.scalar for organic drift on position.
Tips
- Scale is linked by default (X and Y move together). Click the link icon in Properties to unlink for independent axis scaling.
- Shelf tools (R, O, P, G, T) auto-create a geometry -> T2D -> DrawShape -> Output chain
- Click a layer in the viewport to auto-select its T2D node. If no T2D exists, one is auto-inserted.
- Shift constrains gizmo rotation to 15-degree increments
- Shift constrains gizmo scale to proportional