app icon: lynx (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs
- assets/app-icon/: lightcraft.svg (master), lightcraft-small.svg, 1024 PNG, macOS 512 PNG, .icns, .ico, hicolor theme (ai.storyteller.lightcraft), LICENSE.txt, README (palette, geometry, provenance, files) - packaging/icons.sh regenerates everything (resvg, iconutil, new `cargo xtask ico`) - apps/lightcraft: runtime window/Dock icon + Wayland app id; build.rs embeds the .ico and VERSIONINFO into lightcraft.exe (winresource, Windows targets only) - packaging/linux/ai.storyteller.lightcraft.desktop - ATTRIBUTION.md entry; icon at the top of README Co-Authored-By: Claude Opus 5.5 <[email protected]>
No files matched your search
@@ -2085,6 +2085,7 @@ dependencies = [
|
||||
"muda",
|
||||
"rfd",
|
||||
"serde_json",
|
||||
"winresource",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3768,6 +3769,15 @@ dependencies = [
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
@@ -4058,6 +4068,21 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
@@ -4088,6 +4113,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
@@ -5003,6 +5034,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="LightCraft app icon: an engraved lynx in paper and ink on LightCraft amber (#f2a516)" src="assets/app-icon/hicolor/256x256/apps/ai.storyteller.lightcraft.png" width="128">
|
||||
</p>
|
||||
|
||||
<h1 align="center">LightCraft</h1>
|
||||
|
||||
<h3 align="center">Your photos. Your pixels. Your machine.</h3>
|
||||
|
||||
@@ -19,6 +19,10 @@ egui = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
# Icon + VERSIONINFO for lightcraft.exe; build.rs only uses it when targeting Windows.
|
||||
winresource = "0.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
rfd = { workspace = true }
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//! Windows only: embed the app icon and version info (VERSIONINFO) into `lightcraft.exe`, so
|
||||
//! Explorer, the taskbar, the Start menu and Alt-Tab show the lynx.
|
||||
//!
|
||||
//! On every other target this does nothing. A missing resource compiler is a warning, so a
|
||||
//! cross-compile from macOS or Linux still links, unless `LIGHTCRAFT_REQUIRE_WINRES=1` turns it
|
||||
//! into an error (for release builds).
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=../../assets/app-icon/lightcraft.ico");
|
||||
println!("cargo:rerun-if-env-changed=LIGHTCRAFT_REQUIRE_WINRES");
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
|
||||
return;
|
||||
}
|
||||
let mut res = winresource::WindowsResource::new();
|
||||
res.set_icon("../../assets/app-icon/lightcraft.ico")
|
||||
.set("ProductName", "LightCraft")
|
||||
.set("FileDescription", "LightCraft photo library and raw developer")
|
||||
.set("LegalCopyright", "Copyright (c) the LightCraft authors. MIT OR Apache-2.0.")
|
||||
.set("OriginalFilename", "lightcraft.exe")
|
||||
.set("InternalName", "lightcraft");
|
||||
if let Err(e) = res.compile() {
|
||||
if std::env::var_os("LIGHTCRAFT_REQUIRE_WINRES").is_some() {
|
||||
panic!("embedding Windows resources failed: {e}");
|
||||
}
|
||||
println!("cargo:warning=lightcraft.exe built without icon/version resources: {e}");
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,19 @@ mod native_menu;
|
||||
use lightcraft_engine::Session;
|
||||
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";
|
||||
|
||||
/// The window, taskbar and Dock icon (assets/app-icon/, regenerated by packaging/icons.sh). macOS
|
||||
/// gets the version with Apple's transparent margin so it sits right among the other Dock icons.
|
||||
fn app_icon() -> egui::IconData {
|
||||
#[cfg(target_os = "macos")]
|
||||
let png: &[u8] = include_bytes!("../../../assets/app-icon/lightcraft-macos-512.png");
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.lightcraft.png");
|
||||
eframe::icon_data::from_png_bytes(png).unwrap_or_default()
|
||||
}
|
||||
|
||||
struct App(LightcraftApp, #[cfg(target_os = "macos")] Option<native_menu::NativeMenu>);
|
||||
|
||||
impl eframe::App for App {
|
||||
@@ -251,7 +264,10 @@ fn main() -> eframe::Result {
|
||||
.with_drag_and_drop(true)
|
||||
.with_fullsize_content_view(true)
|
||||
.with_titlebar_shown(false)
|
||||
.with_title_shown(false),
|
||||
.with_title_shown(false)
|
||||
.with_icon(app_icon())
|
||||
// Wayland matches the window to packaging/linux/ai.storyteller.lightcraft.desktop by this id
|
||||
.with_app_id(APP_ID),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
|
||||
@@ -9,6 +9,7 @@ covers a whole directory), or if a licence file referenced in the Licence column
|
||||
| Path | Asset | Creator | Source | Licence | Added | Modifications |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `assets/fonts/Inter-*.ttf` (Regular, Medium, SemiBold) | Inter | Rasmus Andersson | https://github.com/rsms/inter | SIL Open Font License 1.1 (`assets/fonts/OFL-Inter.txt`) | 2026-09-30 | none |
|
||||
| `assets/app-icon/` (`lightcraft.svg` and the PNG/ICNS/ICO/SVG files derived from it) | LightCraft app icon (lynx) | LightCraft project owner (drawn in ArtCraft) | original work (vectorised from the owner's drawing; see `assets/app-icon/README.md`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | 2026-10-02 | vectorised to SVG; rendered to PNG/ICNS/ICO by `packaging/icons.sh` |
|
||||
| `crates/scenes/` (generated images) | Procedural demo photographs | LightCraft contributors | original work (generated at runtime, no source imagery) | MIT OR Apache-2.0 | 2026-09-30 | n/a |
|
||||
| `crates/ui-egui/src/icons.rs` | UI icons drawn as vector paths in code | LightCraft contributors | original work | MIT OR Apache-2.0 | 2026-09-30 | n/a |
|
||||
| `docs/images/*-tetons.jpg`, `docs/images/grid-pd.jpg` (screenshots containing it) | "The Tetons and the Snake River" | Ansel Adams (1942), U.S. National Archives | https://commons.wikimedia.org/wiki/File:Adams_The_Tetons_and_the_Snake_River.jpg | Public domain (U.S. federal government work) | 2026-09-30 | developed in LightCraft; shown inside app screenshots |
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
LightCraft app icon (lightcraft.svg and every file derived from it in this directory:
|
||||
lightcraft-small.svg, lightcraft-1024.png, lightcraft-macos-512.png, lightcraft.icns,
|
||||
lightcraft.ico, hicolor/).
|
||||
|
||||
Original artwork made for this project by the LightCraft project owner in ArtCraft (no
|
||||
third-party material). Licensed like the rest of LightCraft, under either of
|
||||
|
||||
* Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license (https://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
Regenerate the derived files with packaging/icons.sh.
|
||||
@@ -0,0 +1,49 @@
|
||||
# LightCraft app icon
|
||||
|
||||
A **lynx**, frontal head-and-shoulders portrait with an alert gaze, in the style of an engraving: the
|
||||
Crafting Apps "owl template" (full-bleed colour field, tight portrait, the figure in paper with ink line
|
||||
work and a crisp ink contour, no roundel or border).
|
||||
|
||||
## Palette (exactly three colours)
|
||||
|
||||
| Colour | Hex | Used for |
|
||||
|---|---|---|
|
||||
| Ink | `#0b0b0c` | line work and the contour |
|
||||
| Paper | `#efe9dc` | the figure (the lynx) |
|
||||
| LightCraft amber | `#f2a516` | the full-bleed field (the app colour) |
|
||||
|
||||
## Geometry
|
||||
|
||||
`viewBox="0 0 512 512"`, a rounded-square tile with `rx=112`, clipped; the colour fills the whole tile.
|
||||
macOS files (`.icns`, `lightcraft-macos-512.png`) add Apple's transparent margin (an 824/1024 body);
|
||||
Windows and Linux files use the full-bleed tile so the lynx reads at 16-48 px.
|
||||
|
||||
## Provenance
|
||||
|
||||
The owner's original drawing, made in ArtCraft (2880 px, keyed to the three colours), vectorised with
|
||||
craftrules `assets/logo-options/_tools/vectorize_tile.py`. The source drawing stays in craftrules at
|
||||
`assets/app-icons/lightcraft/source.png`; `lightcraft.svg` here is the canonical artwork. Licence:
|
||||
`LICENSE.txt` (MIT OR Apache-2.0, like the repo).
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
|---|---|
|
||||
| `lightcraft.svg` | canonical vector, traced at 2048 px (about 770 KB) |
|
||||
| `lightcraft-small.svg` | lighter vector, traced at 1024 px (about 360 KB) |
|
||||
| `lightcraft-1024.png` | 1024 px render (store listings, docs) |
|
||||
| `lightcraft-macos-512.png` | runtime window/Dock icon on macOS (embedded by `apps/lightcraft/src/main.rs`) |
|
||||
| `lightcraft.icns` | macOS bundle icon (`CFBundleIconFile`) |
|
||||
| `lightcraft.ico` | Windows icon, 16-256 px, embedded in `lightcraft.exe` by `apps/lightcraft/build.rs` |
|
||||
| `hicolor/<n>x<n>/apps/ai.storyteller.lightcraft.png` | Linux icon theme, 16-512 px; the 256 px one is also the runtime window icon on Windows and Linux |
|
||||
| `hicolor/scalable/apps/ai.storyteller.lightcraft.svg` | Linux scalable icon (the small SVG) |
|
||||
|
||||
The app id is `ai.storyteller.lightcraft`: the Wayland app id, the `.desktop` file
|
||||
(`packaging/linux/ai.storyteller.lightcraft.desktop`, `Icon=ai.storyteller.lightcraft`) and the hicolor icon
|
||||
name. To install on Linux, copy `hicolor/` into `/usr/share/icons/hicolor/` (or `~/.local/share/icons/hicolor/`)
|
||||
and the `.desktop` file into `applications/`.
|
||||
|
||||
## Regenerate
|
||||
|
||||
Edit `lightcraft.svg` (or `lightcraft-small.svg`), then run `packaging/icons.sh` (needs `resvg`; `iconutil`
|
||||
on macOS for the `.icns`; the `.ico` is packed by `cargo xtask ico`). Every output is committed.
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 663 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#f2a516"/><path fill="#efe9dc" fill-rule="evenodd" d="M214 504C214 502 214 502 214 502C213 502 213 502 212 502C212 502 212 501 213 500C214 498 214 498 212 498C210 498 209 497 211 494C212 491 212 490 210 493C209 494 208 495 207 494C207 494 207 494 208 492C210 490 210 489 207 490C205 490 205 490 206 489C207 488 207 488 206 487C206 486 206 486 207 485C207 485 207 485 207 484C207 482 207 480 207 479C209 478 208 476 205 478C202 479 201 478 203 477C204 475 208 472 208 471C208 471 207 470 206 471C203 471 203 471 205 469C206 467 206 466 205 466C204 466 204 467 203 467C202 467 202 467 204 464C206 462 206 462 204 462C203 462 203 461 204 459C205 458 204 458 202 459C200 460 197 459 199 458C199 458 200 456 201 455C201 454 202 452 203 452C205 450 205 450 204 450C203 451 203 450 203 449C204 448 203 448 201 449C199 451 199 450 200 447C202 444 201 443 200 444C198 446 197 445 198 443C199 441 199 441 198 440C198 440 198 440 199 438C202 435 202 432 200 435C199 436 199 434 201 432C203 428 204 426 203 425C203 425 202 424 203 423C203 422 202 421 202 421C201 420 201 419 202 415C203 415 202 414 202 415C201 415 201 414 201 413C200 410 200 410 199 412C198 415 197 414 197 412C198 409 197 410 194 415C191 421 190 421 190 417C190 413 190 413 187 418C184 423 183 424 182 423C182 423 181 424 180 426C178 428 178 429 178 427C178 426 177 426 177 426C177 426 176 426 176 427C176 427 175 428 175 428C175 428 175 427 175 427C175 425 174 425 173 428C171 431 171 431 171 427C171 424 170 424 168 428C165 433 164 434 164 431C164 429 164 429 163 429C162 429 162 428 163 428C164 426 165 423 163 423C162 424 162 423 163 420C163 418 163 417 162 418C161 418 161 417 161 416C161 413 160 413 160 414C159 416 159 415 158 413C157 403 156 400 154 402C154 403 153 403 153 400C154 397 153 397 150 400C147 404 146 404 146 403C146 402 147 395 148 393C148 393 148 392 147 392C146 392 146 392 147 387C148 386 147 386 146 387C145 388 144 387 144 385C144 383 144 383 142 387C140 389 140 389 139 389C139 389 139 388 139 388C140 388 140 386 140 385C140 384 141 382 141 382C141 381 141 381 140 381C139 381 138 378 138 375C138 373 138 373 137 374C136 375 136 375 136 371C136 368 136 368 133 371C131 373 128 374 130 372C132 370 133 363 131 363C130 364 130 363 130 361C130 358 130 358 128 361C125 363 125 363 125 360C126 357 126 355 125 354C125 354 125 353 125 350C125 346 125 346 124 347C123 349 122 347 122 343C122 337 122 337 121 340C119 344 118 343 119 339C119 338 119 337 119 336C119 336 119 335 119 333C120 331 120 330 120 329C119 328 119 326 120 321C121 317 121 315 119 317C118 319 117 318 118 315C119 311 119 311 118 312C116 313 116 313 116 311C117 309 116 309 115 311C114 312 113 312 113 312C112 311 116 301 117 298C119 296 119 296 117 295L116 295 116 290C117 286 117 286 116 286C114 288 114 284 115 278C115 277 114 277 114 278C113 280 112 280 112 277C112 276 112 275 112 274C111 273 111 272 112 270C113 266 113 266 111 267C109 269 108 267 109 264C110 263 110 262 109 262C109 261 109 261 110 260C110 260 111 259 111 259C111 258 111 257 111 256C113 252 113 250 112 250C110 250 111 244 113 242C114 240 115 239 114 239C112 238 112 238 113 237C114 236 116 232 116 231C116 231 116 231 115 231C113 231 113 230 116 227C118 224 118 223 118 223C118 222 118 221 119 220C121 218 121 218 120 217C120 216 120 216 121 214C123 212 123 212 122 211C122 210 122 209 123 208C124 207 124 206 125 206C125 206 125 205 126 204C127 202 127 201 127 201C127 200 127 199 130 196C133 193 134 191 131 188C131 187 130 186 130 184C130 182 130 181 129 180C129 179 128 178 128 178C128 177 128 176 127 176C127 175 127 174 127 173C127 172 126 171 126 171C125 170 125 166 126 165C126 165 126 163 126 161C126 157 126 157 127 156C128 155 128 155 127 154C125 153 125 152 125 152C126 151 126 150 125 149C123 146 122 144 123 143C124 143 124 142 124 141C124 141 124 140 124 139C125 138 124 136 123 137C122 138 121 138 122 136C122 135 122 135 121 135C120 134 120 135 121 133C121 132 122 131 122 131C124 131 125 130 123 129C122 128 122 127 122 127C123 127 123 126 122 126C121 125 121 125 120 123C120 122 120 121 119 120C118 120 118 119 120 118C121 118 120 116 118 116C116 116 115 115 116 113L117 111 116 111C115 110 114 110 115 108C115 107 115 107 114 106C113 105 113 104 114 102C115 102 114 101 114 100C113 99 112 95 113 94C114 94 113 92 112 91C111 91 111 91 112 88C112 85 112 85 111 84C110 83 110 83 110 81C110 79 110 78 110 77C109 76 109 75 109 72C110 66 109 56 108 53C107 52 107 51 107 50C107 50 106 49 104 47C103 46 102 44 101 44C101 43 100 41 99 40C97 38 93 31 93 29C93 28 92 26 92 24C91 22 91 20 91 20C92 19 92 19 93 24C94 29 99 37 101 37C101 37 101 37 101 36C98 31 96 24 97 22C98 21 98 19 98 16C99 12 100 8 102 7C103 6 103 7 103 10C102 16 103 15 105 10C106 7 107 7 107 7C109 6 110 7 109 9C107 14 108 16 110 12C111 10 113 9 112 11C110 16 113 16 115 11C116 9 117 8 118 9Line truncated
|
||||
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 110 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#f2a516"/><path fill="#efe9dc" fill-rule="evenodd" d="M214 504C214 502 214 502 214 502C213 502 213 502 212 502C212 502 212 501 213 500C214 498 214 498 212 498C210 498 209 497 211 494C212 491 212 490 210 493C209 494 208 495 207 494C207 494 207 494 208 492C210 490 210 489 207 490C205 490 205 490 206 489C207 488 207 488 206 487C206 486 206 486 207 485C207 485 207 485 207 484C207 482 207 480 207 479C209 478 208 476 205 478C202 479 201 478 203 477C204 475 208 472 208 471C208 471 207 470 206 471C203 471 203 471 205 469C206 467 206 466 205 466C204 466 204 467 203 467C202 467 202 467 204 464C206 462 206 462 204 462C203 462 203 461 204 459C205 458 204 458 202 459C200 460 197 459 199 458C199 458 200 456 201 455C201 454 202 452 203 452C205 450 205 450 204 450C203 451 203 450 203 449C204 448 203 448 201 449C199 451 199 450 200 447C202 444 201 443 200 444C198 446 197 445 198 443C199 441 199 441 198 440C198 440 198 440 199 438C202 435 202 432 200 435C199 436 199 434 201 432C203 428 204 426 203 425C203 425 202 424 203 423C203 422 202 421 202 421C201 420 201 419 202 415C203 415 202 414 202 415C201 415 201 414 201 413C200 410 200 410 199 412C198 415 197 414 197 412C198 409 197 410 194 415C191 421 190 421 190 417C190 413 190 413 187 418C184 423 183 424 182 423C182 423 181 424 180 426C178 428 178 429 178 427C178 426 177 426 177 426C177 426 176 426 176 427C176 427 175 428 175 428C175 428 175 427 175 427C175 425 174 425 173 428C171 431 171 431 171 427C171 424 170 424 168 428C165 433 164 434 164 431C164 429 164 429 163 429C162 429 162 428 163 428C164 426 165 423 163 423C162 424 162 423 163 420C163 418 163 417 162 418C161 418 161 417 161 416C161 413 160 413 160 414C159 416 159 415 158 413C157 403 156 400 154 402C154 403 153 403 153 400C154 397 153 397 150 400C147 404 146 404 146 403C146 402 147 395 148 393C148 393 148 392 147 392C146 392 146 392 147 387C148 386 147 386 146 387C145 388 144 387 144 385C144 383 144 383 142 387C140 389 140 389 139 389C139 389 139 388 139 388C140 388 140 386 140 385C140 384 141 382 141 382C141 381 141 381 140 381C139 381 138 378 138 375C138 373 138 373 137 374C136 375 136 375 136 371C136 368 136 368 133 371C131 373 128 374 130 372C132 370 133 363 131 363C130 364 130 363 130 361C130 358 130 358 128 361C125 363 125 363 125 360C126 357 126 355 125 354C125 354 125 353 125 350C125 346 125 346 124 347C123 349 122 347 122 343C122 337 122 337 121 340C119 344 118 343 119 339C119 338 119 337 119 336C119 336 119 335 119 333C120 331 120 330 120 329C119 328 119 326 120 321C121 317 121 315 119 317C118 319 117 318 118 315C119 311 119 311 118 312C116 313 116 313 116 311C117 309 116 309 115 311C114 312 113 312 113 312C112 311 116 301 117 298C119 296 119 296 117 295L116 295 116 290C117 286 117 286 116 286C114 288 114 284 115 278C115 277 114 277 114 278C113 280 112 280 112 277C112 276 112 275 112 274C111 273 111 272 112 270C113 266 113 266 111 267C109 269 108 267 109 264C110 263 110 262 109 262C109 261 109 261 110 260C110 260 111 259 111 259C111 258 111 257 111 256C113 252 113 250 112 250C110 250 111 244 113 242C114 240 115 239 114 239C112 238 112 238 113 237C114 236 116 232 116 231C116 231 116 231 115 231C113 231 113 230 116 227C118 224 118 223 118 223C118 222 118 221 119 220C121 218 121 218 120 217C120 216 120 216 121 214C123 212 123 212 122 211C122 210 122 209 123 208C124 207 124 206 125 206C125 206 125 205 126 204C127 202 127 201 127 201C127 200 127 199 130 196C133 193 134 191 131 188C131 187 130 186 130 184C130 182 130 181 129 180C129 179 128 178 128 178C128 177 128 176 127 176C127 175 127 174 127 173C127 172 126 171 126 171C125 170 125 166 126 165C126 165 126 163 126 161C126 157 126 157 127 156C128 155 128 155 127 154C125 153 125 152 125 152C126 151 126 150 125 149C123 146 122 144 123 143C124 143 124 142 124 141C124 141 124 140 124 139C125 138 124 136 123 137C122 138 121 138 122 136C122 135 122 135 121 135C120 134 120 135 121 133C121 132 122 131 122 131C124 131 125 130 123 129C122 128 122 127 122 127C123 127 123 126 122 126C121 125 121 125 120 123C120 122 120 121 119 120C118 120 118 119 120 118C121 118 120 116 118 116C116 116 115 115 116 113L117 111 116 111C115 110 114 110 115 108C115 107 115 107 114 106C113 105 113 104 114 102C115 102 114 101 114 100C113 99 112 95 113 94C114 94 113 92 112 91C111 91 111 91 112 88C112 85 112 85 111 84C110 83 110 83 110 81C110 79 110 78 110 77C109 76 109 75 109 72C110 66 109 56 108 53C107 52 107 51 107 50C107 50 106 49 104 47C103 46 102 44 101 44C101 43 100 41 99 40C97 38 93 31 93 29C93 28 92 26 92 24C91 22 91 20 91 20C92 19 92 19 93 24C94 29 99 37 101 37C101 37 101 37 101 36C98 31 96 24 97 22C98 21 98 19 98 16C99 12 100 8 102 7C103 6 103 7 103 10C102 16 103 15 105 10C106 7 107 7 107 7C109 6 110 7 109 9C107 14 108 16 110 12C111 10 113 9 112 11C110 16 113 16 115 11C116 9 117 8 118 9Line truncated
|
||||
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 85 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#f2a516"/><path fill="#efe9dc" fill-rule="evenodd" d="M213.9 505.5C213.8 505.3 213.8 504.9 214 504.5C214.8 502.3 214.5 501.4 212.9 502C211.9 502.3 211.5 502 212 501.1C213 499.5 213.9 497.5 213.8 497.4C213.7 497.3 213.1 497.5 212.5 497.8C210.1 498.7 209.5 498.3 210 496.4C210.1 495.9 210.4 495 210.5 494.5C210.6 494.1 211 493.3 211.4 492.7C212 491.7 212.2 490.8 211.7 490.8C211.3 490.8 209.7 492.6 208.9 493.8C208.6 494.3 208.3 494.5 207.6 494.5C206.5 494.5 206.3 494.1 207.2 493.2C208.1 492.1 209.2 490.4 209.2 490C209.2 489.8 208.7 489.7 207.1 489.8C204.5 489.9 204.3 489.7 205.9 488.5C207.2 487.6 207.2 487.4 206.4 487C205.5 486.6 205.6 486.3 206.8 485.2L207.9 484.3 207.1 483.5C206.5 482.8 206.4 482.7 206.7 482.2C207 481.8 207 481.6 206.6 481.2C206.2 480.8 206.2 480.7 207.2 479.4C207.8 478.7 208.2 477.9 208.2 477.7C208.2 477 206.2 477.1 204.4 477.9C203 478.6 202.3 478.6 202.1 477.8C202 477.6 203 476.5 204.2 475.2C206.3 473.2 207.5 471.5 207.5 470.6C207.5 470.2 207.4 470.2 205.4 470.7C202.8 471.4 202.7 471.1 204.7 468.7C205.9 467.3 206.4 466.4 206.1 466.1C206 465.9 205.5 466.1 202.8 466.9C201.5 467.3 200.7 467 201.6 466.4C202.7 465.8 206 461.7 206 461C206 460.9 205.3 461.1 204.5 461.4C203.7 461.8 202.9 462 202.8 461.9C202.5 461.7 203.3 459.5 203.6 459.5C203.8 459.5 204.1 459.3 204.4 459C205.2 458.1 204.2 458.2 202.4 459.1C201 459.9 200.6 460 200.2 459.8C200 459.6 199.4 459.5 199.1 459.5C198.7 459.5 198.1 459.4 197.9 459.2C197.5 458.9 197.6 458.8 198.5 458C199.2 457.4 199.8 456.6 200.4 455.4C201.4 453.3 202.1 452.5 203.8 451.3C204.7 450.7 205 450.3 205 449.9C205 449.5 205.4 448.9 205.8 448.4L206.6 447.5 205.7 448C205.2 448.3 204.7 448.8 204.6 449.1C204.4 449.7 203.3 451 203 451C202.5 451 202.8 450.1 203.5 448.9C203.9 448.3 204.2 447.6 204.1 447.5C203.9 447.4 202.7 448.1 201.2 449.3C200.5 449.8 199.8 450.2 199.6 450.2C199.1 450.2 198.9 449.8 199.3 449.3C199.9 448.7 201.2 444.9 201.2 444.1C201.3 443.5 200.7 443.7 199.4 444.6C198.7 445.1 198 445.5 197.8 445.5C197.4 445.5 197.1 444.8 197.5 444.4C197.8 444 199 440.8 198.8 440.6C198.7 440.6 198.5 440.5 198.2 440.5C198 440.5 197.7 440.3 197.5 440.1C197.3 439.8 197.5 439.5 199.1 437.9C200.8 436.2 202.2 434.2 201.8 433.9C201.7 433.8 201.1 434.1 200.5 434.4C198.6 435.5 198.2 434.8 199.7 433C201.1 431.2 202.6 428.7 203 427.6C203.2 427.1 203.4 426.5 203.6 426.4C204 425.9 203.8 425.1 203.2 425.1C202.6 425.1 202.6 425 202.6 423C202.6 421.1 202.6 420.9 202.2 421C200.8 421.2 200.6 419.3 201.9 416.3C202.8 414.1 202.8 413.7 201.7 414.7C200.8 415.6 200.6 415.3 200.6 413C200.6 409.8 200 409.8 198.5 412.8C197.2 415.3 196.8 415.1 197.4 412.5C197.8 410.6 197.7 410.2 197 410.2C196.4 410.2 195.5 411.7 193.1 416.5C190.5 421.8 190 421.8 190 416.8C190 413.5 189.9 413.2 189 414.7C188.7 415.2 188.2 415.9 188 416.1C187.7 416.4 186.6 418.1 185.5 419.9C183.2 423.6 182.9 423.9 182.2 423.3C181.8 423 181.7 423 181.4 423.2C180.8 423.7 178.8 426.8 178.4 427.8C177.6 429.6 177 428.7 177.5 426.6C178 424.3 177.5 424.5 175.7 427.3C174.7 428.7 174.3 428.3 174.8 426.6C175.1 425.9 175.2 425.3 175.2 425.2C175.2 424.3 173.7 426.3 172.3 429C170.9 431.6 170.2 431.9 170.6 429.6C171.1 426.8 171.1 424.5 170.6 424.3C170.1 424.1 169.4 425 169.1 426C169 426.3 168.6 427.1 168.3 427.8C167.9 428.4 167.1 429.7 166.6 430.7C165.4 433.1 164.5 433.1 164.6 430.6C164.6 429.1 164.4 428.8 163.5 428.8C162.5 428.8 162.5 428.6 163.3 427.4C164 426.4 164.8 423.4 164.4 423.1C164.3 423 164 423 163.8 423.2C162.3 424.3 162.1 423.9 162.6 420.9C163.1 418 162.9 416.8 162 417.6C161.1 418.4 160.8 417.8 160.9 415.5C161 413.1 160.8 412.9 159.8 414.5C158.7 416.3 158.4 416 158.4 413.4C158.4 411.7 158.2 410.9 157.9 410.3C157.6 409.7 156.2 402.2 156.2 401C156.2 400.4 155.5 400.9 154.5 401.9C153.3 403.3 152.9 403.2 153.3 401.5C153.6 399.7 153.5 397.9 153.1 397.5C152.7 397.2 152.8 397.2 150.7 399.7C150 400.5 149.3 401.2 149.2 401.2C148.8 401.2 147.5 402.5 147.1 403.3C146.8 404 146.3 404 145.9 403.5C145.8 403.2 145.8 402.4 146.2 400.7C146.7 398.5 147.4 395.1 147.7 393.3C147.8 392.5 147.6 392.3 147 392.8C146.4 393.5 146.2 392.6 146.6 390.8C147.1 388.8 147.4 385.8 147.1 385.8C146.7 385.8 146 386.3 146 386.6C146 387 145.1 387.6 144.9 387.3C144.9 387.2 144.7 386.3 144.6 385.4C144.3 382.3 144.3 382.3 142.4 385.5C140.6 388.5 139.7 389.7 139.3 389.4C138.8 389.1 138.9 388.6 139.6 387.5C140.1 386.8 140.2 386.4 140.1 385.7C140 385.2 140.1 384.2 140.4 383.3C140.9 381.1 140.9 380.8 140.1 380.8C139.5 380.9 139.4 380.7 139.2 380.1C139.2 379.7 138.9 379 138.6 378.6C138.2 378.1 138.1 377.3 138 375.3L137.9 372.6 136.8 373.7C135.6 375.1 135.2 374.8 135.7 372.7C136.1 371.2 136.1 368.3 135.8 368.1C135.6 368.1 135.4 368.2 135.1 368.6C134.4 369.6 130.4 373.5 130.1 373.5C129.7 373.5 129.6 372.5 130 372.1C130.3 371.8 131.3 368.3 131.9 365.6C132.5 362.5 132.3 361.6 131.1 363.1CLine truncated
|
||||
|
After Width: | Height: | Size: 750 KiB |
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerate every app icon file from assets/app-icon/lightcraft.svg.
|
||||
#
|
||||
# Needs: resvg (brew install resvg / cargo install resvg). On macOS, iconutil also writes the
|
||||
# .icns. The outputs are committed, so building and packaging never need these tools.
|
||||
#
|
||||
# packaging/icons.sh
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
DIR="$ROOT/assets/app-icon"
|
||||
SVG="$DIR/lightcraft.svg"
|
||||
ID="ai.storyteller.lightcraft"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
command -v resvg >/dev/null || { echo "error: resvg not found (brew install resvg)" >&2; exit 1; }
|
||||
|
||||
# The SVG is the full-bleed 512 tile (rx=112). Windows and Linux use it as is, so the lynx reads at
|
||||
# 16-48 px. macOS icons follow Apple's grid: an 824/1024 body with a transparent margin, made by
|
||||
# widening the viewBox (512 / 0.805 = 636, so 62 units each side).
|
||||
MAC="$TMP/macos.svg"
|
||||
sed 's/viewBox="0 0 512 512"/viewBox="-62 -62 636 636"/' "$SVG" >"$MAC"
|
||||
grep -q 'viewBox="-62 -62 636 636"' "$MAC" || { echo "error: unexpected viewBox in $SVG" >&2; exit 1; }
|
||||
|
||||
render() { resvg -w "$2" -h "$2" "$1" "$3" </dev/null; }
|
||||
|
||||
render "$SVG" 1024 "$DIR/lightcraft-1024.png"
|
||||
# Runtime window/Dock icon on macOS (embedded by apps/lightcraft/src/main.rs).
|
||||
render "$MAC" 512 "$DIR/lightcraft-macos-512.png"
|
||||
|
||||
# Linux hicolor theme (also the runtime window icon on Windows and Linux: 256x256).
|
||||
for s in 16 24 32 48 64 128 256 512; do
|
||||
mkdir -p "$DIR/hicolor/${s}x${s}/apps"
|
||||
render "$SVG" "$s" "$DIR/hicolor/${s}x${s}/apps/$ID.png"
|
||||
done
|
||||
mkdir -p "$DIR/hicolor/scalable/apps"
|
||||
cp "$DIR/lightcraft-small.svg" "$DIR/hicolor/scalable/apps/$ID.svg"
|
||||
|
||||
# Windows .ico.
|
||||
ICO_PNGS=()
|
||||
for s in 16 20 24 32 40 48 64 128 256; do
|
||||
render "$SVG" "$s" "$TMP/ico-$s.png"
|
||||
ICO_PNGS+=("$TMP/ico-$s.png")
|
||||
done
|
||||
(cd "$ROOT" && cargo run -q -p xtask -- ico "$DIR/lightcraft.ico" "${ICO_PNGS[@]}")
|
||||
|
||||
# macOS .icns.
|
||||
if command -v iconutil >/dev/null; then
|
||||
SET="$TMP/lightcraft.iconset"
|
||||
mkdir -p "$SET"
|
||||
for s in 16 32 128 256 512; do
|
||||
render "$MAC" "$s" "$SET/icon_${s}x${s}.png"
|
||||
render "$MAC" $((s * 2)) "$SET/icon_${s}x${s}@2x.png"
|
||||
done
|
||||
iconutil -c icns -o "$DIR/lightcraft.icns" "$SET"
|
||||
else
|
||||
echo "warning: iconutil not found (macOS only); lightcraft.icns not regenerated" >&2
|
||||
fi
|
||||
echo "icons written to $DIR"
|
||||
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=LightCraft
|
||||
GenericName=Photo Library and Raw Developer
|
||||
Comment=Organise photos and develop raw files, non-destructively
|
||||
Exec=lightcraft %F
|
||||
Icon=ai.storyteller.lightcraft
|
||||
Terminal=false
|
||||
Categories=Graphics;2DGraphics;RasterGraphics;Photography;
|
||||
Keywords=photo;raw;camera;library;develop;
|
||||
StartupWMClass=ai.storyteller.lightcraft
|
||||
MimeType=image/jpeg;image/png;image/tiff;image/webp;image/x-adobe-dng;image/x-sony-arw;image/x-canon-cr2;image/x-canon-cr3;image/x-nikon-nef;image/x-fuji-raf;image/x-olympus-orf;image/x-panasonic-rw2;image/x-pentax-pef;
|
||||
@@ -0,0 +1,104 @@
|
||||
//! `cargo xtask ico <out.ico> <in.png>…`: pack PNG images into a Windows `.ico`.
|
||||
//!
|
||||
//! Vista and later read PNG-compressed icon entries, so each PNG is stored as is. The size comes
|
||||
//! from the PNG's IHDR chunk; 256 px is written as 0, per the ICO format.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
/// Width and height from a PNG's IHDR chunk.
|
||||
pub fn png_size(png: &[u8]) -> Result<(u32, u32), String> {
|
||||
const SIG: &[u8] = b"\x89PNG\r\n\x1a\n";
|
||||
if png.len() < 24 || &png[..8] != SIG || &png[12..16] != b"IHDR" {
|
||||
return Err("not a PNG file".into());
|
||||
}
|
||||
let w = u32::from_be_bytes([png[16], png[17], png[18], png[19]]);
|
||||
let h = u32::from_be_bytes([png[20], png[21], png[22], png[23]]);
|
||||
Ok((w, h))
|
||||
}
|
||||
|
||||
/// An ICO file holding `pngs`, which must be square and at most 256 px.
|
||||
pub fn pack(pngs: &[Vec<u8>]) -> Result<Vec<u8>, String> {
|
||||
if pngs.is_empty() || pngs.len() > u16::MAX as usize {
|
||||
return Err("need at least one PNG".into());
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(&0u16.to_le_bytes()); // reserved
|
||||
out.extend_from_slice(&1u16.to_le_bytes()); // type: icon
|
||||
out.extend_from_slice(&(pngs.len() as u16).to_le_bytes());
|
||||
let mut offset = 6 + 16 * pngs.len();
|
||||
for png in pngs {
|
||||
let (w, h) = png_size(png)?;
|
||||
if w != h || w == 0 || w > 256 {
|
||||
return Err(format!("icon images must be square and at most 256 px, got {w}x{h}"));
|
||||
}
|
||||
let dim = if w == 256 { 0 } else { w as u8 };
|
||||
out.extend_from_slice(&[dim, dim, 0, 0]); // width, height, palette size, reserved
|
||||
out.extend_from_slice(&1u16.to_le_bytes()); // colour planes
|
||||
out.extend_from_slice(&32u16.to_le_bytes()); // bits per pixel
|
||||
out.extend_from_slice(&(png.len() as u32).to_le_bytes());
|
||||
out.extend_from_slice(&(offset as u32).to_le_bytes());
|
||||
offset += png.len();
|
||||
}
|
||||
for png in pngs {
|
||||
out.extend_from_slice(png);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn run(args: &[&str]) -> Result<(), String> {
|
||||
let [out, inputs @ ..] = args else {
|
||||
return Err("usage: cargo xtask ico <out.ico> <in.png>…".into());
|
||||
};
|
||||
let pngs = inputs.iter().map(|p| std::fs::read(p).map_err(|e| format!("read {p}: {e}"))).collect::<Result<Vec<_>, _>>()?;
|
||||
let ico = pack(&pngs)?;
|
||||
std::fs::write(Path::new(out), &ico).map_err(|e| format!("write {out}: {e}"))?;
|
||||
println!("{out}: {} images, {} bytes", pngs.len(), ico.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn fake_png(size: u32) -> Vec<u8> {
|
||||
let mut v = b"\x89PNG\r\n\x1a\n\0\0\0\x0dIHDR".to_vec();
|
||||
v.extend_from_slice(&size.to_be_bytes());
|
||||
v.extend_from_slice(&size.to_be_bytes());
|
||||
v.extend_from_slice(&[8, 6, 0, 0, 0, 1, 2, 3, 4]);
|
||||
v
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packs_directory_and_payloads() {
|
||||
let a = fake_png(16);
|
||||
let b = fake_png(256);
|
||||
let ico = pack(&[a.clone(), b.clone()]).unwrap();
|
||||
assert_eq!(&ico[..6], &[0, 0, 1, 0, 2, 0]);
|
||||
// Entry 0: 16 px at offset 6 + 32.
|
||||
assert_eq!(&ico[6..8], &[16, 16]);
|
||||
assert_eq!(u32::from_le_bytes(ico[14..18].try_into().unwrap()), a.len() as u32);
|
||||
assert_eq!(u32::from_le_bytes(ico[18..22].try_into().unwrap()), 38);
|
||||
// Entry 1: 256 px stored as 0, right after the first payload.
|
||||
assert_eq!(&ico[22..24], &[0, 0]);
|
||||
assert_eq!(u32::from_le_bytes(ico[34..38].try_into().unwrap()), 38 + a.len() as u32);
|
||||
assert_eq!(&ico[38..38 + a.len()], &a[..]);
|
||||
assert_eq!(&ico[38 + a.len()..], &b[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_bad_input() {
|
||||
assert!(pack(&[]).is_err());
|
||||
assert!(pack(&[fake_png(512)]).is_err());
|
||||
assert!(pack(&[b"GIF89a".to_vec()]).is_err());
|
||||
assert_eq!(png_size(&fake_png(48)).unwrap(), (48, 48));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn committed_icon_is_valid() {
|
||||
let path = crate::root().join("assets/app-icon/lightcraft.ico");
|
||||
let Ok(ico) = std::fs::read(&path) else { return };
|
||||
assert_eq!(&ico[..4], &[0, 0, 1, 0]);
|
||||
let n = u16::from_le_bytes([ico[4], ico[5]]) as usize;
|
||||
assert!(n >= 4, "expected several sizes in {}", path.display());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
mod assets;
|
||||
mod bench;
|
||||
mod ico;
|
||||
mod layers;
|
||||
mod parity;
|
||||
mod stats;
|
||||
@@ -21,6 +22,8 @@ commands:
|
||||
bench [FILE] [--strict] [--threshold PCT]
|
||||
run the render benchmark, append to target/bench/history.jsonl, compare CPU time with
|
||||
the previous run (default input: corpus/raw/arw-sony-a7m3-compressed.arw)
|
||||
ico <out.ico> <in.png>...
|
||||
pack square PNGs (<= 256 px) into a Windows .ico (see packaging/icons.sh)
|
||||
layers enforce the crate dependency layering (plan/architecture.md §3)
|
||||
parity [--write]
|
||||
check docs/parity.md (every cmd:/ctl: id and path it cites exists) and print the
|
||||
@@ -39,6 +42,7 @@ fn main() -> ExitCode {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let rest: Vec<&str> = args.iter().skip(1).map(String::as_str).collect();
|
||||
let result = match args.first().map(String::as_str) {
|
||||
Some("ico") => ico::run(&rest),
|
||||
Some("layers") => cmd_layers(),
|
||||
Some("assets") => assets::run(&root()),
|
||||
Some("bench") => bench::run(&root(), &rest),
|
||||
|
||||