feat: update LemonSpace ADR to reflect WebWorker migration and caching strategy
- Revised the image processing pipeline description to highlight the transition to a Web-Worker-based architecture using OffscreenCanvas for rendering. - Updated caching strategy details to clarify how source images are cached and how parameter changes affect rendering. - Documented the current implementation status, including the integration of worker requests for preview and full rendering, and noted deviations from the original architectural vision. - Added sections on fallback mechanisms and the influence of the WebWorker migration guide on the current implementation.
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 1. Entscheidung
|
## 1. Entscheidung
|
||||||
|
|
||||||
Adjustment-Nodes arbeiten non-destruktiv über eine **edge-basierte Pipeline**. Die Edge-Kette im Canvas *ist* der Stack — kein separates Datenmodell. Die Bildverarbeitung läuft client-seitig über **eigene GLSL-Shader** mit einem minimalen WebGL-Wrapper. Keine externen Packages.
|
Adjustment-Nodes arbeiten non-destruktiv über eine **edge-basierte Pipeline**. Die Edge-Kette im Canvas *ist* der Stack — kein separates Datenmodell. Die Bildverarbeitung läuft client-seitig primär über eine **Web-Worker-Pipeline** mit OffscreenCanvas/2D-Rendering; ein WebGL-Pfad existiert ergänzend für kompatible Teilpfade. Keine externen Packages.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ Adjustment-Nodes arbeiten non-destruktiv über eine **edge-basierte Pipeline**.
|
|||||||
| Alternative | Warum verworfen |
|
| Alternative | Warum verworfen |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Expliziter Stack als Datenstruktur | Redundantes Datenmodell neben den Edges; Umsortieren erfordert Array-Manipulation statt Edge-Neuverbindung; widerspricht dem Canvas-Paradigma |
|
| Expliziter Stack als Datenstruktur | Redundantes Datenmodell neben den Edges; Umsortieren erfordert Array-Manipulation statt Edge-Neuverbindung; widerspricht dem Canvas-Paradigma |
|
||||||
| Canvas 2D API (CPU) | Zu langsam bei großen Bildern; blockiert Main Thread; kein Parallelismus |
|
| Reine Canvas-2D-Pipeline im Main Thread | Bei größeren Bildern und häufiger Interaktion zu teuer; blockiert UI-Thread |
|
||||||
| glfx.js | Letztes Update 9+ Jahre alt; kein ESM-Support; müsste geforkt und gepflegt werden |
|
| glfx.js | Letztes Update 9+ Jahre alt; kein ESM-Support; müsste geforkt und gepflegt werden |
|
||||||
| PixiJS | 200+ KB Framework-Overhead für einen Use Case (Filter auf Einzelbild); bringt Scene Graph, Sprites, Animation mit die wir nicht brauchen |
|
| PixiJS | 200+ KB Framework-Overhead für einen Use Case (Filter auf Einzelbild); bringt Scene Graph, Sprites, Animation mit die wir nicht brauchen |
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ Wenn ein Node seine Live-Preview rendern will, traversiert er die Edge-Kette **r
|
|||||||
1. Node fragt: "Wer ist mein Input?" → folge eingehender Edge
|
1. Node fragt: "Wer ist mein Input?" → folge eingehender Edge
|
||||||
2. Rekursiv weiter bis ein Node mit Bild-Daten erreicht wird (Bild-Node, KI-Bild-Node)
|
2. Rekursiv weiter bis ein Node mit Bild-Daten erreicht wird (Bild-Node, KI-Bild-Node)
|
||||||
3. Sammle alle Adjustment-Parameter in Reihenfolge ein
|
3. Sammle alle Adjustment-Parameter in Reihenfolge ein
|
||||||
4. Wende Shader-Pipeline auf das Quell-Bild an
|
4. Wende die aktive Bildpipeline (Worker-Renderpfad) auf das Quell-Bild an
|
||||||
5. Zeige Ergebnis als Preview
|
5. Zeige Ergebnis als Preview
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -92,27 +92,80 @@ function getSourceImage(nodeId: string, edges: Edge[], nodes: Node[]): string |
|
|||||||
|
|
||||||
### Caching-Strategie
|
### Caching-Strategie
|
||||||
|
|
||||||
Jeder Adjustment-Node cached sein Preview-Ergebnis als WebGL-Texture. Bei einer Parameteränderung wird nur ab diesem Node neu gerendert — Upstream-Ergebnisse bleiben gecached.
|
Die aktuelle Produktiv-Implementierung cached im Worker vor allem das Quellbild (`sourceUrl` → `ImageBitmap`). Bei reinen Parameteränderungen bleibt dieser Cache gültig, nur die Verarbeitungsschritte werden neu ausgeführt.
|
||||||
|
|
||||||
```
|
```
|
||||||
Bild → Kurven → Farbe → Detail
|
Bild-URL unverändert
|
||||||
↑
|
→ Quell-Bitmap bleibt im Worker-Cache
|
||||||
User ändert Farbe-Parameter
|
→ neue Parameter triggern nur Re-Render der Verarbeitung
|
||||||
→ Kurven-Cache bleibt gültig
|
|
||||||
→ Farbe + Detail werden neu gerendert
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Invalidierung: Wenn ein Upstream-Node seine Parameter ändert, werden alle Downstream-Caches invalidiert. Die Invalidierung propagiert über die Edge-Kette vorwärts.
|
Invalidierung erfolgt request-basiert: Neue Requests verdrängen veraltete Ergebnisse; stale Bitmaps werden verworfen und freigegeben.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementierungsstand (Stand: 31.03.2026)
|
||||||
|
|
||||||
|
### Produktiv umgesetzt
|
||||||
|
|
||||||
|
- WebWorker-Migration ist produktiv aktiv über `lib/image-pipeline/pipeline.worker.ts`, `lib/image-pipeline/pipeline-bridge.ts` und `lib/image-pipeline/index.ts`.
|
||||||
|
- Preview- und Full-Render laufen über Worker-Requests aus `hooks/use-pipeline-preview.ts` und `components/canvas/nodes/render-node.tsx`.
|
||||||
|
- Die Worker-Pipeline rendert aktuell über `OffscreenCanvas` + 2D-Kontext (inkl. Kurven-LUT, Canvas-Filter und nachgelagerte Pixel-Adjustments), Histogramm-Berechnung erfolgt im Worker.
|
||||||
|
- Render-Node nutzt `bridge.renderFull(...)` und liefert aktuell einen lokalen Download-Export (kein Convex-Upload in diesem Pfad).
|
||||||
|
- Lifecycle-Cleanup ist angebunden: `disposePipelineBridge()` wird in `components/canvas/canvas.tsx` beim Unmount ausgeführt.
|
||||||
|
|
||||||
|
### Abweichungen zur ursprünglichen ADR-Intention / Zielvision aus dem Guide
|
||||||
|
|
||||||
|
- Die ursprünglich beschriebene, primär shader-zentrierte WebGL-Architektur ist nicht 1:1 der produktive Standardpfad.
|
||||||
|
- Statt einer reinen „WebGL-im-Worker“-Ausführung nutzt die aktuelle Worker-Pipeline einen OffscreenCanvas/2D-Rendering-Pfad mit ergänzenden ImageData-Operationen.
|
||||||
|
- Der Guide skizziert konzeptionell eine zentrale Worker-Instanz; die aktuelle Bridge betreibt getrennte Worker-Kanäle für Preview und Full-Render.
|
||||||
|
- Der im ADR beschriebene Render-Node-Flow mit Convex-Storage-Materialisierung ist in der aktuellen UI nicht der Default-Exportpfad.
|
||||||
|
|
||||||
|
### Fallback- und Recovery-Mechanismen
|
||||||
|
|
||||||
|
- `usePipelinePreview` versucht Worker-Rendering zuerst und schaltet bei Fehlern auf Main-Thread-Fallback (`canvas-render.ts`) um.
|
||||||
|
- Während des Fallback-Betriebs werden Worker-Recovery-Retries zeit- und zählbasiert angestoßen; bei erfolgreicher Probe wird zurück auf Worker gewechselt.
|
||||||
|
- Stale Ergebnisse werden über Request-Sequenzierung verworfen; betroffene `ImageBitmap`s werden aktiv freigegeben.
|
||||||
|
- Preview-Metriken erfassen u. a. Fallback-Switches und Recoveries über `lib/image-pipeline/preview-metrics.ts`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Einfluss des WebWorker-Migration-Guides
|
||||||
|
|
||||||
|
### Übernommene Konzepte
|
||||||
|
|
||||||
|
- Entkopplung von UI und Bildpipeline über Worker + Bridge (`pipeline.worker.ts` / `pipeline-bridge.ts`).
|
||||||
|
- Request-basierte Worker-API mit korrelierbarer Request-ID.
|
||||||
|
- Rückgabe von Preview-Bitmaps und Histogramm-Daten über Worker-Messages.
|
||||||
|
- Singleton-Verwaltung der Bridge (`lib/image-pipeline/index.ts`) und Cleanup im Canvas-Lifecycle.
|
||||||
|
|
||||||
|
### Bewusst abgewandelte Punkte
|
||||||
|
|
||||||
|
- Reine WebGL-im-Worker-Zielarchitektur wurde zugunsten eines OffscreenCanvas/2D-Pfads umgesetzt.
|
||||||
|
- Getrennte Worker für Preview und Full-Render statt nur eines universellen Workers.
|
||||||
|
- Render-Node-Integration ist aktuell auf clientseitigen Export fokussiert, nicht auf serverseitige Persistierung als Standardfluss.
|
||||||
|
|
||||||
|
### Offene Punkte / Follow-ups
|
||||||
|
|
||||||
|
- Evaluierung, ob und wo ein stärkerer WebGL-Worker-Pfad wieder sinnvoll ist (insbesondere bei komplexen Anpassungen oder sehr großen Bildern).
|
||||||
|
- Fortlaufende Beobachtung der Worker/Fallback-Quote anhand `preview-metrics`.
|
||||||
|
- Falls persistierte Render-Artefakte wieder Produktziel werden, separaten Upload-/Persistenzpfad explizit ergänzen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. WebGL-Wrapper
|
## 4. WebGL-Wrapper
|
||||||
|
|
||||||
|
> **Hinweis zum Ist-Stand:** Dieser Abschnitt dokumentiert weiterhin die WebGL-Ziel-/Referenzarchitektur der Pipeline. Produktiv läuft die Preview-/Render-Ausführung derzeit primär im Worker über OffscreenCanvas/2D.
|
||||||
|
|
||||||
### Dateien
|
### Dateien
|
||||||
|
|
||||||
```
|
```
|
||||||
lib/
|
lib/
|
||||||
image-pipeline/
|
image-pipeline/
|
||||||
|
index.ts ← Singleton-Lifecycle für PipelineBridge
|
||||||
|
pipeline-bridge.ts ← Main-Thread-Bridge (Worker API)
|
||||||
|
pipeline.worker.ts ← Worker-Pipeline (OffscreenCanvas/2D)
|
||||||
|
preview-metrics.ts ← Laufzeit-Metriken (Worker/Fallback)
|
||||||
gl-wrapper.ts ← WebGL-Context, Texture-Management, Shader-Kompilierung
|
gl-wrapper.ts ← WebGL-Context, Texture-Management, Shader-Kompilierung
|
||||||
pipeline.ts ← Pipeline-Traversierung, Cache, Orchestrierung
|
pipeline.ts ← Pipeline-Traversierung, Cache, Orchestrierung
|
||||||
shaders/
|
shaders/
|
||||||
@@ -320,7 +373,7 @@ data: {
|
|||||||
preset: string | null, // "warm" | "cool" | "vintage" | "desaturate" | null
|
preset: string | null, // "warm" | "cool" | "vintage" | "desaturate" | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// type: "light"
|
// type: "light-adjust"
|
||||||
data: {
|
data: {
|
||||||
brightness: number, // -100 bis +100, default 0
|
brightness: number, // -100 bis +100, default 0
|
||||||
contrast: number, // -100 bis +100, default 0
|
contrast: number, // -100 bis +100, default 0
|
||||||
@@ -337,7 +390,7 @@ data: {
|
|||||||
preset: string | null, // "hdr" | "lowkey" | "highkey" | "flat" | null
|
preset: string | null, // "hdr" | "lowkey" | "highkey" | "flat" | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// type: "detail"
|
// type: "detail-adjust"
|
||||||
data: {
|
data: {
|
||||||
sharpen: {
|
sharpen: {
|
||||||
amount: number, // 0–500, default 0 (Prozent)
|
amount: number, // 0–500, default 0 (Prozent)
|
||||||
@@ -371,48 +424,43 @@ data: {
|
|||||||
|
|
||||||
### Schema-Ergänzung (convex/schema.ts)
|
### Schema-Ergänzung (convex/schema.ts)
|
||||||
|
|
||||||
Die `nodes`-Tabelle braucht keine Schema-Änderung — das polymorphe `data`-Feld (`v.any()`) trägt die Parameter bereits. Neue `type`-Werte (`curves`, `color-adjust`, `light`, `detail`, `render`) werden in die bestehende Union aufgenommen.
|
Die `nodes`-Tabelle braucht keine Schema-Änderung — das polymorphe `data`-Feld (`v.any()`) trägt die Parameter bereits. Neue `type`-Werte (`curves`, `color-adjust`, `light-adjust`, `detail-adjust`, `render`) werden in die bestehende Union aufgenommen.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Render-Node
|
## 6. Render-Node
|
||||||
|
|
||||||
Der Render-Node ist der einzige Node in der Bildbearbeitungs-Kategorie, der serverseitig arbeitet.
|
Der Render-Node rendert aktuell client-seitig über die Worker-Bridge und bietet primär einen lokalen Datei-Export.
|
||||||
|
|
||||||
### Flow
|
### Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
1. User klickt "Render" am Render-Node
|
1. User startet Export im Render-Node
|
||||||
2. Client: collectPipeline() → vollständiger Adjustment-Stack
|
2. Client: `collectPipeline()` + `getSourceImage()`
|
||||||
3. Client: Führt Pipeline client-seitig aus (WebGL)
|
3. Client: `PipelineBridge.renderFull(...)` im Worker (OffscreenCanvas)
|
||||||
4. Client: glWrapper.toBlob() → Ergebnis als Blob
|
4. Worker: `convertToBlob(...)` und Rückgabe von Blob + Output-Dimensionen
|
||||||
5. Client: Upload Blob → Convex Storage (wie Bild-Upload)
|
5. Client: Download-Link (`URL.createObjectURL`) wird erzeugt und ausgelöst
|
||||||
6. Client: updateData({ storageId, lastRenderedAt }) → Convex Mutation
|
|
||||||
7. Convex Query: storageId → url auflösen (wie bei Bild-/KI-Bild-Node)
|
|
||||||
8. Render-Node zeigt finales Bild
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Warum client-seitig rendern statt server-seitig?**
|
**Warum client-seitig rendern?**
|
||||||
|
|
||||||
- WebGL-Pipeline existiert bereits im Client (Preview)
|
- Pipeline-Logik und Vorschaupfad sind bereits im Client vorhanden
|
||||||
- Kein Server-Roundtrip für die Bildverarbeitung nötig
|
- Kein obligatorischer Server-Roundtrip für die Bildberechnung
|
||||||
- Server müsste die gleiche Pipeline in jimp/sharp nachbauen (Aufwand, Parität-Risiko)
|
- Export ist direkt als Datei verfügbar
|
||||||
- Nur der Upload des fertigen Blobs geht über Convex Storage
|
|
||||||
|
|
||||||
**Render-Status am Node:**
|
**Render-Status am Node:**
|
||||||
|
|
||||||
```
|
```
|
||||||
idle → rendering → uploading → done | error
|
idle → rendering → done | error
|
||||||
```
|
```
|
||||||
|
|
||||||
- `rendering`: Client führt Pipeline aus (schnell, < 1s)
|
- `rendering`: Worker rendert und erzeugt Blob
|
||||||
- `uploading`: Blob wird zu Convex Storage hochgeladen
|
- `done`: Download wurde angestoßen
|
||||||
- `done`: storageId gesetzt, Bild sichtbar
|
- `error`: Worker-Render oder Download-Erzeugung fehlgeschlagen
|
||||||
- `error`: Pipeline oder Upload fehlgeschlagen
|
|
||||||
|
|
||||||
### Re-Render
|
### Re-Render
|
||||||
|
|
||||||
Wenn Upstream-Adjustments geändert werden, zeigt der Render-Node einen visuellen Hinweis: "Out of date — Re-render". Der Render-Node tracked einen `pipelineHash` (Hash über alle Upstream-Parameter) und vergleicht ihn mit dem Hash zum Zeitpunkt des letzten Renders.
|
Pipeline-Hashing bleibt als Basis für Änderungsdetektion relevant (`hashPipeline(...)`). Ein persistierter „Out of date — Re-render“-Flow mit serverseitigem Artefakt ist in der aktuellen Export-Implementierung nicht der zentrale Pfad.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -437,17 +485,21 @@ components/canvas/nodes/
|
|||||||
```tsx
|
```tsx
|
||||||
// Pseudocode
|
// Pseudocode
|
||||||
function AdjustmentPreview({ nodeId }: { nodeId: string }) {
|
function AdjustmentPreview({ nodeId }: { nodeId: string }) {
|
||||||
const nodes = useNodes();
|
const { nodes, edges } = useCanvasGraph();
|
||||||
const edges = useEdges();
|
const nodeWidth = useNodeWidth(nodeId);
|
||||||
|
|
||||||
// Pipeline rückwärts traversieren
|
// Pipeline rückwärts traversieren
|
||||||
const sourceUrl = getSourceImage(nodeId, edges, nodes);
|
const sourceUrl = getSourceImage(nodeId, edges, nodes);
|
||||||
const pipeline = collectPipeline(nodeId, edges, nodes);
|
const pipeline = collectPipeline(nodeId, edges, nodes);
|
||||||
|
|
||||||
// WebGL-Pipeline ausführen (gecached)
|
// Worker-Preview (mit Main-Thread-Fallback)
|
||||||
const previewUrl = usePipelinePreview(sourceUrl, pipeline);
|
const { previewRef, histogram, isRendering } = usePipelinePreview(
|
||||||
|
sourceUrl,
|
||||||
|
pipeline,
|
||||||
|
nodeWidth,
|
||||||
|
);
|
||||||
|
|
||||||
return <img src={previewUrl} className="w-full h-auto rounded" />;
|
return <canvas ref={previewRef} className="w-full h-auto rounded" />;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -503,8 +555,8 @@ adjustmentPresets: defineTable({
|
|||||||
nodeType: v.union( // Für welchen Adjustment-Typ
|
nodeType: v.union( // Für welchen Adjustment-Typ
|
||||||
v.literal("curves"),
|
v.literal("curves"),
|
||||||
v.literal("color-adjust"),
|
v.literal("color-adjust"),
|
||||||
v.literal("light"),
|
v.literal("light-adjust"),
|
||||||
v.literal("detail"),
|
v.literal("detail-adjust"),
|
||||||
),
|
),
|
||||||
params: v.any(), // Die gespeicherten Parameter
|
params: v.any(), // Die gespeicherten Parameter
|
||||||
createdAt: v.number(),
|
createdAt: v.number(),
|
||||||
@@ -558,8 +610,8 @@ Neue Sidebar-Kategorie **"Bildbearbeitung"** mit fünf Einträgen:
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Kurven | `curves` | `TrendingUp` (lucide) |
|
| Kurven | `curves` | `TrendingUp` (lucide) |
|
||||||
| Farbe | `color-adjust` | `Palette` (lucide) |
|
| Farbe | `color-adjust` | `Palette` (lucide) |
|
||||||
| Licht | `light` | `Sun` (lucide) |
|
| Licht | `light-adjust` | `Sun` (lucide) |
|
||||||
| Detail | `detail` | `Focus` (lucide) |
|
| Detail | `detail-adjust` | `Focus` (lucide) |
|
||||||
| Render | `render` | `ImageDown` (lucide) |
|
| Render | `render` | `ImageDown` (lucide) |
|
||||||
|
|
||||||
Drag-Data: `application/lemonspace-node-type` mit dem jeweiligen `type`-String (konsistent mit bestehenden Nodes).
|
Drag-Data: `application/lemonspace-node-type` mit dem jeweiligen `type`-String (konsistent mit bestehenden Nodes).
|
||||||
@@ -592,17 +644,19 @@ Die Validierung läuft in `canvas.tsx` bei `onConnect` — ungültige Verbindung
|
|||||||
```
|
```
|
||||||
lib/
|
lib/
|
||||||
image-pipeline/
|
image-pipeline/
|
||||||
gl-wrapper.ts ← WebGL-Context, Texture, Shader-Kompilierung
|
index.ts ← get/dispose PipelineBridge (Singleton)
|
||||||
pipeline.ts ← Pipeline-Traversierung, Cache, Orchestrierung
|
pipeline-bridge.ts ← Main-Thread-Bridge für Worker-Aufrufe
|
||||||
presets.ts ← Built-in Presets für alle Adjustment-Typen
|
pipeline.worker.ts ← Worker-Pipeline (Preview + Full Render)
|
||||||
curve-interpolation.ts ← Monotone kubische Interpolation → LUT
|
pipeline.ts ← Edge-Traversierung + Pipeline-Hashing
|
||||||
shaders/
|
canvas-render.ts ← Main-Thread-Fallback-Rendering
|
||||||
passthrough.vert ← Gemeinsamer Vertex-Shader
|
webgl-render.ts ← Optionaler WebGL-Renderpfad
|
||||||
curves.frag ← Tonwert-Kurven + Levels
|
gl-wrapper.ts ← WebGL-Helfer für den optionalen Pfad
|
||||||
color-adjust.frag ← HSL, Color Balance, Temperature, Vibrance
|
curve-lut.ts ← Kurven-LUT-Berechnung + Anwendung
|
||||||
light.frag ← Brightness, Contrast, Exposure, H/S, Vignette
|
adjustments.ts ← Aggregation/Anwendung von Adjustment-Parametern
|
||||||
detail.frag ← Sharpen, Clarity, Denoise, Grain
|
histogram.ts ← Histogramm-Berechnung und Datentransferformate
|
||||||
blur.frag ← Hilfshader für Unsharp Mask + Denoise
|
render-size.ts ← Zielauflösung/Skalierungslogik
|
||||||
|
preview-metrics.ts ← Worker/Fallback-Metriken
|
||||||
|
presets.ts ← Built-in Presets für Adjustment-Typen
|
||||||
|
|
||||||
components/canvas/nodes/
|
components/canvas/nodes/
|
||||||
adjustment-preview.tsx ← Shared Preview für alle Adjustment-Nodes
|
adjustment-preview.tsx ← Shared Preview für alle Adjustment-Nodes
|
||||||
@@ -610,10 +664,10 @@ components/canvas/nodes/
|
|||||||
color-adjust-node.tsx ← HSL-Slider, Color Balance, Temperature
|
color-adjust-node.tsx ← HSL-Slider, Color Balance, Temperature
|
||||||
light-node.tsx ← Slider-Batterie für Licht-Parameter
|
light-node.tsx ← Slider-Batterie für Licht-Parameter
|
||||||
detail-node.tsx ← Sharpen/Clarity/Denoise/Grain Slider
|
detail-node.tsx ← Sharpen/Clarity/Denoise/Grain Slider
|
||||||
render-node.tsx ← Render-Button, Format-Auswahl, finales Bild
|
render-node.tsx ← Download-Export über `bridge.renderFull(...)`
|
||||||
|
|
||||||
hooks/
|
hooks/
|
||||||
use-pipeline-preview.ts ← Hook: Pipeline ausführen → Preview-URL
|
use-pipeline-preview.ts ← Hook: Worker-Preview mit Fallback + Recovery
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -623,12 +677,12 @@ hooks/
|
|||||||
| Thema | Status | Notizen |
|
| Thema | Status | Notizen |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| User-Presets persistieren | ✅ | Convex-Tabelle `adjustmentPresets` mit userId-Index. Kein Local Storage — Presets überleben Cache-Clear und sind geräteübergreifend verfügbar. |
|
| User-Presets persistieren | ✅ | Convex-Tabelle `adjustmentPresets` mit userId-Index. Kein Local Storage — Presets überleben Cache-Clear und sind geräteübergreifend verfügbar. |
|
||||||
| Histogram-UI im Kurven-Node | ✅ | Histogram wird aus dem Pipeline-Output berechnet — zeigt die Tonwertverteilung *nach* allen vorhergehenden Adjustments. `gl.readPixels()` auf den aktuellen Framebuffer, dann Häufigkeitsverteilung über R/G/B/Luminanz in JS berechnen. Downsampled auf Preview-Auflösung (nicht Originalbild), damit der Readback schnell bleibt. |
|
| Histogram-UI im Kurven-Node | ✅ | Histogram wird aus dem Pipeline-Output berechnet — zeigt die Tonwertverteilung *nach* allen vorhergehenden Adjustments. Im Worker wird dazu das finale ImageData gelesen und in Histogram-Bins aggregiert. |
|
||||||
| Preview-Auflösung dynamisch | ✅ | Proportional zur Node-Breite × `devicePixelRatio`, gecapped bei 1024px. Adjustment-Nodes haben eine Mindestbreite von 240px. |
|
| Preview-Auflösung dynamisch | ✅ | Proportional zur Node-Breite × `devicePixelRatio`, gecapped bei 1024px. Adjustment-Nodes haben eine Mindestbreite von 240px. |
|
||||||
| Adjustment-Node Resize | ✅ | Resizeable (wie alle Nodes via base-node-wrapper), mit `minWidth: 240`. Preview skaliert mit, Slider-Layout bleibt stabil. |
|
| Adjustment-Node Resize | ✅ | Resizeable (wie alle Nodes via base-node-wrapper), mit `minWidth: 240`. Preview skaliert mit, Slider-Layout bleibt stabil. |
|
||||||
| Render-Node: Client- vs. Server-seitig | ✅ | Client-seitig (WebGL → Blob → Upload). Server müsste Pipeline duplizieren. |
|
| Render-Node: Client- vs. Server-seitig | ✅ | Client-seitig über Worker-Bridge; aktueller Pfad ist Download-Export (`renderFull`) statt serverseitiger Persistierung. |
|
||||||
| WebGL-Fallback | ⏳ | Canvas 2D als Fallback? Praktisch alle modernen Browser haben WebGL2. Aufwand vs. Nutzen. |
|
| Worker-Fallback/Recovery | ✅ | Bei Worker-Fehlern Fallback auf Main Thread; periodische Recovery-Versuche zurück in den Worker-Pfad. |
|
||||||
| Detail-Node: Multi-Pass-Architektur | ⏳ | Framebuffer-Ping-Pong für Unsharp Mask + Denoise. Exakte Implementierung TBD. |
|
| Reine WebGL-im-Worker-Architektur | ⏳ | Guide-Zielbild; aktuell produktiv ist ein OffscreenCanvas/2D-Pfad im Worker. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -637,12 +691,12 @@ hooks/
|
|||||||
| Aspekt | Wert |
|
| Aspekt | Wert |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Credit-Kosten Adjustments | 0 Cr (client-seitig) |
|
| Credit-Kosten Adjustments | 0 Cr (client-seitig) |
|
||||||
| Credit-Kosten Render | 0 Cr (kein KI-API-Call, nur Convex Storage) |
|
| Credit-Kosten Render | 0 Cr (kein KI-API-Call; aktueller Exportpfad ist client-seitig) |
|
||||||
| Preview-Latenz (Ziel) | < 16ms (60fps bei Slider-Drag) |
|
| Preview-Latenz (Ziel) | < 16ms (60fps bei Slider-Drag) |
|
||||||
| Preview-Auflösung | Dynamisch: nodeWidth × devicePixelRatio, max 1024px |
|
| Preview-Auflösung | Dynamisch: nodeWidth × devicePixelRatio, max 1024px |
|
||||||
| Mindestbreite Adjustment-Nodes | 240px |
|
| Mindestbreite Adjustment-Nodes | 240px |
|
||||||
| Max. Bild-Auflösung Render | Original-Auflösung |
|
| Max. Bild-Auflösung Render | Original-Auflösung |
|
||||||
| WebGL-Version | WebGL2 (ES 3.0 Shaders) |
|
| Primärer Renderpfad | Web Worker + OffscreenCanvas/2D |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user