Skip to content
Sign in
Image studioUpscale compareMenu

Upscale comparePro

An original and its upscale on one shared view: drag or key the divider, zoom and pan both sides together, and put the same spot from both images side by side in a split lens with the pixel grid.

ProA 4× upscale against its original on one shared view. Drag the divider, scroll or pinch to zoom both sides, hold L for the split lens.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/upscale-compare

Usage

tsx
import { UpscaleCompare } from "@/components/ui/chatcn/pro/upscale-compare"

<UpscaleCompare before={original} after={job.result} progress={job.progress} onRetry={job.retry} />

Pass both images with their intrinsic width and height; the labels show the sizes and the factor. Both sides share one zoom and pan, so the divider always compares the same spot. Once a pixel of the original covers 3 screen pixels it draws without smoothing, the way a viewer should, so what you see is what the model started from. Leave after out while the job runs: the after side shows the original softened behind the progress.

Examples

Split lens

ProThe lens at 8× on the divider: the original's pixels on one half, the upscale on the other, with the source pixel grid.

The lens shows the original on its start half and the upscale on its end half, from the same region, so the seam lines up across both sources. From 8× it draws the source pixel grid on each half at that half's own resolution. It draws the images but never reads them, so it needs no CORS.

States

ProUpscaling with live progress, a failed job with Retry, and disabled.

Props

PropTypeNotes
before, after{ src, alt, width, height }after is optional while upscaling
factornumberFor the label. Derived from the widths
status"ready" | "upscaling" | "error"Default from whether after is set
progressnumber0 to 1 while upscaling. Shown as a percent and a thin fill
error, onRetrystring, () => voidWhat failed and how to fix it, and the Retry action
position, defaultPosition, onPositionChangenumber0 to 100 from the start edge. Default 50
loupe, defaultLoupe, onLoupeChangebooleanThe split lens
loupeZoom, loupeZoomLevels, loupeSizenumber, number[], numberDefault 4, [2, 4, 8] and 184 px
beforeLabel, afterLabelstringDefault "Original" and "Upscaled"
toolbarbooleanZoom buttons, percent and the lens toggle. Default on
disabledbooleanHides the handle and stops every gesture

Keyboard

KeyAction
← →Move the divider 1 %. Mirrored in right to left layouts
Shift + arrows, PageUp PageDownMove it 10 %
Home, EndAll original, all upscale
+, -, 0Zoom both sides in, out, or fit. With the lens open, + and - change its zoom
L (hold)Show the lens on the divider while held
Shift + LKeep the lens open; Esc closes it

Accessibility

  • The handle is a role="slider" with a value text that names both sides: "Original 40%, upscaled 60%".
  • The images are decorative layers of one comparison; the labels carry the sizes and the factor. Give each image an alt that describes the picture.
  • Progress is a progressbar; a failed job is an alert with the fix and a Retry button.
  • Touch: drag along the image to move the divider, pinch to zoom, drag to pan once zoomed. Vertical swipes still scroll the page until you zoom.
  • Under forced colors the lens hides and a real outline keeps the frame.

Motion

  • Click to jump glides the divider in 180 ms; dragging and keys move it at once.
  • Zoom buttons and double click animate the shared view for 200 ms. Wheel, pinch, drag and keys track the input directly.
  • The lens opens from 0.96 with a 150 ms fade under the pointer, and instantly from the keyboard.
  • Under reduced motion only the fades remain.