# Image Resource Management ## Overview The Knoe Database Installer includes multiple image assets that need to work both when running from source and when packaged as a standalone binary. This document explains how image resources are managed. ## Image Assets The following images are included in the installer: ### Primary Assets - **knoeIcon.png** (1,494,782 bytes) - Application icon - **knoeLogo.png** (2,190,703 bytes) - Main Knoe logo - **knoeLogoSepia.png** (2,657,956 bytes) - Sepia-toned background logo - **knoeLogoBlueprint.png** (3,107,147 bytes) - Blueprint style logo - **knoeIconblueprint.png** (1,570,109 bytes) - Blueprint style icon ### Supplementary Assets - **knoe-type.gif** (599 bytes) - Small typing animation ## Resource Path Resolution ### The Challenge When packaging Python applications with PyInstaller, resource files need to be found in two different scenarios: 1. **Development**: Running from source, files are in `PROJECT_ROOT/img/` 2. **Production**: Running from bundle, files are in PyInstaller's temporary extraction directory ### The Solution The `get_resource_path()` function automatically resolves paths correctly in both scenarios: ```python def get_resource_path(relative_path): """Get absolute path to resource, works for dev and for PyInstaller.""" try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = Path(sys._MEIPASS) except AttributeError: # Running from source base_path = PROJECT_ROOT return base_path / relative_path ``` ### Usage All image loading code uses this function: ```python # Instead of: bg_path = Path('img/knoeLogoSepia.png') # Use: bg_path = get_resource_path('img/knoeLogoSepia.png') ``` ## Implementation Details ### Modified Code Locations The following locations in `install.py` were updated to use `get_resource_path()`: 1. **Background logo loading** (line ~191) ```python bg_path = get_resource_path('img/knoeLogoSepia.png') ``` 2. **Application icon candidates** (line ~406) ```python img_candidates = [ get_resource_path('img/knoeIcon.png'), get_resource_path('img/knoe-type.png'), get_resource_path('img/knoe-type.gif'), get_resource_path('img/Knoe.png'), get_resource_path('img/knoeLogoSepia.png'), ] ``` 3. **DMG background image** (line ~3820) ```python bg_img = get_resource_path('img/knoeLogoSepia.png') ``` ### PyInstaller Configuration The `installer.spec` file includes the entire `img/` directory in the bundle: ```python datas = [ ('installer', 'installer'), ('conf', 'conf'), ('etc', 'etc'), ('img', 'img'), # All images included ('docs', 'docs'), ] ``` This ensures all images are: 1. Copied into the PyInstaller bundle 2. Extracted to the temporary directory at runtime 3. Accessible via `sys._MEIPASS / 'img' / filename` ## Testing ### Verify Resource Paths Run the test script: ```bash python3 test_resource_paths.py ``` This tests: - Path resolution from source - Path resolution simulating PyInstaller environment - Existence of all image files Expected output: ``` ✓ App icon ✓ Background logo (sepia) ✓ Main logo ✓ Blueprint logo ✓ Blueprint icon ✓ All tests passed! Images will be correctly included in PyInstaller build. ``` ### Manual Testing From source: ```bash python3 install.py --gui # Check that logo appears in background ``` From built binary: ```bash make package ./dist/Knoe\ Installer.app/Contents/MacOS/knoe-installer --gui # Check that logo appears in background ``` ## Icon Formats ### PNG (Source) - Format: PNG with transparency - Resolution: 1024x1024 recommended - Location: `img/knoeIcon.png` ### ICNS (macOS Bundle) - Generated by build system from PNG - Contains multiple resolutions (16x16 through 1024x1024) - Location: `build/knoe.icns` (intermediate), embedded in `.app` bundle - Generated by: `make build/knoe.icns` The build system automatically converts PNG to ICNS using macOS tools: ```bash sips -z 512 512 img/knoeIcon.png --out build/icon.iconset/icon_512x512.png iconutil -c icns build/icon.iconset -o build/knoe.icns ``` ## Background Image Usage The GUI installer uses `knoeLogoSepia.png` as a subtle background: 1. **Loading**: Image loaded via PIL (Pillow) 2. **Processing**: - Converted to RGBA - Blended with white background at 15% opacity - Creates subtle watermark effect 3. **Rendering**: - Scaled to fill canvas - Maintains aspect ratio - Centered on canvas Code snippet: ```python bg_path = get_resource_path('img/knoeLogoSepia.png') if bg_path.exists(): original = Image.open(str(bg_path)).convert('RGBA') white_bg = Image.new('RGBA', original.size, (255, 255, 255, 255)) self._bg_pil = Image.blend(white_bg, original, 0.15) ``` ## Adding New Images To add new image resources: 1. **Add image file** to `img/` directory 2. **Update code** to use `get_resource_path()`: ```python new_img = get_resource_path('img/new_image.png') ``` 3. **No spec changes needed** - entire `img/` directory is already included 4. **Test** with `python3 test_resource_paths.py` ## Troubleshooting ### Image Not Found in Built Binary **Symptom:** Image loads from source but not from built `.app` **Cause:** Path hardcoded instead of using `get_resource_path()` **Solution:** ```python # Wrong: img_path = PROJECT_ROOT / 'img' / 'logo.png' # Correct: img_path = get_resource_path('img/logo.png') ``` ### Image Not Showing in GUI **Cause:** File doesn't exist or wrong path **Solution:** Check with test script: ```bash python3 test_resource_paths.py ``` ### Build Size Too Large **Cause:** Large images included in bundle **Solution:** - Optimize PNG files: `pngcrush`, `optipng`, etc. - Consider JPEG for photos (PNG for logos/icons) - Remove unused images from `img/` directory ## Performance Considerations ### Startup Time PyInstaller extracts bundled resources to a temporary directory on each launch: - **Small images** (< 100 KB): Negligible impact - **Large images** (> 1 MB): 100-200ms extraction time - **Total time**: ~1-2 seconds for all resources ### Memory Usage Images are loaded into memory when displayed: - **knoeLogoSepia.png**: ~2.6 MB on disk, ~10 MB in memory (RGBA) - **Scaled versions**: Additional memory for display size ### Optimization For production, consider: 1. Compress images before including 2. Use lazy loading (load only when needed) 3. Cache scaled versions instead of re-scaling ## References - [PyInstaller Data Files](https://pyinstaller.org/en/stable/spec-files.html#adding-data-files) - [Pillow (PIL) Documentation](https://pillow.readthedocs.io/) - [macOS Icon Creation](https://developer.apple.com/design/human-interface-guidelines/app-icons)