SVG Path Morphing

Morph between two SVG paths to create smooth shape transitions. The morphing system handles path normalization, segment matching, and interpolation automatically.

Quick Start

use Atelier\Svg\Morphing\Morph;
use Atelier\Svg\Path\Data;

$start = Data::parse('M 0 0 L 100 0 L 100 100 L 0 100 Z');
$end   = Data::parse('M 50 0 L 100 50 L 50 100 L 0 50 Z');

// Get a single interpolated path at 50%
$mid = Morph::between($start, $end, 0.5);

The Morph Facade

Atelier\Svg\Morphing\Morph provides three static entry points.

Interpolate at a Point

$path = Morph::between($start, $end, 0.3, 'ease-in-out');

The $t parameter ranges from 0.0 (start shape) to 1.0 (end shape). An optional easing function controls the interpolation curve.

Generate Animation Frames

$frames = Morph::frames($start, $end, 60, 'ease-in-out');
// Returns an array of 60 Data objects

Builder Pattern

For more control, use the fluent builder via Morph::create():

$frames = Morph::create()
    ->from($start)
    ->to($end)
    ->withDuration(2000, 60)   // 2 seconds at 60fps = 120 frames
    ->withEasing('ease-out-cubic')
    ->generate();

The builder returns a MorphingBuilder instance with these methods:

Method Description
from(Data $path) Set the starting path
to(Data $path) Set the ending path
withFrames(int $count) Set the number of frames directly
withDuration(int $ms, int $fps) Calculate frame count from duration and framerate
withEasing(string|Closure $easing) Set the easing function
generate() Generate all frames as Data[]
at(float $t) Get a single interpolated frame

ShapeMorpher

Morph delegates to ShapeMorpher, which orchestrates the three-stage pipeline (normalize, match, interpolate). You can use it directly for lower-level access:

use Atelier\Svg\Morphing\ShapeMorpher;

$morpher = new ShapeMorpher();
$result  = $morpher->morph($start, $end, 0.5, 'ease-in');

// Inspect intermediate steps
$normalized = $morpher->normalize($start);
[$matchedA, $matchedB] = $morpher->match($start, $end);

Easing Functions

The following named easing functions are available:

Name Effect
linear Constant speed
ease-in Slow start (quadratic)
ease-out Slow end (quadratic)
ease-in-out Slow start and end (quadratic)
ease-in-cubic Slow start (cubic)
ease-out-cubic Slow end (cubic)
ease-in-out-cubic Slow start and end (cubic)
ease-out-elastic Bouncy overshoot
ease-in-back Pulls back before moving forward
ease-out-back Overshoots then settles

You can also pass a custom Closure as the easing function:

$frames = Morph::create()
    ->from($start)
    ->to($end)
    ->withFrames(60)
    ->withEasing(fn(float $t): float => $t * $t * $t)
    ->generate();

Or use MorphingInterpolator::cubicBezierEasing() to create a CSS-style cubic bezier:

use Atelier\Svg\Morphing\MorphingInterpolator;

$easing = MorphingInterpolator::cubicBezierEasing(0.25, 0.1, 0.25, 1.0);
$path = Morph::between($start, $end, 0.5, $easing);

See also: