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 how position translates into a final pixel offset. The downstream pipeline (overscan computation, matrix construction, gizmo) only ever sees absolute pixels-from-comp-center; the mode is resolved at the start of T2D evaluation.
All three modes share one forward formula: T(P) = (P − pivot) × S × R + position. position is where the anchor lands in comp space. The mode just controls how the position value's coordinates are interpreted before being resolved to comp-space pixels:
- Pixels (default):
positionis in absolute pixels with comp center =(0, 0).(0, 0)= anchor at comp center,(960, 0)on a 1920px-wide comp = anchor at right edge. - Normalized:
positionis[0, 1]from top-left to bottom-right.(0.5, 0.5)= anchor at comp center,(0.5, 0.78)= anchor at a typical lower-thirds Y. Designs survive resolution and aspect changes —(0.5, 0.78)lands at the same relative spot whether the comp is 1920×1080 or 1080×1920. - Pinned: 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.)
Because position is where the anchor lands, you get clean placement with zero half-width math: anchor=(0, 0.5) plus any position places the content's left-middle at that position.
Switch modes freely while iterating — values aren't auto-converted between modes, so the visual position will 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. - 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 —positionauto-compensates — so 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