# Image Processing Overlay

Image Processing Overlay [#image-processing-overlay]

The **Image Processing Overlay*&#x2A; is a full-bleed overlay shown over a dimmed source image while vision inference runs: a spinner ring + centered icon, a status headline + optional sub-line, and an optional cancel link. A &#x2A;*`scan`** variant adds an animated scan-line sweep + scan-grid. It renders **nothing** when not processing — the element is absent from the DOM, not merely hidden.

Drive it with any vision hook's `isLoading` and wire the cancel link to the hook's `cancel` to abort the in-flight operation.

**When to use it:** over the source image of any model that takes a moment — detection, segmentation, OCR, captioning — to show progress without blocking the page.

Preview [#preview]

```tsx
'use client';

import * as React from 'react';
import { ImageProcessingOverlay } from '@/components/image-processing-overlay';

/** Inline SVG data URL — no network request in the preview. */
const IMAGE = `data:image/svg+xml;utf8,${encodeURIComponent(
  `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="260"><rect width="400" height="260" fill="#1e293b"/><circle cx="200" cy="130" r="70" fill="#475569"/></svg>`,
)}`;

/**
 * Demo for the ImageProcessingOverlay, used by the docs live preview.
 * Toggles a simulated processing pass so both the spinner and the scan variant
 * are visible, and demonstrates that nothing renders when idle.
 */
export default function ImageProcessingOverlayDemo() {
  const [processing, setProcessing] = React.useState(false);
  const [variant, setVariant] = React.useState<'spinner' | 'scan'>('scan');

  function run() {
    setProcessing(true);
    setTimeout(() => setProcessing(false), 4000);
  }

  return (
    <div className="w-full max-w-md space-y-3">
      <div className="relative overflow-hidden rounded-lg border border-border">
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={IMAGE}
          alt="demo"
          className={processing ? 'w-full opacity-50' : 'w-full'}
        />
        <ImageProcessingOverlay
          processing={processing}
          variant={variant}
          status="Detecting objects…"
          detail="yolos-tiny · running on WASM"
          onCancel={() => setProcessing(false)}
        />
      </div>

      <div className="flex flex-wrap items-center gap-2">
        <button
          type="button"
          onClick={run}
          disabled={processing}
          className="rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground disabled:opacity-50"
        >
          {processing ? 'Processing…' : 'Run (4s)'}
        </button>
        <button
          type="button"
          onClick={() => setVariant((v) => (v === 'scan' ? 'spinner' : 'scan'))}
          className="rounded-md border border-border bg-card px-3 py-1.5 text-sm font-medium text-card-foreground"
        >
          Variant: {variant}
        </button>
      </div>
    </div>
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/media-vision/image-processing-overlay
```

Dependencies [#dependencies]

* **Data source:** driven purely by the `processing` boolean and the `onCancel` callback you pass — works with any backend's loading/cancel state. Recommended LocalMode producers: any vision hook's `isLoading` + `cancel` — `useDetectObjects` / `useSegmentImage` / `useCaptionImage` / `useImageToImage` from `@localmode/react` (optional).

* `lucide-react` — icons

* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `image-processing-overlay.tsx` — the component (scan keyframes shipped inline)
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**ImageProcessingOverlay**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `processing` | `boolean` | — | **Required.** Whether inference is running. When `false`, the overlay renders nothing (the element is absent from the DOM). Wire this to a vision hook's `isLoading` / `isProcessing`. |
| `status` | `string` | `"Processing image…"` | Headline status text. |
| `detail` | `string` | — | Optional sub-line under the status (e.g. model name or progress). |
| `variant` | `"scan" \| "spinner"` | `"spinner"` | Visual variant. `"spinner"` shows a spinner ring + icon; `"scan"` adds an animated scan-line/scan-grid sweep over the source. |
| `icon` | `ReactNode` | `a scan-line icon` | Icon rendered inside the spinner ring. |
| `onCancel` | `function` | — | When provided, render a "Cancel" link that calls this — wire it to the hook's `cancel` to abort the in-flight operation. |
| `cancelLabel` | `string` | `"Cancel"` | Cancel link label. |

Backing hooks [#backing-hooks]

Any vision hook from [`@localmode/react`](https://localmode.dev/docs/react) exposes `isLoading` and `cancel` — `useDetectObjects`, `useSegmentImage`, `useCaptionImage`, `useImageToImage`, … Pass `isLoading` to `processing` and `cancel` to `onCancel`.

Examples [#examples]

Over a dimmed image [#over-a-dimmed-image]

```tsx
import { ImageProcessingOverlay } from '@/components/image-processing-overlay';
import { useDetectObjects } from '@localmode/react';

export function Example({ src }: { src: string }) {
  const { isLoading, cancel } = useDetectObjects({ model });

  return (
    <div className="relative">
      <img src={src} alt="" className={isLoading ? 'opacity-50' : ''} />
      <ImageProcessingOverlay
        processing={isLoading}
        status="Detecting objects…"
        onCancel={cancel}
      />
    </div>
  );
}
```

Scan variant [#scan-variant]

```tsx
<ImageProcessingOverlay processing={isLoading} variant="scan" status="Scanning document…" />
```

Customization [#customization]

The scan-line/scan-grid keyframes are shipped inline (a scoped `<style>`), so the overlay animates standalone after `shadcn add` with no global CSS. Swap the icon via the `icon` prop, restyle the spinner ring, or tune the scan animation directly in the copied `image-processing-overlay.tsx`. The overlay inherits `rounded-[inherit]` from its container.