How Wobble Animation Works: Masks, Meshes, Springs, and Damping
Every wobble we ship is the same five-step pipeline running on the original pixels. We do not generate frames with a model; we deform the image you loaded, sample it at fixed time steps, and let your browser encode the result.
A close read of the pipeline behind every wobble: how a painted region becomes a deformable mesh, how springs and damping decide the feel, and which parameters the four presets actually change.
Most wobble tutorials stop at "paint a region, pick a preset, record a GIF." That sentence is true, but it skips the part we find interesting: which exact number changes when you switch from Soft wobble to Spring bounce, why a higher Cohesion makes a sticker feel like rubber instead of liquid, and where the pipeline has hard limits. This page is for people who want to look under the hood.
The 30-second version
A wobble is five stages:
- The still image you loaded stays as the texture. Nothing is generated.
- Your paint becomes a mask: a per-pixel yes/no record that says which pixels may move.
- That mask is overlaid on a mesh: a sparse grid of points and triangles.
- A physics step runs 60 times per second and pulls each mesh point toward a target, then adds damping and cohesion.
- The deformed mesh is rendered back onto the original image, and the result is sampled 16 times per second for GIF output.
Everything you can tune in the panel sits inside stage 4. Everything you cannot tune is in stages 1-3 and 5, and those stages are why some effects are simply not achievable.
The mesh is why wobble is cheap
We could simulate every pixel as an independent spring, but that would be slow and would tear the image apart. Instead, we lay a coarse triangular mesh over the painted region. For a 1024×1536 image, the mesh uses roughly a 64×96 grid of points. When a point moves, the triangles around it bend, and the texture mapped onto those triangles bends with them.
Mesh density is a deliberate trade-off. We chose the resolution we did because:
- It is fine enough to express the soft curves of jelly, hair, and fabric.
- It is coarse enough that the physics step stays under 5 ms on a mid-range laptop, which is what keeps the live preview at 60 fps.
- Below about 32×48 the wobble starts to feel like a stiff plate. Above about 96×144 the cost climbs faster than the visible benefit.
This is also why very small painted regions (a button, a single eye) sometimes look rigid. The mesh has fewer points inside them, so there is less room to deform. Painting a slightly larger area than you think you need almost always reads better than trying to be precise.
The nine parameters, in plain English
Every wobble is determined by nine numerical parameters. Here is what each one actually controls, in the order the panel surfaces them:
| Parameter | What it does | Range |
|---|---|---|
| Input strength | How much external motion (drag, sensor, auto) pushes the frame. | 0-100 |
| Stretch | How far individual mesh points are allowed to travel from rest. | 0-100 |
| Bounce | Spring stiffness: how snappily the mesh returns to rest after a push. | 0-100 |
| Damping | Energy removed per second. High damping kills oscillation quickly. | 0-100 |
| Cohesion | How much the mesh resists internal shearing. Higher = the region moves as one object. | 0-100 |
| Gravity direction | Which way the simulated gravity points. Down, up, left, right, or none. | enum |
| Gravity strength | Magnitude of that gravity. Combined with damping, decides whether the material settles or keeps moving. | 0-2 |
| Variation | How much random per-vertex noise is added each step. Each part of the painted area gets a slightly different nudge each step. Low = smooth, high = jittery. | 0-100 |
| Max stretch | Hard cap on how far any single point can travel. Prevents tearing at extreme inputs. | 0-100 |
You almost never touch these directly. The four presets are just nine numbers each. When you understand what each row does, the preset names stop being magic.
The four presets, decoded
These are the actual values each preset sets. The numbers match the wobble you see when you click each button.
| Preset | Input | Stretch | Bounce | Damping | Cohesion | Gravity | Variation | Max stretch |
|---|---|---|---|---|---|---|---|---|
| Soft wobble | 42 | 50 | 12 | 22 | 20 | down 0.55 | 6 | 62 |
| Spring bounce | 80 | 52 | 96 | 7 | 70 | down 0.80 | 0 | 55 |
| Quick shake | 28 | 6 | 55 | 72 | 92 | down 0.25 | 90 | 18 |
| Zero-G float | 26 | 65 | 8 | 0 | 0 | none | 3 | 70 |
Now the names make sense:
- Soft wobble has bounce at 12 (slow return) and damping at 22 (moderate energy loss). It settles gracefully.
- Spring bounce has bounce at 96 and damping at 7 — almost no energy loss — so a small push produces a long, visible oscillation. The cohesion at 70 keeps the whole region moving as one mass instead of flapping.
- Quick shake has variation at 90 (each part of the painted area gets a slightly different nudge each step) and max stretch at 18 (tight cap). The result is fast, jittery, and reads as nervousness rather than swing.
- Zero-G float sets gravity to none, damping to 0, and cohesion to 0. Without gravity to settle it and without damping to bleed energy, the region keeps drifting slowly until it runs out of room. The variation at 3 prevents the perfect stillness that would feel uncanny.
What you can and cannot change at runtime
Inside the app, the Fine tuning sliders expose most of the nine parameters, with safe defaults derived from the active preset. What you cannot reach from the UI:
- Mesh resolution. We did not expose it because it would change the visual identity of every preset. The mesh is part of what makes Soft wobble look like Soft wobble.
- The fixed time step. Physics runs at 60 updates per second internally; we render and export at 16 fps (for GIF) or 30 fps (for video). Decoupling render rate from physics rate is what keeps a slow device from feeling laggy when you drag.
- Per-vertex properties. How much each point of the mesh weighs, how much it resists motion, and how quickly it loses energy are all set automatically based on distance from the mask center. Points near the painted edge move less; points near the center move more. This is invisible most of the time but is why the edges of your mask stay anchored while the middle wobbles.
Where this model stops working
We have run the same pipeline on a lot of images, and the same few failure modes keep showing up. Knowing them saves time.
- Very thin regions. A very thin painted region does not have enough internal structure to deform convincingly. The output looks like a sliding stripe, not a wobbling object. Solution: paint a slightly larger region.
- Regions that include a lot of background. Background pixels inside the mask will deform with the foreground, producing a visible halo. The model has no concept of "background"; it sees a region, not a subject.
- High-resolution stills with very small subjects. A 4K source has the same mesh density as a 1K source. A 200-pixel subject ends up with too few mesh points to deform convincingly, which is below the threshold where wobble reads as wobble. Crop the source first.
- Animated GIFs as input. Purupuru Maker refuses animated sources on purpose. The first frame would freeze the result and surprise the user.
- Long recordings with low damping. Low damping means energy accumulates. A 4-second recording on Spring bounce can end with the mesh visibly drifting because the camera-follow export cuts a moving frame.
The full pipeline, in order
If you want to trace one frame of wobble end to end, here is the sequence the app follows, from image load through to the encoded file you download:
- Image load decodes the source file, validates it is not animated, and resizes it to the working resolution if needed.
- Mask paint writes strokes into a region structure. Strokes are kept as polylines; the per-pixel yes/no record is generated when needed.
- Mesh creation drops a 64×96 grid of points onto the painted region and assigns each point a weight based on distance from the boundary.
- Physics tick (60 times per second) integrates forces: input, gravity, cohesion, variation, then clamps velocity and position.
- Render draws the original image texture onto a framebuffer, warped through the current mesh positions.
- Sample reads back the framebuffer at 16 fps for GIF or 30 fps for video.
- Encode hands the captured frames to the browser's media encoder (or our in-browser GIF encoder for GIF output).
Each step has one job, and each step can fail in isolation. When something looks wrong, working backward from the symptom usually points to one stage:
- Edge artifacts: stage 2 (mask).
- Stiff shape, no give: stages 3-4 (mesh density or excessive cohesion).
- Endless oscillation: stage 4 (damping too low for the duration you record).
- Large file size: stage 7 (format, duration, dimensions).
- Visible banding or color drift: stage 7 (GIF palette limit).
The control panel does not let you reach all of these directly. That is on purpose. The point of presets is to keep the user in the part of the parameter space that produces a recognizable effect, and to make the boundaries between effects obvious instead of mushy.
Why we built it this way
Wobble could be done with a generative model — a small network that produces frames given an input image and a prompt. We considered it. The output looks great in screenshots and falls apart in practice: results are not reproducible across runs, the source image is effectively lossy once the model touches it, and the file sizes balloon because the model also has to encode color and texture each frame. We chose the deform-the-original approach because the user already gave us the pixels they want. The honest engineering answer is: do not throw away information you already have.