ObsiGate/IMAGE_RENDERING_GUIDE.md

381 lines
9.8 KiB
Markdown

# Obsidian Image Rendering - Implementation Guide
## Overview
ObsiGate now supports comprehensive Obsidian-compatible image rendering with intelligent multi-strategy path resolution. This document provides implementation details, testing guidance, and troubleshooting tips.
---
## Features Implemented
### ✅ Supported Image Syntaxes
1. **Standard Markdown with HTML attributes** (Obsidian-compatible)
```markdown
[<img width="180" height="60" src="path/to/image.svg"/>](https://example.com)
```
- Preserves `width` and `height` attributes
- Maintains clickable link wrapper
- Resolves `src` through the resolution pipeline
2. **Obsidian wiki-link embed with full path**
```markdown
![[06_Boite_a_Outils/6.2_Attachments/image.svg]]
```
- Full vault-relative path
- Resolves relative to vault root
3. **Obsidian wiki-link embed with filename only**
```markdown
![[image.svg]]
```
- Filename only, no path
- Resolved using attachment index built at startup
4. **Standard Markdown image**
```markdown
![alt text](path/to/image.png)
```
- Goes through multi-strategy resolution pipeline
- External URLs (http://, https://) are preserved unchanged
### ✅ Attachment Index
- **Startup scan**: Asynchronous scan of all vaults for image files
- **Supported formats**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`, `.bmp`, `.ico`
- **Index structure**: `{vault_name: {filename_lower: [absolute_path, ...]}}`
- **Resolution cache**: Results cached per vault + filename for performance
- **Logging**: Number of attachments indexed per vault logged at startup
### ✅ Multi-Strategy Path Resolution
Priority order (stops at first successful resolution):
| Priority | Strategy | Description |
|----------|----------|-------------|
| 1 | Absolute path | If path is absolute and file exists |
| 2 | Config attachments folder | Resolve relative to `VAULT_N_ATTACHMENTS_PATH` |
| 3 | Startup index (unique match) | Lookup filename in index; use if only one match |
| 4 | Same directory | Resolve relative to current markdown file's directory |
| 5 | Vault root relative | Resolve relative to vault root |
| 6 | Startup index (closest match) | If multiple matches, pick best path match |
| 7 | Fallback | Display styled placeholder with tooltip |
### ✅ Configuration Schema
New environment variables per vault:
```bash
# Required
VAULT_1_NAME=MyVault
VAULT_1_PATH=/vaults/MyVault
# Optional - Image configuration
VAULT_1_ATTACHMENTS_PATH=06_Boite_a_Outils/6.2_Attachments # Relative path
VAULT_1_SCAN_ATTACHMENTS=true # Default: true
```
### ✅ API Endpoints
**Serve Image**
```
GET /api/image/{vault_name}?path=relative/path/to/image.png
```
- Returns image with proper MIME type
- Supports all common image formats
- Path traversal protection
**Rescan Vault Attachments**
```
POST /api/attachments/rescan/{vault_name}
```
- Clears cache for the vault
- Re-scans vault directory for images
- Returns attachment count
**Attachment Statistics**
```
GET /api/attachments/stats?vault={vault_name}
```
- Returns attachment counts per vault
- Optional vault filter
### ✅ Frontend Styling
**Image Rendering**
- Images displayed with `max-width: 100%` for responsiveness
- Rounded corners and subtle shadow
- Hover effect on linked images
**Placeholder for Missing Images**
- Styled error box with dashed border
- Shows filename in monospace font
- Tooltip displays full path
- Red color scheme for visibility
---
## Testing Guide
### Test Case 1: Standard Markdown Image
**Markdown:**
```markdown
![My Image](images/test.png)
```
**Expected:**
- Image resolves via multi-strategy resolution
- Displays with proper styling
- Shows placeholder if not found
### Test Case 2: Wiki-link with Full Path
**Markdown:**
```markdown
![[Assets/Images/diagram.svg]]
```
**Expected:**
- Resolves relative to vault root
- SVG renders inline
- Maintains aspect ratio
### Test Case 3: Wiki-link with Filename Only
**Markdown:**
```markdown
![[logo.png]]
```
**Expected:**
- Searches attachment index
- Resolves to unique match if only one exists
- Shows placeholder if not found or ambiguous
### Test Case 4: HTML Image in Link
**Markdown:**
```markdown
[<img width="200" height="100" src="banner.jpg"/>](https://example.com)
```
**Expected:**
- Preserves width and height attributes
- Image is clickable and links to URL
- Resolves banner.jpg through resolution pipeline
### Test Case 5: External Image URL
**Markdown:**
```markdown
![External](https://example.com/image.png)
```
**Expected:**
- URL preserved unchanged
- Image loaded from external source
- No resolution attempted
### Test Case 6: Missing Image
**Markdown:**
```markdown
![[nonexistent.png]]
```
**Expected:**
- Displays: `[image not found: nonexistent.png]`
- Styled with red dashed border
- Tooltip shows full attempted path
### Test Case 7: Attachments Path Priority
**Setup:**
```bash
VAULT_1_ATTACHMENTS_PATH=Attachments
```
**Markdown:**
```markdown
![[photo.jpg]]
```
**Expected:**
- Checks `Attachments/photo.jpg` first (strategy 2)
- Falls back to index search if not found
- Logs resolution strategy used
---
## Troubleshooting
### Images Not Displaying
**Symptom:** Images show as placeholders even though files exist
**Checks:**
1. Verify attachment index was built at startup:
```bash
docker logs obsigate | grep "indexed.*attachments"
```
2. Check attachment stats:
```bash
curl http://localhost:2020/api/attachments/stats
```
3. Verify file permissions (Docker must be able to read images)
4. Check if image extension is supported (see `IMAGE_EXTENSIONS` in `attachment_indexer.py`)
**Solution:**
- Rescan attachments: `curl -X POST http://localhost:2020/api/attachments/rescan/VaultName`
- Check Docker volume mounts in `docker-compose.yml`
- Verify `VAULT_N_SCAN_ATTACHMENTS` is not set to `false`
### Attachment Scan Disabled
**Symptom:** Log shows "attachment scanning disabled"
**Cause:** `VAULT_N_SCAN_ATTACHMENTS=false` in environment
**Solution:**
- Remove the variable or set to `true`
- Restart container: `docker-compose restart obsigate`
### Wrong Image Resolved (Multiple Matches)
**Symptom:** Image with common filename resolves to wrong file
**Cause:** Multiple files with same name in different directories
**Solution:**
1. Use full path syntax: `![[folder/subfolder/image.png]]`
2. Configure `VAULT_N_ATTACHMENTS_PATH` to prioritize specific folder
3. Rename files to be unique
### Performance Issues with Large Vaults
**Symptom:** Slow startup or high memory usage
**Cause:** Large number of images being indexed
**Optimization:**
1. Disable scanning for vaults without images:
```bash
VAULT_N_SCAN_ATTACHMENTS=false
```
2. Use specific attachments folder to reduce scan scope:
```bash
VAULT_N_ATTACHMENTS_PATH=Images
```
3. Monitor memory usage:
```bash
docker stats obsigate
```
---
## Architecture
### Module Structure
```
backend/
├── attachment_indexer.py # Image scanning and indexing
├── image_processor.py # Markdown preprocessing
├── indexer.py # Vault indexing (updated)
└── main.py # API endpoints (updated)
```
### Data Flow
```
Startup:
├─ indexer.build_index()
│ ├─ Scans markdown files
│ └─ Calls attachment_indexer.build_attachment_index()
└─ attachment_indexer builds image index per vault
Rendering:
├─ User requests markdown file
├─ main._render_markdown() called
│ ├─ image_processor.preprocess_images()
│ │ ├─ Detects all 4 image syntaxes
│ │ ├─ Calls resolve_image_path() for each
│ │ └─ Transforms to /api/image/{vault}?path=...
│ └─ mistune renders to HTML
└─ Frontend displays with styled images
Image Serving:
├─ Browser requests /api/image/{vault}?path=...
├─ main.api_image() validates and resolves path
├─ Determines MIME type
└─ Returns image bytes with proper content-type
```
### Resolution Cache
- **Key:** `(vault_name, image_src)`
- **Value:** `Optional[Path]` (resolved absolute path or None)
- **Invalidation:** On vault rescan
- **Thread-safe:** Protected by `_attachment_lock`
---
## Performance Characteristics
| Operation | Complexity | Notes |
|-----------|-----------|-------|
| Attachment scan | O(n) | n = number of files in vault |
| Image resolution (cached) | O(1) | Hash table lookup |
| Image resolution (uncached) | O(k) | k = number of strategies (max 7) |
| Rescan vault | O(n) | Rebuilds index for one vault |
**Memory Usage:**
- ~100 bytes per indexed image (filename + path)
- Resolution cache grows with unique image references
- Cache cleared on rescan
---
## Future Enhancements
Potential improvements for future versions:
1. **Lazy loading**: Only index images when first accessed
2. **Image thumbnails**: Generate and cache thumbnails for large images
3. **Image metadata**: Extract and display EXIF data
4. **Batch rescan**: Rescan all vaults with one command
5. **File watcher**: Auto-rescan on filesystem changes
6. **Image optimization**: Compress images on-the-fly
7. **CDN support**: Serve images from external CDN
---
## Acceptance Criteria Status
- [x] All 4 image syntaxes render correctly in markdown preview
- [x] Startup scan completes without blocking UI (async/background)
- [x] Images with filename-only wiki-links resolve via index
- [x] Config `attachmentsPath` used as priority lookup
- [x] Unresolved images show visible placeholder, not broken icon
- [x] No regression on standard markdown image syntax `![]()`
- [x] Rescan command works and updates display without restart
---
## Version Information
**Implementation Date:** 2025
**ObsiGate Version:** 1.2.0 (pending)
**Python Version:** 3.11+
**Dependencies:** No new dependencies required
---
*For questions or issues, refer to the main README.md or open an issue on the project repository.*