mirror of
https://github.com/aziis98/statuspage.git
synced 2026-10-06 14:45:01 +00:00
screenshots: generate light/dark/collage in one run with mtime caching; collage 1080p
This commit is contained in:
@@ -7,7 +7,7 @@ numeric metrics, and keeps recent history in SQLite.
|
|||||||
The backend is a single Go binary, the frontend is a Preact + Vite page, and
|
The backend is a single Go binary, the frontend is a Preact + Vite page, and
|
||||||
everything is configured with one YAML file.
|
everything is configured with one YAML file.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -120,16 +120,26 @@ README image, but is handy for previewing the UI quickly:
|
|||||||
### Screenshot
|
### Screenshot
|
||||||
|
|
||||||
`scripts/generate-screenshot` builds the frontend and server, starts the app in
|
`scripts/generate-screenshot` builds the frontend and server, starts the app in
|
||||||
mock mode, captures the top of the page with headless Chromium, and frames it
|
mock mode, captures the top of the page with headless Chromium in both light and
|
||||||
with a transparent background, border, and shadow. It needs `chromium` and
|
dark, and frames each with a transparent background, border, and shadow. It
|
||||||
`python3-PIL` on PATH:
|
needs `chromium` and `python3-PIL` on PATH:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./scripts/generate-screenshot
|
./scripts/generate-screenshot
|
||||||
```
|
```
|
||||||
|
|
||||||
The PNG is written to `assets/screenshots/status.png` — the image at the top of
|
This writes three PNGs to `assets/screenshots/`:
|
||||||
this README.
|
|
||||||
|
| file | contents |
|
||||||
|
| --------------------------- | ------------------------------------------------- |
|
||||||
|
| `status.png` | light capture |
|
||||||
|
| `status-dark.png` | dark capture |
|
||||||
|
| `status-collage.png` | dark behind, offset top-left, light on top |
|
||||||
|
|
||||||
|
Runs are incremental: a capture is skipped when no frontend/server source is
|
||||||
|
newer than the existing PNG, and the collage is rebuilt only when the mtime of a
|
||||||
|
light/dark variant is newer than the collage. `status-collage.png` is the image
|
||||||
|
at the top of this README.
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 572 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 506 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 532 KiB After Width: | Height: | Size: 537 KiB |
+57
-14
@@ -3,7 +3,10 @@ set -euo pipefail
|
|||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
OUT="assets/screenshots/status.png"
|
ASSETS="assets/screenshots"
|
||||||
|
LIGHT="$ASSETS/status.png"
|
||||||
|
DARK="$ASSETS/status-dark.png"
|
||||||
|
COLLAGE="$ASSETS/status-collage.png"
|
||||||
PORT="${PORT:-17645}"
|
PORT="${PORT:-17645}"
|
||||||
URL="http://127.0.0.1:$PORT"
|
URL="http://127.0.0.1:$PORT"
|
||||||
|
|
||||||
@@ -13,6 +16,13 @@ if [ -z "$CHROMIUM" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if python3 -c 'import PIL' 2>/dev/null; then
|
||||||
|
HAVE_PIL=1
|
||||||
|
else
|
||||||
|
HAVE_PIL=0
|
||||||
|
echo "note: python3-PIL not found, skipping framing and collage" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
echo "building frontend..."
|
echo "building frontend..."
|
||||||
bun run build
|
bun run build
|
||||||
|
|
||||||
@@ -41,23 +51,29 @@ if ! curl -sf "$URL/api/status" >/dev/null 2>&1; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$(dirname "$OUT")"
|
mkdir -p "$ASSETS"
|
||||||
|
|
||||||
|
# $1=theme $2=out $3=shadow $4=border
|
||||||
|
capture() {
|
||||||
|
local theme="$1" out="$2" shadow="$3" border="$4"
|
||||||
|
|
||||||
|
if [ -f "$out" ] &&
|
||||||
|
! find src index.html public server go.mod go.sum \
|
||||||
|
-type f -newer "$out" -print -quit 2>/dev/null | grep -q .; then
|
||||||
|
echo "up to date, skipping ($out)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
"$CHROMIUM" --headless=new --disable-gpu --hide-scrollbars \
|
"$CHROMIUM" --headless=new --disable-gpu --hide-scrollbars \
|
||||||
--window-size=1280,720 \
|
--window-size=1280,720 \
|
||||||
--force-device-scale-factor=2 \
|
--force-device-scale-factor=2 \
|
||||||
--screenshot="$OUT" \
|
--screenshot="$out" \
|
||||||
--virtual-time-budget=8000 \
|
--virtual-time-budget=8000 \
|
||||||
"$URL/" 2>/dev/null
|
"$URL/?theme=$theme" 2>/dev/null
|
||||||
|
|
||||||
# frame the capture: rounded corners, 2px border, hard offset shadow (2x for device scale)
|
# frame the capture: rounded corners, 2px border, hard offset shadow (2x for device scale)
|
||||||
if ! python3 -c 'import PIL' 2>/dev/null; then
|
if [ "$HAVE_PIL" = 1 ]; then
|
||||||
echo "note: python3-PIL not found, skipping frame" >&2
|
python3 - "$out" "$shadow" "$border" <<'PY'
|
||||||
echo "screenshot written to $OUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
python3 - "$OUT" <<'PY'
|
|
||||||
import sys
|
import sys
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
@@ -68,8 +84,8 @@ r = 16 # 0.5rem radius
|
|||||||
b = 4 # 2px border
|
b = 4 # 2px border
|
||||||
s = 12 # 0.35rem shadow offset
|
s = 12 # 0.35rem shadow offset
|
||||||
m = 24 # margin around the frame
|
m = 24 # margin around the frame
|
||||||
BORDER = (17, 17, 17)
|
BORDER = tuple(int(c) for c in sys.argv[3].split(","))
|
||||||
SHADOW = (136, 136, 136)
|
SHADOW = tuple(int(c) for c in sys.argv[2].split(","))
|
||||||
|
|
||||||
mask = Image.new("L", (W, H), 0)
|
mask = Image.new("L", (W, H), 0)
|
||||||
ImageDraw.Draw(mask).rounded_rectangle([0, 0, W - 1, H - 1], radius=r, fill=255)
|
ImageDraw.Draw(mask).rounded_rectangle([0, 0, W - 1, H - 1], radius=r, fill=255)
|
||||||
@@ -89,5 +105,32 @@ canvas.alpha_composite(img_r, (m + b, m + b))
|
|||||||
canvas.save(path)
|
canvas.save(path)
|
||||||
print(f"framed {path}")
|
print(f"framed {path}")
|
||||||
PY
|
PY
|
||||||
|
fi
|
||||||
|
echo "wrote $out"
|
||||||
|
}
|
||||||
|
|
||||||
echo "screenshot written to $OUT"
|
capture light "$LIGHT" "136,136,136" "17,17,17"
|
||||||
|
capture dark "$DARK" "0,0,0" "58,64,58"
|
||||||
|
|
||||||
|
# collage: rebuild only when a variant is newer than the existing collage
|
||||||
|
if [ "$HAVE_PIL" = 1 ]; then
|
||||||
|
if [ -f "$COLLAGE" ] && [ "$COLLAGE" -nt "$LIGHT" ] && [ "$COLLAGE" -nt "$DARK" ]; then
|
||||||
|
echo "collage up to date, skipping ($COLLAGE)"
|
||||||
|
else
|
||||||
|
python3 - "$LIGHT" "$DARK" "$COLLAGE" <<'PY'
|
||||||
|
import sys
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
light = Image.open(sys.argv[1]).convert("RGBA")
|
||||||
|
dark = Image.open(sys.argv[2]).convert("RGBA")
|
||||||
|
W, H = light.size
|
||||||
|
ox, oy = 480, 160
|
||||||
|
canvas = Image.new("RGBA", (W + ox, H + oy), (242, 242, 242, 255))
|
||||||
|
canvas.alpha_composite(dark, (0, 0))
|
||||||
|
canvas.alpha_composite(light, (ox, oy))
|
||||||
|
canvas.thumbnail((1920, 1080), Image.LANCZOS)
|
||||||
|
canvas.save(sys.argv[3])
|
||||||
|
print(f"wrote {sys.argv[3]}")
|
||||||
|
PY
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user