Files
spectra/docs/display-orientation.md

104 lines
3.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Display Orientation
## Purpose
Allow the user to configure the physical orientation of the Inky Impression
display so that images are displayed upright regardless of how the panel is
mounted. The orientation setting also drives the Unsplash orientation filter so
fetched photos match the display's effective aspect ratio (portrait vs.
landscape).
## Configuration
A new `display.orientation` key (integer, degrees clockwise) in
`config.yaml` / `DEFAULT_CONFIG`:
```yaml
display:
orientation: 0 # 0, 90, 180, 270
```
| Value | Unsplash filter | Effective aspect |
|-------|----------------|------------------|
| 0 | landscape | width > height |
| 90 | portrait | height > width |
| 180 | landscape | width > height |
| 270 | portrait | height > width |
The old `unsplash.orientation` config key is **no longer used** by
`refresh()` — orientation is now derived from `display.orientation` +
`display.resolution`.
## Image Processing Pipeline
1. **Fetch** raw photo from Unsplash (with orientation filter already matching
the effective aspect ratio — see below).
2. **`process_image()`** in `InkyDisplay`:
- Compute effective width/height by swapping physical dimensions when
orientation is 90° or 270°.
- Crop to effective aspect ratio.
- Resize to effective dimensions.
- Rotate by `-orientation` degrees (so the result matches the physical
resolution of the display panel).
3. **`show()`** sends the processed (and rotated) image to `inky.set_image()`.
No additional transformation is needed — the panel hardware always expects
its native `width × height` pixel grid.
## Unsplash Filter Derivation
In `cli.py:refresh()`, after reading `display.orientation` and
`display.resolution`:
1. If orientation is 90 or 270, swap width ↔ height to get effective
dimensions.
2. If effective height > effective width → request `"portrait"`.
3. If effective width > effective height → request `"landscape"`.
4. If roughly equal → request `"squarish"`.
This means the Unsplash API is sent `orientation=portrait` when the display is
mounted vertically, which returns taller photos that need less cropping.
## Hot-Reload
`_reload_config()` in `cli.py` detects changes to
`display.orientation` and updates `display.orientation` live. The next
`process_image()` call uses the new orientation. No display loop restart is
needed.
## Library Caching
`_save_to_library()` passes the **effective** width/height to
`save_unsplash_image()` (swapped when orientation is 90/270). The stored image
is processed to the effective dimensions at orientation=0 (no rotation). This
keeps library images in their "natural" viewing orientation.
The preview endpoint (`/api/preview/<id>`) creates an `InkyDisplay` with the
current orientation, so the preview matches what the physical display shows.
## UI
Settings page (Display article) shows a `<select>` with four options:
- 0° (Landscape)
- 90° (Portrait)
- 180° (Landscape)
- 270° (Portrait)
Saved via the existing `PUT /api/config/display/orientation` route.
The old `unsplash.orientation` dropdown has been **removed** from the settings
page — it is now auto-derived from the display orientation.
## Edge Cases
- **0° (default):** No rotation applied. Preserves existing behavior.
- **180°:** Image is cropped/resized at physical dimensions, then rotated
upside-down. No dimension swap. Useful for ceiling-mounted displays.
- **90° / 270°:** Width and height are swapped for cropping/resizing, then
rotated back to physical dimensions. Images fetched from Unsplash with the
matching portrait/landscape filter.
- **Preview:** Shows the rotated image so the user sees exactly what the
display will show.
- **rotate(expand=True):** Only used with 90° multiples so no fractional
pixels appear.