---
title: "ImageGeneration"
description: "A WebGL pixel-mosaic placeholder for generated or slow-loading images."
category: Effects
status: beta
since: 0.6.2
tags: [image, generation, loading, shader, webgl, placeholder]
syncStatus: reviewed
verified: true
---

## Usage

Wraps one element, paints a shader inside it, and dissolves into the image once it is ready. Requires the `three` peer dependency (`>=0.149.0`).

### Presets
`preset` picks the effect; `images` is the pool to reveal from.
:::TuffDemoWrapper{demo="ImageGenerationShowcaseDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const POOL = ['/samples/gen-1.jpg', '/samples/gen-2.jpg', '/samples/gen-3.jpg']
  const autoReveal = ref(false)
  </script>

  <template>
    <TxImageGeneration preset="pixels-organic" :images="POOL" :auto-reveal="autoReveal">
      <div style="width: 220px; height: 220px; border-radius: 18px" />
    </TxImageGeneration>

    <TxImageGeneration preset="pixels-mechanic" :images="POOL">
      <div style="width: 220px; height: 220px; border-radius: 18px" />
    </TxImageGeneration>

    <TxImageGeneration preset="sweep-gradient" :strength="0.5">
      <div style="width: 220px; height: 220px; border-radius: 18px" />
    </TxImageGeneration>
  </template>
---
:::

### Loading Flag with a Known Image
When the card stays mounted after the job ends, reveal or hide the image through the ref. Trigger the reveal after the new `images` reach the component, and leave `paused` unset.

```vue
<script setup lang="ts">
import { ref, watch } from 'vue'

const props = defineProps<{ generating: boolean, src: string }>()
const cell = ref()

// flush: 'post' fires after the component has the new images.
watch(() => props.generating, (generating) => {
  // Generating: fade any shown image back to the shader.
  // Done: reveal the image and hold it until the next run.
  if (generating)
    cell.value?.triggerHide()
  else
    cell.value?.triggerReveal({ hold: 'manual' })
}, { flush: 'post' })
</script>

<template>
  <TxImageGeneration ref="cell" :images="[props.src]" role="img" :aria-label="props.generating ? 'Generating image' : 'Result'" :aria-busy="props.generating">
    <div style="width: 320px; height: 200px; border-radius: 12px" />
  </TxImageGeneration>
</template>
```

### Regenerate
`triggerRegenerate()` works only while an image is showing: the image breaks into cells, churns, and the next image from the pool dissolves in.

```vue
<script setup lang="ts">
import { ref } from 'vue'

const cell = ref()
const variants = ['/a.jpg', '/b.jpg', '/c.jpg']
</script>

<template>
  <TxImageGeneration ref="cell" preset="pixels-mechanic" :images="variants">
    <div style="width: 320px; height: 320px; border-radius: 20px" />
  </TxImageGeneration>
  <TxButton @click="cell?.triggerReveal({ hold: 'manual' })">Show</TxButton>
  <TxButton @click="cell?.triggerRegenerate({ durationMs: 3000 })">Regenerate</TxButton>
</template>
```

### Best Practices

- Give the wrapped child an explicit width and height; the wrapper is `inline-block` and sizes itself to the child.
- Pass `images` before triggering a reveal; with an empty pool every trigger is a silent no-op.
- As a placeholder that never reveals an image, animate only while working (`:paused="!isGenerating"`). When it does reveal one, leave `paused` unset: a paused card silently ignores reveals.
- Serve reveal images same-origin or with CORS headers; cross-origin images still reveal, but `triggerRegenerate()` can't sample their colors and falls back to the preset palette.
- Use `sweep-gradient` with a lower `strength` for quiet grid tiles; keep the pixel mosaics for the main result.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `preset` | `'pixels-organic' \| 'pixels-mechanic' \| 'sweep-gradient'` | `'pixels-organic'` | Bundled effect: soft mosaic, grid-locked mosaic, or diagonal sweep. |
| `theme` | `'auto' \| 'dark' \| 'light'` | `'auto'` | Theme; `auto` follows the page or OS theme live. |
| `strength` | `number` | `1` | 0–1 scales canvas opacity; above 1 it boosts the palette instead. |
| `speed` | `number` | `1` | Scales the whole effect clock (drift, churn, flicker). |
| `pixelScale` | `number` | `1` | Pixel-cell size multiplier; the reveal dissolve stays in step. |
| `cardBg` | `string` | preset default | Card background, also used for the shader's contrast logic. |
| `colors` | `(string \| null \| undefined)[]` | preset palette | Palette override, one entry per shader slot. |
| `images` | `string \| string[]` | `[]` | Reveal pool; picks at random, never repeating the previous image. |
| `autoReveal` | `boolean` | `false` | Loops shader → reveal → hold → hide on its own. |
| `revealDelayRange` | `[number, number]` | `[2, 4]` | Random delay between reveals, in seconds. |
| `revealInitialDelay` | `number \| [number, number]` | jitter | One-time delay before the first reveal. |
| `revealHoldMs` | `number \| [number, number]` | `2000` | How long the image stays fully visible. |
| `revealFadeOutMs` | `number` | `300` | Fade back to the shader. |
| `borderRadius` | `number` | auto-detected | Card corner radius in CSS px. |
| `paused` | `boolean` | `false` | Freezes the shader and auto-reveal; reveal, hide, and regenerate do nothing while paused. |
| `fragmentShader` | `string` | bundled | Replacement GLSL 1.00 fragment shader; applies page-wide while set. |
| `excludeSrcs` | `() => string[] \| Set<string> \| null` | none | Sources this pick must avoid, for instances sharing a pool. |

### Events

| Event | Payload | Description |
|------|------|-------------|
| `cycle` | `ImageGenerationCycleEvent` | Auto-reveal phase changes (`idle` → `reveal` → `visible` → `hide`). |

### Slots

| Slot | Props | Description |
|------|------|-------------|
| `default` | - | The card element the effect sizes itself to. |

### Exposed Methods

| Method | Description |
|------|-------------|
| `element` | The root wrapper `<div>` (`HTMLDivElement`); `null` while unmounted. |
| `triggerReveal({ hold?: 'auto' \| 'manual' })` | Runs one reveal, held until `triggerHide()` with `manual`; no-op mid-pass or with empty `images`. |
| `triggerHide()` | Fades the image back to the shader; no-op when nothing is showing. |
| `triggerRegenerate({ durationMs?, tintFromImage?, autoReveal? })` | Breaks the shown image into churning cells, then dissolves in the next; no-op unless an image is showing. |
| `isImageActive()` | `true` while an image is revealing, visible, or hiding. |

## Overview

- One shared `THREE.WebGLRenderer` and WebGL context serve the whole page; each card copies its frame into its own 2D canvas.
- Frame rate is capped at 10 fps, the GL canvas at a device pixel ratio of 1.25, and the visible canvas at 2.
- Cards pause offscreen (`IntersectionObserver`, 64 px margin), the loop stops when no card is active, and WebGL context loss is handled.
- Decoded images are cached by URL across cards and drawn like `object-fit: cover`, center-cropped.
- The corner radius comes from the child's computed `border-top-left-radius` and applies to all four corners.
- `prefers-reduced-motion` is not handled; wire it yourself when needed, such as `:paused="reduced && generating"`.

## Technologies

- `engine/**` and `presets/**` are ported from [Jakubantalik/Libraries · img-fx](https://github.com/Jakubantalik/Libraries/tree/main/packages/img-fx) (MIT © Jakub Antalik).
- Source: `packages/tuffex/packages/components/src/image-generation/`.

<TuffDocSourceLink />
