Skip to content
Sign in
Image studioOutpaint canvasMenu

Outpaint canvasPro

Zoom out from an image and drag eight handles to extend its canvas. The area the model will fill shows as a dashed frame with its size before anything runs, with aspect presets, Zoom out 1.5× and 2×, extend by a side, undo, a size cap warning and the result revealed from the original's edges outward.

ProA square shot widened to 16:9. Drag the eight handles or the image, try Zoom out 2×, then extend it and watch the new area reveal from the original's edges.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/outpaint-canvas

Usage

tsx
import { OutpaintCanvas } from "@/components/ui/chatcn/pro/outpaint-canvas"

<OutpaintCanvas
  src={image.url}
  alt={image.alt}
  width={image.width}
  height={image.height}
  generating={job.running}
  progress={job.progress}
  result={job.url}
  onGenerate={(frame, output) => start({ frame, output })}
/>

The frame is in the original's own pixels, relative to its top left corner, and always contains it: { x: -300, y: 0, width: 1368, height: 768 } adds 300 px on each side of a 768 px square. Added sizes snap to step (8 px by default) so they suit the model. onGenerate also receives the output size, scaled down when the long edge passes maxSize. Pass the finished image of the whole frame as result; it shows until the frame changes again, so people can keep extending. The frame math (resize with ratio and symmetry, zoom out, extend, area) is exported from chatcn-pro-studio-2a-outpaint for your own controls.

Examples

States

ProGenerating with progress, extended, failed with Retry, over the size limit, disabled and a narrow frame.

Props

PropTypeNotes
src, altstringThe original image
width, heightnumberThe original's size in px. Required: every frame value uses it
frame, defaultFrame, onFrameChange{ x, y, width, height }Default the original itself. Fires once per drag, key press burst, preset or undo
aspect, defaultAspect, onAspectChange"free" | "1:1" | "4:3" | "3:2" | "16:9" | "9:16"A preset locks the ratio while you drag. Default free
aspectsOutpaintAspect[]Which presets the toolbar offers
onGenerate, generateLabel(frame, output) => void, stringShows the "Extend image" button
generating, progress, expectedMs, onCancelboolean, number | null, number, () => voidLocks the frame; real progress when you have it, an honest creep when you do not
resultstring | nullThe finished image of the whole frame
error, onRetryboolean | string, () => voidRetry defaults to generating again
maxSizenumberLong edge the model returns. Default 4096, with an amber warning past it
stepnumberAdded sizes snap to it. Default 8
disabled, toolbar, stageHeightboolean, boolean, numberStage height default 440 px

Keyboard

KeyAction
Arrows on a handleMove that edge or corner by 8 px; with Shift, by 64 px; with Alt, from the center
Page Up, Page Down on a handleGrow or shrink that side by 64 px
Home on a handleRemove what that side added
Arrows on the imageMove the image inside the frame
+, -, 0Zoom the view in, out, or fit the frame
Space and dragPan the view
⌘Z or Ctrl+Z, ⇧⌘Z or Ctrl+YUndo and redo frame changes

Accessibility

  • Each handle is a slider named for its side, "Extend left edge", whose value is the pixels it added, read as "320 px left added".
  • The image is a focusable group that says where it sits in the frame; the stage describes every key.
  • Frame changes, undo, the finished result and errors are announced politely. Progress shows as a number, not color.
  • Numbers stay left to right in right to left layouts. Handles take 44 px on touch screens.

Motion

  • Handles, the image and pans follow the pointer with no easing.
  • After a pointer edit the view refits the new frame in 280 ms; keyboard edits refit at once. Zoom buttons glide in 180 ms.
  • The result reveals from the original's edges outward with a 280 ms clip path. Under reduced motion every glide is instant and the result fades in 150 ms.