Transforms

Transforms modify sprites at render time without changing the source definition. Pixelsrc supports two transform systems designed for different use cases.

When to Use Which

Use CaseSystemExample
Keyframe animationsCSS Transforms"transform": "rotate(90deg) scale(2)"
Derived spritesOp-style Transforms"transform": [{"op": "mirror-h"}]
Geometric only (rotate, flip, scale)EitherContext determines choice
Effects (dither, outline, shadow)Op-style only"transform": [{"op": "sel-out"}]

Rule of thumb:

  • In an animation keyframe → use CSS transform strings
  • In a sprite with source → use op-style transform arrays

CSS Transforms (Keyframe Animations)

CSS transforms use familiar web syntax and are primarily used within keyframe animations. They're designed for pixel art with specific considerations for crisp rendering.

Syntax

{
  "type": "animation",
  "name": "example",
  "keyframes": {
    "0%": { "sprite": "star", "transform": "translate(0, 0) rotate(0deg)" },
    "100%": { "sprite": "star", "transform": "translate(10, -5) rotate(90deg)" }
  },
  "duration": "1s"
}

Multiple transforms can be combined in a single string, separated by spaces.

Transform Functions

FunctionSyntaxDescription
translatetranslate(x, y)Move by x, y pixels
rotaterotate(deg)Rotate clockwise
scalescale(n) or scale(x, y)Scale uniformly or non-uniformly
flipflip(x) or flip(y)Horizontal or vertical flip

translate(x, y)

Translate Transform

Position offset using translate(x, y), translateX(x), translateY(y).

{"type": "palette", "name": "arrow_pal", "colors": {"{_}": "#00000000", "{a}": "#FF0000"}}
{"type": "sprite", "name": "arrow_right", "palette": "arrow_pal", "size": [2, 2], "regions": {"a": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "arrow_base", "palette": "arrow_pal", "size": [2, 2], "regions": {"a": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "slide_right", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translate(0, 0)"}, "100%": {"sprite": "arrow_right", "transform": "translate(8px, 0)"}}}
{"type": "animation", "name": "slide_down", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translateY(0)"}, "100%": {"sprite": "arrow_base", "transform": "translateY(4px)"}}}
{"type": "animation", "name": "slide_diagonal", "duration": "500ms", "keyframes": {"0%": {"sprite": "arrow_base", "transform": "translate(0, 0)"}, "50%": {"sprite": "arrow_base", "transform": "translate(4px, 4px)"}, "100%": {"sprite": "arrow_base", "transform": "translate(8px, 8px)"}}}

Move the sprite by the specified pixel offset.

"transform": "translate(10, 5)"      // Move right 10, down 5
"transform": "translate(-5, 0)"      // Move left 5
"transform": "translate(10px, 5px)"  // Optional px suffix
ParameterTypeDescription
xintegerHorizontal offset (positive = right)
yintegerVertical offset (positive = down, optional, defaults to 0)

rotate(deg)

Rotate Transform

Rotation using rotate(deg) - pixel art supports 90, 180, 270 degrees.

{"type": "palette", "name": "shape_pal", "colors": {"{_}": "#00000000", "{s}": "#00FF00"}}
{"type": "sprite", "name": "L_shape", "palette": "shape_pal", "size": [2, 2], "regions": {"s": {"points": [[0, 0], [0, 1], [1, 1]]}}}
{"type": "sprite", "name": "arrow_up", "palette": "shape_pal", "size": [2, 2], "regions": {"s": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "rotate_90", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(90deg)"}}}
{"type": "animation", "name": "rotate_180", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(180deg)"}}}
{"type": "animation", "name": "rotate_270", "duration": "500ms", "keyframes": {"0%": {"sprite": "L_shape", "transform": "rotate(0deg)"}, "100%": {"sprite": "L_shape", "transform": "rotate(270deg)"}}}
{"type": "animation", "name": "spin_full", "duration": "1s", "keyframes": {"0%": {"sprite": "arrow_up", "transform": "rotate(0deg)"}, "25%": {"sprite": "arrow_up", "transform": "rotate(90deg)"}, "50%": {"sprite": "arrow_up", "transform": "rotate(180deg)"}, "75%": {"sprite": "arrow_up", "transform": "rotate(270deg)"}, "100%": {"sprite": "arrow_up", "transform": "rotate(360deg)"}}}

Rotate the sprite clockwise by the specified angle.

"transform": "rotate(90deg)"   // Quarter turn clockwise
"transform": "rotate(180)"     // Half turn (deg suffix optional)
"transform": "rotate(270deg)"  // Three-quarter turn

Pixel Art Considerations:

For crisp pixel art, use 90-degree increments (0, 90, 180, 270). Other angles work but may produce anti-aliased edges:

AngleResult
0, 90, 180, 270Pixel-perfect rotation
45, 135, 225, 315Diagonal, some blurring
Other valuesArbitrary rotation with anti-aliasing

scale(n) or scale(x, y)

Scale Transform

Scaling using scale(s), scale(x, y), scaleX(x), scaleY(y).

{"type": "palette", "name": "scale_pal", "colors": {"{_}": "#00000000", "{d}": "#0000FF"}}
{"type": "sprite", "name": "dot", "palette": "scale_pal", "size": [1, 1], "regions": {"d": {"points": [[0, 0]]}}}
{"type": "sprite", "name": "square", "palette": "scale_pal", "size": [2, 2], "regions": {"d": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "scale_up", "duration": "500ms", "keyframes": {"0%": {"sprite": "dot", "transform": "scale(1)"}, "100%": {"sprite": "dot", "transform": "scale(4)"}}}
{"type": "animation", "name": "scale_xy", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scale(1, 1)"}, "50%": {"sprite": "square", "transform": "scale(2, 1)"}, "100%": {"sprite": "square", "transform": "scale(2, 2)"}}}
{"type": "animation", "name": "scale_x_only", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scaleX(1)"}, "100%": {"sprite": "square", "transform": "scaleX(3)"}}}
{"type": "animation", "name": "scale_y_only", "duration": "500ms", "keyframes": {"0%": {"sprite": "square", "transform": "scaleY(1)"}, "100%": {"sprite": "square", "transform": "scaleY(3)"}}}
{"type": "animation", "name": "pulse_scale", "duration": "500ms", "keyframes": {"0%": {"sprite": "dot", "transform": "scale(1)", "opacity": 1.0}, "50%": {"sprite": "dot", "transform": "scale(2)", "opacity": 0.6}, "100%": {"sprite": "dot", "transform": "scale(1)", "opacity": 1.0}}}

Scale the sprite uniformly or non-uniformly.

"transform": "scale(2)"        // Double size (uniform)
"transform": "scale(2, 1)"     // Double width only
"transform": "scale(1, 0.5)"   // Half height
ParameterTypeDescription
nfloatUniform scale factor (must be positive)
x, yfloatNon-uniform scale factors

Alternative syntax:

"transform": "scaleX(2)"       // Scale width only
"transform": "scaleY(0.5)"     // Scale height only

Pixel Art Considerations:

Integer scale factors (2, 3, 4) maintain pixel-perfect appearance. Fractional scales may blur:

ScaleResult
1, 2, 3, 4...Pixel-perfect scaling
1.5, 2.5...Blended pixels
0.5, 0.25...Reduced resolution

flip(x) or flip(y)

Flip Transform

Flipping sprites using scaleX(-1) and scaleY(-1).

{"type": "palette", "name": "flip_pal", "colors": {"{_}": "#00000000", "{f}": "#FF00FF"}}
{"type": "sprite", "name": "face_right", "palette": "flip_pal", "size": [2, 2], "regions": {"f": {"rect": [0, 0, 2, 2]}}}
{"type": "sprite", "name": "arrow_left", "palette": "flip_pal", "size": [2, 2], "regions": {"f": {"rect": [0, 0, 2, 2]}}}
{"type": "animation", "name": "flip_horizontal", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scaleX(1)"}, "100%": {"sprite": "face_right", "transform": "scaleX(-1)"}}}
{"type": "animation", "name": "flip_vertical", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scaleY(1)"}, "100%": {"sprite": "face_right", "transform": "scaleY(-1)"}}}
{"type": "animation", "name": "flip_both", "duration": "500ms", "keyframes": {"0%": {"sprite": "face_right", "transform": "scale(1, 1)"}, "50%": {"sprite": "face_right", "transform": "scale(-1, 1)"}, "100%": {"sprite": "face_right", "transform": "scale(-1, -1)"}}}
{"type": "animation", "name": "mirror_walk", "duration": "1s", "keyframes": {"0%": {"sprite": "arrow_left", "transform": "translate(0, 0) scaleX(1)"}, "50%": {"sprite": "arrow_left", "transform": "translate(8px, 0) scaleX(1)"}, "51%": {"sprite": "arrow_left", "transform": "translate(8px, 0) scaleX(-1)"}, "100%": {"sprite": "arrow_left", "transform": "translate(0, 0) scaleX(-1)"}}}

Mirror the sprite horizontally or vertically.

"transform": "flip(x)"         // Horizontal mirror
"transform": "flip(y)"         // Vertical mirror
"transform": "flip(x) flip(y)" // Both axes

Alternative syntax:

"transform": "flipX()"         // Same as flip(x)
"transform": "flipY()"         // Same as flip(y)
"transform": "flip(h)"         // Horizontal (alias)
"transform": "flip(v)"         // Vertical (alias)

Flipping is always pixel-perfect with no quality loss.

Transform Order

Transforms apply in CSS order: translate → rotate → scale → flip.

"transform": "translate(10, 0) rotate(90deg) scale(2)"

This translates first, then rotates, then scales.

Complete Examples

Spinning animation:

{"type": "animation", "name": "spin", "keyframes": {"0%": {"sprite": "star", "transform": "rotate(0deg)"}, "100%": {"sprite": "star", "transform": "rotate(360deg)"}}, "duration": 1000, "timing_function": "linear"}

Pulsing scale effect:

{"type": "animation", "name": "pulse", "keyframes": {"0%": {"sprite": "star", "transform": "scale(1)", "opacity": 1.0}, "50%": {"sprite": "star", "transform": "scale(1.5)", "opacity": 0.5}, "100%": {"sprite": "star", "transform": "scale(1)", "opacity": 1.0}}, "duration": "2s", "timing_function": "ease-in-out"}

Bouncing with translation:

{"type": "animation", "name": "bounce", "keyframes": {"0%": {"sprite": "ball", "transform": "translate(0, 0)"}, "50%": {"sprite": "ball", "transform": "translate(0, -10)"}, "100%": {"sprite": "ball", "transform": "translate(0, 0)"}}, "duration": "500ms", "timing_function": "ease-in-out"}

Flip on hover (walk cycle):

{"type": "sprite", "name": "walk_left", "source": "walk_right", "transform": [{"op": "mirror-h"}]}

Op-style Transforms (Derived Sprites)

Create new sprites derived from existing ones using transforms. These are specified via the transform array on a sprite definition.

Note: Op-style transforms are for sprite derivation, not animations. For animated transforms, see CSS Transforms above.

String Syntax (Simple Operations)

For common geometric operations, use string syntax:

{"type": "sprite", "name": "hero_left", "source": "hero_right", "transform": ["mirror-h"]}
{"type": "sprite", "name": "hero_down", "source": "hero_right", "transform": ["rotate:90"]}
{"type": "sprite", "name": "hero_big", "source": "hero", "transform": ["scale:2,2"]}
{"type": "sprite", "name": "hero_shadow", "source": "hero", "transform": ["shadow:1,1:{shadow}"]}
OperationString SyntaxDescription
Mirror H"mirror-h"Flip horizontally (left-right)
Mirror V"mirror-v"Flip vertically (top-bottom)
Rotate"rotate:90"Rotate 90°, 180°, or 270° clockwise
Scale"scale:2,2"Scale by X,Y factors
Shift"shift:1,1"Shift pixels by X,Y offset
Shadow"shadow:1,1:{token}"Add drop shadow at offset with token
Sel-out"sel-out" or "sel-out:{fallback}"Selective outline

Aliases: flip-h = mirror-h, flip-v = mirror-v, rot = rotate

Object Syntax (Advanced Operations)

For operations with multiple parameters, use object syntax:

{
  "type": "sprite",
  "name": "hero_outlined",
  "source": "hero",
  "transform": [
    {"op": "operation_name", ...options}
  ]
}

Transforms are applied in array order.


Effect Transforms

Effect transforms modify sprite appearance through arrays of operations.

Dithering Patterns

Apply dithering patterns for gradients, transparency effects, and texture.

{
  "type": "sprite",
  "name": "gradient",
  "source": "solid",
  "transform": [
    {"op": "dither", "pattern": "checker", "tokens": ["{dark}", "{light}"], "threshold": 0.5}
  ]
}

Dither Fields

FieldRequiredDefaultDescription
opYes-Must be "dither"
patternYes-Dither pattern name
tokensYes-Two-element array [dark, light]
thresholdNo0.5Blend threshold (0.0-1.0)
seedNoautoRandom seed for noise pattern

Built-in Patterns

PatternDescription
checker2x2 checkerboard
ordered-2x22x2 Bayer matrix (4 levels)
ordered-4x44x4 Bayer matrix (16 levels)
ordered-8x88x8 Bayer matrix (64 levels)
diagonalDiagonal line pattern
horizontalHorizontal line pattern
verticalVertical line pattern
noiseRandom dither (seeded)

Gradient Dither

Create smooth gradients across the sprite:

{
  "op": "dither-gradient",
  "direction": "vertical",
  "from": "{sky_light}",
  "to": "{sky_dark}",
  "pattern": "ordered-4x4"
}
FieldRequiredDefaultDescription
opYes-Must be "dither-gradient"
directionYes-"vertical", "horizontal", or "radial"
fromYes-Starting color token
toYes-Ending color token
patternNo"ordered-4x4"Dither pattern to use

Selective Outline (Sel-out)

Selective outline varies the outline color based on the adjacent fill color, creating softer, more natural edges.

{
  "transform": [
    {"op": "sel-out", "fallback": "{outline}"}
  ]
}

Sel-out Fields

FieldRequiredDefaultDescription
opYes-Must be "sel-out"
fallbackNo"{_}"Default outline color
auto_darkenNo0.3Auto-darken factor (0.0-1.0)
mappingNoautoExplicit fill→outline mapping

Auto-Darken Mode

By default, sel-out automatically darkens each fill color to create its outline:

{"op": "sel-out", "auto_darken": 0.3}

Skin-colored pixels get a darker skin outline, hair pixels get a darker hair outline, etc.

Explicit Mapping

Define exactly which outline color to use for each fill:

{
  "op": "sel-out",
  "mapping": {
    "{skin}": "{skin_dark}",
    "{hair}": "{hair_dark}",
    "*": "{outline}"
  }
}

The * key is the fallback for any unspecified fill colors.

Squash & Stretch

Deform sprites for impact and bounce effects. Classic animation technique.

{
  "transform": [
    {"op": "squash", "amount": 0.3}
  ]
}

Squash/Stretch Fields

FieldRequiredDefaultDescription
opYes-"squash" or "stretch"
amountYes-Deformation amount (0.0-1.0)
anchorNo"center"Transform anchor point
preserve_areaNotrueMaintain sprite area

Anchor Points

ValueDescription
"center"Center of sprite
"bottom"Bottom center
"top"Top center
[x, y]Custom coordinates

Squash vs Stretch

  • Squash: Compress vertically, expand horizontally (landing, impact)
  • Stretch: Expand vertically, compress horizontally (jumping, anticipation)
{"type": "sprite", "name": "ball_land", "source": "ball", "transform": [
  {"op": "squash", "amount": 0.4, "anchor": "bottom"}
]}

{"type": "sprite", "name": "ball_jump", "source": "ball", "transform": [
  {"op": "stretch", "amount": 0.3, "anchor": "bottom"}
]}

Chaining Transforms

Apply multiple transforms in sequence:

{
  "type": "sprite",
  "name": "hero_processed",
  "source": "hero",
  "transform": [
    {"op": "sel-out", "auto_darken": 0.25},
    {"op": "dither", "pattern": "checker", "tokens": ["{shadow}", "{_}"], "threshold": 0.3}
  ]
}

Transforms apply in array order: first sel-out, then dither.

Complete Example

{"type": "palette", "name": "character", "colors": {
  "{_}": "#00000000",
  "{skin}": "#FFCC99",
  "{skin_dark}": "#CC9966",
  "{hair}": "#8B4513",
  "{hair_dark}": "#5C2E0A",
  "{outline}": "#000000"
}}

{"type": "sprite", "name": "hero_raw", "size": [7, 5], "palette": "character", "regions": {
  "hair": {"union": [{"rect": [2, 0, 3, 1]}, {"rect": [1, 1, 5, 1]}], "z": 0},
  "skin": {"rect": [1, 2, 5, 2], "z": 0}
}}

{"type": "sprite", "name": "hero", "source": "hero_raw", "transform": [
  {"op": "sel-out", "mapping": {
    "{skin}": "{skin_dark}",
    "{hair}": "{hair_dark}",
    "*": "{outline}"
  }}
]}

The hero sprite has automatic selective outlining based on the fill colors.

Use Cases

Retro-Style Gradients

{"op": "dither-gradient", "direction": "vertical", "from": "{sky_top}", "to": "{sky_bottom}", "pattern": "ordered-4x4"}

Soft Shadows

{"op": "dither", "pattern": "ordered-2x2", "tokens": ["{shadow}", "{_}"], "threshold": 0.6}

Impact Effects

{"op": "squash", "amount": 0.5, "anchor": "bottom", "preserve_area": true}

Professional Outlines

{"op": "sel-out", "auto_darken": 0.3}