# 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 [](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 [](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.*