prole/docs/IMAGE-RESOURCES.md

6.6 KiB

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:

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:

# 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)

    bg_path = get_resource_path('img/knoeLogoSepia.png')
    
  2. Application icon candidates (line ~406)

    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)

    bg_img = get_resource_path('img/knoeLogoSepia.png')
    

PyInstaller Configuration

The installer.spec file includes the entire img/ directory in the bundle:

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:

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:

python3 install.py --gui
# Check that logo appears in background

From built binary:

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:

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:

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():
    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:

# 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:

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