diff --git a/README.md b/README.md index 9fae8532..d11c8b75 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| | [Music Player](./plugins/ledmatrix-music/) | Now playing with album art (Spotify & YouTube Music) | | -| [Static Image Display](./plugins/static-image/) | Image display with scaling and transparency | | +| [Static Image Display](./plugins/static-image/) | Image display with scaling and transparency | static-image on an LED panel | ### Content (2) diff --git a/docs/assets/static-image/background-color.png b/docs/assets/static-image/background-color.png new file mode 100644 index 00000000..fac023aa Binary files /dev/null and b/docs/assets/static-image/background-color.png differ diff --git a/docs/assets/static-image/fit-and-aspect.png b/docs/assets/static-image/fit-and-aspect.png new file mode 100644 index 00000000..ff4373d3 Binary files /dev/null and b/docs/assets/static-image/fit-and-aspect.png differ diff --git a/docs/assets/static-image/hero.png b/docs/assets/static-image/hero.png new file mode 100644 index 00000000..d0d19430 Binary files /dev/null and b/docs/assets/static-image/hero.png differ diff --git a/docs/assets/static-image/panel-sizes.png b/docs/assets/static-image/panel-sizes.png new file mode 100644 index 00000000..4caa0f1f Binary files /dev/null and b/docs/assets/static-image/panel-sizes.png differ diff --git a/docs/assets/static-image/sample/mark-a.png b/docs/assets/static-image/sample/mark-a.png new file mode 100644 index 00000000..cc970443 Binary files /dev/null and b/docs/assets/static-image/sample/mark-a.png differ diff --git a/docs/assets/static-image/sample/mark-b.png b/docs/assets/static-image/sample/mark-b.png new file mode 100644 index 00000000..413f77d6 Binary files /dev/null and b/docs/assets/static-image/sample/mark-b.png differ diff --git a/docs/assets/static-image/sample/transparent.png b/docs/assets/static-image/sample/transparent.png new file mode 100644 index 00000000..fd63adc2 Binary files /dev/null and b/docs/assets/static-image/sample/transparent.png differ diff --git a/docs/assets/static-image/sample/wide.png b/docs/assets/static-image/sample/wide.png new file mode 100644 index 00000000..a91fb5f0 Binary files /dev/null and b/docs/assets/static-image/sample/wide.png differ diff --git a/docs/assets/static-image/shots.json b/docs/assets/static-image/shots.json new file mode 100644 index 00000000..0621dffb --- /dev/null +++ b/docs/assets/static-image/shots.json @@ -0,0 +1,310 @@ +{ + "plugin": "static-image", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-02T16:30:00+00:00" + }, + "shots": [ + { + "name": "hero", + "config": { + "enabled": true, + "images": [ + { + "id": "mark-a", + "path": "docs/assets/static-image/sample/mark-a.png", + "display_order": 0 + } + ] + } + }, + { + "name": "fit-on", + "config": { + "enabled": true, + "images": [ + { + "id": "wide", + "path": "docs/assets/static-image/sample/wide.png", + "display_order": 0 + } + ], + "fit_to_display": true + }, + "standalone": false + }, + { + "name": "fit-off", + "config": { + "enabled": true, + "images": [ + { + "id": "wide", + "path": "docs/assets/static-image/sample/wide.png", + "display_order": 0 + } + ], + "fit_to_display": false + }, + "standalone": false + }, + { + "name": "aspect-on", + "config": { + "enabled": true, + "images": [ + { + "id": "wide", + "path": "docs/assets/static-image/sample/wide.png", + "display_order": 0 + } + ], + "fit_to_display": true, + "preserve_aspect_ratio": true + }, + "standalone": false + }, + { + "name": "aspect-off", + "config": { + "enabled": true, + "images": [ + { + "id": "wide", + "path": "docs/assets/static-image/sample/wide.png", + "display_order": 0 + } + ], + "fit_to_display": true, + "preserve_aspect_ratio": false + }, + "standalone": false + }, + { + "name": "bg-black", + "config": { + "enabled": true, + "images": [ + { + "id": "transparent", + "path": "docs/assets/static-image/sample/transparent.png", + "display_order": 0 + } + ], + "background_color": [ + 0, + 0, + 0 + ] + }, + "standalone": false + }, + { + "name": "bg-navy", + "config": { + "enabled": true, + "images": [ + { + "id": "transparent", + "path": "docs/assets/static-image/sample/transparent.png", + "display_order": 0 + } + ], + "background_color": [ + 20, + 30, + 90 + ] + }, + "standalone": false + }, + { + "name": "bg-red", + "config": { + "enabled": true, + "images": [ + { + "id": "transparent", + "path": "docs/assets/static-image/sample/transparent.png", + "display_order": 0 + } + ], + "background_color": [ + 120, + 20, + 20 + ] + }, + "standalone": false + }, + { + "name": "bg-grey", + "config": { + "enabled": true, + "images": [ + { + "id": "transparent", + "path": "docs/assets/static-image/sample/transparent.png", + "display_order": 0 + } + ], + "background_color": [ + 60, + 60, + 60 + ] + }, + "standalone": false + }, + { + "name": "size-64x32", + "config": { + "enabled": true, + "images": [ + { + "id": "mark-a", + "path": "docs/assets/static-image/sample/mark-a.png", + "display_order": 0 + } + ] + }, + "width": 64, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x32", + "config": { + "enabled": true, + "images": [ + { + "id": "mark-a", + "path": "docs/assets/static-image/sample/mark-a.png", + "display_order": 0 + } + ] + }, + "width": 128, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x64", + "config": { + "enabled": true, + "images": [ + { + "id": "mark-a", + "path": "docs/assets/static-image/sample/mark-a.png", + "display_order": 0 + } + ] + }, + "width": 128, + "height": 64, + "scale": 8, + "standalone": false + }, + { + "name": "size-256x32", + "config": { + "enabled": true, + "images": [ + { + "id": "mark-a", + "path": "docs/assets/static-image/sample/mark-a.png", + "display_order": 0 + } + ] + }, + "width": 256, + "height": 32, + "scale": 4, + "standalone": false + } + ], + "composites": [ + { + "name": "fit-and-aspect", + "columns": 2, + "cells": [ + { + "shot": "fit-on", + "label": "fit_to_display: true", + "sublabel": "the default; the 192x64 image is scaled to the panel" + }, + { + "shot": "fit-off", + "label": "fit_to_display: false", + "sublabel": "drawn at native size, so it overflows" + }, + { + "shot": "aspect-on", + "label": "preserve_aspect_ratio: true", + "sublabel": "the default; proportions kept, letterboxed" + }, + { + "shot": "aspect-off", + "label": "preserve_aspect_ratio: false", + "sublabel": "stretched to fill the panel exactly" + } + ] + }, + { + "name": "background-color", + "columns": 2, + "cells": [ + { + "shot": "bg-black", + "label": "[0, 0, 0]", + "sublabel": "the default; transparent areas stay unlit" + }, + { + "shot": "bg-navy", + "label": "[20, 30, 90]", + "sublabel": "transparent areas fill with the colour" + }, + { + "shot": "bg-red", + "label": "[120, 20, 20]", + "sublabel": "" + }, + { + "shot": "bg-grey", + "label": "[60, 60, 60]", + "sublabel": "every pixel lit \u2014 brightest and most power" + } + ] + }, + { + "name": "panel-sizes", + "columns": 1, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32", + "sublabel": "the square mark is capped by panel height" + }, + { + "shot": "size-128x32", + "label": "128 x 32", + "sublabel": "the common two-panel chain" + }, + { + "shot": "size-128x64", + "label": "128 x 64", + "sublabel": "twice the height, so twice the mark" + }, + { + "shot": "size-256x32", + "label": "256 x 32", + "sublabel": "height still caps it; the image centres" + } + ] + } + ] +} diff --git a/plugins.json b/plugins.json index f900d57c..9ec1cf31 100644 --- a/plugins.json +++ b/plugins.json @@ -780,10 +780,10 @@ "plugin_path": "plugins/static-image", "stars": 0, "downloads": 0, - "last_updated": "2026-07-17", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.0.5" + "latest_version": "1.0.6" }, { "id": "stock-news", diff --git a/plugins/static-image/README.md b/plugins/static-image/README.md index e75f2b84..2f57e998 100644 --- a/plugins/static-image/README.md +++ b/plugins/static-image/README.md @@ -1,220 +1,337 @@ ------------------------------------------------------------------------------------ -### Connect with ChuckBuilds +# Static Image -- Show support on Youtube: https://www.youtube.com/@ChuckBuilds -- Stay in touch on Instagram: https://www.instagram.com/ChuckBuilds/ -- Want to chat or need support? Reach out on the ChuckBuilds Discord: https://discord.com/invite/uW36dVAtcT -- Feeling Generous? Support the project: - - Github Sponsorship: https://github.com/sponsors/ChuckBuilds - - Buy Me a Coffee: https://buymeacoffee.com/chuckbuilds - - Ko-fi: https://ko-fi.com/chuckbuilds/ +Show your own images on the LED matrix — a logo, a photo, a pixel-art piece. +One image, or a set that rotates, with optional per-image schedules so a +picture only appears at the times you choose. ------------------------------------------------------------------------------------ +![A sample mark scaled to fit a 128x32 panel](../../docs/assets/static-image/hero.png) -# Static Image Display Plugin +*Every image in this README is real plugin output, rendered at the true panel +size and then scaled up so the pixels stay pixels. The coloured marks are plain +samples standing in for your own images.* -Display static images on your LED matrix with automatic scaling, aspect ratio preservation, and transparency support. +--- -## Features +## Table of Contents -- **Multiple Format Support**: PNG, JPG, BMP, GIF, and more -- **Automatic Scaling**: Fit images to your display dimensions -- **Aspect Ratio Preservation**: Keep images looking correct -- **Transparency Support**: Handle PNG alpha channels -- **Configurable Background**: Set custom background colors -- **High-Quality Scaling**: LANCZOS resampling for best quality +1. [What's On Screen](#whats-on-screen) +2. [Installation](#installation) +3. [Quick Start](#quick-start) +4. [Adding Images](#adding-images) +5. [Scaling and Background](#scaling-and-background) + - [fit_to_display and preserve_aspect_ratio](#fit_to_display-and-preserve_aspect_ratio) + - [background_color](#background_color) +6. [Rotating Through Several Images](#rotating-through-several-images) + - [rotation_mode](#rotation_mode) + - [The two rotation intervals](#the-two-rotation-intervals) +7. [Per-Image Schedules](#per-image-schedules) +8. [Panel Sizes](#panel-sizes) +9. [Troubleshooting](#troubleshooting) +10. [Development](#development) +11. [Support](#support) -## Configuration +--- -### Single image +## What's On Screen + +One image at a time, centred on the panel. With several images configured the +plugin cycles through them; with one, it simply shows that one. + +Transparent areas of a PNG are filled with `background_color`, which defaults to +black — so on an LED panel a transparent background means those pixels are +simply off. + +--- + +## Installation + +**From the Plugin Store (recommended).** Open the LEDMatrix web interface at +`http://:5000`, go to **Plugin Manager**, find **Static Image +Display** in the **Plugin Store** section, and click **Install**. Images are +uploaded from the plugin's own tab. + +**Manually.** Copy this directory into your LEDMatrix `plugin-repos/` and +restart the display service. + +`enabled` defaults to **`false`**, so nothing appears until you switch the +plugin on. + +--- + +## Quick Start ```json { - "enabled": true, - "images": [ - { "id": "logo", "path": "assets/static_images/my_logo.png" } - ], - "fit_to_display": true, - "preserve_aspect_ratio": true, - "background_color": [0, 0, 0], - "display_duration": 10 + "static-image": { + "enabled": true, + "images": [ + { "id": "logo", "path": "assets/plugins/static-image/logo.png" } + ] + } } ``` -### Multiple images with rotation +Several images on a rotation: ```json { - "enabled": true, - "images": [ - { "id": "logo_a", "path": "assets/static_images/logo_a.png" }, - { "id": "logo_b", "path": "assets/static_images/logo_b.png" }, - { "id": "logo_c", "path": "assets/static_images/logo_c.png" } - ], - "image_config": { - "mode": "multiple", - "rotation_mode": "sequential" - }, - "rotation_settings": { - "sequential_loop": true - }, - "image_rotation_interval": 15, - "fit_to_display": true, - "preserve_aspect_ratio": true, - "background_color": [0, 0, 0], - "display_duration": 30 + "static-image": { + "enabled": true, + "image_config": { "mode": "multiple", "rotation_mode": "sequential" }, + "image_rotation_interval": 15, + "images": [ + { "id": "logo", "path": "assets/plugins/static-image/logo.png", "display_order": 1 }, + { "id": "badge", "path": "assets/plugins/static-image/badge.png", "display_order": 2 }, + { "id": "banner", "path": "assets/plugins/static-image/banner.png", "display_order": 3 } + ] + } } ``` -### Configuration Options +--- -The full schema lives in -[`config_schema.json`](config_schema.json) — the web UI form is generated -from it. Key options: +## Adding Images -| Key | Default | Notes | -|---|---|---| -| `enabled` | `false` | Master switch | -| `images` | `[]` | Array of image entries (each an object with a `path` and an optional per-image `schedule`), managed via the file-upload widget. See **Per-Image Scheduling** below. | -| `image_config.mode` | `"single"` | How images are presented: `"single"` or `"multiple"` | -| `image_config.rotation_mode` | `"sequential"` | Order when showing multiple images: `"sequential"`, `"random"`, `"time_based"` (advance on a wall-clock interval), or `"date_based"` (one image per day) | -| `rotation_settings.sequential_loop` | `true` | Loop back to the first image after the last | -| `rotation_settings.random_seed` | `null` | Optional fixed seed for reproducible random order | -| `rotation_settings.time_intervals.enabled` | `false` | Tie image changes to wall-clock intervals | -| `rotation_settings.time_intervals.interval_seconds` | `3600` | Wall-clock interval when enabled | -| `image_rotation_interval` | `15` | Seconds between images during rotation | -| `fit_to_display` | `true` | Scale image to display dimensions | -| `preserve_aspect_ratio` | `true` | Don't stretch when scaling | -| `background_color` | `[0, 0, 0]` | RGB fill behind transparent pixels | -| `display_duration` | `10` | Seconds the plugin holds the screen each rotation | +The `images` array is normally filled by the upload widget in the web UI, which +writes the file and the entry for you. Each entry takes: -### Per-Image Scheduling +| Key | Type | Default | What it does | +|-----|------|---------|--------------| +| `id` | string | *auto* | Unique identifier for the entry | +| `path` | string | — | File path, relative to the project root | +| `display_order` | integer | `0` | Order for sequential rotation; lower goes first | +| `uploaded_at` | string | *auto* | Upload timestamp, set by the UI | +| `schedule` | object | `null` | Optional time window — see [Per-Image Schedules](#per-image-schedules) | -Each entry in `images` can carry an optional `schedule` so an image only shows at -certain times (e.g. a "good morning" board, or business-hours signage): +PNG, JPEG and GIF all load; PNG is the sensible choice for anything with sharp +edges or transparency. A still frame is taken from an animated GIF — this +plugin does not animate. -| Schedule key | Default | Notes | -|---|---|---| -| `schedule.enabled` | `false` | Turn scheduling on for this image | -| `schedule.mode` | `"always"` | `"always"` (any time), `"time_range"` (same window every day), or `"per_day"` (a different window per weekday) | -| `schedule.start_time` / `schedule.end_time` | `"08:00"` / `"18:00"` | Window bounds (HH:MM, 24-hour) for `time_range` mode | -| `schedule.days.` | — | Per-weekday `enabled` + `start_time`/`end_time`, used when `mode` is `"per_day"` | +--- -When several images are eligible at the same time, they rotate per -`image_config.rotation_mode`. +## Scaling and Background -## Usage +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `fit_to_display` | boolean | `true` | Scale the image to the panel | +| `preserve_aspect_ratio` | boolean | `true` | Keep proportions while scaling | +| `background_color` | array | `[0, 0, 0]` | Fill for transparent areas, `[R, G, B]` | -### Basic Setup +### `fit_to_display` and `preserve_aspect_ratio` -1. Place your image in a directory (e.g., `assets/static_images/`) -2. Configure the plugin with the image path -3. Enable the plugin -4. The image will display automatically during rotation +![Four panels: a wide image fitted and unfitted, and with aspect ratio +preserved and stretched](../../docs/assets/static-image/fit-and-aspect.png) -### Image Guidelines +- **`fit_to_display: true`** (the default) scales the image to the panel. With + it off, the image is drawn at its native pixel size — a 192×64 source on a + 128×32 panel shows only the middle of itself. +- **`preserve_aspect_ratio: true`** (the default) keeps proportions, so a wide + image is letterboxed rather than squashed. Turn it off only when you want the + image to fill the panel exactly and do not mind the distortion. -**Recommended:** -- Use PNG format for best quality and transparency support -- Size images close to your display resolution for best performance -- For 64x32 displays: 64x32 or 128x64 images work well -- Use transparency to blend with background +The two work together: `preserve_aspect_ratio` has no effect when +`fit_to_display` is off, because nothing is being scaled. -**Supported Formats:** -- PNG (recommended for transparency) -- JPG/JPEG -- BMP -- GIF -- TIFF +**Design images at your panel's aspect ratio** where you can. A 128×32 panel is +4:1, which is unusually wide — a square logo will only ever use a third of it. -### Tips for Best Results +Choosing a format: -1. **For Logos**: Use PNG with transparent background -2. **For Photos**: Use JPG for smaller file size -3. **For Pixel Art**: Use PNG at native resolution -4. **For Icons**: Scale to exact display size +| Content | Use | Why | +|---------|-----|-----| +| Logo or icon | PNG with transparency | Sharp edges stay sharp, and the background stays unlit | +| Photograph | JPEG | Much smaller for the same result; transparency is not needed | +| Pixel art | PNG at native resolution | Sized to the panel exactly, no resampling to soften it | -## Advanced Usage +### `background_color` -### Dynamic Image Updates +Transparent pixels in a PNG are filled with this colour. -Change images programmatically via the Web UI or API: +![Four panels showing a transparent-background mark over black, navy, red and +grey](../../docs/assets/static-image/background-color.png) -```python -# Via plugin manager -plugin.set_image_path("assets/static_images/new_image.png") -plugin.reload_image() -``` +Black (the default) leaves those pixels unlit, which is what you almost always +want on an LED matrix. A non-black background lights **every** pixel on the +panel — noticeably brighter in a dark room and a real increase in power draw. -### Multiple Images +--- -Put all the images you want to cycle through into the `images` array (see -the multi-image example above) and set `image_config.mode` to -`"multiple"`. +## Rotating Through Several Images -## Troubleshooting +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `image_config.mode` | string | `single` | `single` shows one image; `multiple` rotates | +| `image_config.rotation_mode` | string | `sequential` | How the next image is chosen | +| `rotation_settings.sequential_loop` | boolean | `true` | Return to the first image after the last | +| `rotation_settings.random_seed` | integer / null | `null` | Fixes the random order; `null` uses the clock | +| `image_rotation_interval` | number | `15` | Seconds each image is shown | +| `display_duration` | number | `10` | Seconds the plugin holds the panel per turn | + +### `rotation_mode` + +| Value | What it does | +|-------|--------------| +| `sequential` | In `display_order`, wrapping at the end when `sequential_loop` is on | +| `random` | A random pick each time. Set `random_seed` for a repeatable order | +| `time_based` | Advances on its own timer — **also needs `rotation_settings.time_intervals.enabled: true`** | +| `date_based` | **Not implemented.** The code is a stub that always returns the first image | -**Image not displaying:** -- Check that image path is correct -- Verify image file exists -- Check file permissions -- Review logs for error messages +Two of those deserve emphasis: -**Image looks distorted:** -- Enable `preserve_aspect_ratio` -- Check image dimensions vs display size -- Verify image isn't corrupted +- **`time_based` needs a second switch.** Setting `rotation_mode: "time_based"` + alone does nothing; `rotation_settings.time_intervals.enabled` must also be + `true`, and `interval_seconds` (default `3600`) sets the pace. With the switch + off, the image never advances. +- **`date_based` does nothing at all.** It is a placeholder in the source — + selecting it pins the display to the first available image. It is listed here + because the setting is offered in the UI and silently doing nothing is worse + than being told. -**Image appears cropped:** -- Enable `fit_to_display` -- Check image size matches display +With `sequential_loop: false` the rotation stops on the last image rather than +wrapping, which is what you want for a sequence with an ending. -**Transparency not working:** -- Use PNG format with alpha channel -- Verify background_color is set correctly +### The two rotation intervals -## Examples +There are two different "how often" settings and they are not alternatives: + +| Setting | Default | Applies | +|---------|---------|---------| +| `image_rotation_interval` | `15` | Always. Seconds each image is shown before the next | +| `rotation_settings.time_intervals.interval_seconds` | `3600` | **Only** in `time_based` mode, and only with `time_intervals.enabled` on | + +`image_rotation_interval` is the one most people want. It falls back to +`display_duration` if unset, so leaving both alone gives a 10-second dwell. + +--- + +## Per-Image Schedules + +Each image can carry a `schedule` object restricting when it is eligible. +Images outside their window are skipped by the rotation entirely. -### Logo Display ```json { - "enabled": true, - "images": [ - { "id": "company_logo", "path": "assets/static_images/company_logo.png" } - ], - "fit_to_display": true, - "preserve_aspect_ratio": true, - "background_color": [0, 0, 0] + "id": "open-sign", + "path": "assets/plugins/static-image/open.png", + "schedule": { + "enabled": true, + "mode": "time_range", + "start_time": "08:00", + "end_time": "18:00" + } } ``` -### Pixel Art -```json -{ - "enabled": true, - "images": [ - { "id": "pixel_art", "path": "assets/static_images/pixel_art.png" } - ], - "fit_to_display": false, - "preserve_aspect_ratio": true, - "background_color": [0, 0, 50] -} +| Key | Default | What it does | +|-----|---------|--------------| +| `enabled` | `false` | Turn scheduling on for this image. **With it off the schedule is ignored entirely** | +| `mode` | `always` | `always`, `time_range` (same window every day), or `per_day` (a different window per weekday) | +| `start_time` | `"08:00"` | Window opens, `HH:MM` 24-hour | +| `end_time` | `"18:00"` | Window closes, `HH:MM` 24-hour | +| `days` | `null` | Per-weekday windows, used only when `mode` is `per_day` | + +The common mistake is setting `mode` and the times but leaving `enabled` at +`false`, which leaves the image always visible. + +If every image is scheduled out at once there is nothing eligible to draw, so +keep at least one image unscheduled as a fallback. + +--- + +## Panel Sizes + +![The same mark on 64x32, 128x32, 128x64 and 256x32 +panels](../../docs/assets/static-image/panel-sizes.png) + +With `preserve_aspect_ratio` on, a square image is capped by the panel's +*height*, so a longer chain does not make it bigger — it just centres it in +more black. Doubling the height to 128×64 doubles the mark. If you want an +image to use a wide panel, give it a wide source. + +--- + +## Troubleshooting + +**Nothing appears.** +`enabled` defaults to `false`. After that, check the `images` array is not +empty and that at least one entry's `path` resolves. + +**The image is missing but others show.** +Check the log for a load warning. `path` is relative to the LEDMatrix project +root, not to the plugin directory. + +**Only part of the image is visible.** +`fit_to_display` is off, so the image is drawn at native size and cropped by +the panel. Turn it on, or resize the source. + +**The image looks squashed.** +`preserve_aspect_ratio` is off. Turn it on to letterbox instead of stretch. + +**It never rotates.** +Check `image_config.mode` is `multiple` — `single` never advances. If +`rotation_mode` is `time_based`, also check +`rotation_settings.time_intervals.enabled` is `true`. If it is `date_based`, +that mode is a stub and will not rotate at all. + +**A scheduled image shows all the time.** +`schedule.enabled` defaults to `false`; the window is ignored until you set it. + +**The whole panel is lit and it is too bright.** +`background_color` is not black. Transparent areas are being filled with it. + +--- + +## Development + +### Project structure + +```text +static-image/ +├── manifest.json # Plugin metadata and version history +├── manager.py # StaticImagePlugin +├── config_schema.json # Settings schema; source of truth for defaults +├── requirements.txt +├── test/ # Harness config and golden images +└── README.md ``` -### Full Screen Photo -```json -{ - "enabled": true, - "images": [ - { "id": "photo", "path": "assets/static_images/photo.jpg" } - ], - "fit_to_display": true, - "preserve_aspect_ratio": false, - "background_color": [0, 0, 0] -} +### Changing the image programmatically + +The plugin exposes two methods for driving it from code or another plugin +rather than through configuration: + +```python +plugin.set_image_path("assets/plugins/static-image/alert.png") +plugin.reload_image() ``` -## License +Both return a boolean for whether they succeeded. This bypasses the `images` +array and the rotation, so it suits a one-off takeover — an alert, say — rather +than a permanent change. + +### Regenerating the images in this README + +```bash +python scripts/render_docs_assets.py --plugin static-image +``` + +`--check` verifies the committed images still match what the plugin renders. +The sample marks live in `docs/assets/static-image/sample/`. + +Note that rotation cannot be shown in a still: every `rotation_mode` starts on +the first eligible image, and the differences only appear across frames. The +rotation behaviour above is documented from the source rather than from a +screenshot. + +--- + +## Support -GPL-3.0 License - see main LEDMatrix repository for details. +- YouTube: +- Instagram: +- Discord: +- Sponsor: [GitHub Sponsors](https://github.com/sponsors/ChuckBuilds) · + [Buy Me a Coffee](https://buymeacoffee.com/chuckbuilds) · + [Ko-fi](https://ko-fi.com/chuckbuilds/) +Released under the GNU General Public License v3.0 — see [LICENSE](LICENSE). diff --git a/plugins/static-image/manifest.json b/plugins/static-image/manifest.json index ccba68e6..85f75abf 100644 --- a/plugins/static-image/manifest.json +++ b/plugins/static-image/manifest.json @@ -1,7 +1,7 @@ { "id": "static-image", "name": "Static Image Display", - "version": "1.0.5", + "version": "1.0.6", "author": "ChuckBuilds", "description": "Display static images on your LED matrix with automatic scaling, aspect ratio preservation, and transparency support. Perfect for logos, artwork, or custom graphics.", "entry_point": "manager.py", @@ -18,6 +18,12 @@ "static_image" ], "versions": [ + { + "released": "2026-09-02", + "version": "1.0.6", + "notes": "Documentation only, no behaviour change. Rewrites the README around real rendered screenshots and documents all 23 settings including the per-image schedule object. Records two things a user would otherwise discover by staring at an unchanging panel: rotation_mode 'date_based' is a stub in the source that always returns the first image, and 'time_based' does nothing unless rotation_settings.time_intervals.enabled is also turned on. Separates the two rotation intervals that look interchangeable -- image_rotation_interval always applies, while time_intervals.interval_seconds is only read in time_based mode -- and notes that schedule.enabled defaults to false, so a configured window is ignored until it is switched on.", + "ledmatrix_min": "2.0.0" + }, { "released": "2026-07-17", "version": "1.0.5", @@ -46,7 +52,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-07-17", + "last_updated": "2026-09-02", "stars": 0, "downloads": 0, "verified": true,