diff --git a/AGENTS.md b/AGENTS.md
index b199c7f96d..1e889b730d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -73,8 +73,8 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** Don't commit
font files here (Inter, already in `assets/fonts/`, is the one exception); add new fonts to craft-fonts. LightCraft
uses it as the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts`
- then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p lightcraft` (or any cargo/xtask command). `crates/engine/build.rs`
- embeds the manifest's fonts as `lightcraft_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web`
+ then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p dac-app` (or any cargo/xtask command). `crates/engine/build.rs`
+ embeds the manifest's fonts as `dac_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web`
ships Noto Sans CJK SC Regular as a separate asset loaded by the UI before startup. The UI
(`theme::font_definitions`) and the export watermark renderer use CJK faces (picked by script) as fallbacks after Inter. Unset,
`CRAFT_FONTS` is empty: everything builds, tests and runs, but Chinese and Japanese text lack glyphs. Releases always build
@@ -82,7 +82,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
without it; the FreeBSD CI job runs them with it. Rules: craftrules
[`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md).
- Icons drawn in code (e.g. `crates/ui-egui/src/icons.rs`) are original work and are recorded in `assets/ATTRIBUTION.md` as such; do not trace them from Adobe icons.
-- Demo/test images: generated procedurally by `lightcraft-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published.
+- Demo/test images: generated procedurally by `dac-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published.
- **Enforced:** `cargo xtask assets` (in `ci`) fails when an image/icon/font/sound/video/raw/ICC/XMP file is not matched
by a path pattern in the first column of `assets/ATTRIBUTION.md`, when a referenced licence file is missing, when a
file name suggests Adobe material, or when an Adobe profile/template format (`.dcp`, `.lcp`, `.lrtemplate`, …) appears.
@@ -90,7 +90,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- When in doubt about an asset's licence: don't use it.
## Running and looking at the app
-- `cargo run --release -p lightcraft -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`).
+- `cargo run --release -p dac-app -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`).
- **Never send OS-level synthetic input** (osascript/System Events keystrokes or clicks, `cliclick`, accessibility
automation): it goes to whatever window is frontmost — the user's terminal or other apps. Drive LightCraft only
through its control channel (`ui.key`, `ui.pointer`, `ui.clickWidget`, `ui.menu.invoke`) or headless snapshots.
@@ -101,7 +101,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
`ui.screenshot {"path": …, "headless": true}`; windowed screenshots fall back to headless after 2 s.
- MCP: `lightcraft-cli mcp` (headless, `--demo` for the procedural library) or `lightcraft-cli mcp --connect` (drives
the running app). See `docs/mcp.md`. Quick non-UI checks: `lightcraft-cli render in.jpg -o out.jpg --set light.exposure=1`.
-- Export goes through `lightcraft_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in
+- Export goes through `dac_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in
`crates/ui-egui/src/menus.rs`.
- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`.
- Parallel agents: separate git worktrees and `CARGO_TARGET_DIR=target/agent-`; each agent uses its **own control port** (pick one in 18000–19999, never the default 7980) and its own scratch subfolder (`//`) — never `rm -rf` shared paths; delete your target dir when done (disk is shared); keep every `Cargo.toml` valid at all times (the `crates/*` glob means one broken manifest breaks everyone).
@@ -110,7 +110,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
## Testing & performance (do this often)
- Unit/property tests next to the code; end-to-end tests drive real binaries (`crates/mcp/tests/e2e.rs`,
- `apps/lightcraft-cli/tests/`). New features need at least one test that would fail without them.
+ `apps/cli/tests/`). New features need at least one test that would fail without them.
- **Run the app after every user-visible change**: launch with `--control 7980`, drive it with a JSON-lines script
(`docs/showcase/run.py file.jsonl`), take `ui.screenshot`, and look at it. Check `ui.inspect` → `perf`.
- **Benchmarks:** `cargo xtask bench` (24 MP raw from corpus; CPU and GPU columns) appends to `target/bench/history.jsonl`
@@ -118,7 +118,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- **Measure, don't guess**: `LIGHTCRAFT_PROFILE=1` prints per-stage pipeline timings to stderr; time CLI renders
with `/usr/bin/time`. Record numbers in `plan/STATUS.md` → Metrics. Budgets: slider update ≤ 16 ms (draft) / loupe
≤ 60 ms on ~2.5 MP; export ≤ 1 s per 24 MP JPEG.
-- Never commit media: test images are procedural (`lightcraft-scenes`) or CC0 downloads in the gitignored `corpus/`.
+- Never commit media: test images are procedural (`dac-scenes`) or CC0 downloads in the gitignored `corpus/`.
## Map of the code
`geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine`
diff --git a/CLAUDE.md b/CLAUDE.md
index 994920d3d8..7e678f38b4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -73,8 +73,8 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** Don't commit
font files here (Inter, already in `assets/fonts/`, is the one exception); add new fonts to craft-fonts. LightCraft
uses it as the optional build input `CRAFT_FONTS_DIR`: `git clone https://github.com/storytold/craft-fonts ../craft-fonts`
- then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p lightcraft` (or any cargo/xtask command). `crates/engine/build.rs`
- embeds the manifest's fonts as `lightcraft_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web`
+ then `CRAFT_FONTS_DIR=../craft-fonts cargo run -p dac-app` (or any cargo/xtask command). `crates/engine/build.rs`
+ embeds the manifest's fonts as `dac_engine::CRAFT_FONTS` (wasm32: BIZ UDPGothic Regular only); `cargo xtask web`
ships Noto Sans CJK SC Regular as a separate asset loaded by the UI before startup. The UI
(`theme::font_definitions`) and the export watermark renderer use CJK faces (picked by script) as fallbacks after Inter. Unset,
`CRAFT_FONTS` is empty: everything builds, tests and runs, but Chinese and Japanese text lack glyphs. Releases always build
@@ -82,7 +82,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
without it; the FreeBSD CI job runs them with it. Rules: craftrules
[`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md).
- Icons drawn in code (e.g. `crates/ui-egui/src/icons.rs`) are original work and are recorded in `assets/ATTRIBUTION.md` as such; do not trace them from Adobe icons.
-- Demo/test images: generated procedurally by `lightcraft-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published.
+- Demo/test images: generated procedurally by `dac-scenes`, or CC0 downloads kept in the gitignored `corpus/` with their source recorded. Screenshots of Adobe apps live only in the gitignored `plan/` and are never committed or published.
- **Enforced:** `cargo xtask assets` (in `ci`) fails when an image/icon/font/sound/video/raw/ICC/XMP file is not matched
by a path pattern in the first column of `assets/ATTRIBUTION.md`, when a referenced licence file is missing, when a
file name suggests Adobe material, or when an Adobe profile/template format (`.dcp`, `.lcp`, `.lrtemplate`, …) appears.
@@ -90,7 +90,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- When in doubt about an asset's licence: don't use it.
## Running and looking at the app
-- `cargo run --release -p lightcraft -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`).
+- `cargo run --release -p dac-app -- --control 7980` opens the desktop app with the JSON-lines control server (see `docs/control-protocol.md`).
- **Never send OS-level synthetic input** (osascript/System Events keystrokes or clicks, `cliclick`, accessibility
automation): it goes to whatever window is frontmost — the user's terminal or other apps. Drive LightCraft only
through its control channel (`ui.key`, `ui.pointer`, `ui.clickWidget`, `ui.menu.invoke`) or headless snapshots.
@@ -101,7 +101,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
`ui.screenshot {"path": …, "headless": true}`; windowed screenshots fall back to headless after 2 s.
- MCP: `lightcraft-cli mcp` (headless, `--demo` for the procedural library) or `lightcraft-cli mcp --connect` (drives
the running app). See `docs/mcp.md`. Quick non-UI checks: `lightcraft-cli render in.jpg -o out.jpg --set light.exposure=1`.
-- Export goes through `lightcraft_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in
+- Export goes through `dac_engine::export` (one encoder for app, CLI, MCP and web); UI-only commands live in
`crates/ui-egui/src/menus.rs`.
- Shell gotcha: `mv`/`cp` are aliased interactive here — use `/bin/mv -f` / `/bin/cp -f`.
- Parallel agents: separate git worktrees and `CARGO_TARGET_DIR=target/agent-`; each agent uses its **own control port** (pick one in 18000–19999, never the default 7980) and its own scratch subfolder (`//`) — never `rm -rf` shared paths; delete your target dir when done (disk is shared); keep every `Cargo.toml` valid at all times (the `crates/*` glob means one broken manifest breaks everyone).
@@ -110,7 +110,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
## Testing & performance (do this often)
- Unit/property tests next to the code; end-to-end tests drive real binaries (`crates/mcp/tests/e2e.rs`,
- `apps/lightcraft-cli/tests/`). New features need at least one test that would fail without them.
+ `apps/cli/tests/`). New features need at least one test that would fail without them.
- **Run the app after every user-visible change**: launch with `--control 7980`, drive it with a JSON-lines script
(`docs/showcase/run.py file.jsonl`), take `ui.screenshot`, and look at it. Check `ui.inspect` → `perf`.
- **Benchmarks:** `cargo xtask bench` (24 MP raw from corpus; CPU and GPU columns) appends to `target/bench/history.jsonl`
@@ -118,7 +118,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- **Measure, don't guess**: `LIGHTCRAFT_PROFILE=1` prints per-stage pipeline timings to stderr; time CLI renders
with `/usr/bin/time`. Record numbers in `plan/STATUS.md` → Metrics. Budgets: slider update ≤ 16 ms (draft) / loupe
≤ 60 ms on ~2.5 MP; export ≤ 1 s per 24 MP JPEG.
-- Never commit media: test images are procedural (`lightcraft-scenes`) or CC0 downloads in the gitignored `corpus/`.
+- Never commit media: test images are procedural (`dac-scenes`) or CC0 downloads in the gitignored `corpus/`.
## Map of the code
`geom`, `color`, `raster`, `tiff` (L0) → `raw`, `codecs`, `meta`, `develop` (L1) → `pipeline` → `catalog` → `engine`
diff --git a/Cargo.lock b/Cargo.lock
index 45d8ff15c9..0ee1920db6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1175,6 +1175,410 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "dac-app"
+version = "0.4.0"
+dependencies = [
+ "dac-codecs",
+ "dac-engine",
+ "dac-raster",
+ "dac-sysmem",
+ "dac-ui-egui",
+ "eframe",
+ "egui",
+ "log",
+ "muda",
+ "rfd",
+ "serde_json",
+ "winresource",
+]
+
+[[package]]
+name = "dac-catalog"
+version = "0.4.0"
+dependencies = [
+ "crc32fast",
+ "dac-develop",
+ "dac-meta",
+ "log",
+ "proptest",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+ "web-time",
+]
+
+[[package]]
+name = "dac-cli"
+version = "0.4.0"
+dependencies = [
+ "dac-codecs",
+ "dac-engine",
+ "dac-mcp",
+ "dac-merge",
+ "dac-raster",
+ "dac-sysmem",
+ "dac-ui-egui",
+ "dhat",
+ "serde_json",
+]
+
+[[package]]
+name = "dac-codecs"
+version = "0.4.0"
+dependencies = [
+ "bytemuck",
+ "dac-color",
+ "dac-geom",
+ "dac-heif",
+ "dac-raster",
+ "image",
+ "image-webp",
+ "jpeg-decoder",
+ "jpeg-encoder",
+ "jxl-oxide",
+ "moxcms 0.9.1",
+ "png",
+ "proptest",
+ "ravif",
+ "rayon",
+ "serde",
+ "thiserror 2.0.21",
+ "tiff",
+ "zune-core",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "dac-color"
+version = "0.4.0"
+dependencies = [
+ "dac-geom",
+ "proptest",
+ "serde",
+]
+
+[[package]]
+name = "dac-denoise"
+version = "0.4.0"
+dependencies = [
+ "dac-denoise-core",
+ "dac-raster",
+ "faer-core",
+ "miniz_oxide 0.8.9",
+ "proptest",
+ "rayon",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.21",
+ "web-time",
+]
+
+[[package]]
+name = "dac-denoise-core"
+version = "0.4.0"
+dependencies = [
+ "dac-raster",
+ "miniz_oxide 0.8.9",
+ "proptest",
+ "rayon",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.21",
+ "web-time",
+]
+
+[[package]]
+name = "dac-develop"
+version = "0.4.0"
+dependencies = [
+ "dac-color",
+ "dac-geom",
+ "miniz_oxide 0.8.9",
+ "proptest",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "dac-engine"
+version = "0.4.0"
+dependencies = [
+ "ab_glyph",
+ "dac-catalog",
+ "dac-codecs",
+ "dac-color",
+ "dac-denoise",
+ "dac-denoise-core",
+ "dac-develop",
+ "dac-faces",
+ "dac-fetch",
+ "dac-geom",
+ "dac-gpu",
+ "dac-merge",
+ "dac-meta",
+ "dac-pipeline",
+ "dac-preview",
+ "dac-raster",
+ "dac-raw",
+ "dac-scenes",
+ "dac-segment",
+ "dac-tiff",
+ "harfrust",
+ "libc",
+ "log",
+ "miniz_oxide 0.8.9",
+ "rayon",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+ "unicode-segmentation",
+ "web-time",
+]
+
+[[package]]
+name = "dac-faces"
+version = "0.4.0"
+dependencies = [
+ "matrixmultiply",
+ "ndarray",
+ "proptest",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.21",
+ "web-time",
+]
+
+[[package]]
+name = "dac-fetch"
+version = "0.4.0"
+dependencies = [
+ "log",
+ "rustls",
+ "rustls-rustcrypto",
+ "sha2",
+ "webpki-roots",
+]
+
+[[package]]
+name = "dac-geom"
+version = "0.4.0"
+dependencies = [
+ "proptest",
+ "serde",
+]
+
+[[package]]
+name = "dac-gpu"
+version = "0.4.0"
+dependencies = [
+ "bytemuck",
+ "dac-color",
+ "dac-denoise",
+ "dac-develop",
+ "dac-geom",
+ "dac-pipeline",
+ "dac-raster",
+ "dac-scenes",
+ "log",
+ "pollster 1.0.1",
+ "wgpu",
+]
+
+[[package]]
+name = "dac-heif"
+version = "0.4.0"
+dependencies = [
+ "heic-rs",
+]
+
+[[package]]
+name = "dac-mcp"
+version = "0.4.0"
+dependencies = [
+ "dac-codecs",
+ "dac-engine",
+ "dac-raster",
+ "log",
+ "serde_json",
+]
+
+[[package]]
+name = "dac-merge"
+version = "0.4.0"
+dependencies = [
+ "dac-codecs",
+ "dac-color",
+ "dac-geom",
+ "dac-meta",
+ "dac-raster",
+ "dac-raw",
+ "dac-scenes",
+ "rayon",
+ "serde",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "dac-meta"
+version = "0.4.0"
+dependencies = [
+ "dac-geom",
+ "dac-tiff",
+ "proptest",
+ "quick-xml 0.38.4",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "dac-pipeline"
+version = "0.4.0"
+dependencies = [
+ "dac-color",
+ "dac-develop",
+ "dac-geom",
+ "dac-raster",
+ "dac-raw",
+ "dac-scenes",
+ "image",
+ "proptest",
+ "rayon",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "dac-preview"
+version = "0.4.0"
+dependencies = [
+ "dac-raster",
+ "jpeg-encoder",
+ "log",
+ "siphasher",
+ "zune-core",
+ "zune-jpeg",
+]
+
+[[package]]
+name = "dac-raster"
+version = "0.4.0"
+dependencies = [
+ "dac-color",
+ "dac-geom",
+ "libc",
+ "proptest",
+ "rayon",
+ "serde",
+]
+
+[[package]]
+name = "dac-raw"
+version = "0.4.0"
+dependencies = [
+ "dac-color",
+ "dac-geom",
+ "dac-meta",
+ "dac-raster",
+ "dac-tiff",
+ "jpeg-encoder",
+ "jxl-oxide",
+ "miniz_oxide 0.8.9",
+ "proptest",
+ "rayon",
+ "serde",
+ "sha2",
+ "thiserror 2.0.21",
+ "zune-core",
+ "zune-jpeg",
+ "zune-jpegxl",
+]
+
+[[package]]
+name = "dac-scenes"
+version = "0.4.0"
+dependencies = [
+ "dac-color",
+ "dac-raster",
+ "image",
+ "rayon",
+ "serde",
+]
+
+[[package]]
+name = "dac-segment"
+version = "0.4.0"
+dependencies = [
+ "candle-core",
+ "candle-nn",
+ "dac-fetch",
+ "log",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "dac-sysmem"
+version = "0.4.0"
+
+[[package]]
+name = "dac-tiff"
+version = "0.4.0"
+dependencies = [
+ "proptest",
+ "serde",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "dac-ui-egui"
+version = "0.4.0"
+dependencies = [
+ "dac-catalog",
+ "dac-codecs",
+ "dac-color",
+ "dac-denoise",
+ "dac-develop",
+ "dac-engine",
+ "dac-faces",
+ "dac-geom",
+ "dac-meta",
+ "dac-pipeline",
+ "dac-preview",
+ "dac-raster",
+ "egui",
+ "log",
+ "serde",
+ "serde_json",
+ "toml",
+ "web-time",
+]
+
+[[package]]
+name = "dac-web"
+version = "0.4.0"
+dependencies = [
+ "crc32fast",
+ "dac-codecs",
+ "dac-engine",
+ "dac-preview",
+ "dac-raster",
+ "dac-ui-egui",
+ "eframe",
+ "egui",
+ "js-sys",
+ "log",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "der"
version = "0.7.10"
@@ -2974,410 +3378,6 @@ dependencies = [
"redox_syscall 0.9.4",
]
-[[package]]
-name = "lightcraft"
-version = "0.4.0"
-dependencies = [
- "eframe",
- "egui",
- "lightcraft-codecs",
- "lightcraft-engine",
- "lightcraft-raster",
- "lightcraft-sysmem",
- "lightcraft-ui-egui",
- "log",
- "muda",
- "rfd",
- "serde_json",
- "winresource",
-]
-
-[[package]]
-name = "lightcraft-catalog"
-version = "0.4.0"
-dependencies = [
- "crc32fast",
- "lightcraft-develop",
- "lightcraft-meta",
- "log",
- "proptest",
- "serde",
- "serde_json",
- "thiserror 2.0.21",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-cli"
-version = "0.4.0"
-dependencies = [
- "dhat",
- "lightcraft-codecs",
- "lightcraft-engine",
- "lightcraft-mcp",
- "lightcraft-merge",
- "lightcraft-raster",
- "lightcraft-sysmem",
- "lightcraft-ui-egui",
- "serde_json",
-]
-
-[[package]]
-name = "lightcraft-codecs"
-version = "0.4.0"
-dependencies = [
- "bytemuck",
- "image",
- "image-webp",
- "jpeg-decoder",
- "jpeg-encoder",
- "jxl-oxide",
- "lightcraft-color",
- "lightcraft-geom",
- "lightcraft-heif",
- "lightcraft-raster",
- "moxcms 0.9.1",
- "png",
- "proptest",
- "ravif",
- "rayon",
- "serde",
- "thiserror 2.0.21",
- "tiff",
- "zune-core",
- "zune-jpeg",
-]
-
-[[package]]
-name = "lightcraft-color"
-version = "0.4.0"
-dependencies = [
- "lightcraft-geom",
- "proptest",
- "serde",
-]
-
-[[package]]
-name = "lightcraft-denoise"
-version = "0.4.0"
-dependencies = [
- "faer-core",
- "lightcraft-denoise-core",
- "lightcraft-raster",
- "miniz_oxide 0.8.9",
- "proptest",
- "rayon",
- "serde",
- "serde_json",
- "sha2",
- "thiserror 2.0.21",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-denoise-core"
-version = "0.4.0"
-dependencies = [
- "lightcraft-raster",
- "miniz_oxide 0.8.9",
- "proptest",
- "rayon",
- "serde",
- "serde_json",
- "sha2",
- "thiserror 2.0.21",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-develop"
-version = "0.4.0"
-dependencies = [
- "lightcraft-color",
- "lightcraft-geom",
- "miniz_oxide 0.8.9",
- "proptest",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "lightcraft-engine"
-version = "0.4.0"
-dependencies = [
- "ab_glyph",
- "harfrust",
- "libc",
- "lightcraft-catalog",
- "lightcraft-codecs",
- "lightcraft-color",
- "lightcraft-denoise",
- "lightcraft-denoise-core",
- "lightcraft-develop",
- "lightcraft-faces",
- "lightcraft-fetch",
- "lightcraft-geom",
- "lightcraft-gpu",
- "lightcraft-merge",
- "lightcraft-meta",
- "lightcraft-pipeline",
- "lightcraft-preview",
- "lightcraft-raster",
- "lightcraft-raw",
- "lightcraft-scenes",
- "lightcraft-segment",
- "lightcraft-tiff",
- "log",
- "miniz_oxide 0.8.9",
- "rayon",
- "serde",
- "serde_json",
- "thiserror 2.0.21",
- "unicode-segmentation",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-faces"
-version = "0.4.0"
-dependencies = [
- "matrixmultiply",
- "ndarray",
- "proptest",
- "serde",
- "serde_json",
- "sha2",
- "thiserror 2.0.21",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-fetch"
-version = "0.4.0"
-dependencies = [
- "log",
- "rustls",
- "rustls-rustcrypto",
- "sha2",
- "webpki-roots",
-]
-
-[[package]]
-name = "lightcraft-geom"
-version = "0.4.0"
-dependencies = [
- "proptest",
- "serde",
-]
-
-[[package]]
-name = "lightcraft-gpu"
-version = "0.4.0"
-dependencies = [
- "bytemuck",
- "lightcraft-color",
- "lightcraft-denoise",
- "lightcraft-develop",
- "lightcraft-geom",
- "lightcraft-pipeline",
- "lightcraft-raster",
- "lightcraft-scenes",
- "log",
- "pollster 1.0.1",
- "wgpu",
-]
-
-[[package]]
-name = "lightcraft-heif"
-version = "0.4.0"
-dependencies = [
- "heic-rs",
-]
-
-[[package]]
-name = "lightcraft-mcp"
-version = "0.4.0"
-dependencies = [
- "lightcraft-codecs",
- "lightcraft-engine",
- "lightcraft-raster",
- "log",
- "serde_json",
-]
-
-[[package]]
-name = "lightcraft-merge"
-version = "0.4.0"
-dependencies = [
- "lightcraft-codecs",
- "lightcraft-color",
- "lightcraft-geom",
- "lightcraft-meta",
- "lightcraft-raster",
- "lightcraft-raw",
- "lightcraft-scenes",
- "rayon",
- "serde",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "lightcraft-meta"
-version = "0.4.0"
-dependencies = [
- "lightcraft-geom",
- "lightcraft-tiff",
- "proptest",
- "quick-xml 0.38.4",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "lightcraft-pipeline"
-version = "0.4.0"
-dependencies = [
- "image",
- "lightcraft-color",
- "lightcraft-develop",
- "lightcraft-geom",
- "lightcraft-raster",
- "lightcraft-raw",
- "lightcraft-scenes",
- "proptest",
- "rayon",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "lightcraft-preview"
-version = "0.4.0"
-dependencies = [
- "jpeg-encoder",
- "lightcraft-raster",
- "log",
- "siphasher",
- "zune-core",
- "zune-jpeg",
-]
-
-[[package]]
-name = "lightcraft-raster"
-version = "0.4.0"
-dependencies = [
- "libc",
- "lightcraft-color",
- "lightcraft-geom",
- "proptest",
- "rayon",
- "serde",
-]
-
-[[package]]
-name = "lightcraft-raw"
-version = "0.4.0"
-dependencies = [
- "jpeg-encoder",
- "jxl-oxide",
- "lightcraft-color",
- "lightcraft-geom",
- "lightcraft-meta",
- "lightcraft-raster",
- "lightcraft-tiff",
- "miniz_oxide 0.8.9",
- "proptest",
- "rayon",
- "serde",
- "sha2",
- "thiserror 2.0.21",
- "zune-core",
- "zune-jpeg",
- "zune-jpegxl",
-]
-
-[[package]]
-name = "lightcraft-scenes"
-version = "0.4.0"
-dependencies = [
- "image",
- "lightcraft-color",
- "lightcraft-raster",
- "rayon",
- "serde",
-]
-
-[[package]]
-name = "lightcraft-segment"
-version = "0.4.0"
-dependencies = [
- "candle-core",
- "candle-nn",
- "lightcraft-fetch",
- "log",
- "serde",
- "serde_json",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "lightcraft-sysmem"
-version = "0.4.0"
-
-[[package]]
-name = "lightcraft-tiff"
-version = "0.4.0"
-dependencies = [
- "proptest",
- "serde",
- "thiserror 2.0.21",
-]
-
-[[package]]
-name = "lightcraft-ui-egui"
-version = "0.4.0"
-dependencies = [
- "egui",
- "lightcraft-catalog",
- "lightcraft-codecs",
- "lightcraft-color",
- "lightcraft-denoise",
- "lightcraft-develop",
- "lightcraft-engine",
- "lightcraft-faces",
- "lightcraft-geom",
- "lightcraft-meta",
- "lightcraft-pipeline",
- "lightcraft-preview",
- "lightcraft-raster",
- "log",
- "serde",
- "serde_json",
- "toml",
- "web-time",
-]
-
-[[package]]
-name = "lightcraft-web"
-version = "0.4.0"
-dependencies = [
- "crc32fast",
- "eframe",
- "egui",
- "js-sys",
- "lightcraft-codecs",
- "lightcraft-engine",
- "lightcraft-preview",
- "lightcraft-raster",
- "lightcraft-ui-egui",
- "log",
- "serde",
- "serde_json",
- "wasm-bindgen",
- "wasm-bindgen-futures",
- "web-sys",
-]
-
[[package]]
name = "linebender_resource_handle"
version = "0.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 5f7c85db6b..93840caeb5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,30 +11,30 @@ repository = "https://github.com/storytold/lightcraft"
[workspace.dependencies]
# internal
-lightcraft-denoise-core = { path = "crates/denoise-core" }
-lightcraft-denoise = { path = "crates/denoise", default-features = false }
-lightcraft-geom = { path = "crates/geom" }
-lightcraft-faces = { path = "crates/faces" }
-lightcraft-sysmem = { path = "crates/sysmem" }
-lightcraft-color = { path = "crates/color" }
-lightcraft-raster = { path = "crates/raster" }
-lightcraft-tiff = { path = "crates/tiff" }
-lightcraft-raw = { path = "crates/raw" }
-lightcraft-codecs = { path = "crates/codecs" }
-lightcraft-meta = { path = "crates/meta" }
-lightcraft-develop = { path = "crates/develop" }
-lightcraft-scenes = { path = "crates/scenes" }
-lightcraft-pipeline = { path = "crates/pipeline" }
-lightcraft-gpu = { path = "crates/gpu" }
-lightcraft-catalog = { path = "crates/catalog" }
-lightcraft-preview = { path = "crates/preview" }
-lightcraft-merge = { path = "crates/merge" }
-lightcraft-engine = { path = "crates/engine" }
-lightcraft-ui-egui = { path = "crates/ui-egui" }
-lightcraft-mcp = { path = "crates/mcp" }
-lightcraft-fetch = { path = "crates/fetch" }
-lightcraft-heif = { path = "crates/heif" }
-lightcraft-segment = { path = "crates/segment" }
+dac-denoise-core = { path = "crates/denoise-core" }
+dac-denoise = { path = "crates/denoise", default-features = false }
+dac-geom = { path = "crates/geom" }
+dac-faces = { path = "crates/faces" }
+dac-sysmem = { path = "crates/sysmem" }
+dac-color = { path = "crates/color" }
+dac-raster = { path = "crates/raster" }
+dac-tiff = { path = "crates/tiff" }
+dac-raw = { path = "crates/raw" }
+dac-codecs = { path = "crates/codecs" }
+dac-meta = { path = "crates/meta" }
+dac-develop = { path = "crates/develop" }
+dac-scenes = { path = "crates/scenes" }
+dac-pipeline = { path = "crates/pipeline" }
+dac-gpu = { path = "crates/gpu" }
+dac-catalog = { path = "crates/catalog" }
+dac-preview = { path = "crates/preview" }
+dac-merge = { path = "crates/merge" }
+dac-engine = { path = "crates/engine" }
+dac-ui-egui = { path = "crates/ui-egui" }
+dac-mcp = { path = "crates/mcp" }
+dac-fetch = { path = "crates/fetch" }
+dac-heif = { path = "crates/heif" }
+dac-segment = { path = "crates/segment" }
# foundation
thiserror = "2"
@@ -70,7 +70,7 @@ zune-jpegxl = { version = "0.5", default-features = false, features = ["std"] }
ravif = { version = "0.13", default-features = false, features = ["threading"] } # no "asm": it needs nasm (non-Rust)
# gpu (crates/gpu; native backends only, no GL: everything stays pure Rust)
-# SAM 3 inference (lightcraft-segment). 0.9.2: later releases pull `tokenizers` with the
+# SAM 3 inference (dac-segment). 0.9.2: later releases pull `tokenizers` with the
# Oniguruma C library into candle-core, and the product is pure Rust.
candle-core = { version = "=0.9.2", default-features = false }
candle-nn = { version = "=0.9.2", default-features = false }
@@ -86,7 +86,7 @@ rfd = "0.17"
muda = { version = "0.21", default-features = false } # native menu bar (macOS: objc2/AppKit, pure Rust; no GTK)
web-time = "1"
-# web (apps/lightcraft-web)
+# web (apps/web)
wasm-bindgen = "0.2.129"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
@@ -132,15 +132,15 @@ panic = "abort"
opt-level = "s"
[profile.web.package]
-lightcraft-pipeline = { opt-level = 3 }
-lightcraft-raster = { opt-level = 3 }
-lightcraft-color = { opt-level = 3 }
-lightcraft-develop = { opt-level = 3 }
-lightcraft-geom = { opt-level = 3 }
-lightcraft-raw = { opt-level = 3 }
-lightcraft-codecs = { opt-level = 3 }
-lightcraft-scenes = { opt-level = 3 }
-lightcraft-preview = { opt-level = 3 }
+dac-pipeline = { opt-level = 3 }
+dac-raster = { opt-level = 3 }
+dac-color = { opt-level = 3 }
+dac-develop = { opt-level = 3 }
+dac-geom = { opt-level = 3 }
+dac-raw = { opt-level = 3 }
+dac-codecs = { opt-level = 3 }
+dac-scenes = { opt-level = 3 }
+dac-preview = { opt-level = 3 }
zune-jpeg = { opt-level = 3 }
zune-core = { opt-level = 3 }
jpeg-decoder = { opt-level = 3 }
diff --git a/NOTICE b/NOTICE
index 254e262800..b4115d0b6b 100644
--- a/NOTICE
+++ b/NOTICE
@@ -15,7 +15,7 @@ list (author, source, license) is in assets/ATTRIBUTION.md.
Inter (assets/fonts/Inter-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-Inter.txt.
Showcase photographs by Ansel Adams, Dorothea Lange and NASA (docs/images/) — public domain. See assets/ATTRIBUTION.md.
-crates/segment (lightcraft-segment) ports the SAM 3 model code of Hugging Face Transformers
+crates/segment (dac-segment) ports the SAM 3 model code of Hugging Face Transformers
(https://github.com/huggingface/transformers, src/transformers/models/sam3 and sam3_tracker),
Copyright The HuggingFace Team and Meta Platforms, Inc., licensed under the Apache License 2.0.
The port was modified by the LightCraft contributors (2026): translated from Python/PyTorch to
diff --git a/README.md b/README.md
index bca63f8a8a..0152518f48 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@
- LightCraft on getartcraft.com ·
+ LightCraft on getartcraft.com ·
ArtCraft ·
All Crafting Apps
@@ -241,7 +241,7 @@ lightcraft --control 7980 ~/Pictures/trip
to the running app with screenshots, clicks and gestures. See [docs/mcp.md](docs/mcp.md).
```sh
- cargo build --release -p lightcraft-cli
+ cargo build --release -p dac-cli
claude mcp add lightcraft -- "$PWD/target/release/lightcraft-cli" mcp ~/Pictures/shoot # headless
claude mcp add lightcraft-app -- "$PWD/target/release/lightcraft-cli" mcp --connect 127.0.0.1:7980 # live app
```
@@ -327,12 +327,12 @@ LightCraft is young and moving fast. **Where we honestly stand** (details in the
```sh
git clone https://github.com/storytold/lightcraft && cd lightcraft
-cargo run --release -p lightcraft # opens your library (~/Pictures/LightCraft Library; a new one starts with demo photos)
-cargo run --release -p lightcraft -- ~/Pictures/trip # import your photos (folders are scanned, duplicates skipped)
-cargo run --release -p lightcraft -- --memory # a throwaway in-memory demo session (writes nothing)
-cargo run --release -p lightcraft -- --control 7980 # with the automation channel
+cargo run --release -p dac-app # opens your library (~/Pictures/LightCraft Library; a new one starts with demo photos)
+cargo run --release -p dac-app -- ~/Pictures/trip # import your photos (folders are scanned, duplicates skipped)
+cargo run --release -p dac-app -- --memory # a throwaway in-memory demo session (writes nothing)
+cargo run --release -p dac-app -- --control 7980 # with the automation channel
cargo xtask web --serve # the same app in the browser: http://127.0.0.1:8080/
-cargo run --release -p lightcraft-cli -- render photo.jpg -o out.jpg --set light.exposure=0.5
+cargo run --release -p dac-cli -- render photo.jpg -o out.jpg --set light.exposure=0.5
cargo xtask ci # fmt, clippy, tests, layering, wasm checks
```
@@ -348,7 +348,7 @@ override these defaults. Ordinary development commands keep their own settings.
```sh
git clone https://github.com/storytold/craft-fonts ../craft-fonts
-CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p lightcraft
+CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p dac-app
```
Without it LightCraft builds and runs the same, but Chinese and Japanese text have no glyphs. Fonts are never committed to this
@@ -367,9 +367,9 @@ next start; the file stops growing at 16 MiB, `--version` and `--help` write non
`LIGHTCRAFT_NO_PREFS` log to standard error only. By default LightCraft's own crates log at `info` and everything else
at `warn`. `LIGHTCRAFT_LOG=info` or `debug` works as before (that level for LightCraft's own crates, warnings and
errors from the rest; any other value: warnings and errors only) and wins over `RUST_LOG`, which otherwise replaces
-the default with env_logger-style directives such as `RUST_LOG=debug` or `RUST_LOG=warn,lightcraft_pipeline=trace` (a
+the default with env_logger-style directives such as `RUST_LOG=debug` or `RUST_LOG=warn,dac_pipeline=trace` (a
directive ending in `*` covers every target starting with it, as in `lightcraft*=debug`). Panics are recorded there
-too, and still in `lightcraft-panics.log` in the temp folder. The logger is `apps/lightcraft/src/logging.rs`.
+too, and still in `lightcraft-panics.log` in the temp folder. The logger is `apps/app/src/logging.rs`.
The web build needs the `wasm32-unknown-unknown` target and the matching `wasm-bindgen` CLI
(`cargo xtask web` prints the exact install command); see [docs/web.md](docs/web.md).
@@ -432,7 +432,7 @@ Questions, ideas or a bug you'd like to talk through first? Bring them to [Disco
## Downloads
-**New to LightCraft?** Download it from the [LightCraft page on getartcraft.com](https://getartcraft.com/apps/lightcraft). That's the easiest way to install it.
+**New to LightCraft?** Download it from the [LightCraft page on getartcraft.com](https://getartcraft.com/apps/app). That's the easiest way to install it.
**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/lightcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/lightcraft/releases) has earlier versions and their notes. `` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
@@ -488,7 +488,7 @@ stand on its own.
|
| **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
| **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
| **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
-|
| **LightCraft** | **Photo library and raw development · you are here** | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
+|
| **LightCraft** | **Photo library and raw development · you are here** | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/app) |
|
| **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
| **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
| **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
@@ -514,7 +514,7 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud
discord.gg/artcraft ·
getartcraft.com ·
The Crafting Apps ·
- LightCraft
+ LightCraft
diff --git a/apps/lightcraft/Cargo.toml b/apps/app/Cargo.toml
similarity index 58%
rename from apps/lightcraft/Cargo.toml
rename to apps/app/Cargo.toml
index e4d7f2b3b3..6152c0ec96 100644
--- a/apps/lightcraft/Cargo.toml
+++ b/apps/app/Cargo.toml
@@ -1,28 +1,32 @@
[package]
-name = "lightcraft"
+name = "dac-app"
version.workspace = true
edition.workspace = true
license.workspace = true
publish = false
-default-run = "lightcraft"
+default-run = "app"
+
+[[bin]]
+name = "app"
+path = "src/main.rs"
[lints]
workspace = true
[features]
default = ["denoise"]
-denoise = ["lightcraft-engine/denoise"]
-rawnind-model = ["denoise", "lightcraft-engine/rawnind-model"]
-# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose
+denoise = ["dac-engine/denoise"]
+rawnind-model = ["denoise", "dac-engine/rawnind-model"]
+# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose
# it (HEVC patents); build with `--features heif` to include it.
-heif = ["lightcraft-codecs/heif"]
+heif = ["dac-codecs/heif"]
[dependencies]
-lightcraft-engine = { workspace = true, features = ["sam"] }
-lightcraft-sysmem = { workspace = true }
-lightcraft-ui-egui = { workspace = true }
-lightcraft-codecs = { workspace = true }
-lightcraft-raster = { workspace = true }
+dac-engine = { workspace = true, features = ["sam"] }
+dac-sysmem = { workspace = true }
+dac-ui-egui = { workspace = true }
+dac-codecs = { workspace = true }
+dac-raster = { workspace = true }
eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11"] }
egui = { workspace = true }
serde_json = { workspace = true }
diff --git a/apps/lightcraft/build.rs b/apps/app/build.rs
similarity index 100%
rename from apps/lightcraft/build.rs
rename to apps/app/build.rs
diff --git a/apps/lightcraft/examples/gen_photos.rs b/apps/app/examples/gen_photos.rs
similarity index 79%
rename from apps/lightcraft/examples/gen_photos.rs
rename to apps/app/examples/gen_photos.rs
index ee7ba9a5eb..539a614a20 100644
--- a/apps/lightcraft/examples/gen_photos.rs
+++ b/apps/app/examples/gen_photos.rs
@@ -1,12 +1,12 @@
//! Generate a folder of procedural test photos (JPEG) for import/startup benchmarks.
//!
-//! `cargo run --release -p lightcraft --example gen_photos -- OUT_DIR [COUNT=200] [LONG_EDGE=2000]`
+//! `cargo run --release -p dac-app --example gen_photos -- OUT_DIR [COUNT=200] [LONG_EDGE=2000]`
//!
-//! Each photo is a `lightcraft-scenes` demo scene rendered through the pipeline with a varied
+//! Each photo is a `dac-scenes` demo scene rendered through the pipeline with a varied
//! look (exposure, temperature, vibrance), so every file has distinct bytes. The output is our own
//! procedural content; keep it out of the repository (write to a scratch or gitignored folder).
-use lightcraft_engine::Session;
+use dac_engine::Session;
use serde_json::json;
fn main() {
@@ -30,11 +30,11 @@ fn main() {
});
let _ = s.execute("develop.set", &json!({"values": v}));
let img = s.render_now(id, edge, edge).expect("render");
- let bytes = lightcraft_codecs::encode_jpeg(
- &lightcraft_codecs::EncodeImage::rgba8(&img.image),
+ let bytes = dac_codecs::encode_jpeg(
+ &dac_codecs::EncodeImage::rgba8(&img.image),
88,
- lightcraft_codecs::ChromaSubsampling::S420,
- &lightcraft_codecs::EncodeMeta::default(),
+ dac_codecs::ChromaSubsampling::S420,
+ &dac_codecs::EncodeMeta::default(),
)
.expect("encode");
let dir = out.join(format!("roll-{:02}", i / 50 + 1));
diff --git a/apps/app/src/alloc_release.rs b/apps/app/src/alloc_release.rs
new file mode 100644
index 0000000000..d396ebc11b
--- /dev/null
+++ b/apps/app/src/alloc_release.rs
@@ -0,0 +1,12 @@
+//! Give memory the system allocator keeps after frees back to the system
+//! ([`dac_engine::memory::set_release_hook`]). The platform call lives in
+//! `dac-sysmem`, the workspace's only crate allowed `unsafe`.
+
+/// Install the hook.
+pub fn install() {
+ dac_engine::memory::set_release_hook(release);
+}
+
+fn release() {
+ let _ = dac_sysmem::release_free_memory();
+}
diff --git a/apps/lightcraft/src/control_server.rs b/apps/app/src/control_server.rs
similarity index 99%
rename from apps/lightcraft/src/control_server.rs
rename to apps/app/src/control_server.rs
index 16bd5df0fa..64b62ca01b 100644
--- a/apps/lightcraft/src/control_server.rs
+++ b/apps/app/src/control_server.rs
@@ -14,7 +14,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::Duration;
-use lightcraft_ui_egui::ControlRequest;
+use dac_ui_egui::ControlRequest;
use serde_json::{Value, json};
/// Longest accepted request line (bytes, without the newline). Requests are small JSON objects;
diff --git a/apps/lightcraft/src/dialog_filter.rs b/apps/app/src/dialog_filter.rs
similarity index 97%
rename from apps/lightcraft/src/dialog_filter.rs
rename to apps/app/src/dialog_filter.rs
index 87760f7994..d82dece8d9 100644
--- a/apps/lightcraft/src/dialog_filter.rs
+++ b/apps/app/src/dialog_filter.rs
@@ -126,8 +126,8 @@ mod tests {
/// The app's own extension list (the one the folder scan uses) is covered whole.
#[test]
fn import_extensions_are_all_covered_in_both_cases() {
- let pats = dialog_extensions_for(lightcraft_engine::import::EXTENSIONS, true);
- for e in lightcraft_engine::import::EXTENSIONS {
+ let pats = dialog_extensions_for(dac_engine::import::EXTENSIONS, true);
+ for e in dac_engine::import::EXTENSIONS {
assert!(pats.iter().any(|p| glob_ext(p, &e.to_uppercase())), "{e}");
assert!(pats.iter().any(|p| p == e), "{e}");
}
diff --git a/apps/lightcraft/src/logging.rs b/apps/app/src/logging.rs
similarity index 96%
rename from apps/lightcraft/src/logging.rs
rename to apps/app/src/logging.rs
index 718392f359..ac47eb695b 100644
--- a/apps/lightcraft/src/logging.rs
+++ b/apps/app/src/logging.rs
@@ -12,7 +12,7 @@
//! and naga are chatty). `LIGHTCRAFT_LOG` keeps the meaning it had with the old logger: `info` or
//! `debug` lowers LightCraft's own crates to that level, any other value means warnings and errors
//! only. Without it, `RUST_LOG` replaces the default with env_logger-style directives: `debug`,
-//! `warn,lightcraft_pipeline=trace`, `wgpu_core=info`. A directive ending in `*` matches every
+//! `warn,dac_pipeline=trace`, `wgpu_core=info`. A directive ending in `*` matches every
//! target that starts with it (`lightcraft*=debug`).
//!
//! Records logged before the settings folder is known are kept (up to [`MAX_PENDING`]) and
@@ -329,7 +329,7 @@ pub fn record_panics(logger: &'static AppLogger) {
previous(info);
let thread = std::thread::current();
let at = info.location().map(|l| format!(" at {}:{}", l.file(), l.line())).unwrap_or_default();
- let report = format!("panic{at}: {}", lightcraft_engine::guard::panic_message(info.payload()));
+ let report = format!("panic{at}: {}", dac_engine::guard::panic_message(info.payload()));
logger.record_panic(thread.name().unwrap_or("?"), &report);
}));
}
@@ -372,8 +372,8 @@ mod tests {
let f = Filter::parse(DEFAULT_FILTER);
assert_eq!(f.level_for("lightcraft"), LevelFilter::Info);
assert_eq!(f.level_for("lightcraft::control_server"), LevelFilter::Info);
- assert_eq!(f.level_for("lightcraft_engine::guard"), LevelFilter::Info);
- assert_eq!(f.level_for("lightcraft_ui_egui::render"), LevelFilter::Info);
+ assert_eq!(f.level_for("dac_engine::guard"), LevelFilter::Info);
+ assert_eq!(f.level_for("dac_ui_egui::render"), LevelFilter::Info);
assert_eq!(f.level_for("wgpu_core::device"), LevelFilter::Warn);
assert_eq!(f.level_for("naga"), LevelFilter::Warn);
assert_eq!(f.max(), LevelFilter::Info);
@@ -385,8 +385,7 @@ mod tests {
/// `LIGHTCRAFT_LOG` keeps working exactly as it did with the old stderr-only logger.
#[test]
fn lightcraft_log_keeps_its_meaning() {
- let targets =
- ["lightcraft", "lightcraft::control_server", "lightcraft_engine::segment", "lightcraft_gpu", "wgpu_core::device", "naga", "eframe"];
+ let targets = ["dac-app", "lightcraft::control_server", "dac_engine::segment", "dac_gpu", "wgpu_core::device", "naga", "eframe"];
let levels = [log::Level::Error, log::Level::Warn, log::Level::Info, log::Level::Debug, log::Level::Trace];
for value in ["info", "debug", "warn", "error", "verbose", "INFO"] {
let f = Filter::parse(&filter_spec(Some(value), None));
@@ -408,21 +407,21 @@ mod tests {
// The app's own variable is the more specific choice.
let f = Filter::parse(&filter_spec(Some("info"), Some("trace")));
assert_eq!(f.level_for("eframe"), LevelFilter::Warn);
- assert_eq!(f.level_for("lightcraft_engine"), LevelFilter::Info);
+ assert_eq!(f.level_for("dac_engine"), LevelFilter::Info);
}
#[test]
fn directives_follow_env_logger_and_the_most_specific_one_wins() {
- let f = Filter::parse("info,wgpu_core=error,lightcraft_pipeline=trace,lightcraft_pipeline::tiles=off");
+ let f = Filter::parse("info,wgpu_core=error,dac_pipeline=trace,dac_pipeline::tiles=off");
assert_eq!(f.level_for("eframe"), LevelFilter::Info);
assert_eq!(f.level_for("wgpu_core::instance"), LevelFilter::Error);
- assert_eq!(f.level_for("lightcraft_pipeline::stage"), LevelFilter::Trace);
- assert_eq!(f.level_for("lightcraft_pipeline::tiles"), LevelFilter::Off);
+ assert_eq!(f.level_for("dac_pipeline::stage"), LevelFilter::Trace);
+ assert_eq!(f.level_for("dac_pipeline::tiles"), LevelFilter::Off);
assert_eq!(f.max(), LevelFilter::Trace);
// A module name is matched at `::` boundaries, not as a bare prefix.
assert_eq!(f.level_for("wgpu_core_extra"), LevelFilter::Info);
// A trailing `*` is a prefix.
- assert_eq!(Filter::parse("lightcraft*=debug").level_for("lightcraft_raw::cr2"), LevelFilter::Debug);
+ assert_eq!(Filter::parse("lightcraft*=debug").level_for("dac_raw::cr2"), LevelFilter::Debug);
assert_eq!(Filter::parse("lightcraft*=debug").level_for("eframe"), LevelFilter::Error);
// A bare target name sets that target to the most verbose level, as env_logger does.
assert_eq!(Filter::parse("naga").level_for("naga::front"), LevelFilter::Trace);
@@ -598,15 +597,15 @@ mod tests {
let path = logger.attach_dir(&dir).expect("attach");
assert_eq!(path, dir.join(LOG_FILE));
record(&logger, log::Level::Warn, "wgpu_hal::vulkan", "a real warning");
- record(&logger, log::Level::Error, "lightcraft_engine::guard", "`develop.reset` failed unexpectedly: boom");
+ record(&logger, log::Level::Error, "dac_engine::guard", "`develop.reset` failed unexpectedly: boom");
record(&logger, log::Level::Debug, "lightcraft", "below info");
let text = read(&path);
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 3, "{text}");
assert!(lines[0].contains(" INFO [") && lines[0].ends_with("lightcraft: before the settings directory"), "{text}");
assert!(lines[1].ends_with("wgpu_hal::vulkan: a real warning"), "{text}");
- assert!(lines[2].contains(" ERROR [") && lines[2].ends_with("lightcraft_engine::guard: `develop.reset` failed unexpectedly: boom"), "{text}");
- assert!(logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("lightcraft_ui_egui").build()));
+ assert!(lines[2].contains(" ERROR [") && lines[2].ends_with("dac_engine::guard: `develop.reset` failed unexpectedly: boom"), "{text}");
+ assert!(logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("dac_ui_egui").build()));
assert!(!logger.enabled(&log::Metadata::builder().level(log::Level::Info).target("naga").build()));
// Attaching again rotates: the first log becomes `.1`.
logger.attach_dir(&dir).expect("attach again");
diff --git a/apps/lightcraft/src/main.rs b/apps/app/src/main.rs
similarity index 91%
rename from apps/lightcraft/src/main.rs
rename to apps/app/src/main.rs
index 832b158b33..f9cfadf91a 100644
--- a/apps/lightcraft/src/main.rs
+++ b/apps/app/src/main.rs
@@ -9,11 +9,11 @@
//! line are imported (duplicates are skipped). `--memory` runs an in-memory session that writes
//! nothing (demo photos unless files are given; used by the README showcase scripts). A library
//! that can't be opened is never replaced silently: the window says why and asks what to do
-//! (`lightcraft_ui_egui::panels::library_problem`).
+//! (`dac_ui_egui::panels::library_problem`).
//!
//! `--control ` (or `LIGHTCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control server:
//! `{"id":1,"method":"ui.inspect","params":{}}` → `{"id":1,"ok":true,"result":…}`.
-//! See `lightcraft_ui_egui::control` for the methods.
+//! See `dac_ui_egui::control` for the methods.
//!
//! On Windows, release builds are GUI-subsystem programs: launching the app opens no console
//! window (issue #7). Their `--help` / `--version` output and diagnostics then have no console to
@@ -29,10 +29,10 @@ mod logging;
#[cfg(target_os = "macos")]
mod native_menu;
+use dac_engine::Session;
+use dac_ui_egui::panels::library_problem::LibraryProblem;
+use dac_ui_egui::{DacApp, Services, UiState};
use dialog_filter::FileDialogExt as _;
-use lightcraft_engine::Session;
-use lightcraft_ui_egui::panels::library_problem::LibraryProblem;
-use lightcraft_ui_egui::{LightcraftApp, Services, UiState};
/// Reverse-DNS app id: Wayland app id, `.desktop` file and hicolor icon name.
const APP_ID: &str = "ai.storyteller.lightcraft";
@@ -47,14 +47,14 @@ fn app_icon() -> egui::IconData {
eframe::icon_data::from_png_bytes(png).unwrap_or_default()
}
-struct App(LightcraftApp, PrefsWriter, #[cfg(target_os = "macos")] Option);
+struct App(DacApp, PrefsWriter, #[cfg(target_os = "macos")] Option);
impl eframe::App for App {
fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
#[cfg(target_os = "macos")]
if let Some(m) = self.2.as_mut()
&& m.update(&mut self.0, ctx)
- && lightcraft_ui_egui::panels::notices::may_close(&mut self.0)
+ && dac_ui_egui::panels::notices::may_close(&mut self.0)
{
// Quit from the menu bar: save exactly what closing the window saves (settings and
// library, after the unsaved-changes check; otherwise its prompt shows), then end the
@@ -88,14 +88,14 @@ impl eframe::App for App {
fn window_wgpu_options() -> eframe::egui_wgpu::WgpuConfiguration {
let mut c = eframe::egui_wgpu::WgpuConfiguration::default();
if let eframe::egui_wgpu::WgpuSetup::CreateNew(n) = &mut c.wgpu_setup {
- n.instance_descriptor.backends = lightcraft_engine::gpu::backend::window_backends();
- n.instance_descriptor.backend_options = lightcraft_engine::gpu::backend::backend_options();
+ n.instance_descriptor.backends = dac_engine::gpu::backend::window_backends();
+ n.instance_descriptor.backend_options = dac_engine::gpu::backend::backend_options();
}
c
}
/// Written while the GPU compute device is created, removed once that returned
-/// (`lightcraft_gpu::backend::set_init_marker`). Not with `LIGHTCRAFT_NO_PREFS` (tests, scripts).
+/// (`dac_gpu::backend::set_init_marker`). Not with `LIGHTCRAFT_NO_PREFS` (tests, scripts).
fn gpu_marker_path() -> Option {
if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() {
return None;
@@ -114,7 +114,7 @@ fn gpu_crash_check(in_memory: bool) -> Option {
}
fn gpu_crash_check_at(marker: Option, in_memory: bool) -> Option {
- use lightcraft_engine::gpu::backend;
+ use dac_engine::gpu::backend;
let left = if in_memory {
marker.as_deref().and_then(backend::read_init_marker)
} else {
@@ -134,7 +134,7 @@ fn gpu_crash_notice(what: &str) -> String {
}
fn config_dir() -> Option {
- lightcraft_engine::config::config_dir()
+ dac_engine::config::config_dir()
}
/// `/ui.json`, the saved UI state and app settings — `None` when nothing is read or
@@ -234,7 +234,7 @@ struct PrefsWriter {
}
impl PrefsWriter {
- fn save(&mut self, app: &LightcraftApp) -> Result<(), String> {
+ fn save(&mut self, app: &DacApp) -> Result<(), String> {
self.save_ui(&app.ui, app.library_problem.is_some())
}
@@ -258,7 +258,7 @@ impl PrefsWriter {
Ok(())
}
- fn tick(&mut self, app: &mut LightcraftApp, ctx: &egui::Context) {
+ fn tick(&mut self, app: &mut DacApp, ctx: &egui::Context) {
let now = ctx.input(|i| i.time);
let moved = app.ui.settings.library_path != self.library;
if !moved && now - self.checked < 3.0 {
@@ -329,8 +329,8 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
Services {
// Commands' file dialogs run on their own thread (#191): a dialog on the UI thread
// stopped the window answering the compositor, which then reported the app as hung.
- picker: Some(Box::new(move |req: lightcraft_ui_egui::pick::PickRequest| {
- use lightcraft_ui_egui::pick::PickKind;
+ picker: Some(Box::new(move |req: dac_ui_egui::pick::PickRequest| {
+ use dac_ui_egui::pick::PickKind;
let (tx, rx) = std::sync::mpsc::channel();
let repaint = ctx.clone();
std::thread::Builder::new()
@@ -363,13 +363,13 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
log_file: log_file.map(|p| p.display().to_string()),
pick_lightroom_catalog: Some(Box::new(|| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Import Lightroom Catalog"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Lightroom Classic Catalog"), &["lrcat"])
+ .set_title(dac_ui_egui::i18n::tr("Import Lightroom Catalog"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("Lightroom Classic Catalog"), &["lrcat"])
.pick_file()
.map(|p| p.to_string_lossy().to_string())
})),
pick_folder: Some(Box::new(|| {
- rfd::FileDialog::new().set_title(lightcraft_ui_egui::i18n::tr("Open Library")).pick_folder().map(|p| p.to_string_lossy().to_string())
+ rfd::FileDialog::new().set_title(dac_ui_egui::i18n::tr("Open Library")).pick_folder().map(|p| p.to_string_lossy().to_string())
})),
open_with: Some(Box::new(|path: &str, app: &str| {
// spawned, never waited for: the editor runs alongside
@@ -421,7 +421,7 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
})),
pick_files: Some(Box::new(|| {
rfd::FileDialog::new()
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Photos"), lightcraft_engine::import::EXTENSIONS)
+ .add_filter_nocase(dac_ui_egui::i18n::tr("Photos"), dac_engine::import::EXTENSIONS)
.pick_files()
.unwrap_or_default()
.into_iter()
@@ -430,16 +430,16 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
})),
pick_denoise_model: Some(Box::new(|| {
rfd::FileDialog::new()
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("ONNX model"), &["onnx"])
+ .add_filter_nocase(dac_ui_egui::i18n::tr("ONNX model"), &["onnx"])
.pick_file()
.map(|p| vec![p.to_string_lossy().into_owned()])
.unwrap_or_default()
})),
pick_preset_files: Some(Box::new(|| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Import Presets"))
+ .set_title(dac_ui_egui::i18n::tr("Import Presets"))
.add_filter_nocase(
- lightcraft_ui_egui::i18n::tr("Presets & Profiles"),
+ dac_ui_egui::i18n::tr("Presets & Profiles"),
&["lcpreset", "xmp", "lrtemplate", "zip", "dng", "lmp", "mplumpack", "cube"],
)
.pick_files()
@@ -450,32 +450,32 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
})),
pick_model_file: Some(Box::new(|| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Add a Face Recognition Model"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Face models (ONNX)"), &["onnx"])
+ .set_title(dac_ui_egui::i18n::tr("Add a Face Recognition Model"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("Face models (ONNX)"), &["onnx"])
.pick_file()
.map(|p| vec![p.to_string_lossy().to_string()])
.unwrap_or_default()
})),
pick_tracklog: Some(Box::new(|| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Auto-Tag from Tracklog"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("GPS Track Log"), &["gpx"])
+ .set_title(dac_ui_egui::i18n::tr("Auto-Tag from Tracklog"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("GPS Track Log"), &["gpx"])
.pick_file()
.map(|p| vec![p.to_string_lossy().to_string()])
.unwrap_or_default()
})),
save_preset_file: Some(Box::new(|name: &str| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Export Presets"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("LightCraft Preset"), &["lcpreset"])
+ .set_title(dac_ui_egui::i18n::tr("Export Presets"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("LightCraft Preset"), &["lcpreset"])
.set_file_name(name)
.save_file()
.map(|p| p.to_string_lossy().to_string())
})),
pick_curve_preset_files: Some(Box::new(|| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Import Point Curve Presets"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve", "json"])
+ .set_title(dac_ui_egui::i18n::tr("Import Point Curve Presets"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve", "json"])
.pick_files()
.unwrap_or_default()
.into_iter()
@@ -484,17 +484,17 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
})),
save_curve_preset_file: Some(Box::new(|name: &str| {
rfd::FileDialog::new()
- .set_title(lightcraft_ui_egui::i18n::tr("Export Point Curve Presets"))
- .add_filter_nocase(lightcraft_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve"])
+ .set_title(dac_ui_egui::i18n::tr("Export Point Curve Presets"))
+ .add_filter_nocase(dac_ui_egui::i18n::tr("Point Curve Presets"), &["lccurve"])
.set_file_name(name)
.save_file()
.map(|p| p.to_string_lossy().to_string())
})),
// atomic (temp file + sync + rename): a failed write never leaves a truncated file
- write_shared: Some(std::sync::Arc::new(lightcraft_engine::export::write_file)),
- write: Some(Box::new(lightcraft_engine::export::write_file)),
- png: Some(Box::new(|img: &lightcraft_raster::Rgba8| {
- lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default()
+ write_shared: Some(std::sync::Arc::new(dac_engine::export::write_file)),
+ write: Some(Box::new(dac_engine::export::write_file)),
+ png: Some(Box::new(|img: &dac_raster::Rgba8| {
+ dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(img), &dac_codecs::EncodeMeta::default()).unwrap_or_default()
})),
// the library is a folder on disk: backed up with the user's other files
backup_library: None,
@@ -505,7 +505,7 @@ fn services(ctx: egui::Context, log_file: Option<&std::path::Path>) -> Services
/// The library session with the folder face models are kept in (`/models`).
fn open_session(in_memory: bool, dir: Option, seed_demo: bool) -> (Session, Option) {
let (mut s, problem) = open_library_session(in_memory, dir, seed_demo);
- s.face_models_dir = lightcraft_engine::config::default_face_models_dir();
+ s.face_models_dir = dac_engine::config::default_face_models_dir();
(s, problem)
}
@@ -569,7 +569,7 @@ ENVIRONMENT:
";
/// The launch arguments' files and folders to import (absolute), and a notice for each one that
-/// is not imported (see [`lightcraft_engine::import::launch_path`]; issue #374).
+/// is not imported (see [`dac_engine::import::launch_path`]; issue #374).
fn launch_imports(args: &[String]) -> (Vec, Vec) {
let cwd = std::env::current_dir().ok();
let home = std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).filter(|h| !h.is_empty()).map(std::path::PathBuf::from);
@@ -577,10 +577,10 @@ fn launch_imports(args: &[String]) -> (Vec, Vec) {
}
fn launch_imports_in(args: &[String], cwd: Option<&std::path::Path>, home: Option<&std::path::Path>) -> (Vec, Vec) {
- use lightcraft_engine::import::{LaunchPath, launch_path};
+ use dac_engine::import::{LaunchPath, launch_path};
let (mut files, mut notices) = (Vec::new(), Vec::new());
for a in args {
- match launch_path(a, cwd, home, lightcraft_engine::devices::dcim_in) {
+ match launch_path(a, cwd, home, dac_engine::devices::dcim_in) {
Some(LaunchPath::Import(p)) => files.push(p),
Some(LaunchPath::Refused { path, why }) => {
log::warn!("not importing {path} given at launch: {why}");
@@ -616,7 +616,7 @@ fn control_port_from(what: &str, value: Option) -> Option {
fn main() -> eframe::Result {
// First, so every start-up record is kept for the log file (`logging`).
let logger = logging::install();
- lightcraft_engine::guard::install_hook(std::env::temp_dir().join("lightcraft-panics.log"));
+ dac_engine::guard::install_hook(std::env::temp_dir().join("lightcraft-panics.log"));
if let Some(logger) = logger {
logging::record_panics(logger);
}
@@ -671,7 +671,7 @@ fn main() -> eframe::Result {
let (prefs, prefs_warning, keep_prefs_file) = load_prefs(in_memory);
// the saved language from the start, so a failed start is reported in it too
if let Some(ui) = &prefs {
- lightcraft_ui_egui::i18n::set_language(ui.language);
+ dac_ui_egui::i18n::set_language(ui.language);
}
// --library, else the library last opened from Settings, else the default location
let library_dir = library_dir.or_else(|| {
@@ -681,12 +681,12 @@ fn main() -> eframe::Result {
.filter(|p| !p.is_empty() && std::env::var_os("LIGHTCRAFT_LIBRARY").is_none())
.map(Into::into)
});
- let library_dir = library_dir.or_else(lightcraft_engine::library::default_dir);
+ let library_dir = library_dir.or_else(dac_engine::library::default_dir);
// GPU compute: off if the preference says so, or if the last launch died creating the device
// (issue #136) — before anything can create it
let gpu_crash = gpu_crash_check(in_memory);
let gpu_on = prefs.as_ref().is_none_or(|u| u.settings.gpu) && gpu_crash.is_none();
- lightcraft_engine::gpu::set_enabled(gpu_on);
+ dac_engine::gpu::set_enabled(gpu_on);
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("LightCraft")
@@ -715,11 +715,11 @@ fn main() -> eframe::Result {
std::env::var_os("LIGHTCRAFT_SAM3_DIR").map(std::path::PathBuf::from).or_else(|| config_dir().map(|d| d.join("models").join("sam3")));
// the user's own download locations, one base URL per line (LIGHTCRAFT_SAM3_MIRRORS too)
session.segmenter.mirrors_file = config_dir().map(|d| d.join("models").join("sam3-mirrors.txt"));
- let mut app = LightcraftApp::new(session, services(cc.egui_ctx.clone(), app_log_file.as_deref()));
+ let mut app = DacApp::new(session, services(cc.egui_ctx.clone(), app_log_file.as_deref()));
if let Some(ui) = prefs {
app.ui = ui;
}
- lightcraft_ui_egui::i18n::set_language(app.ui.language);
+ dac_ui_egui::i18n::set_language(app.ui.language);
app.integrated_titlebar = cfg!(target_os = "macos");
app.notices.extend(prefs_warning);
app.notices.extend(launch_notices);
@@ -748,10 +748,10 @@ fn main() -> eframe::Result {
// listed and read on the import worker once the window shows (issue #374: never
// here, before the first frame, where a large folder kept the window from
// appearing)
- if let Err(e) = lightcraft_ui_egui::import::start_paths(&mut app, files) {
+ if let Err(e) = dac_ui_egui::import::start_paths(&mut app, files) {
log::warn!("import at launch: {e}");
}
- app.ui.view = lightcraft_ui_egui::state::ViewMode::PhotoGrid;
+ app.ui.view = dac_ui_egui::state::ViewMode::PhotoGrid;
}
// native menu bar generated from the command registry (macOS; elsewhere the menus are
// drawn in the window's top bar). LIGHTCRAFT_NO_NATIVE_MENU=1 keeps the in-window menus.
@@ -779,7 +779,7 @@ fn startup_failed(error: &str, log_file: Option<&std::path::Path>) {
if std::env::var_os("LIGHTCRAFT_NO_PREFS").is_some() {
return; // tests and scripts: no dialog to click away
}
- let (title, text) = lightcraft_ui_egui::i18n::startup_failed_message(error, log_file.map(|p| p.display().to_string()).as_deref());
+ let (title, text) = dac_ui_egui::i18n::startup_failed_message(error, log_file.map(|p| p.display().to_string()).as_deref());
let _ = rfd::MessageDialog::new()
.set_level(rfd::MessageLevel::Error)
.set_title(title)
@@ -903,10 +903,10 @@ mod tests {
assert!(m.exists(), "the marker stays for the next ordinary launch");
// the next ordinary launch reports it once and removes it
assert!(gpu_crash_check_at(Some(m.clone()), false).is_some());
- lightcraft_engine::gpu::backend::set_init_marker(None);
+ dac_engine::gpu::backend::set_init_marker(None);
assert!(!m.exists());
assert_eq!(gpu_crash_check_at(Some(m.clone()), false), None);
- lightcraft_engine::gpu::backend::set_init_marker(None);
+ dac_engine::gpu::backend::set_init_marker(None);
let _ = std::fs::remove_dir_all(&d);
}
@@ -915,9 +915,9 @@ mod tests {
fn gpu_crash_marker_is_reported_once() {
let d = dir("gpu-marker");
let m = d.join("gpu-init.marker");
- assert_eq!(lightcraft_engine::gpu::backend::take_init_marker(&m), None);
+ assert_eq!(dac_engine::gpu::backend::take_init_marker(&m), None);
std::fs::write(&m, "GPU device creation started (backends VULKAN | DX12)").unwrap();
- let what = lightcraft_engine::gpu::backend::take_init_marker(&m).unwrap();
+ let what = dac_engine::gpu::backend::take_init_marker(&m).unwrap();
let notice = gpu_crash_notice(&what);
assert!(notice.contains("GPU rendering is now off") && notice.contains("Use the GPU for rendering"), "{notice}");
assert!(!m.exists());
diff --git a/apps/lightcraft/src/native_menu.rs b/apps/app/src/native_menu.rs
similarity index 87%
rename from apps/lightcraft/src/native_menu.rs
rename to apps/app/src/native_menu.rs
index 256faa1f81..999bc059cd 100644
--- a/apps/lightcraft/src/native_menu.rs
+++ b/apps/app/src/native_menu.rs
@@ -1,12 +1,12 @@
//! The native macOS menu bar (via `muda`, pure Rust over AppKit), generated from the same model as
-//! the in-window menus ([`lightcraft_ui_egui::menubar`]): the registry's menu paths, shortcuts,
+//! the in-window menus ([`dac_ui_egui::menubar`]): the registry's menu paths, shortcuts,
//! enabled and checked state, and live labels ("Undo Exposure").
//!
//! Key handling: AppKit offers a key press to the menu bar only when it carries ⌘ or ⌃, or is a
//! function key (F1…). Every other key (`E`, `1`, `⇧P`, `⌥Y`…) goes straight to the window, whose
//! winit view always takes it, so a menu item never fires from it. Hence
//! - shortcuts the menu bar really receives ([`menu_delivers`]) are listed in
-//! `LightcraftApp::native_shortcuts` and skipped by the egui shortcut handler (nothing fires
+//! `DacApp::native_shortcuts` and skipped by the egui shortcut handler (nothing fires
//! twice); the others stay on their menu items for display and egui runs them;
//! - while a text field has keyboard focus, accelerators without ⌘ (`G`, `1`, `Delete`…) and the
//! text-editing ones (⌘A/⌘C/⌘V/⌘X/⌘Z) are removed, so typing and text editing work; they come
@@ -15,8 +15,8 @@
use std::collections::{HashMap, HashSet};
use std::sync::mpsc::{Receiver, channel};
-use lightcraft_ui_egui::LightcraftApp;
-use lightcraft_ui_egui::menubar::{MenuNode, menu_bar};
+use dac_ui_egui::DacApp;
+use dac_ui_egui::menubar::{MenuNode, menu_bar};
use muda::accelerator::{Accelerator, Code, Modifiers};
use muda::{CheckMenuItem, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
use serde_json::Value;
@@ -159,14 +159,14 @@ fn menu_delivers(sc: &str) -> bool {
sc.split('+').any(|part| matches!(part, "Cmd" | "Ctrl") || part.strip_prefix('F').is_some_and(|n| n.parse::().is_ok()))
}
-/// The shortcuts the menu bar runs itself, for `LightcraftApp::native_shortcuts`.
+/// The shortcuts the menu bar runs itself, for `DacApp::native_shortcuts`.
fn owned_by_menu<'a>(installed: impl Iterator- , text_focus: bool) -> HashSet {
installed.filter(|sc| menu_delivers(sc) && !(text_focus && yields_to_text(sc))).map(str::to_string).collect()
}
/// `&` marks a mnemonic in muda labels.
fn label_text(s: &str) -> String {
- lightcraft_ui_egui::i18n::tr(s).replace('&', "&&")
+ dac_ui_egui::i18n::tr(s).replace('&', "&&")
}
fn structure_of(bar: &[(String, Vec)]) -> String {
@@ -189,7 +189,7 @@ fn structure_of(bar: &[(String, Vec)]) -> String {
out.push(';');
}
}
- let mut s = format!("{};", lightcraft_ui_egui::i18n::language().code());
+ let mut s = format!("{};", dac_ui_egui::i18n::language().code());
for (t, items) in bar {
s.push_str(t);
walk(items, &mut s);
@@ -207,7 +207,7 @@ fn tidy_separators(mut nodes: Vec) -> Vec {
impl NativeMenu {
/// Build the menu bar and install it as the application menu (main thread, after launch).
- pub fn install(app: &mut LightcraftApp, ctx: &egui::Context) -> NativeMenu {
+ pub fn install(app: &mut DacApp, ctx: &egui::Context) -> NativeMenu {
let (tx, rx) = channel::();
let repaint = ctx.clone();
MenuEvent::set_event_handler(Some(move |e: MenuEvent| {
@@ -220,7 +220,7 @@ impl NativeMenu {
m
}
- fn rebuild(&mut self, app: &mut LightcraftApp) {
+ fn rebuild(&mut self, app: &mut DacApp) {
let bar = menu_bar(app);
self.structure = structure_of(&bar);
self.menu = Menu::new();
@@ -228,19 +228,19 @@ impl NativeMenu {
// the application menu
let app_menu = Submenu::new("LightCraft", true);
- let about = MenuItem::with_id("app.about", lightcraft_ui_egui::i18n::tr("About LightCraft"), true, None);
- let settings = MenuItem::with_id(SETTINGS, lightcraft_ui_egui::i18n::tr("Settings…"), true, accelerator(SETTINGS_KEY));
- let quit = MenuItem::with_id(QUIT, lightcraft_ui_egui::i18n::tr("Quit LightCraft"), true, accelerator("Cmd+Q"));
+ let about = MenuItem::with_id("app.about", dac_ui_egui::i18n::tr("About LightCraft"), true, None);
+ let settings = MenuItem::with_id(SETTINGS, dac_ui_egui::i18n::tr("Settings…"), true, accelerator(SETTINGS_KEY));
+ let quit = MenuItem::with_id(QUIT, dac_ui_egui::i18n::tr("Quit LightCraft"), true, accelerator("Cmd+Q"));
let _ = app_menu.append_items(&[
&about,
&PredefinedMenuItem::separator(),
&settings,
&PredefinedMenuItem::separator(),
- &PredefinedMenuItem::services(Some(lightcraft_ui_egui::i18n::tr("Services"))),
+ &PredefinedMenuItem::services(Some(dac_ui_egui::i18n::tr("Services"))),
&PredefinedMenuItem::separator(),
- &PredefinedMenuItem::hide(Some(lightcraft_ui_egui::i18n::tr("Hide LightCraft"))),
- &PredefinedMenuItem::hide_others(Some(lightcraft_ui_egui::i18n::tr("Hide Others"))),
- &PredefinedMenuItem::show_all(Some(lightcraft_ui_egui::i18n::tr("Show All"))),
+ &PredefinedMenuItem::hide(Some(dac_ui_egui::i18n::tr("Hide LightCraft"))),
+ &PredefinedMenuItem::hide_others(Some(dac_ui_egui::i18n::tr("Hide Others"))),
+ &PredefinedMenuItem::show_all(Some(dac_ui_egui::i18n::tr("Show All"))),
&PredefinedMenuItem::separator(),
&quit,
]);
@@ -250,8 +250,8 @@ impl NativeMenu {
let sub = Submenu::new(label_text(title), true);
if title == "Window" {
let _ = sub.append_items(&[
- &PredefinedMenuItem::minimize(Some(lightcraft_ui_egui::i18n::tr("Minimize"))),
- &PredefinedMenuItem::maximize(Some(lightcraft_ui_egui::i18n::tr("Zoom"))),
+ &PredefinedMenuItem::minimize(Some(dac_ui_egui::i18n::tr("Minimize"))),
+ &PredefinedMenuItem::maximize(Some(dac_ui_egui::i18n::tr("Zoom"))),
&PredefinedMenuItem::separator(),
]);
}
@@ -267,13 +267,13 @@ impl NativeMenu {
// ⌘W, the system's own item
let _ = sub.append_items(&[
&PredefinedMenuItem::separator(),
- &PredefinedMenuItem::close_window(Some(lightcraft_ui_egui::i18n::tr("Close Window"))),
+ &PredefinedMenuItem::close_window(Some(dac_ui_egui::i18n::tr("Close Window"))),
]);
}
if title == "Window" {
let _ = sub.append_items(&[
&PredefinedMenuItem::separator(),
- &PredefinedMenuItem::bring_all_to_front(Some(lightcraft_ui_egui::i18n::tr("Bring All to Front"))),
+ &PredefinedMenuItem::bring_all_to_front(Some(dac_ui_egui::i18n::tr("Bring All to Front"))),
]);
sub.set_as_windows_menu_for_nsapp();
}
@@ -309,7 +309,7 @@ impl NativeMenu {
Some(c) => {
let it = CheckMenuItem::with_id(
key.clone(),
- lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"),
+ dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"),
*enabled,
*c,
accel,
@@ -320,7 +320,7 @@ impl NativeMenu {
None => {
let it = MenuItem::with_id(
key.clone(),
- lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"),
+ dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&"),
*enabled,
accel,
);
@@ -347,7 +347,7 @@ impl NativeMenu {
}
/// Tell the egui shortcut handler which shortcuts the menu bar currently owns.
- fn publish_shortcuts(&self, app: &mut LightcraftApp) {
+ fn publish_shortcuts(&self, app: &mut DacApp) {
let installed = self.items.values().filter(|i| i.accel.is_some()).filter_map(|i| i.shortcut.as_deref());
// (none while the keymap editor records a shortcut: every key goes to it)
app.native_shortcuts = if self.capturing { HashSet::new() } else { owned_by_menu(installed, self.text_focus) };
@@ -358,8 +358,8 @@ impl NativeMenu {
/// accelerators with the app state. Returns whether Quit was chosen: the caller saves
/// everything and exits the process itself (closing the viewport while AppKit terminates the
/// app can crash in winit's teardown, PR #444).
- pub fn update(&mut self, app: &mut LightcraftApp, ctx: &egui::Context) -> bool {
- lightcraft_ui_egui::i18n::set_language(app.ui.language);
+ pub fn update(&mut self, app: &mut DacApp, ctx: &egui::Context) -> bool {
+ dac_ui_egui::i18n::set_language(app.ui.language);
let mut quit = false;
while let Ok(key) = self.rx.try_recv() {
if key == QUIT {
@@ -372,7 +372,7 @@ impl NativeMenu {
}
if let Some(it) = self.items.get(&key) {
let (cmd, params) = (it.cmd.clone(), it.params.clone());
- let _ = lightcraft_ui_egui::menubar::run_item(app, &cmd, params);
+ let _ = dac_ui_egui::menubar::run_item(app, &cmd, params);
}
}
let bar = menu_bar(app);
@@ -388,8 +388,8 @@ impl NativeMenu {
let Some(it) = items.get_mut(&MenuNode::key(id, params)) else { continue };
if it.label != *label {
match &it.handle {
- Handle::Plain(h) => h.set_text(lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")),
- Handle::Check(h) => h.set_text(lightcraft_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")),
+ Handle::Plain(h) => h.set_text(dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")),
+ Handle::Check(h) => h.set_text(dac_ui_egui::menubar::display_item_label(id, params, label).replace('&', "&&")),
}
it.label = label.clone();
}
@@ -445,8 +445,8 @@ mod tests {
/// languages — must change the structure key.
#[test]
fn switching_language_rebuilds_native_menu_structure() {
- use lightcraft_ui_egui::i18n::{Locale, set_language};
- let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
+ use dac_ui_egui::i18n::{Locale, set_language};
+ let app = DacApp::new(dac_engine::Session::with_demo(), Default::default());
set_language(Locale::En);
let bar = menu_bar(&app);
let mut seen = std::collections::BTreeSet::new();
@@ -470,7 +470,7 @@ mod tests {
assert!(yields_to_text("G") && yields_to_text("Delete") && yields_to_text("Cmd+V"));
assert!(!yields_to_text("Cmd+Shift+E"));
// every registry shortcut that a menu shows maps (or is deliberately left to egui)
- let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
+ let app = DacApp::new(dac_engine::Session::with_demo(), Default::default());
fn all(n: &[MenuNode], out: &mut Vec) {
for x in n {
match x {
@@ -495,7 +495,7 @@ mod tests {
fn single_key_shortcuts_stay_with_egui() {
assert!(menu_delivers("Cmd+Shift+H") && menu_delivers("Ctrl+H") && menu_delivers("F2") && menu_delivers("Cmd+F11"));
assert!(!menu_delivers("E") && !menu_delivers("Shift+P") && !menu_delivers("Alt+Y") && !menu_delivers("1") && !menu_delivers("F"));
- let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
+ let app = DacApp::new(dac_engine::Session::with_demo(), Default::default());
fn all(n: &[MenuNode], out: &mut Vec) {
for x in n {
match x {
diff --git a/apps/lightcraft-cli/Cargo.toml b/apps/cli/Cargo.toml
similarity index 50%
rename from apps/lightcraft-cli/Cargo.toml
rename to apps/cli/Cargo.toml
index 6471ab86d6..f5834b79a2 100644
--- a/apps/lightcraft-cli/Cargo.toml
+++ b/apps/cli/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "lightcraft-cli"
+name = "dac-cli"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -7,32 +7,32 @@ publish = false
description = "LightCraft command-line tool: headless render, command listing and the MCP server"
[[bin]]
-name = "lightcraft-cli"
+name = "app-cli"
path = "src/main.rs"
[lints]
workspace = true
[dependencies]
-lightcraft-engine = { workspace = true }
-lightcraft-sysmem = { workspace = true }
-lightcraft-mcp = { workspace = true }
-lightcraft-ui-egui = { workspace = true }
-lightcraft-codecs = { workspace = true }
-lightcraft-raster = { workspace = true }
-lightcraft-merge = { workspace = true }
+dac-engine = { workspace = true }
+dac-sysmem = { workspace = true }
+dac-mcp = { workspace = true }
+dac-ui-egui = { workspace = true }
+dac-codecs = { workspace = true }
+dac-raster = { workspace = true }
+dac-merge = { workspace = true }
serde_json = { workspace = true }
dhat = { workspace = true, optional = true }
[features]
default = ["denoise"]
-denoise = ["lightcraft-engine/denoise"]
-rawnind-model = ["denoise", "lightcraft-engine/rawnind-model"]
-# Heap profiling: `cargo run --release -p lightcraft-cli --features dhat-heap -- snapshot …` counts
+denoise = ["dac-engine/denoise"]
+rawnind-model = ["denoise", "dac-engine/rawnind-model"]
+# Heap profiling: `cargo run --release -p dac-cli --features dhat-heap -- snapshot …` counts
# every allocation (live/peak bytes in `library.memory` / `ui.inspect`) and writes dhat-heap.json
# (open it in DHAT's viewer: allocation sites by bytes at the peak). Slows allocation-heavy work.
dhat-heap = ["dep:dhat"]
-# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose
+# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose
# it (HEVC patents); build with `--features heif` to include it.
-heif = ["lightcraft-codecs/heif"]
+heif = ["dac-codecs/heif"]
diff --git a/apps/cli/src/alloc_release.rs b/apps/cli/src/alloc_release.rs
new file mode 100644
index 0000000000..d396ebc11b
--- /dev/null
+++ b/apps/cli/src/alloc_release.rs
@@ -0,0 +1,12 @@
+//! Give memory the system allocator keeps after frees back to the system
+//! ([`dac_engine::memory::set_release_hook`]). The platform call lives in
+//! `dac-sysmem`, the workspace's only crate allowed `unsafe`.
+
+/// Install the hook.
+pub fn install() {
+ dac_engine::memory::set_release_hook(release);
+}
+
+fn release() {
+ let _ = dac_sysmem::release_free_memory();
+}
diff --git a/apps/lightcraft-cli/src/main.rs b/apps/cli/src/main.rs
similarity index 93%
rename from apps/lightcraft-cli/src/main.rs
rename to apps/cli/src/main.rs
index 7c9d255e93..0cb3b6995a 100644
--- a/apps/lightcraft-cli/src/main.rs
+++ b/apps/cli/src/main.rs
@@ -20,8 +20,8 @@ use std::io::{BufReader, Write};
use std::path::Path;
use std::process::ExitCode;
-use lightcraft_engine::Session;
-use lightcraft_mcp::{Backend, DEFAULT_ADDR, Headless, Remote, Server, expand_paths};
+use dac_engine::Session;
+use dac_mcp::{Backend, DEFAULT_ADDR, Headless, Remote, Server, expand_paths};
use serde_json::{Value, json};
const USAGE: &str = "\
@@ -110,9 +110,9 @@ fn library_warnings(session: &mut Session, who: &str) {
}
}
-fn library_error(dir: &str, e: lightcraft_engine::EngineError) -> String {
+fn library_error(dir: &str, e: dac_engine::EngineError) -> String {
match e {
- lightcraft_engine::EngineError::LibraryInUse(why) => format!(
+ dac_engine::EngineError::LibraryInUse(why) => format!(
"{dir}: {why}\nTo work with the library while the app has it open, start the app with `--control PORT` and use `lightcraft-cli mcp --connect 127.0.0.1:PORT`."
),
e => format!("{dir}: {e}"),
@@ -125,16 +125,16 @@ fn main() -> ExitCode {
#[cfg(feature = "dhat-heap")]
let _heap = {
let file = std::env::var("LIGHTCRAFT_DHAT_FILE").unwrap_or_else(|_| "dhat-heap.json".into());
- lightcraft_engine::memory::set_heap_stats(|| {
+ dac_engine::memory::set_heap_stats(|| {
let s = dhat::HeapStats::get();
- lightcraft_engine::memory::HeapUsage { current: s.curr_bytes as u64, peak: s.max_bytes as u64 }
+ dac_engine::memory::HeapUsage { current: s.curr_bytes as u64, peak: s.max_bytes as u64 }
});
dhat::Profiler::builder().file_name(file).build()
};
alloc_release::install();
// Warnings (a GPU render redone on the CPU, an unknown backend name) on stderr; LIGHTCRAFT_LOG
// or RUST_LOG picks another level (#168).
- lightcraft_engine::logging::install("lightcraft-cli");
+ dac_engine::logging::install("dac-cli");
let args: Vec = std::env::args().skip(1).collect();
let r = match args.first().map(String::as_str) {
Some("run") => run(&args[1..]),
@@ -217,15 +217,15 @@ fn calibrate(args: &[String]) -> Result<(), String> {
files = (0..max).filter_map(|k| files.get(k * found / max).cloned()).collect();
}
eprintln!("calibrate: {} of {found} raw files", files.len());
- let dir = out.or_else(lightcraft_engine::camera_profiles::dir).ok_or("no profiles folder: pass --out DIR")?;
+ let dir = out.or_else(dac_engine::camera_profiles::dir).ok_or("no profiles folder: pass --out DIR")?;
let workers = std::thread::available_parallelism().map_or(4, |n| n.get()).clamp(1, 6);
let next = std::sync::atomic::AtomicUsize::new(0);
let done = std::sync::atomic::AtomicUsize::new(0);
- let pools: Vec = std::thread::scope(|scope| {
+ let pools: Vec = std::thread::scope(|scope| {
let handles: Vec<_> = (0..workers)
.map(|_| {
scope.spawn(|| {
- let mut pool = lightcraft_engine::camera_profiles::Pool::default();
+ let mut pool = dac_engine::camera_profiles::Pool::default();
while let Some(path) = files.get(next.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) {
let result = std::fs::read(path).map_err(|e| e.to_string()).and_then(|bytes| pool.add(&bytes));
let n = done.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
@@ -241,7 +241,7 @@ fn calibrate(args: &[String]) -> Result<(), String> {
.collect();
handles.into_iter().filter_map(|h| h.join().ok()).collect()
});
- let mut pool = lightcraft_engine::camera_profiles::Pool::default();
+ let mut pool = dac_engine::camera_profiles::Pool::default();
for p in pools {
pool.merge(p);
}
@@ -249,7 +249,7 @@ fn calibrate(args: &[String]) -> Result<(), String> {
for result in pool.fit(5) {
match result {
Ok(profile) => {
- let path = lightcraft_engine::camera_profiles::save(&profile, &dir)?;
+ let path = dac_engine::camera_profiles::save(&profile, &dir)?;
println!(
"{}: {} photos, {} colour pairs, hue/saturation table {} → {}",
profile.model,
@@ -399,8 +399,8 @@ fn merge(args: &[String]) -> Result<(), String> {
ids.sort_by_key(|id| {
paths.iter().position(|f| {
s.catalog
- .photo(lightcraft_engine::catalog::PhotoId(*id))
- .is_some_and(|ph| matches!(&ph.source, lightcraft_engine::catalog::Source::File { path } if path == f))
+ .photo(dac_engine::catalog::PhotoId(*id))
+ .is_some_and(|ph| matches!(&ph.source, dac_engine::catalog::Source::File { path } if path == f))
})
});
p.insert("ids".into(), json!(ids));
@@ -427,22 +427,20 @@ fn synth_merge(args: &[String]) -> Result<(), String> {
let mut written = Vec::new();
match kind {
"hdr" => {
- for (k, b) in lightcraft_merge::synth::bracket_dngs(1800, 1200, &[-2.0, 0.0, 2.0]).map_err(|e| e.to_string())?.into_iter().enumerate() {
+ for (k, b) in dac_merge::synth::bracket_dngs(1800, 1200, &[-2.0, 0.0, 2.0]).map_err(|e| e.to_string())?.into_iter().enumerate() {
let p = Path::new(&dir).join(format!("bracket-{k}.dng"));
std::fs::write(&p, b).map_err(|e| e.to_string())?;
written.push(p);
}
}
"panorama" | "pano" => {
- for (k, v) in lightcraft_merge::synth::pano_views(1200, 900, 1000.0, &[-50.0, -25.0, 0.0, 25.0, 50.0])
- .map_err(|e| e.to_string())?
- .into_iter()
- .enumerate()
+ for (k, v) in
+ dac_merge::synth::pano_views(1200, 900, 1000.0, &[-50.0, -25.0, 0.0, 25.0, 50.0]).map_err(|e| e.to_string())?.into_iter().enumerate()
{
let p = Path::new(&dir).join(format!("view-{k}.png"));
let img = v.to_srgb8();
- let png = lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &lightcraft_codecs::EncodeMeta::default())
- .map_err(|e| e.to_string())?;
+ let png =
+ dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(&img), &dac_codecs::EncodeMeta::default()).map_err(|e| e.to_string())?;
std::fs::write(&p, png).map_err(|e| e.to_string())?;
written.push(p);
}
@@ -562,7 +560,7 @@ fn run(args: &[String]) -> Result<(), String> {
Some(dir) => {
let mut h = Headless::default();
h.session.open_library(dir, demo).map_err(|e| library_error(dir, e))?;
- library_warnings(&mut h.session, "lightcraft-cli");
+ library_warnings(&mut h.session, "dac-cli");
h
}
None if demo => Headless::demo(),
@@ -667,7 +665,7 @@ fn render(args: &[String]) -> Result<(), String> {
if !values.is_empty() {
run(&mut s, "develop.set", json!({"values": values}))?;
}
- use lightcraft_engine::export::{ExportFormat, ExportOptions, export_photo};
+ use dac_engine::export::{ExportFormat, ExportOptions, export_photo};
let mut p = json!({"quality": quality});
if let Some(n) = size {
p["longEdge"] = json!(n);
@@ -681,7 +679,7 @@ fn render(args: &[String]) -> Result<(), String> {
o.format = ExportFormat::parse(&ext)
.ok_or_else(|| format!("{output}: unknown extension (use .jpg .png .tif .webp .avif .dng or --opt format=…)"))?;
}
- let e = export_photo(&mut s, lightcraft_engine::catalog::PhotoId(id), &o, 1)?;
+ let e = export_photo(&mut s, dac_engine::catalog::PhotoId(id), &o, 1)?;
// never over the input (or its sidecar), however it is spelled: `render IMG.jpg -o IMG.jpg`
let sidecars: Vec<(String, &Vec)> =
e.sidecars.iter().map(|(ext, bytes)| (Path::new(&output).with_extension(ext).to_string_lossy().to_string(), bytes)).collect();
@@ -689,9 +687,9 @@ fn render(args: &[String]) -> Result<(), String> {
for p in std::iter::once(&output).chain(sidecars.iter().map(|(p, _)| p)) {
guard.check(Path::new(p)).map_err(|err| format!("render: {err}"))?;
}
- lightcraft_engine::export::write_file(&output, &e.bytes)?;
+ dac_engine::export::write_file(&output, &e.bytes)?;
for (sc, bytes) in &sidecars {
- lightcraft_engine::export::write_file(sc, bytes)?;
+ dac_engine::export::write_file(sc, bytes)?;
eprintln!("lightcraft-cli: wrote {sc}");
}
eprintln!("lightcraft-cli: wrote {output} ({}×{})", e.width, e.height);
@@ -699,7 +697,7 @@ fn render(args: &[String]) -> Result<(), String> {
}
fn snapshot(args: &[String]) -> Result<(), String> {
- use lightcraft_ui_egui::headless::Headless;
+ use dac_ui_egui::headless::Headless;
use std::time::{Duration, Instant};
let mut library: Option = None;
let mut script: Option = None;
@@ -728,7 +726,7 @@ fn snapshot(args: &[String]) -> Result<(), String> {
if script.is_none() && output.is_none() {
return Err("snapshot: give -o OUT.png and/or --script FILE".into());
}
- lightcraft_ui_egui::headless::viewport_pixels(size, scale).map_err(|e| format!("snapshot: bad --size/--scale: {e}"))?;
+ dac_ui_egui::headless::viewport_pixels(size, scale).map_err(|e| format!("snapshot: bad --size/--scale: {e}"))?;
let t0 = Instant::now();
let mut session = match &library {
Some(dir) => {
@@ -747,15 +745,15 @@ fn snapshot(args: &[String]) -> Result<(), String> {
let n = r["imported"].as_array().map_or(0, Vec::len);
eprintln!("lightcraft-cli snapshot: imported {n} files in {:.0} ms", ti.elapsed().as_secs_f64() * 1e3);
}
- let services = lightcraft_ui_egui::Services {
- write_shared: Some(std::sync::Arc::new(lightcraft_engine::export::write_file)),
- write: Some(Box::new(lightcraft_engine::export::write_file)),
- png: Some(Box::new(|img: &lightcraft_raster::Rgba8| {
- lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(img), &lightcraft_codecs::EncodeMeta::default()).unwrap_or_default()
+ let services = dac_ui_egui::Services {
+ write_shared: Some(std::sync::Arc::new(dac_engine::export::write_file)),
+ write: Some(Box::new(dac_engine::export::write_file)),
+ png: Some(Box::new(|img: &dac_raster::Rgba8| {
+ dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(img), &dac_codecs::EncodeMeta::default()).unwrap_or_default()
})),
..Default::default()
};
- let mut app = lightcraft_ui_egui::LightcraftApp::new(session, services);
+ let mut app = dac_ui_egui::DacApp::new(session, services);
// Snapshot sessions promise no GPU: disable photo compute as well as the compositor
// before the first frame can start background adapter discovery.
// This UI state is session-local: the CLI neither loads nor saves desktop ui.json.
diff --git a/apps/lightcraft-cli/tests/cli.rs b/apps/cli/tests/cli.rs
similarity index 94%
rename from apps/lightcraft-cli/tests/cli.rs
rename to apps/cli/tests/cli.rs
index 2009e818db..945b09c7e5 100644
--- a/apps/lightcraft-cli/tests/cli.rs
+++ b/apps/cli/tests/cli.rs
@@ -5,7 +5,7 @@ use std::process::{Command, Stdio};
use serde_json::{Value, json};
-const BIN: &str = env!("CARGO_BIN_EXE_lightcraft-cli");
+const BIN: &str = env!("CARGO_BIN_EXE_app-cli");
fn tmp(name: &str) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("lightcraft-cli-test-{}", std::process::id()));
@@ -14,8 +14,8 @@ fn tmp(name: &str) -> std::path::PathBuf {
}
fn gradient_png(path: &std::path::Path) {
- let img = lightcraft_raster::Rgba8::from_fn(120, 80, |x, y| [(x * 2) as u8, (y * 3) as u8, 100, 255]);
- let png = lightcraft_codecs::encode_png(&lightcraft_codecs::EncodeImage::rgba8(&img), &Default::default()).unwrap();
+ let img = dac_raster::Rgba8::from_fn(120, 80, |x, y| [(x * 2) as u8, (y * 3) as u8, 100, 255]);
+ let png = dac_codecs::encode_png(&dac_codecs::EncodeImage::rgba8(&img), &Default::default()).unwrap();
std::fs::write(path, png).unwrap();
}
@@ -33,7 +33,7 @@ fn calibrate_reads_raf_inputs_and_reports_bad_files() {
}
fn mean(path: &std::path::Path) -> f64 {
- let d = lightcraft_codecs::decode(&std::fs::read(path).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(path).unwrap(), Default::default()).unwrap();
let img = d.to_srgb8();
img.data.iter().map(|p| (p[0] as u32 + p[1] as u32 + p[2] as u32) as f64).sum::() / (3 * img.data.len()) as f64
}
@@ -88,9 +88,9 @@ fn render_subcommand() {
.status()
.unwrap();
assert!(st.success());
- let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (60, 40));
- let d = lightcraft_codecs::decode(&std::fs::read(&plain).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&plain).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (120, 80));
assert!(mean(&out) > mean(&plain) + 10.0);
// Bad control id fails cleanly.
@@ -128,9 +128,9 @@ fn render_export_options() {
.status()
.unwrap();
assert!(st.success());
- let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (60, 40));
- assert_eq!(d.space.named, Some(lightcraft_codecs::NamedSpace::DisplayP3));
+ assert_eq!(d.space.named, Some(dac_codecs::NamedSpace::DisplayP3));
// original + sidecar named after the output
let out = tmp("o-copy.png");
let st = Command::new(BIN)
@@ -188,7 +188,7 @@ fn snapshot_subcommand_renders_the_ui_headlessly() {
assert_eq!(replies.len(), 6);
assert!(replies.iter().all(|r| r["ok"] == true), "{replies:?}");
for (p, dimmed) in [(&a, false), (&b, true)] {
- let d = lightcraft_codecs::decode(&std::fs::read(p).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(p).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (640, 400));
// the export dialog dims everything around it
assert_eq!(mean(p) < mean(&a) - 2.0, dimmed, "{}", p.display());
@@ -196,7 +196,7 @@ fn snapshot_subcommand_renders_the_ui_headlessly() {
// no script: one settled screenshot, at 2× scale
let o = Command::new(BIN).args(["snapshot", "-o", a.to_str().unwrap(), "--size", "320x240", "--scale", "2"]).output().unwrap();
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
- let d = lightcraft_codecs::decode(&std::fs::read(&a).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&a).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (640, 480));
}
@@ -229,7 +229,7 @@ fn snapshot_script_failure_exits_non_zero() {
assert!(replies[0]["error"].as_str().unwrap_or("").contains("grid"), "{replies:?}");
assert_eq!(replies[1]["ok"], true, "{replies:?}");
// continue-and-report: the final screenshot is still written
- let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (320, 200));
}
@@ -251,7 +251,7 @@ fn snapshot_ui_zoom_keeps_requested_pixel_dimensions() {
let replies: Vec = String::from_utf8_lossy(&o.stdout).lines().map(|line| serde_json::from_str(line).unwrap()).collect();
assert!(replies.iter().all(|reply| reply["ok"] == true), "{replies:?}");
assert!(replies[1]["result"]["pixelsPerPoint"].as_f64().unwrap() > scale.parse::().unwrap());
- let decoded = lightcraft_codecs::decode(&std::fs::read(out).unwrap(), Default::default()).unwrap();
+ let decoded = dac_codecs::decode(&std::fs::read(out).unwrap(), Default::default()).unwrap();
assert_eq!((decoded.width, decoded.height), expected);
}
}
@@ -281,7 +281,7 @@ fn snapshot_defaults_to_cpu_without_gpu_environment_overrides() {
assert_eq!(gpu["available"], false, "{gpu}");
assert_eq!(gpu["adapter"], Value::Null, "no device is created: {gpu}");
assert!(gpu["reason"].as_str().is_some_and(|reason| reason.contains("preference")), "{gpu}");
- let decoded = lightcraft_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap();
+ let decoded = dac_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap();
assert_eq!((decoded.width, decoded.height), (480, 320));
}
@@ -316,7 +316,7 @@ fn snapshot_starts_without_a_gpu() {
assert_eq!(gpu["available"], false, "{var}: {gpu}");
assert_eq!(gpu["adapter"], Value::Null, "{var}: no device was created: {gpu}");
assert!(gpu["reason"].as_str().is_some_and(|r| r.contains(reason)), "{var}: {gpu}");
- let d = lightcraft_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
+ let d = dac_codecs::decode(&std::fs::read(&out).unwrap(), Default::default()).unwrap();
assert_eq!((d.width, d.height), (480, 320));
}
}
@@ -484,7 +484,7 @@ fn snapshot_preserves_fractional_scale_rounding() {
let output =
Command::new(BIN).env("LIGHTCRAFT_GPU", "0").args(["snapshot", "--size", "345x200", "--scale", "0.9", "-o"]).arg(&image).output().unwrap();
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
- let decoded = lightcraft_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap();
+ let decoded = dac_codecs::decode(&std::fs::read(image).unwrap(), Default::default()).unwrap();
assert_eq!((decoded.width, decoded.height), (311, 180));
}
@@ -494,14 +494,14 @@ fn snapshot_preserves_fractional_scale_rounding() {
fn snapshot_leaves_desktop_gpu_preferences_untouched() {
let dir = tmp("snapshot-desktop-preferences");
let library = dir.join("library");
- let mut session = lightcraft_engine::Session::new().with_fs();
+ let mut session = dac_engine::Session::new().with_fs();
session.open_library(&library, true).unwrap();
drop(session);
let config = dir.join("config");
let app_config = config.join("lightcraft");
std::fs::create_dir_all(&app_config).unwrap();
let ui_path = app_config.join("ui.json");
- let mut ui = lightcraft_ui_egui::UiState::default();
+ let mut ui = dac_ui_egui::UiState::default();
ui.settings.gpu = true;
let saved = serde_json::to_vec_pretty(&ui).unwrap();
std::fs::write(&ui_path, &saved).unwrap();
diff --git a/apps/lightcraft-cli/src/alloc_release.rs b/apps/lightcraft-cli/src/alloc_release.rs
deleted file mode 100644
index 83056aef44..0000000000
--- a/apps/lightcraft-cli/src/alloc_release.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-//! Give memory the system allocator keeps after frees back to the system
-//! ([`lightcraft_engine::memory::set_release_hook`]). The platform call lives in
-//! `lightcraft-sysmem`, the workspace's only crate allowed `unsafe`.
-
-/// Install the hook.
-pub fn install() {
- lightcraft_engine::memory::set_release_hook(release);
-}
-
-fn release() {
- let _ = lightcraft_sysmem::release_free_memory();
-}
diff --git a/apps/lightcraft/src/alloc_release.rs b/apps/lightcraft/src/alloc_release.rs
deleted file mode 100644
index 83056aef44..0000000000
--- a/apps/lightcraft/src/alloc_release.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-//! Give memory the system allocator keeps after frees back to the system
-//! ([`lightcraft_engine::memory::set_release_hook`]). The platform call lives in
-//! `lightcraft-sysmem`, the workspace's only crate allowed `unsafe`.
-
-/// Install the hook.
-pub fn install() {
- lightcraft_engine::memory::set_release_hook(release);
-}
-
-fn release() {
- let _ = lightcraft_sysmem::release_free_memory();
-}
diff --git a/apps/lightcraft-web/Cargo.toml b/apps/web/Cargo.toml
similarity index 82%
rename from apps/lightcraft-web/Cargo.toml
rename to apps/web/Cargo.toml
index aaa14a648b..c45c52573b 100644
--- a/apps/lightcraft-web/Cargo.toml
+++ b/apps/web/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "lightcraft-web"
+name = "dac-web"
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -13,11 +13,11 @@ workspace = true
crate-type = ["cdylib", "rlib"]
[dependencies]
-lightcraft-engine = { workspace = true }
-lightcraft-ui-egui = { workspace = true }
-lightcraft-codecs = { workspace = true }
-lightcraft-raster = { workspace = true }
-lightcraft-preview = { workspace = true }
+dac-engine = { workspace = true }
+dac-ui-egui = { workspace = true }
+dac-codecs = { workspace = true }
+dac-raster = { workspace = true }
+dac-preview = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
@@ -73,7 +73,7 @@ web-sys = { workspace = true, features = [
] }
[features]
-# Open HEIC/HEIF photos (the optional lightcraft-heif crate). Not default so distributors choose
+# Open HEIC/HEIF photos (the optional dac-heif crate). Not default so distributors choose
# it (HEVC patents); build with `--features heif` to include it. On wasm32 panics abort, so
# heic-rs's `catch_unwind` guard does not apply there.
-heif = ["lightcraft-codecs/heif"]
+heif = ["dac-codecs/heif"]
diff --git a/apps/lightcraft-web/index.html b/apps/web/index.html
similarity index 94%
rename from apps/lightcraft-web/index.html
rename to apps/web/index.html
index b088ad18cd..6fb74707da 100644
--- a/apps/lightcraft-web/index.html
+++ b/apps/web/index.html
@@ -22,7 +22,7 @@
Loading LightCraft…