199 lines
7.8 KiB
Markdown
199 lines
7.8 KiB
Markdown
# Whiteboard Image Limits — Komplettdokumentation
|
||
|
||
> **Letztes Update:** 2026-07-02 · **Status:** 6 JS-Patches aktiv, OOM-Problem identifiziert, URL-Referenzen als Lösung geplant
|
||
|
||
---
|
||
|
||
## 1. Problem
|
||
|
||
Das Nextcloud Whiteboard (Excalidraw) hat mehrere hartkodierte Limits für Bildimporte:
|
||
|
||
| Limit | Default | Effekt |
|
||
|---|---|---|
|
||
| Downscale auf max. Kantenlänge | **1440px** (Excalidraw) + **1440px** (Whiteboard-eigener `image-blob-reduce`) | Bilder schrumpfen auf Briefmarken-Größe |
|
||
| Dateigrößen-Check | **4 MB** (Excalidraw) + **4 MB** (Whiteboard) | Größere Bilder werden abgelehnt |
|
||
| RAM-Verbrauch (Base64 im JSON) | **~50 MB/Bild** (4000×3000px decoded) | Ab ~3 Bildern → Chrome OOM-Crash |
|
||
|
||
Alle diese Limits sind **doppelt** vorhanden: einmal im Excalidraw-Core (`percentages-*.chunk.mjs`), einmal in der Whiteboard-Bridge (`NcSelect-*.chunk.mjs`).
|
||
|
||
---
|
||
|
||
## 2. Architektur: Warum kein Docker-Neustart nötig ist
|
||
|
||
```
|
||
Docker Image (read-only) Docker Volume (read-write)
|
||
┌──────────────────────────┐ ┌─────────────────────────────┐
|
||
│ Nextcloud PHP │ │ /custom_apps/whiteboard/js/ │
|
||
│ Apache/Nginx Config │ │ ├─ NcSelect-CknHatsl.mjs │ ← Patches #3–#6
|
||
│ System-Libs │ │ ├─ percentages-*.mjs │ ← Patches #1–#2
|
||
│ │ │ └─ whiteboard-*-patch.js │ ← Scroll + Quality
|
||
│ (NIE angefasst) │ │ │
|
||
└──────────────────────────┘ └─────────────────────────────┘
|
||
▲
|
||
Persistiert über Restarts,
|
||
NICHT über App-Updates
|
||
```
|
||
|
||
**Der gesamte Bildverarbeitungs-Pipeline läuft im Browser:**
|
||
1. User dropped Bild → Whiteboard JS (`B0()` mit `image-blob-reduce`)
|
||
2. → Excalidraw JS (`initializeImage()`)
|
||
3. → Canvas-Rendering + Base64-Encode
|
||
4. → Upload JSON an Nextcloud (PHP macht **nichts** mit dem Bild)
|
||
|
||
Deshalb reicht ein **Browser-Cache-Refresh (Cachebuster)** — kein Container-Neustart.
|
||
|
||
---
|
||
|
||
## 3. Alle Patches (Stand 2026-07-02)
|
||
|
||
### 3.1 Scroll-Zoom (Miro-like)
|
||
**Datei:** `scripts/whiteboard-scroll-patch.js`
|
||
- Scrollen = Zoomen, Mittelklick-Drag = Pannen
|
||
- Registriert in `LoadViewerListener.php`, `WhiteboardDirectEditor.php`, `RecordingController.php`
|
||
|
||
### 3.2 Image Quality (Post-Processing)
|
||
**Datei:** `scripts/whiteboard-image-patch.js`
|
||
- Verhindert Qualitätsverlust durch Excalidraws internes `imageBlobReduce`-Postprocessing
|
||
|
||
### 3.3 `IM` und `TM` — Whiteboard-eigene Limits
|
||
**Datei:** `percentages-BXMCSKIN-D6x-nnv3.chunk.mjs`
|
||
|
||
```
|
||
...Cl=2,Ff=[1,2,3],dl=10,IM=1/0,TM=100*1024*1024,je=...
|
||
```
|
||
|
||
| Konstante | Vorher | Nachher | Bedeutung |
|
||
|---|---|---|---|
|
||
| `IM` | `1440` | `1/0` (=∞) | Max. Kantenlänge für `image-blob-reduce` |
|
||
| `TM` | `4*1024*1024` | `100*1024*1024` | Max. Dateigröße für Whiteboard-Preprocessing |
|
||
|
||
### 3.4 `maxImageSizeBytes` — Excalidraw-Fallback
|
||
**Datei:** `NcSelect-CknHatsl.chunk.mjs`
|
||
|
||
```
|
||
maxImageSizeBytes:F||9e15
|
||
```
|
||
|
||
| Vorher | Nachher |
|
||
|---|---|
|
||
| `F` (Nextcloud Config, `undefined` → 4MB Hardcoded) | `F\|\|9e15` (Fallback ≈ unendlich) |
|
||
|
||
### 3.5 `maxWidthOrHeight` + `imageOptions` — Excalidraw-Limits
|
||
**Datei:** `NcSelect-CknHatsl.chunk.mjs`
|
||
|
||
```
|
||
maxWidthOrHeight:8192,imageOptions:{maxWidthOrHeight:1/0,maxFileSizeBytes:1/0}
|
||
```
|
||
|
||
| Property | Vorher | Nachher |
|
||
|---|---|---|
|
||
| `maxWidthOrHeight` | `2048` (harter Reject) | `8192` |
|
||
| `imageOptions.maxWidthOrHeight` | `1440` (Downscale) | `1/0` (=∞) |
|
||
| `imageOptions.maxFileSizeBytes` | `4*1024*1024` (fileTooBig) | `1/0` (=∞) |
|
||
|
||
### 3.6 Infrastruktur
|
||
| Ebene | Einstellung | Wert |
|
||
|---|---|---|
|
||
| Nextcloud DB | `occ config whiteboard maxFileSize` | `100` (MB) |
|
||
| PHP | `upload_max_filesize` | `512M` |
|
||
| PHP | `post_max_size` | `512M` |
|
||
| PHP | `memory_limit` | `512M` |
|
||
| Nginx | `client_max_body_size` | `0` (unlimited) |
|
||
| Nginx | Whiteboard JS/CSS `Cache-Control` | `no-cache, no-store, must-revalidate` |
|
||
| Cache | `theming cachebuster` | **26** |
|
||
|
||
---
|
||
|
||
## 4. Warum das nicht reicht (OOM-Problem)
|
||
|
||
Mit `IM=1/0` und `imageOptions.maxWidthOrHeight=1/0` wird kein Downscaling mehr gemacht. Das bedeutet aber: **Jedes importierte Bild wird in voller原生auflösung als Base64 im Excalidraw-JSON gespeichert.**
|
||
|
||
**RAM-Verbrauch pro Bild:**
|
||
```
|
||
Breite × Höhe × 4 Bytes (RGBA decoded) + Base64-String (~33% Overhead)
|
||
|
||
5000 × 2000 × 4 = 40 MB decoded + 13 MB Base64 = ~53 MB RAM/Bild
|
||
4000 × 3000 × 4 = 48 MB decoded + 16 MB Base64 = ~64 MB RAM/Bild
|
||
```
|
||
|
||
**Bei 3 Bildern → ~160 MB + UI-Overhead → OOM-Crash nach ~2 Minuten.**
|
||
|
||
---
|
||
|
||
## 5. Excalidraw vs. Miro: Der fundamentale Unterschied
|
||
|
||
| | Excalidraw/Whiteboard | Miro |
|
||
|---|---|---|
|
||
| Bild-Speicherung | **Base64 inline im JSON** | URL-Referenz auf Server |
|
||
| Rendering | Canvas 2D, alle Bilder immer im RAM | WebGL + Tiled-Streaming |
|
||
| LOD | ❌ Nicht vorhanden | ✅ Nur sichtbare Kacheln geladen |
|
||
| RAM pro 4K-Bild | ~50 MB | ~0 MB (lazy, GPU-Textur) |
|
||
| 50× 4K-Bilder | 💥 unmöglich | ✅ problemlos |
|
||
|
||
Miro speichert nur **URLs** (100 Bytes statt 50 MB) und lädt Bilder erst beim Hinscrollen.
|
||
|
||
---
|
||
|
||
## 6. Geplanter Fix: URL-Referenzen statt Base64
|
||
|
||
### Ansatz
|
||
Statt das Bild als `data:image/png;base64,...` im JSON zu speichern, soll nur eine **URL** gespeichert werden. Der Browser lädt das Bild dann nur bei Bedarf.
|
||
|
||
```
|
||
// JETZT:
|
||
"files": { "abc123": { "dataURL": "data:image/png;base64,iVBORw0K...", ... } }
|
||
|
||
// ZIEL:
|
||
"files": { "abc123": { "url": "/remote.php/dav/files/.../image.png", ... } }
|
||
```
|
||
|
||
### Was geändert werden muss
|
||
|
||
| Komponente | Änderung | Aufwand |
|
||
|---|---|---|
|
||
| **Whiteboard Import** | Bild als Nextcloud-Datei speichern (WebDAV), nicht als Base64 | JS-Patch |
|
||
| **Excalidraw JSON-Format** | `dataURL` → `url` (relativer Pfad) | JS-Patch |
|
||
| **Excalidraw Rendering** | `Image()` vom Server laden statt aus Base64 | JS-Patch |
|
||
| **Nextcloud CORS** | `Access-Control-Allow-Origin` für Bild-URLs | Config |
|
||
| **File-ID Mapping** | `fileId` → Nextcloud-Dateipfad | JS-Patch |
|
||
|
||
### Risiken
|
||
- Alles JS-Patches auf dem Volume → gehen bei App-Update verloren
|
||
- Startup-Script zur Wiederherstellung nötig
|
||
- Langfristig: Upstream-PR an Excalidraw/Whiteboard
|
||
|
||
---
|
||
|
||
## 7. Direkter Link zum Excalidraw-Upstream
|
||
|
||
- **DEFAULT_IMAGE_OPTIONS** (Source of Truth): [`packages/common/src/constants.ts#L340-L342`](https://github.com/excalidraw/excalidraw/blob/master/packages/common/src/constants.ts)
|
||
- **LOD Feature Request** (seit 2022 offen): [Issue #5334](https://github.com/excalidraw/excalidraw/issues/5334)
|
||
- **Image Resolution Bug**: [Issue #5334](https://github.com/excalidraw/excalidraw/issues/5334)
|
||
|
||
---
|
||
|
||
## 8. Verifikation
|
||
|
||
Server-seitig verifizieren (nach jedem Patch):
|
||
```bash
|
||
# NcSelect
|
||
curl -s "https://nextcloud.niklashmotion.art/custom_apps/whiteboard/js/NcSelect-CknHatsl.chunk.mjs?v=26" \
|
||
| grep -oP 'maxImageSizeBytes[^,]+|imageOptions:\{[^}]+\}'
|
||
|
||
# Percentages
|
||
curl -s "https://nextcloud.niklashmotion.art/custom_apps/whiteboard/js/percentages-BXMCSKIN-D6x-nnv3.chunk.mjs?v=26" \
|
||
| grep -oP 'IM=[^,]+|TM=[^,]+'
|
||
```
|
||
|
||
Automatisiert: `scripts/verify-whiteboard-patches.sh` (im Skill-Verzeichnis).
|
||
|
||
---
|
||
|
||
## 9. Nächste Schritte
|
||
|
||
1. [x] Alle Limits beseitigt (6 JS-Patches + Infrastruktur)
|
||
2. [ ] **4096px-Cap setzen** (Quick-Fix gegen OOM) — 2 Minuten, 12+ Bilder stabil
|
||
3. [ ] **URL-Referenzen implementieren** (echte Lösung) — 1–2 Tage Entwicklungszeit
|
||
4. [ ] Startup-Script für Persistenz über App-Updates
|
||
5. [ ] Langfristig: Upstream-PR für konfigurierbare Limits + URL-Referenzen
|