9.8 KiB
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
-
Standard Markdown with HTML attributes (Obsidian-compatible)
[<img width="180" height="60" src="path/to/image.svg"/>](https://example.com)- Preserves
widthandheightattributes - Maintains clickable link wrapper
- Resolves
srcthrough the resolution pipeline
- Preserves
-
Obsidian wiki-link embed with full path
![[06_Boite_a_Outils/6.2_Attachments/image.svg]]- Full vault-relative path
- Resolves relative to vault root
-
Obsidian wiki-link embed with filename only
![[image.svg]]- Filename only, no path
- Resolved using attachment index built at startup
-
Standard Markdown image
- 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:
# 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:

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:
![[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:
![[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:
[<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:

Expected:
- URL preserved unchanged
- Image loaded from external source
- No resolution attempted
Test Case 6: Missing Image
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:
VAULT_1_ATTACHMENTS_PATH=Attachments
Markdown:
![[photo.jpg]]
Expected:
- Checks
Attachments/photo.jpgfirst (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:
-
Verify attachment index was built at startup:
docker logs obsigate | grep "indexed.*attachments" -
Check attachment stats:
curl http://localhost:2020/api/attachments/stats -
Verify file permissions (Docker must be able to read images)
-
Check if image extension is supported (see
IMAGE_EXTENSIONSinattachment_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_ATTACHMENTSis not set tofalse
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:
- Use full path syntax:
![[folder/subfolder/image.png]] - Configure
VAULT_N_ATTACHMENTS_PATHto prioritize specific folder - 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:
-
Disable scanning for vaults without images:
VAULT_N_SCAN_ATTACHMENTS=false -
Use specific attachments folder to reduce scan scope:
VAULT_N_ATTACHMENTS_PATH=Images -
Monitor memory usage:
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:
- Lazy loading: Only index images when first accessed
- Image thumbnails: Generate and cache thumbnails for large images
- Image metadata: Extract and display EXIF data
- Batch rescan: Rescan all vaults with one command
- File watcher: Auto-rescan on filesystem changes
- Image optimization: Compress images on-the-fly
- CDN support: Serve images from external CDN
Acceptance Criteria Status
- All 4 image syntaxes render correctly in markdown preview
- Startup scan completes without blocking UI (async/background)
- Images with filename-only wiki-links resolve via index
- Config
attachmentsPathused as priority lookup - Unresolved images show visible placeholder, not broken icon
- No regression on standard markdown image syntax
![]() - 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.