diff --git a/README.md b/README.md index 887d259..3f77e45 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ stairs show. On exit it sends a black frame. The stairs power must be on nix-shell --run "python treppe_gl.py shaders/flow.glsl" nix-shell --run "python treppe_gl.py shaders/pulse.glsl --dry-run" -Options: `--host`, `--port`, `--fps`, `--dry-run` (send nothing), +Wrappers: `./treppe-run.sh [brightness]` and `./treppe-dry.sh [brightness]` (dry run, nothing is sent). + +Options: `--brightness 0-100` (scales output, default 100), `--host`, `--port`, `--fps`, `--dry-run` (send nothing), `--no-window` (headless), `--white {extract,alpha,off}`. Shaders define `mainImage(out vec4 fragColor, in vec2 fragCoord)` and may diff --git a/tests/test_treppe_gl.py b/tests/test_treppe_gl.py index 7c982c9..cae6c2e 100644 --- a/tests/test_treppe_gl.py +++ b/tests/test_treppe_gl.py @@ -59,3 +59,12 @@ def test_udp_loopback(): tx = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) tx.sendto(t.BLACK_FRAME, rx.getsockname()) assert rx.recv(2048) == t.BLACK_FRAME + + +def test_brightness_scales_and_clamps(): + px = np.array([[1.0, 0.5, 0.25, 1.0]] + [[0, 0, 0, 0]] * 15) + half = t.to_rgbw(px, "extract", 50)[0] + np.testing.assert_allclose(half, [0.375, 0.125, 0.0, 0.125]) + assert t.to_rgbw(px, "extract", 0).max() == 0 + np.testing.assert_allclose(t.to_rgbw(px, "extract", 500), + t.to_rgbw(px, "extract", 100)) diff --git a/treppe-dry.sh b/treppe-dry.sh new file mode 100755 index 0000000..ef57b40 --- /dev/null +++ b/treppe-dry.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Usage: treppe-dry.sh [brightness 0-100, default 100] +set -euo pipefail +here=$(dirname "$(realpath "$0")") +[ $# -ge 1 ] || { echo "usage: $0 [brightness 0-100]" >&2; exit 2; } +shader=$(realpath "$1") +cd "$here" +exec nix-shell --run "python treppe_gl.py '$shader' --brightness ${2:-100} --dry-run" diff --git a/treppe-run.sh b/treppe-run.sh new file mode 100755 index 0000000..a17f896 --- /dev/null +++ b/treppe-run.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Usage: treppe-run.sh [brightness 0-100, default 100] +set -euo pipefail +here=$(dirname "$(realpath "$0")") +[ $# -ge 1 ] || { echo "usage: $0 [brightness 0-100]" >&2; exit 2; } +shader=$(realpath "$1") +cd "$here" +exec nix-shell --run "python treppe_gl.py '$shader' --brightness ${2:-100}" diff --git a/treppe_gl.py b/treppe_gl.py index 061e214..6e9db2d 100644 --- a/treppe_gl.py +++ b/treppe_gl.py @@ -35,23 +35,26 @@ WHITE_MODES = ("extract", "alpha", "off") # --- Protocol -------------------------------------------------------------- -def to_rgbw(pixels, white="extract"): +def to_rgbw(pixels, white="extract", brightness=100.0): """ Convert float RGBA pixels (STEPS x 4, 0..1) to RGBW (STEPS x 4). extract: w = min(r, g, b), subtracted from r, g, b alpha: the shader's alpha channel is the white value off: w = 0 + + brightness (0..100, percent) scales the result linearly. """ + scale = np.float32(min(max(brightness, 0.0), 100.0) / 100.0) px = np.clip(np.asarray(pixels, dtype=np.float32), 0.0, 1.0) rgb = px[:, :3] if white == "extract": w = rgb.min(axis=1, keepdims=True) - return np.hstack([rgb - w, w]) + return np.hstack([rgb - w, w]) * scale if white == "alpha": - return np.hstack([rgb, px[:, 3:4]]) + return np.hstack([rgb, px[:, 3:4]]) * scale if white == "off": - return np.hstack([rgb, np.zeros((len(px), 1), dtype=np.float32)]) + return np.hstack([rgb, np.zeros((len(px), 1), dtype=np.float32)]) * scale raise ValueError("unknown white mode: {}".format(white)) @@ -380,7 +383,7 @@ def run(args): last = now pixels = renderer.render_strip(t, dt, frame) - rgbw = to_rgbw(pixels, args.white) + rgbw = to_rgbw(pixels, args.white, args.brightness) if sock: sock.sendto(encode_frame(rgbw), target) @@ -414,6 +417,8 @@ def parse_args(argv=None): p.add_argument("--port", type=int, default=PORT_DEFAULT) p.add_argument("--fps", type=float, default=60.0) p.add_argument("--white", choices=WHITE_MODES, default="extract") + p.add_argument("--brightness", type=float, default=100.0, + help="0..100 percent, scales what is sent (default 100)") p.add_argument("--dry-run", action="store_true", help="do not send anything over the network") p.add_argument("--no-window", dest="window", action="store_false",