Files
test-repo/docs/.vitepress/theme/components/appendix/image-gen-intro/DiffusionProcessDemo.vue
T

303 lines
6.6 KiB
Vue
Raw Normal View History

2026-01-15 20:10:19 +08:00
<template>
<div class="diffusion-magic">
<div class="magic-frame">
<!-- The Canvas -->
<div class="canvas-wrapper">
<canvas
ref="canvasRef"
width="300"
height="300"
/>
<!-- Overlay Status -->
<div
class="status-overlay"
:class="{ visible: isProcessing }"
>
<div class="step-counter">
Step {{ currentStep }} / {{ totalSteps }}
</div>
<div class="step-desc">
{{ stepDescription }}
</div>
2026-01-15 20:10:19 +08:00
</div>
</div>
<!-- Controls -->
<div class="controls">
<button
class="magic-btn"
:disabled="isProcessing"
@click="startDenoise"
>
<span class="icon"></span>
{{ isProcessing ? '去噪中...' : '开始去噪 (Denoise)' }}
</button>
<button
class="reset-btn"
:disabled="isProcessing"
@click="reset"
>
<span class="icon">🔄</span> 重置
</button>
2026-01-15 20:10:19 +08:00
</div>
</div>
<div class="info-bar">
<span class="icon">💡</span>
<span>
<strong>观察重点</strong>
注意看图像不是一下子变出来的而是像在雾气中慢慢显影这就是 Diffusion 的核心它在不断猜测噪声背后的真相
</span>
</div>
2026-01-15 20:10:19 +08:00
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
2026-01-15 20:10:19 +08:00
const canvasRef = ref(null)
const isProcessing = ref(false)
2026-01-15 20:10:19 +08:00
const currentStep = ref(0)
const totalSteps = 50
2026-01-15 20:10:19 +08:00
let animationFrame = null
// Use a simple gradient pattern as the "Target Image" to avoid external assets
const drawTargetImage = (ctx) => {
// Draw a sunset landscape
2026-01-15 20:10:19 +08:00
const gradient = ctx.createLinearGradient(0, 0, 0, 300)
gradient.addColorStop(0, '#2c3e50')
gradient.addColorStop(0.5, '#e67e22')
gradient.addColorStop(1, '#f1c40f')
2026-01-15 20:10:19 +08:00
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 300, 300)
// Draw a sun
2026-01-15 20:10:19 +08:00
ctx.beginPath()
ctx.arc(150, 200, 60, 0, Math.PI * 2)
ctx.fillStyle = '#f39c12'
2026-01-15 20:10:19 +08:00
ctx.fill()
// Draw mountains
2026-01-15 20:10:19 +08:00
ctx.beginPath()
ctx.moveTo(0, 300)
ctx.lineTo(100, 200)
2026-01-15 20:10:19 +08:00
ctx.lineTo(200, 250)
ctx.lineTo(300, 150)
2026-01-15 20:10:19 +08:00
ctx.lineTo(300, 300)
ctx.fillStyle = '#2c3e50'
2026-01-15 20:10:19 +08:00
ctx.fill()
}
const drawNoise = (ctx, amount) => {
const w = 300
const h = 300
const idata = ctx.getImageData(0, 0, w, h)
const buffer = new Uint32Array(idata.data.buffer)
// We need to blend the target image with noise based on 'amount' (0 to 1)
// But since we can't easily read back the target image every frame efficiently without offscreen canvas,
// let's do a simpler trick: Draw target, then draw semi-transparent noise on top.
// Actually, let's generate noise overlay.
// Amount 1.0 = Full Noise (Opaque)
// Amount 0.0 = No Noise (Transparent)
// Clear and draw target first
drawTargetImage(ctx)
if (amount <= 0) return
const noiseCanvas = document.createElement('canvas')
noiseCanvas.width = w
noiseCanvas.height = h
const nCtx = noiseCanvas.getContext('2d')
const nImgData = nCtx.createImageData(w, h)
const data = nImgData.data
for (let i = 0; i < data.length; i += 4) {
const gray = Math.random() * 255
data[i] = gray // R
data[i+1] = gray // G
data[i+2] = gray // B
data[i+3] = 255 // Alpha
2026-01-15 20:10:19 +08:00
}
nCtx.putImageData(nImgData, 0, 0)
ctx.globalAlpha = amount
ctx.drawImage(noiseCanvas, 0, 0)
ctx.globalAlpha = 1.0
2026-01-15 20:10:19 +08:00
}
const stepDescription = computed(() => {
if (currentStep.value === 0) return '纯噪声 (Pure Noise)'
if (currentStep.value < 10) return '隐约出现轮廓...'
if (currentStep.value < 30) return '色彩开始浮现...'
if (currentStep.value < 50) return '细节逐渐清晰...'
return '生成完成 (Done)!'
2026-01-15 20:10:19 +08:00
})
const startDenoise = () => {
if (isProcessing.value) return
isProcessing.value = true
currentStep.value = 0
const animate = () => {
if (currentStep.value >= totalSteps) {
isProcessing.value = false
return
}
currentStep.value++
const noiseLevel = 1 - (currentStep.value / totalSteps)
// Non-linear ease out for better visual
const visualNoise = Math.pow(noiseLevel, 1.5)
const ctx = canvasRef.value.getContext('2d')
drawNoise(ctx, visualNoise)
animationFrame = requestAnimationFrame(animate)
2026-01-15 20:10:19 +08:00
}
animate()
}
const reset = () => {
if (animationFrame) cancelAnimationFrame(animationFrame)
isProcessing.value = false
currentStep.value = 0
const ctx = canvasRef.value.getContext('2d')
drawNoise(ctx, 1.0)
}
onMounted(() => {
reset()
})
</script>
<style scoped>
.diffusion-magic {
margin: 20px 0;
max-width: 400px; /* Compact width */
margin-left: auto;
margin-right: auto;
font-family: var(--vp-font-family-base);
2026-01-15 20:10:19 +08:00
}
.magic-frame {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
2026-01-15 20:10:19 +08:00
}
.canvas-wrapper {
position: relative;
width: 100%;
padding-bottom: 100%; /* Square aspect ratio */
background: #000;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
image-rendering: pixelated;
2026-01-15 20:10:19 +08:00
}
.status-overlay {
position: absolute;
bottom: 16px;
left: 16px;
right: 16px;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
padding: 8px 12px;
border-radius: 6px;
color: #fff;
opacity: 0;
transform: translateY(10px);
transition: all 0.3s ease;
pointer-events: none;
2026-01-15 20:10:19 +08:00
}
.status-overlay.visible {
opacity: 1;
transform: translateY(0);
}
2026-01-15 20:10:19 +08:00
.step-counter {
font-size: 10px;
opacity: 0.8;
text-transform: uppercase;
letter-spacing: 1px;
2026-01-15 20:10:19 +08:00
}
.step-desc {
font-size: 14px;
font-weight: 600;
margin-top: 2px;
2026-01-15 20:10:19 +08:00
}
.controls {
padding: 16px;
2026-01-15 20:10:19 +08:00
display: flex;
gap: 12px;
background: var(--vp-c-bg);
border-top: 1px solid var(--vp-c-divider);
2026-01-15 20:10:19 +08:00
}
button {
flex: 1;
border: none;
padding: 10px;
border-radius: 6px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
2026-01-15 20:10:19 +08:00
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
2026-01-15 20:10:19 +08:00
}
.magic-btn {
background: var(--vp-c-brand);
color: white;
2026-01-15 20:10:19 +08:00
}
.magic-btn:hover:not(:disabled) {
background: var(--vp-c-brand-dark);
2026-01-15 20:10:19 +08:00
}
.magic-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
2026-01-15 20:10:19 +08:00
}
.reset-btn {
background: var(--vp-c-bg-alt);
color: var(--vp-c-text-1);
flex: 0.4;
2026-01-15 20:10:19 +08:00
}
.reset-btn:hover:not(:disabled) {
background: var(--vp-c-bg-mute);
2026-01-15 20:10:19 +08:00
}
.info-bar {
margin-top: 12px;
font-size: 13px;
color: var(--vp-c-text-2);
display: flex;
gap: 8px;
line-height: 1.4;
padding: 0 8px;
2026-01-15 20:10:19 +08:00
}
</style>