README: product-style showcase with screenshots of public-domain photos developed in LightCraft; reproducible showcase scripts; display-referred tone map for JPEGs (exact pass-through); fixes (crop panel wrap, overlay clipping, greyscale histogram, painted chevrons)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-09-30 22:56:38 -04:00
co-authored by Claude Opus 5.5
parent a7ea39573b
commit 440cedfa9c
27 changed files with 492 additions and 26 deletions
+18
View File
@@ -0,0 +1,18 @@
# README showcase
The screenshots in `docs/images/` are captured by driving LightCraft over its control channel.
```sh
# public-domain set (downloaded into the gitignored corpus/, see assets/ATTRIBUTION.md for sources)
cargo run --release -p lightcraft -- --control 7980 corpus/images/pd &
python3 docs/showcase/run.py docs/showcase/pd-edits.jsonl
# procedural demo library
cargo run --release -p lightcraft -- --control 7980 &
python3 docs/showcase/run.py docs/showcase/demo.jsonl
# compress for the repo (macOS)
for f in docs/images/*.png; do sips -s format jpeg -s formatOptions 82 "$f" --out "${f%.png}.jpg" && rm "$f"; done
```
`run.py` sends each JSON line (`{"method":…,"params":…}` or `{"sleep": seconds}`) to `127.0.0.1:7980`.
+35
View File
@@ -0,0 +1,35 @@
# Demo-library showcase: run LightCraft with `--control 7980` (no files → procedural demo library)
{"method":"ui.resize","params":{"width":1600,"height":1000}}
{"method":"engine.execute","params":{"command":"view.photoGrid"}}
{"method":"ui.set","params":{"leftPanel":true,"right":"edit","thumbSize":230,"openSections":["light","color"],"presets":false}}
{"sleep":7}
{"method":"ui.screenshot","params":{"path":"docs/images/grid-demo.png"}}
# masking: radial gradient over the sun on "Lake at first light"
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[1]}}}
{"method":"engine.execute","params":{"command":"view.detail"}}
{"method":"ui.set","params":{"leftPanel":false}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"light.highlights":-55,"light.shadows":35,"color.vibrance":25,"effects.clarity":15}}}}
{"method":"engine.execute","params":{"command":"panel.masking"}}
{"method":"engine.execute","params":{"command":"mask.add","params":{"kind":"linear","start":[0.5,0.0],"end":[0.5,0.45],"name":"Sky"}}}
{"method":"engine.execute","params":{"command":"mask.adjust","params":{"values":{"exposure":-0.6,"temp":25,"saturation":20,"dehaze":20}}}}
{"method":"engine.execute","params":{"command":"mask.add","params":{"kind":"radial","center":[0.62,0.4],"rx":0.2,"ry":0.14,"feather":70,"name":"Sun glow"}}}
{"method":"engine.execute","params":{"command":"mask.adjust","params":{"values":{"exposure":0.5,"temp":40,"saturation":15}}}}
{"sleep":7}
{"method":"ui.screenshot","params":{"path":"docs/images/masking.png"}}
# presets column + colour mixer
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[6]}}}
{"method":"engine.execute","params":{"command":"panel.edit"}}
{"method":"engine.execute","params":{"command":"preset.apply","params":{"id":"lc.golden-hour","amount":100}}}
{"method":"engine.execute","params":{"command":"panel.presets"}}
{"method":"ui.set","params":{"openSections":["color"],"openFlyouts":["mixer"],"mixerMode":"purple"}}
{"sleep":7}
{"method":"ui.screenshot","params":{"path":"docs/images/presets-mixer.png"}}
{"method":"engine.execute","params":{"command":"panel.presets"}}
# black & white + grain on the dunes
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[2]}}}
{"method":"engine.execute","params":{"command":"preset.apply","params":{"id":"lc.bw-high-contrast","amount":100}}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"grain.amount":30,"vignette.amount":-25}}}}
{"method":"ui.set","params":{"beforeAfter":"split","openSections":["effects"],"openFlyouts":[]}}
{"sleep":7}
{"method":"ui.screenshot","params":{"path":"docs/images/bw-split.png"}}
{"method":"ui.set","params":{"beforeAfter":"off"}}
+66
View File
@@ -0,0 +1,66 @@
# Public-domain showcase: run LightCraft with `--control 7980 corpus/images/pd` (see docs/showcase/README.md)
{"method":"ui.resize","params":{"width":1600,"height":1000}}
{"method":"engine.execute","params":{"command":"photo.setMeta","params":{"ids":[1],"title":"The Tetons and the Snake River","creator":"Ansel Adams (1942), U.S. National Archives","keywords":["mountains","river","black and white","national park"]}}}
{"method":"engine.execute","params":{"command":"photo.setMeta","params":{"ids":[2],"title":"Migrant Mother","creator":"Dorothea Lange (1936), Library of Congress","keywords":["portrait","documentary"]}}}
{"method":"engine.execute","params":{"command":"photo.setMeta","params":{"ids":[3],"title":"Earthrise","creator":"NASA / Bill Anders, Apollo 8 (1968)","keywords":["space","earth","moon"]}}}
{"method":"engine.execute","params":{"command":"photo.setMeta","params":{"ids":[4],"title":"The Blue Marble","creator":"NASA, Apollo 17 (1972)","keywords":["space","earth"]}}}
{"method":"engine.execute","params":{"command":"photo.rate","params":{"ids":[1,2,3,4],"rating":5}}}
{"method":"engine.execute","params":{"command":"photo.flag","params":{"ids":[1,3],"flag":"pick"}}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[1]}}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"light.exposure":0.15,"light.contrast":28,"light.highlights":-45,"light.shadows":38,"light.whites":22,"light.blacks":-22,"effects.clarity":28,"effects.dehaze":18,"effects.texture":12,"vignette.amount":-18}}}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[2]}}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"light.contrast":18,"light.highlights":-35,"light.shadows":42,"light.blacks":-15,"effects.clarity":22,"effects.texture":18,"vignette.amount":-22,"grading.highlights.hue":42,"grading.highlights.sat":16,"grading.shadows.hue":28,"grading.shadows.sat":12}}}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[3]}}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"wb.temp":6800,"light.exposure":0.25,"light.contrast":12,"light.highlights":-30,"light.shadows":25,"light.blacks":-18,"effects.dehaze":22,"effects.clarity":16,"color.vibrance":22}}}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[4]}}}
{"method":"engine.execute","params":{"command":"develop.set","params":{"values":{"light.contrast":16,"light.highlights":-38,"light.shadows":20,"light.blacks":-20,"effects.dehaze":15,"effects.clarity":12,"color.vibrance":30}}}}
# hero
{"method":"ui.set","params":{"leftPanel":false,"filmstrip":true,"beforeAfter":"off","openSections":["light","effects"],"openFlyouts":[],"right":"edit","presets":false}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[1]}}}
{"method":"engine.execute","params":{"command":"view.detail"}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/hero-tetons.png"}}
# before / after
{"method":"ui.set","params":{"beforeAfter":"sideBySide","openSections":["light"]}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/ba-tetons.png"}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[2]}}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/ba-migrant-mother.png"}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[3]}}}
{"method":"ui.set","params":{"openSections":["light","color"]}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/ba-earthrise.png"}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[4]}}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/ba-blue-marble.png"}}
# colour grading
{"method":"ui.set","params":{"beforeAfter":"off","openSections":["color"],"openFlyouts":["grading"]}}
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[2]}}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/grading-migrant-mother.png"}}
# tone curve
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[1]}}}
{"method":"engine.execute","params":{"command":"develop.curve","params":{"channel":"master","points":[[0,0.04],[0.25,0.2],[0.5,0.52],[0.78,0.84],[1,0.97]]}}}
{"method":"ui.set","params":{"openSections":["light"],"openFlyouts":["curve"],"curveChannel":"master"}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/curve-tetons.png"}}
# crop & straighten
{"method":"engine.execute","params":{"command":"crop.aspect","params":{"aspect":"16x9"}}}
{"method":"engine.execute","params":{"command":"crop.straighten","params":{"angle":2.5}}}
{"method":"engine.execute","params":{"command":"panel.crop"}}
{"sleep":6}
{"method":"ui.screenshot","params":{"path":"docs/images/crop-tetons.png"}}
{"method":"engine.execute","params":{"command":"crop.reset"}}
{"method":"engine.execute","params":{"command":"panel.edit"}}
# info + keywords
{"method":"engine.execute","params":{"command":"library.select","params":{"ids":[3]}}}
{"method":"engine.execute","params":{"command":"panel.info"}}
{"sleep":5}
{"method":"ui.screenshot","params":{"path":"docs/images/info-earthrise.png"}}
{"method":"engine.execute","params":{"command":"panel.edit"}}
# grid
{"method":"engine.execute","params":{"command":"view.squareGrid"}}
{"method":"ui.set","params":{"leftPanel":true,"thumbSize":300}}
{"sleep":5}
{"method":"ui.screenshot","params":{"path":"docs/images/grid-pd.png"}}
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env python3
"""lcb.py FILE.jsonl — run control requests from a JSON-lines file ({"method":..., "params":...} or {"sleep": s})."""
import json, socket, sys, time
def call(method, params):
s = socket.create_connection(("127.0.0.1", 7980), timeout=180)
s.sendall((json.dumps({"id": 1, "method": method, "params": params}) + "\n").encode())
buf = b""
while not buf.endswith(b"\n"):
c = s.recv(1 << 20)
if not c: break
buf += c
return json.loads(buf)
for line in open(sys.argv[1]):
line = line.strip()
if not line or line.startswith("#"): continue
r = json.loads(line)
if "sleep" in r:
time.sleep(r["sleep"]); continue
out = call(r["method"], r.get("params", {}))
print(r["method"], json.dumps(out)[:160])