Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ __pycache__/

# macOS
.DS_Store

__pycache__/
351 changes: 351 additions & 0 deletions docs/images/daqiri_rx_path.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions docs/images/packet_diagrams/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
PYTHON ?= python3
REPO_ROOT := $(shell git -C "$(CURDIR)" rev-parse --show-toplevel 2>/dev/null)
GENERATED_PATHS := \
docs/images/packet_diagrams/hds \
docs/images/packet_diagrams/flow_steering \
docs/images/packet_diagrams/reorder \
docs/images/packet_diagrams/reorder_quantize

.PHONY: all hds flow-steering reorder check clean

all: hds flow-steering reorder

hds:
$(PYTHON) hds_animation.py

flow-steering:
$(PYTHON) flow_steering_animation.py

reorder:
$(PYTHON) reorder_animation.py

check: all
@if [ -z "$(REPO_ROOT)" ]; then \
echo "error: packet diagram reproducibility check requires a git checkout" >&2; \
exit 1; \
fi
@if [ -n "$$(git -C "$(REPO_ROOT)" status --porcelain -- $(GENERATED_PATHS))" ]; then \
git -C "$(REPO_ROOT)" status --short -- $(GENERATED_PATHS); \
echo "error: packet diagram outputs are stale; rerun make -C docs/images/packet_diagrams and commit the results" >&2; \
exit 1; \
fi
@echo "Packet diagram outputs are reproducible."

clean:
$(RM) hds/*.webp hds/*-poster.png
$(RM) flow_steering/*.webp flow_steering/*-poster.png
$(RM) reorder/*.webp reorder/*-poster.png
$(RM) reorder_quantize/*.webp reorder_quantize/*-poster.png
63 changes: 63 additions & 0 deletions docs/images/packet_diagrams/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Packet Diagram Animations

These scripts generate the animated packet-path diagrams used by the DAQIRI docs.

## Requirements

- Python 3
- Pillow 11.1.x with animated WebP support
- DejaVu Sans or Liberation Sans fonts installed in a standard system font path

Check the encoder support with:

```bash
python3 - <<'PY'
from PIL import Image, features
print("Pillow", Image.__version__)
print("webp", features.check("webp"))
print("webp_anim", features.check("webp_anim"))
PY
```

## Regenerate

From the repository root:

```bash
make -C docs/images/packet_diagrams
```

Before committing, run:

```bash
make -C docs/images/packet_diagrams check
```

The check regenerates the assets and fails if any generated output directory
has uncommitted changes afterward.

Or run one generator directly:

```bash
python3 docs/images/packet_diagrams/hds_animation.py
python3 docs/images/packet_diagrams/flow_steering_animation.py
python3 docs/images/packet_diagrams/reorder_animation.py
```

Each generator writes animated WebP files and PNG posters into its adjacent output directory. The reorder generator emits both the reorder-only and reorder-plus-convert variants.

Animated WebP encoders may coalesce identical consecutive frames, so a decoded
WebP frame count can be lower than the script's logical frame count. Use
`make check` to verify reproducibility rather than comparing decoded frame
counts directly.

## Size Notes

`anim_common.py` currently uses `WEBP_METHOD = 0` to keep local regeneration
fast. Raising the method is still available when smaller artifacts matter more
than encoder time; expect roughly another 20-35% size reduction depending on the
animation. Run `make check` after changing it.

The PR history before the WebP conversion contains large GIF blobs across the
animation commits. Squash before merging so those transient GIF binaries do not
remain in the main branch history.
79 changes: 79 additions & 0 deletions docs/images/packet_diagrams/anim_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from __future__ import annotations

from pathlib import Path
from collections.abc import Sequence

from PIL import Image, ImageFont, features


WEBP_QUALITY = 80
WEBP_METHOD = 0


def font(size: int, *, scale: int = 2, bold: bool = False, mono: bool = False) -> ImageFont.FreeTypeFont:
if mono:
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
"/usr/share/fonts/truetype/noto/NotoSansMono-Bold.ttf" if bold else "/usr/share/fonts/truetype/noto/NotoSansMono-Regular.ttf",
"C:/Windows/Fonts/consolab.ttf" if bold else "C:/Windows/Fonts/consola.ttf",
]
elif bold:
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf",
"C:/Windows/Fonts/segoeuib.ttf",
]
else:
candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"C:/Windows/Fonts/segoeui.ttf",
]
for path in candidates:
if Path(path).exists():
return ImageFont.truetype(path, size * scale)
raise FileNotFoundError(f"missing required font; tried: {', '.join(candidates)}")


def font_scheme(scale: int = 2) -> dict[str, ImageFont.FreeTypeFont]:
return {
"label": font(18, scale=scale, bold=True),
"small": font(14, scale=scale),
"tiny": font(12, scale=scale),
"chip": font(21, scale=scale, bold=True),
"badge": font(15, scale=scale, bold=True),
"nic_center": font(20, scale=scale, bold=True),
"packet": font(18, scale=scale, bold=True),
"queue": font(18, scale=scale, bold=True),
"slot": font(18, scale=scale, bold=True),
}


def require_webp_animation_support() -> None:
if not features.check("webp") or not features.check("webp_anim"):
raise RuntimeError("Pillow must be built with animated WebP support to render packet diagrams")


def save_webp_animation(frames: Sequence[Image.Image], path: Path, duration_ms: int) -> None:
if not frames:
raise ValueError("cannot save animation with no frames")
require_webp_animation_support()

rgba_frames = [frame.convert("RGBA") for frame in frames]
tmp_path = path.with_name(f".{path.name}.tmp")
if tmp_path.exists():
tmp_path.unlink()
# libwebp may coalesce identical consecutive frames; verify outputs with
# Makefile's reproducibility check rather than decoded frame counts.
rgba_frames[0].save(
tmp_path,
format="WEBP",
save_all=True,
append_images=rgba_frames[1:],
duration=duration_ms,
loop=0,
lossless=False,
quality=WEBP_QUALITY,
method=WEBP_METHOD,
)
tmp_path.replace(path)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading