Skip to content

feat(image-editor): Warp Text — arc / bulge / flag / wave / rise / fish / inflate#79

Merged
lyfuci merged 1 commit into
mainfrom
feat/image-editor-warp-text
May 29, 2026
Merged

feat(image-editor): Warp Text — arc / bulge / flag / wave / rise / fish / inflate#79
lyfuci merged 1 commit into
mainfrom
feat/image-editor-warp-text

Conversation

@lyfuci

@lyfuci lyfuci commented May 28, 2026

Copy link
Copy Markdown
Owner

What

Warp Text (Photoshop Type ▸ Warp Text) as a non-destructive, re-editable property of a text layer (shape.warp). Pick a style, dial Bend / Horizontal / Vertical distortion, see it live, Apply — the text stays editable vector text with a warp envelope.

8 styles: None · Arc · Bulge · Flag · Wave · Rise · Fish · Inflate, each with Bend and Horizontal/Vertical Distortion (−100…100).

How

Vertical-envelope warp. Every classic PS warp shape (for horizontal text) is a deformation where each vertical column of the rendered text keeps its x but is bent/stretched vertically. So the renderer rasterizes the text to a padded offscreen and remaps it column-by-column between a per-column top/bottom edge curve via bilinear inverse sampling (the same technique as the Wave/Ripple filters). This reproduces all the shapes faithfully with a trivial 1-D inverse — no 2-D mesh solve.

That's also why Horizontal Distortion here is a left/right asymmetry of the envelope (still a function of u), not PS's true horizontal perspective: real perspective makes x depend on the row and would force a 2-D inversion. The vertical-envelope model covers the popular set cleanly.

Integration care

  • drawShape runs on every render (unlike a baked filter), so warped bitmaps are cached keyed on text / font / align / warp params / scale; plain or zero-amount warps short-circuit to the existing drawText. The offscreen reuses drawText's exact layout (multi-line / align / letterSpacing / underline) — one implementation, so warp can't silently drift from plain text.
  • Live preview without history pollution: the dialog overlays the draft warp on the target layer via a derived displayState fed to the canvas; the real document state is untouched until Apply commits a single undo step. Cancel just drops the preview.
  • The warp is purely visual — the layer's logical bbox is unchanged, so hit-testing / move / handles keep working. Verified by asserting getLayerBBox(warped) === getLayerBBox(plain) and that pickLayer selects a warped layer at its centre.

Verification

  • pnpm typecheck · pnpm lint · pnpm build — clean.
  • +9 unit tests (envelope identity for none/zero params, isWarpActive gating, zero-bend remap is an exact no-op, real bends move pixels and stay in range). Full suite 197 green.
  • Rendered all 8 styles + horizontal/vertical distortion in headless Chrome via the real drawShape dispatch (contact sheet), and confirmed the select/move integration (bbox unchanged, warped layer pickable at centre).

EN + zh-CN strings included.

🤖 Generated with Claude Code

Photoshop Type > Warp Text as a non-destructive, re-editable property of a
text layer (shape.warp). The renderer rasterizes the text and bends it via a
vertical-envelope warp — each column is remapped between a per-column top/bottom
edge curve with bilinear inverse sampling — which faithfully reproduces the
classic PS warp shapes without a 2-D mesh solve.

- 8 styles (none + arc/bulge/flag/wave/rise/fish/inflate) with Bend and
  Horizontal/Vertical distortion (-100..100). Distortion stays a vertical-
  envelope modulation, not 2-D perspective, so the inverse stays a 1-D solve.
- Warp Text dialog (Layer menu, enabled for a plain text layer) with live
  canvas preview via a display-state overlay; Apply commits one undo step,
  Cancel drops the preview — history untouched during preview.
- drawShape runs on every render, so warped bitmaps are cached keyed on text /
  font / align / warp params / scale; plain or zero-amount warps short-circuit
  to the existing drawText. Layout (multi-line / align / letterSpacing /
  underline) is shared with drawText so warp can't drift from plain text.
- The warp is purely visual: the layer's logical bbox is unchanged, so
  hit-testing, move and handles keep working (verified select + pick on a
  warped layer in headless Chrome).

9 new unit tests (envelope identity, isWarpActive gating, zero-bend remap is a
no-op, real bends move pixels in range); full suite 197 green. typecheck /
lint / build clean. All 8 styles + distortion verified in headless Chrome via
the real drawShape dispatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lyfuci lyfuci merged commit 39ec172 into main May 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant