summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--AGENTS.md134
-rw-r--r--Cargo.lock6477
-rw-r--r--Cargo.toml14
-rw-r--r--Makefile.am6
-rw-r--r--README.md33
-rw-r--r--codegen/expr2rust.mac31
-rw-r--r--codegen/fixup.py14
-rw-r--r--codegen/lsq_cylindrical.mac38
-rw-r--r--codegen/lsq_rectilinear.mac60
-rwxr-xr-xcodegen/regen.sh16
-rw-r--r--configure.ac68
-rw-r--r--crates/gipfel-app/Cargo.toml19
-rw-r--r--crates/gipfel-app/data/org.gipfel.App.desktop13
-rw-r--r--crates/gipfel-app/src/compass.rs611
-rw-r--r--crates/gipfel-app/src/dump.rs285
-rw-r--r--crates/gipfel-app/src/main.rs1805
-rw-r--r--crates/gipfel-app/src/viewport.rs659
-rw-r--r--crates/gipfel-cli/Cargo.toml13
-rw-r--r--crates/gipfel-cli/src/main.rs371
-rw-r--r--crates/gipfel-core/Cargo.toml13
-rw-r--r--crates/gipfel-core/examples/genjpg.rs24
-rw-r--r--crates/gipfel-core/src/hill.rs224
-rw-r--r--crates/gipfel-core/src/lib.rs23
-rw-r--r--crates/gipfel-core/src/lsq.rs392
-rw-r--r--crates/gipfel-core/src/metadata.rs697
-rw-r--r--crates/gipfel-core/src/panorama.rs502
-rw-r--r--crates/gipfel-core/src/profile.rs362
-rw-r--r--crates/gipfel-core/src/projection/cylindrical.rs86
-rw-r--r--crates/gipfel-core/src/projection/generated/cylindrical_funcs.rs10
-rw-r--r--crates/gipfel-core/src/projection/generated/rectilinear_funcs.rs16
-rw-r--r--crates/gipfel-core/src/projection/mod.rs174
-rw-r--r--crates/gipfel-core/src/projection/rectilinear.rs104
-rw-r--r--crates/gipfel-core/src/stitch.rs323
-rw-r--r--crates/gipfel-core/src/track.rs248
-rw-r--r--crates/gipfel-core/tests/golden.rs511
-rw-r--r--examples/Makefile63
-rwxr-xr-xexamples/gipfel2html136
-rw-r--r--gipfel.spec56
-rw-r--r--src/Fl_Search_Chooser.H42
-rw-r--r--src/Fl_Search_Chooser.cxx137
-rw-r--r--src/Fl_Value_Dial.H29
-rw-r--r--src/Fl_Value_Dial.cxx67
-rw-r--r--src/GipfelWidget.H93
-rw-r--r--src/GipfelWidget.cxx822
-rw-r--r--src/Hill.H69
-rw-r--r--src/Hill.cxx341
-rw-r--r--src/ImageMetaData.H65
-rw-r--r--src/ImageMetaData.cxx322
-rw-r--r--src/JPEGOutputImage.H38
-rw-r--r--src/JPEGOutputImage.cxx104
-rw-r--r--src/Makefile.am45
-rw-r--r--src/Makefile.lsq_funcs7
-rw-r--r--src/OutputImage.H32
-rw-r--r--src/OutputImage.cxx58
-rw-r--r--src/Panorama.H83
-rw-r--r--src/Panorama.cxx476
-rw-r--r--src/PreviewOutputImage.H34
-rw-r--r--src/PreviewOutputImage.cxx65
-rw-r--r--src/ProjectionCylindrical.H33
-rw-r--r--src/ProjectionCylindrical.cxx26
-rw-r--r--src/ProjectionLSQ.H59
-rw-r--r--src/ProjectionLSQ.cxx294
-rw-r--r--src/ProjectionRectilinear.H38
-rw-r--r--src/ProjectionRectilinear.cxx12
-rw-r--r--src/ScanImage.H31
-rw-r--r--src/ScanImage.cxx118
-rw-r--r--src/ScreenDump.H26
-rw-r--r--src/ScreenDump.cxx59
-rw-r--r--src/Stitch.H32
-rw-r--r--src/Stitch.cxx117
-rw-r--r--src/TIFFOutputImage.H33
-rw-r--r--src/TIFFOutputImage.cxx97
-rw-r--r--src/ViewParams.H21
-rw-r--r--src/choose_hill.H15
-rw-r--r--src/choose_hill.cxx43
-rw-r--r--src/expr2c.mac28
-rw-r--r--src/gipfel.cxx738
-rw-r--r--src/lsq_cylindrical.mac38
-rw-r--r--src/lsq_rectilinear.mac60
-rw-r--r--src/strsep.c70
-rw-r--r--src/strsep.h12
-rwxr-xr-xtests/gen_golden.sh47
-rw-r--r--tests/golden/export.txt17
-rw-r--r--tests/golden/fit_cyl.txt14
-rw-r--r--tests/golden/fit_rect.txt14
-rw-r--r--tests/golden/funcs.txt200
-rw-r--r--tests/golden/stitch.ppmbin0 -> 18445 bytes
-rw-r--r--tests/golden/stitch_cyl_bic.txt3072
-rw-r--r--tests/golden/stitch_dist_bic.txt3072
-rw-r--r--tests/golden/stitch_rect_bic.txt3072
-rw-r--r--tests/golden/stitch_rect_near.txt3072
-rw-r--r--tests/golden/test.jpgbin0 -> 17491 bytes
-rw-r--r--tests/harness/build/cyl_funcs.cxx10
-rwxr-xr-xtests/harness/build/exportdumpbin0 -> 403880 bytes
-rwxr-xr-xtests/harness/build/fitdumpbin0 -> 252104 bytes
-rwxr-xr-xtests/harness/build/funcsdumpbin0 -> 29832 bytes
-rw-r--r--tests/harness/build/rect_funcs.cxx16
-rwxr-xr-xtests/harness/build/stitchdumpbin0 -> 276152 bytes
-rw-r--r--tests/harness/exportdump.cxx77
-rw-r--r--tests/harness/fitdump.cxx83
-rw-r--r--tests/harness/funcsdump.cxx60
-rw-r--r--tests/harness/stitchdump.cxx102
103 files changed, 27234 insertions, 5258 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7804fde
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/target
+harness/build
+*.tmp
+gipfel-app-bin.tar.gz
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..f8992c7
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,134 @@
+# AGENTS.md — gipfel-rs
+
+Guidance for AI coding agents working in this repository.
+
+## What this is
+
+`gipfel-rs` is a Rust port of **gipfel** (a mountain-photo annotator: you load a
+photo, mark known peaks, and it fits the camera viewpoint + lens distortion from
+the hill database). The original is C++/FLTK (`https://git.flpsed.org/gipfel`,
+cloned at `~/gipfel`). This port targets **Cosmic DE** (libcosmic) on Linux.
+
+The port is organized as milestones (M1 core → M5 polish); see `~/gipfel/plan.md`
+for the full plan and `~/gipfel-spikes/MEMO.md` for early UI-spike decisions.
+
+## Workspace layout
+
+```
+crates/gipfel-core/ headless computational core (no GUI, no native C deps)
+ hill.rs hill DB: CSV load (Latin-1), duplicate marking
+ panorama.rs viewpoint, geodesy, refraction, occlusion, visibility
+ projection/ rectilinear + cylindrical projections
+ generated/ maxima-generated symbolic functions (DO NOT hand-edit)
+ lsq.rs pure-Rust Levenberg-Marquardt fit (was GSL; GSL removed)
+ metadata.rs `gipfel:` JPEG comment + EXIF read/write
+ profile.rs distortion profiles (FLTK prefs format, XDG data dir)
+ stitch.rs cylindrical panorama resampling (ScanImage/Stitch port) + TIFF/JPEG out
+ track.rs GPX track parser (hardened: no XXE, size cap)
+crates/gipfel-cli/ CLI: export-position, export-hills, info, stitch, profiles
+crates/gipfel-app/ libcosmic GUI app (binary name: `gipfel`)
+ main.rs app state + Elm-style update/view + sidebar
+ viewport.rs custom canvas Widget (image + markers + labels)
+ compass.rs compass-strip orientation widget
+ dump.rs off-screen image renderer (screen dump)
+codegen/ maxima scripts + regen.sh (regenerates projection/generated/)
+tests/ C++ reference harnesses (.cxx) + golden files
+```
+
+## Build & test
+
+```sh
+cargo build --release # app binary: target/release/gipfel
+cargo test --lib --test golden # unit + golden tests (the main gate)
+cargo test --workspace # everything incl. app-crate tests
+cargo clippy --workspace --all-targets
+```
+
+- **Rust edition 2021.** No pinned toolchain file.
+- **No native C dependencies** since GSL was replaced by a pure-Rust LM. The only
+ system libs are what libcosmic/wgpu need (Vulkan/GL, fontconfig).
+- The dev environment has **no reachable display server** — the GUI app compiles
+ but cannot be run here. Runtime UI verification is done by the user on a real
+ Wayland desktop. Do not assume you can launch `gipfel` to test it.
+
+## Golden tests — the critical invariant
+
+`crates/gipfel-core/tests/golden.rs` compares output against files in
+`tests/golden/` produced by the **original C++ build**. This is the contract that
+keeps the math faithful.
+
+- Fit tests (`fit_rectilinear_matches_c`, `fit_cylindrical_matches_c`): fitted
+ params within **1e-6 relative**, final projected coords within **1e-9 relative**.
+- Function-grid tests: ~1e-15 agreement on the maxima-generated derivatives.
+- Several golden tests **skip silently** if the hill DB is missing. They look for
+ the data file via `$GIPFEL_DATA`, then `$HOME/gipfel/gipfel.dat`. Set
+ `GIPFEL_DATA=/path/to/gipfel.dat` so they actually run (a skipped golden test
+ is a green test that verified nothing).
+- **Never edit `tests/golden/*` to make a failing test pass.** They are ground
+ truth from C++. If a golden test fails, your change altered behavior — fix the
+ code or understand the deviation.
+
+Regenerating goldens (only when the C++ reference changes):
+`tests/gen_golden.sh` — needs the built C++ tree (`$GIPFEL_SRC`, default
+`~/gipfel/src`), `$GIPFEL_DATA`, g++, GSL, maxima. The `.cxx` harnesses still use
+C++ GSL; that is intentional (they are the reference, not part of the Rust build).
+
+## Code generation (maxima)
+
+`crates/gipfel-core/src/projection/generated/*_funcs.rs` are **generated** from
+`codegen/lsq_{rectilinear,cylindrical}.mac` via `codegen/regen.sh` (needs maxima;
+optional rustfmt). The mathematical models (refraction, cone-visibility,
+distortion) and their symbolic derivatives are **frozen** — do not change the
+models. If you regenerate, re-run the golden tests immediately.
+
+## Hard compatibility constraints
+
+- **`gipfel.dat` CSV format** and the **`gipfel:` JPEG comment parameter string**
+ must stay byte-compatible with the C++ version (keys `nick`/`tilt` are kept in
+ the file even though the code renamed them to pitch/roll).
+- **Hill indices are stable handles.** `Panorama::hills` keeps insertion order;
+ `mark_duplicates` sorts an index permutation, never the vec. Track lists and
+ known-hill sets index into `hills`. Do not reorder `hills` in place.
+- **Terminology:** UI says "Pitch"/"Roll"; internal fields are `a_pitch`/`a_roll`;
+ hill elevation is `a_elev` (distinct from camera pitch). JPEG keys stay
+ `nick`/`tilt`.
+- Distortion profiles live in `$XDG_DATA_HOME/gipfel/distortion-profiles.prefs`
+ (FLTK prefs format, legacy auto-import). Keep the `%g`-style float formatting
+ (`profile::fmt_g`) matching C.
+
+## Code conventions
+
+- **Elm architecture** in the app: `update(&mut self, Message) -> Task` is one
+ flat, **exhaustive** `match` (no catch-all) — keep it exhaustive so new
+ `Message` variants are forced to be handled. Prefer extracting small helper
+ methods over a catch-all arm.
+- Shared helpers to reuse (don't duplicate): `slider_item()`, `pick_open`/
+ `pick_save` (file dialogs), `compass::newton_inverse` (scalar inverse
+ projection), `viewport::layout_label_y` (label layout shared by screen + dump).
+- Sidebar rows use `settings::item_row` with fixed-width labels when fields must
+ align to equal widths (see the Distortion section).
+- libcosmic is **pinned to git rev `430f3d4`** (see `crates/gipfel-app/Cargo.toml`)
+ and vendors iced 0.14.2. The correct nav-bar return type is
+ `cosmic::Action<Message>` (crate root), not `cosmic::app::Action`.
+- Rendering gotchas: the wgpu renderer draws in fixed layer order (use
+ `start_layer`/`end_layer` to force overlays above the image); keep strong
+ `Plain<P>` paragraph refs alive across the frame in widget `Tree` state.
+
+## Platform notes
+
+- **Linux / Cosmic first.** No Windows/macOS support yet.
+- A standalone Windows build is an open experiment. The main blocker (GSL) is
+ already removed. Remaining work: swap libcosmic's `xdg-portal` feature for
+ `rfd` (file dialogs), port the hill-DB search path (`/usr/share`, `$HOME` →
+ `dirs` + exe-relative) and font fallback, then get libcosmic itself to compile
+ for `x86_64-pc-windows-gnu` (upstream does not test Windows).
+
+## Do / Don't
+
+- ✅ Run `cargo test --lib --test golden` (with `GIPFEL_DATA` set) after any
+ change to the core math, projection, fit, metadata, or stitch code.
+- ✅ Keep the build warning-free (rustc `dead_code`/`unused_*` lints are the
+ signal; clippy may not be installed in every environment).
+- ❌ Don't hand-edit `projection/generated/` or `tests/golden/`.
+- ❌ Don't change the mathematical models or file formats.
+- ❌ Don't assume the GUI can be launched in this environment.
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..7518c3d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,6477 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ab_glyph"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
+dependencies = [
+ "ab_glyph_rasterizer",
+ "owned_ttf_parser",
+]
+
+[[package]]
+name = "ab_glyph_rasterizer"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
+
+[[package]]
+name = "accesskit"
+version = "0.22.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "uuid",
+]
+
+[[package]]
+name = "accesskit_atspi_common"
+version = "0.15.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "atspi-common",
+ "serde",
+ "zvariant",
+]
+
+[[package]]
+name = "accesskit_consumer"
+version = "0.32.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "accesskit_macos"
+version = "0.23.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "hashbrown 0.16.1",
+ "objc2 0.5.3",
+ "objc2-app-kit 0.2.2",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "accesskit_unix"
+version = "0.18.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "accesskit_atspi_common",
+ "atspi",
+ "futures-lite",
+ "serde",
+ "tokio",
+ "tokio-stream",
+ "zbus",
+]
+
+[[package]]
+name = "accesskit_windows"
+version = "0.30.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "accesskit_consumer",
+ "hashbrown 0.16.1",
+ "static_assertions",
+ "windows 0.61.3",
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "accesskit_winit"
+version = "0.30.0"
+source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
+dependencies = [
+ "accesskit",
+ "accesskit_macos",
+ "accesskit_unix",
+ "accesskit_windows",
+ "raw-window-handle",
+ "winit",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom 0.3.4",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "almost"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3aa2999eb46af81abb65c2d30d446778d7e613b60bbf4e174a027e80f90a3c14"
+
+[[package]]
+name = "android-activity"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd"
+dependencies = [
+ "android-properties",
+ "bitflags 2.13.2",
+ "cc",
+ "jni",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-sys",
+ "num_enum",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "android-properties"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "apply"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f47b57fc4521e3cae26a4d45b5227f8fadee4c345be0fefd8d5d1711afb8aeb9"
+
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "arc-swap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+
+[[package]]
+name = "as-raw-xcb-connection"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b"
+
+[[package]]
+name = "ash"
+version = "0.38.0+1.3.281"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "ashpd"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39"
+dependencies = [
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand 0.9.5",
+ "raw-window-handle",
+ "serde",
+ "serde_repr",
+ "tokio",
+ "url",
+ "zbus",
+]
+
+[[package]]
+name = "ashpd"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33a3c86f3fd70c0ffa500ed189abfa90b5a52398a45d5dc372fcc38ebeb7a645"
+dependencies = [
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand 0.9.5",
+ "serde",
+ "serde_repr",
+ "tokio",
+ "url",
+ "zbus",
+]
+
+[[package]]
+name = "async-broadcast"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix 1.1.5",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix 1.1.5",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 1.1.5",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "atomicwrites"
+version = "0.4.2"
+source = "git+https://github.com/jackpot51/rust-atomicwrites#043ab4859d53ffd3d55334685303d8df39c9f768"
+dependencies = [
+ "rustix 0.38.44",
+ "tempfile",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "atspi"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d"
+dependencies = [
+ "atspi-common",
+ "atspi-proxies",
+]
+
+[[package]]
+name = "atspi-common"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d"
+dependencies = [
+ "enumflags2",
+ "serde",
+ "static_assertions",
+ "zbus",
+ "zbus-lockstep",
+ "zbus-lockstep-macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "atspi-proxies"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
+dependencies = [
+ "atspi-common",
+ "serde",
+ "zbus",
+]
+
+[[package]]
+name = "auto_enums"
+version = "0.8.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3091d68264354f211516b91dce6f71046e444fab1867716035f736667243affb"
+dependencies = [
+ "derive_utils",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "basic-toml"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "block"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
+
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "block2"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
+dependencies = [
+ "objc2 0.5.3",
+]
+
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2 0.6.5",
+]
+
+[[package]]
+name = "blocking"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "borsh"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde_core",
+]
+
+[[package]]
+name = "btoi"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "build_helpers"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "cfg_aliases",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "by_address"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "calloop"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
+dependencies = [
+ "bitflags 2.13.2",
+ "polling",
+ "rustix 1.1.5",
+ "tracing",
+]
+
+[[package]]
+name = "calloop-wayland-source"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
+dependencies = [
+ "calloop",
+ "rustix 1.1.5",
+ "wayland-backend",
+ "wayland-client",
+]
+
+[[package]]
+name = "cc"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "clipboard-win"
+version = "5.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a"
+dependencies = [
+ "error-code",
+]
+
+[[package]]
+name = "clipboard_macos"
+version = "0.1.0"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "objc",
+ "objc-foundation",
+ "objc_id",
+]
+
+[[package]]
+name = "clipboard_wayland"
+version = "0.2.2"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "dnd",
+ "mime 0.1.0",
+ "smithay-clipboard",
+]
+
+[[package]]
+name = "clipboard_x11"
+version = "0.4.2"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "thiserror 1.0.69",
+ "x11rb",
+]
+
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation 0.9.4",
+ "core-graphics",
+ "foreign-types",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "cocoa-foundation"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "core-foundation 0.9.4",
+ "core-graphics-types 0.1.3",
+ "libc",
+ "objc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
+dependencies = [
+ "serde",
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "combine"
+version = "4.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "configparser"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031ad3d167622974ed636505ae4556267ed4aa2aea0464122fa6bc9698a32825"
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "core-graphics-types 0.1.3",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation 0.9.4",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
+dependencies = [
+ "bitflags 2.13.2",
+ "core-foundation 0.10.1",
+ "libc",
+]
+
+[[package]]
+name = "core_maths"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
+dependencies = [
+ "libm",
+]
+
+[[package]]
+name = "cosmic-config"
+version = "1.0.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "atomicwrites",
+ "cosmic-config-derive",
+ "cosmic-settings-daemon",
+ "dirs",
+ "futures-util",
+ "iced_futures",
+ "known-folders",
+ "notify",
+ "ron",
+ "serde",
+ "tokio",
+ "tracing",
+ "xdg",
+ "zbus",
+]
+
+[[package]]
+name = "cosmic-config-derive"
+version = "1.0.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "cosmic-freedesktop-icons"
+version = "0.4.0"
+source = "git+https://github.com/pop-os/freedesktop-icons#ab4c57b8e416c6af9297cb04d101889896fd9a92"
+dependencies = [
+ "bstr",
+ "btoi",
+ "memchr",
+ "memmap2",
+ "thiserror 2.0.21",
+ "tracing",
+ "xdg",
+]
+
+[[package]]
+name = "cosmic-settings-daemon"
+version = "0.1.0"
+source = "git+https://github.com/pop-os/dbus-settings-bindings#eed01dd3609e90e3c8cd043656734c500956c793"
+dependencies = [
+ "zbus",
+]
+
+[[package]]
+name = "cosmic-text"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73"
+dependencies = [
+ "bitflags 2.13.2",
+ "fontdb",
+ "harfrust",
+ "linebender_resource_handle",
+ "log",
+ "rangemap",
+ "rustc-hash 2.1.3",
+ "self_cell",
+ "skrifa 0.40.0",
+ "smol_str",
+ "swash",
+ "sys-locale",
+ "unicode-bidi",
+ "unicode-linebreak",
+ "unicode-script",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "cosmic-theme"
+version = "1.0.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "almost",
+ "configparser",
+ "cosmic-config",
+ "csscolorparser",
+ "dirs",
+ "hex_color",
+ "palette",
+ "ron",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "cryoglyph"
+version = "0.1.0"
+source = "git+https://github.com/iced-rs/cryoglyph.git?rev=e429a025df36ab8145708acb309080ae3deec17a#e429a025df36ab8145708acb309080ae3deec17a"
+dependencies = [
+ "cosmic-text",
+ "etagere",
+ "lru",
+ "rustc-hash 2.1.3",
+ "wgpu",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "css-color"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42aaeae719fd78ce501d77c6cdf01f7e96f26bcd5617a4903a1c2b97e388543a"
+
+[[package]]
+name = "csscolorparser"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168dbdf4dced8f57fa4246ac9080598452b49d70bb2d0e46422142f113140dd8"
+dependencies = [
+ "num-traits",
+ "phf",
+ "serde",
+ "uncased",
+]
+
+[[package]]
+name = "ctor"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf"
+dependencies = [
+ "dtor",
+]
+
+[[package]]
+name = "cursor-icon"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
+
+[[package]]
+name = "darling"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "data-url"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+
+[[package]]
+name = "defmt"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0"
+dependencies = [
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "derive_setters"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e6f6fa1f03c14ae082120b84b3c7fbd7b8588d924cf2d7c3daf9afd49df8b9"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "derive_utils"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc05a5d33db20c784f873e84934ad94bb209a090987ac5f62fede2c178234f23"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "libc",
+ "objc2 0.6.5",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
+dependencies = [
+ "libloading",
+]
+
+[[package]]
+name = "dnd"
+version = "0.1.0"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "bitflags 2.13.2",
+ "mime 0.1.0",
+ "raw-window-handle",
+ "smithay-client-toolkit",
+ "smithay-clipboard",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "dpi"
+version = "0.1.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+
+[[package]]
+name = "drm"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "drm-ffi",
+ "drm-fourcc",
+ "rustix 0.38.44",
+]
+
+[[package]]
+name = "drm-ffi"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6"
+dependencies = [
+ "drm-sys",
+ "rustix 0.38.44",
+]
+
+[[package]]
+name = "drm-fourcc"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
+
+[[package]]
+name = "drm-sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d09ff881f92f118b11105ba5e34ff8f4adf27b30dae8f12e28c193af1c83176"
+dependencies = [
+ "libc",
+ "linux-raw-sys 0.6.5",
+]
+
+[[package]]
+name = "dtor"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b"
+
+[[package]]
+name = "endi"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "error-code"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
+
+[[package]]
+name = "etagere"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342"
+dependencies = [
+ "euclid",
+ "svg_fmt",
+]
+
+[[package]]
+name = "euclid"
+version = "0.22.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
+dependencies = [
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "fax"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "find-crate"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
+
+[[package]]
+name = "flate2"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide 0.9.1",
+ "zlib-rs",
+]
+
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+
+[[package]]
+name = "float-cmp"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "float_next_after"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
+
+[[package]]
+name = "fluent"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477"
+dependencies = [
+ "fluent-bundle",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-bundle"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash 2.1.3",
+ "self_cell",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
+dependencies = [
+ "memchr",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "font-types"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "font-types"
+version = "0.12.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "fontconfig-parser"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
+dependencies = [
+ "roxmltree",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
+dependencies = [
+ "fontconfig-parser",
+ "log",
+ "memmap2",
+ "slotmap",
+ "tinyvec",
+ "ttf-parser",
+]
+
+[[package]]
+name = "fontdue"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e796cb6894e6418daacea84b28dd4f98d3b32c74d5bb452ba1ecfbf5ad8e84ca"
+dependencies = [
+ "hashbrown 0.15.5",
+ "ttf-parser",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "gethostname"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
+dependencies = [
+ "rustix 1.1.5",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+]
+
+[[package]]
+name = "gif"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gipfel-app"
+version = "0.1.0"
+dependencies = [
+ "cosmic-text",
+ "fontdb",
+ "fontdue",
+ "gipfel-core",
+ "image",
+ "libcosmic",
+ "tokio",
+]
+
+[[package]]
+name = "gipfel-cli"
+version = "0.1.0"
+dependencies = [
+ "gipfel-core",
+]
+
+[[package]]
+name = "gipfel-core"
+version = "0.1.0"
+dependencies = [
+ "dirs",
+ "image",
+ "kamadak-exif",
+ "quick-xml",
+ "tiff",
+]
+
+[[package]]
+name = "gl_generator"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
+dependencies = [
+ "khronos_api",
+ "log",
+ "xml-rs",
+]
+
+[[package]]
+name = "glam"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
+
+[[package]]
+name = "glow"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
+dependencies = [
+ "js-sys",
+ "slotmap",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "glutin_wgl_sys"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e"
+dependencies = [
+ "gl_generator",
+]
+
+[[package]]
+name = "gpu-allocator"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
+dependencies = [
+ "ash",
+ "hashbrown 0.16.1",
+ "log",
+ "presser",
+ "thiserror 2.0.21",
+ "windows 0.62.2",
+]
+
+[[package]]
+name = "gpu-descriptor"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
+dependencies = [
+ "bitflags 2.13.2",
+ "gpu-descriptor-types",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "gpu-descriptor-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "grid"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
+
+[[package]]
+name = "guillotiere"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
+dependencies = [
+ "euclid",
+ "svg_fmt",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "num-traits",
+ "zerocopy",
+]
+
+[[package]]
+name = "harfrust"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "core_maths",
+ "read-fonts 0.37.0",
+ "smallvec",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hex_color"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
+dependencies = [
+ "arrayvec",
+ "rand 0.8.8",
+ "serde",
+]
+
+[[package]]
+name = "hexf-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "i18n-config"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef"
+dependencies = [
+ "basic-toml",
+ "log",
+ "serde",
+ "serde_derive",
+ "thiserror 1.0.69",
+ "unic-langid",
+]
+
+[[package]]
+name = "i18n-embed"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305"
+dependencies = [
+ "arc-swap",
+ "fluent",
+ "fluent-langneg",
+ "fluent-syntax",
+ "i18n-embed-impl",
+ "intl-memoizer",
+ "log",
+ "parking_lot",
+ "rust-embed",
+ "sys-locale",
+ "thiserror 1.0.69",
+ "unic-langid",
+ "walkdir",
+]
+
+[[package]]
+name = "i18n-embed-fl"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "602e6bd30c3db2749e13e38b363a3d98d9d41de1d8de7a79c31bb69e45b47cda"
+dependencies = [
+ "find-crate",
+ "fluent",
+ "fluent-syntax",
+ "i18n-config",
+ "i18n-embed",
+ "proc-macro-error3",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+ "unic-langid",
+]
+
+[[package]]
+name = "i18n-embed-impl"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2"
+dependencies = [
+ "find-crate",
+ "i18n-config",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "iced"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "build_helpers",
+ "dnd",
+ "iced_accessibility",
+ "iced_core",
+ "iced_debug",
+ "iced_futures",
+ "iced_program",
+ "iced_renderer",
+ "iced_runtime",
+ "iced_widget",
+ "iced_winit",
+ "image",
+ "mime 0.1.0",
+ "thiserror 2.0.21",
+ "window_clipboard",
+]
+
+[[package]]
+name = "iced_accessibility"
+version = "0.1.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "accesskit",
+ "accesskit_winit",
+]
+
+[[package]]
+name = "iced_core"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "bitflags 2.13.2",
+ "build_helpers",
+ "bytes",
+ "dnd",
+ "glam",
+ "iced_accessibility",
+ "lilt",
+ "log",
+ "mime 0.1.0",
+ "num-traits",
+ "palette",
+ "raw-window-handle",
+ "rustc-hash 2.1.3",
+ "serde",
+ "smol_str",
+ "thiserror 2.0.21",
+ "unicode-segmentation",
+ "web-time",
+ "window_clipboard",
+]
+
+[[package]]
+name = "iced_debug"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "iced_core",
+ "iced_futures",
+ "log",
+]
+
+[[package]]
+name = "iced_futures"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "futures",
+ "iced_core",
+ "log",
+ "rustc-hash 2.1.3",
+ "tokio",
+ "wasm-bindgen-futures",
+ "wasmtimer",
+]
+
+[[package]]
+name = "iced_graphics"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "cosmic-text",
+ "half",
+ "iced_core",
+ "iced_futures",
+ "image",
+ "kamadak-exif",
+ "log",
+ "lyon_path",
+ "raw-window-handle",
+ "rustc-hash 2.1.3",
+ "thiserror 2.0.21",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "iced_program"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "iced_graphics",
+ "iced_runtime",
+]
+
+[[package]]
+name = "iced_renderer"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "iced_graphics",
+ "iced_tiny_skia",
+ "iced_wgpu",
+ "log",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "iced_runtime"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "build_helpers",
+ "bytes",
+ "dnd",
+ "iced_accessibility",
+ "iced_core",
+ "iced_futures",
+ "raw-window-handle",
+ "thiserror 2.0.21",
+ "window_clipboard",
+]
+
+[[package]]
+name = "iced_tiny_skia"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "bytemuck",
+ "cosmic-text",
+ "iced_debug",
+ "iced_graphics",
+ "kurbo 0.10.4",
+ "log",
+ "resvg",
+ "rustc-hash 2.1.3",
+ "softbuffer",
+ "tiny-skia 0.11.4",
+]
+
+[[package]]
+name = "iced_wgpu"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "as-raw-xcb-connection",
+ "bitflags 2.13.2",
+ "build_helpers",
+ "bytemuck",
+ "cryoglyph",
+ "futures",
+ "glam",
+ "guillotiere",
+ "iced_debug",
+ "iced_graphics",
+ "log",
+ "lyon",
+ "raw-window-handle",
+ "resvg",
+ "rustc-hash 2.1.3",
+ "rustix 0.38.44",
+ "thiserror 2.0.21",
+ "tiny-xlib",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-sys",
+ "wgpu",
+ "x11rb",
+]
+
+[[package]]
+name = "iced_widget"
+version = "0.14.2"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "build_helpers",
+ "dnd",
+ "iced_accessibility",
+ "iced_renderer",
+ "iced_runtime",
+ "log",
+ "num-traits",
+ "ouroboros",
+ "rustc-hash 2.1.3",
+ "thiserror 2.0.21",
+ "unicode-segmentation",
+ "window_clipboard",
+]
+
+[[package]]
+name = "iced_winit"
+version = "0.14.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "build_helpers",
+ "cursor-icon",
+ "dnd",
+ "iced_accessibility",
+ "iced_debug",
+ "iced_futures",
+ "iced_graphics",
+ "iced_program",
+ "iced_runtime",
+ "log",
+ "rustc-hash 2.1.3",
+ "rustix 0.38.44",
+ "thiserror 2.0.21",
+ "wasm-bindgen-futures",
+ "wayland-client",
+ "web-sys",
+ "winapi",
+ "window_clipboard",
+ "winit",
+ "winit-core",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc"
+
+[[package]]
+name = "icu_properties"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf"
+
+[[package]]
+name = "icu_provider"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "image"
+version = "0.25.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
+dependencies = [
+ "bytemuck",
+ "byteorder-lite",
+ "moxcms",
+ "num-traits",
+ "png 0.18.1",
+ "tiff",
+ "zune-core 0.5.3",
+ "zune-jpeg 0.5.15",
+]
+
+[[package]]
+name = "image-webp"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
+dependencies = [
+ "byteorder-lite",
+ "quick-error",
+]
+
+[[package]]
+name = "imagesize"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
+
+[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "inotify"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
+dependencies = [
+ "bitflags 2.13.2",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "intl-memoizer"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jiff"
+version = "0.2.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "jiff-tzdb-platform",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
+dependencies = [
+ "defmt",
+ "log",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jiff-tzdb"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7"
+
+[[package]]
+name = "jiff-tzdb-platform"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
+dependencies = [
+ "jiff-tzdb",
+]
+
+[[package]]
+name = "jni"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys 0.4.1",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.21",
+ "walkdir",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "jni-macros"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
+dependencies = [
+ "getrandom 0.4.3",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "kamadak-exif"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837"
+dependencies = [
+ "mutate_once",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
+dependencies = [
+ "bitflags 2.13.2",
+ "serde",
+]
+
+[[package]]
+name = "khronos-egl"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
+dependencies = [
+ "libc",
+ "libloading",
+ "pkg-config",
+]
+
+[[package]]
+name = "khronos_api"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
+
+[[package]]
+name = "known-folders"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "kqueue"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea"
+dependencies = [
+ "kqueue-sys",
+ "libc",
+]
+
+[[package]]
+name = "kqueue-sys"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
+dependencies = [
+ "bitflags 2.13.2",
+ "libc",
+]
+
+[[package]]
+name = "kurbo"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440"
+dependencies = [
+ "arrayvec",
+ "smallvec",
+]
+
+[[package]]
+name = "kurbo"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "smallvec",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.191"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
+
+[[package]]
+name = "libcosmic"
+version = "1.0.0"
+source = "git+https://github.com/pop-os/libcosmic.git?rev=430f3d48a97d8779f9c1985473912e594ccca1ae#430f3d48a97d8779f9c1985473912e594ccca1ae"
+dependencies = [
+ "apply",
+ "ashpd 0.12.3",
+ "auto_enums",
+ "build_helpers",
+ "cosmic-config",
+ "cosmic-freedesktop-icons",
+ "cosmic-settings-daemon",
+ "cosmic-theme",
+ "css-color",
+ "derive_setters",
+ "enumflags2",
+ "float-cmp 0.10.0",
+ "futures",
+ "i18n-embed",
+ "i18n-embed-fl",
+ "iced",
+ "iced_accessibility",
+ "iced_core",
+ "iced_futures",
+ "iced_renderer",
+ "iced_runtime",
+ "iced_tiny_skia",
+ "iced_wgpu",
+ "iced_widget",
+ "iced_winit",
+ "image",
+ "jiff",
+ "log",
+ "palette",
+ "phf",
+ "rfd",
+ "roxmltree",
+ "rust-embed",
+ "serde",
+ "slotmap",
+ "taffy",
+ "thiserror 2.0.21",
+ "tokio",
+ "tracing",
+ "unicode-segmentation",
+ "url",
+ "zbus",
+]
+
+[[package]]
+name = "libloading"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
+dependencies = [
+ "cfg-if",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libredox"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
+dependencies = [
+ "bitflags 2.13.2",
+ "libc",
+ "plain",
+ "redox_syscall 0.9.5",
+]
+
+[[package]]
+name = "lilt"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "337d4c256f7d9f2dbd633891d48ace853efa5b1554122d9220b172ad9c03c3a9"
+dependencies = [
+ "web-time",
+]
+
+[[package]]
+name = "linebender_resource_handle"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
+
+[[package]]
+name = "litrs"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "lru"
+version = "0.16.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
+
+[[package]]
+name = "lyon"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7"
+dependencies = [
+ "lyon_algorithms",
+ "lyon_tessellation",
+]
+
+[[package]]
+name = "lyon_algorithms"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a6b9d4e8b48acd531fbf1241ad9b270df6e60b6b92bdea1469f4f40899d805"
+dependencies = [
+ "lyon_path",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_geom"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82dd26aa4dc0b5b70b9115de59824842bcb43cf4a28e1b1e6d0ad148c93f93e"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_path"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e"
+dependencies = [
+ "lyon_geom",
+ "num-traits",
+]
+
+[[package]]
+name = "lyon_tessellation"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd4872650e70dc907dd4f7ebc449d7d657a0d2658e423b2e724970870bbbf75b"
+dependencies = [
+ "float_next_after",
+ "lyon_path",
+ "num-traits",
+]
+
+[[package]]
+name = "malloc_buf"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "memmap2"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "metal"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15"
+dependencies = [
+ "bitflags 2.13.2",
+ "block",
+ "core-graphics-types 0.2.0",
+ "foreign-types",
+ "log",
+ "objc",
+ "paste",
+]
+
+[[package]]
+name = "mime"
+version = "0.1.0"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "smithay-clipboard",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime 0.3.17",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
+dependencies = [
+ "num-traits",
+ "pxfm",
+]
+
+[[package]]
+name = "mutate_once"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af"
+
+[[package]]
+name = "naga"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bitflags 2.13.2",
+ "cfg-if",
+ "cfg_aliases",
+ "codespan-reporting",
+ "half",
+ "hashbrown 0.16.1",
+ "hexf-parse",
+ "indexmap",
+ "libm",
+ "log",
+ "num-traits",
+ "once_cell",
+ "rustc-hash 1.1.0",
+ "spirv",
+ "thiserror 2.0.21",
+ "unicode-ident",
+]
+
+[[package]]
+name = "ndk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
+dependencies = [
+ "bitflags 2.13.2",
+ "jni-sys 0.3.1",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-sys"
+version = "0.6.0+11769913"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
+dependencies = [
+ "jni-sys 0.3.1",
+]
+
+[[package]]
+name = "notify"
+version = "8.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f812702fbc75d35416d3f884e04925be7e9498f4eceea2e24ba34559284674"
+dependencies = [
+ "bitflags 2.13.2",
+ "inotify",
+ "kqueue",
+ "libc",
+ "log",
+ "mio",
+ "notify-types",
+ "objc2-core-foundation",
+ "objc2-core-services",
+ "walkdir",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "notify-types"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "objc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
+dependencies = [
+ "malloc_buf",
+]
+
+[[package]]
+name = "objc-foundation"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
+dependencies = [
+ "block",
+ "objc",
+ "objc_id",
+]
+
+[[package]]
+name = "objc-sys"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
+
+[[package]]
+name = "objc2"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b"
+dependencies = [
+ "objc-sys",
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
+dependencies = [
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.5.1",
+ "libc",
+ "objc2 0.5.3",
+ "objc2-core-data",
+ "objc2-core-image",
+ "objc2-foundation 0.2.2",
+ "objc2-quartz-core",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "objc2 0.6.5",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.5.1",
+ "objc2 0.5.3",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "dispatch2",
+ "objc2 0.6.5",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.13.2",
+ "libc",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
+dependencies = [
+ "block2 0.5.1",
+ "objc2 0.5.3",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-core-services"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b"
+dependencies = [
+ "libc",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-core-video"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-foundation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.5.1",
+ "libc",
+ "objc2 0.5.3",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "objc2 0.6.5",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-metal"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.5.1",
+ "objc2 0.5.3",
+ "objc2-foundation 0.2.2",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.5.1",
+ "objc2 0.5.3",
+ "objc2-foundation 0.2.2",
+ "objc2-metal",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2 0.6.5",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+]
+
+[[package]]
+name = "objc_id"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+dependencies = [
+ "objc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "orbclient"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747"
+dependencies = [
+ "libc",
+ "libredox",
+]
+
+[[package]]
+name = "ordered-float"
+version = "5.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "owned_ttf_parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
+dependencies = [
+ "ttf-parser",
+]
+
+[[package]]
+name = "palette"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64"
+dependencies = [
+ "approx",
+ "palette_derive",
+ "palette_math",
+ "phf",
+ "serde",
+]
+
+[[package]]
+name = "palette_derive"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be"
+dependencies = [
+ "by_address",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "palette_math"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.18",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "uncased",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+ "uncased",
+]
+
+[[package]]
+name = "pico-args"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113"
+
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
+
+[[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide 0.8.9",
+]
+
+[[package]]
+name = "png"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
+dependencies = [
+ "bitflags 2.13.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide 0.8.9",
+]
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix 1.1.5",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "pollster"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "presser"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error-attr3"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e564d14133360e1ae169ffde5da25881b5fa47261665b8e5713c212c27799da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error3"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f0d4471b3436c22106b21913b1dda531558918ae9b7ec55d58aa84b43552233"
+dependencies = [
+ "proc-macro-error-attr3",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "version_check",
+ "yansi",
+]
+
+[[package]]
+name = "profiling"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
+
+[[package]]
+name = "pxfm"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
+dependencies = [
+ "rand_chacha 0.9.0",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "range-alloc"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
+
+[[package]]
+name = "rangemap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
+[[package]]
+name = "read-fonts"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5"
+dependencies = [
+ "bytemuck",
+ "core_maths",
+ "font-types 0.11.3",
+]
+
+[[package]]
+name = "read-fonts"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
+dependencies = [
+ "bytemuck",
+ "font-types 0.12.7",
+ "once_cell",
+]
+
+[[package]]
+name = "redox_event"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5018d583d6d2f5499352aea8d177e9067d1eb03ab17c78169d5ba7a30001b15"
+dependencies = [
+ "bitflags 2.13.2",
+ "libredox",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0"
+dependencies = [
+ "libredox",
+ "thiserror 2.0.21",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+
+[[package]]
+name = "renderdoc-sys"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
+
+[[package]]
+name = "resvg"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
+dependencies = [
+ "gif",
+ "image-webp",
+ "log",
+ "pico-args",
+ "rgb",
+ "svgtypes",
+ "tiny-skia 0.11.4",
+ "usvg",
+ "zune-jpeg 0.4.21",
+]
+
+[[package]]
+name = "rfd"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4fe518b6db89699d59862e4d1e3a0226316090f4e4ec09f7dd686be25634c7c"
+dependencies = [
+ "ashpd 0.11.1",
+ "block2 0.6.2",
+ "dispatch2",
+ "js-sys",
+ "log",
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "pollster",
+ "raw-window-handle",
+ "urlencoding",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "ron"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3"
+dependencies = [
+ "bitflags 2.13.2",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "typeid",
+ "unicode-ident",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
+
+[[package]]
+name = "rust-embed"
+version = "8.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19afa5b4b6a611de00bd1bdae6ae6f39084c9399f0679c3f52d8469cf335cc23"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d8afda6374eac59e066abee06d265247ebbaf3006cf878e2879e8356e34053"
+dependencies = [
+ "mime_guess",
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn 2.0.119",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d84e8ba78bd384263e5922f084cbe1b081c3b7e69add59c8fb097b879ba968a"
+dependencies = [
+ "sha2",
+ "walkdir",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.13.2",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.15",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
+dependencies = [
+ "bitflags 2.13.2",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.12.1",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "rustybuzz"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "core_maths",
+ "log",
+ "smallvec",
+ "ttf-parser",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-properties",
+ "unicode-script",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sctk-adwaita"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2a4360b9abcdcee43809e6fc11d6d61ca5d25734026a8fc46c2883eea3f13f"
+dependencies = [
+ "ab_glyph",
+ "log",
+ "memmap2",
+ "smithay-client-toolkit",
+ "tiny-skia 0.12.0",
+]
+
+[[package]]
+name = "self_cell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
+dependencies = [
+ "foldhash 0.2.0",
+ "indexmap",
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+
+[[package]]
+name = "simd_cesu8"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "simplecss"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256"
+
+[[package]]
+name = "skrifa"
+version = "0.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac"
+dependencies = [
+ "bytemuck",
+ "read-fonts 0.37.0",
+]
+
+[[package]]
+name = "skrifa"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
+dependencies = [
+ "bytemuck",
+ "read-fonts 0.41.0",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "slotmap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.16.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8"
+
+[[package]]
+name = "smithay-client-toolkit"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
+dependencies = [
+ "bitflags 2.13.2",
+ "calloop",
+ "calloop-wayland-source",
+ "cursor-icon",
+ "libc",
+ "log",
+ "memmap2",
+ "rustix 1.1.5",
+ "thiserror 2.0.21",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-csd-frame",
+ "wayland-cursor",
+ "wayland-protocols",
+ "wayland-protocols-experimental",
+ "wayland-protocols-misc",
+ "wayland-protocols-wlr",
+ "wayland-scanner",
+ "xkeysym",
+]
+
+[[package]]
+name = "smithay-clipboard"
+version = "0.8.0"
+source = "git+https://github.com/pop-os/smithay-clipboard?tag=sctk-0.20#859b02c88f45c554049a67c6ddeec1692ce0e20b"
+dependencies = [
+ "libc",
+ "raw-window-handle",
+ "smithay-client-toolkit",
+ "wayland-backend",
+]
+
+[[package]]
+name = "smol_str"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeb7109d23f319619d4cc0de5464613c8b7b1a09db2774a58101459cf6be2f10"
+dependencies = [
+ "borsh",
+ "serde_core",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "softbuffer"
+version = "0.4.1"
+source = "git+https://github.com/pop-os/softbuffer?tag=cosmic-4.0#c2b2c19ddb38ff17495643699f97cb1f2064a1be"
+dependencies = [
+ "as-raw-xcb-connection",
+ "bytemuck",
+ "cfg_aliases",
+ "cocoa",
+ "core-graphics",
+ "drm",
+ "fastrand",
+ "foreign-types",
+ "js-sys",
+ "log",
+ "memmap2",
+ "objc",
+ "raw-window-handle",
+ "redox_syscall 0.5.18",
+ "rustix 0.38.44",
+ "tiny-xlib",
+ "wasm-bindgen",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-sys",
+ "web-sys",
+ "windows-sys 0.52.0",
+ "x11rb",
+]
+
+[[package]]
+name = "spirv"
+version = "0.3.0+sdk-1.3.268.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+dependencies = [
+ "float-cmp 0.9.0",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "svg_fmt"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
+
+[[package]]
+name = "svgtypes"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
+dependencies = [
+ "kurbo 0.11.3",
+ "siphasher",
+]
+
+[[package]]
+name = "swash"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e"
+dependencies = [
+ "skrifa 0.44.0",
+ "yazi",
+ "zeno",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "sys-locale"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "taffy"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b"
+dependencies = [
+ "arrayvec",
+ "grid",
+ "serde",
+ "slotmap",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.3",
+ "once_cell",
+ "rustix 1.1.5",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
+dependencies = [
+ "thiserror-impl 2.0.21",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "tiff"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
+dependencies = [
+ "fax",
+ "flate2",
+ "half",
+ "quick-error",
+ "weezl",
+ "zune-jpeg 0.5.15",
+]
+
+[[package]]
+name = "tiny-skia"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png 0.17.16",
+ "tiny-skia-path 0.11.4",
+]
+
+[[package]]
+name = "tiny-skia"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "tiny-skia-path 0.12.0",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
+[[package]]
+name = "tiny-xlib"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4"
+dependencies = [
+ "as-raw-xcb-connection",
+ "ctor",
+ "libloading",
+ "pkg-config",
+ "tracing",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a"
+dependencies = [
+ "displaydoc",
+ "serde_core",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
+
+[[package]]
+name = "tokio"
+version = "1.54.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.17+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+dependencies = [
+ "core_maths",
+]
+
+[[package]]
+name = "type-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
+dependencies = [
+ "rustc-hash 2.1.3",
+]
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "uds_windows"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "uncased"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unic-langid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
+dependencies = [
+ "unic-langid-impl",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
+dependencies = [
+ "serde",
+ "tinystr",
+]
+
+[[package]]
+name = "unicase"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
+name = "unicode-bidi-mirroring"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
+
+[[package]]
+name = "unicode-ccc"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
+name = "unicode-script"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
+[[package]]
+name = "usvg"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"
+dependencies = [
+ "base64",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "imagesize",
+ "kurbo 0.11.3",
+ "log",
+ "pico-args",
+ "roxmltree",
+ "rustybuzz",
+ "simplecss",
+ "siphasher",
+ "strict-num",
+ "svgtypes",
+ "tiny-skia-path 0.11.4",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
+dependencies = [
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e"
+dependencies = [
+ "js-sys",
+ "tokio",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.129"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasmtimer"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wayland-backend"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix 1.1.5",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
+dependencies = [
+ "bitflags 2.13.2",
+ "rustix 1.1.5",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-csd-frame"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
+dependencies = [
+ "bitflags 2.13.2",
+ "cursor-icon",
+ "wayland-backend",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.31.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d"
+dependencies = [
+ "rustix 1.1.5",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
+dependencies = [
+ "bitflags 2.13.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-experimental"
+version = "20250721.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
+dependencies = [
+ "bitflags 2.13.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-misc"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
+dependencies = [
+ "bitflags 2.13.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-plasma"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91"
+dependencies = [
+ "bitflags 2.13.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
+dependencies = [
+ "bitflags 2.13.2",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
+dependencies = [
+ "dlib",
+ "log",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
+[[package]]
+name = "wgpu"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f"
+dependencies = [
+ "arrayvec",
+ "bitflags 2.13.2",
+ "bytemuck",
+ "cfg-if",
+ "cfg_aliases",
+ "document-features",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "log",
+ "naga",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "smallvec",
+ "static_assertions",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "wgpu-core",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core"
+version = "28.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d23f4642f53f666adcfd2d3218ab174d1e6681101aef18696b90cbe64d1c10f9"
+dependencies = [
+ "arrayvec",
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.13.2",
+ "bytemuck",
+ "cfg_aliases",
+ "document-features",
+ "hashbrown 0.16.1",
+ "indexmap",
+ "log",
+ "naga",
+ "once_cell",
+ "parking_lot",
+ "portable-atomic",
+ "profiling",
+ "raw-window-handle",
+ "rustc-hash 1.1.0",
+ "smallvec",
+ "thiserror 2.0.21",
+ "wgpu-core-deps-apple",
+ "wgpu-core-deps-emscripten",
+ "wgpu-core-deps-windows-linux-android",
+ "wgpu-hal",
+ "wgpu-types",
+]
+
+[[package]]
+name = "wgpu-core-deps-apple"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-emscripten"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-core-deps-windows-linux-android"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae"
+dependencies = [
+ "wgpu-hal",
+]
+
+[[package]]
+name = "wgpu-hal"
+version = "28.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d6cb474beb218824dcc9e1ce679d973f719262789bfb27407da560cac20eeb"
+dependencies = [
+ "android_system_properties",
+ "arrayvec",
+ "ash",
+ "bit-set",
+ "bitflags 2.13.2",
+ "block",
+ "bytemuck",
+ "cfg-if",
+ "cfg_aliases",
+ "core-graphics-types 0.2.0",
+ "glow",
+ "glutin_wgl_sys",
+ "gpu-allocator",
+ "gpu-descriptor",
+ "hashbrown 0.16.1",
+ "js-sys",
+ "khronos-egl",
+ "libc",
+ "libloading",
+ "log",
+ "metal",
+ "naga",
+ "ndk-sys",
+ "objc",
+ "once_cell",
+ "ordered-float",
+ "parking_lot",
+ "portable-atomic",
+ "portable-atomic-util",
+ "profiling",
+ "range-alloc",
+ "raw-window-handle",
+ "renderdoc-sys",
+ "smallvec",
+ "thiserror 2.0.21",
+ "wasm-bindgen",
+ "web-sys",
+ "wgpu-types",
+ "windows 0.62.2",
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "wgpu-types"
+version = "28.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "js-sys",
+ "log",
+ "web-sys",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window_clipboard"
+version = "0.4.1"
+source = "git+https://github.com/pop-os/window_clipboard.git?tag=sctk-0.20#f68595ee0e62fbd6589f4709b5aaa5c3c7ea5f6c"
+dependencies = [
+ "clipboard-win",
+ "clipboard_macos",
+ "clipboard_wayland",
+ "clipboard_x11",
+ "dnd",
+ "mime 0.1.0",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "windows"
+version = "0.61.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+dependencies = [
+ "windows-collections 0.2.0",
+ "windows-core 0.61.2",
+ "windows-future 0.2.1",
+ "windows-link 0.1.3",
+ "windows-numerics 0.2.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections 0.3.2",
+ "windows-core 0.62.2",
+ "windows-future 0.3.2",
+ "windows-numerics 0.3.1",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.2.1",
+ "windows-result 0.4.1",
+ "windows-strings 0.5.1",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+ "windows-threading 0.1.0",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+ "windows-threading 0.2.1",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core 0.62.2",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link 0.2.1",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winit"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "cfg_aliases",
+ "cursor-icon",
+ "dpi",
+ "libc",
+ "raw-window-handle",
+ "rustix 1.1.5",
+ "smol_str",
+ "tracing",
+ "winit-android",
+ "winit-appkit",
+ "winit-common",
+ "winit-core",
+ "winit-orbital",
+ "winit-uikit",
+ "winit-wayland",
+ "winit-web",
+ "winit-win32",
+ "winit-x11",
+]
+
+[[package]]
+name = "winit-android"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "android-activity",
+ "bitflags 2.13.2",
+ "dpi",
+ "ndk",
+ "raw-window-handle",
+ "smol_str",
+ "tracing",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-appkit"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "dispatch2",
+ "dpi",
+ "objc2 0.6.5",
+ "objc2-app-kit 0.3.2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-video",
+ "objc2-foundation 0.3.2",
+ "raw-window-handle",
+ "smol_str",
+ "tracing",
+ "winit-common",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-common"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "memmap2",
+ "objc2 0.6.5",
+ "objc2-core-foundation",
+ "smol_str",
+ "tracing",
+ "winit-core",
+ "x11-dl",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winit-core"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "cursor-icon",
+ "dpi",
+ "keyboard-types",
+ "raw-window-handle",
+ "smol_str",
+ "web-time",
+]
+
+[[package]]
+name = "winit-orbital"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "dpi",
+ "libredox",
+ "orbclient",
+ "raw-window-handle",
+ "redox_event",
+ "smol_str",
+ "tracing",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-uikit"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2 0.6.2",
+ "dispatch2",
+ "dpi",
+ "objc2 0.6.5",
+ "objc2-core-foundation",
+ "objc2-foundation 0.3.2",
+ "objc2-ui-kit",
+ "raw-window-handle",
+ "smol_str",
+ "tracing",
+ "winit-common",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-wayland"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "ahash",
+ "bitflags 2.13.2",
+ "calloop",
+ "cursor-icon",
+ "dpi",
+ "libc",
+ "memmap2",
+ "raw-window-handle",
+ "rustix 1.1.5",
+ "sctk-adwaita",
+ "smithay-client-toolkit",
+ "smol_str",
+ "tracing",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-plasma",
+ "winit-common",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-web"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "atomic-waker",
+ "bitflags 2.13.2",
+ "concurrent-queue",
+ "cursor-icon",
+ "dpi",
+ "js-sys",
+ "pin-project",
+ "raw-window-handle",
+ "smol_str",
+ "tracing",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "web-time",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-win32"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "cursor-icon",
+ "dpi",
+ "raw-window-handle",
+ "smol_str",
+ "tracing",
+ "unicode-segmentation",
+ "windows-sys 0.59.0",
+ "winit-core",
+]
+
+[[package]]
+name = "winit-x11"
+version = "0.31.0-beta.2"
+source = "git+https://github.com/pop-os/winit.git?rev=9567503#9567503d34130113e36b0167a2b8829c3756e429"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytemuck",
+ "calloop",
+ "cursor-icon",
+ "dpi",
+ "libc",
+ "percent-encoding",
+ "raw-window-handle",
+ "rustix 1.1.5",
+ "smol_str",
+ "tracing",
+ "winit-common",
+ "winit-core",
+ "x11-dl",
+ "x11rb",
+ "xkbcommon-dl",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11rb"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
+dependencies = [
+ "as-raw-xcb-connection",
+ "gethostname",
+ "libc",
+ "libloading",
+ "once_cell",
+ "rustix 1.1.5",
+ "x11rb-protocol",
+ "xcursor",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+
+[[package]]
+name = "xcursor"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
+
+[[package]]
+name = "xdg"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5"
+
+[[package]]
+name = "xkbcommon-dl"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5"
+dependencies = [
+ "bitflags 2.13.2",
+ "dlib",
+ "log",
+ "once_cell",
+ "xkeysym",
+]
+
+[[package]]
+name = "xkeysym"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
+
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
+[[package]]
+name = "yazi"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5"
+
+[[package]]
+name = "yoke"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+ "synstructure",
+]
+
+[[package]]
+name = "zbus"
+version = "5.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "libc",
+ "ordered-stream",
+ "rustix 1.1.5",
+ "serde",
+ "serde_repr",
+ "tokio",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.2",
+ "winnow",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus-lockstep"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863"
+dependencies = [
+ "zbus_xml",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus-lockstep-macros"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "zbus-lockstep",
+ "zbus_xml",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd"
+dependencies = [
+ "serde",
+ "winnow",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_xml"
+version = "5.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a"
+dependencies = [
+ "serde",
+ "winnow",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zcheapstr"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "zeno"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40"
+dependencies = [
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+
+[[package]]
+name = "zune-core"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+
+[[package]]
+name = "zune-core"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b"
+
+[[package]]
+name = "zune-jpeg"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core 0.4.12",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
+dependencies = [
+ "zune-core 0.5.3",
+]
+
+[[package]]
+name = "zvariant"
+version = "5.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "url",
+ "winnow",
+ "zcheapstr",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.9",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 3.0.9",
+ "winnow",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..3a99088
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,14 @@
+[workspace]
+resolver = "2"
+members = ["crates/gipfel-core", "crates/gipfel-cli", "crates/gipfel-app"]
+
+[workspace.dependencies]
+kamadak-exif = "0.6"
+quick-xml = { version = "0.41", default-features = false }
+tiff = "0.11"
+dirs = "6"
+image = { version = "0.25", default-features = false, features = ["jpeg", "tiff", "pnm"] }
+
+[profile.release]
+lto = true
+debug = 1
diff --git a/Makefile.am b/Makefile.am
deleted file mode 100644
index c8ee547..0000000
--- a/Makefile.am
+++ /dev/null
@@ -1,6 +0,0 @@
-SUBDIRS = src
-dist_doc_DATA = README NEWS
-
-EXTRA_DIST = gipfel.dat gipfel.spec
-gpsdir = $(datadir)/gipfel
-gps_DATA = gipfel.dat
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6099395
--- /dev/null
+++ b/README.md
@@ -0,0 +1,33 @@
+# gipfel-rs
+
+Rust port of [gipfel](https://git.flpsed.org/gipfel)'s computational core.
+
+- `crates/gipfel-core` — hills, panorama geometry, projections (maxima-generated
+ symbolic derivatives), pure-Rust Levenberg-Marquardt fit, `gipfel:` JPEG comment + EXIF I/O
+- `crates/gipfel-cli` — `export-position`, `export-hills`, `info`
+- `codegen/` — maxima scripts (`expr2rust.mac`) and `regen.sh`
+- `tests/` — C reference harnesses + golden files from the original C++ build
+
+```sh
+cargo test --lib --test golden # unit + golden tests
+cargo run -p gipfel-cli -- info <img.jpg>
+```
+
+The Rust build has no native C dependencies. Regenerate goldens with
+`tests/gen_golden.sh` (needs the built C++ tree, which uses GSL; see
+`GIPFEL_SRC`/`GIPFEL_DATA` in the script).
+
+## gipfel-app (M2/M3)
+
+COSMIC desktop application: `cargo build --release -p gipfel-app` →
+`target/release/gipfel [image.jpg] [--data gipfel.dat]`.
+
+- Viewport: ctrl+wheel zoom (cursor-anchored), wheel/drag pan, fit/zoom buttons
+- Left-drag a flag to reposition (refits on release); middle-click or
+ double-click toggles a hill "known"; right-click → Find Peak / Hide-Unhide
+- Viewpoint / Find Peak pickers are searchable dialogs
+- Sidebar: compass dial, focal/pitch/roll/visibility/track sliders, viewpoint
+ lat/lon/height, distortion k0/k1/x0, projection, show-hidden
+- File menu: Open/Save (writes the `gipfel:` comment), Screen Dump (PNG/JPG
+ with markers burned in), Load Track (CSV: lat,lon,height rows)
+- Install `crates/gipfel-app/data/org.gipfel.App.desktop` per freedesktop
diff --git a/codegen/expr2rust.mac b/codegen/expr2rust.mac
new file mode 100644
index 0000000..e4e6be8
--- /dev/null
+++ b/codegen/expr2rust.mac
@@ -0,0 +1,31 @@
+/*
+ * Rust code generation for gipfel-rs.
+ *
+ * Port of gipfel's expr2c.mac (Copyright 2006-2009 Johannes Hofmann).
+ * Emits one Rust function per line (grep "^pub fn"); integer literals are
+ * converted to float literals by fixup.py afterwards.
+ */
+
+load("stringproc")$
+
+varsubst(v) := ssubst("_", "%", string(v))$
+
+printdecl(vars) := (
+ sprint("let mut", varsubst(first(vars)), ": f64 = 0.0;"),
+ for v in rest(vars) do sprint("let mut", varsubst(v), ": f64 = 0.0;"))$
+
+printdef(expr) := sprint(varsubst(first(expr)), "=", varsubst(second(expr)), ";")$
+
+block2rust(expr) :=
+ if ?equal(op(expr), block) then (
+ printdecl(first(expr)),
+ for d in reverse(rest(reverse(rest(expr)))) do printdef(d),
+ sprint("return", varsubst(last(expr)), ";"))
+ else
+ sprint("return", varsubst(expr), ";")$
+
+expr2rust(funcname, argstr, expr) := (
+ sprint("pub fn", funcname, "(", argstr, ") -> f64 {"),
+ block2rust(subst(pow, "^", optimize(expr))),
+ sprint("}", "
+"))$
diff --git a/codegen/fixup.py b/codegen/fixup.py
new file mode 100644
index 0000000..1131f11
--- /dev/null
+++ b/codegen/fixup.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+"""Post-process maxima output for Rust:
+ - convert bare integer literals to float literals (1 -> 1.0)
+ - drop maxima noise lines (keep only 'pub fn' lines)
+"""
+import re
+import sys
+
+pat = re.compile(r"(?<![\w.])-?\d+(?![\w.])")
+
+for line in sys.stdin:
+ if not line.startswith("pub fn"):
+ continue
+ sys.stdout.write(pat.sub(lambda m: m.group(0) + ".0", line))
diff --git a/codegen/lsq_cylindrical.mac b/codegen/lsq_cylindrical.mac
new file mode 100644
index 0000000..316d3f9
--- /dev/null
+++ b/codegen/lsq_cylindrical.mac
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
+ */
+
+/*
+ * cylindrical projection model
+ */
+
+x : m_view - c_view$
+y : tan(c_pitch - m_elev)$
+x_rot : y * sin(c_roll) + x * cos(c_roll)$
+y_rot : y * cos(c_roll) - x * sin(c_roll)$
+x_scale : x_rot * scale$
+y_scale : y_rot * scale$
+
+/*
+ * Some mangling for C code generation
+ */
+
+load("expr2rust.mac")$
+
+x_expand : trigexpand(x_scale)$
+y_expand : trigexpand(y_scale)$
+
+args: "c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64"$
+
+expr2rust("mac_x", args, x_expand)$
+expr2rust("mac_y", args, y_expand)$
+
+expr2rust("mac_x_dc_view", args, diff(x_expand, c_view))$
+expr2rust("mac_x_dc_pitch", args, diff(x_expand, c_pitch))$
+expr2rust("mac_x_dc_roll", args, diff(x_expand, c_roll))$
+expr2rust("mac_x_dscale", args, diff(x_expand, scale))$
+expr2rust("mac_y_dc_view", args, diff(y_expand, c_view))$
+expr2rust("mac_y_dc_pitch", args, diff(y_expand, c_pitch))$
+expr2rust("mac_y_dc_roll", args, diff(y_expand, c_roll))$
+expr2rust("mac_y_dscale", args, diff(y_expand, scale))$
+
diff --git a/codegen/lsq_rectilinear.mac b/codegen/lsq_rectilinear.mac
new file mode 100644
index 0000000..4dbb36a
--- /dev/null
+++ b/codegen/lsq_rectilinear.mac
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
+ */
+
+/*
+ * rectilinear (pinhole) projection model with distortion correction
+ */
+
+/* switch to cartesian coordinates */
+c_x : cos(m_elev) * cos(m_view - c_view)$
+c_y : cos(m_elev) * sin(m_view - c_view)$
+c_z : sin(m_elev)$
+
+/* pitch rotation */
+c_x_rot : cos(c_pitch) * c_x + sin(c_pitch) * c_z$
+c_y_rot : c_y$
+c_z_rot : -sin(c_pitch) * c_x + cos(c_pitch) * c_z$
+
+/* pinhole projection */
+x : c_y_rot / c_x_rot$
+y : -c_z_rot / c_x_rot$
+
+/* rotation around view axis */
+x_rot : y * sin(c_roll) + x * cos(c_roll) + x0$
+y_rot : y * cos(c_roll) - x * sin(c_roll)$
+
+/* distortion correction */
+d : sqrt(x_rot ^ 2 + y_rot ^ 2)$
+dist_fact : d ^ 2 * k1 + d * k0$
+x_dist : x_rot * (1 + dist_fact) * scale$
+y_dist : y_rot * (1 + dist_fact) * scale$
+
+/*
+ * Some mangling for C code generation
+ */
+
+load("expr2rust.mac")$
+
+x_expand : trigexpand(x_dist)$
+y_expand : trigexpand(y_dist)$
+
+args: "c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64"$
+
+expr2rust("mac_x", args, x_expand)$
+expr2rust("mac_y", args, y_expand)$
+
+expr2rust("mac_x_dc_view", args, diff(x_expand, c_view))$
+expr2rust("mac_x_dc_pitch", args, diff(x_expand, c_pitch))$
+expr2rust("mac_x_dc_roll", args, diff(x_expand, c_roll))$
+expr2rust("mac_x_dscale", args, diff(x_expand, scale))$
+expr2rust("mac_x_dk0", args, diff(x_expand, k0))$
+expr2rust("mac_x_dk1", args, diff(x_expand, k1))$
+expr2rust("mac_x_dx0", args, diff(x_expand, x0))$
+expr2rust("mac_y_dc_view", args, diff(y_expand, c_view))$
+expr2rust("mac_y_dc_pitch", args, diff(y_expand, c_pitch))$
+expr2rust("mac_y_dc_roll", args, diff(y_expand, c_roll))$
+expr2rust("mac_y_dscale", args, diff(y_expand, scale))$
+expr2rust("mac_y_dk0", args, diff(y_expand, k0))$
+expr2rust("mac_y_dk1", args, diff(y_expand, k1))$
+expr2rust("mac_y_dx0", args, diff(y_expand, x0))$
diff --git a/codegen/regen.sh b/codegen/regen.sh
new file mode 100755
index 0000000..dbf9f6b
--- /dev/null
+++ b/codegen/regen.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+# Regenerate the symbolic projection functions with maxima.
+# Run from anywhere; requires maxima (and rustfmt if available).
+set -e
+cd "$(dirname "$0")"
+OUT=../crates/gipfel-core/src/projection/generated
+
+for p in rectilinear cylindrical; do
+ maxima -b lsq_${p}.mac | python3 fixup.py > ${OUT}/${p}_funcs.rs
+done
+
+if command -v rustfmt >/dev/null; then
+ rustfmt ${OUT}/rectilinear_funcs.rs ${OUT}/cylindrical_funcs.rs 2>/dev/null || true
+fi
+echo "generated:"
+wc -l ${OUT}/*_funcs.rs
diff --git a/configure.ac b/configure.ac
deleted file mode 100644
index 1766ac2..0000000
--- a/configure.ac
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- Autoconf -*-
-# Process this file with autoconf to produce a configure script.
-
-AC_PREREQ(2.59)
-AC_INIT(gipfel, 0.4.1, Johannes.Hofmann@gmx.de)
-AM_INIT_AUTOMAKE
-AC_CONFIG_SRCDIR([src/Panorama.H])
-AC_CONFIG_HEADER(config.h)
-
-CPPFLAGS="-Wall -g -I/usr/local/include -DSTD_DATADIR=\\\"$datadir\\\" -DDOCDIR=\\\"$docdir\\\" $CPPFLAGS"
-LDFLAGS="-g $LDFLAGS"
-
-# Checks for programs.
-AC_PROG_CXX
-AC_PROG_CC
-
-AC_LANG_CPLUSPLUS
-
-# Checks for libraries.
-
-# Checks for header files.
-AC_HEADER_STDC
-AC_HEADER_SYS_WAIT
-AC_CHECK_HEADERS([fcntl.h stdlib.h string.h unistd.h])
-
-# Checks for typedefs, structures, and compiler characteristics.
-AC_HEADER_STDBOOL
-AC_C_CONST
-AC_TYPE_OFF_T
-AC_TYPE_PID_T
-
-# Checks for library functions.
-AC_FUNC_FORK
-AC_FUNC_MALLOC
-AC_FUNC_REALLOC
-AC_TYPE_SIGNAL
-AC_CHECK_FUNCS([strchr strdup strrchr strstr mkstemp fsync strsep])
-
-# Check for fltk
-AC_PATH_PROG(FLTKCONFIG,fltk-config)
-if test "x$FLTKCONFIG" = x; then
- echo "fltk-config not found"
- exit 1
-fi
-
-CXXFLAGS="`$FLTKCONFIG --use-images --cflags` $CXXFLAGS"
-LIBS="`$FLTKCONFIG --use-images --ldflags` $LIBS"
-
-# Check for gsl
-AC_PATH_PROG(GSLCONFIG,gsl-config)
-if test "x$GSLCONFIG" = x; then
- echo "gsl-config not found"
- exit 1
-fi
-
-CXXFLAGS="`$GSLCONFIG --cflags` $CXXFLAGS"
-LIBS="`$GSLCONFIG --libs` $LIBS"
-
-# Check for libtiff
-AC_CHECK_HEADERS([tiffio.h], [], [echo "Error: tiffio.h not found."; exit 1;])
-AC_CHECK_LIB([tiff], [TIFFOpen], [], [echo "Error: libtiff.so not found."; exit 1;])
-
-# Check for exiv2
-AC_CHECK_HEADERS([exiv2/exif.hpp], [], [echo "Error: exiv2/exif.hpp not found."; exit 1;])
-LIBS="-lexiv2 $LIBS"
-
-AC_CONFIG_FILES([Makefile src/Makefile])
-AC_OUTPUT
diff --git a/crates/gipfel-app/Cargo.toml b/crates/gipfel-app/Cargo.toml
new file mode 100644
index 0000000..22589c1
--- /dev/null
+++ b/crates/gipfel-app/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "gipfel-app"
+version = "0.1.0"
+edition = "2021"
+description = "gipfel port to Cosmic DE"
+license = "GPL-2.0-or-later"
+
+[[bin]]
+name = "gipfel"
+path = "src/main.rs"
+
+[dependencies]
+gipfel-core = { path = "../gipfel-core" }
+libcosmic = { git = "https://github.com/pop-os/libcosmic.git", rev = "430f3d48a97d8779f9c1985473912e594ccca1ae", default-features = false, features = ["winit", "wgpu", "tokio", "a11y", "xdg-portal"] }
+image.workspace = true
+fontdue = "0.9"
+cosmic-text = "0.19"
+fontdb = "0.23"
+tokio = { version = "1", features = ["time"] }
diff --git a/crates/gipfel-app/data/org.gipfel.App.desktop b/crates/gipfel-app/data/org.gipfel.App.desktop
new file mode 100644
index 0000000..b20c468
--- /dev/null
+++ b/crates/gipfel-app/data/org.gipfel.App.desktop
@@ -0,0 +1,13 @@
+[Desktop Entry]
+Type=Application
+Name=gipfel
+GenericName=Panorama annotator
+Comment=Annotate mountain panoramas
+Exec=gipfel %U
+TryExec=gipfel
+Icon=org.gipfel.App
+Terminal=false
+Categories=Graphics;Photography;
+Keywords=mountain;panorama;photo;annotation;
+MimeType=image/jpeg;image/png;
+StartupNotify=true
diff --git a/crates/gipfel-app/src/compass.rs b/crates/gipfel-app/src/compass.rs
new file mode 100644
index 0000000..a17cd5c
--- /dev/null
+++ b/crates/gipfel-app/src/compass.rs
@@ -0,0 +1,611 @@
+//! Experimental compass strip: shows N/E/S/W at their projected positions
+//! relative to the view direction; drag horizontally to change the direction.
+//!
+//! Two experimental styles (switchable from the View menu):
+//! - `Aligned`: the strip shares the image's horizontal transform, so each
+//! direction appears exactly above its position in the image. Grab-drag
+//! keeps the direction under the cursor fixed.
+//! - `Ruler`: a fixed 0..360° ruler across the widget width; press or drag
+//! sets the direction to the value under the cursor. The shaded band marks
+//! the current horizontal field of view.
+//!
+//! Layout (top to bottom): direction labels, center marker, tick scale on a
+//! baseline. The whole strip sits on a secondary-container background so it
+//! is visually distinct from the image (pan) area.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use cosmic::iced::advanced::graphics::geometry::Renderer as _;
+use cosmic::iced::advanced::renderer::Style as RendererStyle;
+use cosmic::iced::advanced::text::{self as adv_text, paragraph::Plain, LineHeight};
+use cosmic::iced::advanced::widget::{tree, Tree, Widget as WidgetTrait};
+use cosmic::iced::advanced::{Clipboard, Layout, Shell};
+use cosmic::iced::mouse::{self, Button, Cursor, Interaction};
+use cosmic::iced::widget::canvas::{Frame, Path, Stroke};
+use cosmic::iced::advanced::graphics::geometry::Style as GeoStyle;
+use cosmic::iced::{alignment, Color, Event, Length, Pixels, Point, Rectangle, Size};
+use cosmic::Renderer as R;
+use cosmic::Theme;
+
+use gipfel_core::projection::{
+ projection_from_type, Projection, ProjArgs, ProjectionType, ViewParams,
+};
+
+pub const STRIP_H: f32 = 56.0;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CompassStyle {
+ Off,
+ Aligned,
+ Ruler,
+}
+
+/// inverse: azimuth whose image x is `x_img`, measured with `c_ref` as
+/// center angle. Analytic base inverse + damped Newton refinement so lens
+/// distortion is honoured.
+/// Damped, clamped Newton iteration for a scalar inverse projection: find
+/// `a` such that `f(a) ≈ target`. `f` returns `None` where the forward
+/// projection is undefined (the iteration stops there). The step is clamped
+/// to ±0.3 rad and `a` kept in `[lo, hi]` to stay off the projection poles.
+pub(crate) fn newton_inverse(
+ target: f64,
+ mut f: impl FnMut(f64) -> Option<f64>,
+ seed: f64,
+ lo: f64,
+ hi: f64,
+) -> f64 {
+ let mut a = seed.clamp(lo, hi);
+ for _ in 0..6 {
+ let Some(y) = f(a) else { break };
+ let resid = y - target;
+ let h = 1e-5;
+ let (Some(y1), Some(y2)) = (f(a + h), f(a - h)) else {
+ break;
+ };
+ let d = (y1 - y2) / (2.0 * h);
+ if !d.is_finite() || d.abs() < 1e-9 {
+ break;
+ }
+ let step = (resid / d).clamp(-0.3, 0.3);
+ a = (a - step).clamp(lo, hi);
+ if step.abs() < 1e-10 {
+ break;
+ }
+ }
+ a
+}
+
+pub fn alph_at_img_x(
+ ptype: ProjectionType,
+ proj: &dyn Projection,
+ parms: &ViewParams,
+ x_img: f64,
+ c_ref: f64,
+) -> f64 {
+ let mut p = *parms;
+ p.a_center = c_ref;
+ // analytic base inverse (ignores distortion); Newton refines it
+ let seed = match ptype {
+ ProjectionType::Rectilinear => c_ref + (x_img / p.scale).atan(),
+ ProjectionType::Cylindrical => c_ref + x_img / p.scale,
+ };
+ // rectilinear has a pole at |alph - center| = 90°; keep Newton inside
+ let (lo, hi) = match ptype {
+ ProjectionType::Rectilinear => (c_ref - 1.55, c_ref + 1.55),
+ ProjectionType::Cylindrical => (c_ref - 3.1, c_ref + 3.1),
+ };
+ newton_inverse(
+ x_img,
+ |a| Some(proj.mac_x(ProjArgs::from_params(&p, a, 0.0))),
+ seed,
+ lo,
+ hi,
+ )
+}
+
+pub struct Compass<M: Clone + Send + 'static> {
+ pub style: CompassStyle,
+ pub center_deg: f64,
+ pub zoom: f32,
+ pub pan_x: f32,
+ pub parms: ViewParams,
+ pub ptype: ProjectionType,
+ /// horizontal field of view of the image, degrees (Ruler shading)
+ pub fov_deg: f64,
+ /// lens field-of-view half-angle limit in radians (0 = none). Ticks and
+ /// direction labels beyond this are dropped: a distortion model can fold
+ /// out-of-view directions back onto the strip, showing bogus ticks.
+ pub max_view_angle: f64,
+ pub on_change: Rc<dyn Fn(f64) -> M>,
+}
+
+impl<M: Clone + Send + 'static> Clone for Compass<M> {
+ fn clone(&self) -> Self {
+ Compass {
+ style: self.style,
+ center_deg: self.center_deg,
+ zoom: self.zoom,
+ pan_x: self.pan_x,
+ parms: self.parms,
+ ptype: self.ptype,
+ fov_deg: self.fov_deg,
+ max_view_angle: self.max_view_angle,
+ on_change: self.on_change.clone(),
+ }
+ }
+}
+
+#[derive(Default)]
+struct CompassState {
+ /// (press x, center angle at press)
+ drag: Option<(f32, f64)>,
+ /// strong refs: the text pipeline only keeps a Weak to each paragraph
+ paragraphs: RefCell<Vec<Plain<<R as adv_text::Renderer>::Paragraph>>>,
+}
+
+const DIRS: [(f64, &str); 8] = [
+ (0.0, "N"),
+ (45.0, "NE"),
+ (90.0, "E"),
+ (135.0, "SE"),
+ (180.0, "S"),
+ (225.0, "SW"),
+ (270.0, "W"),
+ (315.0, "NW"),
+];
+
+/// one text to draw: content, x, y, center around x?, color
+struct Txt {
+ text: String,
+ x: f32,
+ y: f32,
+ centered: bool,
+ color: Color,
+}
+
+fn stroke_tick(frame: &mut Frame<R>, x: f32, y0: f32, h: f32, w: f32, color: Color) {
+ if !x.is_finite() {
+ return;
+ }
+ // snap thin lines to integer coords for crisp 1 px strokes
+ let x = if w < 1.5 { x.round() } else { x };
+ let p = Path::new(|p| {
+ p.move_to(Point::new(x, y0));
+ p.line_to(Point::new(x, y0 - h));
+ });
+ frame.stroke(
+ &p,
+ Stroke {
+ width: w,
+ style: GeoStyle::Solid(color),
+ ..Default::default()
+ },
+ );
+}
+
+impl<M: Clone + Send + 'static> Compass<M> {
+ /// image-space x of azimuth `alph` (on the horizon), current parms
+ fn x_img_at(&self, proj: &dyn Projection, alph: f64) -> f64 {
+ proj.get_coordinates(alph, 0.0, &self.parms).0
+ }
+
+ fn alph_at_img_x(
+ &self,
+ proj: &dyn Projection,
+ x_img: f64,
+ c_ref: f64,
+ ) -> f64 {
+ alph_at_img_x(self.ptype, proj, &self.parms, x_img, c_ref)
+ }
+
+ /// screen x of the image center for the Aligned style
+ fn center_x(&self, b: Rectangle) -> f32 {
+ b.x + b.width / 2.0 + self.pan_x
+ }
+}
+
+impl<M: Clone + Send + 'static> WidgetTrait<M, Theme, R> for Compass<M> {
+ fn size(&self) -> Size<Length> {
+ Size::new(Length::Fill, Length::Fixed(STRIP_H))
+ }
+
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<CompassState>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(CompassState::default())
+ }
+
+ fn layout(
+ &mut self,
+ _tree: &mut Tree,
+ _renderer: &R,
+ limits: &cosmic::iced::advanced::layout::Limits,
+ ) -> cosmic::iced::advanced::layout::Node {
+ cosmic::iced::advanced::layout::Node::new(Size::new(limits.max().width, STRIP_H))
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut R,
+ theme: &Theme,
+ _style: &RendererStyle,
+ layout: Layout<'_>,
+ _cursor: Cursor,
+ _viewport: &Rectangle,
+ ) {
+ use cosmic::iced::advanced::text::Renderer as _;
+
+ let b = layout.bounds();
+ let _ = theme;
+ // dark instrument panel, clearly distinct from the image area
+ let on: Color = Color::from_rgb(0.85, 0.85, 0.85);
+ let bg: Color = Color::from_rgb(0.16, 0.16, 0.16);
+ let divider: Color = Color::from_rgb(0.32, 0.32, 0.32);
+ let red = Color::from_rgb(1.0, 0.4, 0.25);
+
+ // geometry rows (snapped so lines stay crisp)
+ let label_y = (b.y + 5.0).round();
+ let y0 = (b.y + STRIP_H - 10.0).round(); // baseline
+ let tri_y = (b.y + 25.0).round();
+ let z = self.zoom;
+ let proj = projection_from_type(self.ptype);
+
+ let mut texts: Vec<Txt> = Vec::new();
+ let mut frame = Frame::with_bounds(renderer, b);
+
+ // panel background + top divider: clearly a separate, draggable area
+ let panel = Path::new(|p| p.rectangle(b.position(), b.size()));
+ frame.fill(&panel, bg);
+ let sep = Path::new(|p| {
+ p.move_to(Point::new(b.x, b.y + 0.5));
+ p.line_to(Point::new(b.x + b.width, b.y + 0.5));
+ });
+ frame.stroke(
+ &sep,
+ Stroke {
+ width: 1.0,
+ style: GeoStyle::Solid(divider),
+ ..Default::default()
+ },
+ );
+
+ match self.style {
+ CompassStyle::Off => return,
+ CompassStyle::Aligned => {
+ let cx = self.center_x(b);
+ let c = self.parms.a_center;
+ // pixels per degree at the center
+ let ppd = ((self.x_img_at(&*proj, c + 1f64.to_radians())
+ - self.x_img_at(&*proj, c - 1f64.to_radians()))
+ * z as f64
+ / 2.0)
+ .abs();
+ let step = [1.0f64, 2.0, 5.0, 10.0, 15.0, 30.0, 45.0, 90.0]
+ .into_iter()
+ .find(|s| s * ppd >= 6.0)
+ .unwrap_or(90.0);
+
+ let a_lo = self
+ .alph_at_img_x(&*proj, ((b.x - cx) / z) as f64, c)
+ .min(c - std::f64::consts::PI);
+ let a_hi = self
+ .alph_at_img_x(&*proj, ((b.x + b.width - cx) / z) as f64, c)
+ .max(c + std::f64::consts::PI);
+ // the rectilinear projection folds directions behind the
+ // camera onto the same pixels (tan has 180 deg period);
+ // never draw beyond +-88 deg of the center. Cylindrical is
+ // linear in azimuth and may legitimately span > 180 deg.
+ let fold = match self.ptype {
+ ProjectionType::Rectilinear => 1.535,
+ ProjectionType::Cylindrical => std::f64::consts::PI,
+ };
+ // additionally clamp to the lens FOV so distortion-folded
+ // out-of-view directions do not get ticks/labels
+ let fold = if self.max_view_angle > 0.0 {
+ fold.min(self.max_view_angle)
+ } else {
+ fold
+ };
+ let a_lo = a_lo.max(c - fold);
+ let a_hi = a_hi.min(c + fold);
+
+ // baseline
+ let line = Path::new(|p| {
+ p.move_to(Point::new(b.x, y0 + 0.5));
+ p.line_to(Point::new(b.x + b.width, y0 + 0.5));
+ });
+ frame.stroke(
+ &line,
+ Stroke {
+ width: 1.0,
+ style: GeoStyle::Solid(on),
+ ..Default::default()
+ },
+ );
+
+ // ticks (rise from the baseline, stay below the labels);
+ // `a` is radians, `step` degrees -> convert once
+ let step_rad = step.to_radians();
+ let mut a = (a_lo / step_rad).floor() * step_rad;
+ while a <= a_hi {
+ let x = cx + self.x_img_at(&*proj, a) as f32 * z;
+ let rem = a.to_degrees().rem_euclid(45.0);
+ let major = rem.min(45.0 - rem) < step / 2.0;
+ stroke_tick(&mut frame, x, y0, if major { 12.0 } else { 6.0 }, 1.0, on);
+ a += step_rad;
+ }
+
+ // direction labels on the top row; skip directions outside
+ // the +-88 deg window (they would fold onto the same pixel)
+ for (deg, name) in DIRS {
+ let alph = deg.to_radians();
+ let rel = (alph - c + std::f64::consts::PI).rem_euclid(std::f64::consts::TAU)
+ - std::f64::consts::PI;
+ if rel.abs() > fold {
+ continue;
+ }
+ let x = cx + self.x_img_at(&*proj, alph) as f32 * z;
+ if !x.is_finite() || x < b.x - 20.0 || x > b.x + b.width + 20.0 {
+ continue;
+ }
+ let color = if deg == 0.0 { red } else { on };
+ texts.push(Txt {
+ text: name.to_string(),
+ x,
+ y: label_y,
+ centered: true,
+ color,
+ });
+ }
+
+ // center marker: triangle + hairline (tick area only)
+ let tri = Path::new(|p| {
+ p.move_to(Point::new(cx - 6.0, tri_y));
+ p.line_to(Point::new(cx + 6.0, tri_y));
+ p.line_to(Point::new(cx, tri_y + 8.0));
+ p.close();
+ });
+ frame.fill(&tri, red);
+ let hair = Path::new(|p| {
+ p.move_to(Point::new(cx.round(), tri_y + 8.0));
+ p.line_to(Point::new(cx.round(), y0));
+ });
+ frame.stroke(
+ &hair,
+ Stroke {
+ width: 1.0,
+ style: GeoStyle::Solid(red),
+ ..Default::default()
+ },
+ );
+
+ let deg = self.center_deg.rem_euclid(360.0);
+ texts.push(Txt {
+ text: format!("{deg:05.1}°"),
+ x: cx + 10.0,
+ y: tri_y - 3.0,
+ centered: false,
+ color: red,
+ });
+ }
+ CompassStyle::Ruler => {
+ let bx = b.x;
+ let f = |alph_deg: f64| {
+ bx + (alph_deg.rem_euclid(360.0) / 360.0) as f32 * b.width
+ };
+ let center = self.center_deg.rem_euclid(360.0);
+ let mx = f(center);
+
+ // FOV band (between label row and baseline)
+ let half = (self.fov_deg / 2.0 / 360.0) as f32 * b.width;
+ let x0 = (mx - half).max(bx);
+ let x1 = (mx + half).min(bx + b.width);
+ if x1 > x0 {
+ let band = Path::new(|p| {
+ p.rectangle(
+ Point::new(x0, tri_y),
+ Size::new(x1 - x0, y0 - tri_y),
+ );
+ });
+ frame.fill(&band, Color::from_rgba(0.4, 0.65, 1.0, 0.20));
+ }
+
+ // baseline + ticks every 5 deg
+ let line = Path::new(|p| {
+ p.move_to(Point::new(bx, y0 + 0.5));
+ p.line_to(Point::new(bx + b.width, y0 + 0.5));
+ });
+ frame.stroke(
+ &line,
+ Stroke {
+ width: 1.0,
+ style: GeoStyle::Solid(on),
+ ..Default::default()
+ },
+ );
+ for deg in (0..360).step_by(5) {
+ let major = deg % 45 == 0;
+ stroke_tick(
+ &mut frame,
+ f(deg as f64),
+ y0,
+ if major { 12.0 } else { 6.0 },
+ 1.0,
+ on,
+ );
+ }
+ for (deg, name) in DIRS {
+ let color = if deg == 0.0 { red } else { on };
+ texts.push(Txt {
+ text: name.to_string(),
+ x: f(deg),
+ y: label_y,
+ centered: true,
+ color,
+ });
+ }
+
+ // marker
+ let tri = Path::new(|p| {
+ p.move_to(Point::new(mx - 6.0, tri_y));
+ p.line_to(Point::new(mx + 6.0, tri_y));
+ p.line_to(Point::new(mx, tri_y + 8.0));
+ p.close();
+ });
+ frame.fill(&tri, red);
+ texts.push(Txt {
+ text: format!("{center:05.1}°"),
+ x: mx + 10.0,
+ y: tri_y - 3.0,
+ centered: false,
+ color: red,
+ });
+ }
+ }
+
+ renderer.draw_geometry(frame.into_geometry());
+
+ // text (kept alive in state until next frame; positions rounded
+ // so glyphs land on pixel boundaries)
+ let font = renderer.default_font();
+ let state = tree.state.downcast_ref::<CompassState>();
+ {
+ let mut paras = state.paragraphs.borrow_mut();
+ paras.clear();
+ for t in texts.iter() {
+ paras.push(Plain::<<R as adv_text::Renderer>::Paragraph>::new(adv_text::Text {
+ content: t.text.clone(),
+ bounds: Size::new(120.0, 20.0),
+ size: Pixels(13.0),
+ line_height: LineHeight::default(),
+ font,
+ align_x: alignment::Horizontal::Left.into(),
+ align_y: alignment::Vertical::Top.into(),
+ shaping: adv_text::Shaping::Basic,
+ wrapping: adv_text::Wrapping::None,
+ ellipsize: adv_text::Ellipsize::None,
+ }));
+ }
+ }
+ let paras = state.paragraphs.borrow();
+ for (gi, t) in texts.iter().enumerate() {
+ let para = &paras[gi];
+ let w = para.min_bounds().width;
+ let x = if t.centered { t.x - w / 2.0 } else { t.x };
+ renderer.fill_paragraph(
+ para.raw(),
+ Point::new(x.round(), t.y.round()),
+ t.color,
+ b,
+ );
+ }
+ }
+
+ fn update(
+ &mut self,
+ tree: &mut Tree,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor: Cursor,
+ _renderer: &R,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, M>,
+ _viewport: &Rectangle,
+ ) {
+ let state = tree.state.downcast_mut::<CompassState>();
+ let b = layout.bounds();
+ let proj = projection_from_type(self.ptype);
+
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(Button::Left)) => {
+ let Some(p) = cursor.position_over(b) else { return };
+ state.drag = Some((p.x, self.center_deg));
+ if self.style == CompassStyle::Ruler {
+ let alph = ((p.x - b.x) / b.width * 360.0) as f64;
+ shell.publish((self.on_change)(alph));
+ }
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(Button::Left)) => {
+ state.drag = None;
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ let Some((x0, c0)) = state.drag else { return };
+ let Some(p) = cursor.position() else { return };
+ match self.style {
+ CompassStyle::Aligned => {
+ let cx = self.center_x(b);
+ let z = self.zoom;
+ let a0 =
+ self.alph_at_img_x(&*proj, ((x0 - cx) / z) as f64, c0.to_radians());
+ let a1 =
+ self.alph_at_img_x(&*proj, ((p.x - cx) / z) as f64, c0.to_radians());
+ let new_c = (c0.to_radians() - (a1 - a0)).to_degrees();
+ shell.publish((self.on_change)(new_c));
+ }
+ CompassStyle::Ruler => {
+ let alph = ((p.x - b.x) / b.width * 360.0) as f64;
+ shell.publish((self.on_change)(alph));
+ }
+ CompassStyle::Off => {}
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ _tree: &Tree,
+ layout: Layout<'_>,
+ cursor: Cursor,
+ _viewport: &Rectangle,
+ _renderer: &R,
+ ) -> Interaction {
+ if cursor.position_over(layout.bounds()).is_some() {
+ Interaction::Grab
+ } else {
+ Interaction::None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn roundtrip(ptype: ProjectionType, k0: f64, k1: f64, x0: f64) {
+ let proj = projection_from_type(ptype);
+ let mut parms = ViewParams::new();
+ parms.a_center = 0.3;
+ parms.scale = 3000.0;
+ parms.k0 = k0;
+ parms.k1 = k1;
+ parms.x0 = x0;
+ for x in [-2500.0, -800.0, -50.0, 0.0, 120.0, 900.0, 2400.0] {
+ let a = alph_at_img_x(ptype, &*proj, &parms, x, parms.a_center);
+ let (x2, _) = proj.get_coordinates(a, 0.0, &parms);
+ assert!(
+ (x2 - x).abs() < 1e-6,
+ "{ptype:?} x={x}: round-trips to {x2} (alph {a})"
+ );
+ }
+ }
+
+ #[test]
+ fn inverse_rectilinear() {
+ roundtrip(ProjectionType::Rectilinear, 0.0, 0.0, 0.0);
+ }
+ #[test]
+ fn inverse_rectilinear_distorted() {
+ // realistic magnitudes: x0 is in normalized (pre-scale) units
+ roundtrip(ProjectionType::Rectilinear, 0.002, -0.0003, 0.01);
+ }
+ #[test]
+ fn inverse_cylindrical() {
+ roundtrip(ProjectionType::Cylindrical, 0.0, 0.0, 0.0);
+ }
+}
diff --git a/crates/gipfel-app/src/dump.rs b/crates/gipfel-app/src/dump.rs
new file mode 100644
index 0000000..0bcfe56
--- /dev/null
+++ b/crates/gipfel-app/src/dump.rs
@@ -0,0 +1,285 @@
+//! Screen dump: render markers/labels onto the photo and save as PNG/JPEG.
+//! Replaces FLTK's window capture from the original "Screen Dump" menu item.
+
+use image::RgbaImage;
+use std::path::Path;
+
+pub struct DumpMarker {
+ pub x: f64,
+ pub y: f64,
+ pub name: String,
+ pub known: bool,
+ pub hidden: bool,
+}
+
+pub struct DumpTrackPt {
+ pub x: f64,
+ pub y: f64,
+ pub hidden: bool,
+ pub width: f64,
+}
+
+fn px(img: &mut RgbaImage, x: i64, y: i64, c: [u8; 4]) {
+ if x >= 0 && y >= 0 && (x as u32) < img.width() && (y as u32) < img.height() {
+ img.put_pixel(x as u32, y as u32, image::Rgba(c));
+ }
+}
+
+fn line(img: &mut RgbaImage, x0: f64, y0: f64, x1: f64, y1: f64, c: [u8; 4], w: i64) {
+ let dx = x1 - x0;
+ let dy = y1 - y0;
+ let n = (dx.hypot(dy) as i64).max(1);
+ for i in 0..=n {
+ let t = i as f64 / n as f64;
+ let (x, y) = (x0 + dx * t, y0 + dy * t);
+ for oy in -w..=w {
+ for ox in -w..=w {
+ px(img, x.round() as i64 + ox, y.round() as i64 + oy, c);
+ }
+ }
+ }
+}
+
+fn circle(img: &mut RgbaImage, cx: f64, cy: f64, r: f64, c: [u8; 4]) {
+ let n = (2.0 * std::f64::consts::PI * r.max(1.0)) as i64;
+ for i in 0..=n {
+ let a = i as f64 / n as f64 * 2.0 * std::f64::consts::PI;
+ px(img, (cx + r * a.cos()).round() as i64, (cy + r * a.sin()).round() as i64, c);
+ px(img, (cx + (r + 1.0) * a.cos()).round() as i64, (cy + (r + 1.0) * a.sin()).round() as i64, c);
+ }
+}
+
+fn tri(img: &mut RgbaImage, p: [(f64, f64); 3], c: [u8; 4]) {
+ let ymin = p.iter().map(|q| q.y() as i64).min().unwrap();
+ let ymax = p.iter().map(|q| q.y() as i64).max().unwrap();
+ for y in ymin..=ymax {
+ let mut xmin = i64::MAX;
+ let mut xmax = i64::MIN;
+ for i in 0..3 {
+ let a = p[i];
+ let b = p[(i + 1) % 3];
+ if (a.y() as i64) == (b.y() as i64) {
+ continue;
+ }
+ if y >= (a.y() as i64).min(b.y() as i64) && y <= (a.y() as i64).max(b.y() as i64) {
+ let t = (y as f64 - a.y()) / (b.y() - a.y());
+ let x = a.x() + t * (b.x() - a.x());
+ xmin = xmin.min(x.round() as i64);
+ xmax = xmax.max(x.round() as i64);
+ }
+ }
+ for x in xmin..=xmax {
+ px(img, x, y, c);
+ }
+ }
+}
+
+trait XY {
+ fn x(self) -> f64;
+ fn y(self) -> f64;
+}
+impl XY for (f64, f64) {
+ fn x(self) -> f64 {
+ self.0
+ }
+ fn y(self) -> f64 {
+ self.1
+ }
+}
+
+fn load_font(family: &str) -> Option<fontdue::Font> {
+ // Resolve the COSMIC interface font (or system sans-serif fallback) through
+ // the same font discovery cosmic's text engine uses, then hand the bytes to
+ // fontdue for the bitmap dump.
+ static FS: std::sync::OnceLock<std::sync::Mutex<cosmic_text::FontSystem>> =
+ std::sync::OnceLock::new();
+ let mut fs = FS
+ .get_or_init(|| std::sync::Mutex::new(cosmic_text::FontSystem::new()))
+ .lock()
+ .unwrap();
+ let fam = [fontdb::Family::Name(family)];
+ let id = fs
+ .db()
+ .query(&fontdb::Query { families: &fam, ..Default::default() })
+ .or_else(|| fs.db().query(&fontdb::Query::default()))?;
+ let font = fs.get_font(id, fontdb::Weight::NORMAL)?;
+ fontdue::Font::from_bytes(
+ font.data().to_vec(),
+ fontdue::FontSettings { scale: 64.0, ..Default::default() },
+ )
+ .ok()
+}
+
+struct GlyphBlit {
+ x: f32,
+ y: f32,
+ width: usize,
+ #[allow(dead_code)]
+ height: usize,
+ pixels: Vec<u8>,
+}
+
+fn layout_text(font: &fontdue::Font, text: &str, size: f32) -> Vec<GlyphBlit> {
+ use fontdue::layout::{CoordinateSystem, Layout as FontLayout, TextStyle};
+ let mut l = FontLayout::new(CoordinateSystem::PositiveYDown);
+ l.append(&[font], &TextStyle::new(text, size, 0));
+ l.glyphs()
+ .iter()
+ .map(|g| {
+ let (m, pixels) = font.rasterize_config(g.key);
+ GlyphBlit { x: g.x, y: g.y, width: m.width, height: m.height, pixels }
+ })
+ .collect()
+}
+
+fn draw_text(img: &mut RgbaImage, font: &fontdue::Font, x: f64, y: f64, text: &str, c: [u8; 4], size: f32) {
+ for glyph in layout_text(font, text, size) {
+ let gx0 = (x + glyph.x as f64).round() as i64;
+ let gy0 = (y + glyph.y as f64).round() as i64;
+ for (i, &alpha) in glyph.pixels.iter().enumerate() {
+ if alpha == 0 {
+ continue;
+ }
+ let gx = gx0 + (i % glyph.width) as i64;
+ let gy = gy0 + (i / glyph.width) as i64;
+ if gx < 0 || gy < 0 || gx >= img.width() as i64 || gy >= img.height() as i64 {
+ continue;
+ }
+ let a = alpha as u32;
+ let old = img.get_pixel(gx as u32, gy as u32).0;
+ let blend = |o: u8, n: u8| ((o as u32 * (255 - a) + n as u32 * a) / 255) as u8;
+ px(img, gx, gy, [blend(old[0], c[0]), blend(old[1], c[1]), blend(old[2], c[2]), 255]);
+ }
+ }
+}
+
+fn text_width(font: &fontdue::Font, text: &str, size: f32) -> f64 {
+ layout_text(font, text, size)
+ .iter()
+ .map(|g| (g.x + g.width as f32) as f64)
+ .fold(0.0, f64::max)
+}
+
+/// Compose the dump: photo + crosses/leaders/labels + track.
+///
+/// `font_size` is the on-screen label font size in screen pixels, `scale`
+/// converts screen pixels to image pixels (1 / zoom): the overlay geometry
+/// is specified in screen pixels (as seen on the display) but the dump is
+/// rendered at the image's native resolution, so every size must be scaled
+/// accordingly. Label placement runs the exact same collision-avoiding
+/// layout as the on-screen viewport (in screen space, then mapped back to
+/// image space), so the dump shows the labels at the same positions.
+pub fn render(
+ img: &RgbaImage,
+ markers: &[DumpMarker],
+ track: &[DumpTrackPt],
+ out: &Path,
+ font_size: f32,
+ scale: f64,
+ font_family: &str,
+) -> Result<(), String> {
+ let mut canvas = img.clone();
+ let (w, h) = (img.width() as f64, img.height() as f64);
+ let (cx, cy) = (w / 2.0, h / 2.0);
+ let s = scale.max(0.1);
+ let font_img = f64::from(font_size) * s;
+ let font = load_font(font_family);
+
+ // track
+ for win in track.windows(2) {
+ let (a, b) = (&win[0], &win[1]);
+ let c = if a.hidden { [51, 102, 255, 255] } else { [255, 51, 51, 255] };
+ line(&mut canvas, cx + a.x, cy + a.y, cx + b.x, cy + b.y, c, (a.width as i64).max(1));
+ }
+
+ // label layout: the exact same collision-avoiding code as the on-screen
+ // viewport (see viewport::layout_label_y). It runs in screen space
+ // (marker positions / scale, screen font size); the resulting offsets
+ // are mapped back to image space with s. Needed for the leader lines
+ // and the label drawing below.
+ let line_h = f64::from(font_size) * 1.3 + 2.0;
+ let in_img: Vec<bool> = markers
+ .iter()
+ .map(|m| m.x.abs() * 2.0 <= w && m.y.abs() * 2.0 <= h)
+ .collect();
+ let layout: Option<Vec<(f32, f32)>> = font.as_ref().map(|font| {
+ let sx: Vec<f32> = markers.iter().map(|m| (m.x / s) as f32).collect();
+ let sy: Vec<f32> = markers.iter().map(|m| (m.y / s) as f32).collect();
+ let widths: Vec<f32> = markers
+ .iter()
+ .map(|m| text_width(font, &m.name, font_size) as f32)
+ .collect();
+ crate::viewport::layout_label_y(&sx, &sy, &widths, &in_img, line_h as f32)
+ });
+
+ let yellow = [255, 230, 51, 255];
+ // ~1 screen pixel stroke at image resolution
+ let lw = ((s - 1.0) / 2.0).round().max(0.0) as i64;
+ // like the screen: known-hill flags in red
+ let flag_color = [255, 64, 64, 255];
+ for (i, m) in markers.iter().enumerate() {
+ let (x, y) = (cx + m.x, cy + m.y);
+ if x < -10.0 || y < -10.0 || x > w + 10.0 || y > h + 10.0 || !in_img[i] {
+ continue;
+ }
+ // yellow cross + leader line up to the label (screen section 3)
+ let d = 4.0 * s;
+ line(&mut canvas, x - d, y, x + d, y, yellow, lw);
+ if let Some(lay) = &layout {
+ let (lx, ly) = lay[i];
+ let label_line = y + (f64::from(ly) - line_h) * s;
+ line(&mut canvas, x, y, x, label_line, yellow, lw);
+ line(&mut canvas, x, label_line, x + f64::from(lx) * s, label_line, yellow, lw);
+ }
+ }
+ // known-hill flags (pole + pennant + circle), on top of all leaders
+ for (i, m) in markers.iter().enumerate() {
+ if !m.known || !in_img[i] {
+ continue;
+ }
+ let (x, y) = (cx + m.x, cy + m.y);
+ if x < -10.0 || y < -10.0 || x > w + 10.0 || y > h + 10.0 {
+ continue;
+ }
+ let pole = 16.0 * s;
+ line(&mut canvas, x, y, x, y - pole, flag_color, (s.round() as i64).max(1));
+ tri(
+ &mut canvas,
+ [(x, y - pole), (x + 10.0 * s, y - pole * 0.8), (x, y - pole * 0.625)],
+ flag_color,
+ );
+ circle(&mut canvas, x, y, 3.0 * s, flag_color);
+ }
+
+ if let (Some(font), Some(layout)) = (&font, &layout) {
+ for (i, m) in markers.iter().enumerate() {
+ if !in_img[i] {
+ continue;
+ }
+ let (_, ly) = layout[i];
+ let (x, y) = (cx + m.x + 2.0 * s, cy + m.y + (f64::from(ly) - line_h) * s);
+ if x < 0.0 || y < 0.0 || x > w || y > h {
+ continue;
+ }
+ let tw = text_width(font, &m.name, font_img as f32);
+ for yy in (y - 2.0 * s) as i64..=(y + font_img + 2.0 * s) as i64 {
+ for xx in (x - 1.0 * s) as i64..=(x + tw + 1.0 * s) as i64 {
+ px(&mut canvas, xx, yy, [0, 0, 0, 160]);
+ }
+ }
+ let c = if m.hidden { [102, 153, 255, 255] } else { [255, 255, 255, 255] };
+ draw_text(&mut canvas, font, x, y, &m.name, c, font_img as f32);
+ }
+ }
+
+ if out
+ .extension()
+ .is_some_and(|e| e.eq_ignore_ascii_case("jpg") || e.eq_ignore_ascii_case("jpeg"))
+ {
+ let rgb = image::DynamicImage::ImageRgba8(canvas).to_rgb8();
+ rgb.save(out.with_extension("jpg")).map_err(|e| e.to_string())?;
+ } else {
+ canvas.save(out.with_extension("png")).map_err(|e| e.to_string())?;
+ }
+ Ok(())
+}
diff --git a/crates/gipfel-app/src/main.rs b/crates/gipfel-app/src/main.rs
new file mode 100644
index 0000000..4cf0c63
--- /dev/null
+++ b/crates/gipfel-app/src/main.rs
@@ -0,0 +1,1805 @@
+//! gipfel for the COSMIC desktop: annotate mountain panoramas.
+//!
+//! M2/M3: full app skeleton (menus, dialogs, settings sidebar) plus the
+//! interactive viewport (marker placement, refit, context menu, track).
+
+mod compass;
+mod dump;
+mod viewport;
+
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+use std::rc::Rc;
+
+use cosmic::app::{Core, Task};
+use cosmic::iced::keyboard::{self, Key};
+use cosmic::iced::window::Event as WinEvent;
+use cosmic::iced::{event, Color, Event, Length, Subscription, Vector};
+use cosmic::widget::{self, menu, menu::action::MenuAction};
+use cosmic::{Application, ApplicationExt, Element, Theme};
+
+use gipfel_core::hill::{self, Hill, FLAG_DUPLIC, FLAG_HIDDEN, FLAG_TRACK_POINT, FLAG_VISIBLE};
+use gipfel_core::metadata::ImageMetaData;
+use gipfel_core::panorama::Panorama;
+use gipfel_core::projection::ProjectionType;
+use gipfel_core::stitch;
+use gipfel_core::track;
+
+use compass::{Compass, CompassStyle, STRIP_H};
+
+use viewport::{Marker, TrackPt, ViewportWidget, VpMsg};
+
+/// actions of the right-click popover menu on the viewport.
+/// The click position / hit index are baked into the action at menu-build
+/// time (like cosmic-files bakes its entity), so the handler never depends
+/// on state that may have changed while the menu was open.
+#[derive(Clone, Copy, PartialEq)]
+pub enum CtxAction {
+ /// image-space position of the right-click
+ FindPeak(f64, f64),
+ /// hill index of the clicked marker
+ ToggleHidden(usize),
+}
+
+impl Eq for CtxAction {}
+
+impl MenuAction for CtxAction {
+ type Message = Message;
+
+ fn message(&self) -> Message {
+ match self {
+ CtxAction::FindPeak(x, y) => Message::ContextFindPeak(*x, *y),
+ CtxAction::ToggleHidden(i) => Message::ContextToggleHidden(*i),
+ }
+ }
+}
+
+pub struct Flags {
+ pub data: Option<PathBuf>,
+ pub image: Option<PathBuf>,
+}
+
+pub struct Gipfel {
+ core: Core,
+ pan: Panorama,
+ project: Option<Project>,
+ /// hill indices of the known (flagged) markers
+ known: Vec<usize>,
+ /// hill indices of the GPS track points, in file order
+ track: Vec<usize>,
+ // viewport state
+ zoom: f32,
+ fit: bool,
+ panv: Vector,
+ hover: Option<usize>,
+ ctrl: bool,
+ version: u64,
+ win_w: f64,
+ win_h: f64,
+ vp_w: f64,
+ vp_h: f64,
+ // sidebar state
+ lat_in: String,
+ lon_in: String,
+ height_in: String,
+ k0_in: String,
+ k1_in: String,
+ x0_in: String,
+ profile_in: String,
+ profiles: Vec<String>,
+ profile_overwrite: bool,
+ track_width: f64,
+ show_hidden: bool,
+ fit_distortion: bool,
+ label_font: f32,
+ compass_style: CompassStyle,
+ status: String,
+ // overlays
+ picker: Option<Picker>,
+ context: Option<ContextState>,
+ /// peak info shown in the status bar while the mouse hovers a marker
+ hover_info: Option<String>,
+ about: bool,
+}
+
+struct Project {
+ path: PathBuf,
+ handle: cosmic::widget::image::Handle,
+ w: u32,
+ h: u32,
+ md: ImageMetaData,
+}
+
+struct Picker {
+ title: String,
+ query: String,
+ mode: PickerMode,
+}
+
+#[derive(Clone, Copy)]
+enum PickerMode {
+ Viewpoint,
+ FindPeak { img_x: f64, img_y: f64 },
+}
+
+struct ContextState {
+ img_x: f64,
+ img_y: f64,
+ hit: Option<usize>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ Vp(VpMsg),
+ Event(Event),
+ WinResized(f64, f64),
+ FileDropped(Vec<PathBuf>),
+
+ OpenImage,
+ OpenPath(Option<PathBuf>),
+ ImageLoaded(Box<Result<LoadedImage, String>>),
+ SaveImage,
+ SavePath(Option<PathBuf>),
+ ImageSaved(Option<PathBuf>),
+ DumpImage,
+ ImageDumped(Box<Result<PathBuf, String>>),
+ StitchTiff,
+ StitchDone(Box<Result<PathBuf, String>>),
+ LoadTrack,
+ TrackLoaded(Box<Result<Vec<Hill>, String>>),
+
+ ChooseViewpoint,
+ PickerQuery(String),
+ PickerPick(usize),
+ PickerClose,
+ ContextFindPeak(f64, f64),
+ ContextToggleHidden(usize),
+ Surface(cosmic::surface::Action<Message>),
+ Redraw,
+
+ Center(f64),
+ Pitch(f64),
+ Roll(f64),
+ LabelFont(f32),
+ Focal(f64),
+ Visibility(f64),
+ TrackWidth(f64),
+ Lat(String),
+ Lon(String),
+ Height(String),
+ ViewApply,
+ K0(String),
+ K1(String),
+ X0(String),
+ DistortionApply,
+ ProfileName(String),
+ ProfileSave,
+ ProfileLoad,
+ ProfileSelect(usize),
+ Projection(ProjectionType),
+ ToggleShowHidden,
+ FitDistortion(bool),
+ ToggleNav,
+ CompassStyle(CompassStyle),
+
+ Fit,
+ ZoomIn,
+ ZoomOut,
+ About(bool),
+ Quit,
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+enum Action {
+ Open,
+ Save,
+ Dump,
+ Stitch,
+ LoadTrack,
+ Quit,
+ ChooseViewpoint,
+ ShowHidden,
+ Fit,
+ ZoomIn,
+ ZoomOut,
+ ProjNormal,
+ ProjPanoramic,
+ CompassAligned,
+ CompassRuler,
+ CompassOff,
+ About,
+}
+
+impl MenuAction for Action {
+ type Message = Message;
+ fn message(&self) -> Message {
+ match self {
+ Action::Open => Message::OpenImage,
+ Action::Save => Message::SaveImage,
+ Action::Dump => Message::DumpImage,
+ Action::Stitch => Message::StitchTiff,
+ Action::LoadTrack => Message::LoadTrack,
+ Action::Quit => Message::Quit,
+ Action::ChooseViewpoint => Message::ChooseViewpoint,
+ Action::ShowHidden => Message::ToggleShowHidden,
+ Action::Fit => Message::Fit,
+ Action::ZoomIn => Message::ZoomIn,
+ Action::ZoomOut => Message::ZoomOut,
+ Action::ProjNormal => Message::Projection(ProjectionType::Rectilinear),
+ Action::ProjPanoramic => Message::Projection(ProjectionType::Cylindrical),
+ Action::CompassAligned => Message::CompassStyle(CompassStyle::Aligned),
+ Action::CompassRuler => Message::CompassStyle(CompassStyle::Ruler),
+ Action::CompassOff => Message::CompassStyle(CompassStyle::Off),
+ Action::About => Message::About(true),
+ }
+ }
+}
+
+impl From<VpMsg> for Message {
+ fn from(m: VpMsg) -> Self {
+ Message::Vp(m)
+ }
+}
+
+#[derive(Clone)]
+pub struct LoadedImage {
+ path: PathBuf,
+ w: u32,
+ h: u32,
+ handle: cosmic::widget::image::Handle,
+ md: ImageMetaData,
+}
+
+impl std::fmt::Debug for LoadedImage {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "LoadedImage({})", self.path.display())
+ }
+}
+
+/// Load a GPS track from a `.gpx` (GPS Exchange) or `.csv`/`.dat` (gipfel
+/// hill-CSV) file, returning hills flagged as track points.
+fn load_track_file(path: &Path) -> Result<Vec<Hill>, String> {
+ if path.extension().is_some_and(|e| e.eq_ignore_ascii_case("gpx")) {
+ track::load_gpx(&path.to_string_lossy(), 2000)
+ } else {
+ let mut hs = hill::load(&path.to_string_lossy()).map_err(|e| e.to_string())?;
+ for h in hs.iter_mut() {
+ h.flags |= FLAG_TRACK_POINT;
+ }
+ Ok(hs)
+ }
+}
+
+fn data_candidates(flag: Option<&Path>) -> Vec<PathBuf> {
+ let mut v = Vec::new();
+ if let Some(p) = flag {
+ v.push(p.to_path_buf());
+ }
+ if let Ok(e) = std::env::var("GIPFEL_DATA") {
+ v.push(PathBuf::from(e));
+ }
+ v.push(PathBuf::from("gipfel.dat"));
+ if let Some(home) = std::env::var_os("HOME") {
+ v.push(PathBuf::from(home).join(".local/share/gipfel/gipfel.dat"));
+ }
+ v.push(PathBuf::from("/usr/local/share/gipfel/gipfel.dat"));
+ v.push(PathBuf::from("/usr/share/gipfel/gipfel.dat"));
+ v
+}
+
+/// Inverse projection for the vertical axis: the elevation whose projected
+/// pixel row (image-centered coords, center column) is `y_img`. Shares the
+/// damped Newton iteration with the compass strip (`compass::newton_inverse`).
+fn elev_at_img_y(pan: &Panorama, y_img: f64) -> f64 {
+ let c = pan.parms.a_center;
+ let p = pan.parms.a_pitch;
+ let seed = p - (y_img / pan.parms.scale).atan();
+ compass::newton_inverse(
+ y_img,
+ |e| pan.project_point(c, e).map(|v| v.1),
+ seed,
+ p - 1.535,
+ p + 1.535,
+ )
+}
+
+impl Gipfel {
+ fn bump(&mut self) {
+ // keep the visibility limit in sync with the lens' actual field of
+ // view: the angle at which the (distorted) projection reaches the
+ // image edge. Directions beyond it cannot be inside the photo, even
+ // if the distortion model folds their projection back into it.
+ let limit = self.view_angle_limit();
+ if (limit - self.pan.max_view_angle).abs() > 1e-9 {
+ self.pan.max_view_angle = limit;
+ // keep the user-placed marker positions (observations) intact
+ self.pan.update_visible_mountains(Some(&self.known));
+ }
+ self.version += 1;
+ }
+
+ /// Reload the stored profile names from the prefs file.
+ fn refresh_profiles(&mut self) {
+ self.profiles = gipfel_core::profile::list().into_iter().map(|(n, _)| n).collect();
+ }
+
+ /// Load a stored profile and apply its distortion parameters.
+ fn apply_profile(&mut self, name: &str) {
+ match gipfel_core::profile::load(name) {
+ Ok(p) => {
+ self.pan.set_distortion_params(p.k0, p.k1, p.x0);
+ self.sync_inputs();
+ self.bump();
+ self.status = format!("loaded profile '{name}'");
+ }
+ Err(e) => self.status = format!("profile '{name}': {e}"),
+ }
+ }
+
+ /// half-angle (radians) the projection maps the image's right edge to,
+ /// i.e. the horizontal FOV of the lens including fitted distortion.
+ /// 0 = no image loaded / not computable.
+ fn view_angle_limit(&self) -> f64 {
+ let Some(p) = &self.project else {
+ return 0.0;
+ };
+ let proj = gipfel_core::projection::projection_from_type(self.pan.projection_type);
+ let c = self.pan.parms.a_center;
+ let a_edge = compass::alph_at_img_x(
+ self.pan.projection_type,
+ &*proj,
+ &self.pan.parms,
+ p.w as f64 / 2.0,
+ c,
+ );
+ (a_edge - c).abs()
+ }
+
+ fn img_max(&self) -> f64 {
+ self.project
+ .as_ref()
+ .map(|p| p.w.max(p.h) as f64)
+ .unwrap_or(1000.0)
+ }
+
+ fn focal_35mm(&self) -> f64 {
+ self.pan.get_scale() * 35.0 / self.img_max()
+ }
+
+ fn set_focal_35mm(&mut self, f: f64) {
+ self.pan.set_scale(f * self.img_max() / 35.0);
+ }
+
+ fn sync_inputs(&mut self) {
+ self.lat_in = format!("{:.5}", self.pan.get_view_lat());
+ self.lon_in = format!("{:.5}", self.pan.get_view_long());
+ self.height_in = format!("{:.0}", self.pan.get_view_height());
+ self.k0_in = format!("{:.5}", self.pan.parms.k0);
+ self.k1_in = format!("{:.5}", self.pan.parms.k1);
+ self.x0_in = format!("{:.1}", self.pan.parms.x0);
+ }
+
+ fn fit_zoom(&self) -> f32 {
+ let p = match &self.project {
+ Some(p) => p,
+ None => return 1.0,
+ };
+ if self.vp_w < 10.0 || self.vp_h < 10.0 {
+ return 1.0;
+ }
+ (self.vp_w / p.w as f64).min(self.vp_h / p.h as f64) as f32
+ }
+
+ fn markers(&self) -> Rc<Vec<Marker>> {
+ let mut v = Vec::new();
+ for &i in &self.pan.visible {
+ let h = &self.pan.hills[i];
+ if h.is_track_point() || h.flags & FLAG_DUPLIC != 0 {
+ continue;
+ }
+ let hidden = h.flags & FLAG_HIDDEN != 0;
+ if hidden && !self.show_hidden {
+ continue;
+ }
+ v.push(Marker {
+ x: h.x,
+ y: h.y,
+ name: h.name.as_str().into(),
+ idx: i,
+ known: self.known.contains(&i),
+ hidden,
+ });
+ }
+ Rc::new(v)
+ }
+
+ fn track_pts(&self) -> Rc<Vec<TrackPt>> {
+ let v = self
+ .track
+ .iter()
+ .filter(|&&i| self.pan.hills[i].flags & FLAG_VISIBLE != 0)
+ .map(|&i| {
+ let h = &self.pan.hills[i];
+ // C: max(scale * track_width / (dist_m * 10), 1) - distance in
+ // meters. (Was wrongly divided by km here, inflating widths
+ // 1000x and breaking stroke triangulation.) Clamped high so
+ // near-camera points can't produce absurd strokes.
+ let dist_m = self.pan.get_real_distance(i).max(1.0);
+ let width =
+ (self.pan.get_scale() * self.track_width / (dist_m * 10.0)).clamp(1.0, 1000.0);
+ TrackPt {
+ x: h.x,
+ y: h.y,
+ hidden: h.flags & FLAG_HIDDEN != 0,
+ width,
+ }
+ })
+ .collect();
+ Rc::new(v)
+ }
+
+ /// recompute viewport metrics from window size, nav and compass state
+ fn remeasure(&mut self) {
+ let nav = if self.core().nav_bar_active() { 340.0 } else { 0.0 };
+ let strip = if self.project.is_some() && self.compass_style != CompassStyle::Off {
+ STRIP_H as f64
+ } else {
+ 0.0
+ };
+ self.vp_w = (self.win_w - nav).max(100.0);
+ self.vp_h = (self.win_h - 100.0 - strip).max(100.0);
+ if self.fit {
+ self.zoom = self.fit_zoom();
+ }
+ }
+
+ fn refit(&mut self) {
+ if self.known.is_empty() {
+ return;
+ }
+ if let Err(e) = self.pan.comp_params(&self.known, self.fit_distortion) {
+ self.status = format!("refit failed: {e}");
+ }
+ self.sync_inputs();
+ self.bump();
+ }
+
+ fn md_from_state(&self) -> ImageMetaData {
+ let mut md = ImageMetaData::new();
+ if let Some(p) = &self.project {
+ md.manufacturer = p.md.manufacturer.clone();
+ md.model = p.md.model.clone();
+ }
+ md.longitude = self.pan.get_view_long();
+ md.latitude = self.pan.get_view_lat();
+ md.height = self.pan.get_view_height();
+ md.direction = self.pan.get_center_angle();
+ md.pitch = self.pan.get_pitch_angle();
+ md.roll = self.pan.get_roll_angle();
+ md.focal_length_35mm = self.focal_35mm();
+ md.projection_type = self.pan.projection_type as i32;
+ md.k0 = self.pan.parms.k0;
+ md.k1 = self.pan.parms.k1;
+ md.x0 = self.pan.parms.x0;
+ md
+ }
+
+ fn open_image(&mut self, path: PathBuf) -> Task<Message> {
+ let task = async move {
+ let img = image::open(&path).map_err(|e| format!("{}: {e}", path.display()))?;
+ let (w, h) = (img.width(), img.height());
+ let rgba = img.to_rgba8();
+ let handle = cosmic::widget::image::Handle::from_rgba(w, h, rgba.into_raw());
+ let mut md = ImageMetaData::new();
+ md.load_image(&path.to_string_lossy());
+ Ok(LoadedImage { path, w, h, handle, md })
+ };
+ cosmic::task::future(async move { Message::ImageLoaded(Box::new(task.await)) })
+ }
+
+ fn picker_entries(&self, p: &Picker) -> Vec<(String, usize)> {
+ let mut idxs: Vec<usize> = match p.mode {
+ PickerMode::Viewpoint => (0..self.pan.hills.len()).collect(),
+ PickerMode::FindPeak { .. } => self.pan.close.clone(),
+ };
+ let q = p.query.to_uppercase();
+ let mut entries: Vec<(String, usize)> = idxs
+ .drain(..)
+ .filter(|&i| {
+ let h = &self.pan.hills[i];
+ h.flags & (FLAG_DUPLIC | FLAG_TRACK_POINT) == 0
+ && (q.is_empty() || h.name.to_uppercase().contains(&q))
+ })
+ .map(|i| (format!("{} ({} m)", self.pan.hills[i].name, self.pan.hills[i].height as i64), i))
+ .collect();
+ entries.sort_by(|a, b| a.0.cmp(&b.0));
+ entries.truncate(300);
+ entries
+ }
+
+ fn update_vp(&mut self, msg: VpMsg) -> Task<Message> {
+ match msg {
+ VpMsg::ZoomAt { factor, img } => {
+ let new_zoom = (self.zoom * factor).clamp(0.02, 16.0);
+ let z = self.zoom as f64;
+ let nz = new_zoom as f64;
+ self.panv = Vector::new(
+ self.panv.x + (img.x as f64 * (z - nz)) as f32,
+ self.panv.y + (img.y as f64 * (z - nz)) as f32,
+ );
+ self.zoom = new_zoom;
+ self.fit = false;
+ }
+ VpMsg::PanBy { delta } => {
+ self.panv = Vector::new(self.panv.x + delta.x, self.panv.y + delta.y);
+ self.fit = false;
+ }
+ VpMsg::PanTo { pan } => {
+ self.panv = pan;
+ self.fit = false;
+ }
+ VpMsg::MoveMarker { idx, img } => {
+ if idx < self.pan.hills.len() {
+ self.pan.hills[idx].x = img.x as f64;
+ self.pan.hills[idx].y = img.y as f64;
+ self.bump();
+ }
+ }
+ VpMsg::DragEnd { had_marker } => {
+ if had_marker {
+ self.refit();
+ }
+ }
+ VpMsg::ToggleKnown(hit) => {
+ if let Some(i) = hit {
+ if let Some(pos) = self.known.iter().position(|&k| k == i) {
+ self.known.remove(pos);
+ } else {
+ self.known.push(i);
+ }
+ self.refit();
+ }
+ }
+ VpMsg::ContextAt { img, hit } => {
+ self.context = Some(ContextState { img_x: img.x as f64, img_y: img.y as f64, hit });
+ }
+ VpMsg::Hover(h) => {
+ if h != self.hover {
+ self.hover = h;
+ self.hover_info = h.map(|i| {
+ let hm = &self.pan.hills[i];
+ format!(
+ "{} ({} m), distance {:.2} km",
+ hm.name,
+ hm.height as i64,
+ self.pan.get_real_distance(i) / 1000.0
+ )
+ });
+ }
+ }
+ }
+ Task::none()
+ }
+}
+
+impl Application for Gipfel {
+ type Executor = cosmic::executor::Default;
+ type Flags = Flags;
+ type Message = Message;
+ const APP_ID: &'static str = "org.gipfel.App";
+
+ fn core(&self) -> &Core {
+ &self.core
+ }
+
+ fn core_mut(&mut self) -> &mut Core {
+ &mut self.core
+ }
+
+ fn init(core: Core, flags: Flags) -> (Self, Task<Message>) {
+ let mut pan = Panorama::new();
+ let mut status = String::from("no hill database loaded");
+ for c in data_candidates(flags.data.as_deref()) {
+ if c.is_file() {
+ match pan.load_data(&c.to_string_lossy()) {
+ Ok(()) => {
+ status = format!("{} hills loaded", pan.hills.len());
+ break;
+ }
+ Err(e) => status = format!("cannot read {}: {e}", c.display()),
+ }
+ }
+ }
+
+ let mut app = Gipfel {
+ core,
+ pan,
+ project: None,
+ known: Vec::new(),
+ track: Vec::new(),
+ zoom: 1.0,
+ fit: true,
+ panv: Vector::ZERO,
+ hover: None,
+ ctrl: false,
+ version: 0,
+ win_w: 1280.0,
+ win_h: 800.0,
+ vp_w: 0.0,
+ vp_h: 0.0,
+ lat_in: String::new(),
+ lon_in: String::new(),
+ height_in: String::new(),
+ k0_in: String::new(),
+ k1_in: String::new(),
+ x0_in: String::new(),
+ profile_in: String::new(),
+ profiles: gipfel_core::profile::list().into_iter().map(|(n, _)| n).collect(),
+ profile_overwrite: false,
+ track_width: 200.0,
+ show_hidden: false,
+ label_font: viewport::DEFAULT_LABEL_FONT,
+ fit_distortion: false,
+ compass_style: CompassStyle::Aligned,
+ status,
+ picker: None,
+ context: None,
+ hover_info: None,
+ about: false,
+ };
+ app.sync_inputs();
+
+ let task = match flags.image {
+ Some(p) => app.open_image(p),
+ None => Task::none(),
+ };
+ (app, task)
+ }
+
+ fn header_start(&self) -> Vec<Element<'_, Message>> {
+ let mut kb = std::collections::HashMap::new();
+ use cosmic::widget::menu::key_bind::{KeyBind, Modifier};
+ let kb = &mut kb;
+ for (ch, action) in [
+ ("o", Action::Open),
+ ("s", Action::Save),
+ ("d", Action::Dump),
+ ("t", Action::LoadTrack),
+ ("v", Action::ChooseViewpoint),
+ ("f", Action::Fit),
+ ("q", Action::Quit),
+ ] {
+ kb.insert(
+ KeyBind { modifiers: vec![Modifier::Ctrl], key: Key::Character(ch.into()) },
+ action,
+ );
+ }
+
+ let file = menu::Tree::with_children(
+ cosmic::widget::RcElementWrapper::new(menu::root("File").into()),
+ menu::items(
+ kb,
+ vec![
+ menu::Item::Button("Open Image…", None, Action::Open),
+ menu::Item::Button("Save Image…", None, Action::Save),
+ menu::Item::Button("Screen Dump…", None, Action::Dump),
+ menu::Item::Button("Stitch Cylindrical TIFF…", None, Action::Stitch),
+ menu::Item::Divider,
+ menu::Item::Button("Load GPX Track…", None, Action::LoadTrack),
+ menu::Item::Divider,
+ menu::Item::Button("Quit", None, Action::Quit),
+ ],
+ ),
+ );
+ let view = menu::Tree::with_children(
+ cosmic::widget::RcElementWrapper::new(menu::root("View").into()),
+ menu::items(
+ kb,
+ vec![
+ menu::Item::Button("Choose Viewpoint…", None, Action::ChooseViewpoint),
+ menu::Item::CheckBox(
+ "Show Hidden",
+ None,
+ self.show_hidden,
+ Action::ShowHidden,
+ ),
+ menu::Item::Divider,
+ menu::Item::Button("Zoom In", None, Action::ZoomIn),
+ menu::Item::Button("Zoom Out", None, Action::ZoomOut),
+ menu::Item::Button("Fit", None, Action::Fit),
+ menu::Item::Divider,
+ menu::Item::CheckBox(
+ "Compass strip",
+ None,
+ self.compass_style == CompassStyle::Aligned,
+ Action::CompassAligned,
+ ),
+ menu::Item::CheckBox(
+ "Compass ruler",
+ None,
+ self.compass_style == CompassStyle::Ruler,
+ Action::CompassRuler,
+ ),
+ menu::Item::CheckBox(
+ "Compass off",
+ None,
+ self.compass_style == CompassStyle::Off,
+ Action::CompassOff,
+ ),
+ ],
+ ),
+ );
+ let projection = menu::Tree::with_children(
+ cosmic::widget::RcElementWrapper::new(menu::root("Projection").into()),
+ menu::items(
+ kb,
+ vec![
+ menu::Item::CheckBox(
+ "Normal",
+ None,
+ self.pan.projection_type == ProjectionType::Rectilinear,
+ Action::ProjNormal,
+ ),
+ menu::Item::CheckBox(
+ "Panoramic",
+ None,
+ self.pan.projection_type == ProjectionType::Cylindrical,
+ Action::ProjPanoramic,
+ ),
+ ],
+ ),
+ );
+ let help = menu::Tree::with_children(
+ cosmic::widget::RcElementWrapper::new(menu::root("Help").into()),
+ menu::items(kb, vec![menu::Item::Button("About gipfel", None, Action::About)]),
+ );
+
+ vec![
+ widget::nav_bar_toggle()
+ .active(self.core().nav_bar_active())
+ .on_toggle(Message::ToggleNav)
+ .into(),
+ menu::bar(vec![file, view, projection, help])
+ .item_height(menu::ItemHeight::Dynamic(40))
+ .item_width(menu::ItemWidth::Uniform(260))
+ .spacing(4.0)
+ .into(),
+ ]
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ event::listen_with(|event, _status, _id| match event {
+ Event::Keyboard(keyboard::Event::ModifiersChanged(m)) => {
+ Some(Message::Event(Event::Keyboard(keyboard::Event::ModifiersChanged(m))))
+ }
+ Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => {
+ if modifiers.control() {
+ if let Key::Character(c) = &key {
+ match c.as_str() {
+ "o" | "O" => return Some(Message::OpenImage),
+ "s" | "S" => return Some(Message::SaveImage),
+ "d" | "D" => return Some(Message::DumpImage),
+ "t" | "T" => return Some(Message::LoadTrack),
+ "v" | "V" => return Some(Message::ChooseViewpoint),
+ "f" | "F" => return Some(Message::Fit),
+ "q" | "Q" => return Some(Message::Quit),
+ _ => {}
+ }
+ }
+ }
+ match key {
+ Key::Named(cosmic::iced::keyboard::key::Named::Escape) => {
+ Some(Message::PickerClose)
+ }
+ _ => None,
+ }
+ }
+ Event::Window(WinEvent::FileDropped(paths)) => Some(Message::FileDropped(paths)),
+ Event::Window(WinEvent::Resized(size)) => {
+ Some(Message::WinResized(size.width as f64, size.height as f64))
+ }
+ _ => None,
+ })
+ }
+
+ fn update(&mut self, message: Message) -> Task<Message> {
+ use cosmic::dialog::file_chooser::FileFilter;
+
+ match message {
+ Message::Vp(m) => return self.update_vp(m),
+ Message::Event(Event::Keyboard(keyboard::Event::ModifiersChanged(m))) => {
+ self.ctrl = m.control();
+ }
+ Message::Event(_) => {}
+ Message::ToggleNav => {
+ self.core_mut().nav_bar_toggle();
+ self.remeasure();
+ }
+ Message::WinResized(w, h) => {
+ self.win_w = w;
+ self.win_h = h;
+ self.remeasure();
+ }
+ Message::FileDropped(paths) => {
+ for p in paths {
+ let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("");
+ if matches!(ext.to_ascii_lowercase().as_str(), "jpg" | "jpeg" | "png") {
+ return self.open_image(p);
+ } else if ext.eq_ignore_ascii_case("csv") || ext.eq_ignore_ascii_case("dat") {
+ // treat as track file
+ let task = async move {
+ hill::load(&p.to_string_lossy())
+ .map(|mut hs| {
+ for h in hs.iter_mut() {
+ h.flags |= FLAG_TRACK_POINT;
+ }
+ hs
+ })
+ .map_err(|e| e.to_string())
+ };
+ return cosmic::task::future(async move {
+ Message::TrackLoaded(Box::new(task.await))
+ });
+ }
+ }
+ }
+ Message::OpenImage => {
+ return cosmic::task::future(async move {
+ let filter =
+ FileFilter::new("Images").glob("*.jpg").glob("*.jpeg").glob("*.png");
+ Message::OpenPath(pick_open("Open Image", filter).await)
+ });
+ }
+ Message::OpenPath(Some(p)) => return self.open_image(p),
+ Message::OpenPath(None) => {}
+ Message::ImageLoaded(res) => match *res {
+ Ok(li) => {
+ let title = li.path.file_name().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default();
+ self.set_header_title(title);
+ let (w, h) = (li.w, li.h);
+ self.project = Some(Project {
+ path: li.path,
+ handle: li.handle,
+ w,
+ h,
+ md: li.md.clone(),
+ });
+ let md = li.md;
+ let have = gipfel_core::metadata::apply_image_metadata(&mut self.pan, &md, w, h);
+ self.known.clear();
+ self.track.clear();
+ self.sync_inputs();
+ self.fit = true;
+ self.remeasure();
+ self.zoom = self.fit_zoom();
+ self.panv = Vector::ZERO;
+ self.bump();
+ // prefill the profile name the auto-match used (C
+ // get_distortion_profile_name) and refresh the store list
+ let auto_profile = gipfel_core::metadata::distortion_profile_name(&md);
+ self.profile_in = auto_profile.clone().unwrap_or_default();
+ self.profile_overwrite = false;
+ self.refresh_profiles();
+ self.status = if have {
+ format!("loaded (gipfel info found)")
+ } else {
+ "loaded (no gipfel info: set viewpoint)".into()
+ };
+ if md.k0.is_nan() {
+ if let Some(name) =
+ auto_profile.filter(|n| self.profiles.iter().any(|p| p == n))
+ {
+ self.status = format!("{} - distortion profile '{name}'", self.status);
+ }
+ }
+ // the first frame after the image appears can be drawn
+ // before the texture is fully uploaded; a late nudge
+ // guarantees the photo shows without user interaction
+ return Task::perform(
+ async { tokio::time::sleep(std::time::Duration::from_millis(150)).await },
+ |_| cosmic::Action::App(Message::Redraw),
+ );
+ }
+ Err(e) => self.status = e,
+ },
+ Message::SaveImage => {
+ let Some(p) = &self.project else {
+ self.status = "no image loaded".into();
+ return Task::none();
+ };
+ let _ = p;
+ return cosmic::task::future(async move {
+ let filter = FileFilter::new("JPEG").glob("*.jpg");
+ Message::SavePath(pick_save("Save Image", filter).await)
+ });
+ }
+ Message::SavePath(out) => {
+ let (Some(out), Some(p)) = (out, &self.project) else { return Task::none() };
+ let md = self.md_from_state();
+ let src = p.path.clone();
+ return cosmic::task::future(async move {
+ match md.save_image(&src.to_string_lossy(), &out.to_string_lossy()) {
+ Ok(()) => Message::ImageSaved(Some(out)),
+ Err(e) => {
+ let _ = e;
+ Message::ImageSaved(None)
+ }
+ }
+ });
+ }
+ Message::ImageSaved(Some(p)) => {
+ self.status = format!("saved {}", p.display());
+ }
+ Message::ImageSaved(None) => {}
+ Message::DumpImage => {
+ let Some(p) = &self.project else {
+ self.status = "no image loaded".into();
+ return Task::none();
+ };
+ let path = p.path.clone();
+ // the dump is at image resolution; screen-pixel overlay sizes
+ // must be divided by the current zoom to look like the screen
+ let zoom = self.zoom.max(0.02) as f64;
+ let markers: Vec<dump::DumpMarker> = self
+ .markers()
+ .iter()
+ .map(|m| dump::DumpMarker {
+ x: m.x,
+ y: m.y,
+ name: m.name.to_string(),
+ known: m.known,
+ hidden: m.hidden,
+ })
+ .collect();
+ let track: Vec<dump::DumpTrackPt> = self
+ .track_pts()
+ .iter()
+ .map(|t| dump::DumpTrackPt { x: t.x, y: t.y, hidden: t.hidden, width: t.width })
+ .collect();
+ let font_size = self.label_font;
+ let font_family = cosmic::config::interface_font().family;
+ return cosmic::task::future(async move {
+ let res: Result<PathBuf, String> = async {
+ let filter = FileFilter::new("Images").glob("*.png").glob("*.jpg");
+ let out =
+ pick_save("Screen Dump", filter).await.ok_or_else(|| "cancelled".to_string())?;
+ let img = image::open(&path)
+ .map(|i| i.to_rgba8())
+ .map_err(|e| format!("{e}"))?;
+ dump::render(&img, &markers, &track, &out, font_size, 1.0 / zoom, &font_family)?;
+ Ok(out)
+ }
+ .await;
+ Message::ImageDumped(Box::new(res))
+ });
+ }
+ Message::ImageDumped(res) => {
+ self.status = match *res {
+ Ok(p) => format!("dumped {}", p.display()),
+ Err(e) => e,
+ };
+ }
+ Message::StitchTiff => {
+ let Some(p) = &self.project else {
+ self.status = "no image loaded".into();
+ return Task::none();
+ };
+ // cylindrical output spanning the lens' actual field of view
+ let edge = self.view_angle_limit(); // half-angle, radians
+ if !(edge > 0.0 && edge < 1.5) {
+ self.status = "cannot determine lens field of view".into();
+ return Task::none();
+ }
+ let center = self.pan.get_center_angle();
+ let (from, to) = (center - edge.to_degrees(), center + edge.to_degrees());
+ // output scale = the source's focal scale (pixels per radian
+ // at image center): the cylindrical image is 1:1 in the middle
+ // and slightly downsampled towards the edges (where the
+ // rectilinear source has increasing angular stretch). Using
+ // the source width instead would upsample ~5-10% and blur.
+ let radius = self.pan.get_scale();
+ let w_out = (2.0 * edge * radius).round_ties_even().max(16.0) as u32;
+ // cover the source's vertical extent (may be off-center
+ // with pitch); output rows are symmetric about elevation 0
+ let e_top = elev_at_img_y(&self.pan, -(p.h as f64 / 2.0));
+ let e_bot = elev_at_img_y(&self.pan, p.h as f64 / 2.0);
+ let e_max = e_top.abs().max(e_bot.abs()).min(85f64.to_radians());
+ let h_out = (2.0 * radius * e_max.tan()).round_ties_even().max(16.0) as u32;
+ let path = p.path.clone();
+ let pan = self.pan.params_snapshot();
+ return cosmic::task::future(async move {
+ let res: Result<PathBuf, String> = async {
+ let filter = FileFilter::new("TIFF images").glob("*.tif").glob("*.tiff");
+ let out = pick_save("Stitch Cylindrical TIFF", filter)
+ .await
+ .ok_or_else(|| "cancelled".to_string())?;
+ let out_for_task = out.clone();
+ tokio::task::spawn_blocking(move || -> Result<(), String> {
+ let mut src = stitch::StitchSource::load(&path.to_string_lossy())?;
+ // use the live (possibly adjusted) parameters
+ src.pan = pan;
+ let img = stitch::resample(
+ &[src],
+ stitch::ScanMode::Bicubic,
+ w_out,
+ h_out,
+ from,
+ to,
+ );
+ stitch::write_tiff(&out_for_task.to_string_lossy(), &img, true)
+ })
+ .await
+ .map_err(|e| e.to_string())??;
+ Ok(out)
+ }
+ .await;
+ Message::StitchDone(Box::new(res))
+ });
+ }
+ Message::StitchDone(res) => {
+ self.status = match *res {
+ Ok(p) => format!("stitched {}", p.display()),
+ Err(e) => e,
+ };
+ }
+ Message::LoadTrack => {
+ return cosmic::task::future(async move {
+ let res: Result<Vec<Hill>, String> = async {
+ let filter = FileFilter::new("Track files")
+ .glob("*.gpx")
+ .glob("*.csv")
+ .glob("*.dat");
+ let path = pick_open("Load GPX Track", filter)
+ .await
+ .ok_or_else(|| "cancelled".to_string())?;
+ let hs = load_track_file(&path).map_err(|e| e.to_string())?;
+ Ok(hs)
+ }
+ .await;
+ Message::TrackLoaded(Box::new(res))
+ });
+ }
+ Message::TrackLoaded(res) => match *res {
+ Ok(hills) => {
+ // remap known hills by name across the index shift caused by
+ // removing the old track
+ let names: Vec<String> = self.known.iter().map(|&i| self.pan.hills[i].name.clone()).collect();
+ self.pan.remove_hills(FLAG_TRACK_POINT);
+ let n = hills.len();
+ self.pan.add_hills(&hills);
+ let base = self.pan.hills.len() - n;
+ self.track = (base..base + n).collect();
+ self.known = names
+ .iter()
+ .filter_map(|n| self.pan.get_pos(n))
+ .collect();
+ self.status = format!("track: {n} points");
+ self.bump();
+ }
+ Err(e) => self.status = e,
+ },
+ Message::ChooseViewpoint => {
+ self.picker = Some(Picker {
+ title: "Choose Viewpoint".into(),
+ query: String::new(),
+ mode: PickerMode::Viewpoint,
+ });
+ }
+ Message::ContextFindPeak(x, y) => {
+ self.context = None;
+ self.picker = Some(Picker {
+ title: "Find Peak".into(),
+ query: String::new(),
+ mode: PickerMode::FindPeak { img_x: x, img_y: y },
+ });
+ }
+ Message::PickerQuery(q) => {
+ if let Some(p) = &mut self.picker {
+ p.query = q;
+ }
+ }
+ Message::PickerPick(i) => {
+ if let Some(p) = self.picker.take() {
+ match p.mode {
+ PickerMode::Viewpoint => {
+ self.pan.set_viewpoint(Some(i));
+ self.sync_inputs();
+ self.bump();
+ self.status = format!("viewpoint: {}", self.pan.hills[i].name);
+ }
+ PickerMode::FindPeak { img_x, img_y } => {
+ if !self.known.contains(&i) {
+ self.known.push(i);
+ }
+ self.pan.hills[i].flags |= FLAG_VISIBLE;
+ if !self.pan.visible.contains(&i) {
+ self.pan.visible.push(i);
+ }
+ self.pan.hills[i].x = img_x;
+ self.pan.hills[i].y = img_y;
+ self.refit();
+ }
+ }
+ }
+ }
+ Message::PickerClose => {
+ self.picker = None;
+ self.about = false;
+ self.context = None;
+ }
+ Message::ContextToggleHidden(i) => {
+ self.context = None;
+ self.pan.hills[i].flags ^= FLAG_HIDDEN;
+ self.bump();
+ }
+ Message::Surface(action) => {
+ return cosmic::task::message(cosmic::Action::Surface(action));
+ }
+ Message::Redraw => {}
+ Message::Center(a) => {
+ self.pan.set_center_angle(a);
+ self.bump();
+ }
+ Message::CompassStyle(s) => {
+ self.compass_style = s;
+ self.remeasure();
+ self.bump();
+ }
+ Message::Pitch(a) => {
+ self.pan.set_pitch_angle(a);
+ self.bump();
+ }
+ Message::Roll(a) => {
+ self.pan.set_roll_angle(a);
+ self.bump();
+ }
+ Message::LabelFont(s) => {
+ self.label_font = s;
+ self.bump();
+ }
+ Message::Focal(f) => {
+ self.set_focal_35mm(f);
+ self.bump();
+ }
+ Message::Visibility(v) => {
+ // the slider shows more visibility to the right; the core
+ // stores a height/distance threshold (higher = fewer hills)
+ self.pan.set_height_dist_ratio(0.11 - v);
+ self.bump();
+ }
+ Message::TrackWidth(w) => self.track_width = w,
+ Message::Lat(s) => self.lat_in = s,
+ Message::Lon(s) => self.lon_in = s,
+ Message::Height(s) => self.height_in = s,
+ Message::ViewApply => {
+ if let Ok(v) = self.lat_in.parse() {
+ self.pan.set_view_lat(v);
+ }
+ if let Ok(v) = self.lon_in.parse() {
+ self.pan.set_view_long(v);
+ }
+ if let Ok(v) = self.height_in.parse() {
+ self.pan.set_view_height(v);
+ }
+ self.bump();
+ }
+ Message::K0(s) => self.k0_in = s,
+ Message::K1(s) => self.k1_in = s,
+ Message::X0(s) => self.x0_in = s,
+ Message::DistortionApply => {
+ let k0 = self.k0_in.parse().unwrap_or(0.0);
+ let k1 = self.k1_in.parse().unwrap_or(0.0);
+ let x0 = self.x0_in.parse().unwrap_or(0.0);
+ self.pan.set_distortion_params(k0, k1, x0);
+ self.bump();
+ }
+ Message::ProfileName(s) => {
+ self.profile_in = s;
+ self.profile_overwrite = false;
+ }
+ Message::ProfileSave => {
+ let name = self.profile_in.trim().to_string();
+ if name.is_empty() {
+ self.status = "enter a profile name".into();
+ } else {
+ let p = gipfel_core::profile::Profile {
+ k0: self.pan.parms.k0,
+ k1: self.pan.parms.k1,
+ x0: self.pan.parms.x0,
+ };
+ let exists = self.profiles.iter().any(|n| *n == name);
+ if exists && !self.profile_overwrite {
+ self.profile_overwrite = true;
+ self.status =
+ format!("profile '{name}' exists - press Save again to overwrite");
+ } else {
+ match gipfel_core::profile::save(&name, p, true) {
+ Ok(()) => {
+ self.status = format!("saved profile '{name}'");
+ self.profile_overwrite = false;
+ self.refresh_profiles();
+ }
+ Err(e) => self.status = format!("save failed: {e}"),
+ }
+ }
+ }
+ }
+ Message::ProfileLoad => {
+ let name = self.profile_in.trim().to_string();
+ if !name.is_empty() {
+ self.apply_profile(&name);
+ }
+ }
+ Message::ProfileSelect(i) => {
+ if let Some(name) = self.profiles.get(i).cloned() {
+ self.profile_in = name.clone();
+ self.apply_profile(&name);
+ }
+ }
+ Message::Projection(t) => {
+ self.pan.set_projection(t);
+ self.bump();
+ }
+ Message::ToggleShowHidden => {
+ self.show_hidden = !self.show_hidden;
+ self.bump();
+ }
+ Message::FitDistortion(v) => {
+ self.fit_distortion = v;
+ // fitting a newly unlocked calibration takes effect at once
+ if v && self.known.len() >= 4 {
+ self.refit();
+ }
+ }
+ Message::Fit => {
+ self.fit = true;
+ self.zoom = self.fit_zoom();
+ self.panv = Vector::ZERO;
+ }
+ Message::ZoomIn => {
+ return self.update_vp(VpMsg::ZoomAt { factor: 1.25, img: cosmic::iced::Point::ORIGIN });
+ }
+ Message::ZoomOut => {
+ return self.update_vp(VpMsg::ZoomAt { factor: 0.8, img: cosmic::iced::Point::ORIGIN });
+ }
+ Message::About(b) => self.about = b,
+ Message::Quit => {
+ if let Some(id) = self.core.main_window_id() {
+ return cosmic::iced::window::close(id);
+ }
+ }
+ }
+ Task::none()
+ }
+
+ /// The settings panel lives in the framework-managed left nav slot
+ /// (standard padding, condensed-mode handling, collapse state).
+ fn nav_bar(&self) -> Option<Element<'_, cosmic::Action<Message>>> {
+ if !self.core().nav_bar_active() {
+ return None;
+ }
+ let close: Element<Message> = widget::button::icon(
+ widget::icon::from_name("pan-start-symbolic").size(16),
+ )
+ .on_press(Message::ToggleNav)
+ .into();
+ let panel: Element<Message> = widget::container(
+ widget::column::with_children([
+ widget::row::with_children([
+ widget::text::heading("gipfel").into(),
+ widget::container(close)
+ .width(Length::Fill)
+ .align_x(cosmic::iced::alignment::Horizontal::Right)
+ .into(),
+ ])
+ .align_y(cosmic::iced::alignment::Vertical::Center)
+ .spacing(8)
+ .into(),
+ widget::scrollable(self.sidebar()).into(),
+ ])
+ .spacing(10),
+ )
+ .padding([10, 12])
+ .width(Length::Fixed(340.0))
+ .height(Length::Fill)
+ .class(cosmic::theme::Container::Secondary)
+ .into();
+ Some(panel.map(cosmic::Action::App))
+ }
+
+ fn view(&self) -> Element<'_, Message> {
+ let content: Element<Message> = match &self.project {
+ Some(p) => {
+ let vp = ViewportWidget::<Message> {
+ handle: p.handle.clone(),
+ img_w: p.w as f32,
+ img_h: p.h as f32,
+ zoom: self.zoom,
+ pan: self.panv,
+ markers: self.markers(),
+ track: self.track_pts(),
+ hover: self.hover,
+ ctrl: self.ctrl,
+ font_size: self.label_font,
+ version: self.version,
+ _msg: PhantomData,
+ };
+ let vp: Element<Message> = Element::new(vp);
+ // standard popover context menu (like cosmic-files): opens in
+ // its own surface, never shifts the main content
+ let vp: Element<Message> =
+ cosmic::widget::context_menu(vp, self.context_menu_tree())
+ .window_id(
+ self.core
+ .main_window_id()
+ .unwrap_or(cosmic::iced::window::Id::RESERVED),
+ )
+ .on_surface_action(Message::Surface)
+ .into();
+ if self.compass_style == CompassStyle::Off {
+ vp
+ } else {
+ let fov = match self.pan.projection_type {
+ ProjectionType::Rectilinear => (
+ 2.0 * ((p.w as f64 * 0.5) / self.pan.parms.scale).atan()
+ )
+ .to_degrees(),
+ ProjectionType::Cylindrical => {
+ (p.w as f64 / self.pan.parms.scale).to_degrees()
+ }
+ };
+ let strip: Element<Message> = Element::new(Compass {
+ style: self.compass_style,
+ center_deg: self.pan.get_center_angle(),
+ zoom: self.zoom,
+ pan_x: self.panv.x,
+ parms: self.pan.parms,
+ ptype: self.pan.projection_type,
+ fov_deg: fov,
+ max_view_angle: self.pan.max_view_angle,
+ on_change: Rc::new(Message::Center),
+ });
+ widget::column::with_children([vp, strip]).into()
+ }
+ }
+ None => widget::container(
+ widget::column::with_children([
+ widget::text::title2("gipfel").into(),
+ widget::text("Open a JPEG to start (File ▸ Open Image…, Ctrl+O).").into(),
+ widget::button::suggested("Open Image…").on_press(Message::OpenImage).into(),
+ ])
+ .spacing(12)
+ .align_x(cosmic::iced::alignment::Horizontal::Center),
+ )
+ .into(),
+ };
+
+ let main: Element<Message> = content;
+
+ let status_text = self.hover_info.as_deref().unwrap_or(&self.status);
+ let status: Element<Message> = widget::container(widget::text::body(status_text.to_string()))
+ .padding([4, 10])
+ .width(Length::Fill)
+ .style(|_: &Theme| widget::container::Style {
+ background: Some(cosmic::iced::Background::Color(Color::from_rgba(0.0, 0.0, 0.0, 0.15))),
+ ..Default::default()
+ })
+ .into();
+
+ let body: Element<Message> = widget::column::with_children([main, status]).into();
+
+ if let Some(picker) = &self.picker {
+ return self.with_dialog(body, self.view_picker(picker));
+ }
+ if self.about {
+ return self.with_dialog(body, self.view_about());
+ }
+ body
+ }
+}
+
+// helpers split out to keep view() readable
+
+/// settings row with the label above a full-width, easy-grip slider
+fn slider_item(
+ label: &str,
+ value: String,
+ slider: impl Into<Element<'static, Message>>,
+) -> Element<'static, Message> {
+ let header: Element<'static, Message> = widget::row::with_children([
+ widget::text::body(label.to_string()).into(),
+ widget::container(widget::text::body(value))
+ .width(Length::Fill)
+ .align_x(cosmic::iced::alignment::Horizontal::Right)
+ .into(),
+ ])
+ .into();
+ let body: Element<'static, Message> = slider.into();
+ widget::column::with_children([header, body])
+ .spacing(2)
+ .into()
+}
+
+/// Async file chooser: returns the chosen path, or None if cancelled.
+async fn pick_open(
+ title: &str,
+ filter: cosmic::dialog::file_chooser::FileFilter,
+) -> Option<PathBuf> {
+ cosmic::dialog::file_chooser::open::Dialog::new()
+ .title(title.to_string())
+ .filter(filter)
+ .open_file()
+ .await
+ .ok()?
+ .url()
+ .to_file_path()
+ .ok()
+}
+
+/// Async save dialog: returns the target path, or None if cancelled.
+async fn pick_save(
+ title: &str,
+ filter: cosmic::dialog::file_chooser::FileFilter,
+) -> Option<PathBuf> {
+ cosmic::dialog::file_chooser::save::Dialog::new()
+ .title(title.to_string())
+ .filter(filter)
+ .save_file()
+ .await
+ .ok()?
+ .url()
+ .and_then(|u| u.to_file_path().ok())
+}
+
+impl Gipfel {
+ fn with_dialog<'a>(&self, body: Element<'a, Message>, dialog: Element<'a, Message>) -> Element<'a, Message> {
+ widget::column::with_children([
+ widget::container(dialog).width(Length::Fill).into(),
+ body,
+ ])
+ .into()
+ }
+
+ fn view_picker<'a>(&self, p: &'a Picker) -> Element<'a, Message> {
+ let entries = self.picker_entries(p);
+ let mut list = widget::column::with_capacity(entries.len());
+ let mut empty = true;
+ for (label, idx) in entries {
+ empty = false;
+ list = list.push(
+ widget::button::text(label)
+ .on_press(Message::PickerPick(idx))
+ .width(Length::Fill),
+ );
+ }
+ if empty {
+ list = list.push(widget::text("no matches"));
+ }
+
+ widget::dialog()
+ .title(p.title.clone())
+ .body("Type to search, click to select.")
+ .control(
+ widget::text_input("search…", &p.query)
+ .on_input(Message::PickerQuery),
+ )
+ .control({
+ let el: Element<Message> = list.into();
+ widget::scrollable(el).height(Length::Fixed(320.0))
+ })
+ .secondary_action(widget::button::text("Cancel").on_press(Message::PickerClose))
+ .is_overlay(false)
+ .into()
+ }
+
+ fn context_menu_tree(&self) -> Option<Vec<menu::Tree<Message>>> {
+ let ctx = self.context.as_ref()?;
+ let mut items: Vec<menu::Item<CtxAction, String>> = Vec::new();
+ // placing a marker only makes sense on empty image space, not on an
+ // existing peak marker
+ if ctx.hit.is_none() {
+ items.push(menu::Item::Button(
+ "Find Peak…".to_string(),
+ None,
+ CtxAction::FindPeak(ctx.img_x, ctx.img_y),
+ ));
+ }
+ if let Some(i) = ctx.hit {
+ let h = &self.pan.hills[i];
+ items.push(menu::Item::Button(
+ format!(
+ "{} {} ({} m)",
+ if h.flags & FLAG_HIDDEN != 0 { "Unhide" } else { "Hide" },
+ h.name,
+ h.height as i64
+ ),
+ None,
+ CtxAction::ToggleHidden(i),
+ ));
+ }
+ if items.is_empty() {
+ return None;
+ }
+ Some(menu::items(&std::collections::HashMap::new(), items))
+ }
+
+ fn view_about<'a>(&self) -> Element<'a, Message> {
+ widget::dialog()
+ .title("About gipfel")
+ .body("gipfel 0.5.0 (COSMIC preview)")
+ .control(widget::text("Annotate mountain panoramas."))
+ .control(widget::text("Port of gipfel by Johannes Hofmann - mathematical models unchanged.")) .primary_action(widget::button::suggested("Close").on_press(Message::About(false)))
+ .is_overlay(false)
+ .into()
+ }
+
+ fn sidebar(&self) -> Element<'_, Message> {
+ let has_img = self.project.is_some();
+ let focal = self.focal_35mm().clamp(1.0, 300.0);
+ let pitch = self.pan.get_pitch_angle().clamp(-40.0, 20.0);
+ let roll = self.pan.get_roll_angle().clamp(-10.0, 10.0);
+ let vis = self.pan.height_dist_ratio.clamp(0.01, 0.1);
+ let vis_ui = 0.11 - vis; // slider: right = more hills visible
+ let label_font = self.label_font;
+ let center = self.pan.get_center_angle().rem_euclid(360.0);
+
+ let vp_name = self
+ .pan
+ .view_name
+ .clone()
+ .unwrap_or_else(|| "manual".to_string());
+
+ let sec_viewpoint = widget::settings::section()
+ .title("Viewpoint")
+ .add(widget::settings::item(
+ "Position",
+ widget::row::with_children([
+ widget::text(vp_name).into(),
+ widget::button::text("Choose…").on_press(Message::ChooseViewpoint).into(),
+ ])
+ .spacing(8),
+ ))
+ .add(widget::settings::item(
+ "Latitude",
+ widget::text_input("°", &self.lat_in)
+ .on_input(Message::Lat)
+ .on_submit(|_| Message::ViewApply),
+ ))
+ .add(widget::settings::item(
+ "Longitude",
+ widget::text_input("°", &self.lon_in)
+ .on_input(Message::Lon)
+ .on_submit(|_| Message::ViewApply),
+ ))
+ .add(widget::settings::item(
+ "Height (m)",
+ widget::text_input("m", &self.height_in)
+ .on_input(Message::Height)
+ .on_submit(|_| Message::ViewApply),
+ ));
+
+ let sec_orientation = widget::settings::section()
+ .title("Orientation")
+ .add(widget::settings::item(
+ "Center (deg)",
+ widget::text(format!("{center:.1}°")),
+ ))
+ .add(slider_item(
+ "Focal length (35mm)",
+ format!("{focal:.1} mm"),
+ widget::slider(1.0..=300.0, focal, Message::Focal)
+ .step(0.01)
+ .height(30.0),
+ ))
+ .add(slider_item(
+ "Pitch (deg)",
+ format!("{pitch:.1}°"),
+ widget::slider(-40.0..=20.0, pitch, Message::Pitch)
+ .step(0.01)
+ .height(30.0),
+ ))
+ .add(slider_item(
+ "Roll (deg)",
+ format!("{roll:.1}°"),
+ widget::slider(-10.0..=10.0, roll, Message::Roll)
+ .step(0.01)
+ .height(30.0),
+ ));
+
+ let sec_display = widget::settings::section()
+ .title("Display")
+ .add(slider_item(
+ "Visibility",
+ format!("{:.0}%", (vis_ui - 0.01) / 0.09 * 100.0),
+ widget::slider(0.01..=0.1, vis_ui, Message::Visibility)
+ .step(0.001)
+ .height(30.0),
+ ))
+ .add(slider_item(
+ "Track width",
+ format!("{:.0} m at 1 km", self.track_width),
+ widget::slider(1.0..=500.0, self.track_width, Message::TrackWidth)
+ .step(1.0)
+ .height(30.0),
+ ))
+ .add(slider_item(
+ "Label size",
+ format!("{label_font:.0} px"),
+ widget::slider(8.0f32..=28.0, label_font, Message::LabelFont)
+ .step(1.0)
+ .height(30.0),
+ ))
+ .add(widget::settings::item(
+ "Show hidden",
+ widget::toggler(self.show_hidden).on_toggle(|_| Message::ToggleShowHidden),
+ ))
+ .add(widget::row::with_children([
+ widget::button::text("Zoom −").on_press(Message::ZoomOut).into(),
+ widget::button::text("Fit").on_press(Message::Fit).into(),
+ widget::button::text("Zoom +").on_press(Message::ZoomIn).into(),
+ widget::text(format!("{:.0}%", self.zoom * 100.0)).into(),
+ ])
+ .spacing(8));
+
+ let sec_projection = widget::settings::section().title("Projection").add(
+ widget::row::with_children([
+ widget::radio(
+ "Normal",
+ ProjectionType::Rectilinear,
+ Some(self.pan.projection_type),
+ Message::Projection,
+ )
+ .into(),
+ widget::radio(
+ "Panoramic",
+ ProjectionType::Cylindrical,
+ Some(self.pan.projection_type),
+ Message::Projection,
+ )
+ .into(),
+ ])
+ .spacing(16),
+ );
+
+ // distortion fitting is locked unless enough markers are placed
+ let can_fit = self.known.len() >= 4;
+ let fit_cb = widget::checkbox(self.fit_distortion);
+ let fit_cb = if can_fit {
+ fit_cb.on_toggle(Message::FitDistortion)
+ } else {
+ fit_cb.on_toggle_maybe(None as Option<fn(bool) -> Message>)
+ };
+ let mut fit_col = vec![fit_cb.into()];
+ if !can_fit {
+ fit_col.push(widget::text("position 4 or more markers to enable").size(11).into());
+ }
+
+ // fixed-width label column: every control starts at the same x and
+ // therefore shares the same width (settings::item sizes labels to
+ // their text, which made the profile field narrower than k0/k1/x0)
+ let mut sec_distortion = widget::settings::section()
+ .title("Distortion")
+ .add(widget::settings::item_row(vec![
+ widget::text("Fit distortion").width(110).into(),
+ widget::column::with_children(fit_col).spacing(2).into(),
+ ]))
+ .add(widget::settings::item_row(vec![
+ widget::text("k0").width(110).into(),
+ widget::text_input("0", &self.k0_in)
+ .on_input(Message::K0)
+ .on_submit(|_| Message::DistortionApply)
+ .into(),
+ ]))
+ .add(widget::settings::item_row(vec![
+ widget::text("k1").width(110).into(),
+ widget::text_input("0", &self.k1_in)
+ .on_input(Message::K1)
+ .on_submit(|_| Message::DistortionApply)
+ .into(),
+ ]))
+ .add(widget::settings::item_row(vec![
+ widget::text("x0").width(110).into(),
+ widget::text_input("0", &self.x0_in)
+ .on_input(Message::X0)
+ .on_submit(|_| Message::DistortionApply)
+ .into(),
+ ]))
+ .add(widget::settings::item_row(vec![
+ widget::text("profile").width(110).into(),
+ widget::text_input("Camera_Model_50.00_mm", &self.profile_in)
+ .on_input(Message::ProfileName)
+ .on_submit(|_| Message::ProfileSave)
+ .into(),
+ ]))
+ .add(
+ widget::row::with_children([
+ widget::button::suggested("Save profile")
+ .on_press(Message::ProfileSave)
+ .into(),
+ widget::button::text("Load profile")
+ .on_press(Message::ProfileLoad)
+ .into(),
+ ])
+ .spacing(8),
+ );
+ if !self.profiles.is_empty() {
+ // stored profiles (shared with the C version); selecting one
+ // loads its parameters
+ let win = self
+ .core
+ .main_window_id()
+ .unwrap_or(cosmic::iced::window::Id::RESERVED);
+ sec_distortion = sec_distortion.add(widget::settings::item_row(vec![
+ widget::text("stored").width(110).into(),
+ widget::dropdown::popup_dropdown(
+ self.profiles.clone(),
+ None,
+ Message::ProfileSelect,
+ win,
+ Message::Surface,
+ |m| m,
+ )
+ .into(),
+ ]));
+ }
+
+ let mut col = widget::column::with_children([
+ sec_viewpoint.into(),
+ sec_orientation.into(),
+ sec_projection.into(),
+ sec_distortion.into(),
+ sec_display.into(),
+ ])
+ .spacing(12)
+ .padding(12);
+
+ if !has_img {
+ col = col.push(widget::text("Load an image to enable the controls."));
+ }
+ col.into()
+ }
+}
+
+fn main() -> cosmic::iced::Result {
+ let mut flags = Flags { data: None, image: None };
+ let mut args = std::env::args().skip(1);
+ while let Some(a) = args.next() {
+ match a.as_str() {
+ "--data" => flags.data = args.next().map(PathBuf::from),
+ s if !s.starts_with('-') && flags.image.is_none() => flags.image = Some(PathBuf::from(s)),
+ _ => {}
+ }
+ }
+ cosmic::app::run::<Gipfel>(
+ cosmic::app::Settings::default().size(cosmic::iced::Size::new(1280.0, 800.0)),
+ flags,
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn elev_inverse_matches_projection() {
+ let mut pan = Panorama::new();
+ pan.set_projection(ProjectionType::Rectilinear);
+ pan.set_center_angle(0.0);
+ pan.set_pitch_angle(0.0);
+ pan.set_scale(200.0);
+ // undistorted rectilinear: y = scale * tan(pitch - elev)
+ for &y in &[-100.0, -30.0, 0.0, 45.0, 120.0] {
+ let e = elev_at_img_y(&pan, y);
+ let (_, y_back) = pan.project_point(0.0, e).unwrap();
+ assert!((y_back - y).abs() < 0.01, "y={y} -> elev={e} -> {y_back}");
+ }
+ // with pitch the band is asymmetric but still invertible
+ pan.set_pitch_angle(10.0);
+ let p = pan.parms.a_pitch;
+ let top = elev_at_img_y(&pan, -100.0);
+ let bot = elev_at_img_y(&pan, 100.0);
+ assert!(top > p && bot < p, "top={top} p={p} bot={bot}");
+ let (_, y_back) = pan.project_point(0.0, top).unwrap();
+ assert!((y_back + 100.0).abs() < 0.01);
+ }
+}
diff --git a/crates/gipfel-app/src/viewport.rs b/crates/gipfel-app/src/viewport.rs
new file mode 100644
index 0000000..3001fc4
--- /dev/null
+++ b/crates/gipfel-app/src/viewport.rs
@@ -0,0 +1,659 @@
+//! The gipfel viewport: a custom widget that draws the photo, hill markers,
+//! decluttered labels, GPS track, and handles all canvas interaction.
+//!
+//! Marker/label placement mirrors the original GipfelWidget::draw/set_labels
+//! (image-space coordinates; the screen transform is applied at draw time).
+
+use std::rc::Rc;
+use std::time::Instant;
+
+use cosmic::iced::advanced::image::{FilterMethod, Handle, Image as CoreImage};
+use cosmic::iced::advanced::renderer::Style as RendererStyle;
+use cosmic::iced::advanced::text::{
+ self as adv_text, paragraph::Plain, LineHeight, Renderer as _,
+};
+use cosmic::iced::advanced::widget::{tree, Tree, Widget as WidgetTrait};
+use cosmic::iced::advanced::{Clipboard, Layout, Shell};
+use cosmic::iced::mouse::{self, Button, Cursor, Interaction, ScrollDelta};
+use cosmic::iced::widget::canvas::{Frame, LineCap, Path, Stroke};
+use cosmic::iced::advanced::graphics::geometry::Style as GeoStyle;
+use cosmic::iced::{
+ alignment, Color, Event, Length, Pixels, Point, Rectangle, Radians, Size, Vector,
+};
+
+use cosmic::Renderer as R;
+use cosmic::Theme;
+
+pub const DEFAULT_LABEL_FONT: f32 = 12.0;
+const CROSS_SIZE: f64 = 4.0;
+
+#[derive(Clone, Debug)]
+pub struct Marker {
+ /// image-space coordinates, origin at image center (like C's m->x/m->y)
+ pub x: f64,
+ pub y: f64,
+ pub name: Rc<str>,
+ pub idx: usize,
+ pub known: bool,
+ pub hidden: bool,
+}
+
+#[derive(Clone, Debug)]
+pub struct TrackPt {
+ pub x: f64,
+ pub y: f64,
+ pub hidden: bool,
+ pub width: f64,
+}
+
+#[derive(Debug, Clone)]
+pub enum VpMsg {
+ /// zoom by `factor`, keeping image point `img` under the cursor
+ ZoomAt { factor: f32, img: Point },
+ PanBy { delta: Vector },
+ PanTo { pan: Vector },
+ MoveMarker { idx: usize, img: Point },
+ DragEnd { had_marker: bool },
+ /// hill index of the toggled marker (None = clicked empty space)
+ ToggleKnown(Option<usize>),
+ ContextAt { img: Point, hit: Option<usize> },
+ /// hill index of the hovered marker
+ Hover(Option<usize>),
+}
+
+#[derive(Clone)]
+struct LabelLayout {
+ version: u64,
+ font_size: f32,
+ zoom: f32,
+ entries: Vec<(f32, f32)>,
+}
+
+#[derive(Default)]
+struct DragState {
+ drag: Drag,
+ last_click: Option<(Instant, Point)>,
+ label_cache: std::cell::RefCell<Option<LabelLayout>>,
+ /// Paragraph primitives must outlive the frame: the renderer stores only a
+ /// Weak reference, so keep strong refs here until the next draw.
+ paragraphs: std::cell::RefCell<Vec<Plain<<R as adv_text::Renderer>::Paragraph>>>,
+}
+
+#[derive(Default, Clone)]
+enum Drag {
+ #[default]
+ None,
+ Marker(usize),
+ Pan { start: Point, pan0: Vector },
+}
+
+pub struct ViewportWidget<M: From<VpMsg> + Send + 'static> {
+ pub handle: Handle,
+ pub img_w: f32,
+ pub img_h: f32,
+ pub zoom: f32,
+ pub pan: Vector,
+ /// hill markers in visible order; label layout depends on this order
+ pub markers: Rc<Vec<Marker>>,
+ pub track: Rc<Vec<TrackPt>>,
+ pub hover: Option<usize>,
+ pub ctrl: bool,
+ /// label font size in pixels (UI-tunable)
+ pub font_size: f32,
+ /// bump whenever marker positions change to invalidate the label cache
+ pub version: u64,
+ pub _msg: std::marker::PhantomData<fn() -> M>,
+}
+
+impl<M: From<VpMsg> + Send + 'static> Clone for ViewportWidget<M> {
+ fn clone(&self) -> Self {
+ ViewportWidget {
+ handle: self.handle.clone(),
+ img_w: self.img_w,
+ img_h: self.img_h,
+ zoom: self.zoom,
+ pan: self.pan,
+ markers: self.markers.clone(),
+ track: self.track.clone(),
+ hover: self.hover,
+ ctrl: self.ctrl,
+ font_size: self.font_size,
+ version: self.version,
+ _msg: std::marker::PhantomData,
+ }
+ }
+}
+
+impl<M: From<VpMsg> + Send + 'static> ViewportWidget<M> {
+ /// line height: paragraph metrics (cosmic LineHeight default = 1.3x) plus
+ /// a 2px gap, so stacked labels never touch
+ fn line_h(&self) -> f32 {
+ self.font_size * 1.3 + 2.0
+ }
+
+ fn center(bounds: Rectangle, pan: Vector) -> Point {
+ Point::new(
+ bounds.x + bounds.width / 2.0 + pan.x,
+ bounds.y + bounds.height / 2.0 + pan.y,
+ )
+ }
+
+ fn to_img(&self, center: Point, p: Point) -> Point {
+ Point::new((p.x - center.x) / self.zoom, (p.y - center.y) / self.zoom)
+ }
+
+ /// hit-test in image space; tolerance scales with zoom (~6 screen px).
+ /// Returns the *hill index* of the hit marker.
+ fn hit(&self, center: Point, p: Point) -> Option<usize> {
+ let img = self.to_img(center, p);
+ let tol = (6.0 / self.zoom).clamp(2.0, 20.0);
+ self.markers
+ .iter()
+ .find(|m| (m.x as f32 - img.x).abs() < tol && (m.y as f32 - img.y).abs() < tol)
+ .map(|m| m.idx)
+ }
+}
+
+impl<M: From<VpMsg> + Send + 'static> WidgetTrait<M, Theme, R> for ViewportWidget<M> {
+ fn size(&self) -> Size<Length> {
+ Size::new(Length::Fill, Length::Fill)
+ }
+
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<DragState>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(DragState::default())
+ }
+
+ fn layout(
+ &mut self,
+ _tree: &mut Tree,
+ _renderer: &R,
+ limits: &cosmic::iced::advanced::layout::Limits,
+ ) -> cosmic::iced::advanced::layout::Node {
+ cosmic::iced::advanced::layout::Node::new(limits.max())
+ }
+
+ fn draw(
+ &self,
+ tree: &Tree,
+ renderer: &mut R,
+ _theme: &Theme,
+ _style: &RendererStyle,
+ layout: Layout<'_>,
+ _cursor: Cursor,
+ _viewport: &Rectangle,
+ ) {
+ use cosmic::iced::advanced::image::Renderer as _;
+ use cosmic::iced::advanced::Renderer as _;
+
+ let bounds = layout.bounds();
+ let center = Self::center(bounds, self.pan);
+ let z = self.zoom;
+
+ // 1. photo
+ let dest = Rectangle::new(
+ Point::new(center.x - self.img_w * z / 2.0, center.y - self.img_h * z / 2.0),
+ Size::new(self.img_w * z, self.img_h * z),
+ );
+ renderer.draw_image(
+ CoreImage {
+ handle: self.handle.clone(),
+ filter_method: FilterMethod::Linear,
+ rotation: Radians(0.0),
+ border_radius: 0.0.into(),
+ opacity: 1.0,
+ snap: false,
+ },
+ dest,
+ bounds,
+ );
+
+ let layout_entries = self.label_layout(tree, renderer);
+
+ // The wgpu renderer draws geometry before images within one layer;
+ // record the overlay in a fresh layer so it lands on top of the photo.
+ renderer.start_layer(bounds);
+ self.draw_overlay(renderer, tree, bounds, center, &layout_entries);
+ renderer.end_layer();
+ }
+
+ fn update(
+ &mut self,
+ tree: &mut Tree,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor: Cursor,
+ _renderer: &R,
+ _clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, M>,
+ _viewport: &Rectangle,
+ ) {
+ let bounds = layout.bounds();
+ let center = Self::center(bounds, self.pan);
+ let state = tree.state.downcast_mut::<DragState>();
+
+ match event {
+ Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
+ let Some(p) = cursor.position_over(bounds) else { return };
+ let (dx, dy) = match delta {
+ ScrollDelta::Pixels { x, y } => (*x, *y),
+ ScrollDelta::Lines { x, y } => (*x * 30.0, *y * 30.0),
+ };
+ if self.ctrl {
+ shell.publish(M::from(VpMsg::ZoomAt {
+ factor: (1.0 + dy * 0.0015).clamp(0.5, 2.0),
+ img: self.to_img(center, p),
+ }));
+ } else {
+ shell.publish(M::from(VpMsg::PanBy { delta: Vector::new(-dx, -dy) }));
+ }
+ shell.request_redraw();
+ }
+ Event::Mouse(mouse::Event::ButtonPressed(Button::Left)) => {
+ let Some(p) = cursor.position_over(bounds) else { return };
+ let now = Instant::now();
+ let dbl = state
+ .last_click
+ .is_some_and(|(t, q)| {
+ t.elapsed().as_millis() < 400
+ && (q.x - p.x).abs() < 6.0
+ && (q.y - p.y).abs() < 6.0
+ });
+ state.last_click = Some((now, p));
+
+ if dbl {
+ state.drag = Drag::None;
+ state.last_click = None;
+ shell.publish(M::from(VpMsg::ToggleKnown(self.hit(center, p))));
+ return;
+ }
+
+ if let Some(idx) = self.hit(center, p) {
+ if self.markers.iter().any(|m| m.idx == idx && m.known) {
+ state.drag = Drag::Marker(idx);
+ return;
+ }
+ }
+ state.drag = Drag::Pan { start: p, pan0: self.pan };
+ }
+ Event::Mouse(mouse::Event::ButtonReleased(Button::Left)) => {
+ let had_marker = matches!(state.drag, Drag::Marker(_));
+ state.drag = Drag::None;
+ shell.publish(M::from(VpMsg::DragEnd { had_marker }));
+ }
+ Event::Mouse(mouse::Event::ButtonPressed(Button::Middle)) => {
+ if let Some(p) = cursor.position_over(bounds) {
+ shell.publish(M::from(VpMsg::ToggleKnown(self.hit(center, p))));
+ }
+ }
+ Event::Mouse(mouse::Event::ButtonPressed(Button::Right)) => {
+ if let Some(p) = cursor.position_over(bounds) {
+ shell.publish(M::from(VpMsg::ContextAt {
+ img: self.to_img(center, p),
+ hit: self.hit(center, p),
+ }));
+ }
+ }
+ Event::Mouse(mouse::Event::CursorMoved { .. }) => {
+ match state.drag.clone() {
+ Drag::Marker(idx) => {
+ if let Some(p) = cursor.position() {
+ shell.publish(M::from(VpMsg::MoveMarker {
+ idx,
+ img: self.to_img(center, p),
+ }));
+ shell.request_redraw();
+ }
+ }
+ Drag::Pan { start, pan0 } => {
+ if let Some(p) = cursor.position() {
+ shell.publish(M::from(VpMsg::PanTo {
+ pan: pan0 + Vector::new(p.x - start.x, p.y - start.y),
+ }));
+ shell.request_redraw();
+ }
+ }
+ Drag::None => {
+ match cursor.position_over(bounds) {
+ Some(p) => shell.publish(M::from(VpMsg::Hover(self.hit(center, p)))),
+ // cursor moved off the viewport (e.g. into the
+ // sidebar): clear any stale hover info
+ None => shell.publish(M::from(VpMsg::Hover(None))),
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ _tree: &Tree,
+ layout: Layout<'_>,
+ cursor: Cursor,
+ _viewport: &Rectangle,
+ _renderer: &R,
+ ) -> Interaction {
+ let center = Self::center(layout.bounds(), self.pan);
+ match cursor.position_over(layout.bounds()) {
+ Some(p) if self.hit(center, p).is_some() => Interaction::Pointer,
+ Some(_) => Interaction::Grab,
+ None => Interaction::None,
+ }
+ }
+}
+
+impl<M: From<VpMsg> + Send + 'static> ViewportWidget<M> {
+ fn draw_overlay(
+ &self,
+ renderer: &mut R,
+ tree: &Tree,
+ bounds: Rectangle,
+ center: Point,
+ layout_entries: &[(f32, f32)],
+ ) {
+ use cosmic::iced::advanced::graphics::geometry::Renderer as _;
+ use cosmic::iced::advanced::text::Renderer as _;
+
+ let z = self.zoom;
+ let to_screen = |p: Point| Point::new(center.x + p.x * z, center.y + p.y * z);
+ let layout_entries = layout_entries;
+ let mut frame = Frame::with_bounds(renderer, bounds);
+
+ // 2. GPS track
+ for w in self.track.windows(2) {
+ let (a, b) = (&w[0], &w[1]);
+ let pa = to_screen(Point::new(a.x as f32, a.y as f32));
+ let pb = to_screen(Point::new(b.x as f32, b.y as f32));
+ let color = if a.hidden {
+ Color::from_rgb(0.2, 0.4, 1.0)
+ } else {
+ Color::from_rgb(1.0, 0.2, 0.2)
+ };
+ let stroke = Stroke {
+ width: (a.width as f32 * z).max(1.0),
+ style: GeoStyle::Solid(color),
+ line_cap: LineCap::Round,
+ ..Default::default()
+ };
+ let path = Path::new(|bld| {
+ bld.move_to(pa);
+ bld.line_to(pb);
+ });
+ frame.stroke(&path, stroke);
+ }
+
+ // 3. hill markers: crosses + leaders
+ // C's draw() skips markers outside the image (fabs(x) > w/2 || ...);
+ // must match label_layout's filter, otherwise off-image markers get
+ // drawn (in the letterbox area) without ever being laid out.
+ let in_img = |m: &Marker| {
+ m.x.abs() * 2.0 <= self.img_w as f64 && m.y.abs() * 2.0 <= self.img_h as f64
+ };
+ let yellow = Color::from_rgb(1.0, 0.9, 0.2);
+ for (i, m) in self.markers.iter().enumerate() {
+ if !in_img(m) {
+ continue;
+ }
+ let p = to_screen(Point::new(m.x as f32, m.y as f32));
+ if !bounds.contains(p) {
+ continue;
+ }
+ let (lx, ly) = layout_entries[i];
+ let d = CROSS_SIZE as f32;
+ let label_line = p.y + ly - self.line_h();
+ let stroke = Stroke {
+ width: 1.0,
+ style: GeoStyle::Solid(yellow),
+ ..Default::default()
+ };
+ let path = Path::new(|bld| {
+ bld.move_to(Point::new(p.x - d, p.y));
+ bld.line_to(Point::new(p.x + d, p.y));
+ bld.move_to(Point::new(p.x, p.y));
+ bld.line_to(Point::new(p.x, label_line));
+ bld.move_to(Point::new(p.x, label_line));
+ bld.line_to(Point::new(p.x + lx, label_line));
+ });
+ frame.stroke(&path, stroke);
+ }
+
+ // known-hill flags (pole + pennant + circle)
+ let flag_color = Color::from_rgb(1.0, 0.25, 0.25);
+ for m in self.markers.iter().filter(|m| m.known) {
+ if !in_img(m) {
+ continue;
+ }
+ let p = to_screen(Point::new(m.x as f32, m.y as f32));
+ if !bounds.contains(p) {
+ continue;
+ }
+ let stroke = Stroke {
+ width: 1.5,
+ style: GeoStyle::Solid(flag_color),
+ ..Default::default()
+ };
+ let pole = Path::new(|bld| {
+ bld.move_to(p);
+ bld.line_to(Point::new(p.x, p.y - 16.0));
+ bld.circle(p, 3.0);
+ });
+ let pennant = Path::new(|bld| {
+ bld.move_to(Point::new(p.x, p.y - 16.0));
+ bld.line_to(Point::new(p.x + 10.0, p.y - 13.0));
+ bld.line_to(Point::new(p.x, p.y - 10.0));
+ bld.close();
+ });
+ frame.stroke(&pole, stroke.clone());
+ frame.fill(&pennant, flag_color);
+ }
+
+ renderer.draw_geometry(frame.into_geometry());
+
+ // 4. labels (screen space, constant font size)
+ let font = renderer.default_font();
+ let size = Pixels(self.font_size);
+ // the renderer keeps only a Weak ref to each paragraph, so hold strong
+ // refs in the widget state until the next frame replaces them
+ let state = tree.state.downcast_ref::<DragState>();
+ {
+ let mut paras = state.paragraphs.borrow_mut();
+ paras.clear();
+ paras.reserve(self.markers.len());
+ for m in self.markers.iter() {
+ paras.push(self.make_paragraph(font, size, &m.name));
+ }
+ }
+ let paras = state.paragraphs.borrow();
+
+ for (i, m) in self.markers.iter().enumerate() {
+ if !in_img(m) {
+ continue;
+ }
+ let p = to_screen(Point::new(m.x as f32, m.y as f32));
+ if !bounds.contains(p) {
+ continue;
+ }
+ let (_, ly) = layout_entries[i];
+ let pos = Point::new(p.x + 2.0, p.y + ly - self.line_h());
+ let is_hover = self.hover == Some(m.idx);
+
+ let para = &paras[i];
+ let mb = para.min_bounds();
+
+ let mut bg = Frame::with_bounds(renderer, bounds);
+ let rect = Rectangle::new(
+ Point::new(p.x + 1.0, pos.y - 1.0),
+ Size::new(mb.width + 2.0, mb.height + 1.0),
+ );
+ let path = Path::new(|bld| bld.rectangle(rect.position(), rect.size()));
+ bg.fill(
+ &path,
+ if is_hover {
+ Color::from_rgba(1.0, 0.9, 0.2, 0.95)
+ } else {
+ Color::from_rgba(0.0, 0.0, 0.0, 0.55)
+ },
+ );
+ renderer.draw_geometry(bg.into_geometry());
+
+ let color = if is_hover {
+ Color::BLACK
+ } else if m.hidden {
+ Color::from_rgb(0.4, 0.6, 1.0)
+ } else {
+ Color::WHITE
+ };
+ renderer.fill_paragraph(
+ para.raw(),
+ Point::new(p.x + 2.0, pos.y),
+ color,
+ bounds,
+ );
+ }
+ }
+
+ fn make_paragraph(&self, font: <R as adv_text::Renderer>::Font, size: Pixels, text: &str) -> Plain<<R as adv_text::Renderer>::Paragraph> {
+ Plain::<<R as adv_text::Renderer>::Paragraph>::new(adv_text::Text {
+ content: text.to_string(),
+ bounds: Size::new(600.0, 40.0),
+ size,
+ line_height: LineHeight::default(),
+ font,
+ align_x: alignment::Horizontal::Left.into(),
+ align_y: alignment::Vertical::Top.into(),
+ shaping: adv_text::Shaping::Basic,
+ wrapping: adv_text::Wrapping::None,
+ ellipsize: adv_text::Ellipsize::None,
+ })
+ }
+
+ /// C set_labels port: place labels above crosses, pushing colliding ones up.
+ /// Markers outside the image are skipped entirely (C: fabs(x) > w/2 filter),
+ /// so off-image peaks never push visible labels up.
+ fn label_layout(&self, tree: &Tree, renderer: &mut R) -> Vec<(f32, f32)> {
+ let state = tree.state.downcast_ref::<DragState>();
+ if let Some(l) = state.label_cache.borrow().as_ref() {
+ if l.version == self.version
+ && l.font_size == self.font_size
+ && l.zoom == self.zoom
+ && l.entries.len() == self.markers.len()
+ {
+ return l.entries.clone();
+ }
+ }
+
+ let font = renderer.default_font();
+ let size = Pixels(self.font_size);
+ let line_h = self.line_h();
+
+ let widths: Vec<f32> = self
+ .markers
+ .iter()
+ .map(|m| self.make_paragraph(font, size, &m.name).min_bounds().width)
+ .collect();
+
+ // C: fabs(m->x) > img->w()/2 || fabs(m->y) > img->h()/2 -> skip
+ // (marker coords are center-relative, like C's m->x/m->y)
+ let in_img: Vec<bool> = self
+ .markers
+ .iter()
+ .map(|m| m.x.abs() * 2.0 <= self.img_w as f64 && m.y.abs() * 2.0 <= self.img_h as f64)
+ .collect();
+
+ // Labels are drawn at constant size in *screen* space, so collisions
+ // depend on the on-screen distance between markers: run the layout on
+ // zoom-scaled coordinates (C had no zoom, image space == screen space).
+ let z = self.zoom;
+ let sx: Vec<f32> = self.markers.iter().map(|m| m.x as f32 * z).collect();
+ let sy: Vec<f32> = self.markers.iter().map(|m| m.y as f32 * z).collect();
+
+ let entries = layout_label_y(&sx, &sy, &widths, &in_img, line_h);
+ *state.label_cache.borrow_mut() = Some(LabelLayout {
+ version: self.version,
+ font_size: self.font_size,
+ zoom: self.zoom,
+ entries: entries.clone(),
+ });
+ entries
+ }
+}
+
+/// Shared label collision-avoidance layout, used for both the on-screen
+/// viewport and the screen dump so the dump shows exactly the same label
+/// placement (plan.md M3: same code path).
+///
+/// `xs`/`ys` are marker positions (center-relative) in one uniform pixel
+/// space, `widths` the label widths and `line_h` the line height, all in
+/// that same space. Markers must be ordered by x (the sweep breaks on the
+/// first non-overlapping predecessor). Returns `(label_width + 1, y_offset)`
+/// per marker; the label is drawn at `(marker.x + 2, marker.y + y_offset -
+/// line_h)`.
+pub fn layout_label_y(
+ xs: &[f32],
+ ys: &[f32],
+ widths: &[f32],
+ in_img: &[bool],
+ line_h: f32,
+) -> Vec<(f32, f32)> {
+ let n = xs.len();
+ let mut label_x = vec![0.0f32; n];
+ let mut label_y = vec![0.0f32; n];
+ let cross = CROSS_SIZE as f32;
+ let overlap = |a1: f32, l1: f32, a2: f32, l2: f32| a1 <= a2 + l2 && a1 + l1 >= a2;
+
+ for i in 0..n {
+ if !in_img[i] {
+ continue;
+ }
+ label_x[i] = widths[i] + 1.0;
+ label_y[i] = 0.0;
+
+ let mut colliding: Vec<usize> = Vec::new();
+ for j in (0..i).rev() {
+ if !in_img[j] {
+ continue;
+ }
+ if overlap(xs[i], label_x[i], xs[j] - cross, label_x[j]) {
+ colliding.push(j);
+ } else {
+ break;
+ }
+ }
+ colliding.sort_by(|&a, &b| {
+ let ya = ys[a] + label_y[a];
+ let yb = ys[b] + label_y[b];
+ yb.total_cmp(&ya)
+ });
+ // C does a single overwrite pass here, which can leave overlaps
+ // when a push moves the label into a band that was already
+ // checked. Iterate to a fixed point instead (converges quickly:
+ // each round only moves the label up, bounded by the stack).
+ for _round in 0..64 {
+ let mut pushed = false;
+ for &j in &colliding {
+ if overlap(
+ ys[i] + label_y[i] - line_h,
+ line_h,
+ ys[j] + label_y[j] - line_h,
+ line_h,
+ ) || overlap(ys[i] + label_y[i] - line_h, line_h, ys[j] - cross, 2.0 * cross) {
+ let ny = (ys[j] + label_y[j] - ys[i] - line_h - 2.0).round();
+ if ny < label_y[i] {
+ label_y[i] = ny;
+ pushed = true;
+ }
+ }
+ }
+ if !pushed {
+ break;
+ }
+ }
+ }
+
+ (0..n).map(|i| (label_x[i], label_y[i])).collect()
+}
diff --git a/crates/gipfel-cli/Cargo.toml b/crates/gipfel-cli/Cargo.toml
new file mode 100644
index 0000000..9f0f96d
--- /dev/null
+++ b/crates/gipfel-cli/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "gipfel-cli"
+version = "0.1.0"
+edition = "2021"
+description = "gipfel command line tools (export-position, export-hills)"
+license = "GPL-2.0-or-later"
+
+[[bin]]
+name = "gipfel-cli"
+path = "src/main.rs"
+
+[dependencies]
+gipfel-core = { path = "../gipfel-core" }
diff --git a/crates/gipfel-cli/src/main.rs b/crates/gipfel-cli/src/main.rs
new file mode 100644
index 0000000..f0690a8
--- /dev/null
+++ b/crates/gipfel-cli/src/main.rs
@@ -0,0 +1,371 @@
+//! gipfel-cli: headless command line tools, mirroring the C binary's
+//! `-p/--export-position`, `-e/--export-hills` and stitch modes.
+
+use gipfel_core::hill::FLAG_EXPORT;
+use gipfel_core::metadata::{apply_image_metadata, image_dimensions, ImageMetaData};
+use gipfel_core::panorama::Panorama;
+use gipfel_core::stitch::{self, ScanMode};
+
+fn usage() {
+ eprintln!(
+ "usage: gipfel-cli <command> [options]\n\
+ \n\
+ commands:\n\
+ \x20 export-position <img> print position CSV line\n\
+ \x20 export-hills <img> [-d <data>] [-e <hills>] export hill positions\n\
+ \x20 info <img> show parsed metadata\n\
+ \x20 stitch [-t out.tif [-4]] [-j out.jpg] [-w N]\n\
+ \x20 [-h N] [-r from,to] [-b] img... cylindrical panorama\n\
+ \x20 profiles list distortion profiles\n\
+ \x20 profile-save <name> <k0> <k1> <x0> save a profile\n"
+ );
+}
+
+fn export_position(img: &str) -> i32 {
+ // C reads metadata directly (no image decode); missing values print as nan
+ let mut md = ImageMetaData::new();
+ md.load_image(img);
+
+ // C: printf(",%s,,%f,%f,%d\n", img_file, latitude, longitude, (int)rint(height))
+ println!(
+ ",{},,{},{},{}",
+ img,
+ fmt_f(md.latitude),
+ fmt_f(md.longitude),
+ md.height.round_ties_even() as i64
+ );
+ 0
+}
+
+fn fmt_f(v: f64) -> String {
+ if v.is_nan() {
+ "nan".to_string()
+ } else {
+ format!("{:.6}", v)
+ }
+}
+
+fn export_hills(img: &str, data: &str, export_file: Option<&str>) -> i32 {
+ let Some((w, h)) = image_dimensions(img) else {
+ eprintln!("could not read image {}", img);
+ return 1;
+ };
+
+ let mut md = ImageMetaData::new();
+ md.load_image(img);
+
+ let mut pan = Panorama::new();
+ let have_info = apply_image_metadata(&mut pan, &md, w, h);
+
+ if let Err(e) = pan.load_data(data) {
+ eprintln!("Could not load datafile {}: {}", data, e);
+ return 1;
+ }
+ pan.set_height_dist_ratio(0.07); // C default "visibility"
+
+ if !have_info {
+ eprintln!("No gipfel info available for {}.", img);
+ return 0;
+ }
+
+ // -e: restrict export to the hills from the given file
+ if let Some(file) = export_file {
+ match gipfel_core::hill::load(file) {
+ Ok(mut extra) => {
+ for m in extra.iter_mut() {
+ m.flags |= FLAG_EXPORT;
+ }
+ pan.add_hills(&extra);
+ }
+ Err(_) => return 1,
+ }
+ }
+
+ println!("#\n# name\theight\tx\ty\tdistance\tflags\n#");
+
+ for row in pan.export_hill_rows(w as i64, h as i64, export_file.is_some()) {
+ println!("{}", row);
+ }
+
+ if export_file.is_some() {
+ for m in pan.hills.iter_mut() {
+ m.flags &= !FLAG_EXPORT;
+ }
+ }
+
+ 0
+}
+
+/// Port of the C stitch mode: resample the given images into one
+/// cylindrical panorama. Flags follow the C binary: -w/-h output size,
+/// -r from,to azimuth range in degrees, -b bicubic, -4 16-bit TIFF,
+/// -j/-t output file (tiff wins if both given, as in C).
+fn stitch(imgs: &[String], opts: &StitchOpts) -> i32 {
+ if imgs.is_empty() || (opts.tiff.is_none() && opts.jpeg.is_none()) {
+ usage();
+ return 2;
+ }
+ let mut sources = Vec::new();
+ for img in imgs {
+ match stitch::StitchSource::load(img) {
+ Ok(s) => sources.push(s),
+ Err(e) => {
+ eprintln!("{e}");
+ return 1;
+ }
+ }
+ }
+ let mode = if opts.bicubic { ScanMode::Bicubic } else { ScanMode::Nearest };
+ let out = stitch::resample(&sources, mode, opts.w, opts.h, opts.from, opts.to);
+ let hit = out.hit.iter().filter(|h| **h).count();
+ eprintln!(
+ "stitched {}x{}: {}/{} pixels covered",
+ out.w, out.h, hit, out.hit.len()
+ );
+ let res = if let Some(path) = &opts.tiff {
+ stitch::write_tiff(path, &out, opts.bits16)
+ } else {
+ stitch::write_jpeg(opts.jpeg.as_ref().unwrap(), &out, 90)
+ };
+ match res {
+ Ok(()) => 0,
+ Err(e) => {
+ eprintln!("{e}");
+ 1
+ }
+ }
+}
+
+struct StitchOpts {
+ w: u32,
+ h: u32,
+ from: f64,
+ to: f64,
+ bicubic: bool,
+ bits16: bool,
+ tiff: Option<String>,
+ jpeg: Option<String>,
+}
+
+/// C defaults: stitch_w=2000, stitch_h=500, stitch_from=0.0, stitch_to=380.0
+fn stitch_defaults() -> StitchOpts {
+ StitchOpts {
+ w: 2000,
+ h: 500,
+ from: 0.0,
+ to: 380.0,
+ bicubic: false,
+ bits16: false,
+ tiff: None,
+ jpeg: None,
+ }
+}
+
+fn stitch_cmd(args: &[String]) -> i32 {
+ let mut o = stitch_defaults();
+ let mut imgs = Vec::new();
+ let mut i = 0;
+ while i < args.len() {
+ let a = args[i].as_str();
+ let need = |i: &mut usize| -> Option<String> {
+ if *i + 1 < args.len() {
+ *i += 1;
+ Some(args[*i].clone())
+ } else {
+ None
+ }
+ };
+ let ok = match a {
+ "-w" => match need(&mut i) {
+ Some(v) => {
+ o.w = v.parse().unwrap_or(2000);
+ true
+ }
+ None => false,
+ },
+ "-h" => match need(&mut i) {
+ Some(v) => {
+ o.h = v.parse().unwrap_or(500);
+ true
+ }
+ None => false,
+ },
+ "-r" => match need(&mut i) {
+ Some(v) => {
+ // C: atof(optarg) [, atof(after comma)] — atof reads the
+ // numeric prefix before the comma
+ let (first, second) = v.split_once(',').unwrap_or((v.as_str(), ""));
+ o.from = first.trim().parse().unwrap_or(0.0);
+ if !second.trim().is_empty() {
+ o.to = second.trim().parse().unwrap_or(380.0);
+ }
+ true
+ }
+ None => false,
+ },
+ "-j" => match need(&mut i) {
+ Some(v) => {
+ o.jpeg = Some(v);
+ true
+ }
+ None => false,
+ },
+ "-t" => match need(&mut i) {
+ Some(v) => {
+ o.tiff = Some(v);
+ true
+ }
+ None => false,
+ },
+ "-b" => {
+ o.bicubic = true;
+ true
+ }
+ "-4" => {
+ o.bits16 = true;
+ true
+ }
+ _ => {
+ if a.starts_with('-') {
+ false
+ } else {
+ imgs.push(a.to_string());
+ true
+ }
+ }
+ };
+ if !ok {
+ usage();
+ return 2;
+ }
+ i += 1;
+ }
+ stitch(&imgs, &o)
+}
+
+fn info(img: &str) -> i32 {
+ let mut md = ImageMetaData::new();
+ md.load_image(img);
+ println!("manufacturer: {:?}", md.manufacturer);
+ println!("model: {:?}", md.model);
+ println!("longitude: {}", md.longitude);
+ println!("latitude: {}", md.latitude);
+ println!("height: {}", md.height);
+ println!("direction: {}", md.direction);
+ println!("pitch: {}", md.pitch);
+ println!("roll: {}", md.roll);
+ println!("focal_length: {}", md.focal_length);
+ println!("focal_length_35mm: {}", md.focal_length_35mm);
+ println!("projection type: {}", md.projection_type);
+ println!("k0: {}", md.k0);
+ println!("k1: {}", md.k1);
+ println!("x0: {}", md.x0);
+ 0
+}
+
+fn profiles() -> i32 {
+ let all = gipfel_core::profile::list();
+ if all.is_empty() {
+ eprintln!("no profiles in {}", gipfel_core::profile::prefs_path().display());
+ }
+ for (name, p) in all {
+ println!("{name}\tk0={}\tk1={}\tx0={}", p.k0, p.k1, p.x0);
+ }
+ 0
+}
+
+fn profile_save(name: &str, k0: f64, k1: f64, x0: f64) -> i32 {
+ let p = gipfel_core::profile::Profile { k0, k1, x0 };
+ match gipfel_core::profile::save(name, p, true) {
+ Ok(()) => {
+ eprintln!("saved profile {name}");
+ 0
+ }
+ Err(e) => {
+ eprintln!("{e}");
+ 1
+ }
+ }
+}
+
+fn profile_save_cmd(args: &[String]) -> i32 {
+ if args.len() != 4 {
+ usage();
+ return 2;
+ }
+ let (Ok(k0), Ok(k1), Ok(x0)) = (
+ args[1].parse::<f64>(),
+ args[2].parse::<f64>(),
+ args[3].parse::<f64>(),
+ ) else {
+ usage();
+ return 2;
+ };
+ profile_save(&args[0], k0, k1, x0)
+}
+
+fn main() {
+ let args: Vec<String> = std::env::args().collect();
+ let Some(cmd) = args.get(1) else {
+ usage();
+ std::process::exit(2);
+ };
+
+ let code = match cmd.as_str() {
+ "export-position" => {
+ if args.len() != 3 {
+ usage();
+ 2
+ } else {
+ export_position(&args[2])
+ }
+ }
+ "export-hills" => {
+ if args.len() < 3 {
+ usage();
+ 2
+ } else {
+ let img = &args[2];
+ let mut data = "gipfel.dat".to_string();
+ let mut export_file: Option<String> = None;
+ let mut i = 3;
+ while i < args.len() {
+ match args[i].as_str() {
+ "-d" => {
+ if i + 1 < args.len() {
+ data = args[i + 1].clone();
+ i += 1;
+ }
+ }
+ "-e" => {
+ if i + 1 < args.len() {
+ export_file = Some(args[i + 1].clone());
+ i += 1;
+ }
+ }
+ _ => {}
+ }
+ i += 1;
+ }
+ export_hills(img, &data, export_file.as_deref())
+ }
+ }
+ "info" => {
+ if args.len() != 3 {
+ usage();
+ 2
+ } else {
+ info(&args[2])
+ }
+ }
+ "profiles" => profiles(),
+ "profile-save" => profile_save_cmd(&args[2..]),
+ "stitch" => stitch_cmd(&args[2..]),
+ _ => {
+ usage();
+ 2
+ }
+ };
+
+ std::process::exit(code);
+}
diff --git a/crates/gipfel-core/Cargo.toml b/crates/gipfel-core/Cargo.toml
new file mode 100644
index 0000000..d8ddfd9
--- /dev/null
+++ b/crates/gipfel-core/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "gipfel-core"
+version = "0.1.0"
+edition = "2021"
+description = "Port of gipfel's computational core (hills, panorama, projections, least-squares fit, metadata)"
+license = "GPL-2.0-or-later"
+
+[dependencies]
+kamadak-exif.workspace = true
+quick-xml.workspace = true
+image.workspace = true
+tiff.workspace = true
+dirs.workspace = true
diff --git a/crates/gipfel-core/examples/genjpg.rs b/crates/gipfel-core/examples/genjpg.rs
new file mode 100644
index 0000000..ae12b94
--- /dev/null
+++ b/crates/gipfel-core/examples/genjpg.rs
@@ -0,0 +1,24 @@
+// creates a test JPEG with a gipfel comment: genjpg <out.jpg>
+use gipfel_core::metadata::ImageMetaData;
+
+fn main() {
+ let out = std::env::args().nth(1).unwrap();
+ let (w, h) = (1600u32, 900u32);
+ let img = image::DynamicImage::new_rgb8(w, h).grayscale();
+ img.save(&out).unwrap();
+
+ let mut md = ImageMetaData::new();
+ md.longitude = 11.15;
+ md.latitude = 47.36;
+ md.height = 2100.0;
+ md.direction = 20.0;
+ md.pitch = 0.0;
+ md.roll = 1.0;
+ md.focal_length_35mm = 35.0;
+ md.projection_type = 0;
+ md.k0 = 0.0;
+ md.k1 = 0.0;
+ md.x0 = 0.0;
+ md.save_image(&out, &out).unwrap();
+ println!("wrote {}", out);
+}
diff --git a/crates/gipfel-core/src/hill.rs b/crates/gipfel-core/src/hill.rs
new file mode 100644
index 0000000..a904948
--- /dev/null
+++ b/crates/gipfel-core/src/hill.rs
@@ -0,0 +1,224 @@
+//! Hill database: data structures, CSV loading, duplicate detection.
+//!
+//! Faithful port of gipfel's `Hill.cxx`.
+
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+
+pub const FLAG_DUPLIC: u32 = 1 << 0;
+pub const FLAG_KNOWN: u32 = 1 << 1;
+pub const FLAG_HIDDEN: u32 = 1 << 2;
+pub const FLAG_VISIBLE: u32 = 1 << 3;
+pub const FLAG_TRACK_POINT: u32 = 1 << 4;
+pub const FLAG_EXPORT: u32 = 1 << 5;
+
+pub const PI: f64 = std::f64::consts::PI;
+pub const DEG2RAD: f64 = PI / 180.0;
+
+#[derive(Clone, Debug)]
+pub struct Hill {
+ pub name: String,
+ /// latitude, radians
+ pub phi: f64,
+ /// longitude, radians
+ pub lam: f64,
+ /// height, meters
+ pub height: f64,
+ /// azimuth from viewpoint, radians
+ pub alph: f64,
+ /// peak angle over horizon, radians
+ pub a_elev: f64,
+ /// distance from viewpoint, fraction of earth radius
+ pub dist: f64,
+ pub x: f64,
+ pub y: f64,
+ pub label_x: f64,
+ pub label_y: f64,
+ pub flags: u32,
+}
+
+impl Hill {
+ pub fn new(name: &str, phi: f64, lam: f64, height: f64) -> Self {
+ Hill {
+ name: name.to_string(),
+ phi,
+ lam,
+ height,
+ alph: 0.0,
+ a_elev: 0.0,
+ dist: 0.0,
+ x: 0.0,
+ y: 0.0,
+ label_x: 0.0,
+ label_y: 0.0,
+ flags: 0,
+ }
+ }
+
+ pub fn is_track_point(&self) -> bool {
+ self.flags & FLAG_TRACK_POINT != 0
+ }
+}
+
+/// `atof()` semantics: parse the longest numeric prefix, 0.0 on failure.
+fn atof(s: &str) -> f64 {
+ let s = s.trim();
+ if s.is_empty() {
+ return 0.0;
+ }
+ if let Ok(v) = s.parse::<f64>() {
+ return v;
+ }
+ for i in (1..s.len()).rev() {
+ if let Ok(v) = s[..i].parse::<f64>() {
+ return v;
+ }
+ }
+ 0.0
+}
+
+/// Load a hill database. Two line formats are accepted:
+/// - `SHORT,NAME,DESC,LAT,LON,HEIGHT` (6 fields)
+/// - `LAT,LON,HEIGHT` (3 fields, track point)
+pub fn load(path: &str) -> std::io::Result<Vec<Hill>> {
+ let file = File::open(path)?;
+ let mut hills = Vec::new();
+
+ // the database is Latin-1; decode to Unicode code points
+ for line in BufReader::new(file).split(b'\n') {
+ let raw = line?;
+ let line: String = raw.iter().map(|b| *b as char).collect();
+ // strsep() semantics: split into at most 10 fields, keeping empties
+ let vals: Vec<&str> = line.split(',').take(10).collect();
+ let n = vals.len();
+
+ if n == 6 {
+ // standard format including name and description
+ let phi = atof(vals[3]) * DEG2RAD;
+ let lam = atof(vals[4]) * DEG2RAD;
+ let height = atof(vals[5]);
+ hills.push(Hill::new(vals[1], phi, lam, height));
+ } else if n == 3 {
+ // track point format
+ let phi = atof(vals[0]) * DEG2RAD;
+ let lam = atof(vals[1]) * DEG2RAD;
+ let height = atof(vals[2]);
+ let mut h = Hill::new("", phi, lam, height);
+ // NOTE: original does not set TRACK_POINT here
+ h.flags |= 0;
+ hills.push(h);
+ }
+ }
+
+ Ok(hills)
+}
+
+/// Mark hills that are (near-)exact duplicates of an earlier hill.
+/// Faithful port of `Hills::mark_duplicates`.
+///
+/// Unlike the C original (which sorts the pointer array in place), this
+/// must NOT reorder `hills`: in this port, hill indices are stable handles
+/// (track lists, known-hill sets, visible lists). Sorting an index
+/// permutation instead keeps the semantics identical while preserving
+/// insertion order.
+pub fn mark_duplicates(hills: &mut [Hill], dist: f64) {
+ // descending phi (mirrors the C qsort comparator order)
+ let mut order: Vec<usize> = (0..hills.len()).collect();
+ order.sort_by(|&a, &b| {
+ hills[b]
+ .phi
+ .partial_cmp(&hills[a].phi)
+ .unwrap_or(std::cmp::Ordering::Equal)
+ });
+
+ let len = hills.len();
+ for pos in 0..len {
+ let i = order[pos];
+ if hills[i].is_track_point() {
+ continue;
+ }
+
+ let mut j = pos + 1;
+ while j < len && (hills[order[j]].phi - hills[i].phi).abs() <= dist {
+ let jj = order[j];
+ if hills[jj].flags & FLAG_DUPLIC == 0
+ && (hills[jj].lam - hills[i].lam).abs() <= dist
+ && (hills[jj].height - hills[i].height).abs() <= 50.0
+ {
+ hills[jj].flags |= FLAG_DUPLIC;
+ }
+ j += 1;
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn atof_semantics() {
+ assert_eq!(atof("12.5"), 12.5);
+ assert_eq!(atof(""), 0.0);
+ assert_eq!(atof("abc"), 0.0);
+ assert_eq!(atof("12abc"), 12.0);
+ assert_eq!(atof(" -3.5x"), -3.5);
+ }
+
+ #[test]
+ fn load_formats() {
+ let dir = std::env::temp_dir().join("gipfel-core-test");
+ std::fs::create_dir_all(&dir).unwrap();
+ let path = dir.join("hills.csv");
+ std::fs::write(
+ &path,
+ "# comment line\n\
+ ZSP,Zugspitze,Germany,47.421,10.9855,2962\n\
+ FOO,Foo Peak,,47.5,11.0,1000\n\
+ 47.6,11.1,1234\n\
+ bad,line\n",
+ )
+ .unwrap();
+
+ let hills = load(path.to_str().unwrap()).unwrap();
+ assert_eq!(hills.len(), 3);
+ assert_eq!(hills[0].name, "Zugspitze");
+ assert!((hills[0].phi - 47.421 * DEG2RAD).abs() < 1e-12);
+ assert_eq!(hills[0].height, 2962.0);
+ assert_eq!(hills[2].name, "");
+ let _ = std::fs::remove_file(&path);
+ }
+
+ #[test]
+ fn duplicates() {
+ let mut hills = vec![
+ Hill::new("A", 47.0, 11.0, 1000.0),
+ Hill::new("A2", 47.000001, 11.000001, 1010.0), // dup of A
+ Hill::new("B", 47.5, 11.5, 1000.0),
+ Hill::new("C", 47.0, 11.0, 1200.0), // >50m diff -> not dup
+ ];
+ mark_duplicates(&mut hills, 0.00001);
+ assert_eq!(hills.iter().filter(|h| h.flags & FLAG_DUPLIC != 0).count(), 1);
+ }
+
+ #[test]
+ fn mark_duplicates_preserves_order() {
+ // indices are stable handles in the port: mark_duplicates must not
+ // reorder the vec (regression: an in-place phi sort broke the app's
+ // track index range when add_hills ran it on the full hill list)
+ let mut hills = vec![
+ Hill::new("south", 46.0, 11.0, 1000.0),
+ Hill::new("north", 48.0, 11.0, 1000.0),
+ Hill::new("mid", 47.0, 11.0, 1000.0),
+ Hill::new("mid-dup", 47.000001, 11.0, 1010.0),
+ ];
+ mark_duplicates(&mut hills, 0.00001);
+ assert_eq!(hills[0].name, "south");
+ assert_eq!(hills[1].name, "north");
+ assert_eq!(hills[2].name, "mid");
+ assert_eq!(hills[3].name, "mid-dup");
+ // descending phi visits mid-dup first, so 'mid' is the one marked
+ assert_eq!(hills[2].flags & FLAG_DUPLIC, FLAG_DUPLIC);
+ assert_eq!(hills[3].flags & FLAG_DUPLIC, 0);
+ }
+}
diff --git a/crates/gipfel-core/src/lib.rs b/crates/gipfel-core/src/lib.rs
new file mode 100644
index 0000000..2e2fac9
--- /dev/null
+++ b/crates/gipfel-core/src/lib.rs
@@ -0,0 +1,23 @@
+//! gipfel-core: headless port of the gipfel computational core.
+//!
+//! Modules:
+//! - [`hill`]: hill database (CSV load, duplicate marking)
+//! - [`panorama`]: viewpoint, geodesy, refraction, occlusion, visibility
+//! - [`projection`]: rectilinear/cylindrical projections with symbolic
+//! derivatives generated by maxima (see `codegen/`)
+//! - [`lsq`]: Levenberg-Marquardt fit of the view parameters (system GSL)
+//! - [`metadata`]: `gipfel:` JPEG comment + EXIF read/write
+
+pub mod hill;
+pub mod lsq;
+pub mod metadata;
+pub mod panorama;
+pub mod profile;
+pub mod projection;
+pub mod stitch;
+pub mod track;
+
+pub use hill::Hill;
+pub use metadata::ImageMetaData;
+pub use panorama::Panorama;
+pub use projection::{Projection, ProjectionType, ViewParams};
diff --git a/crates/gipfel-core/src/lsq.rs b/crates/gipfel-core/src/lsq.rs
new file mode 100644
index 0000000..6a9d6c7
--- /dev/null
+++ b/crates/gipfel-core/src/lsq.rs
@@ -0,0 +1,392 @@
+//! Non-linear least squares fit of the view parameters.
+//!
+//! Faithful port of `ProjectionLSQ.cxx`. The C++ original uses GSL's
+//! `gsl_multifit_fdfsolver_lmsder`; this is a self-contained Levenberg-
+//! Marquardt with the same scaled damping (`JᵀJ + λ·diag(JᵀJ)`) and the
+//! analytic Jacobian produced by the maxima-generated derivatives. For the
+//! well-conditioned gipfel fit (residuals reach zero at the optimum) it
+//! converges to the same minimum; the golden tests verify agreement with the
+//! C/GSL output.
+
+use crate::hill::Hill;
+use crate::projection::{Projection, ViewParams};
+
+const MAX_ITER: usize = 200;
+
+/// Number of fitted parameters at a given level.
+fn np_for_level(level: usize) -> usize {
+ match level {
+ 0 => 2,
+ 1 => 4,
+ _ => 7,
+ }
+}
+
+struct FitData<'a> {
+ proj: &'a dyn Projection,
+ hills: &'a [Hill],
+ known: &'a [usize],
+ level: usize,
+ /// parameters at solver start; entries above `level` keep these values
+ parms0: ViewParams,
+}
+
+impl FitData<'_> {
+ fn params_from_x(&self, x: &[f64]) -> ViewParams {
+ // C starts from the initial params and only overwrites the entries
+ // that are actually fitted at this level.
+ let mut p = self.parms0;
+ p.a_center = x[0];
+ p.a_pitch = x[1];
+ if self.level > 0 {
+ p.a_roll = x[2];
+ p.scale = x[3];
+ }
+ if self.level > 1 {
+ p.k0 = x[4];
+ p.k1 = x[5];
+ p.x0 = x[6];
+ }
+ p
+ }
+
+ fn args(&self, p: &ViewParams, i: usize) -> crate::projection::ProjArgs {
+ let m = &self.hills[self.known[i]];
+ crate::projection::ProjArgs::from_params(p, m.alph, m.a_elev)
+ }
+
+ /// Residual vector: 2 entries (x,y) per known hill, length `2*known.len()`.
+ fn residuals(&self, x: &[f64], f: &mut [f64]) {
+ let p = self.params_from_x(x);
+ for i in 0..self.known.len() {
+ let m = &self.hills[self.known[i]];
+ let a = self.args(&p, i);
+ f[2 * i] = self.proj.mac_x(a) - m.x;
+ f[2 * i + 1] = self.proj.mac_y(a) - m.y;
+ }
+ }
+
+ /// Jacobian, row-major `m`×`np` (`m = 2*known.len()`).
+ fn jacobian(&self, x: &[f64], j: &mut [f64]) {
+ let p = self.params_from_x(x);
+ let np = np_for_level(self.level);
+ let row = |r: usize, c: usize, v: f64, j: &mut [f64]| j[r * np + c] = v;
+ for i in 0..self.known.len() {
+ let a = self.args(&p, i);
+
+ row(2 * i, 0, self.proj.mac_x_dc_view(a), j);
+ row(2 * i + 1, 0, self.proj.mac_y_dc_view(a), j);
+ row(2 * i, 1, self.proj.mac_x_dc_pitch(a), j);
+ row(2 * i + 1, 1, self.proj.mac_y_dc_pitch(a), j);
+
+ if np > 2 {
+ row(2 * i, 2, self.proj.mac_x_dc_roll(a), j);
+ row(2 * i + 1, 2, self.proj.mac_y_dc_roll(a), j);
+ row(2 * i, 3, self.proj.mac_x_dscale(a), j);
+ row(2 * i + 1, 3, self.proj.mac_y_dscale(a), j);
+ }
+
+ if np > 4 {
+ row(2 * i, 4, self.proj.mac_x_dk0(a), j);
+ row(2 * i + 1, 4, self.proj.mac_y_dk0(a), j);
+ row(2 * i, 5, self.proj.mac_x_dk1(a), j);
+ row(2 * i + 1, 5, self.proj.mac_y_dk1(a), j);
+ row(2 * i, 6, self.proj.mac_x_dx0(a), j);
+ row(2 * i + 1, 6, self.proj.mac_y_dx0(a), j);
+ }
+ }
+ }
+}
+
+fn dot(a: &[f64], b: &[f64]) -> f64 {
+ a.iter().zip(b).map(|(x, y)| x * y).sum()
+}
+
+/// Solve `A·δ = b` in place (A row-major `n`×`n`, overwritten) by Gaussian
+/// elimination with partial pivoting. Returns `None` if the matrix is
+/// (numerically) singular.
+fn gauss_solve(a: &mut [f64], b: &[f64], n: usize) -> Option<Vec<f64>> {
+ let mut x = b.to_vec();
+ for col in 0..n {
+ // partial pivot
+ let mut piv = col;
+ let mut maxv = a[col * n + col].abs();
+ for r in (col + 1)..n {
+ let v = a[r * n + col].abs();
+ if v > maxv {
+ maxv = v;
+ piv = r;
+ }
+ }
+ if maxv < 1e-300 {
+ return None;
+ }
+ if piv != col {
+ for c in 0..n {
+ a.swap(col * n + c, piv * n + c);
+ }
+ x.swap(col, piv);
+ }
+ let diag = a[col * n + col];
+ for r in (col + 1)..n {
+ let factor = a[r * n + col] / diag;
+ a[r * n + col] = 0.0;
+ for c in (col + 1)..n {
+ a[r * n + c] -= factor * a[col * n + c];
+ }
+ x[r] -= factor * x[col];
+ }
+ }
+ for r in (0..n).rev() {
+ let mut s = x[r];
+ for c in (r + 1)..n {
+ s -= a[r * n + c] * x[c];
+ }
+ x[r] = s / a[r * n + r];
+ }
+ Some(x)
+}
+
+/// One Levenberg-Marquardt fit at a single parameter level. On return, `x`
+/// holds the fitted parameters (length `np`).
+fn lm(data: &FitData, x: &mut [f64], np: usize) {
+ let m = 2 * data.known.len();
+ let mut f = vec![0.0f64; m];
+ let mut fnew = vec![0.0f64; m];
+ let mut jac = vec![0.0f64; m * np];
+ let mut a = vec![0.0f64; np * np];
+ let mut ad = vec![0.0f64; np * np];
+ let mut g = vec![0.0f64; np];
+ let mut neg_g = vec![0.0f64; np];
+ let mut xnew = vec![0.0f64; np];
+
+ data.residuals(x, &mut f);
+ let mut cost = 0.5 * dot(&f, &f);
+ let mut lambda: f64 = 1e-3;
+
+ for _ in 0..MAX_ITER {
+ if !cost.is_finite() {
+ break;
+ }
+ data.jacobian(x, &mut jac);
+
+ // A = JᵀJ (symmetric), g = Jᵀf
+ for i in 0..np {
+ for k in i..np {
+ let mut s = 0.0;
+ for r in 0..m {
+ s += jac[r * np + i] * jac[r * np + k];
+ }
+ a[i * np + k] = s;
+ a[k * np + i] = s;
+ }
+ let mut s = 0.0;
+ for r in 0..m {
+ s += jac[r * np + i] * f[r];
+ }
+ g[i] = s;
+ }
+
+ // Solve the damped normal equations (JᵀJ + λ·diag(JᵀJ))·δ = -Jᵀf,
+ // inflating λ until the system is non-singular.
+ let mut delta: Option<Vec<f64>> = None;
+ for _ in 0..50 {
+ ad.copy_from_slice(&a);
+ for i in 0..np {
+ let d = a[i * np + i];
+ ad[i * np + i] = if d > 1e-300 { d * (1.0 + lambda) } else { lambda.max(1e-300) };
+ }
+ for i in 0..np {
+ neg_g[i] = -g[i];
+ }
+ let mut ad_try = ad.clone();
+ if let Some(d) = gauss_solve(&mut ad_try, &neg_g, np) {
+ delta = Some(d);
+ break;
+ }
+ lambda *= 10.0;
+ }
+ let delta = match delta {
+ Some(d) => d,
+ None => break,
+ };
+
+ for i in 0..np {
+ xnew[i] = x[i] + delta[i];
+ }
+ data.residuals(&xnew, &mut fnew);
+ let cost_new = 0.5 * dot(&fnew, &fnew);
+
+ if cost_new < cost {
+ let improvement = cost - cost_new;
+ x.copy_from_slice(&xnew);
+ std::mem::swap(&mut f, &mut fnew);
+ cost = cost_new;
+ lambda = (lambda * 0.1).max(1e-12);
+ // Converged when the cost can no longer improve meaningfully.
+ if improvement <= 1e-18 * cost {
+ break;
+ }
+ } else {
+ lambda *= 10.0;
+ if lambda > 1e16 {
+ break;
+ }
+ }
+ }
+}
+
+/// Run the Levenberg-Marquardt fit for one parameter level and write the
+/// result back into `parms`. Mirrors `ProjectionLSQ::lsq`.
+fn lsq(proj: &dyn Projection, hills: &[Hill], known: &[usize], parms: &mut ViewParams, level: usize) {
+ let np = np_for_level(level);
+ let data = FitData {
+ proj,
+ hills,
+ known,
+ level,
+ parms0: *parms,
+ };
+
+ let mut x = vec![
+ parms.a_center,
+ parms.a_pitch,
+ parms.a_roll,
+ parms.scale,
+ parms.k0,
+ parms.k1,
+ parms.x0,
+ ];
+ x.truncate(np);
+
+ lm(&data, &mut x, np);
+
+ parms.a_center = x[0];
+ parms.a_pitch = x[1];
+ if level > 0 {
+ parms.a_roll = x[2];
+ parms.scale = x[3];
+ }
+ if level > 1 {
+ parms.k0 = x[4];
+ parms.k1 = x[5];
+ parms.x0 = x[6];
+ }
+}
+
+/// Fit the view parameters to the known hills.
+/// Mirrors `ProjectionLSQ::comp_params`. `known` indexes into `hills`.
+/// With `fit_distortion == false` the lens distortion parameters
+/// (k0/k1/x0) are held at their current values and only the viewpoint
+/// parameters are fitted, even for 4+ known hills.
+pub fn comp_params(
+ proj: &dyn Projection,
+ hills: &[Hill],
+ known: &[usize],
+ parms: &mut ViewParams,
+ fit_distortion: bool,
+) -> Result<(), String> {
+ let n = known.len();
+ if n < 1 {
+ return Err("Please position at least 1 hill".to_string());
+ }
+
+ let m1 = &hills[known[0]];
+ let mut new_parms = *parms;
+ new_parms.a_center = m1.alph;
+ new_parms.a_pitch = 0.0;
+
+ if n > 1 {
+ let m2 = &hills[known[1]];
+ let scale_tmp = (m2.x - m1.x).abs() / (m2.alph - m1.alph).abs();
+ if scale_tmp.is_nan() || scale_tmp < 50.0 {
+ return Err(format!("Could not determine initial scale value ({scale_tmp})"));
+ }
+ new_parms.scale = scale_tmp;
+ new_parms.a_roll = 0.0;
+ }
+
+ if n > 3 && fit_distortion {
+ new_parms.k0 = 0.0;
+ new_parms.k1 = 0.0;
+ new_parms.x0 = 0.0;
+ eprintln!("Performing calibration");
+ }
+
+ let level = if n == 1 { 0 } else { 1 };
+ lsq(proj, hills, known, &mut new_parms, level);
+ if n >= 4 && fit_distortion {
+ lsq(proj, hills, known, &mut new_parms, 2);
+ }
+
+ if new_parms.scale.is_nan() || new_parms.scale < 50.0 {
+ return Err("Could not determine reasonable view parameters".to_string());
+ }
+
+ *parms = new_parms;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::hill::Hill;
+ use crate::projection::{Projection, ProjectionType, ViewParams, projection_from_type};
+
+ /// Five observed hills projected with known parameters (incl. distortion).
+ fn fixture() -> (Box<dyn Projection>, Vec<Hill>, Vec<usize>, ViewParams) {
+ let proj = projection_from_type(ProjectionType::Rectilinear);
+ let truth = ViewParams {
+ a_center: 0.3,
+ scale: 2000.0,
+ a_pitch: 0.05,
+ a_roll: 0.0,
+ k0: -0.0012,
+ k1: 2e-5,
+ x0: 0.0,
+ };
+ let mut hills = Vec::new();
+ let mut known = Vec::new();
+ for i in 0..5 {
+ let alph = 0.15 + i as f64 * 0.07;
+ let elev = 0.02 + i as f64 * 0.015;
+ let (x, y) = proj.get_coordinates(alph, elev, &truth);
+ let mut h = Hill::new(&format!("h{i}"), 0.0, 0.0, 1000.0);
+ h.alph = alph;
+ h.a_elev = elev;
+ h.x = x;
+ h.y = y;
+ hills.push(h);
+ known.push(i);
+ }
+ (proj, hills, known, truth)
+ }
+
+ #[test]
+ fn locked_distortion_stays_untouched() {
+ let (proj, hills, known, truth) = fixture();
+ let mut p = truth;
+ p.a_center -= 0.01;
+ p.scale *= 1.02;
+ comp_params(&*proj, &hills, &known, &mut p, false).unwrap();
+ // 5 known hills, but the distortion params must not move
+ assert_eq!(p.k0, truth.k0);
+ assert_eq!(p.k1, truth.k1);
+ assert_eq!(p.x0, truth.x0);
+ // the viewpoint params did fit
+ assert!((p.a_center - truth.a_center).abs() < 1e-6);
+ }
+
+ #[test]
+ fn unlocked_distortion_fit_recovers() {
+ let (proj, hills, known, truth) = fixture();
+ let mut p = truth;
+ p.k0 = 0.0;
+ p.k1 = 0.0;
+ p.a_center -= 0.01;
+ p.scale *= 1.02;
+ comp_params(&*proj, &hills, &known, &mut p, true).unwrap();
+ assert!((p.k0 - truth.k0).abs() < 1e-5, "k0={}", p.k0);
+ assert!((p.k1 - truth.k1).abs() < 1e-7, "k1={}", p.k1);
+ }
+}
diff --git a/crates/gipfel-core/src/metadata.rs b/crates/gipfel-core/src/metadata.rs
new file mode 100644
index 0000000..e2a888f
--- /dev/null
+++ b/crates/gipfel-core/src/metadata.rs
@@ -0,0 +1,697 @@
+//! Image metadata: the `gipfel:` JPEG comment (read/write, byte-compatible
+//! with the C version) plus EXIF fallback (GPS position, focal length).
+//!
+//! Faithful port of gipfel's `ImageMetaData.cxx`.
+
+use std::fs::File;
+use std::io::{BufReader, Write};
+
+use exif::{In, Tag, Value};
+
+pub const NAN: f64 = f64::NAN;
+
+#[derive(Clone, Debug)]
+pub struct ImageMetaData {
+ pub manufacturer: Option<String>,
+ pub model: Option<String>,
+ pub longitude: f64,
+ pub latitude: f64,
+ pub height: f64,
+ pub direction: f64,
+ /// camera pitch, degrees (written as the "nick" key in the gipfel:
+ /// comment for backwards compatibility with the C original)
+ pub pitch: f64,
+ /// camera roll, degrees (written as the "tilt" key for compatibility)
+ pub roll: f64,
+ pub k0: f64,
+ pub k1: f64,
+ pub x0: f64,
+ pub focal_length: f64,
+ pub focal_length_35mm: f64,
+ pub projection_type: i32,
+}
+
+impl Default for ImageMetaData {
+ fn default() -> Self {
+ ImageMetaData {
+ manufacturer: None,
+ model: None,
+ longitude: NAN,
+ latitude: NAN,
+ height: NAN,
+ direction: NAN,
+ pitch: NAN,
+ roll: NAN,
+ k0: NAN,
+ k1: NAN,
+ x0: NAN,
+ focal_length: NAN,
+ focal_length_35mm: NAN,
+ projection_type: 0,
+ }
+ }
+}
+
+impl ImageMetaData {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Read JPEG comment first, then fill remaining gaps from EXIF.
+ pub fn load_image(&mut self, path: &str) {
+ *self = ImageMetaData::new();
+ if let Some(com) = read_jpeg_comment(path) {
+ self.parse_gipfel_comment(&com);
+ }
+ self.load_image_exif(path);
+ }
+
+ /// Write the gipfel comment into a copy of `in_img`, saved as `out_img`.
+ /// Writes atomically via a temp file, like the C original.
+ pub fn save_image(&self, in_img: &str, out_img: &str) -> std::io::Result<()> {
+ let comment = self.format_comment();
+ let tmp = format!("{}.tmp", out_img);
+ write_jpeg_comment(in_img, &tmp, comment.as_bytes())?;
+ std::fs::rename(&tmp, out_img)?;
+ Ok(())
+ }
+
+ pub fn format_comment(&self) -> String {
+ format!(
+ "gipfel: longitude {}, latitude {}, height {}, direction {}, nick {}, tilt {}, focal_length_35mm {}, projection type {}, k0 {}, k1 {}, x0 {}",
+ fmt_f64(self.longitude),
+ fmt_f64(self.latitude),
+ fmt_f64(self.height),
+ fmt_f64(self.direction),
+ fmt_f64(self.pitch),
+ fmt_f64(self.roll),
+ fmt_f64(self.focal_length_35mm),
+ self.projection_type,
+ fmt_f64(self.k0),
+ fmt_f64(self.k1),
+ fmt_f64(self.x0),
+ )
+ }
+
+ /// sscanf(gipfel_format_scan, ...) equivalent; returns true when at
+ /// least 8 fields were matched (the C requirement).
+ pub fn parse_gipfel_comment(&mut self, com: &[u8]) -> bool {
+ let s = String::from_utf8_lossy(com);
+ let mut sc = Scanner { s: s.as_ref() };
+
+ if !sc.lit("gipfel: longitude ") {
+ return false;
+ }
+ let Some(lo) = sc.f64() else { return false };
+ if !sc.lit(", latitude ") { return false; }
+ let la = sc.f64();
+ if la.is_none() { return false; }
+ if !sc.lit(", height ") { return false; }
+ let he = sc.f64();
+ if he.is_none() { return false; }
+ if !sc.lit(", direction ") { return false; }
+ let dir = sc.f64();
+ if dir.is_none() { return false; }
+ if !sc.lit(", nick ") { return false; }
+ let pit = sc.f64();
+ if pit.is_none() { return false; }
+ if !sc.lit(", tilt ") { return false; }
+ let rol = sc.f64();
+ if rol.is_none() { return false; }
+ if !sc.lit(", focal_length_35mm ") { return false; }
+ let fr = sc.f64();
+ if fr.is_none() { return false; }
+ if !sc.lit(", projection type ") { return false; }
+ let pt = sc.i32();
+
+ // 8 matches required: through projection type
+ let Some(pt) = pt else { return false };
+
+ self.longitude = lo;
+ self.latitude = la.unwrap();
+ self.height = he.unwrap();
+ self.direction = dir.unwrap();
+ self.pitch = pit.unwrap();
+ self.roll = rol.unwrap();
+ self.focal_length_35mm = fr.unwrap();
+ self.projection_type = pt;
+
+ // n >= 10: k0 and k1 were matched; x0 defaults to 0.0 when only 10
+ if let Some(k0) = sc.f64_after(", k0 ") {
+ if let Some(k1) = sc.f64_after(", k1 ") {
+ self.k0 = k0;
+ self.k1 = k1;
+ self.x0 = sc.f64_after(", x0 ").unwrap_or(0.0);
+ }
+ }
+
+ true
+ }
+
+ fn load_image_exif(&mut self, path: &str) {
+ let Ok(file) = File::open(path) else { return };
+ let mut reader = BufReader::new(file);
+ let Ok(exif) = exif::Reader::new().read_from_container(&mut reader) else {
+ return;
+ };
+
+ if self.manufacturer.is_none() {
+ if let Some(f) = exif.get_field(Tag::Make, In::PRIMARY) {
+ let s = field_string(f);
+ if !s.is_empty() {
+ self.manufacturer = Some(s);
+ }
+ }
+ }
+ if self.model.is_none() {
+ if let Some(f) = exif.get_field(Tag::Model, In::PRIMARY) {
+ let s = field_string(f);
+ if !s.is_empty() {
+ self.model = Some(s);
+ }
+ }
+ }
+
+ if self.focal_length.is_nan() {
+ exif_set_value(self, &exif, Tag::FocalLength, In::PRIMARY, |md, v| md.focal_length = v);
+ }
+ if self.focal_length_35mm.is_nan() {
+ exif_set_value(
+ self,
+ &exif,
+ Tag::FocalLengthIn35mmFilm,
+ In::PRIMARY,
+ |md, v| md.focal_length_35mm = v,
+ );
+ }
+ if self.longitude.is_nan() {
+ exif_set_coordinate(self, &exif, Tag::GPSLongitude, In::PRIMARY, |md, v| md.longitude = v);
+ if !self.longitude.is_nan() && exif_ref_is(&exif, Tag::GPSLongitudeRef, "W") {
+ self.longitude = -self.longitude;
+ }
+ }
+ if self.latitude.is_nan() {
+ exif_set_coordinate(self, &exif, Tag::GPSLatitude, In::PRIMARY, |md, v| md.latitude = v);
+ if !self.latitude.is_nan() && exif_ref_is(&exif, Tag::GPSLatitudeRef, "S") {
+ self.latitude = -self.latitude;
+ }
+ }
+ if self.height.is_nan() {
+ exif_set_value(self, &exif, Tag::GPSAltitude, In::PRIMARY, |md, v| md.height = v);
+ // GPSAltitudeRef: 0 = above sea level, 1 = below
+ if !self.height.is_nan() {
+ if let Some(f) = exif.get_field(Tag::GPSAltitudeRef, In::PRIMARY) {
+ if let Value::Byte(b) = &f.value {
+ if b.first() == Some(&1) {
+ self.height = -self.height;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/// True if an EXIF ASCII reference tag (GPSLatitudeRef etc.) equals `val`.
+fn exif_ref_is(exif: &exif::Exif, tag: Tag, val: &str) -> bool {
+ exif.get_field(tag, In::PRIMARY)
+ .and_then(|f| match &f.value {
+ Value::Ascii(a) => a.first().map(|v| String::from_utf8_lossy(v).trim() == val),
+ _ => None,
+ })
+ .unwrap_or(false)
+}
+
+fn fmt_f64(v: f64) -> String {
+ if v.is_nan() {
+ // C printf("%f", nan) prints "nan"; keep it parseable by both
+ "nan".to_string()
+ } else {
+ format!("{:.6}", v)
+ }
+}
+
+fn field_string(f: &exif::Field) -> String {
+ let join = |v: &[String]| v.join(" ");
+ match &f.value {
+ Value::Ascii(a) => a
+ .first()
+ .map(|v| String::from_utf8_lossy(v).trim().to_string())
+ .unwrap_or_default(),
+ Value::Rational(r) => join(r.iter().map(|v| v.to_string()).collect::<Vec<_>>().as_slice()),
+ Value::Short(v) => join(v.iter().map(|v| v.to_string()).collect::<Vec<_>>().as_slice()),
+ Value::Long(v) => join(v.iter().map(|v| v.to_string()).collect::<Vec<_>>().as_slice()),
+ Value::SShort(v) => join(v.iter().map(|v| v.to_string()).collect::<Vec<_>>().as_slice()),
+ Value::SLong(v) => join(v.iter().map(|v| v.to_string()).collect::<Vec<_>>().as_slice()),
+ _ => String::new(),
+ }
+}
+
+/// exifSetValue: assign when the tag exists and its value is >= 0.
+fn exif_set_value<F>(md: &mut ImageMetaData, exif: &exif::Exif, tag: Tag, inp: In, set: F)
+where
+ F: FnOnce(&mut ImageMetaData, f64),
+{
+ if let Some(f) = exif.get_field(tag, inp) {
+ if let Some(v) = field_tofloat(f, 0) {
+ if v >= 0.0 {
+ set(md, v);
+ }
+ }
+ }
+}
+
+/// exifSetCoordinate: degrees + minutes/60 + seconds/3600, each component
+/// only used when >= 0 (faithful to the C original, incl. ignoring N/S refs).
+fn exif_set_coordinate<F>(md: &mut ImageMetaData, exif: &exif::Exif, tag: Tag, inp: In, set: F)
+where
+ F: FnOnce(&mut ImageMetaData, f64),
+{
+ let Some(f) = exif.get_field(tag, inp) else {
+ return;
+ };
+
+ let mut dest: Option<f64> = None;
+ for i in 0..3 {
+ if let Some(v) = field_tofloat(f, i) {
+ if v >= 0.0 {
+ dest = Some(match i {
+ 0 => v,
+ 1 => dest.unwrap_or(0.0) + v / 60.0,
+ _ => dest.unwrap_or(0.0) + v / 3600.0,
+ });
+ }
+ }
+ }
+ if let Some(v) = dest {
+ set(md, v);
+ }
+}
+
+fn field_tofloat(f: &exif::Field, idx: usize) -> Option<f64> {
+ match &f.value {
+ Value::Rational(r) => r.get(idx).map(|v| v.num as f64 / v.denom as f64),
+ Value::Short(v) => v.get(idx).map(|v| *v as f64),
+ Value::Long(v) => v.get(idx).map(|v| *v as f64),
+ Value::SShort(v) => v.get(idx).map(|v| *v as f64),
+ Value::SLong(v) => v.get(idx).map(|v| *v as f64),
+ _ => None,
+ }
+}
+
+// ---------------------------------------------------------------------------
+// strtod-like scanner for the sscanf format
+// ---------------------------------------------------------------------------
+
+struct Scanner<'a> {
+ s: &'a str,
+}
+
+impl Scanner<'_> {
+ fn skip_ws(&mut self) {
+ self.s = self.s.trim_start();
+ }
+
+ fn lit(&mut self, lit: &str) -> bool {
+ if self.s.starts_with(lit) {
+ self.s = &self.s[lit.len()..];
+ true
+ } else {
+ false
+ }
+ }
+
+ /// %lf: skip whitespace, parse the longest numeric prefix.
+ fn f64(&mut self) -> Option<f64> {
+ self.skip_ws();
+ for i in (1..=self.s.len()).rev() {
+ if let Ok(v) = self.s[..i].parse::<f64>() {
+ self.s = &self.s[i..];
+ return Some(v);
+ }
+ }
+ None
+ }
+
+ fn f64_after(&mut self, lit: &str) -> Option<f64> {
+ if self.lit(lit) {
+ self.f64()
+ } else {
+ None
+ }
+ }
+
+ /// %d
+ fn i32(&mut self) -> Option<i32> {
+ self.skip_ws();
+ for i in (1..=self.s.len()).rev() {
+ if let Ok(v) = self.s[..i].parse::<i32>() {
+ self.s = &self.s[i..];
+ return Some(v);
+ }
+ }
+ None
+ }
+}
+
+// ---------------------------------------------------------------------------
+// JPEG COM segment reading / writing
+// ---------------------------------------------------------------------------
+
+fn marker_len_ok(marker: u8) -> bool {
+ // standalone markers without length field
+ marker == 0x01 || (0xD0..=0xD9).contains(&marker)
+}
+
+/// Return the payload of the first COM segment before SOS, if any.
+pub fn read_jpeg_comment(path: &str) -> Option<Vec<u8>> {
+ let bytes = std::fs::read(path).ok()?;
+ if bytes.len() < 2 || bytes[0] != 0xFF || bytes[1] != 0xD8 {
+ return None;
+ }
+
+ let mut i = 2;
+ while i + 1 < bytes.len() {
+ if bytes[i] != 0xFF {
+ return None;
+ }
+ let marker = bytes[i + 1];
+ if marker == 0xFF {
+ i += 1;
+ continue;
+ }
+ if marker == 0xDA || marker == 0xD9 {
+ return None;
+ }
+ if marker_len_ok(marker) {
+ i += 2;
+ continue;
+ }
+ if i + 4 > bytes.len() {
+ return None;
+ }
+ let len = (u16::from_be_bytes([bytes[i + 2], bytes[i + 3]]) as usize).checked_add(2)?;
+ if i + len > bytes.len() {
+ return None;
+ }
+ if marker == 0xFE {
+ return Some(bytes[i + 4..i + 2 + len - 2].to_vec());
+ }
+ i += len;
+ }
+ None
+}
+
+/// Copy `in_img` to `out_img`, dropping all COM segments and inserting a
+/// fresh COM segment right after SOI.
+pub fn write_jpeg_comment(in_img: &str, out_img: &str, comment: &[u8]) -> std::io::Result<()> {
+ let bytes = std::fs::read(in_img)?;
+ if bytes.len() < 2 || bytes[0] != 0xFF || bytes[1] != 0xD8 {
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "not a JPEG file"));
+ }
+ if comment.len() + 2 > 0xFFFF {
+ return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "comment too long"));
+ }
+
+ let mut out: Vec<u8> = Vec::with_capacity(bytes.len() + comment.len() + 4);
+ out.extend_from_slice(&bytes[..2]); // SOI
+
+ // Copy the SOI-adjacent header region (APPn/COM/DPI/etc.), dropping COM
+ // segments, and insert our fresh COM after the last APPn segment:
+ // that is where exiv2 (and thus the C gipfel) expects it.
+ let mut i = 2;
+ let mut insert_at = out.len();
+ let mut copy_rest_from = bytes.len();
+ while i + 1 < bytes.len() {
+ if bytes[i] != 0xFF {
+ break;
+ }
+ let marker = bytes[i + 1];
+ if marker == 0xFF {
+ i += 1;
+ continue;
+ }
+ if marker == 0xDA || marker == 0xD9 {
+ copy_rest_from = i;
+ break;
+ }
+ if marker_len_ok(marker) {
+ out.push(0xFF);
+ out.push(marker);
+ i += 2;
+ continue;
+ }
+ if i + 4 > bytes.len() {
+ break;
+ }
+ let len = match u16::from_be_bytes([bytes[i + 2], bytes[i + 3]]) {
+ l => (l as usize).checked_add(2).unwrap_or(usize::MAX),
+ };
+ if len < 2 || i + len > bytes.len() {
+ break;
+ }
+ if marker != 0xFE {
+ out.extend_from_slice(&bytes[i..i + len]);
+ if (0xE0..=0xEF).contains(&marker) {
+ insert_at = out.len();
+ }
+ }
+ i += len;
+ }
+ copy_rest_from = copy_rest_from.min(i.max(2));
+
+ let mut com: Vec<u8> = Vec::with_capacity(comment.len() + 4);
+ com.push(0xFF);
+ com.push(0xFE);
+ com.extend_from_slice(&((comment.len() + 2) as u16).to_be_bytes());
+ com.extend_from_slice(comment);
+ out.splice(insert_at..insert_at, com);
+
+ out.extend_from_slice(&bytes[copy_rest_from..]);
+
+ let mut f = File::create(out_img)?;
+ f.write_all(&out)?;
+ f.flush()
+}
+
+/// Read raw image dimensions (width, height) of a JPEG.
+pub fn image_dimensions(path: &str) -> Option<(u32, u32)> {
+ image::image_dimensions(path).ok()
+}
+
+/// Apply image metadata to a panorama like `GipfelWidget::load_image` does.
+/// Returns false when the gipfel info is incomplete (C: `have_gipfel_info`).
+pub fn apply_image_metadata(
+ pan: &mut crate::panorama::Panorama,
+ md: &ImageMetaData,
+ img_w: u32,
+ img_h: u32,
+) -> bool {
+ use crate::projection::ProjectionType;
+
+ let mut have_info = true;
+
+ pan.set_view_long(md.longitude);
+ pan.set_view_lat(md.latitude);
+ pan.set_view_height(md.height);
+
+ pan.set_projection(match md.projection_type {
+ 1 => ProjectionType::Cylindrical,
+ _ => ProjectionType::Rectilinear,
+ });
+
+ if md.direction.is_nan() {
+ pan.set_center_angle(0.0);
+ have_info = false;
+ } else {
+ pan.set_center_angle(md.direction);
+ }
+
+ if md.pitch.is_nan() {
+ pan.set_pitch_angle(0.0);
+ have_info = false;
+ } else {
+ pan.set_pitch_angle(md.pitch);
+ }
+
+ if md.roll.is_nan() {
+ pan.set_roll_angle(0.0);
+ have_info = false;
+ } else {
+ pan.set_roll_angle(md.roll);
+ }
+
+ // set_focal_length_35mm: scale = fl * max(w, h) / 35
+ let w = img_w.max(img_h);
+ if md.focal_length_35mm.is_nan() || md.focal_length_35mm == 0.0 {
+ pan.set_scale(35.0 * w as f64 / 35.0);
+ have_info = false;
+ } else {
+ pan.set_scale(md.focal_length_35mm * w as f64 / 35.0);
+ }
+
+ // distortion parameters, C ordering:
+ // 1. gipfel data in JPEG comment
+ // 2. matching distortion profile (camera + focal length)
+ // 3. 0.0, 0.0, 0.0
+ let (mut k0, mut k1, mut x0) = (md.k0, md.k1, md.x0);
+ if k0.is_nan() {
+ if let Some(p) = md
+ .manufacturer
+ .as_deref()
+ .zip(md.model.as_deref())
+ .and_then(|(mfr, model)| crate::profile::profile_name(mfr, model, md.focal_length))
+ .and_then(|name| crate::profile::load(&name).ok())
+ {
+ k0 = p.k0;
+ k1 = p.k1;
+ x0 = p.x0;
+ }
+ }
+ if k0.is_nan() {
+ k0 = 0.0;
+ }
+ if k1.is_nan() {
+ k1 = 0.0;
+ }
+ if x0.is_nan() {
+ x0 = 0.0;
+ }
+ pan.set_distortion_params(k0, k1, x0);
+
+ have_info
+}
+
+/// The profile name auto-matched from this image's camera and focal length
+/// (C `get_distortion_profile_name`). Suggested whenever the camera is
+/// known — [`apply_image_metadata`] only *applies* it when the JPEG
+/// comment carries no distortion params.
+pub fn distortion_profile_name(md: &ImageMetaData) -> Option<String> {
+ md.manufacturer
+ .as_deref()
+ .zip(md.model.as_deref())
+ .and_then(|(mfr, model)| crate::profile::profile_name(mfr, model, md.focal_length))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn jpeg_comment_write_read() {
+ let dir = std::env::temp_dir().join("gipfel-core-mdtest");
+ std::fs::create_dir_all(&dir).unwrap();
+ let img = dir.join("t.jpg");
+ let out = dir.join("o.jpg");
+
+ image::DynamicImage::new_rgb8(64, 48)
+ .grayscale()
+ .save(&img)
+ .unwrap();
+
+ let comment = b"gipfel: longitude 1.000000, latitude 2.000000, height 3.000000, direction 4.000000, nick 5.000000, tilt 6.000000, focal_length_35mm 7.000000, projection type 0, k0 0.000000, k1 0.000000, x0 0.000000";
+ write_jpeg_comment(img.to_str().unwrap(), out.to_str().unwrap(), comment).unwrap();
+
+ // no COM before, one after
+ assert!(read_jpeg_comment(img.to_str().unwrap()).is_none());
+ assert_eq!(read_jpeg_comment(out.to_str().unwrap()).unwrap(), comment);
+
+ // rewriting replaces the existing comment
+ let out2 = dir.join("o2.jpg");
+ write_jpeg_comment(out.to_str().unwrap(), out2.to_str().unwrap(), b"short").unwrap();
+ assert_eq!(read_jpeg_comment(out2.to_str().unwrap()).unwrap(), b"short");
+
+ // image still decodes
+ assert!(image::image_dimensions(out.to_str().unwrap()).is_ok());
+
+ let _ = std::fs::remove_dir_all(&dir);
+ }
+
+ #[test]
+ fn distortion_precedence_and_legacy_import() {
+ use crate::panorama::Panorama;
+ use crate::profile;
+
+ let dir = std::env::temp_dir().join(format!("gipfel-prec-{}", std::process::id()));
+ let _ = std::fs::remove_dir_all(&dir);
+ let data = dir.join("data");
+ let home = dir.join("home");
+ std::fs::create_dir_all(&data).unwrap();
+
+ let old = (std::env::var("HOME").ok(), std::env::var("XDG_DATA_HOME").ok());
+ std::env::set_var("HOME", &home);
+ std::env::set_var("XDG_DATA_HOME", &data);
+
+ // legacy FLTK prefs file (C version's location and format)
+ let legacy = home
+ .join(".fltk")
+ .join("Johannes.HofmannATgmx.de")
+ .join("gipfel")
+ .join("DistortionProfiles.prefs");
+ std::fs::create_dir_all(legacy.parent().unwrap()).unwrap();
+ std::fs::write(
+ &legacy,
+ "; FLTK preferences file format 1.0\n; vendor: Johannes.HofmannATgmx.de\n\
+ ; application: gipfel/DistortionProfiles\n\n[.]\n\n\n\
+ [./Canon_EOS_5D_50.00_mm]\n\nk0:-0.01\nk1:0.001\nx0:3\n",
+ )
+ .unwrap();
+
+ // first access imports into the XDG data dir
+ assert!(!profile::prefs_path().exists());
+ let all = profile::list();
+ assert!(all.iter().any(|(n, p)| n == "Canon_EOS_5D_50.00_mm" && p.k0 == -0.01));
+ assert!(profile::prefs_path().starts_with(&data));
+ assert!(profile::prefs_path().exists());
+
+ let mut md = ImageMetaData::new();
+ md.manufacturer = Some("Canon".into());
+ md.model = Some("EOS_5D".into());
+ md.focal_length = 50.0;
+ let mut pan = Panorama::new();
+
+ // 1. comment wins when present
+ md.k0 = -0.9;
+ md.k1 = 0.0;
+ md.x0 = 0.0;
+ apply_image_metadata(&mut pan, &md, 1000, 800);
+ assert!((pan.parms.k0 + 0.9).abs() < 1e-12, "comment should win");
+
+ // 2. profile used when comment absent
+ md.k0 = f64::NAN;
+ md.k1 = f64::NAN;
+ md.x0 = f64::NAN;
+ apply_image_metadata(&mut pan, &md, 1000, 800);
+ assert!(
+ (pan.parms.k0 + 0.01).abs() < 1e-12,
+ "profile should apply: k0={}",
+ pan.parms.k0
+ );
+ // name is suggested regardless of the comment (C save dialog)
+ assert_eq!(
+ distortion_profile_name(&md).as_deref(),
+ Some("Canon_EOS_5D_50.00_mm")
+ );
+ md.k0 = 0.0;
+ assert_eq!(
+ distortion_profile_name(&md).as_deref(),
+ Some("Canon_EOS_5D_50.00_mm")
+ );
+
+ // 3. zero when neither
+ md.k0 = f64::NAN;
+ md.manufacturer = None;
+ apply_image_metadata(&mut pan, &md, 1000, 800);
+ assert_eq!(pan.parms.k0, 0.0);
+
+ match old.0 {
+ Some(h) => std::env::set_var("HOME", h),
+ None => std::env::remove_var("HOME"),
+ }
+ match old.1 {
+ Some(h) => std::env::set_var("XDG_DATA_HOME", h),
+ None => std::env::remove_var("XDG_DATA_HOME"),
+ }
+ let _ = std::fs::remove_dir_all(&dir);
+ }
+}
diff --git a/crates/gipfel-core/src/panorama.rs b/crates/gipfel-core/src/panorama.rs
new file mode 100644
index 0000000..9e27251
--- /dev/null
+++ b/crates/gipfel-core/src/panorama.rs
@@ -0,0 +1,502 @@
+//! Panorama: viewpoint, hill visibility (occlusion, refraction) and the
+//! link between geographic coordinates and image coordinates.
+//!
+//! Faithful port of gipfel's `Panorama.cxx`.
+
+use crate::hill::{Hill, DEG2RAD, PI};
+use crate::projection::{projection_from_type, Projection, ProjectionType, ViewParams};
+
+const EARTH_RADIUS: f64 = 6371000.785;
+
+pub struct Panorama {
+ pub hills: Vec<Hill>,
+ /// indices into `hills`, sorted by alpha descending (rebuilt by update_angles)
+ pub alpha_order: Vec<usize>,
+ /// indices into `hills`, sorted by alpha descending
+ pub close: Vec<usize>,
+ /// indices into `hills`
+ pub visible: Vec<usize>,
+
+ pub view_name: Option<String>,
+ /// latitude, radians
+ pub view_phi: f64,
+ /// longitude, radians
+ pub view_lam: f64,
+ /// meters
+ pub view_height: f64,
+
+ pub height_dist_ratio: f64,
+ pub hide_value: f64,
+ /// Additional visibility limit: maximum angular distance (radians) from
+ /// the image center. The app sets this to the angle the projection maps
+ /// to the image edge (the lens' actual field of view, including fitted
+ /// distortion), so that directions outside the photo — which a distortion
+ /// model can fold back into the image — neither get markers nor compass
+ /// ticks. 0 = no additional limit (only the projection's own limit).
+ pub max_view_angle: f64,
+
+ pub parms: ViewParams,
+ pub projection_type: ProjectionType,
+ proj: Box<dyn Projection>,
+}
+
+impl Default for Panorama {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Panorama {
+ pub fn new() -> Self {
+ Panorama {
+ hills: Vec::new(),
+ alpha_order: Vec::new(),
+ close: Vec::new(),
+ visible: Vec::new(),
+ view_name: None,
+ view_phi: 0.0,
+ view_lam: 0.0,
+ view_height: 0.0,
+ height_dist_ratio: 0.07,
+ hide_value: 1.2,
+ max_view_angle: 0.0,
+ parms: ViewParams::new(),
+ projection_type: ProjectionType::Rectilinear,
+ proj: projection_from_type(ProjectionType::Rectilinear),
+ }
+ }
+
+ pub fn load_data(&mut self, path: &str) -> std::io::Result<()> {
+ let mut hills = crate::hill::load(path)?;
+ crate::hill::mark_duplicates(&mut hills, 0.00001);
+ self.hills = hills;
+ self.update_angles();
+ Ok(())
+ }
+
+ pub fn add_hills(&mut self, extra: &[Hill]) {
+ self.hills.extend_from_slice(extra);
+ crate::hill::mark_duplicates(&mut self.hills, 0.00001);
+ self.update_angles();
+ }
+
+ pub fn remove_hills(&mut self, flags: u32) {
+ self.hills.retain(|m| m.flags & flags == 0);
+ // indices shifted; rebuild all derived lists
+ self.alpha_order.clear();
+ self.close.clear();
+ self.visible.clear();
+ }
+
+ pub fn set_viewpoint(&mut self, idx: Option<usize>) {
+ let Some(idx) = idx else { return };
+ let m = &self.hills[idx];
+ self.view_phi = m.phi;
+ self.view_lam = m.lam;
+ self.view_height = m.height;
+ self.view_name = Some(m.name.clone());
+ self.update_angles();
+ }
+
+ /// Fit view parameters to the known hills (indices into `self.hills`).
+ /// `fit_distortion` enables the 7-parameter fit (k0/k1/x0) for 4+ hills;
+ /// otherwise the distortion params stay locked at their current values.
+ pub fn comp_params(&mut self, known: &[usize], fit_distortion: bool) -> Result<(), String> {
+ let ret = crate::lsq::comp_params(&*self.proj, &self.hills, known, &mut self.parms, fit_distortion);
+ if ret.is_ok() {
+ self.update_visible_mountains(Some(known));
+ }
+ ret
+ }
+
+ /// Like [`Panorama::comp_params`] but the observed hills are an external
+ /// slice (used by the golden fit tests, mirroring the C harness).
+ pub fn comp_params_with(&mut self, hills: &[Hill], known: &[usize], fit_distortion: bool) -> Result<(), String> {
+ let ret = crate::lsq::comp_params(&*self.proj, hills, known, &mut self.parms, fit_distortion);
+ if ret.is_ok() {
+ self.update_visible_mountains(None);
+ }
+ ret
+ }
+
+ pub fn set_center_angle(&mut self, deg: f64) {
+ self.parms.a_center = deg * DEG2RAD;
+ self.update_visible_mountains(None);
+ }
+
+ pub fn set_pitch_angle(&mut self, deg: f64) {
+ self.parms.a_pitch = deg * DEG2RAD;
+ self.update_coordinates(None);
+ }
+
+ pub fn set_roll_angle(&mut self, deg: f64) {
+ self.parms.a_roll = deg * DEG2RAD;
+ self.update_coordinates(None);
+ }
+
+ pub fn set_scale(&mut self, s: f64) {
+ self.parms.scale = s;
+ self.update_coordinates(None);
+ }
+
+ pub fn set_distortion_params(&mut self, k0: f64, k1: f64, x0: f64) {
+ self.parms.k0 = k0;
+ self.parms.k1 = k1;
+ self.parms.x0 = x0;
+ self.update_coordinates(None);
+ }
+
+ pub fn set_height_dist_ratio(&mut self, r: f64) {
+ self.height_dist_ratio = r;
+ self.update_close_mountains();
+ }
+
+ pub fn set_view_long(&mut self, deg: f64) {
+ self.view_lam = deg * DEG2RAD;
+ self.update_angles();
+ }
+
+ pub fn set_view_lat(&mut self, deg: f64) {
+ self.view_phi = deg * DEG2RAD;
+ self.update_angles();
+ }
+
+ pub fn set_view_height(&mut self, v: f64) {
+ self.view_height = v;
+ self.update_angles();
+ }
+
+ pub fn set_projection(&mut self, t: ProjectionType) {
+ self.projection_type = t;
+ self.proj = projection_from_type(t);
+ self.update_angles();
+ }
+
+ pub fn get_center_angle(&self) -> f64 {
+ self.parms.a_center / DEG2RAD
+ }
+ pub fn get_pitch_angle(&self) -> f64 {
+ self.parms.a_pitch / DEG2RAD
+ }
+ pub fn get_roll_angle(&self) -> f64 {
+ self.parms.a_roll / DEG2RAD
+ }
+ pub fn get_scale(&self) -> f64 {
+ self.parms.scale
+ }
+ pub fn get_view_long(&self) -> f64 {
+ self.view_lam / DEG2RAD
+ }
+ pub fn get_view_lat(&self) -> f64 {
+ self.view_phi / DEG2RAD
+ }
+ pub fn get_view_height(&self) -> f64 {
+ self.view_height
+ }
+
+ /// Last hill whose name matches exactly (C keeps the last match).
+ pub fn get_pos(&self, name: &str) -> Option<usize> {
+ let mut ret = None;
+ for (i, m) in self.hills.iter().enumerate() {
+ if m.name == name {
+ ret = Some(i);
+ }
+ }
+ ret
+ }
+
+ pub fn update_angles(&mut self) {
+ for i in 0..self.hills.len() {
+ let (phi, lam) = (self.hills[i].phi, self.hills[i].lam);
+ let d = self.distance(phi, lam);
+ self.hills[i].dist = d;
+ if phi != self.view_phi || lam != self.view_lam {
+ let a = self.alpha(i);
+ self.hills[i].alph = a;
+ }
+ }
+
+ // keep `hills` in insertion order (indices stay stable) and maintain a
+ // separate alpha-descending order, mirroring the C pointer-array sort
+ let mut idx: Vec<usize> = (0..self.hills.len()).collect();
+ idx.sort_by(|&a, &b| {
+ self.hills[b]
+ .alph
+ .partial_cmp(&self.hills[a].alph)
+ .unwrap_or(std::cmp::Ordering::Equal)
+ });
+ self.alpha_order = idx;
+
+ self.update_close_mountains();
+ }
+
+ /// Mark hills that are hidden behind a nearer, taller neighbour.
+ /// Faithful port including the original's `fabs(m->alph - n->alph > pi/2)`
+ /// oddity (comparison inside fabs, i.e. skip when m.alph - n.alph > pi/2).
+ pub fn mark_hidden(&mut self, hills: &[usize]) {
+ for i in 0..hills.len() {
+ let m = hills[i];
+ self.hills[m].flags &= !crate::hill::FLAG_HIDDEN;
+
+ if self.hills[m].flags & crate::hill::FLAG_DUPLIC != 0 {
+ continue;
+ }
+
+ for j in 0..hills.len() {
+ let n = hills[j];
+ if self.hills[n].flags & crate::hill::FLAG_DUPLIC != 0
+ || self.hills[n].flags & crate::hill::FLAG_TRACK_POINT != 0
+ {
+ continue;
+ }
+ if n == m || self.hills[m].alph - self.hills[n].alph > PI / 2.0 {
+ continue;
+ }
+ if self.hills[m].dist < self.hills[n].dist
+ || self.hills[m].a_elev > self.hills[n].a_elev
+ {
+ continue;
+ }
+
+ let h = (self.hills[n].a_elev - self.hills[m].a_elev)
+ / (self.hills[m].alph - self.hills[n].alph).abs();
+ if h.is_infinite() || h > self.hide_value {
+ self.hills[m].flags |= crate::hill::FLAG_HIDDEN;
+ }
+ }
+ }
+ }
+
+ pub fn update_close_mountains(&mut self) {
+ self.close.clear();
+
+ let order = self.alpha_order.clone();
+ for &i in &order {
+ let m = &self.hills[i];
+ if m.flags & crate::hill::FLAG_TRACK_POINT != 0
+ || ((m.phi != self.view_phi || m.lam != self.view_lam)
+ && (m.height / (m.dist * EARTH_RADIUS) > self.height_dist_ratio))
+ {
+ let elev = self.elevation(i);
+ self.hills[i].a_elev = elev;
+ self.close.push(i);
+ }
+ }
+
+ let close = self.close.clone();
+ self.mark_hidden(&close);
+ self.update_visible_mountains(None);
+ }
+
+ pub fn update_visible_mountains(&mut self, excluded: Option<&[usize]>) {
+ self.visible.clear();
+
+ for k in 0..self.close.len() {
+ let i = self.close[k];
+ if self.is_visible(self.hills[i].alph) {
+ self.visible.push(i);
+ self.hills[i].flags |= crate::hill::FLAG_VISIBLE;
+ } else {
+ self.hills[i].flags &= !crate::hill::FLAG_VISIBLE;
+ }
+ }
+
+ let owned;
+ let excl: Option<&[usize]> = match excluded {
+ Some(e) => {
+ owned = e.to_vec();
+ Some(&owned)
+ }
+ None => None,
+ };
+ self.update_coordinates(excl);
+ }
+
+ pub fn update_coordinates(&mut self, excluded: Option<&[usize]>) {
+ for k in 0..self.visible.len() {
+ let i = self.visible[k];
+ if let Some(e) = excluded {
+ if e.contains(&i) {
+ continue;
+ }
+ }
+ let (alph, elev) = (self.hills[i].alph, self.hills[i].a_elev);
+ let (x, y) = self.proj.get_coordinates(alph, elev, &self.parms);
+ self.hills[i].x = x;
+ self.hills[i].y = y;
+ }
+ }
+
+ /// angular distance (fraction of earth radius) viewpoint -> (phi, lam)
+ pub fn distance(&self, phi: f64, lam: f64) -> f64 {
+ let d_lam = self.view_lam - lam;
+
+ let t1 = (phi.cos() * d_lam.sin()).powi(2);
+ let t2 = (self.view_phi.cos() * phi.sin()
+ - self.view_phi.sin() * phi.cos() * d_lam.cos())
+ .powi(2);
+
+ (t1 + t2).sqrt().atan2(
+ self.view_phi.sin() * phi.sin() + self.view_phi.cos() * phi.cos() * d_lam.cos(),
+ )
+ }
+
+ pub fn alpha(&self, idx: usize) -> f64 {
+ let m = &self.hills[idx];
+ let sin_alph = (m.lam - self.view_lam).sin() * m.phi.cos() / m.dist.sin();
+ let cos_alph =
+ (m.phi.sin() - self.view_phi.sin() * m.dist.cos()) / (self.view_phi.cos() * m.dist.sin());
+
+ (sin_alph.atan2(cos_alph) + 2.0 * PI) % (2.0 * PI)
+ }
+
+ /// approximation of refraction effect as described by Tom Chester at
+ /// <http://tchester.org/sgm/analysis/peaks/refraction.html>
+ pub fn refraction(&self, idx: usize) -> f64 {
+ let (alpha, t0) = (6.5, 10.0);
+
+ let a = 2.9e-4 * (-self.view_height / 10000.0).exp() / (1.0 + 2.9 * t0 / 760.0);
+ let b = 2.9 * alpha / (760.0 * (1.0 + 2.9 * t0 / 760.0));
+ let c = a * (b - 1.0 / 10.0);
+
+ c * self.get_real_distance(idx) / (2000.0 * (1.0 + a))
+ }
+
+ pub fn elevation(&self, idx: usize) -> f64 {
+ let m = &self.hills[idx];
+ let theta = self.refraction(idx);
+
+ let b = m.height + self.get_earth_radius(m.phi);
+ let c = self.view_height + self.get_earth_radius(self.view_phi);
+
+ ((m.dist.cos() * b - c) / (m.dist.sin() * b)).atan() - theta
+ }
+
+ /// local distance to center of WGS84 ellipsoid
+ pub fn get_earth_radius(&self, phi: f64) -> f64 {
+ let a = 6378137.000f64;
+ let b = 6356752.315f64;
+ let ata = phi.tan();
+
+ a * (ata.powi(2) + 1.0).powf(1.0 / 2.0)
+ * b.abs()
+ * (b.powi(2) + a.powi(2) * ata.powi(2)).powf(-1.0 / 2.0)
+ }
+
+ /// real (line-of-sight) distance in meters
+ pub fn get_real_distance(&self, idx: usize) -> f64 {
+ let m = &self.hills[idx];
+ let a = self.view_height + self.get_earth_radius(self.view_phi);
+ let b = m.height + self.get_earth_radius(m.phi);
+ let gam = m.dist;
+
+ (a.powi(2) + b.powi(2) - 2.0 * a * b * gam.cos()).sqrt()
+ }
+
+ pub fn is_visible(&self, alph: f64) -> bool {
+ let mut center_dist = (alph - self.parms.a_center).abs();
+ if center_dist > PI {
+ center_dist = 2.0 * PI - center_dist;
+ }
+ if center_dist >= self.proj.get_view_angle() {
+ return false;
+ }
+ self.max_view_angle <= 0.0 || center_dist < self.max_view_angle
+ }
+
+ /// Forward-project (azimuth, elevation) to image-centered pixel coords.
+ /// `None` when the direction is not visible or the projection is
+ /// undefined there (mirrors C `Panorama::get_coordinates` returning 1,
+ /// plus the NaN guard of C `ScanImage::get_pixel_nearest`).
+ pub fn project_point(&self, alph: f64, elev: f64) -> Option<(f64, f64)> {
+ if !self.is_visible(alph) {
+ return None;
+ }
+ let (x, y) = self.proj.get_coordinates(alph, elev, &self.parms);
+ if x.is_nan() || y.is_nan() {
+ return None;
+ }
+ Some((x, y))
+ }
+
+ /// Parameter-only snapshot (no hill data) for resampling: same viewpoint
+ /// and projection parameters, but without the UI's lens-FOV visibility
+ /// limit (the stitch output range defines the FOV itself).
+ pub fn params_snapshot(&self) -> Panorama {
+ let mut p = Panorama::new();
+ p.projection_type = self.projection_type;
+ p.proj = projection_from_type(self.projection_type);
+ p.parms = self.parms;
+ p.view_phi = self.view_phi;
+ p.view_lam = self.view_lam;
+ p.view_height = self.view_height;
+ p
+ }
+
+ pub fn get_coordinates(&self, alph: f64, elev: f64) -> Option<(f64, f64)> {
+ if self.is_visible(alph) {
+ Some(self.proj.get_coordinates(alph, elev, &self.parms))
+ } else {
+ None
+ }
+ }
+
+ /// Export rows for the visible hills, mirroring the C `export_hills`
+ /// output loop: `name\theight\tx\ty\tdistance` with image-center origin.
+ /// When `require_export` is set, only hills with `FLAG_EXPORT` are listed.
+ pub fn export_hill_rows(&self, img_w: i64, img_h: i64, require_export: bool) -> Vec<String> {
+ let mut rows = Vec::new();
+ for &i in &self.visible {
+ let m = &self.hills[i];
+ if m.flags & crate::hill::FLAG_DUPLIC != 0
+ || m.flags & crate::hill::FLAG_HIDDEN != 0
+ || (require_export && m.flags & crate::hill::FLAG_EXPORT == 0)
+ {
+ continue;
+ }
+
+ let x = m.x.round_ties_even() as i64 + img_w / 2;
+ let y = m.y.round_ties_even() as i64 + img_h / 2;
+ if x < 0 || x > img_w || y < 0 || y > img_h {
+ continue;
+ }
+
+ rows.push(format!(
+ "{}\t{}\t{}\t{}\t{}",
+ m.name,
+ m.height.round_ties_even() as i64,
+ x,
+ y,
+ self.get_real_distance(i).round_ties_even() as i64
+ ));
+ }
+ rows
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn max_view_angle_limits_visibility() {
+ let mut pan = Panorama::new();
+ pan.set_center_angle(0.0);
+ let a30 = 30f64.to_radians();
+ let a70 = 70f64.to_radians();
+
+ // rectilinear default limit is +-60 deg
+ assert!(pan.is_visible(a30));
+ assert!(!pan.is_visible(a70));
+
+ // a tighter lens FOV (say +-35 deg) hides the 40 deg direction that
+ // the projection's own +-60 deg limit would allow
+ pan.max_view_angle = 35f64.to_radians();
+ assert!(pan.is_visible(a30));
+ assert!(!pan.is_visible(40f64.to_radians()));
+
+ // a wider limit never overrides the projection's own bound
+ pan.max_view_angle = 80f64.to_radians();
+ assert!(!pan.is_visible(a70));
+ }
+}
diff --git a/crates/gipfel-core/src/profile.rs b/crates/gipfel-core/src/profile.rs
new file mode 100644
index 0000000..e8bda68
--- /dev/null
+++ b/crates/gipfel-core/src/profile.rs
@@ -0,0 +1,362 @@
+//! Lens distortion profiles.
+//!
+//! Profiles live in the standard per-user data directory
+//! (`$XDG_DATA_HOME/gipfel/distortion-profiles.prefs`, i.e.
+//! `~/.local/share/gipfel/…`). The file format is the FLTK preferences
+//! format used by the C version (`[./name]` groups, `key:value` entries,
+//! `%g` values), so the legacy file
+//! `~/.fltk/Johannes.HofmannATgmx.de/gipfel/DistortionProfiles.prefs`
+//! imports automatically into the new location on first use.
+//!
+//! File layout (as written by FLTK):
+//! ```text
+//! ; FLTK preferences file format 1.0
+//! ; vendor: Johannes.HofmannATgmx.de
+//! ; application: gipfel/DistortionProfiles
+//!
+//! [.]
+//!
+//! [./Canon_EOS_5D_50.00_mm]
+//!
+//! k0:-0.00123
+//! k1:2.5e-05
+//! x0:0.5
+//! ```
+
+use std::path::{Path, PathBuf};
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct Profile {
+ pub k0: f64,
+ pub k1: f64,
+ pub x0: f64,
+}
+
+#[derive(Debug)]
+pub enum Error {
+ NotFound,
+ Exists,
+ BadName,
+ Io(std::io::Error),
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Error::NotFound => write!(f, "profile not found"),
+ Error::Exists => write!(f, "profile already exists"),
+ Error::BadName => write!(f, "invalid profile name"),
+ Error::Io(e) => write!(f, "{e}"),
+ }
+ }
+}
+
+/// The profile store in the standard data directory
+/// (`$XDG_DATA_HOME/gipfel/distortion-profiles.prefs`).
+pub fn prefs_path() -> PathBuf {
+ dirs::data_dir()
+ .unwrap_or_else(|| PathBuf::from(std::env::var("HOME").unwrap_or_default()))
+ .join("gipfel")
+ .join("distortion-profiles.prefs")
+}
+
+/// Legacy FLTK preferences file used by the C version.
+fn fltk_prefs_path() -> PathBuf {
+ let home = std::env::var("HOME").unwrap_or_default();
+ Path::new(&home)
+ .join(".fltk")
+ .join("Johannes.HofmannATgmx.de")
+ .join("gipfel")
+ .join("DistortionProfiles.prefs")
+}
+
+/// Path to the profile store; imports legacy FLTK profiles on first use.
+fn ensure_store() -> PathBuf {
+ let path = prefs_path();
+ if !path.exists() {
+ let legacy = fltk_prefs_path();
+ if legacy.exists() {
+ if let Some(dir) = path.parent() {
+ let _ = std::fs::create_dir_all(dir);
+ }
+ // same format, so a copy suffices
+ let _ = std::fs::copy(&legacy, &path);
+ }
+ }
+ path
+}
+
+/// C `get_distortion_profile_name`: `"%s_%s_%.2f_mm"`. `None` when the
+/// camera is unknown or the focal length is unusable (C would build a name
+/// containing "nan" that can never match a stored profile).
+pub fn profile_name(manufacturer: &str, model: &str, focal_mm: f64) -> Option<String> {
+ if manufacturer.is_empty() || model.is_empty() || !focal_mm.is_finite() || focal_mm <= 0.0 {
+ return None;
+ }
+ let name = format!("{manufacturer}_{model}_{focal_mm:.2}_mm");
+ sanitize_name(&name).ok().map(|_| name)
+}
+
+/// Reject names that would break the prefs file structure (FLTK group paths
+/// use '/' as separator; control chars/newlines corrupt the file).
+pub fn sanitize_name(name: &str) -> Result<(), Error> {
+ if name.trim().is_empty() || name.len() > 255 {
+ return Err(Error::BadName);
+ }
+ if name
+ .chars()
+ .any(|c| c == '/' || c == '\\' || c.is_control())
+ {
+ return Err(Error::BadName);
+ }
+ Ok(())
+}
+
+/// Parse a prefs file into ordered (name, profile) pairs. Tolerant:
+/// comments (`;`), the root group `[.]`, blank lines and unknown keys are
+/// ignored; values are parsed with Rust's `f64` (superset of C `%g`).
+fn parse(text: &str) -> Vec<(String, Profile)> {
+ let mut out: Vec<(String, Profile)> = Vec::new();
+ let mut cur: Option<usize> = None;
+ for line in text.lines() {
+ let line = line.trim_end();
+ if line.is_empty() || line.starts_with(';') {
+ continue;
+ }
+ if let Some(group) = line.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
+ cur = match group.strip_prefix("./") {
+ Some(name) => {
+ out.push((
+ name.to_string(),
+ Profile {
+ k0: f64::NAN,
+ k1: f64::NAN,
+ x0: f64::NAN,
+ },
+ ));
+ Some(out.len() - 1)
+ }
+ None => None, // "[.]" root group
+ };
+ continue;
+ }
+ let Some(idx) = cur else { continue };
+ if let Some((key, val)) = line.split_once(':') {
+ let Ok(v) = val.trim().parse::<f64>() else {
+ continue;
+ };
+ let p = &mut out[idx].1;
+ match key.trim() {
+ "k0" => p.k0 = v,
+ "k1" => p.k1 = v,
+ "x0" => p.x0 = v,
+ _ => {}
+ }
+ }
+ }
+ out.retain(|(_, p)| p.k0.is_finite() && p.k1.is_finite() && p.x0.is_finite());
+ out
+}
+
+/// Serialize in FLTK's layout so the C version can read our file.
+fn serialize(profiles: &[(String, Profile)]) -> String {
+ let mut s = String::from(
+ "; FLTK preferences file format 1.0\n\
+ ; vendor: Johannes.HofmannATgmx.de\n\
+ ; application: gipfel/DistortionProfiles\n\
+ \n\
+ [.]\n\
+ \n",
+ );
+ for (name, p) in profiles {
+ s.push_str(&format!(
+ "\n[./{name}]\n\nk0:{}\nk1:{}\nx0:{}\n",
+ fmt_g(p.k0),
+ fmt_g(p.k1),
+ fmt_g(p.x0)
+ ));
+ }
+ s
+}
+
+/// Closest Rust equivalent of C `%g`: scientific notation for magnitudes
+/// outside [1e-4, 1e15), exponent zero-padded to two digits ("2.5e-05").
+/// Unlike C we keep the shortest round-trip mantissa (more precise; the
+/// FLTK reader uses strtod and reads either fine).
+fn fmt_g(v: f64) -> String {
+ if v != 0.0 && (v.abs() < 1e-4 || v.abs() >= 1e15) {
+ if let Some((mant, exp)) = format!("{v:e}").split_once('e') {
+ let neg = exp.starts_with('-');
+ let digits = exp.trim_start_matches(['-', '+']);
+ let digits = if digits.len() < 2 { format!("0{digits}") } else { digits.to_string() };
+ let sign = if neg { '-' } else { '+' };
+ return format!("{mant}e{sign}{digits}");
+ }
+ }
+ if v == v.trunc() && v.abs() < 1e16 {
+ format!("{}", v as i64)
+ } else {
+ format!("{v}")
+ }
+}
+
+fn read_all(path: &Path) -> Result<Vec<(String, Profile)>, Error> {
+ match std::fs::read_to_string(path) {
+ Ok(text) => Ok(parse(&text)),
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Vec::new()),
+ Err(e) => Err(Error::Io(e)),
+ }
+}
+
+fn write_all(path: &Path, profiles: &[(String, Profile)]) -> Result<(), Error> {
+ if let Some(dir) = path.parent() {
+ std::fs::create_dir_all(dir).map_err(Error::Io)?;
+ }
+ // atomic: temp file + rename
+ let tmp = path.with_extension("prefs.tmp");
+ std::fs::write(&tmp, serialize(profiles)).map_err(Error::Io)?;
+ std::fs::rename(&tmp, path).map_err(Error::Io)?;
+ Ok(())
+}
+
+/// Load a profile by exact name from the default store.
+pub fn load(name: &str) -> Result<Profile, Error> {
+ load_from(&ensure_store(), name)
+}
+
+pub fn load_from(path: &Path, name: &str) -> Result<Profile, Error> {
+ read_all(path)?
+ .into_iter()
+ .find(|(n, _)| n == name)
+ .map(|(_, p)| p)
+ .ok_or(Error::NotFound)
+}
+
+/// Save a profile. With `force == false` an existing profile of that name is
+/// not overwritten (C `save_distortion_params(name, force)`).
+pub fn save(name: &str, profile: Profile, force: bool) -> Result<(), Error> {
+ save_to(&ensure_store(), name, profile, force)
+}
+
+pub fn save_to(path: &Path, name: &str, profile: Profile, force: bool) -> Result<(), Error> {
+ sanitize_name(name)?;
+ let mut all = read_all(path)?;
+ if let Some(pos) = all.iter().position(|(n, _)| n == name) {
+ if !force {
+ return Err(Error::Exists);
+ }
+ all[pos].1 = profile;
+ } else {
+ all.push((name.to_string(), profile));
+ }
+ write_all(path, &all)
+}
+
+/// All profiles in the default store, in file order.
+pub fn list() -> Vec<(String, Profile)> {
+ read_all(&ensure_store()).unwrap_or_default()
+}
+
+pub fn list_from(path: &Path) -> Vec<(String, Profile)> {
+ read_all(path).unwrap_or_default()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// Exact bytes as written by the C version (verified against FLTK 1.4).
+ const FLTK_FIXTURE: &str = "; FLTK preferences file format 1.0\n\
+ ; vendor: Johannes.HofmannATgmx.de\n\
+ ; application: gipfel/DistortionProfiles\n\
+ \n\
+ [.]\n\
+ \n\
+ \n\
+ [./Canon_EOS_5D_50.00_mm]\n\
+ \n\
+ k0:-0.00123\n\
+ k1:2.5e-05\n\
+ x0:0.5\n\
+ \n\
+ [./Nikon D850_35.00_mm]\n\
+ \n\
+ k0:0.01\n\
+ k1:0\n\
+ x0:0\n";
+
+ #[test]
+ fn parses_fltk_layout() {
+ let all = parse(FLTK_FIXTURE);
+ assert_eq!(all.len(), 2);
+ assert_eq!(all[0].0, "Canon_EOS_5D_50.00_mm");
+ assert_eq!(all[0].1.k0, -0.00123);
+ assert_eq!(all[0].1.k1, 2.5e-5);
+ assert_eq!(all[0].1.x0, 0.5);
+ assert_eq!(all[1].0, "Nikon D850_35.00_mm");
+ assert_eq!(all[1].1.k0, 0.01);
+ assert_eq!(all[1].1.k1, 0.0);
+ }
+
+ #[test]
+ fn serialize_replicates_fltk_layout() {
+ let all = parse(FLTK_FIXTURE);
+ assert_eq!(serialize(&all), FLTK_FIXTURE);
+ }
+
+ #[test]
+ fn fmt_g_matches_c_percent_g() {
+ assert_eq!(fmt_g(2.5e-5), "2.5e-05");
+ assert_eq!(fmt_g(-0.00123), "-0.00123");
+ assert_eq!(fmt_g(0.0001), "0.0001");
+ assert_eq!(fmt_g(1e15), "1e+15");
+ assert_eq!(fmt_g(0.0), "0");
+ assert_eq!(fmt_g(0.01), "0.01");
+ }
+
+ #[test]
+ fn profile_name_matches_c() {
+ assert_eq!(
+ profile_name("Canon", "EOS_5D", 50.0).as_deref(),
+ Some("Canon_EOS_5D_50.00_mm")
+ );
+ assert_eq!(
+ profile_name("Nikon", "D850", 34.666).as_deref(),
+ Some("Nikon_D850_34.67_mm")
+ );
+ assert!(profile_name("", "D850", 35.0).is_none());
+ assert!(profile_name("Canon", "D850", f64::NAN).is_none());
+ assert!(profile_name("Can/on", "D850", 35.0).is_none());
+ }
+
+ #[test]
+ fn roundtrip_and_overwrite() {
+ let dir = std::env::temp_dir().join(format!("gipfel-prof-{}", std::process::id()));
+ let path = dir.join("DistortionProfiles.prefs");
+ let _ = std::fs::remove_dir_all(&dir);
+
+ let p = Profile { k0: -0.001, k1: 2e-5, x0: 0.0 };
+ save_to(&path, "Cam_A_24.00_mm", p, false).unwrap();
+ // exists -> error without force
+ assert!(matches!(
+ save_to(&path, "Cam_A_24.00_mm", p, false),
+ Err(Error::Exists)
+ ));
+ save_to(&path, "Cam_B_50.00_mm", Profile { k0: 0.1, k1: 0.0, x0: 1.0 }, false).unwrap();
+ // overwrite first, keep second
+ let p2 = Profile { k0: -0.002, k1: 2e-5, x0: 0.0 };
+ save_to(&path, "Cam_A_24.00_mm", p2, true).unwrap();
+
+ let all = list_from(&path);
+ assert_eq!(all.len(), 2);
+ assert_eq!(all[0].1, p2);
+ assert_eq!(load_from(&path, "Cam_B_50.00_mm").unwrap().k0, 0.1);
+ assert!(matches!(load_from(&path, "nope"), Err(Error::NotFound)));
+
+ // bad names rejected
+ assert!(save_to(&path, "a/b", p, true).is_err());
+ assert!(save_to(&path, "with\nnewline", p, true).is_err());
+
+ std::fs::remove_dir_all(&dir).unwrap();
+ }
+}
diff --git a/crates/gipfel-core/src/projection/cylindrical.rs b/crates/gipfel-core/src/projection/cylindrical.rs
new file mode 100644
index 0000000..9adf79c
--- /dev/null
+++ b/crates/gipfel-core/src/projection/cylindrical.rs
@@ -0,0 +1,86 @@
+//! Cylindrical projection.
+
+mod funcs {
+ #![allow(non_snake_case, unused_parens, unused_variables, dead_code, clippy::all)]
+
+ // math shims: the maxima-generated code uses C-style calls
+ #[inline(always)]
+ fn sin(x: f64) -> f64 {
+ x.sin()
+ }
+ #[inline(always)]
+ fn cos(x: f64) -> f64 {
+ x.cos()
+ }
+ #[inline(always)]
+ fn tan(x: f64) -> f64 {
+ x.tan()
+ }
+ #[inline(always)]
+ fn sqrt(x: f64) -> f64 {
+ x.sqrt()
+ }
+ #[inline(always)]
+ fn pow(x: f64, y: f64) -> f64 {
+ x.powf(y)
+ }
+ #[inline(always)]
+ fn exp(x: f64) -> f64 {
+ x.exp()
+ }
+ #[inline(always)]
+ fn fabs(x: f64) -> f64 {
+ x.abs()
+ }
+ #[inline(always)]
+ fn atan2(y: f64, x: f64) -> f64 {
+ y.atan2(x)
+ }
+ #[inline(always)]
+ fn sec(x: f64) -> f64 {
+ 1.0 / x.cos()
+ }
+
+ include!("generated/cylindrical_funcs.rs");
+}
+
+use super::{ProjArgs, Projection};
+
+pub struct ProjectionCylindrical;
+
+impl Projection for ProjectionCylindrical {
+ fn get_view_angle(&self) -> f64 {
+ 6.2831853 /* 360 deg */
+ }
+
+ fn mac_x(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_view(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_view(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_pitch(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_pitch(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_roll(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_roll(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dscale(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dscale(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_view(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_view(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_pitch(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_pitch(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_roll(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_roll(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dscale(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dscale(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+}
diff --git a/crates/gipfel-core/src/projection/generated/cylindrical_funcs.rs b/crates/gipfel-core/src/projection/generated/cylindrical_funcs.rs
new file mode 100644
index 0000000..220a7d8
--- /dev/null
+++ b/crates/gipfel-core/src/projection/generated/cylindrical_funcs.rs
@@ -0,0 +1,10 @@
+pub fn mac_x ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return (cos(c_roll)*(m_view-c_view)-(sin(c_roll)*(_2-_1))/(_1*_2+1.0))*scale ; }
+pub fn mac_y ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return ((-sin(c_roll)*(m_view-c_view))-(cos(c_roll)*(_2-_1))/(_1*_2+1.0))*scale ; }
+pub fn mac_x_dc_view ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { return -cos(c_roll)*scale ; }
+pub fn mac_x_dc_pitch ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; _1 = pow(sec(c_pitch),2.0) ; _2 = sin(c_roll) ; _3 = tan(m_elev) ; _4 = tan(c_pitch) ; _5 = _4*_3+1.0 ; return ((_1*_2*_3*(_3-_4))/pow(_5,2.0)+(_1*_2)/_5)*scale ; }
+pub fn mac_x_dc_roll ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return ((-sin(c_roll)*(m_view-c_view))-(cos(c_roll)*(_2-_1))/(_1*_2+1.0))*scale ; }
+pub fn mac_x_dscale ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return cos(c_roll)*(m_view-c_view)-(sin(c_roll)*(_2-_1))/(_1*_2+1.0) ; }
+pub fn mac_y_dc_view ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { return sin(c_roll)*scale ; }
+pub fn mac_y_dc_pitch ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; _1 = pow(sec(c_pitch),2.0) ; _2 = cos(c_roll) ; _3 = tan(m_elev) ; _4 = tan(c_pitch) ; _5 = _4*_3+1.0 ; return ((_1*_2*_3*(_3-_4))/pow(_5,2.0)+(_1*_2)/_5)*scale ; }
+pub fn mac_y_dc_roll ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return ((sin(c_roll)*(_2-_1))/(_1*_2+1.0)-cos(c_roll)*(m_view-c_view))*scale ; }
+pub fn mac_y_dscale ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; _1 = tan(c_pitch) ; _2 = tan(m_elev) ; return (-sin(c_roll)*(m_view-c_view))-(cos(c_roll)*(_2-_1))/(_1*_2+1.0) ; }
diff --git a/crates/gipfel-core/src/projection/generated/rectilinear_funcs.rs b/crates/gipfel-core/src/projection/generated/rectilinear_funcs.rs
new file mode 100644
index 0000000..7bd450d
--- /dev/null
+++ b/crates/gipfel-core/src/projection/generated/rectilinear_funcs.rs
@@ -0,0 +1,16 @@
+pub fn mac_x ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2.0)+pow(_13*_11*_12-_1*_5*_14*_11,2.0) ; return scale*_15*(k0*sqrt(_16)+k1*_16+1.0) ; }
+pub fn mac_y ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = pow(x0+_1*_5*_14*_11+_13*_11*_12,2.0)+pow(_15,2.0) ; return _15*scale*(k0*sqrt(_16)+k1*_16+1.0) ; }
+pub fn mac_x_dc_view ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; let mut _20 : f64 = 0.0; let mut _21 : f64 = 0.0; let mut _22 : f64 = 0.0; let mut _23 : f64 = 0.0; let mut _24 : f64 = 0.0; let mut _25 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1.0/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = cos(c_roll) ; _15 = _6*_9-_8*_7 ; _16 = x0+_14*_5*_15*_12+_1*_12*_13 ; _17 = _14*_12*_13-_1*_5*_15*_12 ; _18 = 1.0/pow(_11,2.0) ; _19 = pow(_5,2.0) ; _20 = pow(_15,2.0) ; _21 = (-_8*_9)-_6*_7 ; _22 = _14*_5*_21*_12+_2*_1*_5*_15*_12-_4*_14*_19*_20*_18-_4*_1*_5*_15*_18*_13 ; _23 = 2.0*_22*_16+2.0*_17*((-_1*_5*_21*_12)+_2*_14*_5*_15*_12+_4*_1*_19*_20*_18-_4*_14*_5*_15*_18*_13) ; _24 = pow(_17,2.0)+pow(_16,2.0) ; _25 = sqrt(_24) ; return _22*scale*(k0*_25+k1*_24+1.0)+scale*_16*((k0*_23)/(2.0*_25)+k1*_23) ; }
+pub fn mac_x_dc_pitch ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; let mut _20 : f64 = 0.0; let mut _21 : f64 = 0.0; let mut _22 : f64 = 0.0; let mut _23 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1.0/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = cos(c_roll) ; _15 = _6*_9-_8*_7 ; _16 = x0+_14*_5*_15*_12+_1*_12*_13 ; _17 = 1.0/pow(_11,2.0) ; _18 = _4*_3-_2*_5*_10 ; _19 = _14*_12*_13-_1*_5*_15*_12 ; _20 = (-_1*_17*_18*_13)-_14*_5*_15*_17*_18+_1 ; _21 = 2.0*_20*_16+2.0*((-_14*_17*_18*_13)+_1*_5*_15*_17*_18+_14)*_19 ; _22 = pow(_19,2.0)+pow(_16,2.0) ; _23 = sqrt(_22) ; return _20*scale*(k0*_23+k1*_22+1.0)+scale*_16*((k0*_21)/(2.0*_23)+k1*_21) ; }
+pub fn mac_x_dc_roll ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = _13*_11*_12-_1*_5*_14*_11 ; _17 = 2.0*_16*_15+2.0*((-_13*_5*_14*_11)-_1*_11*_12)*_16 ; _18 = pow(_16,2.0)+pow(_15,2.0) ; _19 = sqrt(_18) ; return _16*scale*(k0*_19+k1*_18+1.0)+scale*_15*((k0*_17)/(2.0*_19)+k1*_17) ; }
+pub fn mac_x_dscale ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2.0)+pow(_13*_11*_12-_1*_5*_14*_11,2.0) ; return _15*(k0*sqrt(_16)+k1*_16+1.0) ; }
+pub fn mac_x_dk0 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; return _15*sqrt(pow(_15,2.0)+pow(_13*_11*_12-_1*_5*_14*_11,2.0))*scale ; }
+pub fn mac_x_dk1 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; return _15*(pow(_15,2.0)+pow(_13*_11*_12-_1*_5*_14*_11,2.0))*scale ; }
+pub fn mac_x_dx0 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; _1 = sin(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2.0)+pow(_13*_11*_12-_1*_5*_14*_11,2.0) ; _17 = sqrt(_16) ; return scale*(k0*_17+k1*_16+1.0)+scale*_15*((k0*_15)/_17+2.0*k1*_15) ; }
+pub fn mac_y_dc_view ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; let mut _20 : f64 = 0.0; let mut _21 : f64 = 0.0; let mut _22 : f64 = 0.0; let mut _23 : f64 = 0.0; let mut _24 : f64 = 0.0; let mut _25 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1.0/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = sin(c_roll) ; _15 = _6*_9-_8*_7 ; _16 = _1*_12*_13-_14*_5*_15*_12 ; _17 = 1.0/pow(_11,2.0) ; _18 = pow(_5,2.0) ; _19 = pow(_15,2.0) ; _20 = (-_8*_9)-_6*_7 ; _21 = (-_14*_5*_20*_12)+_2*_1*_5*_15*_12+_4*_14*_18*_19*_17-_4*_1*_5*_15*_17*_13 ; _22 = x0+_1*_5*_15*_12+_14*_12*_13 ; _23 = 2.0*(_1*_5*_20*_12+_2*_14*_5*_15*_12-_4*_1*_18*_19*_17-_4*_14*_5*_15*_17*_13)*_22+2.0*_16*_21 ; _24 = pow(_22,2.0)+pow(_16,2.0) ; _25 = sqrt(_24) ; return _21*scale*(k0*_25+k1*_24+1.0)+_16*scale*((k0*_23)/(2.0*_25)+k1*_23) ; }
+pub fn mac_y_dc_pitch ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; let mut _20 : f64 = 0.0; let mut _21 : f64 = 0.0; let mut _22 : f64 = 0.0; let mut _23 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1.0/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = sin(c_roll) ; _15 = _6*_9-_8*_7 ; _16 = _1*_12*_13-_14*_5*_15*_12 ; _17 = 1.0/pow(_11,2.0) ; _18 = _4*_3-_2*_5*_10 ; _19 = (-_1*_17*_18*_13)+_14*_5*_15*_17*_18+_1 ; _20 = x0+_1*_5*_15*_12+_14*_12*_13 ; _21 = 2.0*((-_14*_17*_18*_13)-_1*_5*_15*_17*_18+_14)*_20+2.0*_19*_16 ; _22 = pow(_20,2.0)+pow(_16,2.0) ; _23 = sqrt(_22) ; return _19*scale*(k0*_23+k1*_22+1.0)+_16*scale*((k0*_21)/(2.0*_23)+k1*_21) ; }
+pub fn mac_y_dc_roll ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; let mut _17 : f64 = 0.0; let mut _18 : f64 = 0.0; let mut _19 : f64 = 0.0; let mut _20 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = (-_1*_5*_14*_11)-_13*_11*_12 ; _17 = x0+_1*_5*_14*_11+_13*_11*_12 ; _18 = 2.0*_15*_17+2.0*_16*_15 ; _19 = pow(_17,2.0)+pow(_15,2.0) ; _20 = sqrt(_19) ; return _16*scale*(k0*_20+k1*_19+1.0)+_15*scale*((k0*_18)/(2.0*_20)+k1*_18) ; }
+pub fn mac_y_dscale ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = pow(x0+_1*_5*_14*_11+_13*_11*_12,2.0)+pow(_15,2.0) ; return _15*(k0*sqrt(_16)+k1*_16+1.0) ; }
+pub fn mac_y_dk0 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; return _15*scale*sqrt(pow(x0+_1*_5*_14*_11+_13*_11*_12,2.0)+pow(_15,2.0)) ; }
+pub fn mac_y_dk1 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; return _15*scale*(pow(x0+_1*_5*_14*_11+_13*_11*_12,2.0)+pow(_15,2.0)) ; }
+pub fn mac_y_dx0 ( c_view: f64, c_pitch: f64, c_roll: f64, scale: f64, k0: f64, k1: f64, x0: f64, m_view: f64, m_elev: f64 ) -> f64 { let mut _1 : f64 = 0.0; let mut _2 : f64 = 0.0; let mut _3 : f64 = 0.0; let mut _4 : f64 = 0.0; let mut _5 : f64 = 0.0; let mut _6 : f64 = 0.0; let mut _7 : f64 = 0.0; let mut _8 : f64 = 0.0; let mut _9 : f64 = 0.0; let mut _10 : f64 = 0.0; let mut _11 : f64 = 0.0; let mut _12 : f64 = 0.0; let mut _13 : f64 = 0.0; let mut _14 : f64 = 0.0; let mut _15 : f64 = 0.0; let mut _16 : f64 = 0.0; _1 = cos(c_roll) ; _2 = sin(c_pitch) ; _3 = sin(m_elev) ; _4 = cos(c_pitch) ; _5 = cos(m_elev) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1.0/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_roll) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = x0+_1*_5*_14*_11+_13*_11*_12 ; return _15*((_16*k0)/sqrt(pow(_16,2.0)+pow(_15,2.0))+2.0*k1*_16)*scale ; }
diff --git a/crates/gipfel-core/src/projection/mod.rs b/crates/gipfel-core/src/projection/mod.rs
new file mode 100644
index 0000000..65b74e5
--- /dev/null
+++ b/crates/gipfel-core/src/projection/mod.rs
@@ -0,0 +1,174 @@
+//! Projections: image coordinates as a function of azimuth/elevation angle,
+//! with symbolically pre-generated derivatives (see `codegen/`).
+//!
+//! Faithful port of gipfel's `ProjectionLSQ.{H,cxx}`, `ProjectionRectilinear.*`
+//! and `ProjectionCylindrical.*`.
+
+pub mod cylindrical;
+pub mod rectilinear;
+
+pub const NAN: f64 = f64::NAN;
+
+/// The 7 fitted view parameters (radians where angular).
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct ViewParams {
+ /// azimuth of image center, radians
+ pub a_center: f64,
+ /// focal scale (pixels)
+ pub scale: f64,
+ /// pitch angle of image center (camera nose up), radians
+ pub a_pitch: f64,
+ /// camera roll, radians
+ pub a_roll: f64,
+ /// radial distortion coefficient 0
+ pub k0: f64,
+ /// radial distortion coefficient 1
+ pub k1: f64,
+ /// distortion center x, pixels
+ pub x0: f64,
+}
+
+impl ViewParams {
+ pub fn new() -> Self {
+ ViewParams {
+ a_center: 0.0,
+ scale: 3500.0,
+ a_pitch: 0.0,
+ a_roll: 0.0,
+ k0: 0.0,
+ k1: 0.0,
+ x0: 0.0,
+ }
+ }
+}
+
+/// The 9 arguments every generated macro function takes.
+#[derive(Clone, Copy, Debug)]
+pub struct ProjArgs {
+ pub c_view: f64,
+ pub c_pitch: f64,
+ pub c_roll: f64,
+ pub scale: f64,
+ pub k0: f64,
+ pub k1: f64,
+ pub x0: f64,
+ pub m_view: f64,
+ pub m_elev: f64,
+}
+
+impl ProjArgs {
+ pub fn from_params(p: &ViewParams, m_view: f64, m_elev: f64) -> Self {
+ ProjArgs {
+ c_view: p.a_center,
+ c_pitch: p.a_pitch,
+ c_roll: p.a_roll,
+ scale: p.scale,
+ k0: p.k0,
+ k1: p.k1,
+ x0: p.x0,
+ m_view,
+ m_elev,
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ProjectionType {
+ Rectilinear = 0,
+ Cylindrical = 1,
+}
+
+/// A projection plus its symbolic partial derivatives w.r.t. the view
+/// parameters. Methods default to `NAN`, matching the C++ base class.
+pub trait Projection: Send + Sync {
+ /// half-width of the visible azimuth range, radians
+ fn get_view_angle(&self) -> f64;
+
+ fn mac_x(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dc_view(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dc_pitch(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dc_roll(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dscale(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dk0(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dk1(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_x_dx0(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dc_view(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dc_pitch(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dc_roll(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dscale(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dk0(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dk1(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+ fn mac_y_dx0(&self, a: ProjArgs) -> f64 {
+ let _ = a;
+ NAN
+ }
+
+ /// Project azimuth/elevation to image coordinates (relative to image center),
+ /// normalizing azimuth around the view center like the C++ original.
+ fn get_coordinates(&self, alph: f64, elev: f64, parms: &ViewParams) -> (f64, f64) {
+ let mut alph = alph;
+ let pi = crate::hill::PI;
+
+ if alph - parms.a_center > pi {
+ alph -= 2.0 * pi;
+ } else if alph - parms.a_center < -pi {
+ alph += 2.0 * pi;
+ }
+
+ let args = ProjArgs::from_params(parms, alph, elev);
+ (self.mac_x(args), self.mac_y(args))
+ }
+}
+
+pub fn projection_from_type(t: ProjectionType) -> Box<dyn Projection> {
+ match t {
+ ProjectionType::Rectilinear => Box::new(rectilinear::ProjectionRectilinear),
+ ProjectionType::Cylindrical => Box::new(cylindrical::ProjectionCylindrical),
+ }
+}
diff --git a/crates/gipfel-core/src/projection/rectilinear.rs b/crates/gipfel-core/src/projection/rectilinear.rs
new file mode 100644
index 0000000..a7037d6
--- /dev/null
+++ b/crates/gipfel-core/src/projection/rectilinear.rs
@@ -0,0 +1,104 @@
+//! Rectilinear (gnomonic) projection with lens distortion.
+
+mod funcs {
+ #![allow(non_snake_case, unused_parens, unused_variables, dead_code, clippy::all)]
+
+ // math shims: the maxima-generated code uses C-style calls
+ #[inline(always)]
+ fn sin(x: f64) -> f64 {
+ x.sin()
+ }
+ #[inline(always)]
+ fn cos(x: f64) -> f64 {
+ x.cos()
+ }
+ #[inline(always)]
+ fn tan(x: f64) -> f64 {
+ x.tan()
+ }
+ #[inline(always)]
+ fn sqrt(x: f64) -> f64 {
+ x.sqrt()
+ }
+ #[inline(always)]
+ fn pow(x: f64, y: f64) -> f64 {
+ x.powf(y)
+ }
+ #[inline(always)]
+ fn exp(x: f64) -> f64 {
+ x.exp()
+ }
+ #[inline(always)]
+ fn fabs(x: f64) -> f64 {
+ x.abs()
+ }
+ #[inline(always)]
+ fn atan2(y: f64, x: f64) -> f64 {
+ y.atan2(x)
+ }
+ #[inline(always)]
+ fn sec(x: f64) -> f64 {
+ 1.0 / x.cos()
+ }
+
+ include!("generated/rectilinear_funcs.rs");
+}
+
+use super::{ProjArgs, Projection};
+
+pub struct ProjectionRectilinear;
+
+impl Projection for ProjectionRectilinear {
+ fn get_view_angle(&self) -> f64 {
+ 1.0471976 /* 60 deg */
+ }
+
+ fn mac_x(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_view(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_view(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_pitch(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_pitch(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dc_roll(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dc_roll(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dscale(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dscale(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dk0(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dk0(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dk1(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dk1(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_x_dx0(&self, a: ProjArgs) -> f64 {
+ funcs::mac_x_dx0(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_view(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_view(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_pitch(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_pitch(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dc_roll(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dc_roll(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dscale(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dscale(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dk0(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dk0(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dk1(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dk1(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+ fn mac_y_dx0(&self, a: ProjArgs) -> f64 {
+ funcs::mac_y_dx0(a.c_view, a.c_pitch, a.c_roll, a.scale, a.k0, a.k1, a.x0, a.m_view, a.m_elev)
+ }
+}
diff --git a/crates/gipfel-core/src/stitch.rs b/crates/gipfel-core/src/stitch.rs
new file mode 100644
index 0000000..a311961
--- /dev/null
+++ b/crates/gipfel-core/src/stitch.rs
@@ -0,0 +1,323 @@
+//! Cylindrical panorama resampling — port of the C++ `Stitch.cxx` /
+//! `ScanImage.cxx` stitch mode.
+//!
+//! The output is a cylindrical (equirectangular-in-azimuth, tan-in-elevation)
+//! image spanning `view_start..view_end` degrees of azimuth. Each output
+//! pixel is mapped back through every source image's fitted projection; the
+//! first source that covers the direction contributes its (resampled) color.
+//!
+//! All sampling arithmetic reproduces the C implementation bit-for-bit:
+//! colors are handled on the 0..=65025 scale (`value * 255`), nearest uses
+//! `rint` (round-half-to-even), bicubic uses the same Keys cubic and only
+//! rounds after the final vertical interpolation.
+
+use crate::metadata::{ImageMetaData, apply_image_metadata};
+use crate::panorama::Panorama;
+use image::RgbImage;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum ScanMode {
+ Nearest,
+ Bicubic,
+}
+
+/// C MAX_VALUE: clamp bound on the 0..=65025 (value*255) scale.
+pub const MAX_VALUE: i64 = 65025;
+
+fn pixel(img: &RgbImage, x: i64, y: i64) -> Option<[i64; 3]> {
+ if x < 0 || y < 0 || x >= img.width() as i64 || y >= img.height() as i64 {
+ return None;
+ }
+ let p = img.get_pixel(x as u32, y as u32);
+ Some([p[0] as i64 * 255, p[1] as i64 * 255, p[2] as i64 * 255])
+}
+
+/// C ScanImage::get_pixel_nearest: NaN-guard, then round to nearest pixel.
+fn get_pixel_nearest(img: &RgbImage, x: f64, y: f64) -> Option<[i64; 3]> {
+ if x.is_nan() || y.is_nan() {
+ return None;
+ }
+ pixel(img, x.round_ties_even() as i64, y.round_ties_even() as i64)
+}
+
+/// C ScanImage interp_cubic (Keys cubic convolution kernel, evaluated in
+/// Bernstein-like power basis).
+#[inline]
+fn interp_cubic(x: f64, x2: f64, x3: f64, v: &[f64; 4]) -> f64 {
+ let a0 = v[3] - v[2] - v[0] + v[1];
+ let a1 = v[0] - v[1] - a0;
+ let a2 = v[2] - v[0];
+ let a3 = v[1];
+ a0 * x3 + a1 * x2 + a2 * x + a3
+}
+
+/// C ScanImage::get_pixel_bicubic. Any of the 16 taps outside the source
+/// makes the pixel unset (C returns 1), matching the "leave black" behavior.
+fn get_pixel_bicubic(img: &RgbImage, x: f64, y: f64) -> Option<[i64; 3]> {
+ if x.is_nan() || y.is_nan() {
+ return None;
+ }
+ let fl_x = x.floor();
+ let fl_y = y.floor();
+ let dx = x - fl_x;
+ let dx2 = dx * dx;
+ let dx3 = dx2 * dx;
+ let dy = y - fl_y;
+ let dy2 = dy * dy;
+ let dy3 = dy2 * dy;
+
+ let mut c1 = [[0f64; 4]; 3];
+ for iy in 0..4usize {
+ let mut c = [[0f64; 4]; 3];
+ for ix in 0..4usize {
+ let p = pixel(img, fl_x as i64 + ix as i64 - 1, fl_y as i64 + iy as i64 - 1)?;
+ for l in 0..3 {
+ c[l][ix] = p[l] as f64;
+ }
+ }
+ for l in 0..3 {
+ c1[l][iy] = interp_cubic(dx, dx2, dx3, &c[l]);
+ }
+ }
+ Some([
+ interp_cubic(dy, dy2, dy3, &c1[0]).round_ties_even() as i64,
+ interp_cubic(dy, dy2, dy3, &c1[1]).round_ties_even() as i64,
+ interp_cubic(dy, dy2, dy3, &c1[2]).round_ties_even() as i64,
+ ])
+}
+
+/// C ScanImage::get_pixel(mode, x, y). Values on the 0..=65025 scale;
+/// bicubic may overshoot (clamped by the caller, as in C).
+pub fn get_pixel(img: &RgbImage, mode: ScanMode, x: f64, y: f64) -> Option<[i64; 3]> {
+ match mode {
+ ScanMode::Nearest => get_pixel_nearest(img, x, y),
+ ScanMode::Bicubic => get_pixel_bicubic(img, x, y),
+ }
+}
+
+/// One input photo: its fitted projection plus its decoded pixels.
+pub struct StitchSource {
+ pub pan: Panorama,
+ pub img: RgbImage,
+}
+
+impl StitchSource {
+ /// Load a JPEG and apply its gipfel metadata (comment or EXIF), like
+ /// `GipfelWidget::load_image` does for the stitch mode.
+ pub fn load(path: &str) -> Result<Self, String> {
+ let img = image::open(path)
+ .map(|i| i.to_rgb8())
+ .map_err(|e| format!("{}: {e}", path))?;
+ let mut md = ImageMetaData::new();
+ md.load_image(path);
+ let mut pan = Panorama::new();
+ apply_image_metadata(&mut pan, &md, img.width(), img.height());
+ Ok(Self { pan, img })
+ }
+
+ /// C GipfelWidget::get_pixel: forward-project (azimuth, elevation)
+ /// through this image's projection and sample the source.
+ pub fn get_pixel(&self, mode: ScanMode, a_alph: f64, a_elev: f64) -> Option<[i64; 3]> {
+ let (px, py) = self.pan.project_point(a_alph, a_elev)?;
+ get_pixel(
+ &self.img,
+ mode,
+ px + self.img.width() as f64 / 2.0,
+ py + self.img.height() as f64 / 2.0,
+ )
+ }
+}
+
+/// The resampled cylindrical panorama. Pixel values are RGB triplets on the
+/// C 0..=65025 scale (clamped); `hit` marks pixels some source contributed
+/// to (unset pixels are black in the C output).
+pub struct StitchedImage {
+ pub w: u32,
+ pub h: u32,
+ pub pixels: Vec<[u16; 3]>,
+ pub hit: Vec<bool>,
+}
+
+/// Port of `Stitch::resample`: output column x maps linearly to azimuth,
+/// output row y maps to elevation via `atan((y_off - y) / radius)`.
+pub fn resample(
+ sources: &[StitchSource],
+ mode: ScanMode,
+ w: u32,
+ h: u32,
+ view_start_deg: f64,
+ view_end_deg: f64,
+) -> StitchedImage {
+ let view_start = view_start_deg.to_radians();
+ let view_end = view_end_deg.to_radians();
+ let mut pixels = vec![[0u16; 3]; (w as usize) * (h as usize)];
+ let mut hit = vec![false; (w as usize) * (h as usize)];
+ if !(view_end > view_start) || w == 0 || h == 0 {
+ return StitchedImage {
+ w,
+ h,
+ pixels,
+ hit,
+ };
+ }
+ let step_view = (view_end - view_start) / w as f64;
+ let y_off = (h / 2) as f64;
+ let radius = w as f64 / (view_end - view_start);
+
+ for y in 0..h {
+ let a_elev = ((y_off - y as f64) / radius).atan();
+ for x in 0..w {
+ let a_view = view_start + x as f64 * step_view;
+ for src in sources {
+ let Some(rgb) = src.get_pixel(mode, a_view, a_elev) else {
+ continue;
+ };
+ let clamped = [
+ rgb[0].clamp(0, MAX_VALUE) as u16,
+ rgb[1].clamp(0, MAX_VALUE) as u16,
+ rgb[2].clamp(0, MAX_VALUE) as u16,
+ ];
+ let i = (y as usize) * (w as usize) + (x as usize);
+ pixels[i] = clamped;
+ hit[i] = true;
+ break; // C: first contributing image wins
+ }
+ }
+ }
+
+ StitchedImage {
+ w,
+ h,
+ pixels,
+ hit,
+ }
+}
+
+/// Write an RGBA TIFF like the C `TIFFOutputImage`: 8 bit (value/255, alpha
+/// 255) or 16 bit (raw 0..=65025, alpha 65025), deflate-compressed.
+pub fn write_tiff(path: &str, img: &StitchedImage, bits16: bool) -> Result<(), String> {
+ use tiff::encoder::colortype::{RGBA16, RGBA8};
+ use tiff::encoder::{Compression, TiffEncoder};
+
+ let n = (img.w as usize) * (img.h as usize);
+ let file = std::fs::File::create(path).map_err(|e| format!("{}: {e}", path))?;
+ let mut enc = TiffEncoder::new(file)
+ .map_err(|e| format!("{}: {e}", path))?
+ .with_compression(Compression::Deflate(Default::default()));
+ if bits16 {
+ let mut buf: Vec<u16> = Vec::with_capacity(n * 4);
+ for p in &img.pixels {
+ buf.extend_from_slice(&[p[0], p[1], p[2], MAX_VALUE as u16]);
+ }
+ enc.write_image::<RGBA16>(img.w, img.h, &buf)
+ .map_err(|e| format!("{}: {e}", path))?;
+ } else {
+ let mut buf: Vec<u8> = Vec::with_capacity(n * 4);
+ for p in &img.pixels {
+ buf.extend_from_slice(&[(p[0] / 255) as u8, (p[1] / 255) as u8, (p[2] / 255) as u8, 255]);
+ }
+ enc.write_image::<RGBA8>(img.w, img.h, &buf)
+ .map_err(|e| format!("{}: {e}", path))?;
+ }
+ Ok(())
+}
+
+/// Write a JPEG (quality 90, like the C `JPEGOutputImage`).
+pub fn write_jpeg(path: &str, img: &StitchedImage, quality: u8) -> Result<(), String> {
+ use image::codecs::jpeg::JpegEncoder;
+ use image::ImageEncoder;
+
+ let rgb: Vec<u8> = img
+ .pixels
+ .iter()
+ .flat_map(|p| [(p[0] / 255) as u8, (p[1] / 255) as u8, (p[2] / 255) as u8])
+ .collect();
+ let enc = JpegEncoder::new_with_quality(
+ std::io::BufWriter::new(std::fs::File::create(path).map_err(|e| format!("{}: {e}", path))?),
+ quality,
+ );
+ enc.write_image(&rgb, img.w, img.h, image::ExtendedColorType::Rgb8)
+ .map_err(|e| format!("{}: {e}", path))?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn solid(v: u8, w: u32, h: u32) -> RgbImage {
+ RgbImage::from_pixel(w, h, image::Rgb([v, v, v]))
+ }
+
+ #[test]
+ fn nearest_rounds_half_to_even() {
+ let img = solid(100, 4, 4);
+ // 1.5 -> 2 (ties to even), 2.5 -> 2
+ assert_eq!(get_pixel_nearest(&img, 1.5, 0.0), Some([100 * 255; 3]));
+ assert_eq!(get_pixel_nearest(&img, 2.5, 0.0), Some([100 * 255; 3]));
+ assert_eq!(get_pixel_nearest(&img, -0.4, 0.0), Some([100 * 255; 3]));
+ assert_eq!(get_pixel_nearest(&img, -0.6, 0.0), None);
+ assert_eq!(get_pixel_nearest(&img, f64::NAN, 0.0), None);
+ }
+
+ #[test]
+ fn bicubic_constant_and_bounds() {
+ let img = solid(100, 4, 4);
+ // constant image: cubic reproduces the constant exactly
+ assert_eq!(get_pixel_bicubic(&img, 1.3, 1.7), Some([100 * 255; 3]));
+ // any tap outside the 4x4 neighborhood -> unset
+ assert_eq!(get_pixel_bicubic(&img, 0.0, 2.0), None); // fl_x-1 = -1
+ assert_eq!(get_pixel_bicubic(&img, 2.0, 3.5), None); // fl_y+2 = 5
+ assert_eq!(get_pixel_bicubic(&img, 1.0, 1.0), Some([100 * 255; 3]));
+ }
+
+ #[test]
+ fn bicubic_matches_keys_cubic_on_ramp() {
+ // Keys cubic (a=-1/2): on a linear ramp the kernel yields
+ // a + b*(2 dx^3 - 3 dx^2 + 2 dx), not the linear value.
+ let mut img = RgbImage::new(8, 8);
+ for x in 0..8 {
+ for y in 0..8 {
+ img.put_pixel(x, y, image::Rgb([(x as u8) * 30, 0, 0]));
+ }
+ }
+ let got = get_pixel_bicubic(&img, 3.25, 4.0).unwrap();
+ let a = 3.0 * 30.0 * 255.0;
+ let b = 30.0 * 255.0;
+ let dx = 0.25f64;
+ let want = (a + b * (2.0 * dx * dx * dx - 3.0 * dx * dx + 2.0 * dx)).round_ties_even();
+ assert_eq!(got[0], want as i64);
+ }
+
+ #[test]
+ fn bicubic_exact_at_integer_positions() {
+ // at integer sample coordinates (dx = dy = 0) the cubic kernel
+ // reproduces the center tap exactly -> a 1:1 resample is lossless
+ let mut img = RgbImage::new(8, 8);
+ for x in 0..8 {
+ for y in 0..8 {
+ img.put_pixel(x, y, image::Rgb([(x * 7 + y * 3) as u8, (y * 5) as u8, 9]));
+ }
+ }
+ for y in 1..6 {
+ for x in 1..6 {
+ let p = img.get_pixel(x, y).0;
+ assert_eq!(
+ get_pixel_bicubic(&img, x as f64, y as f64).unwrap(),
+ [p[0] as i64 * 255, p[1] as i64 * 255, p[2] as i64 * 255]
+ );
+ }
+ }
+ }
+
+ #[test]
+ fn resample_rejects_empty_range() {
+ let img = StitchSource {
+ pan: Panorama::new(),
+ img: solid(50, 8, 8),
+ };
+ let out = resample(&[img], ScanMode::Nearest, 4, 4, 10.0, 10.0);
+ assert_eq!(out.hit.len(), 16);
+ assert!(out.hit.iter().all(|h| !h));
+ }
+}
diff --git a/crates/gipfel-core/src/track.rs b/crates/gipfel-core/src/track.rs
new file mode 100644
index 0000000..5450aa8
--- /dev/null
+++ b/crates/gipfel-core/src/track.rs
@@ -0,0 +1,248 @@
+//! GPS track loading (GPX).
+//!
+//! Minimal streaming parser built directly on quick-xml: we only need the
+//! `trkpt` positions, and real-world files (Garmin etc.) are full of vendor
+//! `<extensions>` blocks that stricter parsers choke on. Waypoints and
+//! routes are ignored; only track points are read.
+//!
+//! Security posture for untrusted files:
+//! - quick-xml is used with `default-features = false` (no serde, no encoding
+//! detection); it never fetches external DTDs/entities and expands only the
+//! five predefined XML entities, so XXE and entity-expansion ("billion
+//! laughs") attacks are structurally impossible; unknown entity references
+//! make the attribute fail to parse and the point is skipped
+//! - input size is capped (MAX_GPX_BYTES) before anything is parsed
+//! - the parser only reads lat/lon attributes and <ele> text; all other
+//! elements (including vendor extensions) are ignored without being
+//! interpreted
+
+use crate::hill::{Hill, FLAG_TRACK_POINT};
+use quick_xml::events::Event;
+
+/// Reject absurdly large track files (a 64 MiB GPX is already ~500k points).
+pub const MAX_GPX_BYTES: u64 = 64 * 1024 * 1024;
+
+/// Load track points from a GPX file (all tracks/segments, in file order).
+///
+/// Consecutive duplicate positions are dropped; the result is decimated with
+/// a uniform stride to at most `max_points` (GPX logs easily reach 10k
+/// points, which would bloat the panorama's hill list).
+pub fn load_gpx(path: &str, max_points: usize) -> Result<Vec<Hill>, String> {
+ let size = std::fs::metadata(path).map_err(|e| format!("{path}: {e}"))?;
+ if size.len() > MAX_GPX_BYTES {
+ return Err(format!("{path}: file too large ({} bytes)", size.len()));
+ }
+ let data = std::fs::read_to_string(path).map_err(|e| format!("{path}: {e}"))?;
+ let mut reader = quick_xml::Reader::from_str(&data);
+ reader.config_mut().trim_text(true);
+
+ let mut pts: Vec<(f64, f64, f64)> = Vec::new(); // (lat, lon, ele) degrees
+ let mut depth: usize = 0;
+ let mut trkpt_depth: Option<usize> = None;
+ let mut in_ele = false;
+ let (mut lat, mut lon, mut ele) = (f64::NAN, f64::NAN, 0.0);
+
+ fn push(pts: &mut Vec<(f64, f64, f64)>, lat: f64, lon: f64, ele: f64) {
+ if lat.is_nan() || lon.is_nan() {
+ return;
+ }
+ if let Some(&(lata, lona, _)) = pts.last() {
+ if lata == lat && lona == lon {
+ return; // consecutive duplicate fix
+ }
+ }
+ pts.push((lat, lon, ele));
+ }
+
+ loop {
+ match reader.read_event().map_err(|e| format!("{path}: {e}"))? {
+ Event::Eof => break,
+ Event::Empty(e) => {
+ if e.local_name().as_ref() == b"trkpt" {
+ let (la, lo) = read_latlon(&e);
+ lat = la;
+ lon = lo;
+ ele = 0.0;
+ push(&mut pts, lat, lon, ele);
+ }
+ }
+ Event::Start(e) => {
+ depth += 1;
+ match e.local_name().as_ref() {
+ b"trkpt" => {
+ trkpt_depth = Some(depth);
+ ele = 0.0;
+ let (la, lo) = read_latlon(&e);
+ lat = la;
+ lon = lo;
+ }
+ // accept <ele> only as a direct child of <trkpt>, so
+ // elevation-like tags inside vendor extensions are ignored
+ b"ele" => {
+ in_ele = trkpt_depth == Some(depth - 1);
+ if in_ele {
+ ele = 0.0;
+ }
+ }
+ _ => {}
+ }
+ }
+ Event::Text(t) => {
+ if in_ele {
+ if let Ok(s) = t.decode() {
+ if let Ok(v) = s.trim().parse() {
+ ele = v;
+ }
+ }
+ }
+ }
+ Event::End(e) => {
+ match e.local_name().as_ref() {
+ b"ele" => in_ele = false,
+ b"trkpt" => {
+ push(&mut pts, lat, lon, ele);
+ trkpt_depth = None;
+ }
+ _ => {}
+ }
+ depth = depth.saturating_sub(1);
+ }
+ _ => {}
+ }
+ }
+
+ if pts.is_empty() {
+ return Err(format!("{path}: no track points"));
+ }
+
+ let stride = pts.len().div_ceil(max_points.max(1));
+ Ok(pts
+ .iter()
+ .step_by(stride)
+ .map(|&(lat, lon, ele)| {
+ let mut h = Hill::new("track", lat.to_radians(), lon.to_radians(), ele);
+ h.flags |= FLAG_TRACK_POINT;
+ h
+ })
+ .collect())
+}
+
+fn read_latlon(e: &quick_xml::events::BytesStart) -> (f64, f64) {
+ let (mut lat, mut lon) = (f64::NAN, f64::NAN);
+ for a in e.attributes().flatten() {
+ if let Ok(v) = a.normalized_value(quick_xml::XmlVersion::Implicit1_0) {
+ match a.key.local_name().as_ref() {
+ b"lat" => lat = v.trim().parse().unwrap_or(f64::NAN),
+ b"lon" => lon = v.trim().parse().unwrap_or(f64::NAN),
+ _ => {}
+ }
+ }
+ }
+ (lat, lon)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn tmp(name: &str, content: &str) -> std::path::PathBuf {
+ let dir = std::env::temp_dir().join(format!("gipfel-gpx-test-{name}"));
+ std::fs::create_dir_all(&dir).unwrap();
+ let p = dir.join("t.gpx");
+ std::fs::write(&p, content).unwrap();
+ p
+ }
+
+ #[test]
+ fn gpx_track_roundtrip() {
+ let gpx = r#"<?xml version="1.0" encoding="UTF-8"?>
+<gpx version="1.1" creator="test" xmlns="http://www.topografix.com/GPX/1/1">
+ <trk><name>tour</name><trkseg>
+ <trkpt lat="47.30" lon="11.10"><ele>1000</ele></trkpt>
+ <trkpt lat="47.31" lon="11.11"><ele>1100</ele></trkpt>
+ <trkpt lat="47.31" lon="11.11"><ele>1100</ele></trkpt>
+ <trkpt lat="47.32" lon="11.12"><ele>1200</ele></trkpt>
+ </trkseg></trk>
+</gpx>"#;
+ let p = tmp("rt", gpx);
+ let hills = load_gpx(p.to_str().unwrap(), 100).unwrap();
+ assert_eq!(hills.len(), 3); // duplicate dropped
+ assert!(hills.iter().all(|h| h.is_track_point()));
+ assert!((hills[1].phi - 47.31f64.to_radians()).abs() < 1e-12);
+ assert!((hills[2].height - 1200.0).abs() < 1e-9);
+ }
+
+ #[test]
+ fn gpx_vendor_extensions_ignored() {
+ // Garmin-style files are full of vendor extension blocks; they must
+ // not confuse the parser (and must not contribute elevation).
+ let gpx = r#"<?xml version="1.0" encoding="utf-8"?>
+<gpx creator="Garmin Desktop App" version="1.1" xmlns="http://www.topografix.com/GPX/1/1"
+ xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1">
+ <metadata><extensions><gpxx:TrackPointExtension xmlns:gpxx="x" version="1.0"/></extensions></metadata>
+ <trk><name>T</name><extensions><gpxx2:x xmlns:gpxx2="y"><ele>9999</ele></gpxx2:x></extensions>
+ <trkseg>
+ <trkpt lat="47.30" lon="11.10">
+ <ele>1000</ele>
+ <extensions><gpxtpx:TrackPointExtension><gpxtpx:hr>120</gpxtpx:hr></gpxtpx:TrackPointExtension></extensions>
+ </trkpt>
+ <trkpt lat="47.31" lon="11.11"><ele>1100</ele></trkpt>
+ </trkseg>
+ </trk>
+</gpx>"#;
+ let p = tmp("ext", gpx);
+ let hills = load_gpx(p.to_str().unwrap(), 100).unwrap();
+ assert_eq!(hills.len(), 2);
+ assert!((hills[0].height - 1000.0).abs() < 1e-9); // not 9999
+ }
+
+ #[test]
+ fn gpx_decimation() {
+ let mut pts = String::new();
+ for i in 0..1000 {
+ pts.push_str(&format!(
+ "<trkpt lat=\"{}\" lon=\"{}\"><ele>1</ele></trkpt>",
+ 47.0 + i as f64 * 1e-5,
+ 11.0 + i as f64 * 1e-5
+ ));
+ }
+ let gpx = format!(
+ r#"<?xml version="1.0"?><gpx version="1.1" creator="t" xmlns="http://www.topografix.com/GPX/1/1"><trk><trkseg>{pts}</trkseg></trk></gpx>"#
+ );
+ let p = tmp("dec", &gpx);
+ let hills = load_gpx(p.to_str().unwrap(), 100).unwrap();
+ assert!(hills.len() <= 100);
+ }
+
+ #[test]
+ fn gpx_bom_is_tolerated() {
+ // Garmin Desktop App writes a UTF-8 BOM; it must not break parsing.
+ let gpx = format!(
+ "\u{feff}<?xml version=\"1.0\"?><gpx version=\"1.1\" creator=\"t\" xmlns=\"http://www.topografix.com/GPX/1/1\"><trk><trkseg><trkpt lat=\"47.30\" lon=\"11.10\"/></trkseg></trk></gpx>"
+ );
+ let p = tmp("bom", &gpx);
+ let hills = load_gpx(p.to_str().unwrap(), 100).unwrap();
+ assert_eq!(hills.len(), 1);
+ }
+
+ #[test]
+ fn gpx_entity_injection_is_inert() {
+ // XXE / entity-expansion attempts: quick-xml only expands the five
+ // predefined entities; unknown references make the attribute fail to
+ // parse, so the point is simply skipped (never expanded, never fetched).
+ let gpx = r#"<?xml version="1.0"?>
+<!DOCTYPE gpx [ <!ENTITY evil "47.30"> <!ENTITY sys SYSTEM "file:///etc/passwd"> ]>
+<gpx version="1.1" creator="t" xmlns="http://www.topografix.com/GPX/1/1">
+ <trk><trkseg>
+ <trkpt lat="&evil;" lon="11.10"><ele>1000</ele></trkpt>
+ <trkpt lat="47.31" lon="&sys;"><ele>1100</ele></trkpt>
+ <trkpt lat="47.32" lon="11.12"><ele>1200</ele></trkpt>
+ </trkseg></trk>
+</gpx>"#;
+ let p = tmp("ent", gpx);
+ let hills = load_gpx(p.to_str().unwrap(), 100).unwrap();
+ // only the clean point survives; the entity-bearing ones are skipped
+ assert_eq!(hills.len(), 1);
+ assert!((hills[0].phi - 47.32f64.to_radians()).abs() < 1e-12);
+ }
+}
diff --git a/crates/gipfel-core/tests/golden.rs b/crates/gipfel-core/tests/golden.rs
new file mode 100644
index 0000000..fb12e40
--- /dev/null
+++ b/crates/gipfel-core/tests/golden.rs
@@ -0,0 +1,511 @@
+//! Golden-value tests against the original C++ implementation.
+//!
+//! Goldens are generated by `tests/gen_golden.sh` (needs the built C++ tree).
+//! Run with `cargo test --test golden` (unit tests: `cargo test --lib`).
+
+use gipfel_core::hill;
+use gipfel_core::metadata::ImageMetaData;
+use gipfel_core::panorama::Panorama;
+use gipfel_core::projection::{
+ cylindrical::ProjectionCylindrical, rectilinear::ProjectionRectilinear, ProjArgs, Projection, ProjectionType,
+};
+
+fn golden(name: &str) -> Vec<String> {
+ let path = format!(
+ "{}/../../tests/golden/{}",
+ env!("CARGO_MANIFEST_DIR"),
+ name
+ );
+ std::fs::read_to_string(&path)
+ .unwrap_or_else(|_| panic!("missing golden file {} (run tests/gen_golden.sh)", path))
+ .lines()
+ .map(|l| l.to_string())
+ .collect()
+}
+
+fn data_path() -> Option<String> {
+ if let Ok(p) = std::env::var("GIPFEL_DATA") {
+ if std::path::Path::new(&p).exists() {
+ return Some(p);
+ }
+ }
+ let home = std::env::var("HOME").unwrap_or_default();
+ let p = format!("{}/gipfel/gipfel.dat", home);
+ std::path::Path::new(&p).exists().then_some(p)
+}
+
+/// relative-or-absolute closeness (libm differences between glibc and Rust)
+fn close(a: f64, b: f64) -> bool {
+ if a.is_nan() && b.is_nan() {
+ return true;
+ }
+ if a.is_infinite() || b.is_infinite() {
+ return a == b;
+ }
+ (a - b).abs() <= 1e-9f64.max(a.abs() * 1e-9)
+}
+
+#[test]
+fn generated_functions_match_c() {
+ let golden = golden("funcs.txt");
+ let rect = ProjectionRectilinear;
+ let cyl = ProjectionCylindrical;
+
+ assert_eq!(golden.len(), 200);
+
+ for (k, line) in golden.iter().enumerate() {
+ let kk = k as f64;
+ let c_view = -3.0 + 0.03 * kk;
+ let c_pitch = 0.10 * (0.11 * kk).sin();
+ let c_roll = 0.05 * (0.07 * kk).cos();
+ let scale = 2000.0 + 37.0 * (kk % 19.0);
+ let k0 = 0.001 * (0.31 * kk).sin();
+ let k1 = 0.0001 * (0.17 * kk).cos();
+ let x0 = 50.0 * (0.13 * kk).sin();
+ let m_view = c_view + 0.5 * (0.23 * kk).sin();
+ let m_elev = 0.05 * (0.19 * kk).cos();
+
+ let a = ProjArgs { c_view, c_pitch, c_roll, scale, k0, k1, x0, m_view, m_elev };
+
+ let mine: Vec<f64> = vec![
+ rect.mac_x(a),
+ rect.mac_y(a),
+ rect.mac_x_dc_view(a),
+ rect.mac_x_dc_pitch(a),
+ rect.mac_x_dc_roll(a),
+ rect.mac_x_dscale(a),
+ rect.mac_x_dk0(a),
+ rect.mac_x_dk1(a),
+ rect.mac_x_dx0(a),
+ rect.mac_y_dc_view(a),
+ rect.mac_y_dc_pitch(a),
+ rect.mac_y_dc_roll(a),
+ rect.mac_y_dscale(a),
+ rect.mac_y_dk0(a),
+ rect.mac_y_dk1(a),
+ rect.mac_y_dx0(a),
+ cyl.mac_x(a),
+ cyl.mac_y(a),
+ cyl.mac_x_dc_view(a),
+ cyl.mac_x_dc_pitch(a),
+ cyl.mac_x_dc_roll(a),
+ cyl.mac_x_dscale(a),
+ cyl.mac_y_dc_view(a),
+ cyl.mac_y_dc_pitch(a),
+ cyl.mac_y_dc_roll(a),
+ cyl.mac_y_dscale(a),
+ ];
+
+ let theirs: Vec<f64> = line.split_whitespace().map(|t| t.parse().unwrap()).collect();
+ assert_eq!(mine.len(), theirs.len(), "line {}", k);
+
+ for (i, (m, t)) in mine.iter().zip(theirs.iter()).enumerate() {
+ assert!(close(*m, *t), "k={} fn#{}: rust {} != c {}", k, i, m, t);
+ }
+ }
+}
+
+/// derivatives of the generated code vs. central finite differences
+#[test]
+fn derivatives_match_finite_differences() {
+ let rect = ProjectionRectilinear;
+ let base = ProjArgs {
+ c_view: 0.3,
+ c_pitch: 0.05,
+ c_roll: 0.02,
+ scale: 3000.0,
+ k0: 0.001,
+ k1: 0.0002,
+ x0: 12.0,
+ m_view: 0.5,
+ m_elev: 0.04,
+ };
+
+ let h = 1e-6f64;
+
+ // (arg index, analytic derivative fn)
+ let dx: [(usize, &dyn Fn(ProjArgs) -> f64); 7] = [
+ (0, &|a| rect.mac_x_dc_view(a)),
+ (1, &|a| rect.mac_x_dc_pitch(a)),
+ (2, &|a| rect.mac_x_dc_roll(a)),
+ (3, &|a| rect.mac_x_dscale(a)),
+ (4, &|a| rect.mac_x_dk0(a)),
+ (5, &|a| rect.mac_x_dk1(a)),
+ (6, &|a| rect.mac_x_dx0(a)),
+ ];
+ let dy: [(usize, &dyn Fn(ProjArgs) -> f64); 7] = [
+ (0, &|a| rect.mac_y_dc_view(a)),
+ (1, &|a| rect.mac_y_dc_pitch(a)),
+ (2, &|a| rect.mac_y_dc_roll(a)),
+ (3, &|a| rect.mac_y_dscale(a)),
+ (4, &|a| rect.mac_y_dk0(a)),
+ (5, &|a| rect.mac_y_dk1(a)),
+ (6, &|a| rect.mac_y_dx0(a)),
+ ];
+
+ let set = |a: ProjArgs, idx: usize, v: f64| -> ProjArgs {
+ let mut b = a;
+ match idx {
+ 0 => b.c_view = v,
+ 1 => b.c_pitch = v,
+ 2 => b.c_roll = v,
+ 3 => b.scale = v,
+ 4 => b.k0 = v,
+ 5 => b.k1 = v,
+ _ => b.x0 = v,
+ }
+ b
+ };
+
+ for (idx, f) in dx {
+ let plus = rect.mac_x(set(base, idx, base_val(&base, idx) + h));
+ let minus = rect.mac_x(set(base, idx, base_val(&base, idx) - h));
+ let fd = (plus - minus) / (2.0 * h);
+ let an = f(base);
+ assert!(
+ (an - fd).abs() <= 1e-4f64.max(an.abs() * 1e-5),
+ "dx deriv arg {}: analytic {} finite-diff {}",
+ idx,
+ an,
+ fd
+ );
+ }
+ for (idx, f) in dy {
+ let plus = rect.mac_y(set(base, idx, base_val(&base, idx) + h));
+ let minus = rect.mac_y(set(base, idx, base_val(&base, idx) - h));
+ let fd = (plus - minus) / (2.0 * h);
+ let an = f(base);
+ assert!(
+ (an - fd).abs() <= 1e-4f64.max(an.abs() * 1e-5),
+ "dy deriv arg {}: analytic {} finite-diff {}",
+ idx,
+ an,
+ fd
+ );
+ }
+}
+
+fn base_val(a: &ProjArgs, idx: usize) -> f64 {
+ match idx {
+ 0 => a.c_view,
+ 1 => a.c_pitch,
+ 2 => a.c_roll,
+ 3 => a.scale,
+ 4 => a.k0,
+ 5 => a.k1,
+ _ => a.x0,
+ }
+}
+
+/// full fit (geodesy + projection + GSL LM) against the C++ fitdump
+fn fit_golden(projtype: ProjectionType, file: &str) {
+ let Some(data) = data_path() else {
+ eprintln!("gipfel.dat not found, skipping");
+ return;
+ };
+ let golden = golden(file);
+
+ let mut pan = Panorama::new();
+ pan.load_data(&data).unwrap();
+ pan.set_projection(projtype);
+
+ pan.set_view_lat(47.36);
+ pan.set_view_long(11.15);
+ pan.set_view_height(2100.0);
+
+ // ground truth
+ pan.set_center_angle(20.0);
+ pan.set_pitch_angle(0.0);
+ pan.set_roll_angle(1.0);
+ pan.set_scale(3000.0);
+
+ // snapshot first 6 visible hills as observations
+ let mut obs: Vec<hill::Hill> = pan.visible[..6].iter().map(|&i| pan.hills[i].clone()).collect();
+ let n = obs.len();
+ let known: Vec<usize> = (0..n).collect();
+ // the C harness copies a_elev explicitly (C copy ctor omits it)
+ for (k, &i) in pan.visible[..6].iter().enumerate() {
+ obs[k].a_elev = pan.hills[i].a_elev;
+ }
+
+ // perturb initial guess
+ pan.set_center_angle(pan.get_center_angle() + 0.5);
+ pan.set_pitch_angle(0.3);
+ pan.set_roll_angle(0.5);
+ pan.set_scale(2800.0);
+
+ let ret = pan.comp_params_with(&obs, &known, true);
+
+ let g_ret: i32 = golden[0]["ret ".len()..].parse().unwrap();
+ assert_eq!(ret.is_ok(), g_ret == 0);
+
+ let g: Vec<f64> = golden[1..8]
+ .iter()
+ .map(|l| l.split_whitespace().nth(1).unwrap().parse().unwrap())
+ .collect();
+
+ let mine = [
+ pan.parms.a_center,
+ pan.parms.a_pitch,
+ pan.parms.a_roll,
+ pan.parms.scale,
+ pan.parms.k0,
+ pan.parms.k1,
+ pan.parms.x0,
+ ];
+ for (m, t) in mine.iter().zip(g.iter()) {
+ // solver results: allow small drift, exact zeros must stay ~zero
+ assert!(
+ (m - t).abs() <= 1e-6f64.max(m.abs() * 1e-6),
+ "fit param {} vs {}",
+ m,
+ t
+ );
+ }
+
+ // final coordinates
+ let proj = gipfel_core::projection::projection_from_type(projtype);
+ for line in &golden[8..] {
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ if parts.len() != 4 {
+ continue;
+ }
+ let i: usize = parts[1].parse().unwrap();
+ let tx: f64 = parts[2].parse().unwrap();
+ let ty: f64 = parts[3].parse().unwrap();
+ let (x, y) = proj.get_coordinates(obs[i].alph, obs[i].a_elev, &pan.parms);
+ assert!(close(x, tx), "hill {} x: {} vs {}", i, x, tx);
+ assert!(close(y, ty), "hill {} y: {} vs {}", i, y, ty);
+ }
+}
+
+#[test]
+fn fit_rectilinear_matches_c() {
+ fit_golden(ProjectionType::Rectilinear, "fit_rect.txt");
+}
+
+#[test]
+fn fit_cylindrical_matches_c() {
+ fit_golden(ProjectionType::Cylindrical, "fit_cyl.txt");
+}
+
+#[test]
+fn real_database_loads() {
+ let Some(data) = data_path() else {
+ eprintln!("gipfel.dat not found, skipping");
+ return;
+ };
+ let hills = hill::load(&data).unwrap();
+ assert!(hills.len() > 21000, "loaded {}", hills.len());
+ assert!(hills.iter().any(|h| h.name == "ZUGSPITZE"));
+}
+
+#[test]
+fn viewpoint_geometry() {
+ let Some(data) = data_path() else {
+ eprintln!("gipfel.dat not found, skipping");
+ return;
+ };
+ let mut pan = Panorama::new();
+ pan.load_data(&data).unwrap();
+ let idx = pan.get_pos("ZUGSPITZE").expect("ZUGSPITZE in db");
+ pan.set_viewpoint(Some(idx));
+
+ // NOTE: set_viewpoint re-sorts hills by azimuth; look the name up again
+ let idx = pan.get_pos("ZUGSPITZE").unwrap();
+ let m = &pan.hills[idx];
+ assert!((m.height - 2963.0).abs() < 1.0);
+ // distance to self is zero
+ assert!(pan.distance(m.phi, m.lam).abs() < 1e-9);
+ // a far hill: Mont Blanc (~45.83 N, 6.86 E)
+ let mb = pan.get_pos("MONT BLANC").expect("MONT BLANC in db");
+ let d = pan.distance(pan.hills[mb].phi, pan.hills[mb].lam);
+ let km = d * 6371000.785 / 1000.0;
+ assert!(km > 340.0 && km < 380.0, "Mont Blanc distance {} km", km);
+}
+
+#[test]
+fn export_hills_matches_c() {
+ let Some(data) = data_path() else {
+ eprintln!("gipfel.dat not found, skipping");
+ return;
+ };
+ let jpg = format!(
+ "{}/../../tests/golden/test.jpg",
+ env!("CARGO_MANIFEST_DIR")
+ );
+ let golden = golden("export.txt");
+
+ let (w, h) = gipfel_core::metadata::image_dimensions(&jpg).expect("test.jpg readable");
+ let mut md = ImageMetaData::new();
+ md.load_image(&jpg);
+
+ let mut pan = Panorama::new();
+ assert!(gipfel_core::metadata::apply_image_metadata(&mut pan, &md, w, h));
+ pan.load_data(&data).unwrap();
+ pan.set_height_dist_ratio(0.07);
+
+ let mut out = vec![
+ "#".to_string(),
+ "# name\theight\tx\ty\tdistance\tflags".to_string(),
+ "#".to_string(),
+ ];
+ out.extend(pan.export_hill_rows(w as i64, h as i64, false));
+
+ assert_eq!(out.len(), golden.len());
+ for (a, b) in out.iter().zip(golden.iter()) {
+ assert_eq!(a, b);
+ }
+}
+
+#[test]
+fn metadata_comment_roundtrip_and_c_format() {
+ let mut md = ImageMetaData::new();
+ md.longitude = 11.1533;
+ md.latitude = 47.3601;
+ md.height = 2100.5;
+ md.direction = 20.0;
+ md.pitch = 0.3;
+ md.roll = 1.0;
+ md.focal_length_35mm = 35.0;
+ md.projection_type = 1;
+ md.k0 = 0.001;
+ md.k1 = -0.0002;
+ md.x0 = 12.0;
+
+ let s = md.format_comment();
+ // exact C printf("%f") compatibility
+ assert_eq!(
+ s,
+ "gipfel: longitude 11.153300, latitude 47.360100, height 2100.500000, direction 20.000000, nick 0.300000, tilt 1.000000, focal_length_35mm 35.000000, projection type 1, k0 0.001000, k1 -0.000200, x0 12.000000"
+ );
+
+ let mut md2 = ImageMetaData::new();
+ assert!(md2.parse_gipfel_comment(s.as_bytes()));
+ assert!((md2.longitude - 11.1533).abs() < 1e-9);
+ assert!((md2.latitude - 47.3601).abs() < 1e-9);
+ assert!((md2.height - 2100.5).abs() < 1e-9);
+ assert!((md2.direction - 20.0).abs() < 1e-9);
+ assert_eq!(md2.projection_type, 1);
+ assert!((md2.k0 - 0.001).abs() < 1e-9);
+ assert!((md2.k1 + 0.0002).abs() < 1e-9);
+ assert!((md2.x0 - 12.0).abs() < 1e-9);
+
+ // short form (8 fields, pre-distortion files): k0/k1/x0 stay NaN
+ let short = b"gipfel: longitude 1.000000, latitude 2.000000, height 3.000000, direction 4.000000, nick 5.000000, tilt 6.000000, focal_length_35mm 7.000000, projection type 0";
+ let mut md3 = ImageMetaData::new();
+ assert!(md3.parse_gipfel_comment(short));
+ assert!(md3.k0.is_nan());
+ assert_eq!(md3.projection_type, 0);
+
+ // trailing garbage after x0 must not break parsing (sscanf semantics)
+ let mut md4 = ImageMetaData::new();
+ let mut s4 = s.clone();
+ s4.push_str(" some other comment junk");
+ assert!(md4.parse_gipfel_comment(s4.as_bytes()));
+ assert!((md4.x0 - 12.0).abs() < 1e-9);
+}
+
+/// The least-squares fit must run on a small stack: the LM solver keeps its
+/// working buffers (residuals, Jacobian, normal equations) on the heap, so a
+/// 128 KiB thread stack is plenty. Guards against regressions that move those
+/// buffers back onto the stack.
+#[test]
+fn fit_survives_small_stack() {
+ let Some(data) = data_path() else {
+ eprintln!("gipfel.dat not found, skipping");
+ return;
+ };
+ std::thread::Builder::new()
+ .stack_size(128 * 1024)
+ .spawn(move || {
+ let mut pan = Panorama::new();
+ pan.load_data(&data).unwrap();
+ pan.set_view_lat(47.36);
+ pan.set_view_long(11.15);
+ pan.set_view_height(2100.0);
+ pan.set_center_angle(20.0);
+ pan.set_pitch_angle(0.0);
+ pan.set_roll_angle(1.0);
+ pan.set_scale(3000.0);
+ let known: Vec<usize> = pan.visible[..4].to_vec();
+ // perturb so the solver actually iterates
+ pan.set_center_angle(20.5);
+ pan.set_scale(2800.0);
+ pan.comp_params(&known, true).unwrap();
+ })
+ .unwrap()
+ .join()
+ .unwrap();
+}
+
+/// Stitch resample against the C Stitch::resample + ScanImage pipeline.
+fn stitch_golden(
+ name: &str,
+ proj: ProjectionType,
+ center: f64,
+ pitch: f64,
+ roll: f64,
+ scale: f64,
+ k0: f64,
+ k1: f64,
+ bicubic: bool,
+) {
+ use gipfel_core::stitch::{self, ScanMode};
+
+ let ppm = format!(
+ "{}/../../tests/golden/stitch.ppm",
+ env!("CARGO_MANIFEST_DIR")
+ );
+ let img = image::open(&ppm)
+ .unwrap_or_else(|e| panic!("missing {}: {e}", ppm))
+ .to_rgb8();
+
+ let mut pan = Panorama::new();
+ pan.set_projection(proj);
+ pan.set_center_angle(center);
+ pan.set_pitch_angle(pitch);
+ pan.set_roll_angle(roll);
+ pan.set_scale(scale);
+ pan.set_distortion_params(k0, k1, 0.0);
+
+ let src = stitch::StitchSource { pan, img };
+ let mode = if bicubic { ScanMode::Bicubic } else { ScanMode::Nearest };
+ let out = stitch::resample(&[src], mode, 64, 48, 10.0, 30.0);
+
+ let mut checked = 0;
+ for line in golden(name) {
+ let f: Vec<&str> = line.split_whitespace().collect();
+ let (x, y, hit): (usize, usize, i32) = (f[0].parse().unwrap(), f[1].parse().unwrap(), f[2].parse().unwrap());
+ let want = [
+ f[3].parse::<u16>().unwrap(),
+ f[4].parse::<u16>().unwrap(),
+ f[5].parse::<u16>().unwrap(),
+ ];
+ let i = y * 64 + x;
+ assert_eq!(out.hit[i], hit == 1, "{name} pixel ({x},{y}) hit flag");
+ assert_eq!(out.pixels[i], want, "{name} pixel ({x},{y})");
+ checked += 1;
+ }
+ assert!(checked > 1000);
+}
+
+#[test]
+fn stitch_rect_nearest_matches_c() {
+ stitch_golden("stitch_rect_near.txt", ProjectionType::Rectilinear, 20.0, 2.0, 0.0, 200.0, 0.0, 0.0, false);
+}
+
+#[test]
+fn stitch_rect_bicubic_matches_c() {
+ stitch_golden("stitch_rect_bic.txt", ProjectionType::Rectilinear, 20.0, 2.0, 0.0, 200.0, 0.0, 0.0, true);
+}
+
+#[test]
+fn stitch_cyl_bicubic_matches_c() {
+ stitch_golden("stitch_cyl_bic.txt", ProjectionType::Cylindrical, 20.0, 2.0, 0.0, 200.0, 0.0, 0.0, true);
+}
+
+#[test]
+fn stitch_distorted_bicubic_matches_c() {
+ stitch_golden("stitch_dist_bic.txt", ProjectionType::Rectilinear, 20.0, 2.0, 3.0, 200.0, -0.3, 0.1, true);
+}
diff --git a/examples/Makefile b/examples/Makefile
deleted file mode 100644
index 062c88c..0000000
--- a/examples/Makefile
+++ /dev/null
@@ -1,63 +0,0 @@
-images := $(wildcard *.jpg)
-slides := $(addprefix gipfelweb/slides/, $(images))
-thumbs := $(addprefix gipfelweb/thumbs/, $(images))
-positions := $(addprefix gipfelweb/gipfel/, $(addsuffix .pos, $(basename $(images))))
-imgpos := $(addprefix gipfelweb/gipfel/, $(addsuffix .imgs, $(basename $(images))))
-hillpos := $(addprefix gipfelweb/gipfel/, $(addsuffix .hills, $(basename $(images))))
-html := $(addprefix gipfelweb/, $(addsuffix .html, $(basename $(images))))
-htmlhills := $(addprefix gipfelweb/hills/, $(addsuffix .html, $(basename $(images))))
-
-all:
- echo "make gipfelweb - create a HTML based we of gipfel oriented"
- echo "images in the current directory."
-
-gipfelweb: dirs $(slides) $(thumbs) $(imgpos) $(hillpos) $(html) $(htmlhills)
-
-clean:
- rm -rf gipfelweb/gipfel
-
-clobber:
- rm -rf gipfelweb
-
-dirs:
- mkdir -p gipfelweb/slides
- mkdir -p gipfelweb/thumbs
- mkdir -p gipfelweb/hills
- mkdir -p gipfelweb/gipfel
-
-gipfelweb/slides/%.jpg: %.jpg
- anytopnm $< | pamscale -f lanczos -xyfit 20000 500 | pnmtojpeg -quality 94 > $@
- jhead -te $< $@
-
-gipfelweb/thumbs/%.jpg: %.jpg
- anytopnm $< | pamscale -f point -xyfit 400 50 | pnmtojpeg -quality 80 > $@
-
-gipfelweb/gipfel/%.pos: %.jpg
- gipfel -p $< > $@
-
-gipfelweb/gipfel/%.imgs: gipfelweb/slides/%.jpg %.opts gipfelweb/gipfel/index.gipfel
- gipfel `cat $*.opts` -e gipfelweb/gipfel/index.gipfel $< > $@
-
-gipfelweb/gipfel/%.imgs: gipfelweb/slides/%.jpg gipfelweb/gipfel/index.gipfel
- gipfel -V 0.05 -e gipfelweb/gipfel/index.gipfel $< > $@
-
-gipfelweb/gipfel/%.hills: gipfelweb/slides/%.jpg %.opts
- gipfel `cat $*.opts` -E $< > $@
-
-gipfelweb/gipfel/%.hills: gipfelweb/slides/%.jpg
- gipfel -V 0.05 -E $< > $@
-
-gipfelweb/gipfel/index.gipfel: $(positions)
- cat $(positions) > gipfelweb/gipfel/index.gipfel
-
-gipfelweb/%.html: gipfelweb/slides/%.jpg gipfelweb/gipfel/%.imgs %.title $(thumbs)
- gipfel2html images "`cat $*.title`" $* > $@
-
-gipfelweb/%.html: gipfelweb/slides/%.jpg gipfelweb/gipfel/%.imgs $(thumbs)
- gipfel2html images $* $* > $@
-
-gipfelweb/hills/%.html: gipfelweb/slides/%.jpg gipfelweb/gipfel/%.hills %.title
- gipfel2html hills "`cat $*.title`" $* > $@
-
-gipfelweb/hills/%.html: gipfelweb/slides/%.jpg gipfelweb/gipfel/%.hills
- gipfel2html hills $* $* > $@
diff --git a/examples/gipfel2html b/examples/gipfel2html
deleted file mode 100755
index c8df477..0000000
--- a/examples/gipfel2html
+++ /dev/null
@@ -1,136 +0,0 @@
-#!/bin/sh
-
-MODE="$1"
-TITLE="$2"
-FILE="$3"
-
-if [ "$MODE" = "images" ]; then
- DATAFILE="gipfelweb/gipfel/${FILE}.imgs"
-else
- DATAFILE="gipfelweb/gipfel/${FILE}.hills"
-fi
-
-cat << EOF
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
-<html>
-<head>
-EOF
-
-echo "<title>$TITLE</title>"
-
-cat << EOF
-<style type="text/css">
-#gipfel { display: block; position: relative; margin: 0; padding: 0; }
-#gipfel li { visibility: hidden; list-style-type: none; position: absolute; background: url(25w.png); border: 1px solid black; font-family: sans-serif; font-size: x-small; color: #000; padding: 0; line-height: 1.3em; overflow: hidden; }
-#gipfel span { visibility: hidden; position: absolute; left: 0; right: 0; display: block; padding: 5px; }
-#gipfel:hover li { visibility: visible; }
-#gipfel li:hover span {visibility: visible;}
-EOF
-
-IFS=" "
-i=0
-grep -v "^#" ${DATAFILE} | \
-while read name height x y dist flags dummy; do
- if [ "$MODE" = "images" ]; then
- thumb_w=`identify -format %w gipfelweb/thumbs/${name}`
- thumb_h=`identify -format %h gipfelweb/thumbs/${name}`
-
- echo "#id_${i} {left:$((${x} - 10 / 2))px; top: $((${y} - 6 / 2))px; width: 10px; height: 6px;}"
- echo "#id_${i}:hover {width: ${thumb_w}px; height: ${thumb_h}px; background: url(thumbs/${name});}"
- else
- echo "#id_${i} {left:$((${x} - 4 / 2))px; top: $((${y} - 4 / 2))px; width: 4px; height: 4px;}"
- echo "#id_${i}:hover {width: 200px; height: 20px;}"
- fi
-
- i=$((${i} + 1))
-done
-
-cat << EOF
-</style></head>
-<body bgcolor="#919faa">
-<br/>
-<br/>
-EOF
-
-echo "<h3>$TITLE</h3>"
-
-if [ "$MODE" = "images" ]; then
- echo "<a href=\"hills/${FILE}.html\">show hill names</a>"
-else
- echo "<a href=\"../${FILE}.html\">back to gipfelweb</a>"
-fi
-
-cat << EOF
-<table border="0" cellpadding="0" cellspacing="1" bgcolor="black">
-<tr><td>
-<table border="0" cellpadding="10" cellspacing="0" bgcolor="white">
-<tr><td>
-<div id="gipfel">
-EOF
-
-if [ "$MODE" = "images" ]; then
- echo "<img src=\"slides/${FILE}.jpg\"/>"
-else
- echo "<img src=\"../slides/${FILE}.jpg\"/>"
-fi
-
-
-echo "<ul>"
-i=0
-grep -v "^#" ${DATAFILE} | \
-while read name height x y dist flags dummy; do
- if [ "$MODE" = "images" ]; then
- THUMB_TITLE="${name%.jpg}"
- if [ -e "${name%.jpg}.title" ]; then
- THUMB_TITLE=`cat ${name%.jpg}.title`
- fi
- echo "<li id=\"id_${i}\"><a href=\"${name%.jpg}.html\"><span>${THUMB_TITLE} (${height}m)</span></a></li>"
- else
- echo "<li id=\"id_${i}\"><span>${name} (${height}m)</span></li>"
- fi
-
- i=$((${i} + 1))
-done
-echo "</ul>"
-
-cat << EOF
-</div>
-</td></tr>
-</table>
-</td></tr>
-</table>
-<br/>
-<b>Created with <a href="http://www.ecademix.com/JohannesHofmann/gipfel.html">gipfel</a></b>
-<br/>
-<br/>
-EOF
-
-if [ "$MODE" = "images" ]; then
- echo Move the mouse over the image to see other images.
-else
- echo Move the mouse over the image to see known mountains.
-fi
-
-cat << EOF
-<br/>
-Move the mouse over the rectangle to see details.
-<br/>
-<br/>
-This image is visible on these images:<br/>
-EOF
-
-grep -l ${FILE} gipfelweb/gipfel/*.imgs | \
-while read f; do
- img="`basename ${f%.imgs}`"
- if [ "$MODE" = "images" ]; then
- echo "<a href=\"${img}.html\"><img src=\"thumbs/${img}.jpg\"></a><br/>"
- else
- echo "<a href=\"../${img}.html\"><img src=\"../thumbs/${img}.jpg\"></a><br/>"
- fi
-done
-
-
-cat << EOF
-</body>
-</html>
-EOF
diff --git a/gipfel.spec b/gipfel.spec
deleted file mode 100644
index eed33bb..0000000
--- a/gipfel.spec
+++ /dev/null
@@ -1,56 +0,0 @@
-Name: gipfel
-Summary: gipfel - Photogrammetry For Mountain Images
-Version: 0.4.0
-Release: 4.0
-URL: http://www.ecademix.com/JohannesHofmann/gipfel.html
-BuildRoot: %{_tmppath}/%{name}-%{version}-build
-Source0: %{name}-%{version}.tar.gz
-License: GPL
-Group: Productivity/Scientific/Other
-
-BuildRequires: binutils gcc gcc-c++ libstdc++-devel gsl-devel fltk-devel libjpeg-devel libtiff-devel libpng-devel
-
-%if 0%{?fedora_version}
-BuildRequires: exiv2-devel
-%else
-BuildRequires: libexiv2-devel
-%endif
-
-%if 0%{?suse_version}
-BuildRequires: xorg-x11-libXext-devel
-%endif
-
-%description
-gipfel helps to find the names of mountains or points of interest
-on a picture.
-It uses a database containing names and GPS data. With the given viewpoint
-(the point from which the picture was taken) and two known mountains
-on the picture, gipfel can compute all parameters needed to compute the
-positions of other mountains on the picture.
-gipfel can also be used to play around with the parameters manually.
-%prep
-%setup -q
-%build
-./configure --prefix=%{_prefix} --docdir=%{_defaultdocdir}/%{name} || cat config.log
-make
-%install
-make DESTDIR=$RPM_BUILD_ROOT install-strip
-rm -rf $RPM_BUILD_ROOT/%{_defaultdocdir}/%{name}
-%files
-%defattr(-, root, root)
-%doc NEWS README
-%{_bindir}/%{name}
-%dir %{_datadir}/%{name}
-%{_datadir}/%{name}/gipfel.dat
-
-%changelog
-* Fri Jan 17 2014 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-- update to gipfel-0.4.0
-* Thu Jul 2 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-- update to gipfel-0.3.2
-* Tue Apr 7 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-- update to gipfel-0.3.1
-* Thu Mar 26 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-- update to gipfel-0.2.9
-* Sun Mar 22 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-- initial revision
diff --git a/src/Fl_Search_Chooser.H b/src/Fl_Search_Chooser.H
deleted file mode 100644
index 37ed431..0000000
--- a/src/Fl_Search_Chooser.H
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef FL_SEARCH_CHOOSER_H
-#define FL_SEARCH_CHOOSER_H
-
-#include <FL/Fl_Window.H>
-#include <FL/Fl_Select_Browser.H>
-#include <FL/Fl_Input.H>
-
-class Fl_Search_Browser : public Fl_Select_Browser {
- private:
- int find_prefix(const char *p, int s, int e);
-
- public:
- Fl_Search_Browser(int X, int Y, int W, int H, const char *c):Fl_Select_Browser(X,Y,W,H,c){};
-
- int find_prefix(const char *p);
-};
-
-class Fl_Search_Chooser : public Fl_Window {
- private:
- Fl_Search_Browser *sb;
- int visible_focus;
-
- static void input_cb(Fl_Input* in, void*c);
- static void ok_cb(Fl_Input* in, void*c);
- static void cancel_cb(Fl_Input* in, void*c);
-
- public:
- Fl_Search_Chooser(const char *title);
-
- void close();
- void add(const char *t, void *d);
- void *data();
- int handle(int event);
-};
-
-#endif
diff --git a/src/Fl_Search_Chooser.cxx b/src/Fl_Search_Chooser.cxx
deleted file mode 100644
index 286516c..0000000
--- a/src/Fl_Search_Chooser.cxx
+++ /dev/null
@@ -1,137 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <string.h>
-#include <FL/Fl.H>
-#include <FL/Fl_Window.H>
-#include <FL/Fl_Button.H>
-#include "Fl_Search_Chooser.H"
-
-int
-Fl_Search_Browser::find_prefix(const char *p) {
- int i = find_prefix(p, 1, size());
- if (i == -1) {
- return 1;
- } else {
- deselect();
- middleline(i);
- select(i);
- return 0;
- }
-}
-
-int
-Fl_Search_Browser::find_prefix(const char *p, int s, int e) {
- if (s < 0 || e > size() || s > e) {
- fprintf(stderr, "Invalid search range %d %d\n", s, e);
- return 1;
- } else if (e - s <= 1) {
- if (strncasecmp(p, text(s), strlen(p)) == 0) {
- return s;
- } else if (strncasecmp(p, text(e), strlen(p)) == 0){
- return e;
- } else {
- return -1;
- }
- } else {
- int med = s + (e - s) / 2;
- if (strncasecmp(p, text(med), strlen(p)) > 0) {
- return find_prefix(p, med, e);
- } else {
- return find_prefix(p, s, med);
- }
- }
-}
-
-void
-Fl_Search_Chooser::input_cb(Fl_Input* in, void*c) {
- Fl_Search_Browser *sb = ((Fl_Search_Chooser *) c)->sb;
- sb->find_prefix(in->value());
-}
-
-void
-Fl_Search_Chooser::ok_cb(Fl_Input* in, void*c) {
- Fl_Search_Chooser *sc = (Fl_Search_Chooser *) c;
- sc->close();
-}
-
-void
-Fl_Search_Chooser::cancel_cb(Fl_Input* in, void*c) {
- Fl_Search_Chooser *sc = (Fl_Search_Chooser *) c;
- sc->sb->deselect();
- sc->close();
-}
-
-class Fl_Search_Input : public Fl_Input {
- public:
- Fl_Search_Input(int x, int y, int w, int h, const char *title) :
- Fl_Input(x, y, w, h, title) {}
- int handle(int event) {
- if (event == FL_KEYBOARD &&
- (Fl::event_key() == FL_Up || Fl::event_key() == FL_Down)) {
- return 0;
- } else {
- return Fl_Input::handle(event);
- }
- }
-};
-
-Fl_Search_Chooser::Fl_Search_Chooser(const char *title) : Fl_Window(320, 320, title?title:"Choose") {
- callback((Fl_Callback*) cancel_cb, this);
- visible_focus = Fl::visible_focus();
- Fl::visible_focus(0);
- Fl_Group *g = new Fl_Group(10, 10, w() - 10, h() - 10);
- sb = new Fl_Search_Browser(g->x(), g->y(), g->w() , g->h() - 100, NULL);
- sb->type(FL_HOLD_BROWSER);
- Fl_Input *in = new Fl_Search_Input(g->x()+50, g->h()-80, g->w()-80, 20, "Search");
- in->callback((Fl_Callback*) input_cb, this);
- in->when(FL_WHEN_CHANGED);
- Fl_Button *cancel_b = new Fl_Button(g->w()-200, g->h()-30, 80, 25, "Cancel");
- cancel_b->callback((Fl_Callback*) cancel_cb, this);
- Fl_Button *ok_b = new Fl_Button(g->w()-100, g->h()-30, 80, 25, "Ok");
- ok_b->callback((Fl_Callback*) ok_cb, this);
- Fl::focus(in);
- g->end();
- end();
-}
-
-void
-Fl_Search_Chooser::close() {
- hide();
- Fl::visible_focus(visible_focus);
-}
-
-void
-Fl_Search_Chooser::add(const char *t, void *d) {
- sb->add(t, d);
-}
-
-void *
-Fl_Search_Chooser::data() {
- int v = sb->value();
- if (v) {
- return sb->data(v);
- } else {
- return NULL;
- }
-}
-
-int
-Fl_Search_Chooser::handle(int event) {
- switch(event) {
- case FL_KEYBOARD:
- int key = Fl::event_key();
-
- if (key == FL_Up || key == FL_Down) {
- return sb->handle(event);
- } else if (key == FL_Enter) {
- close();
- }
- }
-
- return Fl_Window::handle(event);
-}
diff --git a/src/Fl_Value_Dial.H b/src/Fl_Value_Dial.H
deleted file mode 100644
index aa09407..0000000
--- a/src/Fl_Value_Dial.H
+++ /dev/null
@@ -1,29 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef Fl_Value_Dial_H
-#define Fl_Value_Dial_H
-
-#include <FL/Fl_Dial.H>
-
-class Fl_Value_Dial : public Fl_Dial {
- private:
- uchar textfont_, textsize_;
- unsigned textcolor_;
- public:
- void draw();
- int handle(int event);
-
- Fl_Value_Dial(int x,int y,int w,int h, const char *l = 0);
- Fl_Font textfont() const {return (Fl_Font)textfont_;}
- void textfont(uchar s) {textfont_ = s;}
- uchar textsize() const {return textsize_;}
- void textsize(uchar s) {textsize_ = s;}
- Fl_Color textcolor() const {return (Fl_Color)textcolor_;}
- void textcolor(unsigned s) {textcolor_ = s;}
-};
-
-#endif
diff --git a/src/Fl_Value_Dial.cxx b/src/Fl_Value_Dial.cxx
deleted file mode 100644
index 9b54458..0000000
--- a/src/Fl_Value_Dial.cxx
+++ /dev/null
@@ -1,67 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <FL/Fl.H>
-#include <FL/fl_draw.H>
-#include <math.h>
-#include "Fl_Value_Dial.H"
-
-Fl_Value_Dial::Fl_Value_Dial(int X, int Y, int W, int H, const char*l)
- : Fl_Dial(X,Y,W,H,l)
-{
- step(1,100);
- textfont_ = FL_HELVETICA;
- textsize_ = 10;
- textcolor_ = FL_BLACK;
-}
-
-void Fl_Value_Dial::draw() {
- int sxx = x(), syy = y(), sww = w(), shh = h();
- int bxx = x(), byy = y(), bww = w(), bhh = h();
- char buf[128];
-
- if (damage()&FL_DAMAGE_ALL) draw_box(box(),sxx,syy,sww,shh,color());
- Fl_Dial::draw(sxx+Fl::box_dx(box()),
- syy+Fl::box_dy(box()),
- sww-Fl::box_dw(box()),
- shh-Fl::box_dh(box()));
-
- format(buf); // prints at most 20 chars
- fl_font(textfont(), textsize());
-
- fl_color(active_r() ? textcolor() : fl_inactive(textcolor()));
- fl_draw(buf, bxx, byy + fl_height() - 2, bww, bhh, FL_ALIGN_TOP);
-}
-
-int Fl_Value_Dial::handle(int event) {
- switch (event) {
- case FL_KEYBOARD :
- switch (Fl::event_key()) {
- case FL_Left:
- handle_drag(clamp(increment(value(),-1)));
- handle_release();
- return 1;
- case FL_Right:
- handle_drag(clamp(increment(value(),1)));
- handle_release();
- return 1;
- default:
- return 0;
- }
- break;
- case FL_FOCUS :
- case FL_UNFOCUS :
- if (Fl::visible_focus()) {
- redraw();
- return 1;
- } else return 0;
- case FL_ENTER :
- case FL_LEAVE :
- return 1;
- default:
- return Fl_Dial::handle(event);
- }
-}
diff --git a/src/GipfelWidget.H b/src/GipfelWidget.H
deleted file mode 100644
index db67d5e..0000000
--- a/src/GipfelWidget.H
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// Copyright 2006 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef GipfelWidget_H
-#define GipfelWidget_H
-
-#include <stdio.h>
-
-#include <FL/Fl_Group.H>
-#include <FL/Fl_Menu_Button.H>
-
-#include "Panorama.H"
-#include "ImageMetaData.H"
-#include "ScanImage.H"
-
-class GipfelWidget : public Fl_Group {
- private:
- Fl_Image *img;
- Hill *cur_mountain, *focused_mountain;
- Hills *track_points;
- Hills *known_hills;
- Panorama *pan;
- char *img_file;
- double track_width;
- bool have_gipfel_info;
- bool show_hidden;
- ImageMetaData *md;
- int mouse_x, mouse_y;
- char focused_mountain_label[128];
- void (*params_changed_cb)();
-
- int handle(int event);
- Hill * find_mountain(Hills *mnts, int m_x, int m_y);
- int toggle_known_mountain(int m_x, int m_y);
- int set_mountain(int m_x, int m_y);
- void set_labels(Hills *v);
- int get_rel_track_width(Hill *m);
-
- static void find_peak_cb(Fl_Widget *o, void *f);
- static void toggle_hidden_cb(Fl_Widget *o, void *f);
-
- public:
- GipfelWidget(int X,int Y,int W, int H, void (*changed_cb)());
-
- int load_image(char *file);
- int save_image(char *file);
- int export_hills(const char *file, FILE *fp);
- const char * get_image_filename();
- int load_data(const char *file);
- int load_track(const char *file);
- int set_viewpoint(const char *pos);
- void set_viewpoint(const Hill *m);
- void set_center_angle(double a);
- void set_nick_angle(double a);
- void set_tilt_angle(double a);
- void set_focal_length_35mm(double s);
- void set_height_dist_ratio(double r);
- void set_hide_value(double h);
- void set_show_hidden(bool h);
- void set_view_lat(double v);
- void set_view_long(double v);
- void set_view_height(double v);
- const char * get_viewpoint() { return pan->get_viewpoint(); };
- double get_center_angle() { return pan->get_center_angle(); };
- double get_nick_angle() { return pan->get_nick_angle(); };
- double get_tilt_angle() { return pan->get_tilt_angle(); };
- double get_focal_length_35mm();
- double get_height_dist_ratio() { return pan->get_height_dist_ratio(); };
- double get_view_lat() { return pan->get_view_lat(); };
- double get_view_long() { return pan->get_view_long(); };
- double get_view_height() { return pan->get_view_height(); };
- void set_track_width(double w);
- ProjectionLSQ::Projection_t projection() {
- return pan->get_projection();
- };
- void projection(ProjectionLSQ::Projection_t p);
- void get_distortion_params(double *k0, double *k1, double *x0) {
- pan->get_distortion_params(k0, k1, x0);
- };
- void set_distortion_params(double k0, double k1, double x0);
- Hills *get_mountains() { return pan->get_mountains(); };
- int comp_params();
- int get_pixel(ScanImage::mode_t m,
- double a_alph, double a_nick, int *r, int *g, int *b);
- int get_distortion_profile_name(char *buf, int buflen);
- int save_distortion_params(const char *prof_name, int force);
- int load_distortion_params(const char *prof_name);
- void draw();
-};
-#endif
diff --git a/src/GipfelWidget.cxx b/src/GipfelWidget.cxx
deleted file mode 100644
index 30172a1..0000000
--- a/src/GipfelWidget.cxx
+++ /dev/null
@@ -1,822 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <math.h>
-#include <algorithm>
-
-#include <FL/Fl.H>
-#include <FL/Fl_Menu_Item.H>
-#include <FL/Fl_Shared_Image.H>
-#include <FL/Fl_JPEG_Image.H>
-#include <FL/Fl_Preferences.H>
-#include <FL/fl_draw.H>
-
-#include "Fl_Search_Chooser.H"
-#include "choose_hill.H"
-#include "ScanImage.H"
-#include "GipfelWidget.H"
-
-#define CROSS_SIZE 2
-
-static double pi_d, deg2rad;
-
-GipfelWidget::GipfelWidget(int X,int Y,int W, int H, void (*changed_cb)()): Fl_Group(X, Y, W, H) {
- end();
- pi_d = asin(1.0) * 2.0;
- deg2rad = pi_d / 180.0;
- img = NULL;
- pan = new Panorama();
- cur_mountain = NULL;
- focused_mountain = NULL;
- known_hills = new Hills();
- img_file = NULL;
- track_width = 200.0;
- show_hidden = false;
- have_gipfel_info = false;
- md = new ImageMetaData();
- track_points = NULL;
- fl_register_images();
- mouse_x = mouse_y = 0;
- params_changed_cb = changed_cb;
-}
-
-int
-GipfelWidget::load_image(char *file) {
- Fl_Image *new_img;
- double direction, nick, tilt, fl;
-
- new_img = new Fl_JPEG_Image(file);
-
- if (new_img == NULL)
- return 1;
-
- if (img)
- delete img;
-
- img = new_img;
-
- if (img_file)
- free(img_file);
-
- img_file = strdup(file);
-
- known_hills->clear();
-
- h(img->h());
- w(img->w());
-
- // try to retrieve gipfel data from JPEG meta data
- md->load_image(file);
- set_view_long(md->longitude());
- set_view_lat(md->latitude());
- set_view_height(md->height());
- projection((ProjectionLSQ::Projection_t) md->projection_type());
-
- have_gipfel_info = true;
- direction = md->direction();
- if (isnan(direction)) {
- set_center_angle(0.0);
- have_gipfel_info = false;
- } else {
- set_center_angle(direction);
- }
-
- nick = md->nick();
- if (isnan(nick)) {
- set_nick_angle(0.0);
- have_gipfel_info = false;
- } else {
- set_nick_angle(nick);
- }
-
- tilt = md->tilt();
- if (isnan(tilt)) {
- set_tilt_angle(0.0);
- have_gipfel_info = false;
- } else {
- set_tilt_angle(tilt);
- }
-
- fl = md->focal_length_35mm();
- if (isnan(fl) || fl == 0.0) {
- set_focal_length_35mm(35.0);
- have_gipfel_info = false;
- } else {
- set_focal_length_35mm(fl);
- }
-
- // try to get distortion parameters in the following ordering:
- // 1. gipfel data in JPEG comment
- // 2. matching distortion profile
- // 3. set the to 0.0, 0.0
- md->distortion_params(&pan->parms.k0, &pan->parms.k1, &pan->parms.x0);
- if (isnan(pan->parms.k0)) {
- char buf[1024];
- if (get_distortion_profile_name(buf, sizeof(buf)) == 0)
- load_distortion_params(buf);
-
- if (isnan(pan->parms.k0))
- pan->parms.k0 = 0.0;
- if (isnan(pan->parms.k1))
- pan->parms.k1 = 0.0;
- if (isnan(pan->parms.x0))
- pan->parms.x0 = 0.0;
- }
-
- return 0;
-}
-
-const char *
-GipfelWidget::get_image_filename() {
- return img_file;
-}
-
-int
-GipfelWidget::save_image(char *file) {
- if (img_file == NULL) {
- fprintf(stderr, "Nothing to save\n");
- return 1;
- }
-
- md->longitude(get_view_long());
- md->latitude(get_view_lat());
- md->height(get_view_height());
- md->direction(get_center_angle());
- md->nick(get_nick_angle());
- md->tilt(get_tilt_angle());
- md->focal_length_35mm(get_focal_length_35mm());
- md->projection_type((int) projection());
- md->distortion_params(pan->parms.k0, pan->parms.k1, pan->parms.x0);
-
- return md->save_image(img_file, file);
-}
-
-int
-GipfelWidget::load_data(const char *file) {
- int r;
-
- r = pan->load_data(file);
- set_labels(pan->get_visible_mountains());
-
- return r;
-}
-
-int
-GipfelWidget::load_track(const char *file) {
- if (track_points) {
- pan->remove_hills(Hill::TRACK_POINT);
- track_points->clobber();
- delete track_points;
- }
-
- track_points = new Hills();
-
- if (track_points->load(file) != 0) {
- delete track_points;
- track_points = NULL;
- return 1;
- }
-
- for (int i = 0; i < track_points->get_num(); i++)
- track_points->get(i)->flags |= Hill::TRACK_POINT;
-
- pan->add_hills(track_points);
- redraw();
-
- return 0;
-}
-
-int
-GipfelWidget::set_viewpoint(const char *pos) {
- int r;
-
- r = pan->set_viewpoint(pos);
- set_labels(pan->get_visible_mountains());
- redraw();
- return r;
-}
-
-void
-GipfelWidget::set_viewpoint(const Hill *m) {
- pan->set_viewpoint(m);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-static void
-draw_flag(int x, int y) {
- fl_polygon(x, y - 10, x, y - 20, x + 10, y - 15);
- fl_yxline(x, y, y - 10);
- fl_circle(x , y, 3);
-}
-
-void
-GipfelWidget::draw() {
- Hills *mnts;
- Hill *m;
- int i, height;
-
- if (img == NULL)
- return;
-
- fl_push_clip(x(), y(), w(), h());
- img->draw(x(),y(),w(),h(),0,0);
-
- /* hills */
- fl_font(FL_HELVETICA, 8);
- mnts = pan->get_visible_mountains();
-
- fl_color(FL_YELLOW);
- height = fl_height();
- for (i=0; i<mnts->get_num(); i++) {
- m = mnts->get(i);
- int m_x = w() / 2 + x() + (int) rint(m->x);
- int m_y = h() / 2 + y() + (int) rint(m->y);
-
- if ((m->flags & (Hill::DUPLIC|Hill::TRACK_POINT)) ||
- (!show_hidden && (m->flags & Hill::HIDDEN)) ||
- known_hills->contains(m) || m == focused_mountain)
- continue;
-
- if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2)
- continue;
-
- fl_xyline(m_x - CROSS_SIZE, m_y, m_x + CROSS_SIZE);
- fl_yxline(m_x, m_y + m->label_y - height, m_y + CROSS_SIZE);
- fl_xyline(m_x, m_y + m->label_y - height, m_x + m->label_x);
- }
-
- for (i=0; i<mnts->get_num(); i++) {
- m = mnts->get(i);
- int m_x = w() / 2 + x() + (int) rint(m->x);
- int m_y = h() / 2 + y() + (int) rint(m->y);
-
- if ((m->flags & (Hill::DUPLIC|Hill::TRACK_POINT)) ||
- (!show_hidden && (m->flags & Hill::HIDDEN)) || m == focused_mountain)
- continue;
-
- if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2)
- continue;
-
- if (known_hills->contains(m)) {
- if (known_hills->get_num() > 3)
- fl_color(FL_GREEN);
- else
- fl_color(FL_RED);
-
- draw_flag(m_x, m_y);
- fl_color(FL_BLACK);
- } else if (m->flags & Hill::HIDDEN) {
- fl_color(FL_BLUE);
- } else {
- fl_color(FL_BLACK);
- }
-
- fl_draw(m->name, m_x + 2, m_y + m->label_y);
- }
-
- if (focused_mountain) {
- m = focused_mountain;
- int m_x = w() / 2 + x() + (int) rint(m->x);
- int m_y = h() / 2 + y() + (int) rint(m->y);
-
- fl_color(FL_YELLOW);
-
- fl_rectf(m_x, m_y - height,
- (int) fl_width(focused_mountain_label) + 2, height + 2);
- fl_color(FL_BLACK);
- fl_draw(focused_mountain_label, m_x, m_y);
- }
-
-
- /* track */
- if (track_points && track_points->get_num() > 0) {
- int last_x = 0, last_y = 0, last_initialized = 0;
-
- for (i=1; i<track_points->get_num(); i++) {
- m = track_points->get(i);
- int m_x = w() / 2 + x() + (int) rint(m->x);
- int m_y = h() / 2 + y() + (int) rint(m->y);
-
- if (!(track_points->get(i)->flags & Hill::VISIBLE))
- continue;
-
- if (track_points->get(i)->flags & Hill::HIDDEN)
- fl_color(FL_BLUE);
- else
- fl_color(FL_RED);
-
- fl_line_style(FL_SOLID | FL_CAP_ROUND | FL_JOIN_ROUND,
- get_rel_track_width(track_points->get(i)));
- if (last_initialized) {
- fl_begin_line();
- fl_vertex(last_x, last_y);
- fl_vertex(m_x, m_y);
- fl_end_line();
- }
-
- last_x = m_x;
- last_y = m_y;
- last_initialized++;
- }
- fl_line_style(0);
- }
-
- fl_pop_clip();
-}
-
-static int
-overlap(double x1, double l1, double x2, double l2) {
- return x1 <= x2 + l2 && x1 + l1 >= x2;
-}
-
-void
-GipfelWidget::set_labels(Hills *v) {
- int height;
-
- if (!img)
- return;
-
-
- fl_font(FL_HELVETICA, 8);
- height = fl_height();
-
- for (int i = 0; i < v->get_num(); i++) {
- Hill *m = v->get(i);
- Hills colliding;
-
- if (m->flags & (Hill::DUPLIC | Hill::TRACK_POINT))
- continue;
-
- if (!show_hidden && (m->flags & Hill::HIDDEN))
- continue;
-
- if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2)
- continue;
-
- m->label_x = (int) fl_width(m->name) + 1;
- m->label_y = 0;
-
- for (int j = i - 1; j >= 0; j--) {
- Hill *n = v->get(j);
-
- if (n->flags & (Hill::DUPLIC | Hill::TRACK_POINT))
- continue;
-
- if (!show_hidden && (n->flags & Hill::HIDDEN))
- continue;
-
- if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2)
- continue;
-
- if (overlap(m->x, m->label_x, n->x - CROSS_SIZE, n->label_x))
- colliding.add(n);
- else
- break;
- }
-
- colliding.sort(Hills::SORT_LABEL_Y);
-
- for (int j = 0; j < colliding.get_num(); j++) {
- Hill *n = colliding.get(j);
-
- // Check for overlapping labels and
- // overlaps between labels and peak markers
- if (overlap(m->y + m->label_y - height, height,
- n->y + n->label_y - height, height) ||
- overlap(m->y + m->label_y - height,
- height, n->y - CROSS_SIZE, 2 * CROSS_SIZE)) {
-
- m->label_y = (int) rint(n->y + n->label_y - m->y - height - 2);
- }
- }
- }
-}
-
-Hill *
-GipfelWidget::find_mountain(Hills *mnts, int m_x, int m_y) {
- Hill *m;
- int center_x = w() / 2;
- int center_y = h() / 2;
-
- for (int i = 0; i < mnts->get_num(); i++) {
- m = mnts->get(i);
-
- if (m_x - center_x >= m->x - 2 && m_x - center_x < m->x + 2 &&
- m_y - center_y >= m->y - 2 && m_y - center_y < m->y + 2)
- return m;
- }
-
- return NULL;
-}
-
-int
-GipfelWidget::toggle_known_mountain(int m_x, int m_y) {
- Hills *mnts = pan->get_visible_mountains();
- int center_x = w() / 2;
- int center_y = h() / 2;
-
- for (int i = 0; i < mnts->get_num(); i++) {
- Hill *m = mnts->get(i);
-
- if (m->flags & (Hill::DUPLIC | Hill::TRACK_POINT))
- continue;
-
- if (m_x - center_x >= m->x - 2 && m_x - center_x < m->x + 2 &&
- m_y - center_y >= m->y - 2 && m_y - center_y < m->y + 2) {
-
-
- if (known_hills->contains(m))
- known_hills->remove(m);
- else
- known_hills->add(m);
-
- redraw();
- return 0;
- }
- }
-
- cur_mountain = NULL;
- redraw();
- return 1;
-}
-
-int
-GipfelWidget::set_mountain(int m_x, int m_y) {
- int old_x, old_y, old_label_y;
- int center_x = w() / 2;
- int center_y = h() / 2;
-
- if (!cur_mountain)
- return 1;
-
- old_x = (int) rint(cur_mountain->x);
- old_y = (int) rint(cur_mountain->y);
- old_label_y = cur_mountain->label_y;
-
- cur_mountain->x = m_x - center_x;
- cur_mountain->y = m_y - center_y;
- cur_mountain->label_y = 0;
-
- damage(4, center_x + x() + old_x - 2*CROSS_SIZE - 1,
- center_y + y() + old_y + old_label_y - 2*CROSS_SIZE - 20,
- std::max(20, cur_mountain->label_x) + 2*CROSS_SIZE + 4,
- std::max(20, old_label_y) + 22 );
- damage(4,
- (int) rint(center_x + x() + cur_mountain->x - 2*CROSS_SIZE - 1),
- (int) rint(center_y + y() + cur_mountain->y + cur_mountain->label_y - 2*CROSS_SIZE - 20),
- std::max(20, cur_mountain->label_x) + 2*CROSS_SIZE + 4,
- std::max(20, cur_mountain->label_y) + 22 );
-
- return 0;
-}
-
-void
-GipfelWidget::set_center_angle(double a) {
- pan->set_center_angle(a);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_nick_angle(double a) {
- pan->set_nick_angle(a);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_tilt_angle(double a) {
- pan->set_tilt_angle(a);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_focal_length_35mm(double s) {
- int w = std::max(img->w(), img->h()); // assume sensor is wider than high
- pan->set_scale(s * (double) w / 35.0);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::projection(ProjectionLSQ::Projection_t p) {
- pan->set_projection(p);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_distortion_params(double k0, double k1, double x0) {
- pan->set_distortion_params(k0, k1, x0);
- redraw();
-}
-
-double
-GipfelWidget::get_focal_length_35mm() {
- int w;
-
- if (img == NULL)
- return NAN;
-
- w = std::max(img->w(), img->h()); // assume sensor is wider than high
- return pan->get_scale() * 35.0 / (double) w;
-}
-
-void
-GipfelWidget::find_peak_cb(Fl_Widget *, void *f) {
- GipfelWidget *g = (GipfelWidget*) f;
-
- Hill *m = choose_hill(g->pan->get_close_mountains(), "Find Peak");
- if (m) {
- if (!g->known_hills->contains(m))
- g->known_hills->add(m);
-
- m->flags |= Hill::VISIBLE;
- if (! g->pan->get_visible_mountains()->contains(m))
- g->pan->get_visible_mountains()->add(m);
- g->set_labels(g->pan->get_visible_mountains());
-
- g->cur_mountain = m;
- g->set_mountain(g->mouse_x, g->mouse_y);
- g->comp_params();
- }
-}
-
-void
-GipfelWidget::toggle_hidden_cb(Fl_Widget *, void *f) {
- GipfelWidget *g = (GipfelWidget*) f;
-
- Hill *m = g->find_mountain(g->pan->get_visible_mountains(), g->mouse_x, g->mouse_y);
- if (!m)
- return;
-
- if (m->flags & Hill::HIDDEN)
- m->flags &= ~Hill::HIDDEN;
- else
- m->flags |= Hill::HIDDEN;
-
- g->set_labels(g->pan->get_visible_mountains());
- g->redraw();
-}
-
-void
-GipfelWidget::set_height_dist_ratio(double r) {
- pan->set_height_dist_ratio(r);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_hide_value(double h) {
- pan->set_hide_value(h);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_show_hidden(bool h) {
- show_hidden = h;
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_view_lat(double v) {
- pan->set_view_lat(v);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_view_long(double v) {
- pan->set_view_long(v);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-void
-GipfelWidget::set_view_height(double v) {
- pan->set_view_height(v);
- set_labels(pan->get_visible_mountains());
- redraw();
-}
-
-int
-GipfelWidget::comp_params() {
- int ret;
-
- fl_cursor(FL_CURSOR_WAIT);
- ret = pan->comp_params(known_hills);
- set_labels(pan->get_visible_mountains());
- redraw();
- fl_cursor(FL_CURSOR_DEFAULT);
- if (params_changed_cb)
- params_changed_cb();
-
- return ret;
-}
-
-int
-GipfelWidget::get_rel_track_width(Hill *m) {
- double dist = pan->get_real_distance(m);
-
- return (int) rint(std::max((pan->get_scale()*track_width)/(dist*10.0), 1.0));
-}
-
-void
-GipfelWidget::set_track_width(double w) {
- track_width = w;
- redraw();
-}
-
-int
-GipfelWidget::handle(int event) {
- Hill *m;
-
- switch(event) {
- case FL_PUSH:
- mouse_x = Fl::event_x()-x();
- mouse_y = Fl::event_y()-y();
- if (Fl::event_button() == FL_LEFT_MOUSE) {
- m = find_mountain(known_hills, mouse_x, mouse_y);
- if (m)
- cur_mountain = m;
- } else if (Fl::event_button() == FL_MIDDLE_MOUSE) {
- toggle_known_mountain(mouse_x, mouse_y);
- if (focused_mountain) {
- focused_mountain = NULL;
- redraw();
- }
- } else if (Fl::event_button() == FL_RIGHT_MOUSE) {
- focused_mountain = NULL;
-
- Fl_Menu_Button rclick_menu(Fl::event_x_root(),
- Fl::event_y_root(), 80, 1);
- Hill *m = find_mountain(pan->get_visible_mountains(),
- mouse_x, mouse_y);
- char buf[1024];
-
- rclick_menu.add("Find Peak", 0, find_peak_cb, this);
- if (m) {
- if (m->flags & Hill::HIDDEN)
- snprintf(buf, sizeof(buf), "Unhide %s", m->name);
- else
- snprintf(buf, sizeof(buf), "Hide %s", m->name);
-
- rclick_menu.add(buf, 0, toggle_hidden_cb, this);
- }
-
- rclick_menu.popup();
- }
-
- Fl::focus(this);
- return 1;
- case FL_DRAG:
- set_mountain(Fl::event_x()-x(), Fl::event_y()-y());
- return 1;
- case FL_RELEASE:
- cur_mountain = NULL;
- if (known_hills->get_num() > 0)
- comp_params();
- return 1;
- case FL_ENTER:
- return 1;
- case FL_LEAVE:
- return 1;
- case FL_MOVE:
- m = find_mountain(pan->get_visible_mountains(), Fl::event_x()-x(), Fl::event_y()-y());
- if (m != focused_mountain && (!m || !known_hills->contains(m))) {
- if (m)
- snprintf(focused_mountain_label,
- sizeof(focused_mountain_label) - 1,
- "%s (%dm), distance %.2fkm",
- m->name, (int) m->height, pan->get_real_distance(m) / 1000.0);
-
- focused_mountain = m;
- redraw();
- }
- return 1;
- case FL_FOCUS:
- return 1;
- case FL_UNFOCUS:
- return 0;
- }
- return 0;
-}
-
-int
-GipfelWidget::export_hills(const char *file, FILE *fp) {
- Hills export_hills, *mnts;
-
- if (!have_gipfel_info) {
- fprintf(stderr, "No gipfel info available for %s.\n", img_file);
- return 0;
- }
-
- if (file) {
- if (export_hills.load(file) != 0)
- return 1;
-
- for (int i = 0; i < export_hills.get_num(); i++)
- export_hills.get(i)->flags |= Hill::EXPORT;
-
- pan->add_hills(&export_hills);
- }
-
- fprintf(fp, "#\n# name\theight\tx\ty\tdistance\tflags\n#\n");
-
- mnts = pan->get_visible_mountains();
- for (int i = 0; i < mnts->get_num(); i++) {
- Hill *m = mnts->get(i);
- int _x = (int) rint(m->x) + w() / 2;
- int _y = (int) rint(m->y) + h() / 2;
-
- if (m->flags & Hill::DUPLIC || m->flags & Hill::HIDDEN ||
- (file && !(m->flags & Hill::EXPORT))) {
- continue;
- }
-
- if (_x < 0 || _x > w() || _y < 0 || _y > h())
- continue;
-
- fprintf(fp, "%s\t%d\t%d\t%d\t%d\n",
- m->name, (int) rint(m->height), _x, _y,
- (int) rint(pan->get_real_distance(m)));
- }
-
- pan->remove_hills(Hill::EXPORT);
-
- return 0;
-}
-
-int
-GipfelWidget::get_pixel(ScanImage::mode_t m,
- double a_alph, double a_nick, int *r, int *g, int *b) {
- double px, py;
- int ret;
-
- if (img == NULL)
- return 1;
-
- if (pan->get_coordinates(a_alph, a_nick, &px, &py) != 0)
- return 1;
-
- ret = ScanImage::get_pixel(img, m, px + ((double) img->w()) / 2.0,
- py + ((double) img->h()) / 2.0, r, g, b);
-
- return ret;
-}
-
-int
-GipfelWidget::get_distortion_profile_name(char *buf, int buflen) {
- int n;
-
- if (md && md->manufacturer() && md->model()) {
- n = snprintf(buf, buflen, "%s_%s_%.2f_mm",
- md->manufacturer(), md->model(), md->focal_length());
-
- return n > buflen;
- } else {
- return 1;
- }
-}
-
-static Fl_Preferences dist_prefs(Fl_Preferences::USER,
- "Johannes.HofmannATgmx.de", "gipfel/DistortionProfiles");
-
-int
-GipfelWidget::load_distortion_params(const char *prof_name) {
- int ret = 0;
-
- Fl_Preferences prof(dist_prefs, prof_name);
- ret += prof.get("k0", pan->parms.k0, pan->parms.k0);
- ret += prof.get("k1", pan->parms.k1, pan->parms.k1);
- ret += prof.get("x0", pan->parms.x0, pan->parms.x0);
-
- return !ret;
-}
-
-int
-GipfelWidget::save_distortion_params(const char *prof_name, int force) {
- Fl_Preferences prof(dist_prefs, prof_name);
-
- if (!force && prof.entryExists("k0"))
- return 1;
-
- prof.set("k0", pan->parms.k0);
- prof.set("k1", pan->parms.k1);
- prof.set("x0", pan->parms.x0);
-
- return 0;
-}
diff --git a/src/Hill.H b/src/Hill.H
deleted file mode 100644
index c55db15..0000000
--- a/src/Hill.H
+++ /dev/null
@@ -1,69 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef HILL_H
-#define HILL_H
-
-class Hill;
-
-class Hill {
- public:
- typedef enum {
- DUPLIC = 0x01,
- TRACK_POINT = 0x02,
- VISIBLE = 0x04,
- HIDDEN = 0x08,
- EXPORT = 0x10,
- } flags_t;
-
- double phi, lam;
- double height;
- double alph;
- double a_nick;
- double dist;
- double x, y;
- int label_x, label_y;
- char *name;
- int flags;
-
- Hill(const char *n, double p, double l, double h);
- Hill(const Hill& h);
- Hill(double x_tmp, double y_tmp);
- ~Hill();
-};
-
-class Hills {
- private:
- int num, cap;
- Hill **m;
-
- public:
- typedef enum {
- SORT_ALPHA,
- SORT_PHI,
- SORT_NAME,
- SORT_LABEL_Y,
- SORT_X
- } SortType;
-
- Hills();
- Hills(const Hills *h);
- ~Hills();
-
- int load(const char *file);
- void mark_duplicates(double dist);
- void add(Hill *m);
- void remove(const Hill *m);
- void add(Hills *h);
- void sort(SortType t);
- void clear();
- void clobber();
- int contains(const Hill *m) const;
- inline int get_num() const { return num; };
- Hill *get(int n) const;
-};
-
-#endif
diff --git a/src/Hill.cxx b/src/Hill.cxx
deleted file mode 100644
index 2aad3f3..0000000
--- a/src/Hill.cxx
+++ /dev/null
@@ -1,341 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-extern "C" {
-#include "strsep.h"
-}
-#include "Hill.H"
-
-static double pi_d, deg2rad;
-
-Hill::Hill(const char *n, double p, double l, double h) {
- name = strdup(n);
- phi = p;
- lam = l;
- height = h;
- alph = 0.0;
- x = 0;
- y = 0;
- flags = 0;
-}
-
-Hill::Hill(const Hill& h) {
- name = strdup(h.name);
- phi = h.phi;
- lam = h.lam;
- height = h.height;
- alph = h.alph;
- dist = h.dist;
- x = h.x;
- y = h.y;
- label_x = h.label_x;
- label_y = h.label_y;
- flags = h.flags;
-}
-
-Hill::Hill(double x_tmp, double y_tmp) {
- name = NULL;
- phi = 0.0;
- lam = 0.0;
- height = 0.0;
- alph = 0.0;
- x = x_tmp;
- y = y_tmp;
-}
-
-Hill::~Hill() {
- if (name)
- free(name);
-}
-
-Hills::Hills() {
- num = 0;
- cap = 100;
- m = (Hill **) malloc(cap * sizeof(Hill *));
-
- pi_d = asin(1.0) * 2.0;
- deg2rad = pi_d / 180.0;
-}
-
-Hills::Hills(const Hills *h) {
- num = h->num;
- cap = h->cap;
- m = (Hill **) malloc(cap * sizeof(Hill *));
- memcpy(m, h->m, cap * sizeof(Hill *));
-
- pi_d = asin(1.0) * 2.0;
- deg2rad = pi_d / 180.0;
-}
-
-int
-Hills::load(const char *file) {
- FILE *fp;
- char buf[4000];
- char *vals[10];
- char **ap, *bp;
- double phi, lam, height;
- Hill *m;
- int n;
-
- fp = fopen(file, "r");
- if (!fp) {
- perror("fopen");
- return 1;
- }
-
- while (fgets(buf, sizeof(buf), fp)) {
- bp = buf;
- memset(vals, 0, sizeof(vals));
- n = 0;
- for (ap = vals; (*ap = strsep(&bp, ",")) != NULL;) {
- n++;
- if (++ap >= &vals[10])
- break;
- }
-
- // standard format including name and description
- if (n == 6 && vals[1] && vals [3] && vals[4] && vals[5]) {
- phi = atof(vals[3]) * deg2rad;
- lam = atof(vals[4]) * deg2rad;
- height = atof(vals[5]);
-
- m = new Hill(vals[1], phi, lam, height);
-
- add(m);
- // track point format
- } else if (n == 3 && vals[0] && vals[1] && vals[2]) {
- phi = atof(vals[0]) * deg2rad;
- lam = atof(vals[1]) * deg2rad;
- height = atof(vals[2]);
-
- m = new Hill("", phi, lam, height);
-
- add(m);
- }
- }
-
- fclose(fp);
-
- return 0;
-}
-
-void Hills::mark_duplicates(double dist) {
- Hill *m, *n;
- int i, j;
-
- sort(SORT_PHI);
-
- for(i = 0; i < get_num(); i++) {
- m = get(i);
-
- if (m) {
- if (m->flags & Hill::TRACK_POINT)
- continue;
-
- j = i + 1;
- n = get(j);
- while (n && fabs(n->phi - m->phi) <= dist) {
- if (! (n->flags & Hill::DUPLIC)) {
- if (fabs(n->lam - m->lam) <= dist &&
- fabs(n->height - m->height) <= 50.0 ) {
- n->flags |= Hill::DUPLIC;
- }
- }
- j = j + 1;
- n = get(j);
- }
- }
- }
-}
-
-Hills::~Hills() {
- if (m)
- free(m);
-}
-
-void
-Hills::add(Hill *m1) {
- if (num >= cap) {
- cap = cap ? cap * 2 : 100;
- m = (Hill **) realloc(m, cap * sizeof(Hill *));
- }
-
- m[num++] = m1;
-}
-
-void
-Hills::add(Hills *h) {
- for (int i=0; i<h->get_num(); i++)
- add(h->get(i));
-}
-
-static int
-comp_mountains_alpha(const void *n1, const void *n2) {
- Hill *m1 = *(Hill **)n1;
- Hill *m2 = *(Hill **)n2;
-
- if (m1 && m2) {
- if (m1->alph < m2->alph)
- return 1;
- else if (m1->alph > m2->alph)
- return -1;
- else
- return 0;
- } else {
- return 0;
- }
-}
-
-static int
-comp_mountains_phi(const void *n1, const void *n2) {
- Hill *m1 = *(Hill **)n1;
- Hill *m2 = *(Hill **)n2;
-
- if (m1 && m2) {
- if (m1->phi < m2->phi)
- return 1;
- else if (m1->phi > m2->phi)
- return -1;
- else
- return 0;
- } else {
- return 0;
- }
-}
-
-static int
-comp_mountains_name(const void *n1, const void *n2) {
- Hill *m1 = *(Hill **)n1;
- Hill *m2 = *(Hill **)n2;
- int r;
-
- if (m1 && m2) {
- r = strcasecmp(m1->name, m2->name);
- if (r == 0)
- return (int) (m1->height - m2->height);
- else
- return r;
- } else {
- return 0;
- }
-}
-
-static int
-comp_mountains_label_y(const void *n1, const void *n2) {
- Hill *m1 = *(Hill **)n1;
- Hill *m2 = *(Hill **)n2;
-
- if (m1 && m2) {
- if ((m2->y + m2->label_y) > (m1->y + m1->label_y))
- return 1;
- else if ((m2->y + m2->label_y) < (m1->y + m1->label_y))
- return -1;
- else
- return 0;
- } else {
- return 0;
- }
-}
-
-static int
-comp_mountains_x(const void *n1, const void *n2) {
- Hill *m1 = *(Hill **)n1;
- Hill *m2 = *(Hill **)n2;
-
- if (m1 && m2) {
- if (m2->x < m1->x)
- return 1;
- else if (m2->x > m1->x)
- return -1;
- else
- return 0;
- } else {
- return 0;
- }
-}
-
-void
-Hills::sort(SortType t) {
- int (*cmp)(const void *, const void *);
-
- if (!m || num < 2)
- return;
-
- switch (t) {
- case SORT_ALPHA:
- cmp = comp_mountains_alpha;
- break;
- case SORT_PHI:
- cmp = comp_mountains_phi;
- break;
- case SORT_NAME:
- cmp = comp_mountains_name;
- break;
- case SORT_LABEL_Y:
- cmp = comp_mountains_label_y;
- break;
- case SORT_X:
- cmp = comp_mountains_x;
- break;
- default:
- fprintf(stderr, "ERROR: Unknown sort type %d\n", t);
- return;
- }
-
- qsort(m, num, sizeof(Hill *), cmp);
-}
-
-void
-Hills::clear() {
- if (m) {
- free(m);
- m = NULL;
- }
- cap = 0;
- num = 0;
-}
-
-int
-Hills::contains(const Hill *m) const {
- for (int i = 0; i < get_num(); i++)
- if (get(i) == m)
- return 1;
-
- return 0;
-}
-
-void
-Hills::remove(const Hill *h) {
- for (int i = 0; i < get_num(); i++) {
- if (get(i) == h) {
- memmove(&m[i], &m[i+1], (get_num() - i - 1) * sizeof(Hill*));
- num--;
- }
- }
-}
-
-void
-Hills::clobber() {
- int i;
-
- for (i = 0; i < get_num(); i++)
- if (get(i))
- delete(get(i));
-
- clear();
-}
-
-Hill *
-Hills::get(int n) const {
- if (n < 0 || n >= num)
- return NULL;
- else
- return m[n];
-}
diff --git a/src/ImageMetaData.H b/src/ImageMetaData.H
deleted file mode 100644
index 109652d..0000000
--- a/src/ImageMetaData.H
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef IMAGE_META_DATA_H
-#define IMAGE_META_DATA_H
-
-class ImageMetaData {
- private:
- char *_manufacturer;
- char *_model;
- double _longitude;
- double _latitude;
- double _height;
- double _direction;
- double _nick;
- double _tilt;
- double _k0;
- double _k1;
- double _x0;
- double _focal_length;
- double _focal_length_35mm;
- double _scale;
- int _projection_type;
- int _have_gipfel_info;
-
- int load_image_jpgcom(char *name);
- int save_image_jpgcom(char *in_img, char *out_img);
- int load_image_exif(char *name);
- void clear();
-
- public:
- ImageMetaData();
- ~ImageMetaData();
-
- int load_image(char *name);
- int save_image(char *in_img, char *out_img);
-
- const char *manufacturer() {return _manufacturer;};
- const char *model() {return _model;};
- double longitude() {return _longitude;};
- double latitude() {return _latitude;};
- double height() {return _height;};
- double direction() {return _direction;};
- double nick() {return _nick;};
- double tilt() {return _tilt;};
- double focal_length() {return _focal_length;};
- double focal_length_35mm() {return _focal_length_35mm;};
- int projection_type() {return _projection_type;};
- void distortion_params(double *_k0, double *_k1, double *_x0);
-
- void longitude(double v) {_longitude = v;};
- void latitude(double v) {_latitude = v;};
- void height(double v) {_height = v;};
- void direction(double v) {_direction = v;};
- void nick(double v) {_nick = v;};
- void tilt(double v) {_tilt = v;};
- void focal_length_35mm(double v) {_focal_length_35mm = v;};
- void projection_type(int v) {_projection_type = v;};
- void distortion_params(double k0, double k1, double x0);
-};
-
-#endif
diff --git a/src/ImageMetaData.cxx b/src/ImageMetaData.cxx
deleted file mode 100644
index fa334aa..0000000
--- a/src/ImageMetaData.cxx
+++ /dev/null
@@ -1,322 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <stdio.h>
-#include <errno.h>
-#include <math.h>
-#include <string.h>
-#include <fcntl.h>
-#include <libgen.h>
-#include <assert.h>
-#include <sys/param.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-#include <exiv2/exiv2.hpp>
-
-#include "../config.h"
-#include "ImageMetaData.H"
-
-#if !defined(O_BINARY)
-#define O_BINARY 0
-#endif
-
-ImageMetaData::ImageMetaData() {
- _manufacturer = NULL;
- _model = NULL;
- clear();
-}
-
-ImageMetaData::~ImageMetaData() {
- if (_manufacturer)
- free(_manufacturer);
- if (_model)
- free(_model);
-}
-
-void
-ImageMetaData::clear() {
- if (_manufacturer)
- free(_manufacturer);
- _manufacturer = NULL;
- if (_model)
- free(_model);
- _model = NULL;
- _longitude = NAN;
- _latitude = NAN;
- _height = NAN;
- _direction = NAN;
- _nick = NAN;
- _tilt = NAN;
- _k0 = NAN;
- _k1 = NAN;
- _x0 = NAN;
- _focal_length = NAN;
- _focal_length_35mm = NAN;
- _scale = NAN;
- _projection_type = 0;
-}
-
-int
-ImageMetaData::load_image(char *name) {
- clear();
- load_image_jpgcom(name);
- load_image_exif(name); // fill missing values from exif data
- return 0;
-}
-
-int
-ImageMetaData::save_image(char *in_img, char *out_img) {
- return save_image_jpgcom(in_img, out_img);
-}
-
-static void
-exifSetValue(double *destVal, Exiv2::ExifData *exifData, const char *name) {
- Exiv2::ExifKey key(name);
- Exiv2::ExifData::iterator pos = exifData->findKey(key);
- pos = exifData->findKey(key);
- if (pos != exifData->end() && pos->toFloat() >= 0)
- *destVal = pos->toFloat();
-}
-
-static void
-exifSetCoordinate(double *destVal, Exiv2::ExifData *exifData, const char *name) {
- Exiv2::ExifKey key(name);
- Exiv2::ExifData::iterator pos = exifData->findKey(key);
-
- if (pos != exifData->end()) {
- if (pos->toFloat() >= 0)
- *destVal = pos->toFloat();
- if (pos->toFloat(1) >= 0)
- *destVal += pos->toFloat(1) / 60;
- if (pos->toFloat(2) >= 0)
- *destVal += pos->toFloat(2) / 3600;
- }
-}
-
-int
-ImageMetaData::load_image_exif(char *name) {
-#if EXIV2_TEST_VERSION(0,28,0)
- Exiv2::Image::UniquePtr image;
-#else
- Exiv2::Image::AutoPtr image;
-#endif
-
- try {
- image = Exiv2::ImageFactory::open(name);
- image->readMetadata();
- } catch (const Exiv2::Error &error) {
- fprintf(stderr, "Error reading metadata\n");
- return 1;
- }
-
- Exiv2::ExifData &exifData = image->exifData();
- if (exifData.empty()) {
- fprintf(stderr, "%s: No Exif data found in the file", name);
- return 1;
- }
-
- Exiv2::ExifData::iterator pos = exifData.end();
-
- if (!_manufacturer ) {
- Exiv2::ExifKey key("Exif.Image.Make"); // tag auch wirklich vorhanden?
- pos = exifData.findKey(key);
- if (pos != exifData.end() && pos->size())
- _manufacturer = strdup(pos->toString().c_str());
- }
-
- if (!_model) {
- Exiv2::ExifKey key("Exif.Image.Model");
- pos = exifData.findKey(key);
- if (pos != exifData.end() && pos->size() )
- _model = strdup(pos->toString().c_str());
- }
-
- if (std::isnan(_focal_length))
- exifSetValue(&_focal_length, &exifData, "Exif.Photo.FocalLength");
-
- if (std::isnan(_focal_length_35mm))
- exifSetValue(&_focal_length_35mm, &exifData, "Exif.Photo.FocalLengthIn35mmFilm");
-
- if (std::isnan(_longitude))
- exifSetCoordinate(&_longitude, &exifData, "Exif.GPSInfo.GPSLongitude");
-
- if (std::isnan(_latitude))
- exifSetCoordinate(&_latitude, &exifData, "Exif.GPSInfo.GPSLatitude");
-
- if (std::isnan(_height))
- exifSetValue(&_height, &exifData, "Exif.GPSInfo.GPSAltitude");
-
- return 0;
-}
-
-
-#define GIPFEL_FORMAT "gipfel: longitude " FMT_DOUBLE ", latitude " FMT_DOUBLE ", height " FMT_DOUBLE ", direction " FMT_DOUBLE ", nick " FMT_DOUBLE ", tilt " FMT_DOUBLE ", focal_length_35mm " FMT_DOUBLE ", projection type %d, k0 " FMT_DOUBLE ", k1 " FMT_DOUBLE ", x0 " FMT_DOUBLE ""
-
-#define FMT_DOUBLE "%lf"
-static const char *gipfel_format_scan = GIPFEL_FORMAT;
-
-#undef FMT_DOUBLE
-#define FMT_DOUBLE "%f"
-static const char *gipfel_format_prnt = GIPFEL_FORMAT;
-
-#undef FMT_DOUBLE
-#undef GIPFEL_FORMAT
-
-int
-ImageMetaData::load_image_jpgcom(char *name) {
- double lo, la, he, dir, ni, ti, fr, k0, k1, x0 = 0.0;
- int pt = 0;
- int n, ret = 1;
-#if EXIV2_TEST_VERSION(0,28,0)
- Exiv2::Image::UniquePtr image;
-#else
- Exiv2::Image::AutoPtr image;
-#endif
-
- try {
- image = Exiv2::ImageFactory::open(name);
- image->readMetadata();
- } catch (const Exiv2::Error &error) {
- fprintf(stderr, "Error reading metadata\n");
- return 1;
- }
-
- const char *com = image->comment().c_str();
-
- if ((n = sscanf(com, gipfel_format_scan,
- &lo, &la, &he, &dir, &ni, &ti, &fr, &pt, &k0, &k1, &x0)) >= 8) {
- _longitude = lo;
- _latitude = la;
- _height = he;
- _direction = dir;
- _nick = ni;
- _tilt = ti;
- _focal_length_35mm = fr;
- _projection_type = pt;
-
- if (n >= 10) {
- _k0 = k0;
- _k1 = k1;
- _x0 = x0;
- }
-
- ret = 0;
- }
-
- return ret;
-}
-
-int
-ImageMetaData::save_image_jpgcom(char *in_img, char *out_img) {
- char buf[1024], *tmpname;
- int n, in_fd, tmp_fd, err = 0;
-
- char* dirbuf = strdup(out_img);
-#if HAVE_MKSTEMP
- char tmpbuf[MAXPATHLEN];
- snprintf(tmpbuf, sizeof(tmpbuf), "%s/.gipfelXXXXXX", dirname(dirbuf));
- tmp_fd = mkstemp(tmpbuf);
- tmpname = tmpbuf;
-#else
- tmpname = tempnam(dirname(dirbuf), ".gipfel");
- tmp_fd = open(tmpname, O_WRONLY|O_TRUNC|O_CREAT|O_BINARY, S_IRUSR|S_IWUSR);
-#endif
- free(dirbuf);
-
- if (tmp_fd == -1) {
- perror("mkstemp");
- return 1;
- }
-
- in_fd = open(in_img, O_RDONLY|O_BINARY);
- if (in_fd == -1) {
- perror("open");
- unlink(tmpname);
- close(tmp_fd);
- return 1;
- }
-
- while ((n = read(in_fd, buf, sizeof(buf))) > 0) {
- if (n < 0 || write(tmp_fd, buf, n) != n) {
- perror("write");
- err++;
- break;
- }
- }
-
- close(in_fd);
-
-#if EXIV2_TEST_VERSION(0,28,0)
- Exiv2::Image::UniquePtr image = Exiv2::ImageFactory::open(tmpname);
-#else
- Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(tmpname);
-#endif
- if (!image.get())
- err++;
-
- image->readMetadata();
- image->clearComment();
-
- snprintf(buf, sizeof(buf), gipfel_format_prnt,
- _longitude,
- _latitude,
- _height,
- _direction,
- _nick,
- _tilt,
- _focal_length_35mm,
- _projection_type,
- _k0, _k1, _x0);
-
- image->setComment(buf);
-
- try {
- image->writeMetadata();
- } catch (const Exiv2::Error &error) {
- fprintf(stderr, "Error writing metadata\n");
- err++;
- }
-
-#ifdef HAVE_FSYNC
- fsync(tmp_fd); // make sure data is on disk before replacing orig file
-#endif
-
- close(tmp_fd);
-
- if (err == 0) { // only overwrite existing image if everything was ok
-#ifdef WIN32
- // Workaround as Windows does not seem to replace files on rename()
- struct stat stFileInfo;
- if (stat(out_img, &stFileInfo) == 0)
- unlink(out_img);
-#endif
-
- if (rename(tmpname, out_img) != 0) {
- perror("rename");
- err++;
- unlink(tmpname);
- }
- }
-
- return (err != 0);
-}
-
-void
-ImageMetaData::distortion_params(double *k0, double *k1, double *x0) {
- *k0 = _k0;
- *k1 = _k1;
- *x0 = _x0;
-}
-
-void
-ImageMetaData::distortion_params(double k0, double k1, double x0) {
- _k0 = k0;
- _k1 = k1;
- _x0 = x0;
-}
diff --git a/src/JPEGOutputImage.H b/src/JPEGOutputImage.H
deleted file mode 100644
index 5ce9069..0000000
--- a/src/JPEGOutputImage.H
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef JPEGOUTPUTIMAGE_H
-#define JPEGOUTPUTIMAGE_H
-
-#include <stdio.h>
-extern "C" {
-#include <jpeglib.h>
-#undef HAVE_STDLIB_H
-}
-
-#include "OutputImage.H"
-
-class JPEGOutputImage : public OutputImage {
- private:
- unsigned char *row;
- char *file;
- struct jpeg_compress_struct cinfo;
- struct jpeg_error_mgr jerr;
- FILE *fp;
- int quality;
-
- protected:
- int init_internal();
- int set_pixel_internal(int x, int r, int g, int b);
- int next_line_internal();
- int done_internal();
-
- public:
- JPEGOutputImage(const char *file, int quality = 90);
- ~JPEGOutputImage();
-};
-
-#endif
diff --git a/src/JPEGOutputImage.cxx b/src/JPEGOutputImage.cxx
deleted file mode 100644
index 8bbd40b..0000000
--- a/src/JPEGOutputImage.cxx
+++ /dev/null
@@ -1,104 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-extern "C" {
-#include <jpeglib.h>
-}
-
-#include "JPEGOutputImage.H"
-
-JPEGOutputImage::JPEGOutputImage(const char *f, int q) : OutputImage() {
- file = strdup(f);
- fp = NULL;
- row = NULL;
- quality = q;
-}
-
-JPEGOutputImage::~JPEGOutputImage() {
- if (row)
- free(row);
- if (file)
- free(file);
-}
-
-int
-JPEGOutputImage::init_internal() {
- if (row)
- free(row);
- row = NULL;
-
- row = (unsigned char*) calloc(3 * W, sizeof(char));
- if (!row) {
- perror("calloc");
- return 1;
- }
-
- if (fp)
- fclose(fp);
-
- if ((fp = fopen(file, "wb")) == NULL) {
- fprintf(stderr, "can't open %s\n", file);
- return 1;
- }
-
- cinfo.err = jpeg_std_error(&jerr);
- jpeg_create_compress(&cinfo);
- jpeg_stdio_dest(&cinfo, fp);
- cinfo.image_width = W;
- cinfo.image_height = H;
- cinfo.input_components = 3; /* # of color components per pixel */
- cinfo.in_color_space = JCS_RGB;
-
- jpeg_set_defaults(&cinfo);
- jpeg_set_quality(&cinfo, quality, TRUE);
-
- jpeg_start_compress(&cinfo, TRUE);
-
- return 0;
-}
-
-int
-JPEGOutputImage::set_pixel_internal(int x, int r, int g, int b) {
- row[x*3+0] = (unsigned char) (r / 255);
- row[x*3+1] = (unsigned char) (g / 255);
- row[x*3+2] = (unsigned char) (b / 255);
-
- return 0;
-}
-
-int
-JPEGOutputImage::next_line_internal() {
- JSAMPROW row_pointer[1];
-
- row_pointer[0] = row;
- jpeg_write_scanlines(&cinfo, row_pointer, 1);
- memset(row, 0, sizeof(char) * 3 * W);
- return 0;
-}
-
-int
-JPEGOutputImage::done_internal() {
- jpeg_finish_compress(&cinfo);
- jpeg_destroy_compress(&cinfo);
-
- if (fp)
- fclose(fp);
- fp = NULL;
-
- if (row)
- free(row);
- row = NULL;
-
- if (fp)
- fclose(fp);
- fp = NULL;
-
- return 0;
-}
-
diff --git a/src/Makefile.am b/src/Makefile.am
deleted file mode 100644
index 3d4a174..0000000
--- a/src/Makefile.am
+++ /dev/null
@@ -1,45 +0,0 @@
-bin_PROGRAMS = gipfel
-
-gipfel_SOURCES = \
- gipfel.cxx \
- GipfelWidget.cxx \
- Panorama.cxx \
- ProjectionLSQ.cxx \
- ProjectionRectilinear.cxx \
- ProjectionCylindrical.cxx \
- Hill.cxx \
- Fl_Value_Dial.cxx \
- Fl_Search_Chooser.cxx \
- choose_hill.cxx \
- Stitch.cxx \
- OutputImage.cxx \
- JPEGOutputImage.cxx \
- TIFFOutputImage.cxx \
- PreviewOutputImage.cxx \
- ImageMetaData.cxx \
- ScreenDump.cxx \
- ScanImage.cxx \
- strsep.c
-
-noinst_HEADERS = \
- GipfelWidget.H \
- Panorama.H \
- ProjectionLSQ.H \
- ProjectionRectilinear.H \
- ProjectionRectilinear_funcs.cxx \
- ProjectionCylindrical.H \
- ProjectionCylindrical_funcs.cxx \
- Hill.H \
- ViewParams.H \
- Fl_Value_Dial.H \
- Fl_Search_Chooser.H \
- choose_hill.H \
- Stitch.H \
- OutputImage.H \
- JPEGOutputImage.H \
- TIFFOutputImage.H \
- PreviewOutputImage.H \
- ImageMetaData.H \
- ScreenDump.H \
- ScanImage.H \
- strsep.h
diff --git a/src/Makefile.lsq_funcs b/src/Makefile.lsq_funcs
deleted file mode 100644
index 46bab6e..0000000
--- a/src/Makefile.lsq_funcs
+++ /dev/null
@@ -1,7 +0,0 @@
-all: ProjectionRectilinear_funcs.cxx ProjectionCylindrical_funcs.cxx
-
-ProjectionRectilinear_funcs.cxx: lsq_rectilinear.mac expr2c.mac
- maxima -b lsq_rectilinear.mac | grep "^double" > ProjectionRectilinear_funcs.cxx
-
-ProjectionCylindrical_funcs.cxx: lsq_cylindrical.mac expr2c.mac
- maxima -b lsq_cylindrical.mac | grep "^double" > ProjectionCylindrical_funcs.cxx
diff --git a/src/OutputImage.H b/src/OutputImage.H
deleted file mode 100644
index 0718074..0000000
--- a/src/OutputImage.H
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Copyright 2006 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef OUTPUTIMAGE_H
-#define OUTPUTIMAGE_H
-
-class OutputImage {
- private:
- int initialized;
-
- protected:
- int W, H, line;
-
- virtual int init_internal() {return 0;};
- virtual int set_pixel_internal(int x, int r, int g, int b) {return 0;};
- virtual int next_line_internal() {return 0;};
- virtual int done_internal() {return 0;};
-
- public:
- OutputImage();
- virtual ~OutputImage() {};
-
- virtual int init(int w1, int h1);
- int set_pixel(int x, int r, int g, int b);
- int next_line();
- int done();
-};
-
-#endif
diff --git a/src/OutputImage.cxx b/src/OutputImage.cxx
deleted file mode 100644
index b4c4739..0000000
--- a/src/OutputImage.cxx
+++ /dev/null
@@ -1,58 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "OutputImage.H"
-
-OutputImage::OutputImage() {
- W = 0;
- H = 0;
- initialized = 0;
-}
-
-int
-OutputImage::init(int w1, int h1) {
- int ret;
-
- W = w1;
- H = h1;
- line = 0;
-
- ret = init_internal();
-
- if (ret == 0)
- initialized = 1;
-
- return ret;
-}
-
-int
-OutputImage::set_pixel(int x, int r, int g, int b) {
- if (!initialized || x < 0 || x >= W)
- return 1;
- else
- return set_pixel_internal(x, r, g, b);
-}
-
-int
-OutputImage::next_line() {
- if (!initialized || line++ >= H)
- return 1;
- else
- return next_line_internal();
-}
-
-int
-OutputImage::done() {
- if (!initialized) {
- return 1;
- } else {
- next_line();
- return done_internal();
- }
-}
diff --git a/src/Panorama.H b/src/Panorama.H
deleted file mode 100644
index 1f4fdeb..0000000
--- a/src/Panorama.H
+++ /dev/null
@@ -1,83 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef PANORAMA_H
-#define PANORAMA_H
-
-#include "Hill.H"
-#include "ProjectionLSQ.H"
-#include "ViewParams.H"
-
-class Panorama {
- private:
- double view_phi, view_lam, view_height;
- char *view_name;
- double height_dist_ratio;
- double hide_value;
- Hills *mountains;
- Hills *close_mountains;
- Hills *visible_mountains;
- ProjectionLSQ *proj;
- ProjectionLSQ::Projection_t projection_type;
- double pi_d, deg2rad;
-
- Hill * get_pos(const char *name);
- void update_angles();
- void update_coordinates(Hills *excluded_hills = NULL);
- void update_close_mountains();
- void update_visible_mountains(Hills *excluded_hills = NULL);
- void mark_hidden(Hills *hills);
- double distance(double phi, double lam);
- double alpha(const Hill *m);
- double nick(const Hill *m);
- double refraction(const Hill *m);
- double comp_center_angle(double alph_a, double alph_b, double d1, double d2);
- double comp_scale(double alph_a, double alph_b, double d1, double d2);
- int get_matrix(double m[]);
- int optimize(Hill *m1, Hill *m2);
- int is_visible(double a_alph);
-
- public:
- ViewParams parms;
-
- Panorama();
- ~Panorama();
- int load_data(const char *name);
- void add_hills(Hills *h);
- void remove_hills(int flags);
- int set_viewpoint(const char *pos);
- void set_viewpoint(const Hill *m);
- void set_height_dist_ratio(double r);
- void set_hide_value(double h);
- Hills * get_mountains();
- Hills * get_close_mountains();
- Hills * get_visible_mountains();
- void set_center_angle(double a);
- void set_nick_angle(double a);
- void set_tilt_angle(double a);
- void set_scale(double s);
- void set_view_lat(double v);
- void set_view_long(double v);
- void set_view_height(double v);
- const char * get_viewpoint();
- double get_center_angle();
- double get_nick_angle();
- double get_tilt_angle();
- double get_scale();
- double get_height_dist_ratio();
- double get_view_lat();
- double get_view_long();
- double get_view_height();
- double get_earth_radius(double latitude);
- double get_real_distance(const Hill *m);
- int comp_params(Hills *h);
- ProjectionLSQ::Projection_t get_projection();
- void set_projection(ProjectionLSQ::Projection_t p);
- void get_distortion_params(double *k0, double *k1, double *x0);
- void set_distortion_params(double k0, double k1, double x0);
- int get_coordinates(double a_alph, double a_nick, double *x, double *y);
-};
-#endif
diff --git a/src/Panorama.cxx b/src/Panorama.cxx
deleted file mode 100644
index ccba04f..0000000
--- a/src/Panorama.cxx
+++ /dev/null
@@ -1,476 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-#include "Panorama.H"
-#include "ProjectionRectilinear.H"
-#include "ProjectionCylindrical.H"
-
-#define EARTH_RADIUS 6371000.785
-
-Panorama::Panorama() {
- mountains = new Hills();
- close_mountains = new Hills();
- visible_mountains = new Hills();
- height_dist_ratio = 0.07;
- hide_value = 1.2;
- pi_d = asin(1.0) * 2.0;
- deg2rad = pi_d / 180.0;
- parms.a_center = 0.0;
- parms.a_nick = 0.0;
- parms.a_tilt = 0.0;
- parms.scale = 3500.0;
- parms.k0 = 0.0;
- parms.k1 = 0.0;
- parms.x0 = 0.0;
- view_name = NULL;
- view_phi = 0.0;
- view_lam = 0.0;
- view_height = 0.0;
- proj = NULL;
- set_projection(ProjectionLSQ::RECTILINEAR);
-}
-
-Panorama::~Panorama() {
- visible_mountains->clear();
- mountains->clobber();
- delete visible_mountains;
- delete mountains;
-}
-
-int
-Panorama::load_data(const char *name) {
- if (mountains->load(name) != 0) {
- fprintf(stderr, "Could not load datafile %s\n", name);
- return 1;
- }
-
- mountains->mark_duplicates(0.00001);
- update_angles();
-
- return 0;
-}
-
-void
-Panorama::add_hills(Hills *h) {
- mountains->add(h);
-
- mountains->mark_duplicates(0.00001);
- update_angles();
-}
-
-void
-Panorama::remove_hills(int flags) {
- Hills *h_new = new Hills();
- Hill *m;
-
- for (int i = 0; i < mountains->get_num(); i++) {
- m = mountains->get(i);
- if (! (m->flags & flags))
- h_new->add(m);
- }
-
- delete mountains;
- mountains = h_new;
-}
-
-int
-Panorama::set_viewpoint(const char *name) {
- Hill *m = get_pos(name);
- if (m == NULL) {
- fprintf(stderr, "Could not find exactly one entry for %s.\n", name);
- return 1;
- }
-
- set_viewpoint(m);
- return 0;
-}
-
-void
-Panorama::set_viewpoint(const Hill *m) {
- if (m == NULL)
- return;
-
- view_phi = m->phi;
- view_lam = m->lam;
- view_height = m->height;
-
- if (view_name)
- free(view_name);
-
- view_name = strdup(m->name);
-
- update_angles();
-}
-
-Hills *
-Panorama::get_mountains() {
- return mountains;
-}
-
-Hills *
-Panorama::get_close_mountains() {
- return close_mountains;
-}
-
-Hills *
-Panorama::get_visible_mountains() {
- return visible_mountains;
-}
-
-int
-Panorama::comp_params(Hills *h) {
- int ret;
-
- ret = proj->comp_params(h, &parms);
- if (ret == 0)
- update_visible_mountains(h);
-
- return ret;
-}
-
-void
-Panorama::set_center_angle(double a) {
- parms.a_center = a * deg2rad;
- update_visible_mountains();
-}
-
-void
-Panorama::set_nick_angle(double a) {
- parms.a_nick = a * deg2rad;
- update_coordinates();
-}
-
-void
-Panorama::set_tilt_angle(double a) {
- parms.a_tilt = a * deg2rad;
- update_coordinates();
-}
-
-void
-Panorama::set_scale(double s) {
- parms.scale = s;
- update_coordinates();
-}
-
-void
-Panorama::get_distortion_params(double *k0, double *k1, double *x0) {
- *k0 = parms.k0;
- *k1 = parms.k1;
- *x0 = parms.x0;
-}
-
-void
-Panorama::set_distortion_params(double k0, double k1, double x0) {
- parms.k0 = k0;
- parms.k1 = k1;
- parms.x0 = x0;
- update_coordinates();
-}
-
-void
-Panorama::set_height_dist_ratio(double r) {
- height_dist_ratio = r;
- update_close_mountains();
-}
-
-void
-Panorama::set_view_long(double v) {
- view_lam = v * deg2rad;
- update_angles();
-}
-
-void
-Panorama::set_view_lat(double v) {
- view_phi = v * deg2rad;
- update_angles();
-}
-
-void
-Panorama::set_view_height(double v) {
- view_height = v;
- update_angles();
-}
-
-void
-Panorama::set_projection(ProjectionLSQ::Projection_t p) {
- projection_type = p;
-
- if (proj) {
- delete proj;
- proj = NULL;
- }
-
- switch(projection_type) {
- case ProjectionLSQ::RECTILINEAR:
- proj = new ProjectionRectilinear();
- break;
- case ProjectionLSQ::CYLINDRICAL:
- proj = new ProjectionCylindrical();
- break;
- }
-
- update_angles();
-}
-
-const char *
-Panorama::get_viewpoint() {
- return view_name;
-}
-
-double
-Panorama::get_center_angle() {
- return parms.a_center / deg2rad;
-}
-
-double
-Panorama::get_nick_angle() {
- return parms.a_nick / deg2rad;
-}
-
-double
-Panorama::get_tilt_angle() {
- return parms.a_tilt / deg2rad;
-}
-
-double
-Panorama::get_scale() {
- return parms.scale;
-}
-
-double
-Panorama::get_height_dist_ratio() {
- return height_dist_ratio;
-}
-
-double
-Panorama::get_view_long() {
- return view_lam / deg2rad;
-}
-
-double
-Panorama::get_view_lat() {
- return view_phi / deg2rad;
-}
-
-double
-Panorama::get_view_height() {
- return view_height;
-}
-
-ProjectionLSQ::Projection_t
-Panorama::get_projection() {
- return projection_type;
-}
-
-Hill *
-Panorama::get_pos(const char *name) {
- Hill *m, *ret = NULL;
-
- for (int i = 0; i < mountains->get_num(); i++) {
- m = mountains->get(i);
-
- if (strcmp(m->name, name) == 0) {
- ret = m;
- fprintf(stderr, "Found matching entry: %s (%fm)\n",
- m->name, m->height);
- }
- }
-
- return ret;
-}
-
-void
-Panorama::update_angles() {
- for (int i = 0; i < mountains->get_num(); i++) {
- Hill *m = mountains->get(i);
-
- m->dist = distance(m->phi, m->lam);
- if (m->phi != view_phi || m->lam != view_lam)
- m->alph = alpha(m);
- }
-
- mountains->sort(Hills::SORT_ALPHA);
-
- update_close_mountains();
-}
-
-void
-Panorama::set_hide_value(double h) {
- hide_value = h;
- update_visible_mountains();
-}
-
-void
-Panorama::mark_hidden(Hills *hills) {
- double h;
-
- for (int i = 0; i < hills->get_num(); i++) {
- Hill *m = hills->get(i);
-
- m->flags &= ~Hill::HIDDEN;
-
- if (m->flags & Hill::DUPLIC)
- continue;
-
- for (int j = 0; j < hills->get_num(); j++) {
- Hill *n = hills->get(j);
-
- if (n->flags & Hill::DUPLIC || n->flags & Hill::TRACK_POINT)
- continue;
-
- if (m == n || fabs(m->alph - n->alph > pi_d / 2.0))
- continue;
-
- if (m->dist < n->dist || m->a_nick > n->a_nick)
- continue;
-
- h = (n->a_nick - m->a_nick) / fabs(m->alph - n->alph);
- if (isinf(h) || h > hide_value)
- m->flags |= Hill::HIDDEN;
- }
- }
-}
-
-void
-Panorama::update_close_mountains() {
- close_mountains->clear();
-
- for (int i = 0; i < mountains->get_num(); i++) {
- Hill *m = mountains->get(i);
-
- if (m->flags & Hill::TRACK_POINT ||
- ((m->phi != view_phi || m->lam != view_lam) &&
- (m->height / (m->dist * EARTH_RADIUS) > height_dist_ratio))) {
-
- m->a_nick = nick(m);
- close_mountains->add(m);
- }
- }
-
- mark_hidden(close_mountains);
- update_visible_mountains();
-}
-
-void
-Panorama::update_visible_mountains(Hills *excluded_hills) {
- visible_mountains->clear();
-
- for (int i = 0; i < close_mountains->get_num(); i++) {
- Hill *m = close_mountains->get(i);
-
- if (is_visible(m->alph)) {
- visible_mountains->add(m);
- m->flags |= Hill::VISIBLE;
- } else {
- m->flags &= ~Hill::VISIBLE;
- }
- }
-
- update_coordinates(excluded_hills);
-}
-
-void
-Panorama::update_coordinates(Hills *excluded_hills) {
- for (int i = 0; i < visible_mountains->get_num(); i++) {
- Hill *m = visible_mountains->get(i);
- if (!excluded_hills || !excluded_hills->contains(m))
- proj->get_coordinates(m->alph, m->a_nick, &parms, &m->x, &m->y);
- }
-}
-
-double
-Panorama::distance(double phi, double lam) {
- double d_lam = view_lam - lam;
-
- return atan2(sqrt(pow(cos(phi) * sin(d_lam), 2.0) +
- pow(cos(view_phi) * sin(phi) - sin(view_phi) * cos(phi) * cos(d_lam), 2.0)),
- sin(view_phi) * sin(phi) + cos(view_phi) * cos(phi) * cos(d_lam));
-}
-
-double
-Panorama::alpha(const Hill *m) {
- double sin_alph, cos_alph;
-
- sin_alph = sin(m->lam - view_lam) * cos(m->phi) / sin(m->dist);
- cos_alph = (sin(m->phi) - sin(view_phi) * cos(m->dist)) /
- (cos(view_phi) * sin(m->dist));
-
- return fmod(atan2(sin_alph, cos_alph) + 2.0 * pi_d, 2.0 * pi_d);
-}
-
-// approximation of refraction effect as described by Tom Chester at
-// http://tchester.org/sgm/analysis/peaks/refraction.html
-double
-Panorama::refraction(const Hill *m) {
- double a, b, c, alpha = 6.5, T0 = 10.0;
-
- a = 2.9e-4 * exp (-view_height / 10000.0) / (1.0 + 2.9 * T0 / 760.0);
- b = 2.9 * alpha / (760.0 * (1.0 + 2.9 * T0 / 760.0));
- c = a * (b - 1.0 / 10.0);
-
- return c * get_real_distance(m) / (2000.0 * (1.0 + a));
-}
-
-double
-Panorama::nick(const Hill *m) {
- double b, c, theta = refraction(m);
-
- b = m->height + get_earth_radius(m->phi);
- c = view_height + get_earth_radius(view_phi);
-
- return atan((cos(m->dist) * b - c) / (sin(m->dist) * b)) - theta;
-}
-
-// return local distance to center of WGS84 ellipsoid
-double
-Panorama::get_earth_radius(double phi) {
- double a = 6378137.000;
- double b = 6356752.315;
- double r;
- double ata = tan(phi);
-
- r = a*pow(pow(ata,2)+1,1.0/2.0)*fabs(b)*pow(pow(b,2)+pow(a,2)*pow(ata,2),-1.0/2.0);
- return r;
-}
-
-double
-Panorama::get_real_distance(const Hill *m) {
- double a, b, c, gam;
-
- a = view_height + get_earth_radius(view_phi);
- b = m->height + get_earth_radius(m->phi);
- gam = m->dist;
-
- c = sqrt(pow(a, 2.0) + pow(b, 2.0) - 2.0 * a * b * cos(gam));
- return c;
-}
-
-int
-Panorama::is_visible(double a_alph) {
- double center_dist;
-
- center_dist = fabs(a_alph - parms.a_center);
- if (center_dist > pi_d) {
- center_dist = 2*pi_d - center_dist;
- }
-
- return center_dist < proj->get_view_angle();
-}
-
-int
-Panorama::get_coordinates(double a_alph, double a_nick, double *x, double *y) {
- if (is_visible(a_alph)) {
- proj->get_coordinates(a_alph, a_nick, &parms, x, y);
- return 0;
- } else {
- return 1;
- }
-}
diff --git a/src/PreviewOutputImage.H b/src/PreviewOutputImage.H
deleted file mode 100644
index f37493b..0000000
--- a/src/PreviewOutputImage.H
+++ /dev/null
@@ -1,34 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef PREVIEWOUTPUTIMAGE_H
-#define PREVIEWOUTPUTIMAGE_H
-
-#include <stdio.h>
-
-#include <FL/Fl_Widget.H>
-#include <FL/Fl_Image.H>
-
-#include "OutputImage.H"
-
-class PreviewOutputImage : public OutputImage , public Fl_Widget {
- private:
- uchar *data;
- int d;
-
- protected:
- int init_internal();
- int set_pixel_internal(int x, int r, int g, int b);
- int next_line_internal();
-
- public:
- PreviewOutputImage(int X, int Y, int W, int H);
- ~PreviewOutputImage();
-
- void draw();
-};
-
-#endif
diff --git a/src/PreviewOutputImage.cxx b/src/PreviewOutputImage.cxx
deleted file mode 100644
index 560f044..0000000
--- a/src/PreviewOutputImage.cxx
+++ /dev/null
@@ -1,65 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <FL/Fl.H>
-#include <FL/fl_draw.H>
-
-#include "PreviewOutputImage.H"
-
-PreviewOutputImage::PreviewOutputImage(int X, int Y, int W, int H):
- OutputImage(), Fl_Widget(X, Y, W, H) {
- d = 3;
- data = NULL;
-}
-
-PreviewOutputImage::~PreviewOutputImage() {
- if (data)
- free(data);
-}
-
-int
-PreviewOutputImage::init_internal() {
- data = (uchar*) malloc(W * H * d);
- memset(data, 0, W * H * d);
- size(W, H);
- return 0;
-}
-
-int
-PreviewOutputImage::set_pixel_internal(int x, int r, int g, int b) {
- if (!data)
- return 1;
-
- long index = (line * w() * d + (x * d));
- *(data+index+0) = (unsigned char) (r / 255);
- *(data+index+1) = (unsigned char) (g / 255);
- *(data+index+2) = (unsigned char) (b / 255);
-
- return 0;
-}
-
-int
-PreviewOutputImage::next_line_internal() {
- if (line % 10 == 0) {
- redraw();
- Fl::check();
- }
- return 0;
-}
-
-void
-PreviewOutputImage::draw() {
- if (!data)
- return;
-
- fl_push_clip(x(), y(), w(), h());
- fl_draw_image(data, x(), y(), w(), h(), d);
- fl_pop_clip();
-}
diff --git a/src/ProjectionCylindrical.H b/src/ProjectionCylindrical.H
deleted file mode 100644
index 06709c4..0000000
--- a/src/ProjectionCylindrical.H
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Copyright 2006 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef PROJECTIONCYLINDRICAL_H
-#define PROJECTIONCYLINDRICAL_H
-
-#include "ProjectionLSQ.H"
-
-class ProjectionCylindrical : public ProjectionLSQ {
- public:
-
- virtual double get_view_angle() {return 6.2831853;}; /* 360 deg */
- virtual int comp_params(const Hills *h, ViewParams *parms);
-
-#define ARGS double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick
- virtual double mac_x(ARGS);
- virtual double mac_y(ARGS);
- virtual double mac_x_dc_view(ARGS);
- virtual double mac_x_dc_nick(ARGS);
- virtual double mac_x_dc_tilt(ARGS);
- virtual double mac_x_dscale(ARGS);
- virtual double mac_y_dc_view(ARGS);
- virtual double mac_y_dc_nick(ARGS);
- virtual double mac_y_dc_tilt(ARGS);
- virtual double mac_y_dscale(ARGS);
-#undef ARGS
-
-};
-
-#endif
diff --git a/src/ProjectionCylindrical.cxx b/src/ProjectionCylindrical.cxx
deleted file mode 100644
index 094b494..0000000
--- a/src/ProjectionCylindrical.cxx
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <math.h>
-
-#include "ProjectionCylindrical.H"
-
-#include "ProjectionCylindrical_funcs.cxx"
-
-int
-ProjectionCylindrical::comp_params(const Hills *h, ViewParams *parms) {
- Hills h_monotone(h);
-
- h_monotone.sort(Hills::SORT_X);
-
- // ensure that alpha is increasing with x.
- for (int i = 1; i < h_monotone.get_num(); i++)
- if (h_monotone.get(i)->alph < h_monotone.get(i - 1)->alph)
- h_monotone.get(i)->alph += asin(1.0) * 4.0; // += 2pi
-
- return ProjectionLSQ::comp_params(&h_monotone, parms);
-}
diff --git a/src/ProjectionLSQ.H b/src/ProjectionLSQ.H
deleted file mode 100644
index a380b4c..0000000
--- a/src/ProjectionLSQ.H
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef PROJECTIONLSQ_H
-#define PROJECTIONLSQ_H
-
-#include "Hill.H"
-#include "ViewParams.H"
-
-class ProjectionLSQ {
- private:
- double comp_scale(double alph_a, double alph_b, double d1, double d2);
-
- int lsq(const Hills *m, ViewParams *parms, int distortion_correct);
-
- protected:
- static double pi;
- double sec(double a);
-
- public:
- typedef enum {
- RECTILINEAR = 0,
- CYLINDRICAL = 1
- } Projection_t;
-
- ProjectionLSQ() {};
- virtual ~ProjectionLSQ() {};
-
- void get_coordinates(double a_view, double a_nick,
- const ViewParams *parms, double *x, double *y);
-
- virtual int comp_params(const Hills *h, ViewParams *parms);
-
- virtual double get_view_angle();
-
-#define ARGS double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick
- virtual double mac_x(ARGS);
- virtual double mac_y(ARGS);
- virtual double mac_x_dc_view(ARGS);
- virtual double mac_x_dc_nick(ARGS);
- virtual double mac_x_dc_tilt(ARGS);
- virtual double mac_x_dscale(ARGS);
- virtual double mac_x_dk0(ARGS);
- virtual double mac_x_dk1(ARGS);
- virtual double mac_x_dx0(ARGS);
- virtual double mac_y_dc_view(ARGS);
- virtual double mac_y_dc_nick(ARGS);
- virtual double mac_y_dc_tilt(ARGS);
- virtual double mac_y_dscale(ARGS);
- virtual double mac_y_dk0(ARGS);
- virtual double mac_y_dk1(ARGS);
- virtual double mac_y_dx0(ARGS);
-#undef ARGS
-
-};
-#endif
diff --git a/src/ProjectionLSQ.cxx b/src/ProjectionLSQ.cxx
deleted file mode 100644
index bcbb206..0000000
--- a/src/ProjectionLSQ.cxx
+++ /dev/null
@@ -1,294 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <math.h>
-
-#include <gsl/gsl_rng.h>
-#include <gsl/gsl_randist.h>
-#include <gsl/gsl_vector.h>
-#include <gsl/gsl_blas.h>
-#include <gsl/gsl_multifit_nlin.h>
-
-#include "ProjectionLSQ.H"
-
-double ProjectionLSQ::pi = asin(1.0) * 2.0;
-
-double
-ProjectionLSQ::sec(double a) {
- return 1.0 / cos(a);
-}
-
-double
-ProjectionLSQ::get_view_angle() {
- return 0.0;
-}
-
-int
-ProjectionLSQ::comp_params(const Hills *h, ViewParams *parms) {
- const Hill *m1, *m2;
- double scale_tmp;
- int known_hills = h->get_num();
- ViewParams new_parms = *parms;
-
- if (known_hills < 1) {
- fprintf(stderr, "Please position at least 1 hill\n");
- return 1;
- }
-
- m1 = h->get(0);
- new_parms.a_center = m1->alph;
- new_parms.a_nick = 0.0;
-
- if (known_hills > 1) {
- m2 = h->get(1);
-
- scale_tmp = comp_scale(m1->alph, m2->alph, m1->x, m2->x);
-
- if (isnan(scale_tmp) || scale_tmp < 50.0) {
- fprintf(stderr, "Could not determine initial scale value (%f)\n",
- scale_tmp);
- return 1;
- }
-
- new_parms.scale = scale_tmp;
- new_parms.a_tilt = 0.0;
- }
-
- if (known_hills > 3) {
- fprintf(stderr, "Performing calibration\n");
- new_parms.k0 = 0.0;
- new_parms.k1 = 0.0;
- new_parms.x0 = 0.0;
- }
-
- if (known_hills == 1) {
- lsq(h, &new_parms, 0);
- } else if (known_hills < 4) {
- lsq(h, &new_parms, 1);
- } else {
- lsq(h, &new_parms, 1);
- lsq(h, &new_parms, 2);
- }
-
- if (isnan(new_parms.scale) || new_parms.scale < 50.0) {
- fprintf(stderr, "Could not determine reasonable view parameters\n");
- return 1;
- }
-
- *parms = new_parms;
-
- return 0;
-}
-
-struct data {
- ProjectionLSQ *p;
- int level;
- const Hills *h;
- const ViewParams *old_params;
-};
-
-#define CALL(A) dat->p->A(parms.a_center, parms.a_nick, parms.a_tilt, parms.scale, parms.k0, parms.k1, parms.x0, m->alph, m->a_nick)
-
-static int
-lsq_f (const gsl_vector * x, void *data, gsl_vector * f) {
- struct data *dat = (struct data *) data;
- ViewParams parms = *dat->old_params;
-
- parms.a_center = gsl_vector_get (x, 0);
- parms.a_nick = gsl_vector_get (x, 1);
- if (dat->level > 0) {
- parms.a_tilt = gsl_vector_get (x, 2);
- parms.scale = gsl_vector_get (x, 3);
- }
- if (dat->level > 1) {
- parms.k0 = gsl_vector_get (x, 4);
- parms.k1 = gsl_vector_get (x, 5);
- parms.x0 = gsl_vector_get (x, 6);
- }
-
- for (int i=0; i<dat->h->get_num(); i++) {
- Hill *m = dat->h->get(i);
-
- double mx = CALL(mac_x);
- double my = CALL(mac_y);
-
- gsl_vector_set (f, i*2, mx - m->x);
- gsl_vector_set (f, i*2+1, my - m->y);
- }
-
- return GSL_SUCCESS;
-}
-
-
-static int
-lsq_df (const gsl_vector * x, void *data, gsl_matrix * J) {
- struct data *dat = (struct data *) data;
- ViewParams parms = *dat->old_params;
-
- parms.a_center = gsl_vector_get (x, 0);
- parms.a_nick = gsl_vector_get (x, 1);
- if (dat->level > 0) {
- parms.a_tilt = gsl_vector_get (x, 2);
- parms.scale = gsl_vector_get (x, 3);
- }
- if (dat->level > 1) {
- parms.k0 = gsl_vector_get (x, 4);
- parms.k1 = gsl_vector_get (x, 5);
- parms.x0 = gsl_vector_get (x, 6);
- }
-
- for (int i=0; i<dat->h->get_num(); i++) {
- Hill *m = dat->h->get(i);
-
- gsl_matrix_set (J, 2*i, 0, CALL(mac_x_dc_view));
- gsl_matrix_set (J, 2*i, 1, CALL(mac_x_dc_nick));
- if (dat->level > 0) {
- gsl_matrix_set (J, 2*i, 2, CALL(mac_x_dc_tilt));
- gsl_matrix_set (J, 2*i, 3, CALL(mac_x_dscale));
- }
- if (dat->level > 1) {
- gsl_matrix_set (J, 2*i, 4, CALL(mac_x_dk0));
- gsl_matrix_set (J, 2*i, 5, CALL(mac_x_dk1));
- gsl_matrix_set (J, 2*i, 6, CALL(mac_x_dx0));
- }
-
- gsl_matrix_set (J, 2*i+1, 0, CALL(mac_y_dc_view));
- gsl_matrix_set (J, 2*i+1, 1, CALL(mac_y_dc_nick));
- if (dat->level > 0) {
- gsl_matrix_set (J, 2*i+1, 2, CALL(mac_y_dc_tilt));
- gsl_matrix_set (J, 2*i+1, 3, CALL(mac_y_dscale));
- }
- if (dat->level > 1) {
- gsl_matrix_set (J, 2*i+1, 4, CALL(mac_y_dk0));
- gsl_matrix_set (J, 2*i+1, 5, CALL(mac_y_dk1));
- gsl_matrix_set (J, 2*i+1, 6, CALL(mac_y_dx0));
- }
- }
-
- return GSL_SUCCESS;
-}
-
-static int
-lsq_fdf (const gsl_vector * x, void *data, gsl_vector * f, gsl_matrix * J) {
- lsq_f (x, data, f);
- lsq_df (x, data, J);
-
- return GSL_SUCCESS;
-}
-
-int
-ProjectionLSQ::lsq(const Hills *h, ViewParams *parms,
- int level) {
-
- const gsl_multifit_fdfsolver_type *T;
- gsl_multifit_fdfsolver *s;
- gsl_multifit_function_fdf f;
- struct data dat;
- double x_init[8];
- gsl_vector_view x;
- int status;
- int num_params;
-
- if (level == 0)
- num_params = 2;
- else if (level == 1)
- num_params = 4;
- else
- num_params = 7;
-
- dat.p = this;
- dat.level = level;
- dat.h = h;
- dat.old_params = parms;
-
- x_init[0] = parms->a_center;
- x_init[1] = parms->a_nick;
- x_init[2] = parms->a_tilt;
- x_init[3] = parms->scale;
- x_init[4] = parms->k0;
- x_init[5] = parms->k1;
- x_init[6] = parms->x0;
-
- x = gsl_vector_view_array (x_init, num_params);
-
- f.f = &lsq_f;
- f.df = &lsq_df;
- f.fdf = &lsq_fdf;
- f.n = h->get_num() * 2;
- f.p = num_params;
- f.params = &dat;
-
- T = gsl_multifit_fdfsolver_lmsder;
- s = gsl_multifit_fdfsolver_alloc (T, h->get_num() * 2, num_params);
- gsl_multifit_fdfsolver_set (s, &f, &x.vector);
-
- for (int i=0; i<100; i++) {
- status = gsl_multifit_fdfsolver_iterate (s);
- if (status) {
- break;
- }
- }
-
- parms->a_center = gsl_vector_get(s->x, 0);
- parms->a_nick = gsl_vector_get(s->x, 1);
-
- if (level > 0) {
- parms->a_tilt = gsl_vector_get(s->x, 2);
- parms->scale = gsl_vector_get(s->x, 3);
- }
- if (level > 1) {
- parms->k0 = gsl_vector_get(s->x, 4);
- parms->k1 = gsl_vector_get(s->x, 5);
- parms->x0 = gsl_vector_get(s->x, 6);
- }
-
- gsl_multifit_fdfsolver_free (s);
-
- return 0;
-}
-
-void
-ProjectionLSQ::get_coordinates(double alph, double a_nick,
- const ViewParams *parms, double *x, double *y) {
-
- // Normalize alph - parms->a_center to [-pi/2, pi/2]
- if (alph - parms->a_center > pi)
- alph -= 2.0 * pi;
- else if (alph - parms->a_center < -pi)
- alph += 2.0 * pi;
-
- *x = mac_x(parms->a_center, parms->a_nick, parms->a_tilt, parms->scale,
- parms->k0, parms->k1, parms->x0, alph, a_nick);
- *y = mac_y(parms->a_center, parms->a_nick, parms->a_tilt, parms->scale,
- parms->k0, parms->k1, parms->x0, alph, a_nick);
-}
-
-double
-ProjectionLSQ::comp_scale(double a1, double a2, double d1, double d2) {
- return (fabs(d1 - d2) / fabs(a1 - a2));
-}
-
-#define ARGS double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick
-
-double ProjectionLSQ::mac_x(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dc_view(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dc_nick(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dc_tilt(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dscale(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dk0(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dk1(ARGS) {return NAN;}
-double ProjectionLSQ::mac_x_dx0(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dc_view(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dc_nick(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dc_tilt(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dscale(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dk0(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dk1(ARGS) {return NAN;}
-double ProjectionLSQ::mac_y_dx0(ARGS) {return NAN;}
diff --git a/src/ProjectionRectilinear.H b/src/ProjectionRectilinear.H
deleted file mode 100644
index 2d799e3..0000000
--- a/src/ProjectionRectilinear.H
+++ /dev/null
@@ -1,38 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef PROJECTIONRECTILINEAR_H
-#define PROJECTIONRECTILINEAR_H
-
-#include "ProjectionLSQ.H"
-
-class ProjectionRectilinear : public ProjectionLSQ {
- public:
-
- virtual double get_view_angle() {return 1.0471976;}; /* 60 deg */
-
-#define ARGS double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick
- virtual double mac_x(ARGS);
- virtual double mac_y(ARGS);
- virtual double mac_x_dc_view(ARGS);
- virtual double mac_x_dc_nick(ARGS);
- virtual double mac_x_dc_tilt(ARGS);
- virtual double mac_x_dscale(ARGS);
- virtual double mac_x_dk0(ARGS);
- virtual double mac_x_dk1(ARGS);
- virtual double mac_x_dx0(ARGS);
- virtual double mac_y_dc_view(ARGS);
- virtual double mac_y_dc_nick(ARGS);
- virtual double mac_y_dc_tilt(ARGS);
- virtual double mac_y_dscale(ARGS);
- virtual double mac_y_dk0(ARGS);
- virtual double mac_y_dk1(ARGS);
- virtual double mac_y_dx0(ARGS);
-#undef ARGS
-
-};
-
-#endif
diff --git a/src/ProjectionRectilinear.cxx b/src/ProjectionRectilinear.cxx
deleted file mode 100644
index 33cb3a7..0000000
--- a/src/ProjectionRectilinear.cxx
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <math.h>
-
-#include "ProjectionRectilinear.H"
-
-#include "ProjectionRectilinear_funcs.cxx"
diff --git a/src/ScanImage.H b/src/ScanImage.H
deleted file mode 100644
index 1c1b91a..0000000
--- a/src/ScanImage.H
+++ /dev/null
@@ -1,31 +0,0 @@
-//
-// Copyright 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef ScanImage_H
-#define ScanImage_H
-
-#include <FL/Fl_Image.H>
-
-class ScanImage {
- private:
- static int get_pixel_nearest(Fl_Image *img, double x, double y,
- int *r, int *g, int *b);
- static int get_pixel_bicubic(Fl_Image *img, double x, double y,
- int *r, int *g, int *b);
- static int get_pixel(Fl_Image *img, int x, int y,
- int *r, int *g, int *b);
-
- public:
- typedef enum {
- NEAREST = 0,
- BICUBIC = 1
- } mode_t;
-
- static int get_pixel(Fl_Image *img, mode_t mode,
- double x, double y, int *r, int *g, int *b);
-};
-
-#endif
diff --git a/src/ScanImage.cxx b/src/ScanImage.cxx
deleted file mode 100644
index 39f66a7..0000000
--- a/src/ScanImage.cxx
+++ /dev/null
@@ -1,118 +0,0 @@
-//
-// Copyright 2007-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <math.h>
-
-#include <FL/Fl_Image.H>
-#include "ScanImage.H"
-
-int
-ScanImage::get_pixel(Fl_Image *img, mode_t mode,
- double x, double y, int *r, int *g, int *b) {
- if (mode == BICUBIC)
- return get_pixel_bicubic(img, x, y, r, g, b);
- else
- return get_pixel_nearest(img, x, y, r, g, b);
-}
-
-int
-ScanImage::get_pixel_nearest(Fl_Image *img, double x, double y,
- int *r, int *g, int *b) {
-
- if (isnan(x) || isnan(y))
- return 1;
- else
- return get_pixel(img, (int) rint(x), (int) rint(y), r, g, b);
-}
-
-static inline double
-interp_cubic(double x, double x2, double x3, double *v) {
- double a0, a1, a2, a3;
-
- a0 = v[3] - v[2] - v[0] + v[1];
- a1 = v[0] - v[1] - a0;
- a2 = v[2] - v[0];
- a3 = v[1];
-
- return a0 * x3 + a1 * x2 + a2 * x + a3;
-}
-
-int
-ScanImage::get_pixel_bicubic(Fl_Image *img, double x, double y,
- int *r, int *g, int *b) {
-
- double fl_x = floor(x);
- double fl_y = floor(y);
- double dx = x - fl_x, dx2 = dx * dx, dx3 = dx2 * dx;
- double dy = y - fl_y, dy2 = dy * dy, dy3 = dy2 * dy;
- int ic[3];
- double c[3][4];
- double c1[3][4];
-
- for (int iy = 0; iy < 4; iy++) {
- for (int ix = 0; ix < 4; ix++) {
-
- if (get_pixel(img, (int) fl_x + ix - 1, (int) fl_y + iy - 1,
- &ic[0], &ic[1], &ic[2]) != 0)
- return 1;
-
- for (int l = 0; l < 3; l++)
- c[l][ix] = (double) ic[l];
- }
-
- for (int l = 0; l < 3; l++)
- c1[l][iy] = interp_cubic(dx, dx2, dx3, c[l]);
- }
-
- *r = (int) rint(interp_cubic(dy, dy2, dy3, c1[0]));
- *g = (int) rint(interp_cubic(dy, dy2, dy3, c1[1]));
- *b = (int) rint(interp_cubic(dy, dy2, dy3, c1[2]));
- return 0;
-}
-
-int
-ScanImage::get_pixel(Fl_Image *img, int x, int y,
- int *r, int *g, int *b) {
- if ( img->d() == 0 )
- return 1;
-
- if (x < 0 || x >=img->w() || y < 0 || y >= img->h())
- return 1;
-
- long index = (y * img->w() * img->d()) + (x * img->d()); // X/Y -> buf index
- switch (img->count()) {
- case 1:
- { // bitmap
- const unsigned char *buf = (const unsigned char*) img->data()[0];
- switch (img->d())
- {
- case 1:
- *r = *g = *b = *(buf+index);
- break;
- case 3: // 24bit
- *r = (int) *(buf+index+0);
- *g = (int) *(buf+index+1);
- *b = (int) *(buf+index+2);
- break;
- default: // ??
- printf("Not supported: chans=%d\n", img->d());
- return 1;
- }
- break;
- }
- default: // ?? pixmap, bit vals
- printf("Not supported: count=%d\n", img->count());
- return 1;
- }
-
- *r = *r * 255;
- *g = *g * 255;
- *b = *b * 255;
-
- return 0;
-}
diff --git a/src/ScreenDump.H b/src/ScreenDump.H
deleted file mode 100644
index 8564054..0000000
--- a/src/ScreenDump.H
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// Copyright 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef SCREENDUMP_H
-#define SCREENDUMP_H
-
-#include <FL/Fl_Widget.H>
-
-#include "OutputImage.H"
-
-class ScreenDump {
- private:
- int w, h;
- unsigned char * rgb;
-
- public:
- ScreenDump(Fl_Widget *widget);
- ~ScreenDump();
-
- int save(OutputImage *out);
-};
-
-#endif
diff --git a/src/ScreenDump.cxx b/src/ScreenDump.cxx
deleted file mode 100644
index f331a76..0000000
--- a/src/ScreenDump.cxx
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Copyright 2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <FL/Fl.H>
-#include <FL/x.H>
-#include <FL/fl_draw.H>
-
-#include "ScreenDump.H"
-
-ScreenDump::ScreenDump(Fl_Widget *widget) {
- Fl_Offscreen offscreen;
- int x, y;
-
- x = widget->x();
- y = widget->y();
- w = widget->w();
- h = widget->h();
-
- Fl::flush();
- widget->resize(0, 0, w, h);
-
- offscreen = fl_create_offscreen(w, h);
- fl_begin_offscreen(offscreen);
- widget->redraw();
- widget->draw();
- fl_color(FL_YELLOW);
- fl_draw("created with gipfel", w - 80, h - 10);
- rgb = fl_read_image(NULL, 0, 0, w, h);
- fl_end_offscreen();
- fl_delete_offscreen(offscreen);
-
- widget->resize(x, y, w, h);
-}
-
-ScreenDump::~ScreenDump() {
- if (rgb)
- delete[] rgb;
-}
-
-int
-ScreenDump::save(OutputImage *out) {
- if (!rgb)
- return 1;
-
- out->init(w, h);
-
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < w; x++) {
- unsigned char *px = &rgb[(y * w + x) * 3];
- out->set_pixel(x, px[0] * 255, px[1] * 255, px[2] * 255);
- }
- out->next_line();
- }
-
- return out->done();
-}
diff --git a/src/Stitch.H b/src/Stitch.H
deleted file mode 100644
index 7c9a60e..0000000
--- a/src/Stitch.H
+++ /dev/null
@@ -1,32 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef STITCH_H
-#define STITCH_H
-
-#include "GipfelWidget.H"
-#include "OutputImage.H"
-#include "ScanImage.H"
-
-#define MAX_PICS 256
-
-class Stitch {
- private:
- GipfelWidget *gipf[MAX_PICS];
- int num_pics;
- OutputImage *merged_image;
-
- public:
- Stitch();
- ~Stitch();
-
- int load_image(char *file);
- OutputImage * set_output(OutputImage *img);
- int resample(ScanImage::mode_t m,
- int w, int h, double view_start, double view_end);
-};
-
-#endif
diff --git a/src/Stitch.cxx b/src/Stitch.cxx
deleted file mode 100644
index 7b9fc04..0000000
--- a/src/Stitch.cxx
+++ /dev/null
@@ -1,117 +0,0 @@
-//
-// Copyright 2007-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <math.h>
-#include <algorithm>
-
-#include <gsl/gsl_multifit.h>
-
-#include <FL/Fl.H>
-
-#include "OutputImage.H"
-#include "Stitch.H"
-
-#define MAX_VALUE 65025
-
-static double pi_d = asin(1.0) * 2.0;
-static double deg2rad = pi_d / 180.0;
-
-Stitch::Stitch() {
- for (int i = 0; i < MAX_PICS; i++)
- gipf[i] = NULL;
-
- merged_image = NULL;
- num_pics = 0;
-}
-
-Stitch::~Stitch() {
- for (int i = 0; i < MAX_PICS; i++)
- if (gipf[i])
- delete gipf[i];
- else
- break;
-}
-
-int
-Stitch::load_image(char *file) {
- for (int i = 0; i < MAX_PICS; i++) {
- if (gipf[i] == NULL) {
- gipf[i] = new GipfelWidget(0, 0, 800, 600, NULL);
- if (gipf[i]->load_image(file) != 0) {
- delete gipf[i];
- gipf[i] = NULL;
- } else {
- num_pics++;
- }
- break;
- }
- }
-
- return 0;
-}
-
-OutputImage*
-Stitch::set_output(OutputImage *img) {
- OutputImage *ret = merged_image;
- merged_image = img;
- return ret;
-}
-
-int
-Stitch::resample(ScanImage::mode_t m,
- int w, int h, double view_start, double view_end) {
-
- view_start = view_start * deg2rad;
- view_end = view_end * deg2rad;
-
- double step_view = (view_end - view_start) / w;
- int r, g, b;
- int y_off = h / 2;
- int merged_pixel_set;
- double radius = (double) w / (view_end -view_start);
-
- if (merged_image)
- if (merged_image->init(w, h) != 0)
- merged_image = NULL;
-
- for (int y = 0; y < h; y++) {
- double a_nick = atan((double)(y_off - y)/radius);
-
- for (int x = 0; x < w; x++) {
- double a_view;
- a_view = view_start + x * step_view;
- merged_pixel_set = 0;
- for (int i = 0; i < num_pics; i++) {
- if (merged_pixel_set)
- continue;
-
- if (gipf[i]->get_pixel(m, a_view, a_nick,
- &r, &g, &b) == 0) {
-
- r = std::max(std::min(r, MAX_VALUE), 0);
- g = std::max(std::min(g, MAX_VALUE), 0);
- b = std::max(std::min(b, MAX_VALUE), 0);
-
- if (!merged_pixel_set && merged_image) {
- merged_image->set_pixel(x, r, g, b);
- merged_pixel_set++;
- }
- }
- }
- }
-
- if (merged_image)
- merged_image->next_line();
- }
-
- if (merged_image)
- merged_image->done();
-
- return 0;
-}
diff --git a/src/TIFFOutputImage.H b/src/TIFFOutputImage.H
deleted file mode 100644
index b5409eb..0000000
--- a/src/TIFFOutputImage.H
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// Copyright 2006 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef TIFFOUTPUTIMAGE_H
-#define TIFFOUTPUTIMAGE_H
-
-#include <stdio.h>
-#include <tiffio.h>
-
-#include "OutputImage.H"
-
-class TIFFOutputImage : public OutputImage {
- private:
- int bitspersample;
- unsigned char *row;
- char *file;
- TIFF *tiff;
-
- protected:
- int init_internal();
- int set_pixel_internal(int x, int r, int g, int b);
- int next_line_internal();
- int done_internal();
-
- public:
- TIFFOutputImage(const char *file, int b = 8);
- ~TIFFOutputImage();
-};
-
-#endif
diff --git a/src/TIFFOutputImage.cxx b/src/TIFFOutputImage.cxx
deleted file mode 100644
index e9f6a2b..0000000
--- a/src/TIFFOutputImage.cxx
+++ /dev/null
@@ -1,97 +0,0 @@
-//
-// Copyright 2006 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "TIFFOutputImage.H"
-
-TIFFOutputImage::TIFFOutputImage(const char *f, int b) : OutputImage () {
- bitspersample = (b==16)?16:8;
- file = strdup(f);
- tiff = NULL;
- row = NULL;
-}
-
-TIFFOutputImage::~TIFFOutputImage() {
- if (row)
- free(row);
-
- if (file)
- free(file);
-}
-
-int
-TIFFOutputImage::init_internal() {
- if (row)
- free(row);
- row = NULL;
-
- row = (unsigned char*) calloc((bitspersample / 8) * 4 * W, sizeof(char));
- if (!row) {
- perror("calloc");
- return 1;
- }
-
- if (tiff)
- TIFFClose(tiff);
-
- if ((tiff = TIFFOpen(file, "w")) == NULL){
- fprintf(stderr, "can't open %s\n", file);
- return 1;
- }
-
- TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, W);
- TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, H);
- TIFFSetField(tiff, TIFFTAG_COMPRESSION, COMPRESSION_DEFLATE);
- TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
- TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
- TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, 1);
- TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bitspersample);
- TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, 4);
-
- return 0;
-}
-
-int
-TIFFOutputImage::set_pixel_internal(int x, int r, int g, int b) {
- if (bitspersample == 8) {
- row[x*4+0] = (unsigned char) (r / 255);
- row[x*4+1] = (unsigned char) (g / 255);
- row[x*4+2] = (unsigned char) (b / 255);
- row[x*4+3] = 255;
- } else if (bitspersample == 16) {
- unsigned short *row16 = (unsigned short*) row;
- row16[x*4+0] = (unsigned short) r;
- row16[x*4+1] = (unsigned short) g;
- row16[x*4+2] = (unsigned short) b;
- row16[x*4+3] = 65025;
- }
-
- return 0;
-}
-
-int
-TIFFOutputImage::next_line_internal() {
- TIFFWriteEncodedStrip(tiff, line - 1 , row, W * (bitspersample / 8) * 4);
-
- memset(row, 0, (bitspersample / 8) * 4 * W * sizeof(char));
- return 0;
-}
-
-int
-TIFFOutputImage::done_internal() {
- if (tiff)
- TIFFClose(tiff);
- tiff = NULL;
-
- if (row)
- free(row);
- row = NULL;
-
- return 0;
-}
diff --git a/src/ViewParams.H b/src/ViewParams.H
deleted file mode 100644
index b53a8df..0000000
--- a/src/ViewParams.H
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef VIEWPARAMS_H
-#define VIEWPARAMS_H
-
-class ViewParams {
- public:
- double a_center;
- double scale;
- double a_nick;
- double a_tilt;
- double k0;
- double k1;
- double x0;
-};
-
-#endif
diff --git a/src/choose_hill.H b/src/choose_hill.H
deleted file mode 100644
index 27105dc..0000000
--- a/src/choose_hill.H
+++ /dev/null
@@ -1,15 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#ifndef CHOOSE_HILL_H
-#define CHOOSE_HILL_H
-
-#include "Fl_Search_Chooser.H"
-#include "Hill.H"
-
-Hill *choose_hill(const Hills *hills, const char *l);
-
-#endif
diff --git a/src/choose_hill.cxx b/src/choose_hill.cxx
deleted file mode 100644
index 48e44d5..0000000
--- a/src/choose_hill.cxx
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <string.h>
-#include <FL/Fl.H>
-#include "choose_hill.H"
-
-Hill*
-choose_hill(const Hills *hills, const char *l) {
- Fl_Search_Chooser *sc = new Fl_Search_Chooser(l?l:"Choose Hill");
- Hills *h_sort = new Hills(hills);
- Hill *ret;
-
- h_sort->sort(Hills::SORT_NAME);
-
- for (int i=0; i<h_sort->get_num(); i++) {
- char buf[256];
-
- Hill *m = h_sort->get(i);
- if (m->flags & (Hill::DUPLIC | Hill::TRACK_POINT))
- continue;
-
- snprintf(buf, sizeof(buf) - 1, "%s (%dm)", m->name, (int) m->height);
- buf[sizeof(buf) - 1] = '\0';
- sc->add(buf, m);
- }
-
- delete h_sort;
-
- sc->show();
-
- while (sc->shown())
- Fl::wait();
-
- ret = (Hill*) sc->data();
- delete sc;
-
- return ret;
-}
diff --git a/src/expr2c.mac b/src/expr2c.mac
deleted file mode 100644
index 1522169..0000000
--- a/src/expr2c.mac
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
- */
-
-load("stringproc")$
-
-varsubst(v) := ssubst("_", "%", string(v))$
-
-printdecl(vars) := (
- sprint("double", varsubst(first(vars))),
- for v in rest(vars) do sprint(",", varsubst(v)),
- sprint(";"))$
-
-printdef(expr) := sprint(varsubst(first(expr)), "=", varsubst(second(expr)), ";")$
-
-block2c(expr) :=
- if ?equal(op(expr), block) then (
- printdecl(first(expr)),
- for d in reverse(rest(reverse(rest(expr)))) do printdef(d),
- sprint("return", varsubst(last(expr)), ";"))
- else
- sprint("return", varsubst(expr), ";")$
-
-expr2c(funcname, argstr, expr) := (
- sprint("double", funcname, "(", argstr, ") {"),
- block2c(subst(pow, "^", optimize(expr))),
- sprint("}", "
-"))$
diff --git a/src/gipfel.cxx b/src/gipfel.cxx
deleted file mode 100644
index bed5035..0000000
--- a/src/gipfel.cxx
+++ /dev/null
@@ -1,738 +0,0 @@
-//
-// Copyright 2007-2023 Johannes Hofmann <Johannes.Hofmann@gmx.de>
-//
-// This software may be used and distributed according to the terms
-// of the GNU General Public License, incorporated herein by reference.
-
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <libgen.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <math.h>
-#include <assert.h>
-#include <algorithm>
-
-#include <FL/Fl.H>
-#include <FL/Fl_Window.H>
-#include <FL/Fl_Scroll.H>
-#include <FL/Fl_File_Chooser.H>
-#include <FL/Fl_Input.H>
-#include <FL/Fl_Int_Input.H>
-#include <FL/Fl_Menu_Bar.H>
-#include <FL/Fl_Menu_Item.H>
-#include <FL/Fl_Valuator.H>
-#include <FL/Fl_Value_Slider.H>
-#include <FL/Fl_Value_Input.H>
-#include <FL/Fl_Text_Display.H>
-#include <FL/Fl_Text_Buffer.H>
-
-#include "Fl_Value_Dial.H"
-#include "Fl_Search_Chooser.H"
-#include "GipfelWidget.H"
-#include "JPEGOutputImage.H"
-#include "TIFFOutputImage.H"
-#include "PreviewOutputImage.H"
-#include "Stitch.H"
-#include "ScreenDump.H"
-#include "choose_hill.H"
-#include "../config.h"
-
-#ifndef STD_DATADIR
-#define STD_DATADIR "/usr/local/share"
-#endif
-
-#define GIPFEL_DATADIR STD_DATADIR "/" PACKAGE_NAME
-#define DATAFILE "gipfel.dat"
-
-static char *run_dir = NULL;
-static char *img_file = NULL;
-static char *data_file = NULL;
-
-static GipfelWidget *gipf = NULL;
-static Fl_Scroll *scroll;
-static Fl_Window *control_win, *view_win;
-static Fl_Dial *s_center = NULL;
-static Fl_Slider *s_nick, *s_focal_length, *s_tilt, *s_height_dist, *s_track_width;
-static Fl_Value_Input *i_view_lat, *i_view_long, *i_view_height;
-static Fl_Value_Input *i_distortion_k0, *i_distortion_k1, *i_distortion_x0;
-static Fl_Box *b_viewpoint;
-static Fl_Menu_Bar *mb;
-
-#define STITCH_PREVIEW 1
-#define STITCH_JPEG 2
-#define STITCH_TIFF 4
-
-static int stitch(ScanImage::mode_t m , int b_16,
- int stitch_w, int stitch_h,
- double from, double to, int type, const char *path, int argc, char **argv);
-
-static int export_hills(const char *export_file, double visibility);
-static int export_position();
-
-static int
-confirm_overwrite(const char *f) {
- struct stat sb;
-
- if (stat(f, &sb) == 0)
- return fl_choice("%s exists.\n", "Cancel", "Overwrite", NULL, f);
- else
- return 1;
-}
-
-static char *
-file_installed_or_local(const char *inst_dir, const char *name) {
- assert(run_dir);
-
- struct stat sb;
- int buflen = strlen(inst_dir) + strlen(run_dir) + strlen(name) + 1;
- char *buf = (char *) malloc (buflen);
-
- snprintf(buf, buflen, "%s/%s", inst_dir, name);
- if (stat(buf, &sb) == 0)
- return buf;
-
- snprintf(buf, buflen, "%s/../%s", run_dir, name);
- if (stat(buf, &sb) == 0)
- return buf;
-
- free(buf);
- return NULL;
-}
-
-void set_values() {
- double k0 = 0.0, k1 = 0.0, x0 = 0.0;
-
- s_center->value(gipf->get_center_angle());
- s_nick->value(gipf->get_nick_angle());
- s_focal_length->value(gipf->get_focal_length_35mm());
- s_tilt->value(gipf->get_tilt_angle());
- s_height_dist->value(gipf->get_height_dist_ratio());
- i_view_lat->value(gipf->get_view_lat());
- i_view_long->value(gipf->get_view_long());
- i_view_height->value(gipf->get_view_height());
- b_viewpoint->label(gipf->get_viewpoint());
- if (gipf->projection() == ProjectionLSQ::RECTILINEAR) {
- mb->mode(9, FL_MENU_RADIO|FL_MENU_VALUE);
- mb->mode(10, FL_MENU_RADIO);
- } else {
- mb->mode(10, FL_MENU_RADIO|FL_MENU_VALUE);
- mb->mode(9, FL_MENU_RADIO);
- }
-
- gipf->get_distortion_params(&k0, &k1, &x0);
- i_distortion_k0->value(k0);
- i_distortion_k1->value(k1);
- i_distortion_x0->value(x0);
-}
-
-void quit_cb() {
- if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
- return; // ignore Escape
- exit(0);
-}
-
-void open_cb() {
- char *file = fl_file_chooser("Open File?", "*.jpg", img_file);
- if (file) {
- gipf->load_image(file);
- scroll->position(0, 0);
- view_win->label(file);
- view_win->redraw();
- control_win->label(file);
- set_values();
- if (img_file)
- free(img_file);
- img_file = strdup(file);
- }
-}
-
-void track_cb() {
- char *file = fl_file_chooser("Track File?", NULL, NULL);
- if (file && gipf->load_track(file) == 0)
- s_track_width->activate();
-}
-
-void save_cb() {
- char *file = fl_file_chooser("Save Image As?", NULL, img_file);
- if (file && confirm_overwrite(file))
- if (gipf->save_image(file))
- fl_message("ERROR: Saving image %s failed.", file);
-}
-
-void dump_cb(Fl_Widget * o, void*) {
- fl_cursor(FL_CURSOR_WAIT);
- ScreenDump dmp(gipf); // needs to be done before fl_file_chooser()
- fl_cursor(FL_CURSOR_DEFAULT);
- char *file = fl_file_chooser("Save Screen Dump As?", "*.jpg", NULL);
- if (file && confirm_overwrite(file)) {
- JPEGOutputImage out(file, 95);
- if (dmp.save(&out))
- fl_message("ERROR: Saving image %s failed.", file);
- }
-}
-
-void focal_length_cb(Fl_Slider* o, void*) {
- gipf->set_focal_length_35mm(o->value());
-}
-
-void angle_cb(Fl_Slider* o, void*) {
- gipf->set_center_angle(o->value());
-}
-
-void nick_cb(Fl_Slider* o, void*) {
- gipf->set_nick_angle(o->value());
-}
-
-void tilt_cb(Fl_Slider* o, void*) {
- gipf->set_tilt_angle(o->value());
-}
-
-void h_d_cb(Fl_Slider* o, void*) {
- gipf->set_height_dist_ratio(o->value());
-}
-
-void view_lat_cb(Fl_Value_Input* o, void*) {
- gipf->set_view_lat(o->value());
-}
-
-void view_long_cb(Fl_Value_Input* o, void*) {
- gipf->set_view_long(o->value());
-}
-
-void view_height_cb(Fl_Value_Input* o, void*) {
- gipf->set_view_height(o->value());
-}
-
-void track_width_cb(Fl_Value_Input* o, void*) {
- gipf->set_track_width(o->value());
-}
-
-void viewpoint_cb(Fl_Value_Input* o, void*) {
- Hill *m = choose_hill(gipf->get_mountains(), "Choose Viewpoint");
- if (m) {
- gipf->set_viewpoint(m);
- set_values();
- }
-}
-
-void proj_cb(Fl_Value_Input* o, void*d) {
- if (d)
- gipf->projection(ProjectionLSQ::CYLINDRICAL);
- else
- gipf->projection(ProjectionLSQ::RECTILINEAR);
-}
-
-void hidden_cb(Fl_Menu_* o, void*d) {
- gipf->set_show_hidden(o->mvalue()->value() != 0);
-}
-
-void save_distortion_cb(Fl_Widget *, void *) {
- char buf[1024];
- const char * prof_name;
- double k0, k1, x0;
-
- buf[0] = '\0';
- gipf->get_distortion_params(&k0, &k1, &x0);
-
- gipf->get_distortion_profile_name(buf, sizeof(buf));
- prof_name = fl_input("Save Distortion Profile (k0=%f, k1=%f, x0=%f)",
- buf, k0, k1, x0);
-
- if (prof_name == NULL)
- return;
-
- if (gipf->save_distortion_params(prof_name, 0) != 0) {
- if (fl_choice("A profile with this name exists.\n",
- "Cancel", "Overwrite", NULL) == 1)
- gipf->save_distortion_params(prof_name, 1);
- }
- set_values();
-}
-
-void load_distortion_cb(Fl_Widget *, void *) {
- char buf[1024];
- const char * prof_name;
-
- buf[0] = '\0';
- gipf->get_distortion_profile_name(buf, sizeof(buf));
- prof_name = fl_input("Load Distortion Profile", buf);
-
- if (prof_name == NULL)
- return;
-
- if (gipf->load_distortion_params(prof_name) != 0)
- fl_alert("Could not load profile %s.", prof_name);
- else
- set_values();
-}
-
-void distortion_cb(Fl_Value_Input*, void*) {
- gipf->set_distortion_params(
- i_distortion_k0->value(),
- i_distortion_k1->value(),
- i_distortion_x0->value());
-}
-
-void about_cb() {
- fl_message("gipfel -- Photogrammetry for Mountain Images.\n"
- "Version %s\n\n"
- "(c) Johannes Hofmann 2006-2023\n\n"
- "Homepage: http://flpsed.org/gipfel.html\n\n"
- "Default datafile from http://www.viewfinderpanoramas.org/ and\n"
- "http://www.alpin-koordinaten.de\n",
- VERSION);
-
-}
-
-void readme_cb() {
- char *readme = file_installed_or_local(DOCDIR, "README");
- if (!readme) {
- fprintf(stderr, "README file not found\n");
- return;
- }
- Fl_Window *win = new Fl_Window(800, 600, "README");
- Fl_Text_Buffer *textBuf = new Fl_Text_Buffer();
- Fl_Text_Display *textDisp = new Fl_Text_Display(0, 0, win->w(), win->h());
-
- textDisp->textfont(FL_COURIER);
- textDisp->buffer(textBuf);
- win->resizable(textDisp);
- textBuf->appendfile(readme);
- free(readme);
- win->end();
- win->show();
-}
-
-void fill_menubar(Fl_Menu_Bar* mb) {
- mb->add("&File/L&oad Image", FL_CTRL+'o', (Fl_Callback*)open_cb);
- mb->add("&File/&Save Image", FL_CTRL+'s', (Fl_Callback*)save_cb);
- mb->add("&File/Choose &Viewpoint", FL_CTRL+'v', (Fl_Callback*)viewpoint_cb);
- mb->add("&File/Load &Track", FL_CTRL+'t', (Fl_Callback*)track_cb);
- mb->add("&File/Screen &Dump", FL_CTRL+'d', (Fl_Callback*)dump_cb);
- mb->add("&File/&Quit", FL_CTRL+'q', (Fl_Callback*)quit_cb);
-
- mb->add("&Projection/Normal Projection", 0, (Fl_Callback *)proj_cb,
- (void *)0, FL_MENU_RADIO|FL_MENU_VALUE);
- mb->add("&Projection/Panoramic Projection", 0, (Fl_Callback *)proj_cb,
- (void *)1, FL_MENU_RADIO);
-
- mb->add("&Distortion/Load Profile", 0, (Fl_Callback *)load_distortion_cb);
- mb->add("&Distortion/Save Profile", 0, (Fl_Callback *)save_distortion_cb);
-
- mb->add("&Option/Show Hidden", 0, (Fl_Callback *) hidden_cb,
- (void *)0, FL_MENU_TOGGLE);
-
- mb->add("&Help/Readme", 0, (Fl_Callback*)readme_cb);
- mb->add("&Help/About", 0, (Fl_Callback*)about_cb);
-}
-
-void usage() {
- fprintf(stderr,
- "usage: gipfel [-v <viewpoint>] [-d <file>]\n"
- " [-s] [-j <file>] [-t <dir] [-w <width>] [-h <height>]\n"
- " [-e <file>] [-E] [-p]\n"
- " [<image(s)>]\n"
- " -v <viewpoint> Set point from which the picture was taken.\n"
- " This must be a string that unambiguously \n"
- " matches the name of an entry in the data file.\n"
- " -d <file> Use <file> for GPS data.\n"
- " -V <visibility> Set initial visibility.\n"
- " -u <k0>,<k1> Use distortion correction values k0,k1.\n"
- " -s Stitch mode.\n"
- " -4 Create 16bit output (only with TIFF stitching).\n"
- " -r <from>,<to> Stitch range in degrees (e.g. 100.0,200.0).\n"
- " -b Use bicubic interpolation for stitching.\n"
- " -w <width> Width of result image.\n"
- " -h <height> Height of result image.\n"
- " -j <file> JPEG output file in Stitch mode.\n"
- " -t <file> TIFF output file in Stitch mode.\n"
- " -p Export position of image to stdout.\n"
- " -e <file> Export positions of hills from <file> on image.\n"
- " -E Export hills from default data file.\n"
- " <image(s)> JPEG file(s) to use.\n");
-}
-
-Fl_Window *
-create_control_window() {
- Fl_Window *win = new Fl_Window(400,400);
- mb = new Fl_Menu_Bar(0, 0, 400, 30);
- fill_menubar(mb);
-
- s_center = new Fl_Value_Dial(40, 60, 150, 150, NULL);
- s_center->type(FL_LINE_DIAL);
- s_center->labelsize(10);
- s_center->step(0.01);
- s_center->bounds(0.0, 360.0);
- s_center->angles(180, 540);
- s_center->callback((Fl_Callback*)angle_cb);
-
- win->add(new Fl_Box(95, 40, 40, 20, "North"));
- win->add(new Fl_Box(95, 210, 40, 20, "South"));
- win->add(new Fl_Box(190, 125, 40, 20, "East"));
- win->add(new Fl_Box(0, 125, 40, 20, "West"));
-
- s_focal_length = new Fl_Value_Slider(235, 60, 160, 15, "Focal Length in 35mm");
- s_focal_length->type(1);
- s_focal_length->box(FL_THIN_DOWN_BOX);
- s_focal_length->labelsize(10);
- s_focal_length->step(0.01);
- s_focal_length->bounds(1.0, 300.0);
- s_focal_length->slider(FL_UP_BOX);
- s_focal_length->callback((Fl_Callback*)focal_length_cb);
- s_focal_length->align(FL_ALIGN_TOP);
-
- s_nick = new Fl_Value_Slider(235, 90, 160, 15, "Nick (deg.)");
- s_nick->type(1);
- s_nick->box(FL_THIN_DOWN_BOX);
- s_nick->labelsize(10);
- s_nick->step(0.01);
- s_nick->bounds(-40.0, 20.0);
- s_nick->slider(FL_UP_BOX);
- s_nick->callback((Fl_Callback*)nick_cb);
- s_nick->align(FL_ALIGN_TOP);
-
- s_tilt = new Fl_Value_Slider(235, 120, 160, 15, "Tilt (deg.)");
- s_tilt->type(1);
- s_tilt->box(FL_THIN_DOWN_BOX);
- s_tilt->labelsize(10);
- s_tilt->step(0.01);
- s_tilt->bounds(-10.0, 10.0);
- s_tilt->slider(FL_UP_BOX);
- s_tilt->callback((Fl_Callback*)tilt_cb);
- s_tilt->align(FL_ALIGN_TOP);
-
- s_height_dist = new Fl_Value_Slider(235, 150, 160, 15, "Visibility");
- s_height_dist->type(1);
- s_height_dist->box(FL_THIN_DOWN_BOX);
- s_height_dist->labelsize(10);
- s_height_dist->step(-0.001);
- s_height_dist->bounds(0.1, 0.01);
- s_height_dist->slider(FL_UP_BOX);
- s_height_dist->callback((Fl_Callback*)h_d_cb);
- s_height_dist->align(FL_ALIGN_TOP);
-
- s_track_width = new Fl_Value_Slider(235, 180, 160, 15, "Track Width");
- s_track_width->type(1);
- s_track_width->box(FL_THIN_DOWN_BOX);
- s_track_width->labelsize(10);
- s_track_width->step(1.0);
- s_track_width->bounds(1.0, 500.0);
- s_track_width->value(200.0);
- s_track_width->slider(FL_UP_BOX);
- s_track_width->callback((Fl_Callback*)track_width_cb);
- s_track_width->align(FL_ALIGN_TOP);
- s_track_width->deactivate();
-
- // Viewpoint Stuff
- b_viewpoint = new Fl_Box(FL_DOWN_BOX, 30, 305, 300, 80, "");
- b_viewpoint->align(FL_ALIGN_TOP);
-
- i_view_lat = new Fl_Value_Input(40, 320, 100, 20, "Latitude");
- i_view_lat->labelsize(10);
- i_view_lat->align(FL_ALIGN_TOP);
- i_view_lat->when(FL_WHEN_ENTER_KEY);
- i_view_lat->callback((Fl_Callback*)view_lat_cb);
-
- i_view_long = new Fl_Value_Input(200, 320, 100, 20, "Longitude");
- i_view_long->labelsize(10);
- i_view_long->align(FL_ALIGN_TOP);
- i_view_long->when(FL_WHEN_ENTER_KEY);
- i_view_long->callback((Fl_Callback*)view_long_cb);
-
- i_view_height = new Fl_Value_Input(40, 360, 80, 20, "Height");
- i_view_height->labelsize(10);
- i_view_height->align(FL_ALIGN_TOP);
- i_view_height->when(FL_WHEN_ENTER_KEY);
- i_view_height->callback((Fl_Callback*)view_height_cb);
-
- i_distortion_k0 = new Fl_Value_Input(250, 200, 80, 20, "k0");
- i_distortion_k0->labelsize(10);
- i_distortion_k0->textsize(10);
- i_distortion_k0->align(FL_ALIGN_LEFT);
- i_distortion_k0->when(FL_WHEN_ENTER_KEY);
- i_distortion_k0->callback((Fl_Callback*)distortion_cb);
-
- i_distortion_k1 = new Fl_Value_Input(250, 225, 80, 20, "k1");
- i_distortion_k1->labelsize(10);
- i_distortion_k1->textsize(10);
- i_distortion_k1->align(FL_ALIGN_LEFT);
- i_distortion_k1->when(FL_WHEN_ENTER_KEY);
- i_distortion_k1->callback((Fl_Callback*)distortion_cb);
-
- i_distortion_x0 = new Fl_Value_Input(250, 250, 80, 20, "x0");
- i_distortion_x0->labelsize(10);
- i_distortion_x0->textsize(10);
- i_distortion_x0->align(FL_ALIGN_LEFT);
- i_distortion_x0->when(FL_WHEN_ENTER_KEY);
- i_distortion_x0->callback((Fl_Callback*)distortion_cb);
-
- win->end();
- return win;
-}
-
-int main(int argc, char** argv) {
- char c, **my_argv;
- char *view_point = NULL;
- int err, my_argc, sx, sy, sw, sh;
- int stitch_flag = 0, stitch_w = 2000, stitch_h = 500;
- int jpeg_flag = 0, tiff_flag = 0, distortion_flag = 0, position_flag = 0;
- int export_flag = 0;
- int bicubic_flag = 0, b_16_flag = 0;
- double stitch_from = 0.0, stitch_to = 380.0;
- double dist_k0 = 0.0, dist_k1 = 0.0, dist_x0 = 0.0;
- double visibility = 0.07;
- const char *outpath = "/tmp";
- const char *export_file = NULL;
-
- err = 0;
- while ((c = getopt(argc, argv, ":?d:v:sw:h:j:t:u:br:4e:V:pE")) != EOF) {
- switch (c) {
- case '?':
- usage();
- exit(0);
- break;
- case 'd':
- data_file = optarg;
- break;
- case 'e':
- export_flag++;
- export_file = optarg;
- break;
- case 'E':
- export_flag++;
- break;
- case 'v':
- view_point = optarg;
- break;
- case 'V':
- visibility = atof(optarg);
- break;
- case 's':
- stitch_flag++;
- break;
- case 'p':
- position_flag++;
- break;
- case '4':
- b_16_flag++;
- break;
- case 'r':
- stitch_flag++;
- if (optarg && strcmp(optarg, ":")) {
- stitch_from = atof(optarg);
- if (strchr(optarg, ','))
- stitch_to = atof(strchr(optarg, ',') + 1);
- }
- break;
- case 'u':
- char *c;
- distortion_flag++;
- if (optarg && strcmp(optarg, ":")) {
- dist_k0 = atof(optarg);
- c = strchr(optarg, ',');
- if (c) {
- dist_k1 = atof(strchr(optarg, ',') + 1);
- c = strchr(c + 1, ',');
- if (c)
- dist_x0 = atof(strchr(optarg, ',') + 1);
- }
- }
- break;
- case 'j':
- jpeg_flag++;
- outpath = optarg;
- break;
- case 't':
- tiff_flag++;
- outpath = optarg;
- break;
- case 'w':
- stitch_w = atoi(optarg);
- break;
- case 'h':
- stitch_h = atoi(optarg);
- break;
- case 'b':
- bicubic_flag++;
- break;
- default:
- err++;
- }
- }
-
- my_argc = argc - optind;
- my_argv = argv + optind;
-
- char *exec_file = strdup(argv[0]);
- run_dir = strdup(dirname(exec_file));
- free(exec_file);
-
- if (my_argc >= 1)
- img_file = strdup(my_argv[0]);
-
- if (data_file == NULL)
- data_file = file_installed_or_local(GIPFEL_DATADIR, "gipfel.dat");
-
- if (data_file == NULL || err) {
- usage();
- exit(1);
- }
-
- if (stitch_flag) {
- int type = 0;
- if (jpeg_flag) {
- type = STITCH_JPEG;
- } else if (tiff_flag) {
- type = STITCH_TIFF;
- } else {
- type = STITCH_PREVIEW;
- }
-
- return stitch(bicubic_flag ? ScanImage::BICUBIC : ScanImage::NEAREST,
- b_16_flag,
- stitch_w, stitch_h, stitch_from, stitch_to,
- type, outpath, my_argc, my_argv);
-
- } else if (export_flag) {
- return export_hills(export_file, visibility);
- } else if (position_flag) {
- return export_position();
- }
-
- Fl::get_system_colors();
- if (getenv("FLTK_SCHEME"))
- Fl::scheme(NULL);
- else
- Fl::scheme("plastic");
-
- control_win = create_control_window();
- control_win->callback((Fl_Callback*) quit_cb);
-
- view_win = new Fl_Window(800, 600);
- view_win->callback((Fl_Callback*) quit_cb);
-
- // The Fl_Group is used to avoid FL_DAMAGE_ALL in Fl_Scroll::position
- Fl_Group *g = new Fl_Group(0, 0, view_win->w(), view_win->h());
- view_win->resizable(g);
- scroll = new Fl_Scroll(0, 0, view_win->w(), view_win->h());
-
- gipf = new GipfelWidget(0, 0, 800, 600, set_values);
- if (img_file) {
- gipf->load_image(img_file);
- view_win->label(img_file);
- control_win->label(img_file);
- }
-
- if (distortion_flag)
- gipf->set_distortion_params(dist_k0, dist_k1, dist_x0);
-
- Fl::screen_xywh(sx, sy, sw, sh);
- view_win->size(std::min(gipf->w(), sw), std::min(gipf->h(), sh));
- scroll->size(view_win->w(), view_win->h());
-
- gipf->load_data(data_file);
- gipf->set_height_dist_ratio(visibility);
-
- scroll->end();
-
- set_values();
-
- view_win->resizable(scroll);
-
- view_win->end();
- view_win->show(1, argv);
- control_win->show(1, argv);
-
- if (view_point)
- gipf->set_viewpoint(view_point);
- else if (img_file &&
- (isnan(gipf->get_view_lat()) || isnan(gipf->get_view_long())))
- viewpoint_cb(NULL, NULL);
-
- return Fl::run();
-}
-
-static int
-stitch(ScanImage::mode_t m, int b_16,
- int stitch_w, int stitch_h, double from, double to,
- int type, const char *path, int argc, char **argv) {
-
- Fl_Window *win;
- Fl_Scroll *scroll;
- Stitch *st = new Stitch();
-
- for (int i = 0; i < argc; i++)
- st->load_image(argv[i]);
-
- if (type & STITCH_JPEG) {
-
- st->set_output(new JPEGOutputImage(path, 90));
- st->resample(m, stitch_w, stitch_h, from, to);
-
- } else if (type & STITCH_TIFF) {
-
- st->set_output(new TIFFOutputImage(path, b_16 ? 16 : 8));
- st->resample(m, stitch_w, stitch_h, from, to);
-
- } else {
- win = new Fl_Window(0,0, stitch_w, stitch_h);
- scroll = new Fl_Scroll(0, 0, win->w(), win->h());
- PreviewOutputImage *img =
- new PreviewOutputImage(0, 0, stitch_w, stitch_h);
-
- win->resizable(scroll);
- win->show(0, argv);
- st->set_output(img);
-
- st->resample(m, stitch_w, stitch_h, from, to);
-
- img->redraw();
- Fl::run();
- }
-
- return 0;
-}
-
-static int
-export_hills(const char *export_file, double visibility) {
- int ret;
-
- if (!img_file) {
- fprintf(stderr, "export: No image file given.\n");
- return 1;
- }
-
- gipf = new GipfelWidget(0,0,800,600, NULL);
- gipf->load_image(img_file);
- gipf->load_data(data_file);
- gipf->set_height_dist_ratio(visibility);
- ret = gipf->export_hills(export_file, stdout);
- delete gipf;
- gipf = NULL;
-
- return ret;
-}
-
-static int
-export_position() {
- ImageMetaData md;
-
- if (!img_file) {
- fprintf(stderr, "export: No image file given.\n");
- return 1;
- }
-
- if (md.load_image(img_file) == 0) {
- printf(",%s,,%f,%f,%d\n", img_file,
- md.latitude(),
- md.longitude(),
- (int) rint(md.height()));
-
- return 0;
- } else {
- return 1;
- }
-}
diff --git a/src/lsq_cylindrical.mac b/src/lsq_cylindrical.mac
deleted file mode 100644
index ab706f5..0000000
--- a/src/lsq_cylindrical.mac
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
- */
-
-/*
- * cylindrical projection model
- */
-
-x : m_view - c_view$
-y : tan(c_nick - m_nick)$
-x_rot : y * sin(c_tilt) + x * cos(c_tilt)$
-y_rot : y * cos(c_tilt) - x * sin(c_tilt)$
-x_scale : x_rot * scale$
-y_scale : y_rot * scale$
-
-/*
- * Some mangling for C code generation
- */
-
-load("expr2c.mac")$
-
-x_expand : trigexpand(x_scale)$
-y_expand : trigexpand(y_scale)$
-
-args: "double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick"$
-
-expr2c("ProjectionCylindrical::mac_x", args, x_expand)$
-expr2c("ProjectionCylindrical::mac_y", args, y_expand)$
-
-expr2c("ProjectionCylindrical::mac_x_dc_view", args, diff(x_expand, c_view))$
-expr2c("ProjectionCylindrical::mac_x_dc_nick", args, diff(x_expand, c_nick))$
-expr2c("ProjectionCylindrical::mac_x_dc_tilt", args, diff(x_expand, c_tilt))$
-expr2c("ProjectionCylindrical::mac_x_dscale", args, diff(x_expand, scale))$
-expr2c("ProjectionCylindrical::mac_y_dc_view", args, diff(y_expand, c_view))$
-expr2c("ProjectionCylindrical::mac_y_dc_nick", args, diff(y_expand, c_nick))$
-expr2c("ProjectionCylindrical::mac_y_dc_tilt", args, diff(y_expand, c_tilt))$
-expr2c("ProjectionCylindrical::mac_y_dscale", args, diff(y_expand, scale))$
-
diff --git a/src/lsq_rectilinear.mac b/src/lsq_rectilinear.mac
deleted file mode 100644
index 95ed273..0000000
--- a/src/lsq_rectilinear.mac
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2006-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>
- */
-
-/*
- * rectilinear (pinhole) projection model with distortion correction
- */
-
-/* switch to cartesian coordinates */
-c_x : cos(m_nick) * cos(m_view - c_view)$
-c_y : cos(m_nick) * sin(m_view - c_view)$
-c_z : sin(m_nick)$
-
-/* nick rotation */
-c_x_rot : cos(c_nick) * c_x + sin(c_nick) * c_z$
-c_y_rot : c_y$
-c_z_rot : -sin(c_nick) * c_x + cos(c_nick) * c_z$
-
-/* pinhole projection */
-x : c_y_rot / c_x_rot$
-y : -c_z_rot / c_x_rot$
-
-/* rotation around view axis */
-x_rot : y * sin(c_tilt) + x * cos(c_tilt) + x0$
-y_rot : y * cos(c_tilt) - x * sin(c_tilt)$
-
-/* distortion correction */
-d : sqrt(x_rot ^ 2 + y_rot ^ 2)$
-dist_fact : d ^ 2 * k1 + d * k0$
-x_dist : x_rot * (1 + dist_fact) * scale$
-y_dist : y_rot * (1 + dist_fact) * scale$
-
-/*
- * Some mangling for C code generation
- */
-
-load("expr2c.mac")$
-
-x_expand : trigexpand(x_dist)$
-y_expand : trigexpand(y_dist)$
-
-args: "double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick"$
-
-expr2c("ProjectionRectilinear::mac_x", args, x_expand)$
-expr2c("ProjectionRectilinear::mac_y", args, y_expand)$
-
-expr2c("ProjectionRectilinear::mac_x_dc_view", args, diff(x_expand, c_view))$
-expr2c("ProjectionRectilinear::mac_x_dc_nick", args, diff(x_expand, c_nick))$
-expr2c("ProjectionRectilinear::mac_x_dc_tilt", args, diff(x_expand, c_tilt))$
-expr2c("ProjectionRectilinear::mac_x_dscale", args, diff(x_expand, scale))$
-expr2c("ProjectionRectilinear::mac_x_dk0", args, diff(x_expand, k0))$
-expr2c("ProjectionRectilinear::mac_x_dk1", args, diff(x_expand, k1))$
-expr2c("ProjectionRectilinear::mac_x_dx0", args, diff(x_expand, x0))$
-expr2c("ProjectionRectilinear::mac_y_dc_view", args, diff(y_expand, c_view))$
-expr2c("ProjectionRectilinear::mac_y_dc_nick", args, diff(y_expand, c_nick))$
-expr2c("ProjectionRectilinear::mac_y_dc_tilt", args, diff(y_expand, c_tilt))$
-expr2c("ProjectionRectilinear::mac_y_dscale", args, diff(y_expand, scale))$
-expr2c("ProjectionRectilinear::mac_y_dk0", args, diff(y_expand, k0))$
-expr2c("ProjectionRectilinear::mac_y_dk1", args, diff(y_expand, k1))$
-expr2c("ProjectionRectilinear::mac_y_dx0", args, diff(y_expand, x0))$
diff --git a/src/strsep.c b/src/strsep.c
deleted file mode 100644
index c8e4be0..0000000
--- a/src/strsep.c
+++ /dev/null
@@ -1,70 +0,0 @@
-/* Copyright (C) 1992, 93, 96, 97, 98, 99, 2004 Free Software Foundation, Inc.
- This file is part of the GNU C Library.
-
- The GNU C Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- The GNU C Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- 02111-1307 USA. */
-
-#include "../config.h"
-
-#ifndef HAVE_STRSEP
-
-#include "string.h"
-
-char *
-strsep (char **stringp, const char *delim)
-{
- char *begin, *end;
-
- begin = *stringp;
- if (begin == NULL)
- return NULL;
-
- /* A frequent case is when the delimiter string contains only one
- character. Here we don't need to call the expensive `strpbrk'
- function and instead work using `strchr'. */
- if (delim[0] == '\0' || delim[1] == '\0')
- {
- char ch = delim[0];
-
- if (ch == '\0')
- end = NULL;
- else
- {
- if (*begin == ch)
- end = begin;
- else if (*begin == '\0')
- end = NULL;
- else
- end = strchr (begin + 1, ch);
- }
- }
- else
- /* Find the end of the token. */
- end = strpbrk (begin, delim);
-
- if (end)
- {
- /* Terminate the token and set *STRINGP past NUL character. */
- *end++ = '\0';
- *stringp = end;
- }
- else
- /* No more delimiters; this is the last token. */
- *stringp = NULL;
-
- return begin;
-}
-
-#endif
diff --git a/src/strsep.h b/src/strsep.h
deleted file mode 100644
index 28295ba..0000000
--- a/src/strsep.h
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright 2008 Johannes Hofmann <Johannes.Hofmann@gmx.de>
- *
- * This software may be used and distributed according to the terms
- * of the GNU General Public License, incorporated herein by reference.
- */
-
-#include "../config.h"
-
-#ifndef HAVE_STRSEP
-char * strsep (char **stringp, const char *delim);
-#endif
diff --git a/tests/gen_golden.sh b/tests/gen_golden.sh
new file mode 100755
index 0000000..c23ec28
--- /dev/null
+++ b/tests/gen_golden.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+# Generate golden reference data from the original C++ implementation.
+# Requires: the gipfel C++ sources built (objects in $SRC), g++, GSL, maxima.
+set -e
+cd "$(dirname "$0")"
+
+SRC=${GIPFEL_SRC:-$HOME/gipfel/src}
+DATA=${GIPFEL_DATA:-$HOME/gipfel/gipfel.dat}
+BUILD=harness/build
+mkdir -p $BUILD golden
+
+# rename class-scoped generated functions to free functions
+sed 's/ProjectionRectilinear::/rect_/g' $SRC/ProjectionRectilinear_funcs.cxx > $BUILD/rect_funcs.cxx
+sed 's/ProjectionCylindrical::/cyl_/g' $SRC/ProjectionCylindrical_funcs.cxx > $BUILD/cyl_funcs.cxx
+
+g++ -O2 -o $BUILD/funcsdump harness/funcsdump.cxx -I$BUILD -lm
+$BUILD/funcsdump > golden/funcs.txt
+
+g++ -O2 -o $BUILD/fitdump harness/fitdump.cxx \
+ -I$SRC $SRC/Panorama.o $SRC/Hill.o $SRC/ProjectionLSQ.o \
+ $SRC/ProjectionRectilinear.o $SRC/ProjectionCylindrical.o \
+ -lm -lgsl -lgslcblas
+
+$BUILD/fitdump "$DATA" 0 > golden/fit_rect.txt
+$BUILD/fitdump "$DATA" 1 > golden/fit_cyl.txt
+
+g++ -O2 -o $BUILD/exportdump harness/exportdump.cxx \
+ -I$SRC $SRC/Panorama.o $SRC/Hill.o $SRC/ProjectionLSQ.o \
+ $SRC/ProjectionRectilinear.o $SRC/ProjectionCylindrical.o $SRC/ImageMetaData.o \
+ -lexiv2 -ltiff -lgsl -lgslcblas -lm
+
+$BUILD/exportdump golden/test.jpg "$DATA" 2>/dev/null > golden/export.txt
+
+g++ -O2 -o $BUILD/stitchdump harness/stitchdump.cxx \
+ -I$SRC $SRC/Panorama.o $SRC/Hill.o $SRC/ScanImage.o $SRC/ProjectionLSQ.o \
+ $SRC/ProjectionRectilinear.o $SRC/ProjectionCylindrical.o \
+ -lfltk -lgsl -lgslcblas -lm
+
+# stitch: cylindrical resample of one image (projtype center pitch roll scale
+# k0 k1 x0 out_w out_h from to bicubic)
+$BUILD/stitchdump golden/stitch.ppm 0 20 2 0 200 0 0 0 64 48 10 30 0 > golden/stitch_rect_near.txt
+$BUILD/stitchdump golden/stitch.ppm 0 20 2 0 200 0 0 0 64 48 10 30 1 > golden/stitch_rect_bic.txt
+$BUILD/stitchdump golden/stitch.ppm 1 20 2 0 200 0 0 0 64 48 10 30 1 > golden/stitch_cyl_bic.txt
+$BUILD/stitchdump golden/stitch.ppm 0 20 2 3 200 -0.3 0.1 0 64 48 10 30 1 > golden/stitch_dist_bic.txt
+
+echo "golden written:"
+wc -l golden/*.txt
diff --git a/tests/golden/export.txt b/tests/golden/export.txt
new file mode 100644
index 0000000..4928658
--- /dev/null
+++ b/tests/golden/export.txt
@@ -0,0 +1,17 @@
+#
+# name height x y distance flags
+#
+AMIESBERG 1780 163 502 16561
+FELDERNKREUZ 2048 1466 450 19944
+SCHOTTELKARSPITZE 2049 1453 450 20154
+ZWIRCHKOPF 1773 1444 581 4118
+SCHOETTELKOPF 1907 1429 461 21275
+SEINSKOPF 1961 1406 458 19374
+OCHSENSTAFFEL 1871 1401 464 21971
+SIGNALKOPF 1895 1392 465 18879
+OBERE WETTERSTEINSPITZE 2297 879 415 8077
+SIMETSBERG 1840 770 475 24291
+ROTPLATTENSPITZE 2390 683 395 7420
+WETTERSTEINWAND 2482 524 373 7038
+WETTERSTEINKOPF 2433 408 384 6903
+OFELEKOPF 2478 223 336 4954
diff --git a/tests/golden/fit_cyl.txt b/tests/golden/fit_cyl.txt
new file mode 100644
index 0000000..cdde13c
--- /dev/null
+++ b/tests/golden/fit_cyl.txt
@@ -0,0 +1,14 @@
+ret 0
+a_center 6.6322511575784517
+a_nick 7.5149662500849354e-18
+a_tilt 0.017453292519943285
+scale 3000.0000000000009
+k0 0
+k1 0
+x0 0
+hill 0 -1136.4890178984479 91.333004952545252
+hill 1 -1404.1585409517218 -150.78485966743139
+hill 2 -1578.4832174937635 -134.52393246509686
+hill 3 -1747.4097596895024 100.5675701918971
+hill 4 -1817.5350955613387 -72.855082700985704
+hill 5 -2000.1065521517946 77.132392173389633
diff --git a/tests/golden/fit_rect.txt b/tests/golden/fit_rect.txt
new file mode 100644
index 0000000..d37b380
--- /dev/null
+++ b/tests/golden/fit_rect.txt
@@ -0,0 +1,14 @@
+ret 0
+a_center 6.6322511575784384
+a_nick 3.8358482613767501e-16
+a_tilt 0.01745329251994306
+scale 3000.0000000000027
+k0 1.1252597395717405e-14
+k1 1.6387771195849632e-15
+x0 -1.3533330861078106e-14
+hill 0 -1194.2901436402242 97.812455966247811
+hill 1 -1516.1807642651722 -169.86071632098324
+hill 2 -1741.859869105966 -156.929589386619
+hill 3 -1976.4828429431623 118.42826203922655
+hill 4 -2077.8688492890328 -90.908545475464734
+hill 5 -2361.0905544534971 94.952405220629913
diff --git a/tests/golden/funcs.txt b/tests/golden/funcs.txt
new file mode 100644
index 0000000..148f758
--- /dev/null
+++ b/tests/golden/funcs.txt
@@ -0,0 +1,200 @@
+-5.0020872795986167 -99.958363572649731 -1997.5010209985358 100.20872675351576 -99.958363572649731 -0.0025010436397993083 -0.25031293023198864 -0.012526086657295765 2000.0005033366022 99.958363572649702 2002.504111200557 5.0020872795986167 -0.049979181786324864 -5.0020860269899527 -0.2503129302319887 5.0000020849885135e-05 -5.0020860269899492 -99.958338541356667 -1997.5005207899326 100.20865147158865 -99.958338541356667 -0.0025010430134949748 99.958338541356667 2002.5026068169227 5.0020860269899492 -0.049979169270678331
+13516.721449252176 -90.435970179787844 -2094.321714207947 94.176090161439177 -91.370816903521415 6.6356020860344502 88574.515499403147 584080.25339792401 2071.384290138119 115.21423411228621 2050.7511282384571 -230.33272928265995 -0.044396647118207094 -592.62316475689147 -3907.8902807322411 -0.14422847327746321 228.03281699198908 -89.172833284302698 -2034.4667315076003 101.70618777767493 -89.172833284302698 0.11194541825821752 101.5584481408693 2037.4263215910285 -228.03281699198908 -0.043776550458666026
+27767.208948336269 -78.370118821736924 -2312.2369447710262 96.198434307327048 -81.368351460862215 13.388239608648153 354725.43413069751 4639113.3306955835 2205.8452849230935 133.34349798825696 2122.6111715925726 -475.50400455376513 -0.037786942536999484 -1001.1764046447552 -13093.42482460415 -0.23325238856520261 457.28344700366785 -73.773629498937709 -2071.458501120499 102.70564419274925 -73.773629498937709 0.22048382208470002 102.64345149894163 2072.7136185431309 -457.28344700366785 -0.035570698890519628
+42786.744822628083 -61.932343554367023 -2634.2026668600679 106.92714827045366 -66.624046266450847 20.268472203992459 789395.13857445191 15265034.215475645 2383.1289907217902 152.27561820791109 2210.6997071117912 -726.15863833436924 -0.029337917363508777 -1142.6223500995281 -22095.612725883831 -0.2468093463902078 670.06180036875242 -53.275492511765933 -2108.4764212385344 103.19971537509598 -53.275492511765933 0.31741440093261603 103.19002413577427 2108.6744418299004 -670.06180036875242 -0.025237087878619578
+58255.898888001211 -38.939949770053168 -3015.7298274621357 126.83658315586665 -43.332816846375451 27.120995757914905 1370955.9700713323 34635237.551917978 2570.4534376670372 168.40131317582612 2303.1275504441019 -967.4577948495205 -0.018128468235592721 -916.38714071113111 -23151.207625144576 -0.17681797849218084 854.14521581782822 -27.415304235208286 -2145.5205371727593 103.18179930160902 -27.415304235208286 0.39764674851854198 103.17763599693059 2145.6071107369216 -854.14521581782822 -0.012763177018253392
+73448.676763104959 -7.6238731169898015 -3378.0856362827617 154.70173216420562 -8.6914456947891701 33.614955040322634 2066121.5359411193 63534274.830888338 2728.4322527630902 177.06618184881916 2385.7482157252502 -1173.2050997310059 -0.0034891867812310308 -214.46061560918022 -6594.7716315287762 -0.035280785149196019 998.47377689819257 3.7918322863667067 -2182.5903304250364 102.64390350515058 3.7918322863667067 0.45696740361473343 102.5887398067225 2183.7639461162553 -998.47377689819257 0.0017353923507399116
+87330.743415746212 32.308481451665855 -3619.1785045295851 185.66520251611638 37.15379620431473 39.302764813567151 2831907.5016284138 101098935.64888549 2818.6991604203467 174.34612947024877 2444.9201382215633 -1311.1096581278914 0.014540270680317668 1047.6795159480805 37402.0987276542 0.13779202463105583 1093.8050340665045 40.010774483566486 -2219.6847131940563 101.5749028791941 40.010774483566486 0.49226149147907489 101.40894444091182 2223.3172863414416 -1093.8050340665045 0.01800664918252317
+98784.776633330446 78.800201325402142 -3649.5512327121278 210.792964951936 89.621357264118274 43.729427460526985 3618692.7031834144 144833661.69047368 2811.8012527034261 159.4377832902091 2470.1936043395458 -1353.9048084061174 0.034882780577867263 2886.6159671955425 115533.20348404509 0.27412551441404837 1133.2878414841853 80.56359514154228 -2256.8020353140378 99.959478646501879 80.56359514154228 0.50167677799211385 99.627172008521384 2264.3296031634854 -1133.2878414841853 0.035663388730209064
+106896.71978013412 127.50539161514627 -3436.9406133486041 219.96376379307009 140.08681915762014 46.557804782288379 4374117.5957616027 190919326.8967135 2693.7088398612641 135.77857074067586 2456.399155041936 -1291.3040874871499 0.055533707149453951 5217.4059051149061 227726.75899629967 0.29177564638545916 1112.9205641904543 124.42970301454353 -2293.9401037752423 97.777996944129058 124.42970301454353 0.48472150008295045 97.235797377460798 2306.7314148332634 -1112.9205641904543 0.054194121522013736
+111208.94690609786 173.11258385086157 -3024.6817974795981 207.17672773470983 178.74116385900462 47.66778692931755 5047210.4625721155 234757791.7619946 2469.4976518726803 109.54643057642629 2404.8592263451469 -1135.2122568654108 0.074201707608599038 7856.7027988559448 365433.97848517011 0.12226068648735365 1031.8644260261244 170.27121106397678 -2331.0962146994229 95.007471632557071 170.27121106397678 0.4422907955534181 94.230768934684491 2350.310413409959 -1031.8644260261244 0.072983802427765448
+111855.43935949082 211.31288651648168 -2507.5075653240669 174.27322649260614 198.63299981941364 47.196387915396969 5592103.368888028 271637046.94951057 2163.4203747729871 86.60138873451352 2323.6157289528869 -914.4770032823094 0.089161555492186365 10564.381234787095 513166.00072738226 -0.26318881182204934 892.59007720218904 216.48413339014562 -2368.2671973644124 91.623607705915347 216.48413339014562 0.37662028573932027 90.611709440397348 2394.7146122084346 -892.59007720218904 0.091343516198373678
+109534.8921475643 240.27410245586734 -1986.785774372137 129.95323597871536 198.59610217223391 45.506810198406441 5971053.9465199793 297398634.17386824 1815.4074117123682 70.310061328018648 2226.5579705356972 -662.87848732622797 0.099823058768536505 13098.014701861881 652369.19940963306 -0.84189088613913809 700.84419839648479 261.27387415908657 -2405.4494695962917 87.603751476764415 261.27387415908657 0.29116917257851466 86.381995918816273 2439.4712727230062 -700.84419839648479 0.10854751730747261
+105328.29105262848 260.92024568533162 -1538.838424045844 85.066592840869092 183.65379909924218 43.096682100093489 6156843.2530330233 309020443.18338138 1474.685244319008 61.398905866519684 2131.3902303267814 -407.59644179481364 0.10675951132787707 15251.790740863476 765508.38479768345 -1.5454189983856206 465.4326760302805 302.75117281933569 -2442.6391035811575 82.930401222564072 302.75117281933569 0.19043890181271705 81.548817626246986 2484.0218018895171 -465.4326760302805 0.12387527529432721
+100410.56337582441 276.12628246617305 -1208.6794556529276 47.600317869478047 163.12971030835899 40.471811114802257 6134665.014151738 305051888.7217381 1191.6126533132633 59.069742146243442 2056.6585869565693 -162.97207902807421 0.11129636536322976 16870.159747961919 838884.28826701269 -2.2760833245662622 197.8270684546639 339.04239653565259 -2479.8319008974745 77.594786244176447 339.04239653565259 0.079736827269110799 76.123211251351535 2527.7707430952992 -197.8270684546639 0.13665554072376163
+95734.822294933256 289.36893075003775 -1019.9131088692203 19.909257080194696 147.58325249402307 38.020183596081516 5903459.6738222893 285846599.74117625 1009.1046087400678 62.127941863409049 2018.3825537580958 69.749923140585679 0.11492014723988792 17843.850049433193 864002.49086810986 -2.9261034496663907 -88.388815213315112 368.40609632565497 -2517.0234753402683 71.599939376664565 368.40609632565497 -0.035102786025939282 70.120072632571691 2570.1446315999788 88.388815213315112 0.1463090136321108
+91777.764352078346 303.34253291296068 -986.88827951873145 -1.1908071251816681 145.64543878745653 35.920847104531639 5476515.9218860399 253549212.374001 955.14320684023187 69.465857502724404 2026.990554298427 298.21824788685711 0.11872506180546406 18100.900833780765 838027.17186010792 -3.3951331059570724 -378.20594514854037 389.34638056468975 -2554.2093419055277 64.962702444037348 389.34638056468975 -0.14802581023426237 63.558144423279046 2610.6542751304059 378.20594514854037 0.15238605892942847
+88420.90665787262 318.86772038495747 -1120.0766846241718 -21.985600321771891 161.46370961379839 34.113004111833575 4881146.040891408 211819830.3689281 1037.7797065064137 79.846855782865859 2085.2121506296075 532.09322942543861 0.12301995385222124 17602.623290750034 763874.8458372067 -3.605499149967776 -655.97616714621461 400.71320646707989 -2591.3850101322209 57.714235423148033 400.71320646707989 -0.25307722497924945 56.459979295321027 2648.9525220067208 655.97616714621461 0.15459614447032402
+85005.004779554467 334.31491020417934 -1420.7647008094982 -48.011098872984689 193.2255784198162 32.333588733189224 4157295.4959459621 165318984.33801159 1243.6638194532334 91.251617575550227 2187.4110460424563 775.12815633635023 0.1271642868787293 16350.165193493422 650180.55757232022 -3.5159336716229399 -906.25625538918086 401.78035574474148 -2628.5460798647664 49.89883936650002 401.78035574474148 -0.34471519794187178 48.851878444623054 2684.8793287508024 906.25625538918086 0.15282630496186439
+80543.164819928847 345.75479162535362 -1865.964968093753 -80.164806488697039 233.19248523435783 30.211239617377665 3355032.9943088028 119019225.95347808 1540.5276659919978 100.30748499451735 2320.6042203049496 1018.3510420380387 0.12969046947687685 14402.447885390297 510924.39390677586 -3.1334118937172679 -1114.6643737900249 392.29368950596307 -2665.6883374043732 41.571207900290872 392.29368950596307 -0.41810366608778132 40.763805346254522 2718.4872248880952 1114.6643737900249 0.14714692029480986
+54774.217574596754 257.49377349037019 -1770.2454571947612 -83.496260484232778 199.49904379488538 27.387108787298377 1872740.2568401687 57306478.397251055 1393.4786906399324 75.898507984537375 1825.4473280738157 915.64068958305029 0.12874688674518509 8803.7579878593115 269397.93978538067 -1.8640823594196654 -938.73578906254966 275.60866024220729 -1999.8578246143652 24.263798650344896 275.60866024220729 -0.46936789453127481 23.847040250279846 2034.8079709887807 938.73578906254966 0.13780433012110366
+48123.134387004189 250.12072570895006 -2154.4298989760705 -101.19887903877954 215.95995786342363 23.624513690232789 1296694.430322838 32716277.756276436 1652.3685702195667 70.064002341145269 1939.0079936551665 1039.1202961373237 0.12278877059840454 6739.5890992231016 170043.3531425437 -1.3253414913261368 -1009.9434073571433 255.0346825311295 -2036.9264422601732 17.564324176574839 255.0346825311295 -0.49579941451013415 17.310945130573277 2066.7407865852392 1009.9434073571433 0.12520112053565513
+39144.867598011784 230.73531911834888 -2443.9182365940419 -105.77728096281756 214.95933267200758 18.874092380912145 785356.90178847103 15282696.666336469 1877.8375225614002 55.651239239010522 2037.4833881677116 1094.7517850030306 0.11125135926632057 4629.2039410337538 90082.253656036744 -0.78866233641615024 -1028.7027731939904 227.86234110639955 -2073.9737495975405 10.555138994008658 227.86234110639955 -0.4959994084831198 10.434844527754331 2097.8828327247247 1028.7027731939904 0.10986612396644145
+28029.411969285215 200.1891667298209 -2585.8326096705459 -96.132794738625648 195.42180922580309 13.277788711172532 378172.33221566555 5061695.8951167613 2046.1616622696645 35.052754007775761 2113.1933344423355 1068.0301698889602 0.094831438526679718 2700.9487087879743 36151.193060830374 -0.34311615386843514 -992.07052534817126 195.09249930885326 -2110.997498164802 3.2773625323959545 195.09249930885326 -0.46995287794797314 3.250037222234929 2128.7461137781447 992.07052534817126 0.092417100572644836
+15285.329697875801 161.5081888356157 -2580.6389936836877 -77.080318123627251 161.17649395223046 7.1160752783406895 108410.16346497693 770193.73465574556 2147.4797992973772 12.686563717829159 2163.8989093996547 960.89623312800927 0.075190032046376018 1145.4858677359573 8138.0380793536342 -0.04392939889664068 -900.07562294222282 157.92011404154971 -2147.9958754633485 -4.2326566931929515 157.92011404154971 -0.41902961961928437 -4.2093933581590219 2159.8668372506445 900.07562294222282 0.073519606164594839
+1571.1854761697573 118.59411974415893 -2475.229040573498 -56.203967606724859 118.70005749950093 0.71907802113032371 1131.5946007087739 815.5064561035947 2187.6746591065626 -7.2210064167260697 2193.0129907530581 789.12053458748335 0.054276485008768388 85.413509489315373 61.554967108692267 0.098131512615200936 -755.81759653190193 117.65276824653115 -2184.9675579864338 -11.943723900683633 117.65276824653115 -0.34591194349286131 -11.906743752986035 2191.7536638005058 755.81759653190193 0.053845660524728216
+-12651.711321168666 75.690546150458431 -2389.9195909142018 -41.079576534061978 75.889822314151871 -5.6938394784737474 -71436.019414410082 -405049.15325528709 2237.5014053659365 -22.329699939710647 2233.9994409602841 580.68095767100704 0.034064152182924579 427.37548992636914 2423.2604467014853 -0.036836437157529246 -565.39451098978486 75.626981678741899 -2221.9117548085806 -19.828971444290584 75.626981678741899 -0.25445297524292748 -19.802874626009491 2224.8398563290393 565.39451098978486 0.034035545309964853
+-27218.812708129048 33.059981632027252 -2335.533163343106 -33.807952250453106 33.172131908181605 -12.049053876993822 -323042.96410069067 -3863069.0813206779 2283.9511564260865 -32.600820221140381 2276.4999264254625 342.78680153495827 0.014634786025687144 392.36812325524011 4692.0853690863541 -0.0094980096847612581 -337.6596889224374 33.129293861677176 -2258.828240533885 -27.864004235443886 33.129293861677176 -0.14947308053228747 -27.85641338350289 2259.4437695504162 337.6596889224374 0.01466546873026878
+-41912.260532359367 -8.8053248031529101 -2343.0335741851613 -36.446434308703608 -8.8725936657602116 -18.254468872978819 -747464.03960285056 -13486508.126187572 2340.6892960193786 -38.214749959206785 2322.6496422406062 84.818560832738598 -0.0038350717783767027 -157.03432751612903 -2833.3734091902902 0.0037358089593695237 -83.81600892090556 -8.6718836740158878 -2295.7173766156243 -36.024343777050518 -8.6718836740158878 -0.036505230366248066 -36.023974032230136 2295.7409395242667 83.81600892090556 -0.0037769528196933311
+-56625.954106544195 -49.899219413378177 -2441.3855115162701 -50.295537972401938 -50.958102045168282 -24.27173343615268 -1324028.9468637998 -31541978.763935465 2427.2473936709089 -40.083961798430593 2377.6660983425936 -186.93939152185413 -0.021388435239339126 -1166.7443307870972 -27795.03045745298 0.044302187389976024 183.13391920548389 -48.779094986344667 -2332.580120299785 -44.282588643962448 -48.779094986344667 0.078497179256529742 -44.260392929122546 2333.7498632633033 -183.13391920548389 -0.02090831332462266
+-71331.808475325714 -90.508261175079767 -2652.7081590040157 -76.831375717898226 -94.849809770533014 -30.097809483259798 -2020905.3834406107 -59012630.386017576 2559.1933724100554 -39.271203559232688 2445.9145221276663 -468.51490407865339 -0.038189139736320578 -2564.1945180436182 -74877.262721492196 0.14770761705529684 449.05473763333339 -86.400482981510422 -2369.4180207064474 -52.60574409178296 -86.400482981510422 0.18947457284106894 -52.518978965132625 2373.3324695153374 -449.05473763333339 -0.03645589999219849
+-86004.614391498297 -130.81615700789706 -2990.1591666891886 -117.70288521020588 -142.41025913256681 -35.73104046177744 -2797097.6835988224 -95350649.66699487 2744.595661711699 -36.99198540093078 2529.8869453224111 -754.90312142893788 -0.054348216455295827 -4254.4876496796333 -145031.81772158036 0.33715244548891438 699.42818749912578 -120.96511766730254 -2406.233201839721 -60.953152400398196 -120.96511766730254 0.29058088387998576 -60.751776633819418 2414.2092164097712 -699.42818749912578 -0.050255553663191746
+-100531.5157252807 -170.53858960331937 -3449.2800208000563 -173.48437277269915 -194.51627442714974 -41.134008070900443 -3605259.3064122223 -138469610.80238709 2981.7536508985868 -34.825922435601903 2629.4234316525162 -1036.0463160970191 -0.069778473651112671 -6115.8516593933018 -234895.61416431845 0.61792715890855388 920.14596624912895 -152.11950098585802 -2443.0283326030781 -69.275379214695562 -152.11950098585802 0.37649180288425899 -68.909840361332385 2455.9876105632484 -920.14596624912895 -0.062242021679974641
+-114636.36554565781 -208.70931434657749 -3994.6022919548859 -241.41098727134258 -250.16671682670511 -46.205709611309075 -4395004.8127875039 -184980526.07792667 3258.6699081958086 -34.873421471062919 2741.360927976215 -1293.6312376780636 -0.08412306100224809 -8001.6357519765479 -336779.33334179781 0.97400911233840282 1098.3023023407784 -179.70714192156635 -2479.8065841862317 -77.514282040696358 -179.70714192156635 0.44268532944005579 -76.943518418466653 2498.2016799332368 -1098.3023023407784 -0.072433350230377405
+-127844.80617588977 -243.76745995257508 -4551.0591269552442 -313.42090126457094 -306.21996250320859 -50.772361467787832 -5116292.6498334138 -230624583.31038514 3554.3684373818078 -39.485991535862041 2859.7358107730201 -1500.992978125161 -0.096809952324295109 -9755.4660289291241 -439742.2981646001 1.3694039457580796 1222.9418711869287 -203.73422285446833 -2516.5715754756334 -85.604314350710894 -203.73422285446833 0.48567985352936011 -84.802744696675163 2540.3586287641247 -1222.9418711869287 -0.080911129012894489
+-139503.47318276973 -274.00175868793673 -5015.2610333639877 -376.69452422762743 -358.07661579040553 -54.600185198735709 -5722487.0730161443 -270820912.48907447 3841.7871528379665 -50.365048337812105 2976.5632444774096 -1628.0686232905957 -0.10724139283285195 -11239.659388416352 -531925.15296202258 1.7560264015007541 1285.720062764299 -224.3252234172424 -2553.3273074169274 -93.474922931561665 -224.3252234172424 0.50321724570031268 -92.43733660714966 2581.9877772353393 -1285.720062764299 -0.087798521885417771
+-148852.99017070685 -298.16141013208153 -5292.3131842504463 -417.81353585862149 -401.10280655196902 -57.427851146106043 -6172985.5798118589 -301249150.35571134 4091.741114667228 -67.443284683176174 3083.0609607550746 -1650.6167576796211 -0.11503140822996974 -12364.857992378538 -603420.00095626176 2.0862752609317661 1281.4364472040579 -241.67295186237206 -2590.0780875179985 -101.05370973025317 -241.67295186237206 0.4943813453719359 -99.797297353714455 2622.6862467733076 -1281.4364472040579 -0.093238021551841077
+-155143.68914697925 -315.84460086189893 -5338.7390444411667 -428.43628738699016 -431.99909791694614 -59.012434061232128 -6435642.3116548872 -318414623.13668442 4277.4023016964948 -88.509745779168952 3171.0442610733749 -1559.1014562415614 -0.12013868423807489 -13101.808319698343 -648234.80804250424 2.3243089881075454 1208.4084130874874 -255.98777527553398 -2626.8284459196784 -108.26989159872141 -255.98777527553398 0.45964564970996097 -106.83312083435723 2662.1559762269635 -1208.4084130874874 -0.097370777967110675
+-157772.55065519176 -327.40715259022193 -5179.0820600396937 -408.4231823834084 -449.34374537011405 -59.17950137103967 -6489225.1055106027 -320154532.48685879 4378.7138856598594 -110.09741583441708 3234.2057248504379 -1361.8263926007694 -0.12280838431741257 -13466.339394838802 -664379.72533929395 2.450396168293496 1068.6600838295647 -267.45092974861535 -2663.5830446723003 -115.05753657482508 -267.45092974861535 0.40084774337192969 -113.49609744056373 2700.2276773697758 -1068.6600838295647 -0.10031917845034334
+-115729.10171651257 -246.78329264278511 -3614.8761191063422 -269.76836215489169 -335.38813268708526 -57.86455085825628 -4680519.491845252 -226426150.51451358 3245.3010735825956 -95.440204605127505 2418.86466986819 -799.77393088254257 -0.12339164632139255 -9980.8431444128037 -482836.12449771259 1.8190123119504944 642.1837497260409 -204.34819182594165 -1998.0366866704851 -89.795117461150141 -204.34819182594165 0.32109187486302043 -88.596832442420066 2025.0604228753475 -642.1837497260409 -0.10217409591297083
+-112304.94049120303 -248.89821148266691 -3376.1269962554234 -228.23637930877098 -330.77194461370135 -55.132518650566041 -4424845.0802564314 -206230133.97067246 3198.3464202489913 -106.4818401611588 2435.254267761803 -553.4006774466842 -0.12218861633906083 -9806.6569622551233 -457061.91797636834 1.7480647027048928 457.4793861312267 -209.78626080399161 -2034.8616657611653 -94.506342076444582 -209.78626080399161 0.22458487291665524 -93.31131343891343 2060.921935136656 -457.4793861312267 -0.10298785508296103
+-106121.64510680575 -247.62935754668334 -3147.2423989495483 -183.42318003281443 -318.21434221390456 -51.167620591516751 -4025357.4147032052 -177338441.24059913 3093.6270643339881 -113.70699041228956 2432.8674073419911 -283.2621491472969 -0.11939699013822726 -9392.9628540483936 -413810.05947036081 1.5979311266333971 241.44285198036036 -213.15221007391983 -2071.6988483862042 -98.814561465025292 -213.15221007391983 0.11641410413710722 -97.672317446009558 2095.9266509063914 -241.44285198036036 -0.10277348605299895
+-97603.276947706501 -243.07084493681123 -2961.0544359537425 -141.28732745106507 -299.38601368686108 -46.235564636526057 -3507341.5985413915 -142963000.97673935 2949.2680912618316 -117.29321995054326 2416.3609641093549 -5.5658243483738543 -0.11514488154278125 -8734.6707252078086 -356034.53622501309 1.3815194571748537 4.811421323760297 -214.28814182482623 -2108.5516254855552 -102.68892201877564 -214.28814182482623 0.0022792142699006616 -101.64173681231357 2130.2754186685393 -4.811421323760297 -0.10151025193028244
+-87271.583189094832 -235.30151186895819 -2835.2527360454487 -105.97329712778193 -276.34307490928489 -40.629228672762949 -2905537.1201156238 -106861998.77034977 2788.0458105473786 -117.66583586671474 2392.0080324102823 267.66475523246305 -0.10954446548834179 -7833.9048310059197 -288121.16101434571 1.1193430324259641 -240.16399326967178 -212.98449918169314 -2145.4231625255275 -106.10368614023756 -212.98449918169314 -0.11180819053522895 -105.18295346188334 2164.2034035210527 240.16399326967178 -0.099154794777324556
+-75630.722490994638 -224.45180863231582 -2776.8365032295665 -79.834428954324721 -251.26030430854217 -34.613602970706928 -2261848.8409303064 -72773054.205737576 2633.4152709755322 -115.229624926521 2366.6764308530805 527.58610079102937 -0.10272393987749007 -6712.5639750455048 -215971.01149100158 0.83908888470779164 -480.35281245191402 -209.00969589532841 -2182.3163232039078 -109.03679384641663 -209.00969589532841 -0.21984110409698582 -108.26109863555014 2197.9527091434566 480.35281245191402 -0.095656611393742971
+-63071.923666331604 -210.72246150949599 -2784.5991200901412 -63.694684885163042 -226.00206533697195 -28.385204170266249 -1622549.0874879055 -43845593.324902646 2505.3767166588805 -110.31438316706861 2346.6813113420853 766.44318152346398 -0.094834591138387034 -5420.9150087799135 -146487.54651350222 0.57239600523712486 -702.4481410406961 -202.1487463493888 -2219.2335977806611 -111.46834055874849 -202.1487463493888 -0.31613327679599285 -110.84330598418374 2231.7476392496742 702.4481410406961 -0.090976033460571024
+-49824.892807761564 -194.39267300317866 -2848.0802534866984 -57.175579073187102 -201.71777062084661 -22.056172114989625 -1035233.5023928528 -22161595.033865135 2416.9405027858847 -103.32484526110537 2336.7150321936147 975.13120675641869 -0.086052533423275179 -4038.9812475670874 -86463.842747613875 0.34901026797692297 -893.70189499157448 -192.24661093927608 -2256.1770377368366 -113.37925087051653 -192.24661093927608 -0.39561836874350353 -112.89895654536677 2265.7752577817032 893.70189499157448 -0.08510252808290221
+-35968.546905028961 -175.84928284931851 -2944.1822448659659 -58.914435988680815 -178.68391184492091 -15.665743425535261 -545755.97362869279 -8414230.4096599817 2371.8258248578209 -95.0120040431067 2339.0357883670599 1141.5108968103214 -0.076589408906497597 -2668.1866472598313 -41136.951881165907 0.18988561440776192 -1042.6512791046566 -179.25105436989597 -2293.1481983600479 -114.75035883984013 -179.25105436989597 -0.45411641075986792 -114.39991415235428 2300.1728679997714 1042.6512791046566 -0.078071016711627167
+-21496.215549166001 -155.61388173617851 -3035.9204109631828 -66.619227901905788 -156.49203400175725 -9.2139800896553794 -195260.80633173219 -1786368.0305773818 2363.8067767195021 -86.659243385936577 2353.0515064336032 1250.8528423780435 -0.066701192342982649 -1413.518205319443 -12931.748977477286 0.10192058993804047 -1139.7955697532129 -163.24948348175013 -2330.1480907135269 -115.56200943999471 -163.24948348175013 -0.4885536089812314 -115.32074984193387 2335.0229340748324 1139.7955697532129 -0.069974060643699154
+-6414.0875366397804 -134.30129055468018 -3079.2412749434611 -77.203836967834974 -134.46728902572696 -2.7063660492150969 -17301.36124939713 -46751.314757134125 2377.868391934097 -79.90778834574968 2375.3967393048224 1289.1589093198309 -0.0566672112045064 -362.26433313756371 -978.9018112310489 0.076901191710395805 -1178.1838223194502 -144.49455957232806 -2367.1771442697773 -115.79419350159178 -144.49455957232806 -0.49712397566221528 -115.63895384679927 2370.3549641175423 1178.1838223194502 -0.060968168595919012
+9194.8283387350457 -112.91183295940621 -3061.8592051755672 -88.118619793681987 -112.98292006002312 3.8200367007623783 35041.535501642458 133706.65342012836 2411.2766702793419 -76.301024053686405 2409.6452133956655 1252.9532038248697 -0.046909776883841389 -430.307544354534 -1641.9081205611387 -0.016397333596859385 -1153.8780363606941 -123.41434929304825 -2404.2351812824049 -115.42713777898777 -123.41434929304825 -0.47938431091013467 -115.33513377961937 2406.1530639331754 1153.8780363606941 -0.051273099000020042
+25041.60348447362 -90.954706859778753 -2928.5508499152929 -95.184201897694663 -89.940330857204074 10.246155271879552 258776.64827717104 2662799.4392022979 2407.6488019832677 -76.174485348908291 2432.3536274462758 1134.339394740972 -0.037215510171758899 -939.91401951554826 -9671.6706887078635 0.094307926621917559 -1066.2623121466409 -100.60428463271356 -2441.3214037347266 -114.44220631059626 -100.60428463271356 -0.43627754179486128 -114.39319764175592 2442.36732180219 1066.2623121466409 -0.041163782582943356
+40305.564349555054 -68.529801426973179 -2669.6539720287037 -95.915150152551107 -65.52790130217663 16.245693006672735 684278.17487977201 11364124.009645762 2323.0728759912754 -78.278029218701008 2425.1661319272998 941.06213657578235 -0.027621846604987173 -1163.4484767074121 -19321.926744864439 0.17663229406621303 -918.17634147111585 -76.800077099352833 -2478.4343934458338 -112.82293719832805 -76.800077099352833 -0.37008316867034091 -112.80052032141631 2478.9269333611942 918.17634147111585 -0.030955291051734314
+54087.561298045483 -46.400509798126066 -2329.4797067453892 -90.004421242074727 -41.707724678481441 21.480365884847291 1292423.7014457677 29280574.110803679 2149.008043172932 -81.239704268647216 2385.3826974645799 697.18305582399444 -0.018427525733965871 -1108.7414034404203 -25119.150010044887 0.20450026893616982 -715.86080775204141 -52.832765885152256 -2515.5721256390052 -110.5560423281359 -52.832765885152256 -0.28429738195077103 -110.54809228591822 2515.7530324659524 715.86080775204141 -0.020982035697042198
+65705.893805513639 -25.450036623264285 -1959.0795226077521 -78.615728327008938 -20.777266976636266 25.716592487480881 2050218.9511369111 58077100.94949688 1896.4194019558081 -83.684321043243031 2317.5426417063777 430.08713141922914 -0.009960875390710092 -794.1166974547175 -22495.156226208768 0.16388387907079269 -468.71244942744045 -29.569056148659559 -2552.7319959893748 -107.6322320538642 -29.569056148659559 -0.18344909958021152 -107.63064922271006 2552.7695368177597 468.71244942744045 -0.011573016105150512
+74918.690821725424 -6.5695645426702933 -1601.449155546441 -63.968054484427057 -4.6846315385562995 28.903815903443448 2912982.1806380036 97653821.705673084 1594.9861020596518 -84.708259666135518 2232.9970898233078 163.03584689868288 -0.0025345542217092183 -255.43725120461315 -8563.1913411358983 0.05610485331592921 -188.85712334984839 -7.8418184070829584 -2589.910860873551 -104.04678244875029 -7.8418184070829584 -0.07286154450225632 -104.04678144576214 2589.910885839724 188.85712334984839 -0.0030253929039671906
+82019.853877232774 9.5532557710943777 -1289.4430773293552 -48.863489953335893 5.7309213640881431 31.198118629605467 3827347.3826290471 146033176.29381499 1288.6109887734324 -83.946998264668892 2148.1197793307056 -89.506346553151204 0.0036337983153649218 445.78997331319692 17009.202263250838 -0.10028805881028932 109.44039105425327 11.623095559115457 -2627.1050902497032 -99.799825586031005 11.623095559115457 0.041628144181914517 -99.799021959640228 2627.1262448756743 -109.44039105425327 0.0044211089992831709
+87769.499045290882 22.615151877355629 -1051.9613638287246 -36.013652475653011 11.205269784611442 32.921792590131609 4734782.7166266851 199535427.18084303 1028.0375305330981 -81.334503892602172 2081.4955380453553 -323.19757696364189 0.0084828026546720295 1219.9890782962837 51413.350192180587 -0.27174613047620838 410.50147036549748 28.276834372841797 -2664.3106323088782 -94.896398861885359 28.276834372841797 0.15397654552344242 -94.893912164405435 2664.3804506395172 -410.50147036549748 0.010606464505942159
+68938.822614063611 24.204915005454922 -681.92689627100924 -20.173922885457348 10.251007413087954 34.469411307031805 4125333.5437748227 187358593.6066848 637.73442424742598 -56.773193632978746 1517.2761452967475 -400.98198848273626 0.012102457502727461 1448.434191503751 65782.945832704572 -0.30904002720379686 516.50531123900328 30.934026580315333 -1998.9072058922795 -66.10900904432863 30.934026580315333 0.25825265561950161 -66.105841133141368 1999.0029971323768 -516.50531123900328 0.015467013290157667
+73713.29270455298 29.873527056957929 -696.7790074009572 -17.177426955238506 12.087319589255113 36.18718345829798 4677514.3535836618 224144040.61125106 615.40269326366899 -51.732638005032058 1537.8256729267257 -559.79023075924545 0.014665452654373063 1895.6398021880764 90838.067547652783 -0.37400892492074567 710.81289715461912 38.794060364629686 -2036.0616413657704 -61.826675330973686 38.794060364629686 0.34895085770968048 -61.822265883129262 2036.2068626301061 -710.81289715461912 0.019044703173603184
+79352.588354306718 33.923815710909111 -847.49475032370151 -16.981346604232748 15.137373251817269 38.260650122616546 5103268.5099463603 253144370.00518355 705.73985321855855 -44.013496875567917 1599.415046391739 -717.00209514006804 0.016356709600245475 2181.6848579874986 108221.08181231063 -0.3821788565927432 873.73812425818051 44.565497365976832 -2073.2157592496865 -57.035537787971414 44.565497365976832 0.42128164139738694 -57.029953522215528 2073.4187646363985 -873.73812425818051 0.021487703648011974
+85799.996548822295 36.692285445755537 -1143.5622297149812 -18.391586986788827 19.751336107476824 40.644242799063143 5370150.2744383775 270854191.96947515 907.69407888380385 -32.944570950506147 1700.9609009128478 -867.26799342944412 0.017381471078046204 2296.5395650589921 115830.5329332774 -0.3393132489791576 995.17752856052562 48.641483393514797 -2110.3657932263118 -51.748666670880631 48.641483393514797 0.47142469377571083 -51.741847476483294 2110.6439238941434 -995.17752856052562 0.023041915392475035
+92483.186923639412 38.807559664677896 -1555.6379549040771 -19.786509966319343 25.577636426009377 43.055487394617977 5455745.5008139582 274956528.15914619 1202.67404289256 -18.765624413872079 1835.0252176808351 -994.6227644482392 0.018066834108323042 2289.3260502900916 115376.55899054362 -0.26536483970943897 1066.9780742559717 51.624377752892336 -2147.5079406171062 -45.982645731043768 51.624377752892336 0.49673094704654175 -45.974394900585111 2147.893344796214 -1066.9780742559717 0.024033695415685445
+98409.876566492792 41.405120952125067 -2011.3795844579004 -19.262219620020147 32.516465637390318 45.038845110523013 5350203.5289204419 264746211.30936471 1557.0944140724973 -3.2965326282253762 1988.8734891901065 -1075.373733118392 0.018949712106235729 2251.0527597680557 111389.72309831619 -0.18161365929203546 1083.4603176657747 54.286114707416473 -2184.6384449397419 -39.757462626169435 54.286114707416473 0.49586284561362687 -39.747514277821779 2185.185234963189 -1083.4603176657747 0.024844903756254678
+102395.72545248146 45.994471292259533 -2420.0905093109322 -15.247611899891114 41.366473293173286 46.082684722088864 5057804.2538219169 241307611.3188138 1927.9438320624433 10.405492883132142 2146.614950649885 -1086.4134006691297 0.020699582039720762 2271.8822638962292 108391.39966401726 -0.098043014319430313 1041.8188107109074 57.509996943293544 -2221.7536796928021 -33.096355433005435 57.509996943293544 0.46886535135504387 -33.084539765490547 2222.5471470635225 -1041.8188107109074 0.02588208683316541
+103358.74986723096 53.906524911773936 -2712.9170857342187 -7.6289448812756833 53.421871378453531 45.754205341846372 4597074.4949594308 207378737.54649466 2270.404948048697 19.470110910713 2292.0500301300258 -1014.5373439424025 0.023863003502334633 2397.5939250633096 108157.91692606932 -0.010850074384730974 942.3731771362103 62.217781145666663 -2258.8502315235655 -26.02558951702423 62.217781145666663 0.41716386770084563 -26.012142278821884 2260.0179668373712 -942.3731771362103 0.027542178462003835
+100588.52540632928 65.696281483995904 -2868.1235414830567 1.6787260564365636 69.367900357774232 43.810333365125999 3999428.9296148154 166921039.27672553 2545.9605429745643 22.638541599132807 2411.6244097788617 -861.23836254765797 0.02861336301567766 2612.1031964017493 109019.30948512425 0.088731748023088544 788.65105870083721 69.287464098498731 -2295.9249809075882 -18.574161573356101 69.287464098498731 0.34348913706482459 -18.560227490245854 2297.6486456372186 -788.65105870083721 0.030177466941854848
+93907.915908457988 80.965389950546069 -2906.9736714522546 9.5482593442042187 88.539540586642872 40.251999960762099 3306760.6979082641 124493553.12642671 2729.3007274332545 20.631910807640971 2496.8614473673856 -641.01642510906424 0.034704410608892441 2851.0181148122851 107335.67003058492 0.20256513507302609 587.29319437086099 79.468297900133635 -2332.9751784838381 -10.773460602984638 79.468297900133635 0.25173304516539263 -10.761811106989981 2335.5005884475695 -587.29319437086099 0.034062708058351321
+83683.228376475628 98.711097586873464 -2872.5074219376856 13.238548108888233 109.1758029425238 35.309379061804066 2568467.7638374269 84554583.98780632 2812.7019640353483 15.506915699178661 2545.8179250140684 -375.52634050021612 0.04165025214636011 3029.714280910403 99738.931603935751 0.31931373875353386 347.78077115380148 93.300133999823657 -2369.9985152464292 -2.6569490806486256 93.300133999823657 0.1467429414151061 -2.6528719759719941 2373.6408817524803 -347.78077115380148 0.039367145147604921
+70686.822746744278 117.93608652672005 -2810.2174048718675 11.514883727871823 129.41273577680303 29.367188511318769 1838184.1759203456 50797903.861562289 2807.1146571990153 9.7580000962338822 2563.3348439986244 -87.146460468098311 0.048997127763489842 3066.8834670939341 84752.797655899005 0.41580988932684421 81.995318166039738 111.04415215374554 -2406.9931848929227 5.7400497524423919 111.04415215374554 0.034065358606580699 5.7278162526214542 2412.1340534888573 -81.995318166039738 0.046133839698274011
+55859.174574750323 138.17544800833323 -2759.0443868793882 4.6948445110670551 148.34073531390013 22.855636078048413 1170306.130652582 25609392.932813536 2739.7844914193715 5.7419993682183437 2560.0498913616252 205.68217716119713 0.056536599021412941 2894.9152066939987 63348.400126780252 0.46283391449711614 -196.36877514267883 132.63136867348081 -2443.9579367682513 14.380370565240026 132.63136867348081 -0.080347289338248293 14.338873995979775 2451.0307285244712 196.36877514267883 0.054268154121718828
+40045.85110244355 159.73376270229684 -2750.7131252658191 -6.0907613937490508 166.61505346545698 16.141012133189662 616376.77683941741 9715334.2432751171 2648.8047742245194 5.5201050895066581 2550.3220322306342 490.47432334738596 0.064382814470897554 2458.5863228379826 38752.251528351539 0.43129589964027648 -472.51012518283852 157.63409459050521 -2480.892118020035 23.224767087206242 157.63409459050521 -0.19045148133125292 23.136524070503338 2490.3542742169398 472.51012518283852 0.063536515352884002
+23791.992466011983 183.57896960965081 -2811.6813773106774 -19.76879018154602 186.45373373871359 9.4487658721254899 221452.06150574764 2076816.8191769174 2575.5271303284085 10.916602067819747 2549.4044570167039 759.95409116153292 0.072906659892633369 1708.7236946310525 16024.714713454085 0.29705848111233563 -731.30813685911494 185.26383304032501 -2517.7957037920478 32.23140268700142 185.26383304032501 -0.29043214331180101 32.074818258379864 2530.0872029515504 731.30813685911494 0.073575787545800242
+7259.5238887401911 210.91571149909086 -2962.0772216540113 -35.746861457498746 211.06286364656111 2.8413009349276677 20641.494237703289 58682.398562459261 2556.1750054572713 23.492077425700206 2570.3517382991463 1008.816803638259 0.082550180625867275 599.71087777563525 1704.9382349263431 0.045464571296937289 -958.1506549270938 214.39710307560304 -2554.6693145235577 41.355126935440111 214.39710307560304 -0.37501004106735569 41.105880743955012 2570.1595943965294 958.1506549270938 0.083912760499257555
+-9663.1923255777183 241.4123540960654 -3179.5649459975557 -52.769746208599145 241.52342034338264 -3.7280834589420206 -36097.728067876487 -134731.78022649506 2591.1624639270194 43.581643218730605 2609.6057305055588 1223.5799758207097 0.09313748229014869 901.8176619872545 3365.9597305062757 -0.034082047145649615 -1139.7561160276837 243.6285069241053 -2591.5142196882789 50.546670777978761 243.6285069241053 -0.43972072377611254 50.180166933275494 2610.442014934195 1139.7561160276837 0.093992479523188774
+-26936.768629395639 273.20156539498771 -3415.9725444517226 -68.819859785449054 276.45898984385587 -10.24601317207898 -274829.33868409024 -2810027.1385879791 2667.7478268359455 70.403367496487036 2659.0688321760972 1382.8389343995746 0.10391843491631333 2787.4095285143085 28500.21929604622 -0.33584402658157181 -1264.9440123768736 271.34817501076776 -2628.3323275920493 59.751958799539445 271.34817501076776 -0.48115025195012312 59.246736066729092 2650.745262538951 1264.9440123768736 0.10321345569066862
+-44537.064861402774 304.63622928808775 -3647.4397351345774 -80.263992862651108 315.69306782621271 -16.705575716955281 -727766.80322484707 -12024398.740302075 2796.8143112496418 100.46978427526803 2724.9308723687 1471.679065201535 0.11426715277122571 4977.9691460448921 82247.617868428191 -0.69209229109384962 -1325.3092579929962 295.83867920402503 -2665.1261611470268 68.911774669043822 295.83867920402503 -0.49711525056001354 68.253535950255724 2690.828701027167 1325.3092579929962 0.11096724651313768
+-46159.813575564534 246.02350942771818 -2833.8585115916953 -60.668702124286568 263.03700660109024 -23.079906787782267 -1013954.2564786517 -22830510.909397878 2195.2986931710093 95.242985798782342 2075.050510239435 1092.4127835127745 0.12301175471385908 5404.1939352654699 121682.51950937776 -0.77393573991075482 -973.55559293045235 233.35904202435819 -1999.1852163119727 57.685541404030914 233.35904202435819 -0.48677779646522618 57.08301743645157 2020.2870617040678 973.55559293045235 0.1166795210121791
+-59636.518248913002 262.4635507300502 -2920.1342961002824 -51.360255723301108 289.71064669570859 -29.276641261125675 -1609201.1357261601 -45229530.9649591 2345.958259076333 111.63777684663792 2149.2262420549873 1033.8894906482954 0.1288480857781297 7082.1814606726421 199057.61844395223 -0.98652647496659096 -918.05373587269708 244.1335296020487 -2035.9992891529741 64.555548159315379 244.1335296020487 -0.45068911923058275 63.842733091437616 2058.7315705140818 918.05373587269708 0.11984954816006319
+-72839.597006329612 271.04802447769572 -2934.9190562875924 -29.442599310693343 307.10584457408805 -35.120345711827198 -2286611.5879000272 -75925162.03767772 2489.2214934318808 120.84353183984344 2222.0990555127646 909.68198746760015 0.13068853639233158 8508.8547866931694 282529.91537931189 -1.0996840961668113 -810.43598980565412 249.05648943047723 -2072.8017353463042 71.293944427628333 249.05648943047723 -0.39075987936627488 70.490892641176146 2096.415667219806 810.43598980565412 0.12008509615741429
+-85208.955418882819 270.05342027434244 -2887.8482815284415 3.3945632668413781 310.68120775414764 -40.364261212166184 -3004877.8320096848 -113369753.8204072 2601.1874572493875 122.59713723167785 2286.0773773281999 728.1431643302559 0.12792677417069753 9523.3832177801596 359303.66267671087 -1.0861324565867765 -654.74498249135422 247.26146568740532 -2109.5958378098871 77.843888551424783 247.26146568740532 -0.31015868426876086 76.983122145052633 2133.1837253573394 654.74498249135422 0.11713001690545018
+-96079.115741800691 258.88625121757485 -2799.5464202514886 42.226814155833566 298.07707759722996 -44.729569712197716 -3718841.3771332116 -154737341.61372355 2660.4965201572536 118.30243899400408 2334.1730940365151 502.22686964986309 0.12052432552028623 10020.459655208489 416941.49643720849 -0.94683675488032937 -457.83395628684133 238.17521765527235 -2146.3851683449493 84.150416577153791 238.17521765527235 -0.21314429994731907 83.274900833471065 2168.9513195872187 457.83395628684133 0.11088231734416776
+-104798.02708691417 238.03336652775434 -2691.0738656956619 80.444473904887062 269.73513871732553 -47.962483792638068 -4382231.8162590237 -196253703.44313517 2652.479801566059 110.18403398083233 2361.2736658598883 247.16227974127662 0.10893975584794248 9953.5976117571572 445761.53790916316 -0.70955803790997041 -229.07651894953628 221.56177102209497 -2183.1735178097751 90.162800942631037 221.56177102209497 -0.10484051210505092 89.321840185321847 2203.7279897179387 229.07651894953628 0.10140126820233179
+-110863.20102677068 208.80634797545474 -2577.1667325955859 111.72472407524485 228.69561660768451 -49.89342980502731 -4950692.6714638285 -233683101.72205514 2572.3718403565722 100.58973351124384 2365.1547726492959 -21.290245311260701 0.093972253814336063 9324.4290901142977 440132.65540092404 -0.42128402878018545 20.07444329571852 197.54251826549915 -2219.9648195667692 95.836621555237173 197.54251826549915 0.0090344029233656708 95.079965744002337 2237.6315200995059 -20.07444329571852 0.088903023521826802
+-114035.78578717713 172.99523550591852 -2464.5805814399246 131.6171320042703 179.77325364892482 -50.480648865505586 -5384954.2608909048 -262914877.80392924 2426.9353658822633 91.65099141176016 2347.0833041257815 -287.94035007168389 0.076580449537812526 8169.1148451416475 398848.66745764046 -0.13847612770025855 276.65809488685142 166.59049995020368 -2256.7630677484303 101.13518161301883 166.59049995020368 0.12246927617833175 100.50600005319581 2270.8907188972712 -276.65809488685142 0.07374524123514993
+-114396.08171077596 132.51133741397845 -2354.2234030075729 138.39021525491518 128.39917025846216 -49.824077400163745 -5653893.588873487 -280566393.40775102 2234.0752893040326 85.193449666568299 2311.8789666217658 -538.83751842002403 0.057713997131523719 6549.219080353957 324995.64205244434 0.08247876865378094 526.87872739333829 129.49944830637179 -2293.5722320343934 106.02997629765252 129.49944830637179 0.22947679764518217 105.55764510811413 2303.8351144579215 -526.87872739333829 0.056402198739708961
+-112319.29806782401 89.082921092543231 -2245.6091092578231 133.10587957816844 79.397747151951918 -48.143719703310765 -5737167.5871925214 -284504410.33011359 2020.3818722910241 82.695218359620796 2267.3725397974008 -761.6609160462732 0.038183849589602761 4550.2745855476242 225646.74434320079 0.19399072349963375 756.84562591952476 87.330645714219912 -2330.3961707433232 110.50006203350419 87.330645714219912 0.32440875521625578 110.1938627367997 2336.8717189345034 -756.84562591952476 0.037432767129969957
+-108373.8765429138 44.061621136338388 -2142.2227483866095 119.00245473953812 35.925267942089427 -45.727374068740005 -5627092.2387105571 -274190179.6580022 1816.9649730599031 85.132845563465807 2223.3081719926613 -945.89766387499594 0.018591401323349532 2287.8097031355724 111477.63834591188 0.16553423873909021 953.34012725236641 41.342826382880439 -2367.2385441172191 114.53034988726293 41.342826382880439 0.40225321824994364 114.37515134761209 2370.4507100529436 -953.34012725236641 0.017444230541299763
+-103169.0979201119 -1.6247500224200679 -2055.2505141580687 100.32691938357083 -1.2217352342529808 -42.862109646909808 -5329468.8999921866 -250777086.93555358 1654.3287946571504 92.674351713592117 2189.8530569089357 -1082.9095574453399 -0.00067501039568760614 -83.930701046299973 -3949.3422530115117 -0.0084758305979377065 1104.5677400328241 -7.0880616906424905 -2404.1027297095525 118.10902406773792 -7.0880616906424905 0.45889810553918742 118.06381750171873 2405.0232592254301 -1104.5677400328241 -0.0029447701249034029
+-97195.511801507775 -47.384892057203565 -2001.9481320857135 80.959939457368534 -33.70432611762439 -39.769031015346876 -4863191.509977947 -216935927.67675236 1557.2798565111414 104.36504105267798 2175.9677498579681 -1166.0563018318999 -0.019388253705893442 -2370.9099369158221 -105760.90732754151 -0.30305031654149217 1200.8529516455417 -56.551626504047974 -2440.9917417812721 121.22443346684108 -56.551626504047974 0.49134736155709563 121.2242407921419 2440.9956215114844 -1200.8529516455417 -0.023138963381361691
+-90704.379545425938 -92.805870034049164 -1997.2451607485054 63.507860640247443 -64.957438119377684 -36.559604814762572 -4258750.9963245997 -176445303.78298426 1540.8503988673165 118.11447078337964 2187.9294412991248 -1190.9664377793936 -0.037406638465960963 -4357.4201538343395 -180533.28861371093 -0.66344927337164517 1235.2364449245786 -105.68082620205871 -2477.9081565596844 123.86189933462838 -105.68082620205871 0.49787845422191801 123.82313053297825 2478.6839851905343 -1235.2364449245786 -0.042596060540934587
+-83659.705594626183 -137.50225014759948 -2043.8977558721924 49.494645467205764 -98.788277602681092 -33.224664652353532 -3556060.1246077828 -133636801.72559817 1607.9360775101109 131.20867224646128 2228.2780346548384 -1155.7958507819671 -0.054607724443049829 -5844.7046319169867 -219644.10236914197 -1.0161366560104819 1203.9388263887568 -153.21823571526198 -2514.8540451249328 126.00091672484787 -153.21823571526198 0.47813297314883113 125.82977278356125 2518.2745554184989 -1203.9388263887568 -0.060849180188745827
+-75774.471566354085 -181.01229248942357 -2130.2460688041442 40.42995091007468 -137.76092809559034 -29.657327423230562 -2802055.7517294353 -92794091.774343237 1748.951909883385 141.16693030434939 2295.3792455698008 -1061.1903643478458 -0.070846298430302768 -6693.6334205850617 -221669.2632008548 -1.2880033520170746 1106.6614421220916 -198.06588854633435 -2551.8309155586103 127.61320209633199 -198.06588854633435 0.43313559378555438 127.21626625281039 2559.7930511158579 -1106.6614421220916 -0.077520895712851021
+-66619.795541362066 -222.73556452594505 -2238.0652670798786 38.601333905118167 -182.11216893456003 -25.702081613179811 -2048200.7369442021 -57576132.901065379 1943.5189131105085 146.42524140514934 2383.693129011891 -909.94148604620193 -0.08593193075846646 -6847.9217580678305 -192499.12673444749 -1.4256602430259677 946.70312820263814 -239.31590362578731 -2588.8396658210208 128.66195932245245 -239.31590362578731 0.36524040439916594 127.95774564951147 2603.0873089040483 -946.70312820263814 -0.092328666522294489
+-55776.666081171687 -261.90556246702732 -2353.1596475078404 46.713286882917927 -229.65832610971137 -21.215924717067967 -1347670.2710650391 -30512835.878292985 2164.005399126404 146.48827023488309 2484.7234047877942 -706.68856871047353 -0.099621743045655117 -6328.1362111122735 -143276.42730632963 -1.4064660999444678 730.88140601312909 -276.26136966674107 -2625.8805486207748 129.10260616117114 -276.26136966674107 0.27800738151887755 128.03259106594629 2647.8259908077112 -730.88140601312909 -0.10508230112846752
+-42986.942868097438 -297.61504191236253 -2470.5860029519167 66.67962593516755 -276.58219930458193 -16.124134609188836 -752050.68775197503 -12631234.582231587 2380.4866127613918 141.64601484342151 2588.5116571672538 -457.97373983138249 -0.11163354910441205 -5206.7344645165094 -87450.866676672726 -1.2390984932187785 469.257196699112 -308.38907358565547 -2662.9531493064133 128.88502757631139 -308.38907358565547 0.17601545262532334 127.42262200666943 2693.5153638952593 -469.257196699112 -0.11567482129994579
+-20908.51212071293 -243.3653001683347 -1918.7998662300472 72.895025234645047 -235.76277568531671 -10.454256060356466 -227675.24585434594 -2429256.8270170935 1898.8758392072309 98.104075364530928 1987.0027428903622 -127.69349544029963 -0.12168265008416734 -2650.0333561923967 -28275.413070034985 -0.70824500691796366 129.24382857557001 -248.13715327860433 -1997.8219589708578 94.677920836629923 -248.13715327860433 0.064621914287785007 93.313558788867752 2027.0326384732123 -129.24382857557001 -0.12406857663930217
+-8836.6541451041703 -263.85749530005307 -2027.7208520213001 104.05747370915809 -261.89951998606119 -4.3380727271007222 -38808.497848149345 -169430.46176307928 2009.758227365377 89.327593417238703 2057.7979126526848 102.72382939829063 -0.12953239828181298 -1158.7997980257576 -5059.0977687076274 -0.45395342540821282 -102.50020119611308 -265.37764260103677 -2034.9098248608148 93.872901007575038 -265.37764260103677 -0.050319195481646088 92.255106552038185 2070.5942000159216 102.50020119611308 -0.13027866597989041
+4170.2522620950022 -278.97273588910576 -2133.3689037114495 139.03753652037889 -278.20529324170042 2.0107291524083908 8442.0766571251588 17051.176528209355 2064.0555477143093 77.777626216166567 2106.9300481941477 342.41401245155015 -0.13450951585781379 -564.74022999285648 -1140.6536265057971 0.35191243899936842 -337.57459489370558 -278.65366171244119 -2072.0186296294864 92.446158127948166 -278.65366171244119 -0.16276499271634792 90.635525421022805 2113.4115016047795 337.57459489370558 -0.134355671028178
+17454.290093523421 -287.50370964093776 -2222.8380436695629 171.10757144905941 -282.90656948041607 8.268256794658182 147640.09302699537 1234786.301492929 2054.9358011976133 65.427672250130584 2128.3344848369143 575.08578061571177 -0.13619313578443284 -2431.8992184474291 -20339.162486176316 0.53220485688473718 -563.21844366393691 -287.87592313923227 -2109.1463225621746 90.368928431488925 -287.87592313923227 -0.26680172603691943 88.446537651030781 2154.9887439029435 563.21844366393691 -0.13636945672156905
+30711.984331090513 -292.83372684332977 -2347.1348481588007 200.67350582217512 -284.22972180318845 14.297944288217185 456210.52670916723 6648760.4344799882 2047.0630173512186 53.794050765248571 2150.8493743087711 792.4212281744658 -0.13632855067194125 -4349.8924498396382 -63394.838833200163 0.57554657124675224 -766.74170259372886 -292.97737772753061 -2146.2903761201051 87.624673934051799 -292.97737772753061 -0.35695609990397059 85.683261925642142 2194.9210399871004 766.74170259372886 -0.13639542724745374
+44002.511484184681 -295.91090196615067 -2521.537250238257 226.96100846695828 -286.17556236881796 20.138449191846533 922551.63234269491 18956808.014506523 2072.564799471621 44.223410311636783 2185.9964480312128 986.25700202367273 -0.13542833041929092 -6204.0341887073937 -127481.95429681205 0.46340330126132528 -936.23653147706852 -293.89175230862742 -2183.4478270424406 84.210839662574841 -293.89175230862742 -0.42848353843344095 82.344317251682156 2232.9406692714092 936.23653147706852 -0.13450423446619103
+57602.246589690672 -297.18530272636622 -2756.3759542937305 249.39539514999606 -291.77690462157301 25.923603325693374 1524260.2842122414 39922668.586580686 2160.058713449378 38.016547478505572 2243.5590059491055 1147.4147165778372 -0.13374676090295509 -7864.0640047269471 -205971.65999547666 0.2024640177126725 -1061.2676224335305 -290.53889876676158 -2220.6153282421847 80.139054663750088 -290.53889876676158 -0.47761819191428012 78.431906618767357 2268.9492178503233 1061.2676224335305 -0.13075558000304302
+71844.407569616073 -296.38396100667444 -3047.9385674084642 266.05186613951906 -301.87894124735777 31.803633275615791 2229276.1425091173 70030872.103061453 2328.693947750482 36.083018654743341 2329.5322037793258 1264.0579726505086 -0.13120139929467661 -9196.5640144544759 -288902.47645990981 -0.17189751485159074 -1133.5018212080142 -282.81878967649686 -2257.789210604938 75.433726774740094 -282.81878967649686 -0.50177150119876679 73.951879461784955 2303.0307771385237 1133.5018212080142 -0.12519645404006058
+86913.492984646145 -292.51669141139138 -3371.8704342629499 272.93211413661538 -314.51393055428298 37.854308791222188 2997382.0837782114 108299997.99673843 2583.6713128698129 38.445692630532371 2444.7233704245641 1321.9578259890427 -0.12740274016175582 -10088.011192893538 -364495.27002053236 -0.59969268879606208 -1147.239667066822 -270.61549058799466 -2294.9655535580828 70.129274463051118 -270.61549058799466 -0.49966884454129873 68.913772076727582 2335.4441984329324 1147.239667066822 -0.11786388962891754
+102657.78085676646 -284.09944497822784 -3688.4176882373326 265.28029173000471 -325.21683988397888 44.002477864023348 3782133.5743993907 152282064.66215509 2914.0255969294667 43.91423761900775 2584.2258243590791 1307.2835679324451 -0.121774301319429 -10466.834957396844 -421431.76766133326 -1.0083708458343135 -1099.8159778330366 -253.81181228303549 -2332.1402630568523 64.266482953348444 -253.81181228303549 -0.47141705007845547 63.330825267923231 2366.5956005862045 1099.8159778330366 -0.10879203269740055
+118483.37605058675 -269.48403080678122 -3959.7206587780852 240.39178351112406 -328.18698078697361 49.992985675353054 4533491.6940381415 198278500.21499151 3293.1966549238314 50.258271764555907 2737.8646434624084 1210.7172397742365 -0.11370634211256593 -10311.181670050086 -450973.72510254814 -1.328628103128503 -991.84308310442577 -232.31448950064902 -2369.3091554382686 57.888609613818133 -232.31448950064902 -0.41849919118330203 57.219978647299136 2396.9951753920313 991.84308310442577 -0.098022991350484812
+133375.09771958171 -247.18489624222596 -4166.4889900154085 199.82005758858554 -317.79986149561159 55.411340971990732 5201267.3664339893 241783186.03094304 3682.559510905277 54.842376848788469 2891.6287221045441 1030.7333669833401 -0.1026941820698903 -9639.5410858717059 -448098.27901928459 -1.5074316908712726 -827.27844161258542 -206.08859945188149 -2406.4680454290119 51.037870360888505 -206.08859945188149 -0.34369690137622994 50.601841162862577 2427.2042539900067 827.27844161258542 -0.085620523245484623
+146053.00166120779 -216.17069031530249 -4311.9002261021969 149.49781151605717 -289.96955087606841 59.759820646975363 5739051.3766821399 278105646.09602517 4037.507604350526 55.319353954982134 3029.9450937115134 774.70852241899718 -0.088449545955524747 -8494.2773085222871 -411619.67787977756 -1.5148974094097716 -613.30775576052577 -175.19858725268014 -2443.6128364632923 43.752826293625724 -175.19858725268014 -0.25094425358450317 43.500637602504774 2457.7793304918423 613.30775576052577 -0.071685183000278291
+155228.53167643765 -176.12929099737124 -4413.0667223600767 98.059264980421389 -243.12713182602801 62.566921272244116 6108036.3118727235 303067364.97631902 4315.0312613348106 50.028885510853243 3138.4168297462302 458.00989617032911 -0.070991249898174624 -6930.4533991133458 -343873.89702935057 -1.3463073405111123 -360.04482942756488 -139.85188366330988 -2480.7396113753589 36.066996675141084 -139.85188366330988 -0.1451208502327952 35.944130998438261 2489.2193754594691 360.04482942756488 -0.056369159074288543
+159889.02567948098 -127.6820049276882 -4488.6019888980891 54.481732822690347 -178.75356592614793 63.498421636013099 6280211.1145149041 313641535.28986132 4481.3148052246042 38.074878473812056 3206.4716185084353 101.93741694184351 -0.050707706484387688 -5015.1656316792505 -250463.59425997193 -1.0219820237801429 -80.059760531927864 -100.4399364281233 -2517.8447214812099 28.00879986767206 -100.4399364281233 -0.031794980354220757 27.963521044531902 2521.9216417537605 80.059760531927864 -0.039888775388452466
+159527.60880417743 -72.478345667285296 -4549.0640256600509 25.853514271937591 -101.39445572897709 62.4374202756076 6240639.5549515644 308424038.38628972 4517.8486137373138 19.278814530127942 3229.3585552307072 -268.74148993691063 -0.028367258578193853 -2835.3163081879575 -140126.61653884331 -0.58607481988907806 212.24295693704198 -57.570795312540895 -2554.9248720573378 19.602711613649564 -57.570795312540895 0.08306965046459569 19.593318830582042 2556.1496699225972 -212.24295693704198 -0.022532600905104068
+154250.77400192543 -13.104889463401703 -4592.2855108852345 15.628808276218738 -18.049676154350053 59.510329476051481 5988720.9131104555 287861519.80078834 4424.8665943502274 -5.792103065713146 3209.074000535943 -628.28017276141122 -0.0050558987127321386 -508.79177820200096 -24456.236425167386 -0.10329292267031828 501.36580190967356 -12.0874045327294 -2591.9772022619231 10.871359700571066 -12.0874045327294 0.19342816431700369 10.87119839095145 2592.0156626819717 -501.36580190967356 -0.0046633505141702933
+144732.24547781638 47.212002064476714 -4603.538404263616 22.65628629830988 63.099208098107788 55.052204441923315 5538442.0493327286 254206202.16943258 4221.4526354164218 -35.652215551070178 3154.0289533415839 -952.28752569803419 0.017958159781086617 1806.6529445724625 82922.666624871519 0.3484303586006699 771.54088533438141 34.93342563094658 -2628.9993576257275 1.8381603469698944 34.93342563094658 0.2934731401043672 1.8378257566612346 2629.4779871711817 -771.54088533438141 0.013287723708994515
+132027.31870486165 105.07195122512267 -4560.9487837477645 41.387716687488066 134.47403630450029 49.522625170615768 4917710.659715944 211209989.75102758 3942.2912745332305 -67.687496984325506 3077.5311193652083 -1219.8125380267536 0.039411834668088022 3913.6858920237432 168088.28626587451 0.69096810364534511 1007.590119560028 82.204790473123651 -2665.9895533650115 -7.4699508400937216 82.204790473123651 0.37794078003001802 -7.4633336134819697 2668.3533036584877 -1007.590119560028 0.030834505053684791
+86794.410861857643 116.4990344874959 -3290.0059540731299 47.04256363428091 141.30971371148198 43.397205430928814 3083181.6960925544 121055318.96080308 2687.1210142906916 -72.655266186387053 2216.3468079854892 -1047.0559221631975 0.058249517243747953 4138.3735102020255 162485.43700533366 0.63962507930630486 884.77681239401647 94.914819531907384 -1999.9605082760015 -12.593512517410812 94.914819531907384 0.44238840619700825 -12.568426170275513 2003.9523926128134 -884.77681239401647 0.047457409765953688
+75502.828504921956 150.31361945462479 -3166.2392214556348 60.322142751406588 171.95260467113144 37.065698824213037 2480739.2767702625 86571830.397868901 2479.7821661943976 -91.438668112734973 2173.1457335566306 -1136.5566053148107 0.07379166394434207 4938.7407994957175 172350.1679022169 0.62954928736427407 984.74403733291319 127.57984302913388 -2036.9034123344131 -19.903100191641389 127.57984302913388 0.48342858975597114 -19.836552634581409 2043.736804635739 -984.74403733291319 0.062631243509638626
+63805.416568952409 177.97497750295469 -3003.2068540744908 66.321389610024823 192.57203536686075 30.764424575194028 1856747.6210966874 55555336.562597707 2310.6374601332104 -103.57972261786564 2145.893587293097 -1163.8134614595272 0.085812428882813249 5179.0997357761335 154962.7023312723 0.49692297824583331 1034.7341880970855 157.32826987453626 -2073.8208292605104 -27.392005293911662 157.32826987453626 0.49890751595809329 -27.26111012502647 2083.7783521360911 -1034.7341880970855 0.07585741073989212
+51846.4051446464 199.44915881966637 -2829.4319186949797 63.706646204416728 206.45651716318537 24.560116127260255 1258266.4111695907 30719659.336122315 2199.8995044898088 -107.98371319092885 2140.3266957959295 -1130.2408850748834 0.094480890014053229 4840.4547350710418 118176.18206551732 0.29341764635078815 1030.2063292066312 183.01891523384424 -2110.7131153429286 -35.01672895352808 183.01891523384424 0.48801815689560929 -34.801504555815669 2123.7664865920015 -1030.2063292066312 0.086697733412526873
+39496.246011534502 215.1744678114228 -2671.9205219253554 52.912957594062753 216.74119919769055 18.387451588237663 732956.18435386021 13539514.893278619 2154.1344215178665 -105.48969456055812 2157.3141710787595 -1040.0106770839966 0.10017433324554134 3993.1252416083862 73762.906751574352 0.087109404901005633 969.48325510681298 203.64498373381832 -2147.5811829108702 -42.728159796418439 203.64498373381832 0.45134229753576022 -42.415360509463873 2163.4188854465278 -969.48325510681298 0.09480678944777389
+26496.562826570709 225.59500586818231 -2544.9958702635181 34.865172311404201 224.89418197001837 12.126573376004902 325223.40909062669 3967846.3515937091 2164.8781667582202 -98.445428896242973 2192.5789046200721 -898.59250267244931 0.10324714227376765 2768.9922410878175 33782.733512672727 -0.05946145097531913 853.94005934005122 218.41189362937729 -2184.4264969690967 -50.473110503700802 218.41189362937729 0.39081924912588156 -50.058758867163199 2202.5076622715987 -853.94005934005122 0.09995967671825047
+12651.775233750654 230.79541650537132 -2449.0730655898192 10.544756880683472 230.22772224668819 5.6938682420119955 72435.343066056535 413608.90387364564 2210.2983858816788 -89.816083912362629 2237.4276265093213 -712.27945938188668 0.10386832425984308 1321.3754484068525 7545.1102692052054 -0.10536891262034055 688.02670072325543 226.80497633862905 -2221.2510594646515 -58.19619696311193 226.80497633862905 0.30964297962342729 -57.686487387989672 2240.8777170238932 -688.02670072325543 0.10207244659704277
+-1999.7368406082458 230.33014989326779 -2374.4914134740366 -18.428614801451417 230.27604941061813 -0.88523100513866571 -1782.572197251292 -1588.7058581220422 2258.1684455157811 -82.462785849608821 2279.6858712002177 -488.65790367297103 0.10196111106386356 205.3170762526826 182.98750665925684 0.049104251449975815 479.11849791386072 228.64036146140282 -2258.0573812449629 -65.841755792732172 228.64036146140282 0.21209318190077942 -65.252302680751569 2278.4554192562446 -479.11849791386072 0.10121308608295831
+-17132.878504450819 224.20993601286213 -2322.7420236395619 -49.883059839594267 223.61725527385636 -7.4620550977573261 -128075.64961933305 -956604.68081437226 2288.0624647175387 -78.496593754052043 2314.052587535467 -238.9486507778212 0.097652411155427749 1676.0658869142476 12518.636271148283 0.078266642901364328 237.19811596434309 224.09367582545676 -2294.8484420438535 -73.355510690619255 224.09367582545676 0.10330928395659542 -72.709201954769085 2315.2472437865513 -237.19811596434309 0.097601775185303469
+-32383.455862776354 212.99476590107764 -2304.6708470534481 -80.883538803205951 210.34122083637104 -13.880606885030586 -452498.02734051977 -6301912.4125500331 2296.2968886858498 -80.09898452549433 2343.4921949317768 25.626834318234025 0.091296513459527495 2976.2021636139998 41449.385906433999 0.19068817923273362 -25.617745060794132 213.70303737949877 -2331.6276391059296 -80.685768920271684 213.70303737949877 -0.010980602254948192 -80.009702894768253 2351.3294274377467 25.617745060794132 0.091600101748606416
+-47215.915028895004 196.01613307442196 -2298.6262583169901 -107.67706778809296 188.89133115748481 -19.922327016411398 -962249.12087666057 -19389159.475002717 2257.5529463039143 -88.083005849287218 2358.9518768293296 294.90157426367796 0.082707229145325731 3994.7621816304172 80493.792432645918 0.35581629258176706 -295.68463671461228 198.34499848037078 -2368.3987253247783 -87.784037847788852 198.34499848037078 -0.1247614500905537 -87.106129979270833 2386.8309083647987 295.68463671461228 0.083689872776527757
+-60940.093340227351 173.23940499041001 -2277.5662789974649 -126.04412288709933 159.58147320397245 -25.317861794859724 -1631021.9719298158 -42457317.319807634 2154.7971225611341 -102.49193566137521 2353.6531874678712 554.67173343468744 0.071973163685255512 4636.6400255722419 120696.90062514538 0.52949844506752308 -558.53924711693912 179.18436734690692 -2405.1657380202237 -94.605071334486226 179.18436734690692 -0.23204787998211013 -93.950905549828732 2421.9125391610478 558.53924711693912 0.074443027564149111
+-72869.83520309368 145.74232704182353 -2211.0194117644519 -132.72835085805292 125.07186485767812 -29.815808184571878 -2420292.3541237032 -76164321.359454989 1983.6009963686704 -122.21554453681475 2324.8051693961429 787.20609605049117 0.059632703372268218 4840.6729455118375 152331.4194625346 0.66402469014056209 -799.67398430296328 157.60101055940157 -2441.9329197089814 -101.10646019953577 157.60101055940157 -0.3271988479144694 -100.49684393834686 2456.7457432589472 799.67398430296328 0.064484865204337799
+-82507.193955276351 115.86815850635072 -2072.6200898272727 -127.03313112659049 89.905959816465611 -33.255620296362899 -3281426.1555520501 -119338557.81012763 1754.2772369271736 -144.61275393181376 2274.5827273783952 973.70490571474147 0.046702200123478727 4608.2382358620425 167591.91858783233 0.72240267144944037 -1005.3357618348298 135.09864792593169 -2478.7046324260159 -107.24794975314984 135.09864792593169 -0.40521393060654165 -106.69744696949738 2491.4934463926033 1005.3357618348298 0.054453304282922887
+-89692.992989089966 86.881861527179794 -1854.1443377532109 -111.62954968494607 59.050350799215082 -35.620727954364561 -4158648.8108562757 -169005410.78663623 1491.2775429039189 -165.6343906474012 2210.132474076529 1098.440852711846 0.034504313553288243 4028.3096603648278 163708.49280383289 0.69332999569344744 -1163.3051168490122 113.20216527085556 -2515.4852673260316 -112.99069097373561 113.20216527085556 -0.4619956778590199 -112.50719917269234 2526.2953889123883 1163.3051168490122 0.044957174452285767
+-94669.328081493193 62.138335170503005 -1579.5276509361524 -91.788709929625654 36.003985418588016 -37.052574591582463 -4992906.6059711194 -220716478.43751693 1229.7413540359944 -180.88104773494533 2142.5683520143989 1153.3091326063859 0.024320287737965952 3277.2061494910158 144872.20721580941 0.59848556691416399 -1263.6118174060603 93.350994188877863 -2552.2791514316723 -118.29661169483812 93.350994188877863 -0.49456431209630541 -117.88186106107476 2561.2589841732747 1263.6118174060603 0.036536592637525581
+-98022.651165816991 44.09252206240344 -1305.2950822118148 -73.3015494440385 21.611569567301938 -37.817380851009638 -5726463.2268571742 -269161064.7727102 1009.6521037309178 -187.35197312562843 2085.1512798140675 1140.366823922747 0.017011003882100091 2575.8761180884399 121073.95633234411 0.48391715609504227 -1299.1455906722388 76.796570909728629 -2589.0904534937422 -123.12804041591878 76.796570909728629 -0.50121357664824029 -122.77875886149438 2596.4559094286874 1299.1455906722388 0.029628306678136044
+-100513.94725165098 33.693690162094306 -1099.562832393314 -60.474854280047992 14.470201506285974 -38.232768068334337 -6307701.5945955832 -308966525.05769366 868.6774750748009 -184.74636109451026 2051.0140122261751 1070.1359156788906 0.012816162100454283 2114.4303748329903 103569.92885464347 0.39668016070692103 -1266.1254380110124 64.511745854296223 -2625.9230909864737 -127.44764354688567 64.511745854296223 -0.48159963408558859 -127.15706910763298 2631.9237493457681 1266.1254380110124 0.024538511165574827
+-102843.39807443629 30.493120555280054 -1011.7286758352002 -55.349144266119708 12.578006505681198 -38.57591825747798 -6695331.1984658511 -335527433.39483267 835.19423812490595 -175.27128355206537 2050.8517549612084 955.96696085352357 0.011437779653143307 1985.1691524678938 99484.057000013825 0.36084393442831825 -1164.3979761517135 57.119259519220392 -2662.780640275796 -131.21866404308437 57.119259519220392 -0.43675843066455872 -130.97809653686687 2667.6713702159359 1164.3979761517135 0.021425078589354985
+-78003.874379456538 24.612086284980105 -781.44779032589838 -42.873154464172345 10.961607490635284 -39.001937189728274 -5076871.80893471 -255787495.36043221 682.9087955103231 -120.03905110629975 1547.1571587289463 597.69575269733116 0.012306043142490052 1601.874368591519 80707.06687984601 0.27302682372579834 -738.1013769586358 40.58087881343873 -1997.5332977948501 -99.449049710622319 40.58087881343873 -0.36905068847931788 -99.301179251964712 2000.5078462055926 738.1013769586358 0.020290439406719363
+-80455.692553226734 30.201715437543001 -905.94708336122699 -49.628779850942095 15.763155151922861 -39.497149019748029 -5050108.216441296 -251452250.88054436 838.84564708618802 -110.51884112943458 1614.8207266207246 467.54810923490925 0.014826566243270986 1895.7257894567813 94390.950922456483 0.291675669895268 -574.51032236981916 42.747535986544385 -2034.4593802885238 -101.83048750865282 42.747535986544385 -0.28203746802642082 -101.70560434920034 2036.957469915088 574.51032236981916 0.020985535584950605
+-82711.637514587026 38.364559879472168 -1101.0250572344776 -59.217743830453173 24.019165355156588 -39.8802495248732 -4849252.7205338608 -234481117.11861387 1068.5126425797489 -102.15433342658089 1709.5968145279357 311.50001664650239 0.018497859151143766 2249.2535749253511 108760.57020048838 0.2977955025326337 -373.92740107376341 48.167454583934251 -2071.4096882448948 -103.7313199062595 48.167454583934251 -0.18029286454858409 -103.62385557963034 2073.5578677936601 373.92740107376341 0.023224423618097519
+-84123.742005319655 48.390041723905796 -1350.8824999884489 -69.52909434865353 35.734644189343278 -39.850185696503864 -4485950.5531708691 -206793861.3335782 1346.9067184713656 -96.171965306014201 1824.0588108886086 126.37001858611433 0.022922805174754049 2580.428892780229 118952.90603628462 0.27494482507356938 -146.00451816034035 56.170086132840872 -2108.385643744391 -105.12308840871336 56.170086132840872 -0.069163675111482872 -105.02845925057811 2110.2852694259968 146.00451816034035 0.026608283340995201
+-83916.069002380827 59.675792671922338 -1643.9369596279253 -78.627704385986632 50.284728015599626 -39.067071230158675 -3983377.740967948 -171537925.93019018 1642.3290228502588 -93.632276531280937 1947.9735113225438 -88.742322093445978 0.027782026383576512 2832.7259252004451 121986.90697595915 0.21784480821989186 97.758418506803267 65.850953279835082 -2145.3881609437785 -105.97973597950076 65.850953279835082 0.045511368019927036 -105.89447049904071 2147.1156123532251 -97.758418506803267 0.030656868379811488
+-81377.351269137172 71.669801821319894 -1969.4177689590224 -85.066757099692026 66.498127997556026 -37.243639024776733 -3375134.8214595541 -132651159.96092509 1921.7334849198826 -95.550026749047419 2070.011416375009 -329.40481172193154 0.03280082463218302 2972.5131133136588 116827.12938548691 0.13105600512731541 344.59219735017706 76.136056395424674 -2182.4176106627351 -106.27789861081938 76.136056395424674 0.15770809947376524 -106.19968299933301 2184.0249518818009 -344.59219735017706 0.034844877068844246
+-76040.70827849998 83.76943447703745 -2311.0804953268903 -87.9807006225424 82.856754561253183 -34.221740899414925 -2703370.2575116558 -94294508.583589405 2156.4745428522524 -102.71902310833056 2179.7114507098677 -583.92568387253323 0.037700015516218474 2978.1389834558472 103878.53870859892 0.025966631923676074 581.12659424536355 85.858922666964219 -2219.4737960438306 -105.99714262557518 85.858922666964219 0.26153312072248586 -105.92482558299952 2220.9890761504594 -581.12659424536355 0.038640379238057701
+-67798.194712833734 95.147985854996335 -2641.5683389830629 -87.120027931551917 97.608393472645957 -30.012481059244678 -2016135.0832656103 -60231203.19080513 2327.4014987503897 -115.2087873886115 2269.3376297509558 -833.07329830699223 0.042119515650728787 2829.4439578640554 84528.470020505149 -0.081358270467567245 794.11339223523282 93.844132405216584 -2256.5559400967609 -105.12019063636654 93.844132405216584 0.35153315282657494 -105.05374440744271 2257.9832060492581 -794.11339223523282 0.041542333955385827
+-56915.646428877553 104.56529945050416 -2922.0029605869977 -83.006115980323898 108.81629487013565 -24.789044611880467 -1364058.6990356483 -33247884.251827203 2428.2856542410486 -131.6856461209083 2335.2598588507385 -1052.0628940025169 0.045542377809453027 2506.04561807031 61082.939244701032 -0.17124031100787981 971.16517496493566 98.990620347215128 -2293.6626856915868 -103.63317610853706 98.990620347215128 0.42298134798124376 -103.57356934110254 2294.9826925152283 -971.16517496493566 0.043114381684327144
+-43943.172382420635 110.33304866457691 -3111.4162533115355 -77.108252007933899 114.5607366128697 -18.835478946601214 -796564.19933877944 -14718870.711663073 2466.8216133558635 -149.02494121725022 2378.5676699614905 -1215.2303887725632 0.04729234833458075 2000.0230253122197 36956.318592219031 -0.22264016847049628 1101.4689282850338 100.3481002992816 -2330.7921083021988 -101.52594517639285 100.3481002992816 0.47212555863053318 -101.47486327258996 2331.96541659177 -1101.4689282850338 0.043012473338740503
+-29550.44435108787 110.60954677106375 -3185.399296984353 -71.630954131749505 113.42580343341426 -12.468541920290241 -358022.0288019625 -4400398.1359969918 2462.9102154366478 -162.85403610455529 2404.7851703475085 -1302.6396057286429 0.046670694840111285 1340.1035148354358 16471.022826326556 -0.21946164737282425 1176.4337240112429 97.180594284406013 -2367.9417415256471 -98.792390643529288 97.180594284406013 0.49638553755748643 -98.751753100828168 2368.9161782380443 -1176.4337240112429 0.041004470162196632
+-14342.171641356439 103.98021749472829 -3153.0307817096877 -68.685847890556474 105.01601560076419 -5.9585258169324629 -84376.252382668463 -499571.90863109659 2444.5320967705416 -169.17155859363055 2422.7552391909494 -1305.8858055284211 0.043199093267440085 611.72472994544273 3621.8779842189292 -0.16034796058102307 1190.2342980069393 89.012206272611081 -2405.1086151211839 -95.430767061481731 89.012206272611081 0.49448869879806373 -95.402041224810418 2405.832800435961 -1190.2342980069393 0.036980559315584166
+1284.852607915062 90.04627483395457 -3062.4161609088837 -69.301724971927314 90.048102740133857 0.52571710634822499 676.50540719820538 356.35944564795972 2446.2950204134117 -166.43480150373645 2445.0308354875924 -1230.5612172090282 0.036843811306855385 47.411501870300903 24.974725026680403 0.082323571120992714 1140.2170921880308 75.651866626215991 -2442.2893050308585 -91.443909265560222 75.651866626215991 0.46653727176269677 -91.427296426647672 2442.7330823313814 -1140.2170921880308 0.030954118914163661
+17418.12982734574 70.009557857359397 -3033.9497973514149 -75.014853650809414 71.035495418225437 7.0206085559636184 119585.19736508153 830241.63540480484 2548.1154731975207 -156.66340908754179 2508.2046621523396 -1101.1379819694712 0.028218282086803468 480.6547474844607 3337.0316093411925 0.15774619432085443 1027.1421003155053 57.19565409039258 -2479.4799945674995 -86.839270206235312 57.19565409039258 0.41400326493974421 -86.832923131457122 2479.6612327933826 -1027.1421003155053 0.023053467992903094
+34326.425491232672 43.560231038539598 -3047.3788183850102 -82.760306405240655 45.529604560176317 13.632416795565 440621.68788873177 5828692.1306578415 2715.4504328013913 -140.38131068281862 2593.7825052922453 -915.59435135624881 0.017299535757958538 559.1488845795111 7396.6098197093597 0.15295530058775825 855.24231509455024 34.007320063869564 -2516.6765456921466 -81.628713346788928 34.007320063869564 0.3396514357007745 -81.628208133236754 2516.6921219142196 -855.24231509455024 0.013505687078582036
+51996.55961218437 11.61130749364643 -3113.2170788371682 -89.350092430349989 12.616654717036475 20.350903957802103 949952.07415712322 18317116.01801531 2933.1933003362979 -119.68919098315681 2695.3903686583071 -680.0636795692044 0.0045445430503508535 212.1329895580412 4090.3795956556614 0.052829720168347701 632.09228968040361 6.6796205183767743 -2553.8745790574681 -75.828039659112207 6.6796205183767743 0.2473942425363615 -75.826343997588921 2553.931689906161 -632.09228968040361 0.002614332883904804
+70093.302766640481 -24.498628916095768 -3231.8290273971843 -90.835830389089665 -27.713789943831426 27.042169277253276 1620795.0650189607 40529819.833847076 3173.0252237684176 -96.558076551744691 2802.3497950474293 -400.44127964810275 -0.0094516315262715162 -566.49145181723009 -14165.761591394159 -0.12931366565662125 368.28778788494282 -24.01917834505003 -2591.0695612722775 -69.456291482098678 -24.01917834505003 0.14208633791857361 -69.444428489896893 2591.5121862411629 -368.28778788494282 -0.0092666583121334984
+87915.663386189961 -63.067699140210976 -3396.7120908618213 -83.37668402937112 -73.868298259851713 33.440723996268531 2414654.0078333947 73179110.793303221 3398.1817234338846 -72.664245007327892 2901.5129225064325 -84.84345041467904 -0.023989235123701397 -1732.1904496675024 -52496.198801193234 -0.35672627550162878 76.94913520588014 -57.207892423841763 -2628.2568976503885 -62.534941535313891 -57.207892423841763 0.029269355346473996 -62.503439530609249 2629.5815505299183 -76.94913520588014 -0.021760324238813907
+104475.48294667031 -102.19164226477552 -3594.0021552206726 -64.35508511899674 -122.59958600633229 39.188103130784064 3283504.1085927188 115232869.68621983 3570.0484186352992 -49.532691332544658 2979.6830948305496 253.92419414921363 -0.038331448711468694 -3211.7265005753366 -112713.87184808389 -0.58010481187277685 -226.92889842687717 -91.957575591083085 -2665.4320285494964 -55.08712881088038 -91.957575591083085 -0.085119616814282512 -55.028185346382273 2668.2871072179632 226.92889842687717 -0.034492714025162449
+87816.121315044409 -103.61064083136213 -2810.8757721849443 -24.69536873646549 -125.49501108824579 43.908060657522199 3087596.6107981992 121315414.55756415 2704.7183000831815 -21.261611232055362 2239.1212941085896 441.72043579600341 -0.051805320415681068 -3642.9286409252682 -143135.08336285627 -0.55419167844867268 -390.12108291058911 -94.230536164179696 -1999.6970235197125 -34.877638713941622 -94.230536164179696 -0.19506054145529456 -34.81112072890555 2003.5180960362525 390.12108291058911 -0.047115268082089851
+96342.832272912288 -130.16215833435541 -2952.9349652937558 5.3264755072603638 -156.46605287192824 47.296432141832248 3736259.2129800003 160014810.70613202 2700.9541393910581 -8.8725410787454191 2256.3047496028212 683.76747530417435 -0.063898948617749349 -5047.8022265370982 -216184.9785355482 -0.60985420740332352 -600.44129576664579 -120.83823380487195 -2036.7979337200281 -28.778373512441537 -120.83823380487195 -0.29476745005726351 -28.691064703565715 2042.9960446424061 600.44129576664579 -0.059321666079956772
+102037.14566615756 -154.11864638204722 -3032.877423549653 38.91272691708491 -180.36628071925693 49.19823802611262 4321519.9375445116 197265279.94292948 2612.7435362313545 -0.47014139610629457 2245.1774703392457 892.03518841584628 -0.074309858429145226 -6527.2974732797629 -297952.84574550134 -0.57002838535758549 -786.03197376790661 -146.95934206857348 -2073.8813765526329 -22.280866485848247 -146.95934206857348 -0.37899323711085181 -22.181884233665638 2083.1356602396427 786.03197376790661 -0.070857927709051818
+104806.44676978358 -175.06062834506358 -3009.8377613803777 70.724367673940719 -195.35804817994693 49.647772036846789 4804674.945818386 229219839.0771251 2449.046447373486 3.3215146377604841 2210.125739850153 1046.2665897315167 -0.082927820153985587 -8025.3595168274378 -382871.19060661522 -0.42124949218494578 -935.81922208897947 -172.09997843761346 -2110.944474090456 -15.404908259591428 -172.09997843761346 -0.44330612131169089 -15.311019135539407 2123.8890551036661 935.81922208897947 -0.08152533322482873
+104948.6460286009 -192.8545557707792 -2860.2302274444683 95.14832618094286 -200.9267761913186 48.858773756331885 5153173.0181267019 252403466.02228123 2231.4065420340303 2.9263834747141351 2159.6298617309003 1131.8154212429961 -0.089783312742448426 -9469.5160998070805 -463818.83098774211 -0.16304769208237052 -1040.3261200336592 -195.8472245754985 -2147.9846938868336 -8.1736685333257988 -195.8472245754985 -0.48432314712926405 -8.1089350648214378 2165.1320132842702 1040.3261200336592 -0.091176547753956477
+103061.48311710363 -207.61586522170586 -2597.2456018146004 108.30594752946931 -198.19392680740521 47.167726827049719 5342664.0677534342 264187047.68953276 1990.6733745871757 -0.012815133477871665 2104.8795619677944 1143.9273564310117 -0.095018702618629686 -10762.719392997618 -532200.98166167224 0.18844886613839168 -1092.2560837526946 -217.85780117435374 -2184.9999151336174 -0.61502497875855677 -217.85780117435374 -0.49988836785020346 -0.60898775468744759 2206.6609977768744 1092.2560837526946 -0.099706087493983409
+99873.948139441723 -219.76350572247154 -2274.6082461182932 109.84905234967295 -190.08486937987135 44.947771439892769 5358386.2823862126 263135550.06193233 1762.0996387005316 -3.1070475025385198 2057.9482988545128 1088.8668063662419 -0.098903467921904381 -11790.639865245823 -579005.75714783592 0.59784688808978936 -1086.971918166096 -237.83591345806585 -2221.9884874587942 7.2371099707588122 -237.83591345806585 -0.48918628180292345 7.1527337835861484 2248.1998525376343 1086.971918166096 -0.10703686474260389
+96048.157441890638 -230.05907873348858 -1966.0759661908107 102.99598375638162 -180.97248542007452 42.517997982244637 5196159.8853357276 249210459.61951318 1579.6775896693268 -4.1077174394428715 2029.8574274519394 980.3964089377879 -0.10184111497719726 -12446.08733796358 -596920.65186675906 1.0131396940004136 -1022.8402514340813 -255.50401277289882 -2258.9492808700234 15.341755217884405 -255.50401277289882 -0.45278452918728701 15.137584245990766 2289.4172777867593 1022.8402514340813 -0.11310491933284587
+92004.890961623518 -239.46229906722627 -1734.2855498815225 92.352642825388926 -175.57967383157728 40.071816620916159 4863181.309093927 223818244.71204141 1470.58686184251 -1.549607007597775 2028.7872080407269 833.27803480567945 -0.10429542642300796 -12657.463803116119 -582534.59019142936 1.3756019172324134 -901.4178916345777 -270.57107856425375 -2295.8817255179779 23.650419929504316 -270.57107856425375 -0.39260361133910177 23.304558193433184 2329.954786796839 901.4178916345777 -0.11784454641300252
+87818.435975539935 -248.78560629049269 -1612.0201227401485 81.158630662548433 -177.49943777359471 37.641849968083982 4378433.654778149 189680052.85649982 1450.6122543108258 5.2322422551156293 2058.6664833155528 657.71575571486289 -0.10663763664401743 -12403.901974638671 -537354.90078940545 1.6332873903048255 -727.4650100707828 -282.70329363029555 -2332.7858401540161 32.107502155602369 -282.70329363029555 -0.31181526363942685 31.610504218066751 2369.4631972524899 727.4650100707828 -0.1211758652508768
+83210.304492910524 -258.32340335687934 -1604.0281320189004 70.058247137739869 -188.04586923616756 35.10983311937153 3772285.4386216495 150499027.70235848 1521.3787760615414 16.474914928478139 2118.4147025594848 457.6220475821533 -0.1089972166062782 -11710.924733142481 -467218.829144975 1.7518883831767227 -508.77948665763807 -291.50166387273907 -2369.6622483863043 40.651655574208164 -291.50166387273907 -0.21467488888507935 40.01035581901624 2407.6440105743682 508.77948665763807 -0.12299648264672534
+77646.154614115294 -267.6464753986005 -1699.5440746440347 57.881789890603336 -205.90807894442304 32.258477197388991 3084995.7951944652 110444758.41277225 1670.0639772337404 32.297601067865756 2202.0752106587006 232.69216811511805 -0.11119504586564209 -10633.987675330409 -380703.33891402633 1.7193593152493898 -255.8567304082724 -296.49031638303347 -2406.512182092411 49.218082032179268 -296.49031638303347 -0.10629693826683523 48.45737756857632 2444.290630916676 255.8567304082724 -0.12317836160491627
+70509.198345233061 -275.61358471265635 -1882.9619129485354 43.403665646445411 -227.57478998611026 28.849917489866225 2364179.1277541541 73531195.403923601 1871.7969539107442 52.719760370306801 2299.8961186563747 -17.597448457185088 -0.11277151583987574 -9241.3458044544113 -287426.27670014248 1.5449392944764846 18.610744535742352 -297.11805788849654 -2443.3374716190638 57.741514939595689 -297.11805788849654 0.0076148709229715023 56.903425047718748 2479.3236435531935 -18.610744535742352 -0.12157040011804277
+61297.633198151809 -280.52796092053342 -2137.3080696834049 26.879641334722351 -248.19490863337998 24.706825150403791 1661522.8493535391 42998011.24845095 2094.3730510644541 77.399419472930774 2400.1764981224474 -289.99160738866135 -0.11307052032266562 -7603.9415004048888 -196779.93732928977 1.2553580671032816 300.37031221547397 -292.77447113782523 -2480.1405226854158 66.159487268253073 -292.77447113782523 0.12106824353707134 65.299216945637824 2512.8145942498259 -300.37031221547397 -0.11800663891085257
+49784.597777177121 -280.34669601019004 -2441.3720092534627 10.739837202674376 -262.54725146443525 19.771484423025068 1029132.9183140844 20805721.695196252 2304.4045229566327 105.20147836126543 2491.5180651847604 -574.63502718530401 -0.1113370516323233 -5795.2464474252038 -117161.04168325232 0.89072389361138016 574.35083932454256 -282.82052242597786 -2516.924280198476 74.415372487418281 -282.82052242597786 0.22809802991443309 73.594617652268312 2544.9939657078157 -574.35083932454256 -0.11231950850912545
+36091.090335442801 -272.91381325894702 -2764.1246401648177 -0.73026694609272924 -265.96300100593061 14.125671364165481 515872.17934353912 7330336.5912590139 2473.7234586677055 133.77665211649341 2565.0508257546585 -854.06258928195678 -0.10681558248882468 -3900.9251953961875 -55430.581148928984 0.5010329076944614 825.47025884638379 -266.6324385883176 -2553.6921794819482 82.460679000035725 -266.6324385883176 0.32308033614339876 81.738928624849052 2576.2411450686104 -825.47025884638379 -0.10435711882125934
+20651.027829068811 -256.2630489419613 -3061.7565861533599 -2.711892443787038 -255.19214868928788 7.967217526646917 164002.79686485755 1304596.1751585049 2584.8097638545955 159.4734317269008 2616.2231223494241 -1104.5223426745004 -0.098866917030077661 -2035.1460037470026 -16189.014718848231 0.14224265867642408 1039.4657974502209 -243.65650803045747 -2590.4480857060153 90.256176030955672 -243.65650803045747 0.40102847123851115 89.681186778724808 2607.0566951729943 -1039.4657974502209 -0.09400328241915798
+4082.8959848921268 -229.04003490493645 -3283.7289299425647 8.0795424170377643 -229.17807577817408 1.5530224362465297 6336.788840032983 9845.7702303495589 2634.2317721254176 178.00625491651712 2645.8338615786129 -1300.044185660694 -0.087120591443490478 -355.47766645951248 -552.32255868620371 -0.13055363116768529 1203.7011893985207 -213.47144778746991 -2627.1962225792581 97.77161162296423 -213.47144778746991 0.45785515001845595 97.370468137299071 2638.019654677435 -1203.7011893985207 -0.081198724909650022
+-13071.308381918281 -192.18045654019554 -3434.5428337572994 32.125320480101294 -192.02999065339344 -4.9029663848155591 -63936.797706469886 -313126.42204354296 2667.4145174703699 186.88993664663988 2677.0226416399901 -1427.8650523567503 -0.072085692625729766 -940.02854296857106 -4603.730321776311 -0.027700773614035723 1307.9053697938505 -175.85403992105171 -2663.9410916186666 104.98401119902479 -175.85403992105171 0.49058716046280959 104.75619497454991 2669.7344387506764 -1307.9053697938505 -0.065961755409246703
+-22469.114931484979 -106.7558946699472 -2544.5899532476278 47.902400492513159 -105.2425837442006 -11.234557465742489 -254745.92746820618 -2875069.3698467887 1963.9761993114148 136.4267688706837 1992.5146664187666 -1081.9309995553176 -0.053377947334973593 -1210.3560591203259 -13660.110945717179 -0.12792859350186173 995.04244539242109 -96.813097814511778 -1998.2888520487288 82.778225156342643 -96.813097814511778 0.49752122269621052 82.714350494785549 1999.83199448235 -995.04244539242109 -0.048406548907255889
+-35097.569993115889 -64.974871938978566 -2455.924647734028 73.557123879896963 -62.707344911560348 -17.230029451701469 -624453.05195492075 -10933382.610586943 1936.6336962937446 127.85676178744643 2003.3484109582068 -1039.3955856190144 -0.031897335267048875 -1156.0275281346692 -20240.57890965329 -0.12578353007766543 974.3233383383315 -58.578323239527379 -2035.0967309396358 88.041480705033152 -58.578323239527379 0.47831288087301499 88.035763862239065 2035.22888550468 -974.3233383383315 -0.028757154265845547
+-47270.020470001466 -18.683817303985521 -2315.2861428985716 95.768385025892087 -17.672451128396965 -22.79171671649058 -1141223.3347040769 -26770199.373178933 1908.1708402737481 115.86561258866116 2012.8913826067117 -933.82612614769698 -0.0090085907926641864 -451.07677290276899 -10581.114823023207 -0.042279669851482828 900.1031682182537 -15.255153128540949 -2071.9113410798432 93.069547905382166 -15.255153128540949 0.43399381302712331 93.055868728020528 2072.215911260098 -900.1031682182537 -0.0073554258093254336
+-59018.452679818394 30.073092056644295 -2187.9754917386226 112.33773372319536 28.209658218756466 -27.957580615735857 -1778592.8070566077 -51626265.313827813 1908.9219975856788 104.88006119026512 2032.066524237197 -781.48656074249607 0.014245898653076409 906.28918226772021 26306.37298721305 0.063358797502722899 774.56121050312481 32.379947527771819 -2108.7363575536419 97.836228123763888 32.379947527771819 0.36691672690815957 97.73420246464454 2110.937687398904 -774.56121050312481 0.015338677180375092
+-70604.436971834264 80.520294273323699 -2128.925402052897 123.39512549524636 76.510129996785395 -32.869849614447979 -2500058.8709964543 -85292011.330957636 1967.3360180363493 98.335439875263404 2071.4451877671463 -596.94257469573779 0.037486170518307126 2851.1731645643363 97270.626975923151 0.11655955582737126 602.79501879611894 83.246909237251884 -2145.5753515463525 102.30775908620346 83.246909237251884 0.28063082811737383 102.03142083077223 2151.3863511846494 -602.79501879611894 0.038755544337640543
+-82380.102814357102 133.20238622615736 -2173.6597002175804 129.28884413712871 131.42225463300832 -37.702564217097077 -3261548.4500676533 -126011538.70294885 2103.8796912677585 98.351938925762795 2139.2244304398973 -388.00315880513426 0.060962190492520538 5273.6767920799639 203751.1131187529 0.045859096349653725 392.63247258360985 135.98045853380739 -2182.4317087927047 106.44151132801497 135.98045853380739 0.17969449546160635 105.90956734948173 2193.3932435728575 -392.63247258360985 0.062233619466273406
+-94592.992745583106 189.37182164961118 -2341.5529288247112 128.60326892615379 197.95923410470118 -42.571103845896992 -4014546.0034072758 -170640873.76707619 2326.1769561351584 106.15937315684609 2239.4648817286375 -155.25247228199001 0.08522584232655768 8036.9789314748205 341616.98636669316 -0.20169932044110336 154.28475501846501 188.97237367168256 -2219.3085499387671 110.18594105363637 188.97237367168256 0.069435083266635916 109.33233816528242 2236.6356118142066 -154.28475501846501 0.085046072759533103
+-107198.13189376429 250.0266902622694 -2641.8160096472161 117.66690957803974 279.66804291625289 -47.453798979089989 -4709514.4096615268 -215033770.56922084 2626.3907290891843 122.52378808280966 2370.8931132247408 104.65032485644633 0.11068025244013698 10984.373326179506 501540.28806509398 -0.64982006814958948 -100.14481459739751 240.4372857474786 -2256.2086546322357 113.4819535263985 240.4372857474786 -0.044331480565470344 112.26534087864374 2280.6590501302244 100.14481459739751 0.10643527478861381
+-119741.45152135978 314.90179842037787 -3074.6122485540664 91.887692858898561 376.47912550814897 -52.152200139965061 -5299325.2507954482 -254592539.07720467 2981.4050219965116 147.69299721069967 2526.5222192585015 393.59680489831669 0.13715235122838756 13936.419098713928 669539.64062746358 -1.2859607968853257 -357.27265982169217 288.49932628397124 -2293.1343911111339 116.26564505791588 288.49932628397124 -0.15560655915578928 114.67634587555159 2324.914934736054 357.27265982169217 0.1256530166742035
+-131358.73439683038 381.64995726971671 -3624.2693295717481 48.561891907346038 483.17794976059668 -56.304643976352502 -5742492.9401203543 -284901182.6384787 3355.8461894168181 180.72696262136185 2694.2165921713563 706.15544100696468 0.16358763706374485 16684.251681336737 827752.52577868022 -2.0574808382637388 -603.13671954138715 331.29447064235012 -2330.0876529764769 118.47217685093136 331.29447064235012 -0.25852409753167044 116.53552868792475 2368.8102642143399 603.13671954138715 0.14200363079397776
+-140900.77989438578 445.4597229421671 -4246.1063838570553 -9.7716639883362983 589.00487449389925 -59.45180586261003 -6006072.7954721078 -302351967.97131234 3707.4807327330541 218.41377838508782 2858.189705697976 1024.8002159612695 0.18795768900513379 18988.280444202825 955889.83953442518 -2.8723684990534184 -823.95146411859082 367.08123194524813 -2367.0698047114392 120.04032221815156 367.08123194524813 -0.3476588456196586 117.81570193887522 2411.7653028781924 823.95146411859082 0.15488659575748867
+-147158.83581378291 499.42703357757421 -4854.8406439114651 -73.177458415820809 678.98220834614074 -61.137862822510563 -6068155.2915989244 -304683008.3056311 3994.120122175641 254.46870735152919 3001.3322656711989 1318.4069275061781 0.20748941984942842 20594.079722173665 1034031.9028626127 -3.6084368038305796 -1006.8755716555343 394.35076786068038 -2404.0816373500506 120.9170658551829 394.35076786068038 -0.41831141323453852 118.49253544548017 2453.2726603562642 1006.8755716555343 0.1638349679520899
+-149134.46993028419 535.96113700867386 -5329.9890032514413 -125.72736269558618 737.37684526826945 -61.020650544306136 -5919934.6305721365 -291357477.34578872 4180.8990168563751 280.22511934911643 3108.1820131230161 1546.5529494689476 0.21929670090371273 21275.127722663168 1047083.7822216832 -4.1353878459197873 -1140.7498930426016 411.92678114998006 -2441.1233355052318 121.06156836221872 411.92678114998006 -0.46675527538567985 118.54476307206338 2492.9504425448431 1140.7498930426016 0.16854614613337973
+-146281.33880149154 549.09764017677026 -5551.8061444999712 -151.64876207968649 752.76290808689396 -58.960636356909127 -5567253.0479158172 -263723961.15261441 4246.664006638799 287.66969771202207 3168.1163194330679 1670.5728678893997 0.22132109640337372 20897.850237930259 989943.11860573618 -4.3491261004688875 -1216.7638336687235 419.0460217461914 -2478.1944567426608 120.44787457600401 419.0460217461914 -0.49043282292169427 117.9543749504402 2530.5823139337012 1216.7638336687235 0.16890206438782401
+-138646.2299657921 536.80890355800955 -5458.0515869971232 -143.52013284222721 722.58847157100877 -55.062045260441664 -5031286.1567448014 -224901469.33554047 4188.199146540629 273.67457242370557 3177.9323089355171 1667.7756854748013 0.21318860347816104 19480.07678214648 870770.96284827951 -4.2067433228268909 -1229.0110946175773 415.41175632605558 -2515.2939240378014 119.06594227055103 415.41175632605558 -0.4880901884899036 116.7067937119288 2566.138882130404 1229.0110946175773 0.16497686907309594
+-126858.2046540118 501.92035111621527 -5081.7052397322886 -106.39472975282827 654.44229983432456 -49.65096072564063 -4347854.4682349488 -179357171.24082589 4021.2204603644795 242.17101078119771 3142.7748005432122 1540.7303478579499 0.19644632137620952 17202.487196240469 709634.93933980679 -3.7428378839041594 -1174.9007603343789 401.21508348662041 -2552.4200317832497 116.92086158845045 401.21508348662041 -0.45984374181384691 114.79103341113186 2599.7775295119636 1174.9007603343789 0.15703134383037981
+-111960.66304024243 450.91490900579953 -4535.3241410263763 -54.180026015603872 562.91377585798034 -43.194700247007106 -3565071.1692344151 -132216777.68672456 3777.6701204267802 202.08841953709603 3074.9189637378754 1315.0517701928252 0.17396408526458315 14358.112020975888 532495.20555465564 -3.0558485476601254 -1055.3967898212229 377.12208880144692 -2589.5704655278514 114.03045801391823 377.12208880144692 -0.40717468743102736 112.19983986560219 2631.8211023934382 1055.3967898212229 0.14549463302524959
+-95138.273174034868 391.37910674216459 -3955.1519404418173 -1.56911003488116 463.96782936333216 -36.188008054026199 -2739622.4766124561 -88438756.045980766 3499.7617845154282 162.80202166191097 2990.9364912300584 1027.2231014611943 0.14886995311607629 11270.23816951066 363818.68398375303 -2.2731963358749772 -875.06774538221828 344.22810906754592 -2626.7423353336339 110.4217465450586 344.22810906754592 -0.33285193814462466 108.92981119054951 2662.7189859349519 875.06774538221828 0.13093499774345604
+-77429.281965710266 329.62081878581745 -3450.0673123639394 42.0323194129464 370.0513945623768 -29.043241547528229 -1932388.4582222481 -52025174.715437613 3232.0410726552304 130.72415605694999 2908.3039545685915 710.63642298976401 0.12363871672386251 8226.2866145853968 221474.1019395226 -1.5156538545420983 -641.93864870355139 303.9833696772908 -2663.932222337643 106.12687164415961 303.9833696772908 -0.24078719006134711 104.98149737561734 2692.9963859939212 641.93864870355139 0.11402226919628311
+-44035.803112360532 199.37131914697 -2280.9512670018416 54.454036384652071 212.94244667599571 -22.017901556180266 -890880.98505958891 -18802550.418253027 2229.4311637910923 80.396751384278872 2103.0450518819284 286.42676406669551 0.099685659573485005 4033.4478910508792 85128.214209003228 -0.64722901009000289 -271.65939088268397 190.97049895750982 -1998.6209676727792 74.864372741713154 190.97049895750982 -0.13582969544134199 74.257845233508235 2014.9454192045573 271.65939088268397 0.095485249478754905
+-30958.143403170798 157.72485218599309 -2138.5535010186795 69.905304023678539 162.19951069540656 -15.197910359926754 -454092.6655726952 -6779911.6514513968 2132.4034039549197 72.05826158111222 2084.0899712369137 48.891553032951691 0.077429971618062393 2313.501091569377 34542.141275491922 -0.30017375692685672 -47.892776594533515 154.94954519808539 -2035.7734090836009 71.079508385049436 154.94954519808539 -0.023511426899623718 70.679748642268038 2047.2876019041705 47.892776594533515 0.076067523415849478
+-17705.126105102019 117.44074143153451 -2113.8986978941316 78.419787945582399 118.19213397704813 -8.5367049687087846 -149975.21410185914 -1275350.7352118047 2095.2657294338223 70.748240395111665 2087.9718693802533 -188.67279025240813 0.056625236948666592 994.80795764566381 8459.5915916897084 -0.087430484111452239 187.16610637465718 117.1499302731922 -2072.9313959480924 66.803472992832511 117.1499302731922 0.090244024288648583 66.568969442920803 2080.2337438008722 -187.16610637465718 0.056485019418125455
+-4082.3515924594344 77.65205884342366 -2195.2567963889715 80.138340197591702 77.64023622685184 -1.9338472726003952 -7899.2898126439241 -15281.895698897479 2110.2791396086704 75.604637597048864 2112.9779708265883 -426.69292704197295 0.036784490214790938 150.25558271014555 290.68311172456697 0.0055338684971525222 421.00415995655942 78.926617155967762 -2110.091288444868 62.049174848449042 78.926617155967762 0.19943351963835121 61.933467762400198 2114.0334625730738 -421.00415995655942 0.037388260140202634
+9999.5616493249727 37.636247837114283 -2338.2358327417137 73.418376039329416 37.56584087514802 4.6552894084380689 46739.190673254161 218024.94293180181 2139.3824868755855 84.961474619951488 2143.8919534952711 -658.7016328240137 0.017521530650425644 175.91648769958655 820.60004974397236 -0.016158006327833846 640.72900652479655 41.524400696744067 -2147.2492848417246 56.826802291384148 41.524400696744067 0.29829097138025912 56.784758049162562 2148.8391387416714 -640.72900652479655 0.019331657680048447
+24187.324994957609 -1.6871653169507044 -2477.4572950662532 57.410962344721227 -1.667845296927708 11.069713956502339 273034.28660306754 3052109.151896385 2138.040697560959 95.899214861117173 2163.1323314377742 -867.22814211800892 -0.00077215803979437266 -19.045263533322 -212.89715608075386 0.0017925950376717114 833.77797076792774 6.0120541828884191 -2184.4015002109281 51.144888584556803 6.0120541828884191 0.38159174863520723 51.137910362541362 2184.6995811949037 -833.77797076792774 0.0027515122118482466
+37882.402759527278 -37.923323014498273 -2556.3849299338385 34.5443610339751 -36.584863433412202 17.048786120399317 679885.05355052394 11892723.510968788 2087.0944676669642 104.87195727796554 2165.523100964705 -1031.2341243968394 -0.017067202076731895 -680.61945970524584 -11905.569931547165 0.078658540974413715 988.63176787764792 -26.767026103210402 -2221.5440491460668 45.011942049929196 -26.767026103210402 0.44492878842378392 45.011528564334164 2221.5644567281315 -988.63176787764792 -0.012046366383082988
+50434.980129253483 -68.429234443965754 -2518.1184926091555 10.006712724050384 -63.057693380137266 22.326241757084322 1246849.5881889744 29292954.949282877 1974.3812990399956 107.88210997498113 2148.0167647889775 -1129.7484728061613 -0.0302918257830747 -1691.7021195980863 -39744.131486597944 0.23387778311949015 1095.492519869227 -56.224762382352765 -2258.6731307291434 38.438340566442022 -56.224762382352765 0.48494578126127796 38.427705138480327 2259.2982514664945 -1095.492519869227 -0.024889226375543499
+61312.074570521625 -91.307469350126183 -2333.592782542758 -10.08441456609205 -78.205573993860128 26.703865231063428 1941085.3505299219 56439251.680180885 1800.3161343634397 102.15645323564566 2111.7476744598166 -1149.6175022187388 -0.039768061563643813 -2890.7126759449643 -84050.740070861226 0.45921736130070345 1146.8865652661207 -82.037530666765164 -2295.7851139071504 31.438176026269247 -82.037530666765164 0.49951505455841488 31.411952539312107 2297.7016929837168 -1146.8865652661207 -0.035730631823503993
+70248.145918297319 -106.26309512079989 -2022.3011365735213 -21.717361266057186 -81.924341472037241 30.110649772094863 2719758.4184633037 92862072.831391722 1578.7634300004129 87.663088830359044 2062.2288261699973 -1091.3196545105141 -0.045547833313673333 -4114.1291880201034 -140470.77185316273 0.72408636322556608 1138.1556621794343 -104.13468354041561 -2332.8766213877025 24.030766592265653 -104.13468354041561 0.48785069103276224 23.993111150887287 2336.5379014237583 -1138.1556621794343 -0.044635526592548484
diff --git a/tests/golden/stitch.ppm b/tests/golden/stitch.ppm
new file mode 100644
index 0000000..0f0a43e
--- /dev/null
+++ b/tests/golden/stitch.ppm
Binary files differ
diff --git a/tests/golden/stitch_cyl_bic.txt b/tests/golden/stitch_cyl_bic.txt
new file mode 100644
index 0000000..05cbec1
--- /dev/null
+++ b/tests/golden/stitch_cyl_bic.txt
@@ -0,0 +1,3072 @@
+0 0 1 8775 13072 10450
+1 0 1 9656 13072 11019
+2 0 1 10409 13072 11526
+3 0 1 11000 13072 11832
+4 0 1 11841 13072 12423
+5 0 1 12675 13072 12813
+6 0 1 13282 13072 13242
+7 0 1 13990 13072 13590
+8 0 1 14866 13072 14039
+9 0 1 15459 13072 14655
+10 0 1 16323 13072 14980
+11 0 1 17185 13072 15539
+12 0 1 17777 13072 15849
+13 0 1 18652 13072 16425
+14 0 1 19359 13072 16891
+15 0 1 19966 13072 17212
+16 0 1 20835 13072 17809
+17 0 1 21442 13072 18170
+18 0 1 22149 13072 18635
+19 0 1 23025 13072 18966
+20 0 1 23618 13072 19457
+21 0 1 24481 13072 20074
+22 0 1 25369 13072 20397
+23 0 1 26176 13072 20932
+24 0 1 26761 13072 21239
+25 0 1 27633 13072 21822
+26 0 1 28299 13072 22247
+27 0 1 28931 13072 22641
+28 0 1 29801 13072 23015
+29 0 1 30392 13072 23425
+30 0 1 31147 13072 24037
+31 0 1 32028 13072 24358
+32 0 1 32640 13072 24893
+33 0 1 33528 13072 25488
+34 0 1 34335 13072 25804
+35 0 1 34921 13072 26309
+36 0 1 35793 13072 26622
+37 0 1 36459 13072 27212
+38 0 1 37091 13072 27603
+39 0 1 37923 13072 28031
+40 0 1 38774 13072 28380
+41 0 1 39359 13072 28828
+42 0 1 40166 13072 29443
+43 0 1 41054 13072 29768
+44 0 1 41666 13072 30330
+45 0 1 42547 13072 30882
+46 0 1 43303 13072 31203
+47 0 1 43893 13072 31675
+48 0 1 44763 13072 32002
+49 0 1 45396 13072 32598
+50 0 1 46061 13072 32960
+51 0 1 46933 13072 33424
+52 0 1 47519 13072 33756
+53 0 1 48325 13072 34245
+54 0 1 49213 13072 34862
+55 0 1 49825 13072 35182
+56 0 1 50700 13072 35751
+57 0 1 51545 13072 36259
+58 0 1 52253 13072 36597
+59 0 1 52860 13072 37034
+60 0 1 53729 13072 37382
+61 0 1 54336 13072 37985
+62 0 1 55042 13072 38324
+63 0 1 55918 13072 38823
+0 1 1 8775 14229 11024
+1 1 1 9656 14229 11552
+2 1 1 10409 14229 11858
+3 1 1 11000 14229 12456
+4 1 1 11841 14229 12859
+5 1 1 12803 14287 13266
+6 1 1 12678 13956 13593
+7 1 1 13025 13779 14013
+8 1 1 13996 13801 14627
+9 1 1 14618 13801 14949
+10 1 1 15524 13801 15524
+11 1 1 16429 13801 15855
+12 1 1 17050 13801 16458
+13 1 1 17969 13801 16950
+14 1 1 18710 13801 17281
+15 1 1 19347 13801 17891
+16 1 1 20259 13801 18271
+17 1 1 20896 13801 18716
+18 1 1 21638 13801 19068
+19 1 1 22557 13801 19554
+20 1 1 23179 13801 20163
+21 1 1 24085 13801 20495
+22 1 1 25017 13801 21070
+23 1 1 25856 13790 21395
+24 1 1 26550 13918 21956
+25 1 1 27677 14292 22271
+26 1 1 28299 14229 22659
+27 1 1 28931 14229 23059
+28 1 1 29801 14229 23452
+29 1 1 30392 14229 24055
+30 1 1 31147 14229 24367
+31 1 1 32028 14229 24882
+32 1 1 32640 14229 25481
+33 1 1 33528 14229 25800
+34 1 1 34335 14229 26340
+35 1 1 34921 14229 26647
+36 1 1 35793 14229 27245
+37 1 1 36459 14229 27649
+38 1 1 37091 14229 28048
+39 1 1 37923 14229 28417
+40 1 1 38774 14229 28846
+41 1 1 39359 14229 29449
+42 1 1 40166 14229 29759
+43 1 1 41054 14229 30318
+44 1 1 41666 14229 30893
+45 1 1 42547 14229 31207
+46 1 1 43303 14229 31716
+47 1 1 43893 14229 32033
+48 1 1 44763 14229 32633
+49 1 1 45396 14229 33004
+50 1 1 46061 14229 33439
+51 1 1 46933 14229 33781
+52 1 1 47519 14229 34251
+53 1 1 48325 14229 34852
+54 1 1 49213 14229 35170
+55 1 1 49825 14229 35756
+56 1 1 50700 14229 36286
+57 1 1 51545 14229 36607
+58 1 1 52253 14229 37081
+59 1 1 52860 14229 37416
+60 1 1 53729 14229 38018
+61 1 1 54336 14229 38362
+62 1 1 55042 14229 38833
+63 1 1 55918 14229 39156
+0 2 1 8775 15406 11572
+1 2 1 9656 15406 11885
+2 2 1 10409 15406 12492
+3 2 1 11000 15406 12913
+4 2 1 11841 15406 13284
+5 2 1 7261 12943 13393
+6 2 1 39002 27001 15520
+7 2 1 55100 34540 16575
+8 2 1 51944 33569 16403
+9 2 1 51292 33569 16387
+10 2 1 50340 33569 16359
+11 2 1 49391 33569 16343
+12 2 1 48739 33569 16312
+13 2 1 47775 33569 16288
+14 2 1 46997 33569 16272
+15 2 1 46328 33569 16242
+16 2 1 45371 33569 16223
+17 2 1 44704 33569 16201
+18 2 1 43924 33569 16184
+19 2 1 42960 33569 16160
+20 2 1 42307 33569 16130
+21 2 1 41356 33569 16114
+22 2 1 40379 33569 16085
+23 2 1 39790 34063 15893
+24 2 1 35756 28604 17776
+25 2 1 25779 12733 23591
+26 2 1 28299 15406 23115
+27 2 1 28931 15406 23493
+28 2 1 29801 15406 24090
+29 2 1 30392 15406 24402
+30 2 1 31147 15406 24896
+31 2 1 32028 15406 25478
+32 2 1 32640 15406 25796
+33 2 1 33528 15406 26361
+34 2 1 34335 15406 26673
+35 2 1 34921 15406 27280
+36 2 1 35793 15406 27702
+37 2 1 36459 15406 28073
+38 2 1 37091 15406 28470
+39 2 1 37923 15406 28882
+40 2 1 38774 15406 29475
+41 2 1 39359 15406 29778
+42 2 1 40166 15406 30316
+43 2 1 41054 15406 30893
+44 2 1 41666 15406 31214
+45 2 1 42547 15406 31753
+46 2 1 43303 15406 32068
+47 2 1 43893 15406 32674
+48 2 1 44763 15406 33060
+49 2 1 45396 15406 33462
+50 2 1 46061 15406 33828
+51 2 1 46933 15406 34278
+52 2 1 47519 15406 34866
+53 2 1 48325 15406 35171
+54 2 1 49213 15406 35752
+55 2 1 49825 15406 36305
+56 2 1 50700 15406 36622
+57 2 1 51545 15406 37128
+58 2 1 52253 15406 37456
+59 2 1 52860 15406 38060
+60 2 1 53729 15406 38416
+61 2 1 54336 15406 38852
+62 2 1 55042 15406 39192
+63 2 1 55918 15406 39684
+0 3 1 8775 16551 11886
+1 3 1 9656 16551 12505
+2 3 1 10409 16551 12945
+3 3 1 11000 16551 13297
+4 3 1 11841 16551 13721
+5 3 1 7521 14369 13925
+6 3 1 37763 26822 15671
+7 3 1 53121 33500 16423
+8 3 1 50159 32640 16320
+9 3 1 49566 32640 16320
+10 3 1 48702 32640 16320
+11 3 1 47840 32640 16320
+12 3 1 47248 32640 16320
+13 3 1 46373 32640 16320
+14 3 1 45666 32640 16320
+15 3 1 45059 32640 16320
+16 3 1 44190 32640 16320
+17 3 1 43583 32640 16320
+18 3 1 42876 32640 16320
+19 3 1 42000 32640 16320
+20 3 1 41407 32640 16320
+21 3 1 40544 32640 16320
+22 3 1 39656 32640 16320
+23 3 1 39134 33078 16137
+24 3 1 35323 28242 18140
+25 3 1 25869 14183 24069
+26 3 1 28299 16551 23523
+27 3 1 28931 16551 24117
+28 3 1 29801 16551 24433
+29 3 1 30392 16551 24909
+30 3 1 31147 16551 25477
+31 3 1 32028 16551 25790
+32 3 1 32640 16551 26345
+33 3 1 33528 16551 26675
+34 3 1 34335 16551 27294
+35 3 1 34921 16551 27734
+36 3 1 35793 16551 28086
+37 3 1 36459 16551 28511
+38 3 1 37091 16551 28910
+39 3 1 37923 16551 29496
+40 3 1 38774 16551 29800
+41 3 1 39359 16551 30316
+42 3 1 40166 16551 30876
+43 3 1 41054 16551 31203
+44 3 1 41666 16551 31757
+45 3 1 42547 16551 32082
+46 3 1 43303 16551 32696
+47 3 1 43893 16551 33100
+48 3 1 44763 16551 33475
+49 3 1 45396 16551 33867
+50 3 1 46061 16551 34301
+51 3 1 46933 16551 34877
+52 3 1 47519 16551 35178
+53 3 1 48325 16551 35736
+54 3 1 49213 16551 36289
+55 3 1 49825 16551 36621
+56 3 1 50700 16551 37148
+57 3 1 51545 16551 37479
+58 3 1 52253 16551 38087
+59 3 1 52860 16551 38460
+60 3 1 53729 16551 38863
+61 3 1 54336 16551 39225
+62 3 1 55042 16551 39698
+63 3 1 55918 16551 40266
+0 4 1 8775 17666 12495
+1 4 1 9656 17666 12950
+2 4 1 10409 17666 13301
+3 4 1 11000 17666 13746
+4 4 1 11841 17666 14139
+5 4 1 7521 15635 14565
+6 4 1 37763 27225 15842
+7 4 1 53121 33441 16396
+8 4 1 50159 32640 16320
+9 4 1 49566 32640 16320
+10 4 1 48702 32640 16320
+11 4 1 47840 32640 16320
+12 4 1 47248 32640 16320
+13 4 1 46373 32640 16320
+14 4 1 45666 32640 16320
+15 4 1 45059 32640 16320
+16 4 1 44190 32640 16320
+17 4 1 43583 32640 16320
+18 4 1 42876 32640 16320
+19 4 1 42000 32640 16320
+20 4 1 41407 32640 16320
+21 4 1 40544 32640 16320
+22 4 1 39656 32640 16320
+23 4 1 39134 33048 16129
+24 4 1 35323 28547 18207
+25 4 1 25869 15462 24531
+26 4 1 28299 17666 24133
+27 4 1 28931 17666 24457
+28 4 1 29801 17666 24920
+29 4 1 30392 17666 25474
+30 4 1 31147 17666 25795
+31 4 1 32028 17666 26309
+32 4 1 32640 17666 26649
+33 4 1 33528 17666 27283
+34 4 1 34335 17666 27739
+35 4 1 34921 17666 28090
+36 4 1 35793 17666 28536
+37 4 1 36459 17666 28928
+38 4 1 37091 17666 29509
+39 4 1 37923 17666 29818
+40 4 1 38774 17666 30318
+41 4 1 39359 17666 30860
+42 4 1 40166 17666 31190
+43 4 1 41054 17666 31725
+44 4 1 41666 17666 32071
+45 4 1 42547 17666 32698
+46 4 1 43303 17666 33117
+47 4 1 43893 17666 33481
+48 4 1 44763 17666 33894
+49 4 1 45396 17666 34317
+50 4 1 46061 17666 34885
+51 4 1 46933 17666 35187
+52 4 1 47519 17666 35725
+53 4 1 48325 17666 36257
+54 4 1 49213 17666 36602
+55 4 1 49825 17666 37137
+56 4 1 50700 17666 37480
+57 4 1 51545 17666 38097
+58 4 1 52253 17666 38485
+59 4 1 52860 17666 38867
+60 4 1 53729 17666 39251
+61 4 1 54336 17666 39709
+62 4 1 55042 17666 40264
+63 4 1 55918 17666 40567
+0 5 1 8775 18758 12928
+1 5 1 9656 18758 13296
+2 5 1 10409 18758 13751
+3 5 1 11000 18758 14146
+4 5 1 11841 18758 14726
+5 5 1 7521 16874 14979
+6 5 1 37763 27620 15949
+7 5 1 53121 33382 16379
+8 5 1 50159 32640 16320
+9 5 1 49566 32640 16320
+10 5 1 48702 32640 16320
+11 5 1 47840 32640 16320
+12 5 1 47248 32640 16320
+13 5 1 46373 32640 16320
+14 5 1 45666 32640 16320
+15 5 1 45059 32640 16320
+16 5 1 44190 32640 16320
+17 5 1 43583 32640 16320
+18 5 1 42876 32640 16320
+19 5 1 42000 32640 16320
+20 5 1 41407 32640 16320
+21 5 1 40544 32640 16320
+22 5 1 39656 32640 16320
+23 5 1 39134 33018 16113
+24 5 1 35323 28845 18378
+25 5 1 25869 16714 25177
+26 5 1 28299 18758 24472
+27 5 1 28931 18758 24926
+28 5 1 29801 18758 25471
+29 5 1 30392 18758 25805
+30 5 1 31147 18758 26276
+31 5 1 32028 18758 26615
+32 5 1 32640 18758 27246
+33 5 1 33528 18758 27716
+34 5 1 34335 18758 28085
+35 5 1 34921 18758 28541
+36 5 1 35793 18758 28935
+37 5 1 36459 18758 29517
+38 5 1 37091 18758 29832
+39 5 1 37923 18758 30319
+40 5 1 38774 18758 30847
+41 5 1 39359 18758 31187
+42 5 1 40166 18758 31679
+43 5 1 41054 18758 32035
+44 5 1 41666 18758 32678
+45 5 1 42547 18758 33110
+46 5 1 43303 18758 33480
+47 5 1 43893 18758 33907
+48 5 1 44763 18758 34325
+49 5 1 45396 18758 34890
+50 5 1 46061 18758 35195
+51 5 1 46933 18758 35717
+52 5 1 47519 18758 36231
+53 5 1 48325 18758 36583
+54 5 1 49213 18758 37094
+55 5 1 49825 18758 37460
+56 5 1 50700 18758 38090
+57 5 1 51545 18758 38490
+58 5 1 52253 18758 38868
+59 5 1 52860 18758 39267
+60 5 1 53729 18758 39715
+61 5 1 54336 18758 40263
+62 5 1 55042 18758 40566
+63 5 1 55918 18758 41124
+0 6 1 8775 19809 13281
+1 6 1 9656 19809 13736
+2 6 1 10409 19809 14141
+3 6 1 11000 19809 14726
+4 6 1 11841 19809 15077
+5 6 1 7521 18068 15442
+6 6 1 37763 28000 16124
+7 6 1 53121 33326 16354
+8 6 1 50159 32640 16320
+9 6 1 49566 32640 16320
+10 6 1 48702 32640 16320
+11 6 1 47840 32640 16320
+12 6 1 47248 32640 16320
+13 6 1 46373 32640 16320
+14 6 1 45666 32640 16320
+15 6 1 45059 32640 16320
+16 6 1 44190 32640 16320
+17 6 1 43583 32640 16320
+18 6 1 42876 32640 16320
+19 6 1 42000 32640 16320
+20 6 1 41407 32640 16320
+21 6 1 40544 32640 16320
+22 6 1 39656 32640 16320
+23 6 1 39134 32989 16101
+24 6 1 35323 29133 18498
+25 6 1 25869 17920 25601
+26 6 1 28299 19809 24929
+27 6 1 28931 19809 25469
+28 6 1 29801 19809 25814
+29 6 1 30392 19809 26249
+30 6 1 31147 19809 26594
+31 6 1 32028 19809 27202
+32 6 1 32640 19809 27666
+33 6 1 33528 19809 28070
+34 6 1 34335 19809 28525
+35 6 1 34921 19809 28930
+36 6 1 35793 19809 29518
+37 6 1 36459 19809 29839
+38 6 1 37091 19809 30319
+39 6 1 37923 19809 30838
+40 6 1 38774 19809 31190
+41 6 1 39359 19809 31640
+42 6 1 40166 19809 31995
+43 6 1 41054 19809 32634
+44 6 1 41666 19809 33078
+45 6 1 42547 19809 33473
+46 6 1 43303 19809 33903
+47 6 1 43893 19809 34326
+48 6 1 44763 19809 34892
+49 6 1 45396 19809 35201
+50 6 1 46061 19809 35712
+51 6 1 46933 19809 36212
+52 6 1 47519 19809 36574
+53 6 1 48325 19809 37040
+54 6 1 49213 19809 37418
+55 6 1 49825 19809 38064
+56 6 1 50700 19809 38473
+57 6 1 51545 19809 38865
+58 6 1 52253 19809 39271
+59 6 1 52860 19809 39717
+60 6 1 53729 19809 40262
+61 6 1 54336 19809 40567
+62 6 1 55042 19809 41110
+63 6 1 55918 19809 41594
+0 7 1 8775 21020 13700
+1 7 1 9656 21020 14124
+2 7 1 10409 21020 14720
+3 7 1 11000 21020 15074
+4 7 1 11841 21020 15481
+5 7 1 7521 19444 15875
+6 7 1 37763 28438 16242
+7 7 1 53121 33261 16336
+8 7 1 50159 32640 16320
+9 7 1 49566 32640 16320
+10 7 1 48702 32640 16320
+11 7 1 47840 32640 16320
+12 7 1 47248 32640 16320
+13 7 1 46373 32640 16320
+14 7 1 45666 32640 16320
+15 7 1 45059 32640 16320
+16 7 1 44190 32640 16320
+17 7 1 43583 32640 16320
+18 7 1 42876 32640 16320
+19 7 1 42000 32640 16320
+20 7 1 41407 32640 16320
+21 7 1 40544 32640 16320
+22 7 1 39656 32640 16320
+23 7 1 39134 32956 16092
+24 7 1 35323 29464 18578
+25 7 1 25869 19309 26108
+26 7 1 28299 21020 25468
+27 7 1 28931 21020 25819
+28 7 1 29801 21020 26231
+29 7 1 30392 21020 26581
+30 7 1 31147 21020 27171
+31 7 1 32028 21020 27611
+32 7 1 32640 21020 28041
+33 7 1 33528 21020 28489
+34 7 1 34335 21020 28912
+35 7 1 34921 21020 29511
+36 7 1 35793 21020 29837
+37 7 1 36459 21020 30318
+38 7 1 37091 21020 30834
+39 7 1 37923 21020 31193
+40 7 1 38774 21020 31611
+41 7 1 39359 21020 31969
+42 7 1 40166 21020 32588
+43 7 1 41054 21020 33022
+44 7 1 41666 21020 33457
+45 7 1 42547 21020 33882
+46 7 1 43303 21020 34317
+47 7 1 43893 21020 34890
+48 7 1 44763 21020 35202
+49 7 1 45396 21020 35709
+50 7 1 46061 21020 36200
+51 7 1 46933 21020 36572
+52 7 1 47519 21020 36999
+53 7 1 48325 21020 37373
+54 7 1 49213 21020 38018
+55 7 1 49825 21020 38434
+56 7 1 50700 21020 38858
+57 7 1 51545 21020 39261
+58 7 1 52253 21020 39714
+59 7 1 52860 21020 40261
+60 7 1 53729 21020 40567
+61 7 1 54336 21020 41101
+62 7 1 55042 21020 41571
+63 7 1 55918 21020 41959
+0 8 1 8775 22245 14093
+1 8 1 9656 22245 14707
+2 8 1 10409 22245 15058
+3 8 1 11000 22245 15484
+4 8 1 11841 22245 15836
+5 8 1 7521 20835 16451
+6 8 1 37763 28881 16479
+7 8 1 53121 33196 16304
+8 8 1 50159 32640 16320
+9 8 1 49566 32640 16320
+10 8 1 48702 32640 16320
+11 8 1 47840 32640 16320
+12 8 1 47248 32640 16320
+13 8 1 46373 32640 16320
+14 8 1 45666 32640 16320
+15 8 1 45059 32640 16320
+16 8 1 44190 32640 16320
+17 8 1 43583 32640 16320
+18 8 1 42876 32640 16320
+19 8 1 42000 32640 16320
+20 8 1 41407 32640 16320
+21 8 1 40544 32640 16320
+22 8 1 39656 32640 16320
+23 8 1 39134 32923 16076
+24 8 1 35323 29798 18756
+25 8 1 25869 20714 26704
+26 8 1 28299 22245 25817
+27 8 1 28931 22245 26225
+28 8 1 29801 22245 26575
+29 8 1 30392 22245 27153
+30 8 1 31147 22245 27574
+31 8 1 32028 22245 28013
+32 8 1 32640 22245 28433
+33 8 1 33528 22245 28881
+34 8 1 34335 22245 29496
+35 8 1 34921 22245 29826
+36 8 1 35793 22245 30314
+37 8 1 36459 22245 30834
+38 8 1 37091 22245 31193
+39 8 1 37923 22245 31597
+40 8 1 38774 22245 31954
+41 8 1 39359 22245 32557
+42 8 1 40166 22245 32966
+43 8 1 41054 22245 33428
+44 8 1 41666 22245 33844
+45 8 1 42547 22245 34298
+46 8 1 43303 22245 34884
+47 8 1 43893 22245 35198
+48 8 1 44763 22245 35706
+49 8 1 45396 22245 36197
+50 8 1 46061 22245 36571
+51 8 1 46933 22245 36973
+52 8 1 47519 22245 37346
+53 8 1 48325 22245 37971
+54 8 1 49213 22245 38376
+55 8 1 49825 22245 38843
+56 8 1 50700 22245 39238
+57 8 1 51545 22245 39705
+58 8 1 52253 22245 40260
+59 8 1 52860 22245 40566
+60 8 1 53729 22245 41097
+61 8 1 54336 22245 41560
+62 8 1 55042 22245 41954
+63 8 1 55918 22245 42357
+0 9 1 8775 23287 14683
+1 9 1 9656 23287 15030
+2 9 1 10409 23287 15490
+3 9 1 11000 23287 15832
+4 9 1 11841 23287 16364
+5 9 1 7521 22019 16973
+6 9 1 37763 29258 16577
+7 9 1 53121 33140 16286
+8 9 1 50159 32640 16320
+9 9 1 49566 32640 16320
+10 9 1 48702 32640 16320
+11 9 1 47840 32640 16320
+12 9 1 47248 32640 16320
+13 9 1 46373 32640 16320
+14 9 1 45666 32640 16320
+15 9 1 45059 32640 16320
+16 9 1 44190 32640 16320
+17 9 1 43583 32640 16320
+18 9 1 42876 32640 16320
+19 9 1 42000 32640 16320
+20 9 1 41407 32640 16320
+21 9 1 40544 32640 16320
+22 9 1 39656 32640 16320
+23 9 1 39134 32895 16066
+24 9 1 35323 30083 18841
+25 9 1 25869 21910 27133
+26 9 1 28299 23287 26231
+27 9 1 28931 23287 26574
+28 9 1 29801 23287 27146
+29 9 1 30392 23287 27555
+30 9 1 31147 23287 27997
+31 9 1 32028 23287 28379
+32 9 1 32640 23287 28836
+33 9 1 33528 23287 29471
+34 9 1 34335 23287 29805
+35 9 1 34921 23287 30307
+36 9 1 35793 23287 30837
+37 9 1 36459 23287 31189
+38 9 1 37091 23287 31597
+39 9 1 37923 23287 31949
+40 9 1 38774 23287 32541
+41 9 1 39359 23287 32932
+42 9 1 40166 23287 33401
+43 9 1 41054 23287 33790
+44 9 1 41666 23287 34269
+45 9 1 42547 23287 34873
+46 9 1 43303 23287 35188
+47 9 1 43893 23287 35705
+48 9 1 44763 23287 36201
+49 9 1 45396 23287 36569
+50 9 1 46061 23287 36964
+51 9 1 46933 23287 37332
+52 9 1 47519 23287 37943
+53 9 1 48325 23287 38323
+54 9 1 49213 23287 38817
+55 9 1 49825 23287 39202
+56 9 1 50700 23287 39688
+57 9 1 51545 23287 40257
+58 9 1 52253 23287 40563
+59 9 1 52860 23287 41098
+60 9 1 53729 23287 41561
+61 9 1 54336 23287 41951
+62 9 1 55042 23287 42337
+63 9 1 55918 23287 42726
+0 10 1 8775 24371 14994
+1 10 1 9656 24371 15497
+2 10 1 10409 24371 15826
+3 10 1 11000 24371 16368
+4 10 1 11841 24371 16842
+5 10 1 7521 23249 17384
+6 10 1 37763 29650 16746
+7 10 1 53121 33082 16262
+8 10 1 50159 32640 16320
+9 10 1 49566 32640 16320
+10 10 1 48702 32640 16320
+11 10 1 47840 32640 16320
+12 10 1 47248 32640 16320
+13 10 1 46373 32640 16320
+14 10 1 45666 32640 16320
+15 10 1 45059 32640 16320
+16 10 1 44190 32640 16320
+17 10 1 43583 32640 16320
+18 10 1 42876 32640 16320
+19 10 1 42000 32640 16320
+20 10 1 41407 32640 16320
+21 10 1 40544 32640 16320
+22 10 1 39656 32640 16320
+23 10 1 39134 32865 16053
+24 10 1 35323 30380 18979
+25 10 1 25869 23154 27620
+26 10 1 28299 24371 26578
+27 10 1 28931 24371 27150
+28 10 1 29801 24371 27555
+29 10 1 30392 24371 27992
+30 10 1 31147 24371 28352
+31 10 1 32028 24371 28796
+32 10 1 32640 24371 29434
+33 10 1 33528 24371 29777
+34 10 1 34335 24371 30296
+35 10 1 34921 24371 30843
+36 10 1 35793 24371 31182
+37 10 1 36459 24371 31612
+38 10 1 37091 24371 31953
+39 10 1 37923 24371 32540
+40 10 1 38774 24371 32921
+41 10 1 39359 24371 33388
+42 10 1 40166 24371 33744
+43 10 1 41054 24371 34229
+44 10 1 41666 24371 34854
+45 10 1 42547 24371 35173
+46 10 1 43303 24371 35703
+47 10 1 43893 24371 36212
+48 10 1 44763 24371 36567
+49 10 1 45396 24371 36974
+50 10 1 46061 24371 37331
+51 10 1 46933 24371 37933
+52 10 1 47519 24371 38297
+53 10 1 48325 24371 38793
+54 10 1 49213 24371 39154
+55 10 1 49825 24371 39664
+56 10 1 50700 24371 40252
+57 10 1 51545 24371 40558
+58 10 1 52253 24371 41102
+59 10 1 52860 24371 41573
+60 10 1 53729 24371 41951
+61 10 1 54336 24371 42338
+62 10 1 55042 24371 42717
+63 10 1 55918 24371 43333
+0 11 1 8775 25476 15499
+1 11 1 9656 25476 15819
+2 11 1 10409 25476 16376
+3 11 1 11000 25476 16862
+4 11 1 11841 25476 17175
+5 11 1 7521 24505 18027
+6 11 1 37763 30050 16972
+7 11 1 53121 33023 16230
+8 11 1 50159 32640 16320
+9 11 1 49566 32640 16320
+10 11 1 48702 32640 16320
+11 11 1 47840 32640 16320
+12 11 1 47248 32640 16320
+13 11 1 46373 32640 16320
+14 11 1 45666 32640 16320
+15 11 1 45059 32640 16320
+16 11 1 44190 32640 16320
+17 11 1 43583 32640 16320
+18 11 1 42876 32640 16320
+19 11 1 42000 32640 16320
+20 11 1 41407 32640 16320
+21 11 1 40544 32640 16320
+22 11 1 39656 32640 16320
+23 11 1 39134 32835 16045
+24 11 1 35323 30682 19058
+25 11 1 25869 24422 28035
+26 11 1 28299 25476 27164
+27 11 1 28931 25476 27574
+28 11 1 29801 25476 27994
+29 11 1 30392 25476 28351
+30 11 1 31147 25476 28782
+31 11 1 32028 25476 29405
+32 11 1 32640 25476 29741
+33 11 1 33528 25476 30281
+34 11 1 34335 25476 30849
+35 11 1 34921 25476 31173
+36 11 1 35793 25476 31639
+37 11 1 36459 25476 31965
+38 11 1 37091 25476 32552
+39 11 1 37923 25476 32932
+40 11 1 38774 25476 33386
+41 11 1 39359 25476 33728
+42 11 1 40166 25476 34198
+43 11 1 41054 25476 34826
+44 11 1 41666 25476 35154
+45 11 1 42547 25476 35701
+46 11 1 43303 25476 36227
+47 11 1 43893 25476 36565
+48 11 1 44763 25476 37000
+49 11 1 45396 25476 37343
+50 11 1 46061 25476 37941
+51 11 1 46933 25476 38297
+52 11 1 47519 25476 38784
+53 11 1 48325 25476 39120
+54 11 1 49213 25476 39632
+55 11 1 49825 25476 40243
+56 11 1 50700 25476 40552
+57 11 1 51545 25476 41109
+58 11 1 52253 25476 41594
+59 11 1 52860 25476 41953
+60 11 1 53729 25476 42359
+61 11 1 54336 25476 42724
+62 11 1 55042 25476 43332
+63 11 1 55918 25476 43672
+0 12 1 8775 26612 15813
+1 12 1 9656 26612 16387
+2 12 1 10409 26612 16889
+3 12 1 11000 26612 17197
+4 12 1 11841 26612 17790
+5 12 1 7521 25794 18513
+6 12 1 37763 30460 17058
+7 12 1 53121 32962 16214
+8 12 1 50159 32640 16320
+9 12 1 49566 32640 16320
+10 12 1 48702 32640 16320
+11 12 1 47840 32640 16320
+12 12 1 47248 32640 16320
+13 12 1 46373 32640 16320
+14 12 1 45666 32640 16320
+15 12 1 45059 32640 16320
+16 12 1 44190 32640 16320
+17 12 1 43583 32640 16320
+18 12 1 42876 32640 16320
+19 12 1 42000 32640 16320
+20 12 1 41407 32640 16320
+21 12 1 40544 32640 16320
+22 12 1 39656 32640 16320
+23 12 1 39134 32804 16031
+24 12 1 35323 30992 19196
+25 12 1 25869 25724 28683
+26 12 1 28299 26612 27611
+27 12 1 28931 26612 28006
+28 12 1 29801 26612 28378
+29 12 1 30392 26612 28792
+30 12 1 31147 26612 29403
+31 12 1 32028 26612 29721
+32 12 1 32640 26612 30261
+33 12 1 33528 26612 30852
+34 12 1 34335 26612 31168
+35 12 1 34921 26612 31673
+36 12 1 35793 26612 31987
+37 12 1 36459 26612 32579
+38 12 1 37091 26612 32966
+39 12 1 37923 26612 33396
+40 12 1 38774 26612 33743
+41 12 1 39359 26612 34195
+42 12 1 40166 26612 34808
+43 12 1 41054 26612 35131
+44 12 1 41666 26612 35698
+45 12 1 42547 26612 36246
+46 12 1 43303 26612 36567
+47 12 1 43893 26612 37039
+48 12 1 44763 26612 37367
+49 12 1 45396 26612 37965
+50 12 1 46061 26612 38323
+51 12 1 46933 26612 38789
+52 12 1 47519 26612 39119
+53 12 1 48325 26612 39613
+54 12 1 49213 26612 40226
+55 12 1 49825 26612 40545
+56 12 1 50700 26612 41119
+57 12 1 51545 26612 41622
+58 12 1 52253 26612 41960
+59 12 1 52860 26612 42398
+60 12 1 53729 26612 42748
+61 12 1 54336 26612 43351
+62 12 1 55042 26612 43687
+63 12 1 55918 26612 44188
+0 13 1 8775 27786 16400
+1 13 1 9656 27786 16922
+2 13 1 10409 27786 17230
+3 13 1 11000 27786 17831
+4 13 1 11841 27786 18229
+5 13 1 7521 27127 19010
+6 13 1 37763 30885 17283
+7 13 1 53121 32900 16184
+8 13 1 50159 32640 16320
+9 13 1 49566 32640 16320
+10 13 1 48702 32640 16320
+11 13 1 47840 32640 16320
+12 13 1 47248 32640 16320
+13 13 1 46373 32640 16320
+14 13 1 45666 32640 16320
+15 13 1 45059 32640 16320
+16 13 1 44190 32640 16320
+17 13 1 43583 32640 16320
+18 13 1 42876 32640 16320
+19 13 1 42000 32640 16320
+20 13 1 41407 32640 16320
+21 13 1 40544 32640 16320
+22 13 1 39656 32640 16320
+23 13 1 39134 32772 16015
+24 13 1 35323 31313 19372
+25 13 1 25869 27071 29238
+26 13 1 28299 27786 28029
+27 13 1 28931 27786 28429
+28 13 1 29801 27786 28827
+29 13 1 30392 27786 29428
+30 13 1 31147 27786 29737
+31 13 1 32028 27786 30258
+32 13 1 32640 27786 30852
+33 13 1 33528 27786 31170
+34 13 1 34335 27786 31711
+35 13 1 34921 27786 32019
+36 13 1 35793 27786 32620
+37 13 1 36459 27786 33019
+38 13 1 37091 27786 33419
+39 13 1 37923 27786 33787
+40 13 1 38774 27786 34221
+41 13 1 39359 27786 34821
+42 13 1 40166 27786 35129
+43 13 1 41054 27786 35695
+44 13 1 41666 27786 36264
+45 13 1 42547 27786 36578
+46 13 1 43303 27786 37087
+47 13 1 43893 27786 37406
+48 13 1 44763 27786 38007
+49 13 1 45396 27786 38374
+50 13 1 46061 27786 38809
+51 13 1 46933 27786 39151
+52 13 1 47519 27786 39627
+53 13 1 48325 27786 40224
+54 13 1 49213 27786 40540
+55 13 1 49825 27786 41132
+56 13 1 50700 27786 41656
+57 13 1 51545 27786 41978
+58 13 1 52253 27786 42451
+59 13 1 52860 27786 42789
+60 13 1 53729 27786 43391
+61 13 1 54336 27786 43732
+62 13 1 55042 27786 44203
+63 13 1 55918 27786 44526
+0 14 1 8775 28965 16941
+1 14 1 9656 28965 17258
+2 14 1 10409 28965 17868
+3 14 1 11000 28965 18283
+4 14 1 11841 28965 18655
+5 14 1 7521 28466 19510
+6 14 1 37763 31311 17377
+7 14 1 53121 32836 16167
+8 14 1 50159 32640 16320
+9 14 1 49566 32640 16320
+10 14 1 48702 32640 16320
+11 14 1 47840 32640 16320
+12 14 1 47248 32640 16320
+13 14 1 46373 32640 16320
+14 14 1 45666 32640 16320
+15 14 1 45059 32640 16320
+16 14 1 44190 32640 16320
+17 14 1 43583 32640 16320
+18 14 1 42876 32640 16320
+19 14 1 42000 32640 16320
+20 14 1 41407 32640 16320
+21 14 1 40544 32640 16320
+22 14 1 39656 32640 16320
+23 14 1 39134 32740 16006
+24 14 1 35323 31635 19445
+25 14 1 25869 28424 29714
+26 14 1 28299 28965 28485
+27 14 1 28931 28965 28869
+28 14 1 29801 28965 29463
+29 14 1 30392 28965 29772
+30 14 1 31147 28965 30273
+31 14 1 32028 28965 30849
+32 14 1 32640 28965 31169
+33 14 1 33528 28965 31731
+34 14 1 34335 28965 32047
+35 14 1 34921 28965 32657
+36 14 1 35793 28965 33072
+37 14 1 36459 28965 33444
+38 14 1 37091 28965 33840
+39 14 1 37923 28965 34259
+40 14 1 38774 28965 34847
+41 14 1 39359 28965 35149
+42 14 1 40166 28965 35693
+43 14 1 41054 28965 36263
+44 14 1 41666 28965 36587
+45 14 1 42547 28965 37123
+46 14 1 43303 28965 37442
+47 14 1 43893 28965 38049
+48 14 1 44763 28965 38431
+49 14 1 45396 28965 38833
+50 14 1 46061 28965 39198
+51 14 1 46933 28965 39655
+52 14 1 47519 28965 40237
+53 14 1 48325 28965 40542
+54 14 1 49213 28965 41130
+55 14 1 49825 28965 41674
+56 14 1 50700 28965 41994
+57 14 1 51545 28965 42498
+58 14 1 52253 28965 42831
+59 14 1 52860 28965 43435
+60 14 1 53729 28965 43787
+61 14 1 54336 28965 44223
+62 14 1 55042 28965 44562
+63 14 1 55918 28965 45061
+0 15 1 8775 30105 17258
+1 15 1 9656 30105 17880
+2 15 1 10409 30105 18312
+3 15 1 11000 30105 18668
+4 15 1 11841 30105 19088
+5 15 1 7521 29761 20028
+6 15 1 37763 31723 17615
+7 15 1 53121 32776 16135
+8 15 1 50159 32640 16320
+9 15 1 49566 32640 16320
+10 15 1 48702 32640 16320
+11 15 1 47840 32640 16320
+12 15 1 47248 32640 16320
+13 15 1 46373 32640 16320
+14 15 1 45666 32640 16320
+15 15 1 45059 32640 16320
+16 15 1 44190 32640 16320
+17 15 1 43583 32640 16320
+18 15 1 42876 32640 16320
+19 15 1 42000 32640 16320
+20 15 1 41407 32640 16320
+21 15 1 40544 32640 16320
+22 15 1 39656 32640 16320
+23 15 1 39134 32709 15991
+24 15 1 35323 31947 19607
+25 15 1 25869 29732 30226
+26 15 1 28299 30105 28898
+27 15 1 28931 30105 29488
+28 15 1 29801 30105 29801
+29 15 1 30392 30105 30286
+30 15 1 31147 30105 30844
+31 15 1 32028 30105 31163
+32 15 1 32640 30105 31711
+33 15 1 33528 30105 32046
+34 15 1 34335 30105 32669
+35 15 1 34921 30105 33101
+36 15 1 35793 30105 33457
+37 15 1 36459 30105 33878
+38 15 1 37091 30105 34286
+39 15 1 37923 30105 34865
+40 15 1 38774 30105 35168
+41 15 1 39359 30105 35693
+42 15 1 40166 30105 36243
+43 15 1 41054 30105 36576
+44 15 1 41666 30105 37123
+45 15 1 42547 30105 37454
+46 15 1 43303 30105 38070
+47 15 1 43893 30105 38468
+48 15 1 44763 30105 38845
+49 15 1 45396 30105 39234
+50 15 1 46061 30105 39677
+51 15 1 46933 30105 40246
+52 15 1 47519 30105 40547
+53 15 1 48325 30105 41113
+54 15 1 49213 30105 41655
+55 15 1 49825 30105 41993
+56 15 1 50700 30105 42515
+57 15 1 51545 30105 42852
+58 15 1 52253 30105 43460
+59 15 1 52860 30105 43828
+60 15 1 53729 30105 44232
+61 15 1 54336 30105 44593
+62 15 1 55042 30105 45074
+63 15 1 55918 30105 45633
+0 16 1 8775 31215 17868
+1 16 1 9656 31215 18313
+2 16 1 10409 31215 18672
+3 16 1 11000 31215 19109
+4 16 1 11841 31215 19513
+5 16 1 7521 31021 20660
+6 16 1 37763 32125 17780
+7 16 1 53121 32716 16109
+8 16 1 50159 32640 16320
+9 16 1 49566 32640 16320
+10 16 1 48702 32640 16320
+11 16 1 47840 32640 16320
+12 16 1 47248 32640 16320
+13 16 1 46373 32640 16320
+14 16 1 45666 32640 16320
+15 16 1 45059 32640 16320
+16 16 1 44190 32640 16320
+17 16 1 43583 32640 16320
+18 16 1 42876 32640 16320
+19 16 1 42000 32640 16320
+20 16 1 41407 32640 16320
+21 16 1 40544 32640 16320
+22 16 1 39656 32640 16320
+23 16 1 39134 32679 15983
+24 16 1 35323 32250 19675
+25 16 1 25869 31005 30694
+26 16 1 28299 31215 29502
+27 16 1 28931 31215 29822
+28 16 1 29801 31215 30295
+29 16 1 30392 31215 30838
+30 16 1 31147 31215 31167
+31 16 1 32028 31215 31672
+32 16 1 32640 31215 32018
+33 16 1 33528 31215 32656
+34 16 1 34335 31215 33102
+35 16 1 34921 31215 33461
+36 16 1 35793 31215 33899
+37 16 1 36459 31215 34302
+38 16 1 37091 31215 34877
+39 16 1 37923 31215 35184
+40 16 1 38774 31215 35693
+41 16 1 39359 31215 36224
+42 16 1 40166 31215 36562
+43 16 1 41054 31215 37088
+44 16 1 41666 31215 37442
+45 16 1 42547 31215 38070
+46 16 1 43303 31215 38481
+47 16 1 43893 31215 38851
+48 16 1 44763 31215 39258
+49 16 1 45396 31215 39692
+50 16 1 46061 31215 40251
+51 16 1 46933 31215 40553
+52 16 1 47519 31215 41100
+53 16 1 48325 31215 41620
+54 16 1 49213 31215 41975
+55 16 1 49825 31215 42499
+56 16 1 50700 31215 42852
+57 16 1 51545 31215 43469
+58 16 1 52253 31215 43851
+59 16 1 52860 31215 44236
+60 16 1 53729 31215 44616
+61 16 1 54336 31215 45084
+62 16 1 55042 31215 45628
+63 16 1 55918 31215 45956
+0 17 1 8775 32301 18287
+1 17 1 9656 32301 18668
+2 17 1 10409 32301 19110
+3 17 1 11000 32301 19519
+4 17 1 11841 32301 20091
+5 17 1 7521 32255 21073
+6 17 1 37763 32517 17893
+7 17 1 53121 32658 16091
+8 17 1 50159 32640 16320
+9 17 1 49566 32640 16320
+10 17 1 48702 32640 16320
+11 17 1 47840 32640 16320
+12 17 1 47248 32640 16320
+13 17 1 46373 32640 16320
+14 17 1 45666 32640 16320
+15 17 1 45059 32640 16320
+16 17 1 44190 32640 16320
+17 17 1 43583 32640 16320
+18 17 1 42876 32640 16320
+19 17 1 42000 32640 16320
+20 17 1 41407 32640 16320
+21 17 1 40544 32640 16320
+22 17 1 39656 32640 16320
+23 17 1 39134 32649 15966
+24 17 1 35323 32547 19847
+25 17 1 25869 32251 31335
+26 17 1 28299 32301 29833
+27 17 1 28931 32301 30301
+28 17 1 29801 32301 30833
+29 17 1 30392 32301 31175
+30 17 1 31147 32301 31636
+31 17 1 32028 32301 31983
+32 17 1 32640 32301 32618
+33 17 1 33528 32301 33075
+34 17 1 34335 32301 33457
+35 17 1 34921 32301 33900
+36 17 1 35793 32301 34308
+37 17 1 36459 32301 34883
+38 17 1 37091 32301 35194
+39 17 1 37923 32301 35693
+40 17 1 38774 32301 36208
+41 17 1 39359 32301 36558
+42 17 1 40166 32301 37038
+43 17 1 41054 32301 37405
+44 17 1 41666 32301 38049
+45 17 1 42547 32301 38470
+46 17 1 43303 32301 38850
+47 17 1 43893 32301 39267
+48 17 1 44763 32301 39699
+49 17 1 45396 32301 40254
+50 17 1 46061 32301 40559
+51 17 1 46933 32301 41091
+52 17 1 47519 32301 41591
+53 17 1 48325 32301 41955
+54 17 1 49213 32301 42453
+55 17 1 49825 32301 42831
+56 17 1 50700 32301 43460
+57 17 1 51545 32301 43852
+58 17 1 52253 32301 44237
+59 17 1 52860 32301 44628
+60 17 1 53729 32301 45090
+61 17 1 54336 32301 45623
+62 17 1 55042 32301 45964
+63 17 1 55918 32301 46425
+0 18 1 8775 33373 18654
+1 18 1 9656 33373 19092
+2 18 1 10409 33373 19514
+3 18 1 11000 33373 20091
+4 18 1 11841 33373 20440
+5 18 1 7521 33473 21534
+6 18 1 37763 32905 18060
+7 18 1 53121 32601 16068
+8 18 1 50159 32640 16320
+9 18 1 49566 32640 16320
+10 18 1 48702 32640 16320
+11 18 1 47840 32640 16320
+12 18 1 47248 32640 16320
+13 18 1 46373 32640 16320
+14 18 1 45666 32640 16320
+15 18 1 45059 32640 16320
+16 18 1 44190 32640 16320
+17 18 1 43583 32640 16320
+18 18 1 42876 32640 16320
+19 18 1 42000 32640 16320
+20 18 1 41407 32640 16320
+21 18 1 40544 32640 16320
+22 18 1 39656 32640 16320
+23 18 1 39134 32620 15955
+24 18 1 35323 32840 19961
+25 18 1 25869 33481 31751
+26 18 1 28299 33373 30303
+27 18 1 28931 33373 30830
+28 18 1 29801 33373 31182
+29 18 1 30392 33373 31608
+30 18 1 31147 33373 31960
+31 18 1 32028 33373 32574
+32 18 1 32640 33373 33022
+33 18 1 33528 33373 33443
+34 18 1 34335 33373 33881
+35 18 1 34921 33373 34303
+36 18 1 35793 33373 34883
+37 18 1 36459 33373 35199
+38 18 1 37091 33373 35693
+39 18 1 37923 33373 36198
+40 18 1 38774 33373 36560
+41 18 1 39359 33373 36998
+42 18 1 40166 33373 37363
+43 18 1 41054 33373 38006
+44 18 1 41666 33373 38434
+45 18 1 42547 33373 38844
+46 18 1 43303 33373 39260
+47 18 1 43893 33373 39699
+48 18 1 44763 33373 40255
+49 18 1 45396 33373 40562
+50 18 1 46061 33373 41086
+51 18 1 46933 33373 41570
+52 18 1 47519 33373 41946
+53 18 1 48325 33373 42397
+54 18 1 49213 33373 42787
+55 18 1 49825 33373 43434
+56 18 1 50700 33373 43831
+57 18 1 51545 33373 44235
+58 18 1 52253 33373 44629
+59 18 1 52860 33373 45091
+60 18 1 53729 33373 45619
+61 18 1 54336 33373 45972
+62 18 1 55042 33373 46397
+63 18 1 55918 33373 46749
+0 19 1 8775 34440 19055
+1 19 1 9656 34440 19498
+2 19 1 10409 34440 20086
+3 19 1 11000 34440 20435
+4 19 1 11841 34440 20848
+5 19 1 6842 34743 22074
+6 19 1 40988 33012 17681
+7 19 1 58275 32084 15155
+8 19 1 54807 32203 15426
+9 19 1 54059 32203 15357
+10 19 1 52967 32203 15279
+11 19 1 51878 32203 15237
+12 19 1 51130 32203 15159
+13 19 1 50023 32203 15096
+14 19 1 49131 32203 15051
+15 19 1 48364 32203 14993
+16 19 1 47266 32203 14945
+17 19 1 46500 32203 14866
+18 19 1 45605 32203 14824
+19 19 1 44499 32203 14757
+20 19 1 43750 32203 14715
+21 19 1 42659 32203 14641
+22 19 1 41538 32203 14566
+23 19 1 40841 32142 14099
+24 19 1 36450 32815 18695
+25 19 1 25636 34769 32543
+26 19 1 28299 34440 30829
+27 19 1 28931 34440 31185
+28 19 1 29801 34440 31593
+29 19 1 30392 34440 31947
+30 19 1 31147 34440 32545
+31 19 1 32028 34440 32966
+32 19 1 32640 34440 33416
+33 19 1 33528 34440 33844
+34 19 1 34335 34440 34286
+35 19 1 34921 34440 34877
+36 19 1 35793 34440 35195
+37 19 1 36459 34440 35693
+38 19 1 37091 34440 36194
+39 19 1 37923 34440 36562
+40 19 1 38774 34440 36970
+41 19 1 39359 34440 37337
+42 19 1 40166 34440 37960
+43 19 1 41054 34440 38377
+44 19 1 41666 34440 38831
+45 19 1 42547 34440 39237
+46 19 1 43303 34440 39692
+47 19 1 43893 34440 40254
+48 19 1 44763 34440 40562
+49 19 1 45396 34440 41084
+50 19 1 46061 34440 41558
+51 19 1 46933 34440 41943
+52 19 1 47519 34440 42356
+53 19 1 48325 34440 42743
+54 19 1 49213 34440 43389
+55 19 1 49825 34440 43790
+56 19 1 50700 34440 44231
+57 19 1 51545 34440 44618
+58 19 1 52253 34440 45089
+59 19 1 52860 34440 45619
+60 19 1 53729 34440 45975
+61 19 1 54336 34440 46382
+62 19 1 55042 34440 46736
+63 19 1 55918 34440 47333
+0 20 1 8775 35509 19470
+1 20 1 9656 35509 20075
+2 20 1 10409 35509 20419
+3 20 1 11000 35509 20855
+4 20 1 11841 35509 21198
+5 20 1 10711 35640 22052
+6 20 1 22613 34892 20739
+7 20 1 28905 34491 20168
+8 20 1 28318 34543 20456
+9 20 1 28459 34543 20769
+10 20 1 28665 34543 21175
+11 20 1 28869 34543 21331
+12 20 1 29010 34543 21711
+13 20 1 29218 34543 22028
+14 20 1 29386 34543 22221
+15 20 1 29531 34543 22504
+16 20 1 29737 34543 22696
+17 20 1 29881 34543 23107
+18 20 1 30049 34543 23282
+19 20 1 30258 34543 23624
+20 20 1 30398 34543 23788
+21 20 1 30604 34543 24136
+22 20 1 30815 34543 24524
+23 20 1 31115 34516 24525
+24 20 1 30025 34807 26523
+25 20 1 26961 35651 31594
+26 20 1 28299 35509 31183
+27 20 1 28931 35509 31591
+28 20 1 29801 35509 31942
+29 20 1 30392 35509 32530
+30 20 1 31147 35509 32930
+31 20 1 32028 35509 33390
+32 20 1 32640 35509 33789
+33 20 1 33528 35509 34258
+34 20 1 34335 35509 34865
+35 20 1 34921 35509 35185
+36 20 1 35793 35509 35692
+37 20 1 36459 35509 36196
+38 20 1 37091 35509 36562
+39 20 1 37923 35509 36960
+40 20 1 38774 35509 37324
+41 20 1 39359 35509 37932
+42 20 1 40166 35509 38322
+43 20 1 41054 35509 38806
+44 20 1 41666 35509 39200
+45 20 1 42547 35509 39676
+46 20 1 43303 35509 40251
+47 20 1 43893 35509 40559
+48 20 1 44763 35509 41085
+49 20 1 45396 35509 41557
+50 20 1 46061 35509 41942
+51 20 1 46933 35509 42334
+52 20 1 47519 35509 42717
+53 20 1 48325 35509 43345
+54 20 1 49213 35509 43733
+55 20 1 49825 35509 44220
+56 20 1 50700 35509 44595
+57 20 1 51545 35509 45083
+58 20 1 52253 35509 45622
+59 20 1 52860 35509 45972
+60 20 1 53729 35509 46381
+61 20 1 54336 35509 46731
+62 20 1 55042 35509 47318
+63 20 1 55918 35509 47724
+0 21 1 8775 36591 20057
+1 21 1 9656 36591 20394
+2 21 1 10409 36591 20867
+3 21 1 11000 36591 21198
+4 21 1 11841 36591 21748
+5 21 1 12984 36572 22143
+6 21 1 11816 36678 22815
+7 21 1 11648 36735 23344
+8 21 1 12753 36728 23762
+9 21 1 13417 36728 24410
+10 21 1 14384 36728 24806
+11 21 1 15349 36728 25341
+12 21 1 16012 36728 25950
+13 21 1 16993 36728 26319
+14 21 1 17784 36728 26796
+15 21 1 18464 36728 27151
+16 21 1 19437 36728 27741
+17 21 1 20116 36728 28178
+18 21 1 20909 36728 28645
+19 21 1 21889 36728 29029
+20 21 1 22553 36728 29499
+21 21 1 23520 36728 30159
+22 21 1 24514 36728 30541
+23 21 1 25400 36732 31106
+24 21 1 26249 36690 31426
+25 21 1 27739 36570 31057
+26 21 1 28299 36591 31604
+27 21 1 28931 36591 31945
+28 21 1 29801 36591 32528
+29 21 1 30392 36591 32917
+30 21 1 31147 36591 33377
+31 21 1 32028 36591 33741
+32 21 1 32640 36591 34218
+33 21 1 33528 36591 34846
+34 21 1 34335 36591 35169
+35 21 1 34921 36591 35691
+36 21 1 35793 36591 36205
+37 21 1 36459 36591 36560
+38 21 1 37091 36591 36967
+39 21 1 37923 36591 37322
+40 21 1 38774 36591 37922
+41 21 1 39359 36591 38293
+42 21 1 40166 36591 38782
+43 21 1 41054 36591 39151
+44 21 1 41666 36591 39652
+45 21 1 42547 36591 40245
+46 21 1 43303 36591 40553
+47 21 1 43893 36591 41089
+48 21 1 44763 36591 41567
+49 21 1 45396 36591 41943
+50 21 1 46061 36591 42332
+51 21 1 46933 36591 42708
+52 21 1 47519 36591 43323
+53 21 1 48325 36591 43684
+54 21 1 49213 36591 44199
+55 21 1 49825 36591 44563
+56 21 1 50700 36591 45072
+57 21 1 51545 36591 45626
+58 21 1 52253 36591 45965
+59 21 1 52860 36591 46394
+60 21 1 53729 36591 46734
+61 21 1 54336 36591 47317
+62 21 1 55042 36591 47712
+63 21 1 55918 36591 48104
+0 22 1 8775 37694 20364
+1 22 1 9656 37694 20883
+2 22 1 10409 37694 21200
+3 22 1 11000 37694 21761
+4 22 1 11841 37694 22211
+5 22 1 12675 37694 22598
+6 22 1 13282 37694 22977
+7 22 1 13990 37694 23370
+8 22 1 14866 37694 23987
+9 22 1 15459 37694 24327
+10 22 1 16323 37694 24833
+11 22 1 17185 37694 25445
+12 22 1 17777 37694 25770
+13 22 1 18652 37694 26261
+14 22 1 19359 37694 26580
+15 22 1 19966 37694 27152
+16 22 1 20835 37694 27568
+17 22 1 21442 37694 27985
+18 22 1 22149 37694 28345
+19 22 1 23025 37694 28770
+20 22 1 23618 37694 29395
+21 22 1 24481 37694 29736
+22 22 1 25369 37694 30269
+23 22 1 26176 37694 30841
+24 22 1 26761 37694 31167
+25 22 1 27633 37694 31629
+26 22 1 28299 37694 31956
+27 22 1 28931 37694 32541
+28 22 1 29801 37694 32926
+29 22 1 30392 37694 33376
+30 22 1 31147 37694 33722
+31 22 1 32028 37694 34186
+32 22 1 32640 37694 34817
+33 22 1 33528 37694 35148
+34 22 1 34335 37694 35689
+35 22 1 34921 37694 36220
+36 22 1 35793 37694 36558
+37 22 1 36459 37694 36991
+38 22 1 37091 37694 37333
+39 22 1 37923 37694 37929
+40 22 1 38774 37694 38291
+41 22 1 39359 37694 38774
+42 22 1 40166 37694 39114
+43 22 1 41054 37694 39620
+44 22 1 41666 37694 40234
+45 22 1 42547 37694 40545
+46 22 1 43303 37694 41096
+47 22 1 43893 37694 41587
+48 22 1 44763 37694 41945
+49 22 1 45396 37694 42351
+50 22 1 46061 37694 42714
+51 22 1 46933 37694 43321
+52 22 1 47519 37694 43666
+53 22 1 48325 37694 44180
+54 22 1 49213 37694 44524
+55 22 1 49825 37694 45057
+56 22 1 50700 37694 45630
+57 22 1 51545 37694 45955
+58 22 1 52253 37694 46419
+59 22 1 52860 37694 46746
+60 22 1 53729 37694 47330
+61 22 1 54336 37694 47721
+62 22 1 55042 37694 48103
+63 22 1 55918 37694 48720
+0 23 1 8775 38828 20895
+1 23 1 9656 38828 21205
+2 23 1 10409 38828 21781
+3 23 1 11000 38828 22245
+4 23 1 11841 38828 22608
+5 23 1 12675 38828 23011
+6 23 1 13282 38828 23389
+7 23 1 13990 38828 23997
+8 23 1 14866 38828 24323
+9 23 1 15459 38828 24814
+10 23 1 16323 38828 25430
+11 23 1 17185 38828 25753
+12 23 1 17777 38828 26287
+13 23 1 18652 38828 26594
+14 23 1 19359 38828 27178
+15 23 1 19966 38828 27603
+16 23 1 20835 38828 27997
+17 23 1 21442 38828 28370
+18 23 1 22149 38828 28781
+19 23 1 23025 38828 29393
+20 23 1 23618 38828 29714
+21 23 1 24481 38828 30250
+22 23 1 25369 38828 30844
+23 23 1 26176 38828 31160
+24 23 1 26761 38828 31664
+25 23 1 27633 38828 31977
+26 23 1 28299 38828 32568
+27 23 1 28931 38828 32958
+28 23 1 29801 38828 33387
+29 23 1 30392 38828 33735
+30 23 1 31147 38828 34184
+31 23 1 32028 38828 34799
+32 23 1 32640 38828 35124
+33 23 1 33528 38828 35687
+34 23 1 34335 38828 36237
+35 23 1 34921 38828 36559
+36 23 1 35793 38828 37030
+37 23 1 36459 38828 37357
+38 23 1 37091 38828 37954
+39 23 1 37923 38828 38315
+40 23 1 38774 38828 38780
+41 23 1 39359 38828 39111
+42 23 1 40166 38828 39601
+43 23 1 41054 38828 40218
+44 23 1 41666 38828 40538
+45 23 1 42547 38828 41107
+46 23 1 43303 38828 41614
+47 23 1 43893 38828 41952
+48 23 1 44763 38828 42389
+49 23 1 45396 38828 42738
+50 23 1 46061 38828 43341
+51 23 1 46933 38828 43679
+52 23 1 47519 38828 44179
+53 23 1 48325 38828 44502
+54 23 1 49213 38828 45037
+55 23 1 49825 38828 45633
+56 23 1 50700 38828 45949
+57 23 1 51545 38828 46454
+58 23 1 52253 38828 46767
+59 23 1 52860 38828 47357
+60 23 1 53729 38828 47751
+61 23 1 54336 38828 48122
+62 23 1 55042 38828 48730
+63 23 1 55918 38828 49054
+0 24 1 8775 40003 21216
+1 24 1 9656 40003 21809
+2 24 1 10409 40003 22290
+3 24 1 11000 40003 22631
+4 24 1 11841 40003 23066
+5 24 1 12675 40003 23429
+6 24 1 13282 40003 24032
+7 24 1 13990 40003 24354
+8 24 1 14866 40003 24828
+9 24 1 15459 40003 25427
+10 24 1 16323 40003 25745
+11 24 1 17185 40003 26312
+12 24 1 17777 40003 26618
+13 24 1 18652 40003 27214
+14 24 1 19359 40003 27654
+15 24 1 19966 40003 28021
+16 24 1 20835 40003 28421
+17 24 1 21442 40003 28816
+18 24 1 22149 40003 29419
+19 24 1 23025 40003 29730
+20 24 1 23618 40003 30247
+21 24 1 24481 40003 30844
+22 24 1 25369 40003 31162
+23 24 1 26176 40003 31703
+24 24 1 26761 40003 32010
+25 24 1 27633 40003 32609
+26 24 1 28299 40003 33011
+27 24 1 28931 40003 33411
+28 24 1 29801 40003 33779
+29 24 1 30392 40003 34211
+30 24 1 31147 40003 34812
+31 24 1 32028 40003 35121
+32 24 1 32640 40003 35684
+33 24 1 33528 40003 36256
+34 24 1 34335 40003 36570
+35 24 1 34921 40003 37079
+36 24 1 35793 40003 37396
+37 24 1 36459 40003 37997
+38 24 1 37091 40003 38366
+39 24 1 37923 40003 38801
+40 24 1 38774 40003 39143
+41 24 1 39359 40003 39616
+42 24 1 40166 40003 40216
+43 24 1 41054 40003 40533
+44 24 1 41666 40003 41121
+45 24 1 42547 40003 41648
+46 24 1 43303 40003 41970
+47 24 1 43893 40003 42443
+48 24 1 44763 40003 42779
+49 24 1 45396 40003 43382
+50 24 1 46061 40003 43724
+51 24 1 46933 40003 44195
+52 24 1 47519 40003 44519
+53 24 1 48325 40003 45035
+54 24 1 49213 40003 45632
+55 24 1 49825 40003 45951
+56 24 1 50700 40003 46492
+57 24 1 51545 40003 46800
+58 24 1 52253 40003 47398
+59 24 1 52860 40003 47802
+60 24 1 53729 40003 48163
+61 24 1 54336 40003 48765
+62 24 1 55042 40003 49088
+63 24 1 55918 40003 49595
+0 25 1 8775 41193 21830
+1 25 1 9656 41193 22329
+2 25 1 10409 41193 22657
+3 25 1 11000 41193 23124
+4 25 1 11841 41193 23478
+5 25 1 12675 41193 24078
+6 25 1 13282 41193 24403
+7 25 1 13990 41193 24861
+8 25 1 14866 41193 25445
+9 25 1 15459 41193 25752
+10 25 1 16323 41193 26316
+11 25 1 17185 41193 26638
+12 25 1 17777 41193 27248
+13 25 1 18652 41193 27704
+14 25 1 19359 41193 28050
+15 25 1 19966 41193 28481
+16 25 1 20835 41193 28864
+17 25 1 21442 41193 29459
+18 25 1 22149 41193 29768
+19 25 1 23025 41193 30268
+20 25 1 23618 41193 30846
+21 25 1 24481 41193 31165
+22 25 1 25369 41193 31728
+23 25 1 26176 41193 32042
+24 25 1 26761 41193 32652
+25 25 1 27633 41193 33069
+26 25 1 28299 41193 33440
+27 25 1 28931 41193 33836
+28 25 1 29801 41193 34254
+29 25 1 30392 41193 34843
+30 25 1 31147 41193 35145
+31 25 1 32028 41193 35688
+32 25 1 32640 41193 36260
+33 25 1 33528 41193 36583
+34 25 1 34335 41193 37119
+35 25 1 34921 41193 37438
+36 25 1 35793 41193 38045
+37 25 1 36459 41193 38427
+38 25 1 37091 41193 38829
+39 25 1 37923 41193 39194
+40 25 1 38774 41193 39650
+41 25 1 39359 41193 40234
+42 25 1 40166 41193 40538
+43 25 1 41054 41193 41125
+44 25 1 41666 41193 41671
+45 25 1 42547 41193 41990
+46 25 1 43303 41193 42495
+47 25 1 43893 41193 42826
+48 25 1 44763 41193 43430
+49 25 1 45396 41193 43783
+50 25 1 46061 41193 44219
+51 25 1 46933 41193 44559
+52 25 1 47519 41193 45056
+53 25 1 48325 41193 45634
+54 25 1 49213 41193 45953
+55 25 1 49825 41193 46517
+56 25 1 50700 41193 46831
+57 25 1 51545 41193 47441
+58 25 1 52253 41193 47856
+59 25 1 52860 41193 48212
+60 25 1 53729 41193 48812
+61 25 1 54336 41193 49142
+62 25 1 55042 41193 49613
+63 25 1 55918 41193 49935
+0 26 1 8775 42386 22333
+1 26 1 9656 42386 22667
+2 26 1 10409 42386 23155
+3 26 1 11000 42386 23509
+4 26 1 11841 42386 24108
+5 26 1 12675 42386 24440
+6 26 1 13282 42386 24887
+7 26 1 13990 42386 25456
+8 26 1 14866 42386 25769
+9 26 1 15459 42386 26294
+10 26 1 16323 42386 26625
+11 26 1 17185 42386 27252
+12 26 1 17777 42386 27724
+13 26 1 18652 42386 28065
+14 26 1 19359 42386 28520
+15 26 1 19966 42386 28897
+16 26 1 20835 42386 29487
+17 26 1 21442 42386 29800
+18 26 1 22149 42386 30285
+19 26 1 23025 42386 30844
+20 26 1 23618 42386 31162
+21 26 1 24481 42386 31711
+22 26 1 25369 42386 32046
+23 26 1 26176 42386 32668
+24 26 1 26761 42386 33101
+25 26 1 27633 42386 33456
+26 26 1 28299 42386 33877
+27 26 1 28931 42386 34285
+28 26 1 29801 42386 34865
+29 26 1 30392 42386 35167
+30 26 1 31147 42386 35692
+31 26 1 32028 42386 36242
+32 26 1 32640 42386 36575
+33 26 1 33528 42386 37123
+34 26 1 34335 42386 37453
+35 26 1 34921 42386 38069
+36 26 1 35793 42386 38467
+37 26 1 36459 42386 38845
+38 26 1 37091 42386 39233
+39 26 1 37923 42386 39676
+40 26 1 38774 42386 40246
+41 26 1 39359 42386 40546
+42 26 1 40166 42386 41112
+43 26 1 41054 42386 41655
+44 26 1 41666 42386 41993
+45 26 1 42547 42386 42514
+46 26 1 43303 42386 42851
+47 26 1 43893 42386 43459
+48 26 1 44763 42386 43827
+49 26 1 45396 42386 44232
+50 26 1 46061 42386 44592
+51 26 1 46933 42386 45073
+52 26 1 47519 42386 45633
+53 26 1 48325 42386 45951
+54 26 1 49213 42386 46499
+55 26 1 49825 42386 46834
+56 26 1 50700 42386 47457
+57 26 1 51545 42386 47885
+58 26 1 52253 42386 48242
+59 26 1 52860 42386 48842
+60 26 1 53729 42386 49186
+61 26 1 54336 42386 49620
+62 26 1 55042 42386 49958
+63 26 1 55918 42386 50480
+0 27 1 8775 43748 22662
+1 27 1 9656 43748 23158
+2 27 1 10409 43748 23521
+3 27 1 11000 43748 24124
+4 27 1 11841 43748 24464
+5 27 1 12675 43748 24904
+6 27 1 13282 43748 25461
+7 27 1 13990 43748 25787
+8 27 1 14866 43748 26268
+9 27 1 15459 43748 26601
+10 27 1 16323 43748 27225
+11 27 1 17185 43748 27711
+12 27 1 17777 43748 28067
+13 27 1 18652 43748 28534
+14 27 1 19359 43748 28914
+15 27 1 19966 43748 29503
+16 27 1 20835 43748 29822
+17 27 1 21442 43748 30297
+18 27 1 22149 43748 30839
+19 27 1 23025 43748 31168
+20 27 1 23618 43748 31673
+21 27 1 24481 43748 32020
+22 27 1 25369 43748 32658
+23 27 1 26176 43748 33103
+24 27 1 26761 43748 33462
+25 27 1 27633 43748 33899
+26 27 1 28299 43748 34304
+27 27 1 28931 43748 34878
+28 27 1 29801 43748 35184
+29 27 1 30392 43748 35695
+30 27 1 31147 43748 36224
+31 27 1 32028 43748 36563
+32 27 1 32640 43748 37089
+33 27 1 33528 43748 37443
+34 27 1 34335 43748 38072
+35 27 1 34921 43748 38482
+36 27 1 35793 43748 38852
+37 27 1 36459 43748 39259
+38 27 1 37091 43748 39693
+39 27 1 37923 43748 40252
+40 27 1 38774 43748 40554
+41 27 1 39359 43748 41102
+42 27 1 40166 43748 41621
+43 27 1 41054 43748 41977
+44 27 1 41666 43748 42500
+45 27 1 42547 43748 42854
+46 27 1 43303 43748 43470
+47 27 1 43893 43748 43851
+48 27 1 44763 43748 44237
+49 27 1 45396 43748 44616
+50 27 1 46061 43748 45085
+51 27 1 46933 43748 45628
+52 27 1 47519 43748 45957
+53 27 1 48325 43748 46461
+54 27 1 49213 43748 46808
+55 27 1 49825 43748 47446
+56 27 1 50700 43748 47887
+57 27 1 51545 43748 48254
+58 27 1 52253 43748 48857
+59 27 1 52860 43748 49215
+60 27 1 53729 43748 49621
+61 27 1 54336 43748 49977
+62 27 1 55042 43748 50483
+63 27 1 55918 43748 51013
+0 28 1 8775 44801 23132
+1 28 1 9656 44801 23514
+2 28 1 10409 44801 24126
+3 28 1 11000 44801 24473
+4 28 1 11841 44801 24914
+5 28 1 12675 44801 25463
+6 28 1 13282 44801 25803
+7 28 1 13990 44801 26245
+8 28 1 14866 44801 26585
+9 28 1 15459 44801 27188
+10 28 1 16323 44801 27666
+11 28 1 17185 44801 28056
+12 28 1 17777 44801 28524
+13 28 1 18652 44801 28916
+14 28 1 19359 44801 29509
+15 28 1 19966 44801 29834
+16 28 1 20835 44801 30304
+17 28 1 21442 44801 30834
+18 28 1 22149 44801 31178
+19 28 1 23025 44801 31637
+20 28 1 23618 44801 31986
+21 28 1 24481 44801 32622
+22 28 1 25369 44801 33076
+23 28 1 26176 44801 33460
+24 28 1 26761 44801 33901
+25 28 1 27633 44801 34311
+26 28 1 28299 44801 34884
+27 28 1 28931 44801 35196
+28 28 1 29801 44801 35697
+29 28 1 30392 44801 36209
+30 28 1 31147 44801 36561
+31 28 1 32028 44801 37039
+32 28 1 32640 44801 37408
+33 28 1 33528 44801 38052
+34 28 1 34335 44801 38471
+35 28 1 34921 44801 38853
+36 28 1 35793 44801 39268
+37 28 1 36459 44801 39703
+38 28 1 37091 44801 40256
+39 28 1 37923 44801 40560
+40 28 1 38774 44801 41095
+41 28 1 39359 44801 41592
+42 28 1 40166 44801 41958
+43 28 1 41054 44801 42454
+44 28 1 41666 44801 42834
+45 28 1 42547 44801 43463
+46 28 1 43303 44801 43853
+47 28 1 43893 44801 44239
+48 28 1 44763 44801 44629
+49 28 1 45396 44801 45093
+50 28 1 46061 44801 45624
+51 28 1 46933 44801 45967
+52 28 1 47519 44801 46426
+53 28 1 48325 44801 46774
+54 28 1 49213 44801 47409
+55 28 1 49825 44801 47862
+56 28 1 50700 44801 48247
+57 28 1 51545 44801 48858
+58 28 1 52253 44801 49226
+59 28 1 52860 44801 49621
+60 28 1 53729 44801 49990
+61 28 1 54336 44801 50486
+62 28 1 55042 44801 50999
+63 28 1 55918 44801 51350
+0 29 1 8775 45874 23489
+1 29 1 9656 45874 24114
+2 29 1 10409 45874 24466
+3 29 1 11000 45874 24915
+4 29 1 11841 45874 25464
+5 29 1 12675 45874 25813
+6 29 1 13282 45874 26228
+7 29 1 13990 45874 26576
+8 29 1 14866 45874 27162
+9 29 1 15459 45874 27612
+10 29 1 16323 45874 28032
+11 29 1 17185 45874 28490
+12 29 1 17777 45874 28903
+13 29 1 18652 45874 29506
+14 29 1 19359 45874 29835
+15 29 1 19966 45874 30307
+16 29 1 20835 45874 30831
+17 29 1 21442 45874 31186
+18 29 1 22149 45874 31610
+19 29 1 23025 45874 31963
+20 29 1 23618 45874 32579
+21 29 1 24481 45874 33023
+22 29 1 25369 45874 33448
+23 29 1 26176 45874 33882
+24 29 1 26761 45874 34308
+25 29 1 27633 45874 34885
+26 29 1 28299 45874 35200
+27 29 1 28931 45874 35698
+28 29 1 29801 45874 36199
+29 29 1 30392 45874 36564
+30 29 1 31147 45874 36998
+31 29 1 32028 45874 37367
+32 29 1 32640 45874 38011
+33 29 1 33528 45874 38435
+34 29 1 34335 45874 38849
+35 29 1 34921 45874 39261
+36 29 1 35793 45874 39704
+37 29 1 36459 45874 40257
+38 29 1 37091 45874 40564
+39 29 1 37923 45874 41091
+40 29 1 38774 45874 41571
+41 29 1 39359 45874 41951
+42 29 1 40166 45874 42398
+43 29 1 41054 45874 42792
+44 29 1 41666 45874 43439
+45 29 1 42547 45874 43832
+46 29 1 43303 45874 44239
+47 29 1 43893 45874 44630
+48 29 1 44763 45874 45096
+49 29 1 45396 45874 45621
+50 29 1 46061 45874 45975
+51 29 1 46933 45874 46399
+52 29 1 47519 45874 46752
+53 29 1 48325 45874 47367
+54 29 1 49213 45874 47811
+55 29 1 49825 45874 48221
+56 29 1 50700 45874 48846
+57 29 1 51545 45874 49217
+58 29 1 52253 45874 49622
+59 29 1 52860 45874 49994
+60 29 1 53729 45874 50487
+61 29 1 54336 45874 50988
+62 29 1 55042 45874 51353
+63 29 1 55918 45874 51787
+0 30 1 8775 46943 24088
+1 30 1 9656 46943 24445
+2 30 1 10409 46943 24910
+3 30 1 11000 46943 25465
+4 30 1 11841 46943 25814
+5 30 1 12675 46943 26222
+6 30 1 13282 46943 26572
+7 30 1 13990 46943 27147
+8 30 1 14866 46943 27574
+9 30 1 15459 46943 28007
+10 30 1 16323 46943 28434
+11 30 1 17185 46943 28876
+12 30 1 17777 46943 29493
+13 30 1 18652 46943 29825
+14 30 1 19359 46943 30307
+15 30 1 19966 46943 30832
+16 30 1 20835 46943 31189
+17 30 1 21442 46943 31595
+18 30 1 22149 46943 31951
+19 30 1 23025 46943 32551
+20 30 1 23618 46943 32967
+21 30 1 24481 46943 33423
+22 30 1 25369 46943 33844
+23 30 1 26176 46943 34293
+24 30 1 26761 46943 34881
+25 30 1 27633 46943 35197
+26 30 1 28299 46943 35700
+27 30 1 28931 46943 36195
+28 30 1 29801 46943 36566
+29 30 1 30392 46943 36972
+30 30 1 31147 46943 37341
+31 30 1 32028 46943 37966
+32 30 1 32640 46943 38378
+33 30 1 33528 46943 38838
+34 30 1 34335 46943 39238
+35 30 1 34921 46943 39698
+36 30 1 35793 46943 40257
+37 30 1 36459 46943 40564
+38 30 1 37091 46943 41091
+39 30 1 37923 46943 41559
+40 30 1 38774 46943 41948
+41 30 1 39359 46943 42357
+42 30 1 40166 46943 42749
+43 30 1 41054 46943 43395
+44 30 1 41666 46943 43791
+45 30 1 42547 46943 44237
+46 30 1 43303 46943 44619
+47 30 1 43893 46943 45096
+48 30 1 44763 46943 45621
+49 30 1 45396 46943 45979
+50 30 1 46061 46943 46384
+51 30 1 46933 46943 46740
+52 30 1 47519 46943 47340
+53 30 1 48325 46943 47758
+54 30 1 49213 46943 48176
+55 30 1 49825 46943 48820
+56 30 1 50700 46943 49191
+57 30 1 51545 46943 49624
+58 30 1 52253 46943 49991
+59 30 1 52860 46943 50489
+60 30 1 53729 46943 50985
+61 30 1 54336 46943 51356
+62 30 1 55042 46943 51761
+63 30 1 55918 46943 52130
+0 31 1 8775 48017 24412
+1 31 1 9656 48017 24897
+2 31 1 10409 48017 25464
+3 31 1 11000 48017 25807
+4 31 1 11841 48017 26229
+5 31 1 12675 48017 26572
+6 31 1 13282 48017 27143
+7 31 1 13990 48017 27554
+8 31 1 14866 48017 27995
+9 31 1 15459 48017 28380
+10 31 1 16323 48017 28834
+11 31 1 17185 48017 29470
+12 31 1 17777 48017 29805
+13 31 1 18652 48017 30304
+14 31 1 19359 48017 30836
+15 31 1 19966 48017 31188
+16 31 1 20835 48017 31595
+17 31 1 21442 48017 31947
+18 31 1 22149 48017 32538
+19 31 1 23025 48017 32932
+20 31 1 23618 48017 33398
+21 31 1 24481 48017 33791
+22 31 1 25369 48017 34267
+23 31 1 26176 48017 34871
+24 31 1 26761 48017 35188
+25 31 1 27633 48017 35702
+26 31 1 28299 48017 36200
+27 31 1 28931 48017 36568
+28 31 1 29801 48017 36963
+29 31 1 30392 48017 37330
+30 31 1 31147 48017 37941
+31 31 1 32028 48017 38324
+32 31 1 32640 48017 38815
+33 31 1 33528 48017 39202
+34 31 1 34335 48017 39685
+35 31 1 34921 48017 40256
+36 31 1 35793 48017 40562
+37 31 1 36459 48017 41094
+38 31 1 37091 48017 41560
+39 31 1 37923 48017 41949
+40 31 1 38774 48017 42336
+41 31 1 39359 48017 42724
+42 31 1 40166 48017 43353
+43 31 1 41054 48017 43735
+44 31 1 41666 48017 44229
+45 31 1 42547 48017 44597
+46 31 1 43303 48017 45092
+47 31 1 43893 48017 45625
+48 31 1 44763 48017 45977
+49 31 1 45396 48017 46385
+50 31 1 46061 48017 46737
+51 31 1 46933 48017 47327
+52 31 1 47519 48017 47726
+53 31 1 48325 48017 48134
+54 31 1 49213 48017 48779
+55 31 1 49825 48017 49149
+56 31 1 50700 48017 49626
+57 31 1 51545 48017 49980
+58 31 1 52253 48017 50490
+59 31 1 52860 48017 50989
+60 31 1 53729 48017 51357
+61 31 1 54336 48017 51753
+62 31 1 55042 48017 52119
+63 31 1 55918 48017 52729
+0 32 1 8775 49106 24878
+1 32 1 9656 49106 25463
+2 32 1 10409 49106 25794
+3 32 1 11000 49106 26248
+4 32 1 11841 49106 26579
+5 32 1 12675 49106 27151
+6 32 1 13282 49106 27555
+7 32 1 13990 49106 27993
+8 32 1 14866 49106 28352
+9 32 1 15459 49106 28797
+10 32 1 16323 49106 29436
+11 32 1 17185 49106 29778
+12 32 1 17777 49106 30297
+13 32 1 18652 49106 30843
+14 32 1 19359 49106 31182
+15 32 1 19966 49106 31612
+16 32 1 20835 49106 31953
+17 32 1 21442 49106 32541
+18 32 1 22149 49106 32921
+19 32 1 23025 49106 33389
+20 32 1 23618 49106 33745
+21 32 1 24481 49106 34231
+22 32 1 25369 49106 34855
+23 32 1 26176 49106 35174
+24 32 1 26761 49106 35704
+25 32 1 27633 49106 36212
+26 32 1 28299 49106 36567
+27 32 1 28931 49106 36974
+28 32 1 29801 49106 37332
+29 32 1 30392 49106 37934
+30 32 1 31147 49106 38297
+31 32 1 32028 49106 38794
+32 32 1 32640 49106 39156
+33 32 1 33528 49106 39666
+34 32 1 34335 49106 40253
+35 32 1 34921 49106 40559
+36 32 1 35793 49106 41102
+37 32 1 36459 49106 41573
+38 32 1 37091 49106 41951
+39 32 1 37923 49106 42338
+40 32 1 38774 49106 42718
+41 32 1 39359 49106 43334
+42 32 1 40166 49106 43689
+43 32 1 41054 49106 44211
+44 32 1 41666 49106 44567
+45 32 1 42547 49106 45085
+46 32 1 43303 49106 45633
+47 32 1 43893 49106 45972
+48 32 1 44763 49106 46402
+49 32 1 45396 49106 46743
+50 32 1 46061 49106 47329
+51 32 1 46933 49106 47716
+52 32 1 47519 49106 48116
+53 32 1 48325 49106 48744
+54 32 1 49213 49106 49099
+55 32 1 49825 49106 49624
+56 32 1 50700 49106 49964
+57 32 1 51545 49106 50493
+58 32 1 52253 49106 51002
+59 32 1 52860 49106 51357
+60 32 1 53729 49106 51764
+61 32 1 54336 49106 52121
+62 32 1 55042 49106 52723
+63 32 1 55918 49106 53091
+0 33 1 8775 50222 25459
+1 33 1 9656 50222 25780
+2 33 1 10409 50222 26276
+3 33 1 11000 50222 26592
+4 33 1 11841 50222 27171
+5 33 1 12675 50222 27578
+6 33 1 13282 50222 27999
+7 33 1 13990 50222 28355
+8 33 1 14866 50222 28788
+9 33 1 15459 50222 29411
+10 33 1 16323 50222 29746
+11 33 1 17185 50222 30289
+12 33 1 17777 50222 30853
+13 33 1 18652 50222 31178
+14 33 1 19359 50222 31643
+15 33 1 19966 50222 31970
+16 33 1 20835 50222 32559
+17 33 1 21442 50222 32935
+18 33 1 22149 50222 33391
+19 33 1 23025 50222 33732
+20 33 1 23618 50222 34205
+21 33 1 24481 50222 34832
+22 33 1 25369 50222 35158
+23 33 1 26176 50222 35709
+24 33 1 26761 50222 36231
+25 33 1 27633 50222 36569
+26 33 1 28299 50222 37004
+27 33 1 28931 50222 37348
+28 33 1 29801 50222 37947
+29 33 1 30392 50222 38300
+30 33 1 31147 50222 38790
+31 33 1 32028 50222 39123
+32 33 1 32640 50222 39640
+33 33 1 33528 50222 40248
+34 33 1 34335 50222 40556
+35 33 1 34921 50222 41116
+36 33 1 35793 50222 41598
+37 33 1 36459 50222 41957
+38 33 1 37091 50222 42363
+39 33 1 37923 50222 42730
+40 33 1 38774 50222 43338
+41 33 1 39359 50222 43675
+42 33 1 40166 50222 44196
+43 33 1 41054 50222 44534
+44 33 1 41666 50222 45076
+45 33 1 42547 50222 45643
+46 33 1 43303 50222 45966
+47 33 1 43893 50222 46433
+48 33 1 44763 50222 46759
+49 33 1 45396 50222 47348
+50 33 1 46061 50222 47730
+51 33 1 46933 50222 48119
+52 33 1 47519 50222 48736
+53 33 1 48325 50222 49066
+54 33 1 49213 50222 49614
+55 33 1 49825 50222 49947
+56 33 1 50700 50222 50497
+57 33 1 51545 50222 51021
+58 33 1 52253 50222 51358
+59 33 1 52860 50222 51794
+60 33 1 53729 50222 52137
+61 33 1 54336 50222 52736
+62 33 1 55042 50222 53094
+63 33 1 55918 50222 53521
+0 34 1 8775 51376 25772
+1 34 1 9656 51376 26309
+2 34 1 10409 51376 26615
+3 34 1 11000 51376 27205
+4 34 1 11841 51376 27620
+5 34 1 12675 51376 28018
+6 34 1 13282 51376 28388
+7 34 1 13990 51376 28807
+8 34 1 14866 51376 29416
+9 34 1 15459 51376 29731
+10 34 1 16323 51376 30278
+11 34 1 17185 51376 30864
+12 34 1 17777 51376 31179
+13 34 1 18652 51376 31685
+14 34 1 19359 51376 31999
+15 34 1 19966 51376 32594
+16 34 1 20835 51376 32976
+17 34 1 21442 51376 33408
+18 34 1 22149 51376 33753
+19 34 1 23025 51376 34211
+20 34 1 23618 51376 34821
+21 34 1 24481 51376 35142
+22 34 1 25369 51376 35715
+23 34 1 26176 51376 36257
+24 34 1 26761 51376 36578
+25 34 1 27633 51376 37050
+26 34 1 28299 51376 37380
+27 34 1 28931 51376 37980
+28 34 1 29801 51376 38333
+29 34 1 30392 51376 38801
+30 34 1 31147 51376 39129
+31 34 1 32028 51376 39630
+32 34 1 32640 51376 40239
+33 34 1 33528 51376 40557
+34 34 1 34335 51376 41135
+35 34 1 34921 51376 41633
+36 34 1 35793 51376 41972
+37 34 1 36459 51376 42409
+38 34 1 37091 51376 42762
+39 34 1 37923 51376 43365
+40 34 1 38774 51376 43697
+41 34 1 39359 51376 44201
+42 34 1 40166 51376 44520
+43 34 1 41054 51376 45066
+44 34 1 41666 51376 45653
+45 34 1 42547 51376 45968
+46 34 1 43303 51376 46475
+47 34 1 43893 51376 46788
+48 34 1 44763 51376 47383
+49 34 1 45396 51376 47768
+50 34 1 46061 51376 48147
+51 34 1 46933 51376 48754
+52 34 1 47519 51376 49072
+53 34 1 48325 51376 49608
+54 34 1 49213 51376 49930
+55 34 1 49825 51376 50503
+56 34 1 50700 51376 51046
+57 34 1 51545 51376 51367
+58 34 1 52253 51376 51840
+59 34 1 52860 51376 52169
+60 34 1 53729 51376 52769
+61 34 1 54336 51376 53125
+62 34 1 55042 51376 53540
+63 34 1 55918 51376 54149
+0 35 1 8775 52576 26341
+1 35 1 9656 52576 26650
+2 35 1 10409 52576 27252
+3 35 1 11000 52576 27681
+4 35 1 11841 52576 28051
+5 35 1 12675 52576 28449
+6 35 1 13282 52576 28853
+7 35 1 13990 52576 29451
+8 35 1 14866 52576 29757
+9 35 1 15459 52576 30287
+10 35 1 16323 52576 30873
+11 35 1 17185 52576 31192
+12 35 1 17777 52576 31732
+13 35 1 18652 52576 32043
+14 35 1 19359 52576 32646
+15 35 1 19966 52576 33039
+16 35 1 20835 52576 33440
+17 35 1 21442 52576 33806
+18 35 1 22149 52576 34249
+19 35 1 23025 52576 34844
+20 35 1 23618 52576 35149
+21 35 1 24481 52576 35724
+22 35 1 25369 52576 36285
+23 35 1 26176 52576 36600
+24 35 1 26761 52576 37107
+25 35 1 27633 52576 37430
+26 35 1 28299 52576 38033
+27 35 1 28931 52576 38394
+28 35 1 29801 52576 38831
+29 35 1 30392 52576 39171
+30 35 1 31147 52576 39655
+31 35 1 32028 52576 40246
+32 35 1 32640 52576 40562
+33 35 1 33528 52576 41161
+34 35 1 34335 52576 41676
+35 35 1 34921 52576 41999
+36 35 1 35793 52576 42471
+37 35 1 36459 52576 42814
+38 35 1 37091 52576 43416
+39 35 1 37923 52576 43752
+40 35 1 38774 52576 44225
+41 35 1 39359 52576 44547
+42 35 1 40166 52576 45074
+43 35 1 41054 52576 45661
+44 35 1 41666 52576 45980
+45 35 1 42547 52576 46521
+46 35 1 43303 52576 46832
+47 35 1 43893 52576 47435
+48 35 1 44763 52576 47828
+49 35 1 45396 52576 48199
+50 35 1 46061 52576 48799
+51 35 1 46933 52576 49116
+52 35 1 47519 52576 49624
+53 35 1 48325 52576 49938
+54 35 1 49213 52576 50511
+55 35 1 49825 52576 51073
+56 35 1 50700 52576 51388
+57 35 1 51545 52576 51897
+58 35 1 52253 52576 52219
+59 35 1 52860 52576 52822
+60 35 1 53729 52576 53183
+61 35 1 54336 52576 53587
+62 35 1 55042 52576 54185
+63 35 1 55918 52576 54492
+0 36 1 8775 53754 26665
+1 36 1 9656 53754 27282
+2 36 1 10409 53754 27726
+3 36 1 11000 53754 28076
+4 36 1 11841 53754 28503
+5 36 1 12675 53754 28897
+6 36 1 13282 53754 29486
+7 36 1 13990 53754 29791
+8 36 1 14866 53754 30303
+9 36 1 15459 53754 30868
+10 36 1 16323 53754 31193
+11 36 1 17185 53754 31750
+12 36 1 17777 53754 32071
+13 36 1 18652 53754 32684
+14 36 1 19359 53754 33091
+15 36 1 19966 53754 33466
+16 36 1 20835 53754 33858
+17 36 1 21442 53754 34288
+18 36 1 22149 53754 34868
+19 36 1 23025 53754 35169
+20 36 1 23618 53754 35724
+21 36 1 24481 53754 36282
+22 36 1 25369 53754 36611
+23 36 1 26176 53754 37141
+24 36 1 26761 53754 37467
+25 36 1 27633 53754 38076
+26 36 1 28299 53754 38450
+27 36 1 28931 53754 38854
+28 36 1 29801 53754 39216
+29 36 1 30392 53754 39685
+30 36 1 31147 53754 40258
+31 36 1 32028 53754 40563
+32 36 1 32640 53754 41160
+33 36 1 33528 53754 41693
+34 36 1 34335 53754 42018
+35 36 1 34921 53754 42516
+36 36 1 35793 53754 42857
+37 36 1 36459 53754 43459
+38 36 1 37091 53754 43807
+39 36 1 37923 53754 44243
+40 36 1 38774 53754 44582
+41 36 1 39359 53754 45091
+42 36 1 40166 53754 45657
+43 36 1 41054 53754 45981
+44 36 1 41666 53754 46538
+45 36 1 42547 53754 46860
+46 36 1 43303 53754 47473
+47 36 1 43893 53754 47877
+48 36 1 44763 53754 48244
+49 36 1 45396 53754 48839
+50 36 1 46061 53754 49167
+51 36 1 46933 53754 49636
+52 36 1 47519 53754 49959
+53 36 1 48325 53754 50511
+54 36 1 49213 53754 51070
+55 36 1 49825 53754 51399
+56 36 1 50700 53754 51930
+57 36 1 51545 53754 52256
+58 36 1 52253 53754 52865
+59 36 1 52860 53754 53234
+60 36 1 53729 53754 53631
+61 36 1 54336 53754 54219
+62 36 1 55042 53754 54533
+63 36 1 55918 53754 55035
+0 37 1 8775 54890 27281
+1 37 1 9656 54890 27738
+2 37 1 10409 54890 28088
+3 37 1 11000 54890 28534
+4 37 1 11841 54890 28926
+5 37 1 12675 54890 29508
+6 37 1 13282 54890 29817
+7 37 1 13990 54890 30315
+8 37 1 14866 54890 30860
+9 37 1 15459 54890 31188
+10 37 1 16323 54890 31726
+11 37 1 17185 54890 32070
+12 37 1 17777 54890 32696
+13 37 1 18652 54890 33116
+14 37 1 19359 54890 33479
+15 37 1 19966 54890 33893
+16 37 1 20835 54890 34314
+17 37 1 21442 54890 34884
+18 37 1 22149 54890 35185
+19 37 1 23025 54890 35723
+20 37 1 23618 54890 36257
+21 37 1 24481 54890 36601
+22 37 1 25369 54890 37137
+23 37 1 26176 54890 37479
+24 37 1 26761 54890 38095
+25 37 1 27633 54890 38484
+26 37 1 28299 54890 38866
+27 37 1 28931 54890 39249
+28 37 1 29801 54890 39706
+29 37 1 30392 54890 40263
+30 37 1 31147 54890 40565
+31 37 1 32028 54890 41143
+32 37 1 32640 54890 41670
+33 37 1 33528 54890 42018
+34 37 1 34335 54890 42528
+35 37 1 34921 54890 42878
+36 37 1 35793 54890 43484
+37 37 1 36459 54890 43846
+38 37 1 37091 54890 44251
+39 37 1 37923 54890 44609
+40 37 1 38774 54890 45104
+41 37 1 39359 54890 45648
+42 37 1 40166 54890 45976
+43 37 1 41054 54890 46514
+44 37 1 41666 54890 46858
+45 37 1 42547 54890 47485
+46 37 1 43303 54890 47899
+47 37 1 43893 54890 48270
+48 37 1 44763 54890 48864
+49 37 1 45396 54890 49206
+50 37 1 46061 54890 49638
+51 37 1 46933 54890 49976
+52 37 1 47519 54890 50511
+53 37 1 48325 54890 51046
+54 37 1 49213 54890 51389
+55 37 1 49825 54890 51926
+56 37 1 50700 54890 52267
+57 37 1 51545 54890 52885
+58 37 1 52253 54890 53264
+59 37 1 52860 54890 53659
+60 37 1 53729 54890 54240
+61 37 1 54336 54890 54568
+62 37 1 55042 54890 55028
+63 37 1 55918 54890 55355
+0 38 1 8775 55993 27719
+1 38 1 9656 55993 28089
+2 38 1 10409 55993 28542
+3 38 1 11000 55993 28939
+4 38 1 11841 55993 29520
+5 38 1 12675 55993 29834
+6 38 1 13282 55993 30324
+7 38 1 13990 55993 30849
+8 38 1 14866 55993 31191
+9 38 1 15459 55993 31681
+10 38 1 16323 55993 32040
+11 38 1 17185 55993 32683
+12 38 1 17777 55993 33112
+13 38 1 18652 55993 33484
+14 38 1 19359 55993 33908
+15 38 1 19966 55993 34330
+16 38 1 20835 55993 34892
+17 38 1 21442 55993 35197
+18 38 1 22149 55993 35722
+19 38 1 23025 55993 36233
+20 38 1 23618 55993 36587
+21 38 1 24481 55993 37097
+22 38 1 25369 55993 37465
+23 38 1 26176 55993 38094
+24 38 1 26761 55993 38492
+25 38 1 27633 55993 38871
+26 38 1 28299 55993 39269
+27 38 1 28931 55993 39720
+28 38 1 29801 55993 40265
+29 38 1 30392 55993 40568
+30 38 1 31147 55993 41129
+31 38 1 32028 55993 41629
+32 38 1 32640 55993 42001
+33 38 1 33528 55993 42508
+34 38 1 34335 55993 42877
+35 38 1 34921 55993 43491
+36 38 1 35793 55993 43863
+37 38 1 36459 55993 44255
+38 38 1 37091 55993 44628
+39 38 1 37923 55993 45112
+40 38 1 38774 55993 45639
+41 38 1 39359 55993 45980
+42 38 1 40166 55993 46470
+43 38 1 41054 55993 46828
+44 38 1 41666 55993 47471
+45 38 1 42547 55993 47895
+46 38 1 43303 55993 48278
+47 38 1 43893 55993 48875
+48 38 1 44763 55993 49229
+49 38 1 45396 55993 49636
+50 38 1 46061 55993 49990
+51 38 1 46933 55993 50511
+52 38 1 47519 55993 51022
+53 38 1 48325 55993 51376
+54 38 1 49213 55993 51885
+55 38 1 49825 55993 52253
+56 38 1 50700 55993 52883
+57 38 1 51545 55993 53271
+58 38 1 52253 55993 53673
+59 38 1 52860 55993 54252
+60 38 1 53729 55993 54593
+61 38 1 54336 55993 55019
+62 38 1 55042 55993 55360
+63 38 1 55918 55993 55917
+0 39 1 8775 57075 28080
+1 39 1 9656 57075 28527
+2 39 1 10409 57075 28939
+3 39 1 11000 57075 29523
+4 39 1 11841 57075 29841
+5 39 1 12675 57075 30329
+6 39 1 13282 57075 30841
+7 39 1 13990 57075 31197
+8 39 1 14866 57075 31642
+9 39 1 15459 57075 32003
+10 39 1 16323 57075 32644
+11 39 1 17185 57075 33080
+12 39 1 17777 57075 33482
+13 39 1 18652 57075 33905
+14 39 1 19359 57075 34335
+15 39 1 19966 57075 34896
+16 39 1 20835 57075 35204
+17 39 1 21442 57075 35722
+18 39 1 22149 57075 36214
+19 39 1 23025 57075 36583
+20 39 1 23618 57075 37043
+21 39 1 24481 57075 37427
+22 39 1 25369 57075 38073
+23 39 1 26176 57075 38476
+24 39 1 26761 57075 38873
+25 39 1 27633 57075 39273
+26 39 1 28299 57075 39727
+27 39 1 28931 57075 40265
+28 39 1 29801 57075 40571
+29 39 1 30392 57075 41120
+30 39 1 31147 57075 41596
+31 39 1 32028 57075 41981
+32 39 1 32640 57075 42457
+33 39 1 33528 57075 42854
+34 39 1 34335 57075 43482
+35 39 1 34921 57075 43859
+36 39 1 35793 57075 44257
+37 39 1 36459 57075 44636
+38 39 1 37091 57075 45118
+39 39 1 37923 57075 45631
+40 39 1 38774 57075 45987
+41 39 1 39359 57075 46431
+42 39 1 40166 57075 46791
+43 39 1 41054 57075 47432
+44 39 1 41666 57075 47865
+45 39 1 42547 57075 48269
+46 39 1 43303 57075 48874
+47 39 1 43893 57075 49235
+48 39 1 44763 57075 49636
+49 39 1 45396 57075 49998
+50 39 1 46061 57075 50511
+51 39 1 46933 57075 51004
+52 39 1 47519 57075 51372
+53 39 1 48325 57075 51831
+54 39 1 49213 57075 52214
+55 39 1 49825 57075 52862
+56 39 1 50700 57075 53256
+57 39 1 51545 57075 53672
+58 39 1 52253 57075 54255
+59 39 1 52860 57075 54605
+60 39 1 53729 57075 55013
+61 39 1 54336 57075 55364
+62 39 1 55042 57075 55909
+63 39 1 55918 57075 56385
+0 40 1 8775 58146 28490
+1 40 1 9656 58146 28927
+2 40 1 10409 58146 29520
+3 40 1 11000 58146 29840
+4 40 1 11841 58146 30333
+5 40 1 12675 58146 30837
+6 40 1 13282 58146 31203
+7 40 1 13990 58146 31615
+8 40 1 14866 58146 31979
+9 40 1 15459 58146 32602
+10 40 1 16323 58146 33024
+11 40 1 17185 58146 33471
+12 40 1 17777 58146 33883
+13 40 1 18652 58146 34332
+14 40 1 19359 58146 34897
+15 40 1 19966 58146 35206
+16 40 1 20835 58146 35724
+17 40 1 21442 58146 36203
+18 40 1 22149 58146 36584
+19 40 1 23025 58146 37002
+20 40 1 23618 58146 37385
+21 40 1 24481 58146 38032
+22 40 1 25369 58146 38436
+23 40 1 26176 58146 38871
+24 40 1 26761 58146 39263
+25 40 1 27633 58146 39729
+26 40 1 28299 58146 40266
+27 40 1 28931 58146 40572
+28 40 1 29801 58146 41116
+29 40 1 30392 58146 41573
+30 40 1 31147 58146 41973
+31 40 1 32028 58146 42399
+32 40 1 32640 58146 42812
+33 40 1 33528 58146 43457
+34 40 1 34335 58146 43835
+35 40 1 34921 58146 44259
+36 40 1 35793 58146 44634
+37 40 1 36459 58146 45121
+38 40 1 37091 58146 45627
+39 40 1 37923 58146 45992
+40 40 1 38774 58146 46404
+41 40 1 39359 58146 46768
+42 40 1 40166 58146 47390
+43 40 1 41054 58146 47812
+44 40 1 41666 58146 48242
+45 40 1 42547 58146 48863
+46 40 1 43303 58146 49222
+47 40 1 43893 58146 49639
+48 40 1 44763 58146 50000
+49 40 1 45396 58146 50512
+50 40 1 46061 58146 50992
+51 40 1 46933 58146 51373
+52 40 1 47519 58146 51790
+53 40 1 48325 58146 52173
+54 40 1 49213 58146 52819
+55 40 1 49825 58146 53221
+56 40 1 50700 58146 53659
+57 40 1 51545 58146 54252
+58 40 1 52253 58146 54602
+59 40 1 52860 58146 55013
+60 40 1 53729 58146 55366
+61 40 1 54336 58146 55905
+62 40 1 55042 58146 56363
+63 40 1 55918 58146 56762
+0 41 1 8775 59220 28902
+1 41 1 9656 59220 29509
+2 41 1 10409 59220 29830
+3 41 1 11000 59220 30335
+4 41 1 11841 59220 30840
+5 41 1 12675 59220 31206
+6 41 1 13282 59220 31604
+7 41 1 13990 59220 31967
+8 41 1 14866 59220 32576
+9 41 1 15459 59220 32968
+10 41 1 16323 59220 33450
+11 41 1 17185 59220 33846
+12 41 1 17777 59220 34319
+13 41 1 18652 59220 34895
+14 41 1 19359 59220 35204
+15 41 1 19966 59220 35728
+16 41 1 20835 59220 36202
+17 41 1 21442 59220 36586
+18 41 1 22149 59220 36978
+19 41 1 23025 59220 37361
+20 41 1 23618 59220 37990
+21 41 1 24481 59220 38380
+22 41 1 25369 59220 38864
+23 41 1 26176 59220 39241
+24 41 1 26761 59220 39726
+25 41 1 27633 59220 40269
+26 41 1 28299 59220 40574
+27 41 1 28931 59220 41118
+28 41 1 29801 59220 41564
+29 41 1 30392 59220 41971
+30 41 1 31147 59220 42361
+31 41 1 32028 59220 42771
+32 41 1 32640 59220 43417
+33 41 1 33528 59220 43794
+34 41 1 34335 59220 44261
+35 41 1 34921 59220 44623
+36 41 1 35793 59220 45124
+37 41 1 36459 59220 45630
+38 41 1 37091 59220 45995
+39 41 1 37923 59220 46394
+40 41 1 38774 59220 46756
+41 41 1 39359 59220 47365
+42 41 1 40166 59220 47760
+43 41 1 41054 59220 48200
+44 41 1 41666 59220 48840
+45 41 1 42547 59220 49195
+46 41 1 43303 59220 49646
+47 41 1 43893 59220 49997
+48 41 1 44763 59220 50517
+49 41 1 45396 59220 50992
+50 41 1 46061 59220 51375
+51 41 1 46933 59220 51768
+52 41 1 47519 59220 52150
+53 41 1 48325 59220 52778
+54 41 1 49213 59220 53168
+55 41 1 49825 59220 53633
+56 41 1 50700 59220 54242
+57 41 1 51545 59220 54586
+58 41 1 52253 59220 55022
+59 41 1 52860 59220 55366
+60 41 1 53729 59220 55907
+61 41 1 54336 59220 56354
+62 41 1 55042 59220 56760
+63 41 1 55918 59220 57150
+0 42 1 8775 60307 29493
+1 42 1 9656 60307 29815
+2 42 1 10409 60307 30338
+3 42 1 11000 60307 30851
+4 42 1 11841 60307 31206
+5 42 1 12675 60307 31613
+6 42 1 13282 60307 31969
+7 42 1 13990 60307 32569
+8 42 1 14866 60307 32939
+9 42 1 15459 60307 33429
+10 42 1 16323 60307 33798
+11 42 1 17185 60307 34300
+12 42 1 17777 60307 34891
+13 42 1 18652 60307 35199
+14 42 1 19359 60307 35736
+15 42 1 19966 60307 36213
+16 42 1 20835 60307 36589
+17 42 1 21442 60307 36977
+18 42 1 22149 60307 37354
+19 42 1 23025 60307 37970
+20 42 1 23618 60307 38331
+21 42 1 24481 60307 38846
+22 42 1 25369 60307 39210
+23 42 1 26176 60307 39719
+24 42 1 26761 60307 40274
+25 42 1 27633 60307 40576
+26 42 1 28299 60307 41128
+27 42 1 28931 60307 41571
+28 42 1 29801 60307 41973
+29 42 1 30392 60307 42348
+30 42 1 31147 60307 42751
+31 42 1 32028 60307 43381
+32 42 1 32640 60307 43742
+33 42 1 33528 60307 44259
+34 42 1 34335 60307 44606
+35 42 1 34921 60307 45126
+36 42 1 35793 60307 45641
+37 42 1 36459 60307 45995
+38 42 1 37091 60307 46402
+39 42 1 37923 60307 46758
+40 42 1 38774 60307 47358
+41 42 1 39359 60307 47733
+42 42 1 40166 60307 48164
+43 42 1 41054 60307 48805
+44 42 1 41666 60307 49158
+45 42 1 42547 60307 49655
+46 42 1 43303 60307 49990
+47 42 1 43893 60307 50525
+48 42 1 44763 60307 51002
+49 42 1 45396 60307 51378
+50 42 1 46061 60307 51767
+51 42 1 46933 60307 52143
+52 42 1 47519 60307 52758
+53 42 1 48325 60307 53122
+54 42 1 49213 60307 53596
+55 42 1 49825 60307 54225
+56 42 1 50700 60307 54562
+57 42 1 51545 60307 55037
+58 42 1 52253 60307 55368
+59 42 1 52860 60307 55917
+60 42 1 53729 60307 56361
+61 42 1 54336 60307 56762
+62 42 1 55042 60307 57137
+63 42 1 55918 60307 57540
+0 43 1 8775 61420 29797
+1 43 1 9656 61420 30342
+2 43 1 10409 61420 30869
+3 43 1 11000 61420 31207
+4 43 1 11841 61420 31641
+5 43 1 12675 61420 31984
+6 43 1 13282 61420 32581
+7 43 1 13990 61420 32939
+8 43 1 14866 61420 33425
+9 43 1 15459 61420 33763
+10 43 1 16323 61420 34274
+11 43 1 17185 61420 34885
+12 43 1 17777 61420 35194
+13 43 1 18652 61420 35749
+14 43 1 19359 61420 36235
+15 43 1 19966 61420 36595
+16 43 1 20835 61420 37000
+17 43 1 21442 61420 37365
+18 43 1 22149 61420 37973
+19 43 1 23025 61420 38315
+20 43 1 23618 61420 38832
+21 43 1 24481 61420 39174
+22 43 1 25369 61420 39710
+23 43 1 26176 61420 40281
+24 43 1 26761 61420 40582
+25 43 1 27633 61420 41146
+26 43 1 28299 61420 41594
+27 43 1 28931 61420 41981
+28 43 1 29801 61420 42361
+29 43 1 30392 61420 42754
+30 43 1 31147 61420 43371
+31 43 1 32028 61420 43706
+32 43 1 32640 61420 44249
+33 43 1 33528 61420 44586
+34 43 1 34335 61420 45130
+35 43 1 34921 61420 45658
+36 43 1 35793 61420 45996
+37 43 1 36459 61420 46430
+38 43 1 37091 61420 46773
+39 43 1 37923 61420 47370
+40 43 1 38774 61420 47733
+41 43 1 39359 61420 48155
+42 43 1 40166 61420 48779
+43 43 1 41054 61420 49117
+44 43 1 41666 61420 49662
+45 43 1 42547 61420 49984
+46 43 1 43303 61420 50537
+47 43 1 43893 61420 51025
+48 43 1 44763 61420 51384
+49 43 1 45396 61420 51790
+50 43 1 46061 61420 52154
+51 43 1 46933 61420 52761
+52 43 1 47519 61420 53108
+53 43 1 48325 61420 53571
+54 43 1 49213 61420 54200
+55 43 1 49825 61420 54534
+56 43 1 50700 61420 55056
+57 43 1 51545 61420 55372
+58 43 1 52253 61420 55935
+59 43 1 52860 61420 56384
+60 43 1 53729 61420 56770
+61 43 1 54336 61420 57150
+62 43 1 55042 61420 57543
+63 43 1 55918 61420 58160
+0 44 1 8775 62571 30348
+1 44 1 9656 62571 30893
+2 44 1 10409 62571 31215
+3 44 1 11000 62571 31686
+4 44 1 11841 62571 32015
+5 44 1 12675 62571 32614
+6 44 1 13282 62571 32970
+7 44 1 13990 62571 33437
+8 44 1 14866 62571 33766
+9 44 1 15459 62571 34263
+10 44 1 16323 62571 34876
+11 44 1 17185 62571 35194
+12 44 1 17777 62571 35768
+13 44 1 18652 62571 36270
+14 44 1 19359 62571 36608
+15 44 1 19966 62571 37045
+16 44 1 20835 62571 37396
+17 44 1 21442 62571 38000
+18 44 1 22149 62571 38334
+19 44 1 23025 62571 38837
+20 44 1 23618 62571 39157
+21 44 1 24481 62571 39700
+22 44 1 25369 62571 40290
+23 44 1 26176 62571 40594
+24 44 1 26761 62571 41175
+25 44 1 27633 62571 41634
+26 44 1 28299 62571 41998
+27 44 1 28931 62571 42401
+28 44 1 29801 62571 42781
+29 44 1 30392 62571 43389
+30 44 1 31147 62571 43709
+31 44 1 32028 62571 44244
+32 44 1 32640 62571 44568
+33 44 1 33528 62571 45136
+34 44 1 34335 62571 45682
+35 44 1 34921 62571 46004
+36 44 1 35793 62571 46476
+37 44 1 36459 62571 46804
+38 44 1 37091 62571 47403
+39 44 1 37923 62571 47762
+40 44 1 38774 62571 48173
+41 44 1 39359 62571 48784
+42 44 1 40166 62571 49100
+43 44 1 41054 62571 49662
+44 44 1 41666 62571 49982
+45 44 1 42547 62571 50557
+46 44 1 43303 62571 51059
+47 44 1 43893 62571 51397
+48 44 1 44763 62571 51835
+49 44 1 45396 62571 52185
+50 44 1 46061 62571 52789
+51 44 1 46933 62571 53126
+52 44 1 47519 62571 53577
+53 44 1 48325 62571 54189
+54 44 1 49213 62571 54512
+55 44 1 49825 62571 55075
+56 44 1 50700 62571 55383
+57 44 1 51545 62571 55963
+58 44 1 52253 62571 56424
+59 44 1 52860 62571 56787
+60 44 1 53729 62571 57191
+61 44 1 54336 62571 57570
+62 44 1 55042 62571 58178
+63 44 1 55918 62571 58501
+0 45 0 0 0 0
+1 45 0 0 0 0
+2 45 0 0 0 0
+3 45 0 0 0 0
+4 45 0 0 0 0
+5 45 0 0 0 0
+6 45 0 0 0 0
+7 45 0 0 0 0
+8 45 0 0 0 0
+9 45 0 0 0 0
+10 45 0 0 0 0
+11 45 0 0 0 0
+12 45 0 0 0 0
+13 45 0 0 0 0
+14 45 0 0 0 0
+15 45 0 0 0 0
+16 45 0 0 0 0
+17 45 0 0 0 0
+18 45 0 0 0 0
+19 45 0 0 0 0
+20 45 0 0 0 0
+21 45 0 0 0 0
+22 45 0 0 0 0
+23 45 0 0 0 0
+24 45 0 0 0 0
+25 45 0 0 0 0
+26 45 0 0 0 0
+27 45 0 0 0 0
+28 45 0 0 0 0
+29 45 0 0 0 0
+30 45 0 0 0 0
+31 45 0 0 0 0
+32 45 0 0 0 0
+33 45 0 0 0 0
+34 45 0 0 0 0
+35 45 0 0 0 0
+36 45 0 0 0 0
+37 45 0 0 0 0
+38 45 0 0 0 0
+39 45 0 0 0 0
+40 45 0 0 0 0
+41 45 0 0 0 0
+42 45 0 0 0 0
+43 45 0 0 0 0
+44 45 0 0 0 0
+45 45 0 0 0 0
+46 45 0 0 0 0
+47 45 0 0 0 0
+48 45 0 0 0 0
+49 45 0 0 0 0
+50 45 0 0 0 0
+51 45 0 0 0 0
+52 45 0 0 0 0
+53 45 0 0 0 0
+54 45 0 0 0 0
+55 45 0 0 0 0
+56 45 0 0 0 0
+57 45 0 0 0 0
+58 45 0 0 0 0
+59 45 0 0 0 0
+60 45 0 0 0 0
+61 45 0 0 0 0
+62 45 0 0 0 0
+63 45 0 0 0 0
+0 46 0 0 0 0
+1 46 0 0 0 0
+2 46 0 0 0 0
+3 46 0 0 0 0
+4 46 0 0 0 0
+5 46 0 0 0 0
+6 46 0 0 0 0
+7 46 0 0 0 0
+8 46 0 0 0 0
+9 46 0 0 0 0
+10 46 0 0 0 0
+11 46 0 0 0 0
+12 46 0 0 0 0
+13 46 0 0 0 0
+14 46 0 0 0 0
+15 46 0 0 0 0
+16 46 0 0 0 0
+17 46 0 0 0 0
+18 46 0 0 0 0
+19 46 0 0 0 0
+20 46 0 0 0 0
+21 46 0 0 0 0
+22 46 0 0 0 0
+23 46 0 0 0 0
+24 46 0 0 0 0
+25 46 0 0 0 0
+26 46 0 0 0 0
+27 46 0 0 0 0
+28 46 0 0 0 0
+29 46 0 0 0 0
+30 46 0 0 0 0
+31 46 0 0 0 0
+32 46 0 0 0 0
+33 46 0 0 0 0
+34 46 0 0 0 0
+35 46 0 0 0 0
+36 46 0 0 0 0
+37 46 0 0 0 0
+38 46 0 0 0 0
+39 46 0 0 0 0
+40 46 0 0 0 0
+41 46 0 0 0 0
+42 46 0 0 0 0
+43 46 0 0 0 0
+44 46 0 0 0 0
+45 46 0 0 0 0
+46 46 0 0 0 0
+47 46 0 0 0 0
+48 46 0 0 0 0
+49 46 0 0 0 0
+50 46 0 0 0 0
+51 46 0 0 0 0
+52 46 0 0 0 0
+53 46 0 0 0 0
+54 46 0 0 0 0
+55 46 0 0 0 0
+56 46 0 0 0 0
+57 46 0 0 0 0
+58 46 0 0 0 0
+59 46 0 0 0 0
+60 46 0 0 0 0
+61 46 0 0 0 0
+62 46 0 0 0 0
+63 46 0 0 0 0
+0 47 0 0 0 0
+1 47 0 0 0 0
+2 47 0 0 0 0
+3 47 0 0 0 0
+4 47 0 0 0 0
+5 47 0 0 0 0
+6 47 0 0 0 0
+7 47 0 0 0 0
+8 47 0 0 0 0
+9 47 0 0 0 0
+10 47 0 0 0 0
+11 47 0 0 0 0
+12 47 0 0 0 0
+13 47 0 0 0 0
+14 47 0 0 0 0
+15 47 0 0 0 0
+16 47 0 0 0 0
+17 47 0 0 0 0
+18 47 0 0 0 0
+19 47 0 0 0 0
+20 47 0 0 0 0
+21 47 0 0 0 0
+22 47 0 0 0 0
+23 47 0 0 0 0
+24 47 0 0 0 0
+25 47 0 0 0 0
+26 47 0 0 0 0
+27 47 0 0 0 0
+28 47 0 0 0 0
+29 47 0 0 0 0
+30 47 0 0 0 0
+31 47 0 0 0 0
+32 47 0 0 0 0
+33 47 0 0 0 0
+34 47 0 0 0 0
+35 47 0 0 0 0
+36 47 0 0 0 0
+37 47 0 0 0 0
+38 47 0 0 0 0
+39 47 0 0 0 0
+40 47 0 0 0 0
+41 47 0 0 0 0
+42 47 0 0 0 0
+43 47 0 0 0 0
+44 47 0 0 0 0
+45 47 0 0 0 0
+46 47 0 0 0 0
+47 47 0 0 0 0
+48 47 0 0 0 0
+49 47 0 0 0 0
+50 47 0 0 0 0
+51 47 0 0 0 0
+52 47 0 0 0 0
+53 47 0 0 0 0
+54 47 0 0 0 0
+55 47 0 0 0 0
+56 47 0 0 0 0
+57 47 0 0 0 0
+58 47 0 0 0 0
+59 47 0 0 0 0
+60 47 0 0 0 0
+61 47 0 0 0 0
+62 47 0 0 0 0
+63 47 0 0 0 0
diff --git a/tests/golden/stitch_dist_bic.txt b/tests/golden/stitch_dist_bic.txt
new file mode 100644
index 0000000..619e8de
--- /dev/null
+++ b/tests/golden/stitch_dist_bic.txt
@@ -0,0 +1,3072 @@
+0 0 1 9341 15725 11853
+1 0 1 10134 15687 12397
+2 0 1 10658 15640 12829
+3 0 1 11430 15583 13100
+4 0 1 12216 15513 13577
+5 0 1 12994 15423 13818
+6 0 1 52579 33142 16392
+7 0 1 49018 31796 16229
+8 0 1 46273 30728 16169
+9 0 1 43856 29656 16126
+10 0 1 41397 28579 16183
+11 0 1 39140 27495 16214
+12 0 1 37121 26406 16406
+13 0 1 35168 25309 16609
+14 0 1 33412 24204 16766
+15 0 1 31818 23091 17162
+16 0 1 30405 21968 17501
+17 0 1 29150 20837 17811
+18 0 1 28012 19695 18291
+19 0 1 27165 18543 18683
+20 0 1 26396 17381 19382
+21 0 1 25753 16208 19905
+22 0 1 25458 15025 20507
+23 0 1 25386 13853 21133
+24 0 1 25292 12725 21818
+25 0 1 26520 13410 22122
+26 0 1 27892 14109 22175
+27 0 1 28364 13916 22650
+28 0 1 29100 13871 22985
+29 0 1 29947 13833 23363
+30 0 1 30510 13801 23953
+31 0 1 31325 13772 24334
+32 0 1 32164 13743 24711
+33 0 1 32734 13713 25310
+34 0 1 33580 13679 25702
+35 0 1 34341 13641 26041
+36 0 1 34904 13595 26483
+37 0 1 35754 13542 26805
+38 0 1 36441 13480 27368
+39 0 1 37027 13408 27719
+40 0 1 37848 13324 28055
+41 0 1 38664 13229 28421
+42 0 1 39276 13142 28744
+43 0 1 39918 13068 29297
+44 0 1 40765 13005 29628
+45 0 1 41341 12952 29975
+46 0 1 42030 12907 30539
+47 0 1 42870 12869 30880
+48 0 1 43424 12836 31301
+49 0 1 44156 12808 31653
+50 0 1 44984 12782 32011
+51 0 1 45528 12757 32584
+52 0 1 46294 12733 32969
+53 0 1 47107 12708 33334
+54 0 1 47647 12681 33763
+55 0 1 48438 12650 34075
+56 0 1 49233 12614 34640
+57 0 1 49767 12571 35066
+58 0 1 50566 12520 35341
+59 0 1 51355 12459 35879
+60 0 1 52115 12387 36318
+61 0 1 52644 12301 36559
+62 0 1 53447 12202 37031
+63 0 1 54189 12111 37264
+0 1 1 9373 16761 12443
+1 1 1 10161 16725 12849
+2 1 1 10686 16683 13148
+3 1 1 11466 16630 13613
+4 1 1 12251 16565 13872
+5 1 1 13994 16895 14428
+6 1 1 52989 33278 16399
+7 1 1 50673 32647 16321
+8 1 1 50089 32745 16328
+9 1 1 49907 32941 16340
+10 1 1 49542 33214 16342
+11 1 1 49296 33543 16340
+12 1 1 49281 33905 16310
+13 1 1 48900 34275 16265
+14 1 1 48563 34629 16222
+15 1 1 48316 34941 16135
+16 1 1 47579 35183 16056
+17 1 1 46888 35329 15993
+18 1 1 46167 35352 15925
+19 1 1 44936 35224 15897
+20 1 1 43818 34918 15875
+21 1 1 42583 34409 15939
+22 1 1 40999 33671 16075
+23 1 1 39352 32681 16309
+24 1 1 39452 33974 15778
+25 1 1 30100 19729 20669
+26 1 1 27362 14347 22909
+27 1 1 28386 14958 22997
+28 1 1 29144 14908 23390
+29 1 1 29991 14868 23977
+30 1 1 30562 14834 24332
+31 1 1 31401 14804 24734
+32 1 1 32219 14775 25334
+33 1 1 32785 14746 25707
+34 1 1 33635 14714 26073
+35 1 1 34370 14678 26497
+36 1 1 34940 14637 26847
+37 1 1 35791 14588 27412
+38 1 1 36453 14531 27755
+39 1 1 37056 14464 28107
+40 1 1 37874 14386 28470
+41 1 1 38694 14296 28813
+42 1 1 39287 14204 29359
+43 1 1 39956 14124 29660
+44 1 1 40802 14056 30027
+45 1 1 41365 13998 30591
+46 1 1 42086 13949 30901
+47 1 1 42922 13908 31349
+48 1 1 43471 13873 31676
+49 1 1 44236 13842 32061
+50 1 1 45057 13815 32643
+51 1 1 45602 13789 32998
+52 1 1 46401 13765 33397
+53 1 1 47202 13741 33803
+54 1 1 47742 13715 34146
+55 1 1 48550 13685 34721
+56 1 1 49317 13652 35110
+57 1 1 49851 13612 35415
+58 1 1 50659 13565 35973
+59 1 1 51446 13508 36378
+60 1 1 52178 13441 36647
+61 1 1 52713 13361 37107
+62 1 1 53529 13266 37350
+63 1 1 54242 13169 37861
+0 2 1 9408 17797 12864
+1 2 1 10191 17764 13194
+2 2 1 10718 17725 13638
+3 2 1 11505 17678 13920
+4 2 1 12246 17602 14459
+5 2 1 15035 18329 14960
+6 2 1 53784 33556 16410
+7 2 1 50624 32640 16320
+8 2 1 49865 32640 16320
+9 2 1 49329 32640 16320
+10 2 1 48510 32640 16320
+11 2 1 47774 32640 16320
+12 2 1 47229 32640 16320
+13 2 1 46398 32640 16320
+14 2 1 45692 32640 16320
+15 2 1 45131 32640 16320
+16 2 1 44291 32640 16320
+17 2 1 43624 32640 16320
+18 2 1 43037 32640 16320
+19 2 1 42191 32640 16320
+20 2 1 41565 32640 16320
+21 2 1 40940 32640 16320
+22 2 1 40127 32640 16320
+23 2 1 39303 32640 16320
+24 2 1 40282 35078 15339
+25 2 1 30092 20480 21130
+26 2 1 27437 15498 23229
+27 2 1 28414 16005 23425
+28 2 1 29195 15950 24004
+29 2 1 30042 15906 24332
+30 2 1 30623 15869 24758
+31 2 1 31471 15838 25355
+32 2 1 32264 15809 25709
+33 2 1 32829 15781 26102
+34 2 1 33682 15751 26504
+35 2 1 34390 15717 26885
+36 2 1 34971 15679 27446
+37 2 1 35824 15635 27784
+38 2 1 36462 15583 28149
+39 2 1 37085 15522 28509
+40 2 1 37901 15451 28884
+41 2 1 38726 15369 29429
+42 2 1 39303 15275 29705
+43 2 1 40002 15188 30094
+44 2 1 40846 15114 30648
+45 2 1 41402 15051 30932
+46 2 1 42155 14997 31402
+47 2 1 42986 14951 31705
+48 2 1 43537 14912 32122
+49 2 1 44334 14879 32702
+50 2 1 45147 14850 33031
+51 2 1 45695 14824 33460
+52 2 1 46510 14799 33834
+53 2 1 47281 14775 34211
+54 2 1 47821 14749 34784
+55 2 1 48640 14722 35140
+56 2 1 49376 14690 35482
+57 2 1 49916 14654 36046
+58 2 1 50729 14610 36421
+59 2 1 51518 14558 36721
+60 2 1 52220 14496 37161
+61 2 1 52768 14422 37437
+62 2 1 53592 14334 37969
+63 2 1 54275 14233 38352
+0 3 1 9447 18834 13239
+1 3 1 10224 18803 13655
+2 3 1 10750 18767 13963
+3 3 1 11542 18725 14519
+4 3 1 12189 18621 14931
+5 3 1 16117 19722 15258
+6 3 1 54505 33763 16416
+7 3 1 50591 32640 16320
+8 3 1 49843 32640 16320
+9 3 1 49305 32640 16320
+10 3 1 48482 32640 16320
+11 3 1 47758 32640 16320
+12 3 1 47208 32640 16320
+13 3 1 46375 32640 16320
+14 3 1 45682 32640 16320
+15 3 1 45114 32640 16320
+16 3 1 44272 32640 16320
+17 3 1 43618 32640 16320
+18 3 1 43022 32640 16320
+19 3 1 42175 32640 16320
+20 3 1 41560 32640 16320
+21 3 1 40922 32640 16320
+22 3 1 40110 32640 16320
+23 3 1 39283 32640 16320
+24 3 1 40218 34843 15328
+25 3 1 29610 20587 21607
+26 3 1 27617 16789 23622
+27 3 1 28448 17058 24038
+28 3 1 29254 16997 24338
+29 3 1 30101 16948 24787
+30 3 1 30680 16908 25375
+31 3 1 31531 16874 25708
+32 3 1 32299 16845 26130
+33 3 1 32867 16817 26506
+34 3 1 33721 16788 26918
+35 3 1 34405 16757 27471
+36 3 1 34999 16722 27806
+37 3 1 35853 16682 28183
+38 3 1 36471 16635 28538
+39 3 1 37117 16581 28947
+40 3 1 37931 16517 29487
+41 3 1 38761 16442 29759
+42 3 1 39328 16356 30178
+43 3 1 40058 16263 30715
+44 3 1 40899 16181 30976
+45 3 1 41454 16111 31463
+46 3 1 42241 16051 31743
+47 3 1 43067 16000 32193
+48 3 1 43624 15957 32763
+49 3 1 44444 15920 33066
+50 3 1 45229 15888 33518
+51 3 1 45773 15860 33859
+52 3 1 46597 15834 34272
+53 3 1 47338 15810 34834
+54 3 1 47883 15785 35160
+55 3 1 48710 15759 35543
+56 3 1 49416 15730 36101
+57 3 1 49968 15696 36451
+58 3 1 50782 15656 36784
+59 3 1 51574 15609 37197
+60 3 1 52246 15552 37513
+61 3 1 52811 15484 38060
+62 3 1 53642 15403 38411
+63 3 1 54296 15306 38714
+0 4 1 9486 19841 13667
+1 4 1 10255 19816 14004
+2 4 1 10781 19788 14568
+3 4 1 11578 19755 14957
+4 4 1 12078 19616 15253
+5 4 1 17244 21064 15765
+6 4 1 55148 33902 16387
+7 4 1 50560 32640 16320
+8 4 1 49823 32640 16320
+9 4 1 49282 32640 16320
+10 4 1 48456 32640 16320
+11 4 1 47743 32640 16320
+12 4 1 47189 32640 16320
+13 4 1 46353 32640 16320
+14 4 1 45673 32640 16320
+15 4 1 45098 32640 16320
+16 4 1 44255 32640 16320
+17 4 1 43612 32640 16320
+18 4 1 43006 32640 16320
+19 4 1 42159 32640 16320
+20 4 1 41555 32640 16320
+21 4 1 40903 32640 16320
+22 4 1 40092 32640 16320
+23 4 1 39262 32640 16320
+24 4 1 40121 34578 15307
+25 4 1 29151 20828 22335
+26 4 1 27771 18025 24171
+27 4 1 28490 18121 24358
+28 4 1 29321 18052 24825
+29 4 1 30156 17996 25398
+30 4 1 30729 17951 25710
+31 4 1 31583 17914 26159
+32 4 1 32326 17883 26507
+33 4 1 32899 17854 26951
+34 4 1 33755 17826 27489
+35 4 1 34415 17797 27824
+36 4 1 35026 17766 28209
+37 4 1 35881 17730 28560
+38 4 1 36481 17688 29001
+39 4 1 37153 17640 29529
+40 4 1 37968 17583 29802
+41 4 1 38803 17516 30260
+42 4 1 39364 17439 30787
+43 4 1 40127 17350 31040
+44 4 1 40965 17259 31533
+45 4 1 41525 17180 31795
+46 4 1 42345 17113 32277
+47 4 1 43154 17056 32822
+48 4 1 43706 17007 33104
+49 4 1 44534 16966 33573
+50 4 1 45288 16930 33883
+51 4 1 45835 16899 34334
+52 4 1 46667 16872 34873
+53 4 1 47376 16846 35175
+54 4 1 47932 16821 35600
+55 4 1 48765 16796 36141
+56 4 1 49441 16769 36475
+57 4 1 50010 16738 36835
+58 4 1 50822 16702 37220
+59 4 1 51619 16659 37583
+60 4 1 52263 16607 38129
+61 4 1 52849 16545 38457
+62 4 1 53683 16471 38795
+63 4 1 54309 16383 39165
+0 5 1 9524 21034 14046
+1 5 1 10284 20993 14611
+2 5 1 10811 20949 14972
+3 5 1 11612 20900 15304
+4 5 1 11911 20694 15745
+5 5 1 18416 22420 16019
+6 5 1 55705 33972 16366
+7 5 1 50530 32640 16320
+8 5 1 49805 32640 16320
+9 5 1 49260 32640 16320
+10 5 1 48431 32640 16320
+11 5 1 47731 32640 16320
+12 5 1 47171 32640 16320
+13 5 1 46333 32640 16320
+14 5 1 45665 32640 16320
+15 5 1 45082 32640 16320
+16 5 1 44238 32640 16320
+17 5 1 43608 32640 16320
+18 5 1 42990 32640 16320
+19 5 1 42142 32640 16320
+20 5 1 41549 32640 16320
+21 5 1 40882 32640 16320
+22 5 1 40070 32640 16320
+23 5 1 39237 32640 16320
+24 5 1 39972 34269 15332
+25 5 1 28692 21167 23159
+26 5 1 27901 19221 24438
+27 5 1 28540 19194 24878
+28 5 1 29389 19115 25430
+29 5 1 30202 19051 25720
+30 5 1 30771 19000 26193
+31 5 1 31627 18958 26511
+32 5 1 32347 18924 26984
+33 5 1 32928 18894 27504
+34 5 1 33785 18866 27839
+35 5 1 34424 18838 28230
+36 5 1 35054 18809 28575
+37 5 1 35910 18777 29047
+38 5 1 36495 18740 29557
+39 5 1 37196 18698 29837
+40 5 1 38014 18648 30329
+41 5 1 38853 18589 30841
+42 5 1 39414 18521 31108
+43 5 1 40211 18442 31610
+44 5 1 41046 18350 31866
+45 5 1 41612 18262 32373
+46 5 1 42443 18185 32884
+47 5 1 43221 18120 33151
+48 5 1 43772 18064 33628
+49 5 1 44606 18017 33913
+50 5 1 45329 17977 34397
+51 5 1 45885 17942 34906
+52 5 1 46721 17912 35192
+53 5 1 47401 17885 35656
+54 5 1 47972 17859 36169
+55 5 1 48810 17834 36497
+56 5 1 49457 17808 36876
+57 5 1 50046 17779 37235
+58 5 1 50854 17746 37646
+59 5 1 51657 17708 38178
+60 5 1 52274 17661 38493
+61 5 1 52884 17606 38863
+62 5 1 53719 17539 39211
+63 5 1 54320 17458 39560
+0 6 1 9561 22231 14650
+1 6 1 10311 22207 14984
+2 6 1 10840 22181 15349
+3 6 1 11644 22152 15757
+4 6 1 11688 21910 16096
+5 6 1 19637 23861 16545
+6 6 1 56171 33960 16300
+7 6 1 50502 32640 16320
+8 6 1 49789 32640 16320
+9 6 1 49239 32640 16320
+10 6 1 48407 32640 16320
+11 6 1 47720 32640 16320
+12 6 1 47153 32640 16320
+13 6 1 46314 32640 16320
+14 6 1 45659 32640 16320
+15 6 1 45067 32640 16320
+16 6 1 44221 32640 16320
+17 6 1 43603 32640 16320
+18 6 1 42973 32640 16320
+19 6 1 42126 32640 16320
+20 6 1 41541 32640 16320
+21 6 1 40858 32640 16320
+22 6 1 40046 32640 16320
+23 6 1 39210 32640 16320
+24 6 1 39764 33923 15440
+25 6 1 28232 21611 23745
+26 6 1 28001 20380 24963
+27 6 1 28596 20265 25476
+28 6 1 29450 20164 25745
+29 6 1 30239 20083 26235
+30 6 1 30808 20020 26524
+31 6 1 31666 19970 27023
+32 6 1 32361 19931 27520
+33 6 1 32954 19900 27852
+34 6 1 33812 19873 28249
+35 6 1 34431 19849 28587
+36 6 1 35085 19826 29088
+37 6 1 35941 19801 29573
+38 6 1 36516 19773 29864
+39 6 1 37248 19740 30387
+40 6 1 38072 19700 30877
+41 6 1 38914 19653 31168
+42 6 1 39482 19596 31670
+43 6 1 40312 19529 31944
+44 6 1 41128 19449 32480
+45 6 1 41686 19357 32954
+46 6 1 42523 19270 33212
+47 6 1 43269 19195 33687
+48 6 1 43824 19130 33954
+49 6 1 44664 19076 34467
+50 6 1 45356 19029 34937
+51 6 1 45925 18989 35215
+52 6 1 46766 18955 35714
+53 6 1 47417 18926 36190
+54 6 1 48007 18899 36521
+55 6 1 48847 18873 36909
+56 6 1 49468 18847 37248
+57 6 1 50080 18820 37706
+58 6 1 50884 18790 38210
+59 6 1 51692 18755 38526
+60 6 1 52284 18714 38916
+61 6 1 52920 18664 39246
+62 6 1 53754 18604 39648
+63 6 1 54332 18532 40175
+0 7 1 9597 23241 14996
+1 7 1 10336 23213 15392
+2 7 1 10867 23186 15766
+3 7 1 11674 23157 16129
+4 7 1 11411 22862 16716
+5 7 1 20908 25016 16876
+6 7 1 56540 33929 16237
+7 7 1 50476 32640 16320
+8 7 1 49775 32640 16320
+9 7 1 49219 32640 16320
+10 7 1 48385 32640 16320
+11 7 1 47711 32640 16320
+12 7 1 47136 32640 16320
+13 7 1 46295 32640 16320
+14 7 1 45653 32640 16320
+15 7 1 45051 32640 16320
+16 7 1 44205 32640 16320
+17 7 1 43598 32640 16320
+18 7 1 42955 32640 16320
+19 7 1 42108 32640 16320
+20 7 1 41531 32640 16320
+21 7 1 40831 32640 16320
+22 7 1 40017 32640 16320
+23 7 1 39179 32640 16320
+24 7 1 39491 33534 15582
+25 7 1 27772 22377 24656
+26 7 1 28067 21766 25540
+27 7 1 28647 21633 25795
+28 7 1 29502 21511 26289
+29 7 1 30268 21410 26554
+30 7 1 30840 21325 27072
+31 7 1 31699 21255 27542
+32 7 1 32372 21195 27870
+33 7 1 32980 21144 28270
+34 7 1 33838 21099 28600
+35 7 1 34441 21057 29125
+36 7 1 35120 21017 29581
+37 7 1 35977 20977 29886
+38 7 1 36546 20935 30435
+39 7 1 37311 20890 30898
+40 7 1 38144 20840 31221
+41 7 1 38989 20784 31710
+42 7 1 39565 20721 32008
+43 7 1 40406 20648 32573
+44 7 1 41189 20565 33023
+45 7 1 41746 20471 33291
+46 7 1 42587 20365 33755
+47 7 1 43302 20268 34015
+48 7 1 43867 20185 34546
+49 7 1 44710 20115 34973
+50 7 1 45373 20056 35250
+51 7 1 45960 20007 35776
+52 7 1 46803 19966 36209
+53 7 1 47428 19932 36551
+54 7 1 48040 19904 36936
+55 7 1 48881 19879 37264
+56 7 1 49478 19856 37763
+57 7 1 50115 19833 38231
+58 7 1 50915 19809 38559
+59 7 1 51726 19782 38957
+60 7 1 52298 19749 39275
+61 7 1 52961 19709 39729
+62 7 1 53791 19660 40237
+63 7 1 54350 19598 40501
+0 8 1 9631 24283 15435
+1 8 1 10358 24254 15774
+2 8 1 10893 24227 16174
+3 8 1 11702 24199 16727
+4 8 1 11081 23856 17112
+5 8 1 22230 26143 17078
+6 8 1 56807 33850 16190
+7 8 1 50451 32640 16320
+8 8 1 49763 32640 16320
+9 8 1 49201 32640 16320
+10 8 1 48364 32640 16320
+11 8 1 47703 32640 16320
+12 8 1 47120 32640 16320
+13 8 1 46277 32640 16320
+14 8 1 45648 32640 16320
+15 8 1 45034 32640 16320
+16 8 1 44188 32640 16320
+17 8 1 43592 32640 16320
+18 8 1 42935 32640 16320
+19 8 1 42088 32640 16320
+20 8 1 41519 32640 16320
+21 8 1 40800 32640 16320
+22 8 1 39984 32640 16320
+23 8 1 39143 32640 16320
+24 8 1 39143 33163 15816
+25 8 1 27312 22969 25561
+26 8 1 28117 22859 25862
+27 8 1 28691 22755 26360
+28 8 1 29549 22638 26610
+29 8 1 30291 22538 27140
+30 8 1 30869 22458 27578
+31 8 1 31728 22395 27896
+32 8 1 32380 22346 28299
+33 8 1 33005 22308 28619
+34 8 1 33865 22277 29163
+35 8 1 34454 22250 29586
+36 8 1 35161 22227 29907
+37 8 1 36019 22204 30475
+38 8 1 36588 22180 30906
+39 8 1 37387 22152 31267
+40 8 1 38234 22120 31734
+41 8 1 39070 22082 32061
+42 8 1 39638 22036 32646
+43 8 1 40483 21981 33071
+44 8 1 41233 21915 33364
+45 8 1 41794 21836 33823
+46 8 1 42639 21743 34098
+47 8 1 43324 21636 34639
+48 8 1 43903 21536 35019
+49 8 1 44748 21448 35304
+50 8 1 45385 21372 35845
+51 8 1 45993 21305 36233
+52 8 1 46836 21246 36591
+53 8 1 47437 21194 36962
+54 8 1 48075 21148 37289
+55 8 1 48914 21105 37820
+56 8 1 49491 21065 38244
+57 8 1 50155 21026 38596
+58 8 1 50951 20986 38988
+59 8 1 51764 20944 39305
+60 8 1 52319 20897 39804
+61 8 1 53010 20844 40282
+62 8 1 53834 20784 40548
+63 8 1 54378 20713 41017
+0 9 1 9663 25328 15787
+1 9 1 10378 25297 16222
+2 9 1 10918 25268 16758
+3 9 1 11728 25241 17064
+4 9 1 10702 24859 17554
+5 9 1 23606 27207 17511
+6 9 1 56967 33736 16100
+7 9 1 50427 32640 16320
+8 9 1 49753 32640 16320
+9 9 1 49182 32640 16320
+10 9 1 48344 32640 16320
+11 9 1 47697 32640 16320
+12 9 1 47103 32640 16320
+13 9 1 46260 32640 16320
+14 9 1 45643 32640 16320
+15 9 1 45017 32640 16320
+16 9 1 44171 32640 16320
+17 9 1 43585 32640 16320
+18 9 1 42912 32640 16320
+19 9 1 42066 32640 16320
+20 9 1 41502 32640 16320
+21 9 1 40765 32640 16320
+22 9 1 39945 32640 16320
+23 9 1 39102 32640 16320
+24 9 1 38712 32791 16151
+25 9 1 26886 23694 26272
+26 9 1 28160 23954 26411
+27 9 1 28731 23867 26686
+28 9 1 29589 23762 27233
+29 9 1 30309 23645 27635
+30 9 1 30894 23547 27939
+31 9 1 31755 23467 28341
+32 9 1 32387 23403 28652
+33 9 1 33032 23351 29206
+34 9 1 33893 23309 29594
+35 9 1 34473 23274 29931
+36 9 1 35210 23244 30511
+37 9 1 36069 23217 30907
+38 9 1 36644 23191 31308
+39 9 1 37478 23165 31744
+40 9 1 38330 23137 32105
+41 9 1 39133 23105 32702
+42 9 1 39697 23068 33101
+43 9 1 40544 23024 33425
+44 9 1 41264 22973 33868
+45 9 1 41834 22912 34178
+46 9 1 42681 22840 34735
+47 9 1 43338 22756 35083
+48 9 1 43935 22660 35383
+49 9 1 44781 22572 35927
+50 9 1 45394 22498 36269
+51 9 1 46026 22436 36643
+52 9 1 46868 22385 36993
+53 9 1 47450 22343 37329
+54 9 1 48114 22308 37879
+55 9 1 48950 22278 38257
+56 9 1 49511 22253 38639
+57 9 1 50203 22229 39012
+58 9 1 50995 22206 39339
+59 9 1 51809 22181 39875
+60 9 1 52351 22152 40313
+61 9 1 53070 22117 40595
+62 9 1 53886 22075 41109
+63 9 1 54419 22021 41577
+0 10 1 9693 26378 16278
+1 10 1 10396 26343 16787
+2 10 1 10941 26312 17073
+3 10 1 11752 26284 17510
+4 10 1 10281 25877 18177
+5 10 1 25037 28205 17698
+6 10 1 57018 33594 16037
+7 10 1 50405 32640 16320
+8 10 1 49744 32640 16320
+9 10 1 49164 32640 16320
+10 10 1 48325 32640 16320
+11 10 1 47691 32640 16320
+12 10 1 47086 32640 16320
+13 10 1 46243 32640 16320
+14 10 1 45638 32640 16320
+15 10 1 44997 32640 16320
+16 10 1 44153 32640 16320
+17 10 1 43576 32640 16320
+18 10 1 42886 32640 16320
+19 10 1 42041 32640 16320
+20 10 1 41481 32640 16320
+21 10 1 40725 32640 16320
+22 10 1 39898 32640 16320
+23 10 1 39055 32640 16320
+24 10 1 38203 32447 16579
+25 10 1 26536 24552 27128
+26 10 1 28195 25040 26743
+27 10 1 28765 24968 27314
+28 10 1 29624 24881 27708
+29 10 1 30322 24777 28008
+30 10 1 30918 24661 28405
+31 10 1 31779 24565 28707
+32 10 1 32395 24487 29263
+33 10 1 33063 24424 29614
+34 10 1 33925 24373 29965
+35 10 1 34500 24331 30548
+36 10 1 35270 24297 30907
+37 10 1 36130 24267 31348
+38 10 1 36717 24240 31746
+39 10 1 37571 24214 32144
+40 10 1 38410 24188 32744
+41 10 1 39180 24160 33118
+42 10 1 39745 24129 33475
+43 10 1 40595 24092 33895
+44 10 1 41284 24049 34245
+45 10 1 41868 23998 34806
+46 10 1 42716 23937 35135
+47 10 1 43348 23864 35477
+48 10 1 43967 23779 36023
+49 10 1 44813 23681 36326
+50 10 1 45405 23591 36712
+51 10 1 46062 23514 37035
+52 10 1 46903 23448 37387
+53 10 1 47468 23393 37944
+54 10 1 48160 23346 38275
+55 10 1 48992 23307 38691
+56 10 1 49540 23273 39035
+57 10 1 50260 23243 39383
+58 10 1 51052 23215 39942
+59 10 1 51863 23188 40337
+60 10 1 52397 23160 40646
+61 10 1 53143 23128 41194
+62 10 1 53949 23091 41623
+63 10 1 54477 23047 41916
+0 11 1 9721 27433 16819
+1 11 1 10410 27393 17091
+2 11 1 10963 27358 17561
+3 11 1 11774 27328 18066
+4 11 1 9826 26915 18579
+5 11 1 26524 29133 17869
+6 11 1 56956 33433 15987
+7 11 1 50383 32640 16320
+8 11 1 49737 32640 16320
+9 11 1 49147 32640 16320
+10 11 1 48306 32640 16320
+11 11 1 47686 32640 16320
+12 11 1 47068 32640 16320
+13 11 1 46225 32640 16320
+14 11 1 45632 32640 16320
+15 11 1 44976 32640 16320
+16 11 1 44132 32640 16320
+17 11 1 43564 32640 16320
+18 11 1 42856 32640 16320
+19 11 1 42013 32640 16320
+20 11 1 41455 32640 16320
+21 11 1 40677 32640 16320
+22 11 1 39844 32640 16320
+23 11 1 39002 32641 16319
+24 11 1 37678 32185 17051
+25 11 1 26261 25519 27788
+26 11 1 28224 26117 27370
+27 11 1 28795 26060 27755
+28 11 1 29655 25989 28075
+29 11 1 30331 25902 28483
+30 11 1 30941 25799 28796
+31 11 1 31803 25686 29342
+32 11 1 32405 25590 29655
+33 11 1 33098 25513 30018
+34 11 1 33962 25450 30594
+35 11 1 34537 25399 30916
+36 11 1 35341 25357 31389
+37 11 1 36203 25322 31746
+38 11 1 36792 25292 32183
+39 11 1 37645 25265 32777
+40 11 1 38475 25239 33127
+41 11 1 39214 25213 33517
+42 11 1 39784 25185 33909
+43 11 1 40636 25154 34302
+44 11 1 41298 25118 34856
+45 11 1 41898 25075 35168
+46 11 1 42748 25024 35557
+47 11 1 43357 24963 36101
+48 11 1 44000 24890 36395
+49 11 1 44845 24804 36798
+50 11 1 45419 24704 37097
+51 11 1 46104 24612 37471
+52 11 1 46941 24533 38019
+53 11 1 47495 24466 38308
+54 11 1 48215 24410 38754
+55 11 1 49041 24363 39063
+56 11 1 49582 24323 39441
+57 11 1 50330 24289 40009
+58 11 1 51122 24259 40359
+59 11 1 51928 24231 40706
+60 11 1 52460 24204 41274
+61 11 1 53231 24174 41662
+62 11 1 54025 24141 41995
+63 11 1 54553 24102 42439
+0 12 1 9747 28495 17121
+1 12 1 10423 28448 17620
+2 12 1 10984 28409 18090
+3 12 1 11795 28375 18405
+4 12 1 9347 27979 19064
+5 12 1 28069 29989 18017
+6 12 1 56783 33262 15937
+7 12 1 50363 32640 16320
+8 12 1 49731 32640 16320
+9 12 1 49129 32640 16320
+10 12 1 48288 32640 16320
+11 12 1 47680 32640 16320
+12 12 1 47049 32640 16320
+13 12 1 46206 32640 16320
+14 12 1 45624 32640 16320
+15 12 1 44952 32640 16320
+16 12 1 44109 32640 16320
+17 12 1 43547 32640 16320
+18 12 1 42821 32640 16320
+19 12 1 41979 32640 16320
+20 12 1 41421 32640 16320
+21 12 1 40622 32640 16320
+22 12 1 39780 32640 16320
+23 12 1 38982 32656 16286
+24 12 1 37143 32013 17594
+25 12 1 26056 26572 28660
+26 12 1 28247 27187 27777
+27 12 1 28823 27140 28124
+28 12 1 29683 27084 28532
+29 12 1 30339 27014 28882
+30 12 1 30964 26931 29435
+31 12 1 31827 26831 29728
+32 12 1 32418 26719 30099
+33 12 1 33139 26624 30656
+34 12 1 34005 26545 30943
+35 12 1 34586 26482 31437
+36 12 1 35425 26429 31755
+37 12 1 36276 26386 32228
+38 12 1 36855 26350 32807
+39 12 1 37705 26319 33134
+40 12 1 38528 26291 33556
+41 12 1 39237 26265 33915
+42 12 1 39818 26239 34353
+43 12 1 40671 26211 34888
+44 12 1 41307 26180 35190
+45 12 1 41928 26144 35624
+46 12 1 42778 26101 36152
+47 12 1 43368 26050 36451
+48 12 1 44037 25989 36869
+49 12 1 44880 25916 37168
+50 12 1 45442 25829 37584
+51 12 1 46154 25727 38109
+52 12 1 46987 25633 38364
+53 12 1 47533 25552 38829
+54 12 1 48282 25484 39104
+55 12 1 49101 25427 39517
+56 12 1 49639 25380 40079
+57 12 1 50413 25340 40388
+58 12 1 51209 25306 40778
+59 12 1 52006 25276 41351
+60 12 1 52540 25247 41698
+61 12 1 53333 25219 42075
+62 12 1 54110 25189 42485
+63 12 1 54637 25154 42803
+0 13 1 9772 29565 17689
+1 13 1 10433 29511 18119
+2 13 1 11004 29466 18434
+3 13 1 11814 29427 18837
+4 13 1 8861 29073 19511
+5 13 1 29673 30766 18119
+6 13 1 56501 33090 15906
+7 13 1 50343 32640 16320
+8 13 1 49726 32640 16320
+9 13 1 49110 32640 16320
+10 13 1 48269 32640 16320
+11 13 1 47674 32640 16320
+12 13 1 47027 32640 16320
+13 13 1 46185 32640 16320
+14 13 1 45613 32640 16320
+15 13 1 44924 32640 16320
+16 13 1 44083 32640 16320
+17 13 1 43526 32640 16320
+18 13 1 42780 32640 16320
+19 13 1 41940 32640 16320
+20 13 1 41379 32640 16320
+21 13 1 40558 32640 16320
+22 13 1 39714 32640 16320
+23 13 1 39006 32681 16212
+24 13 1 36598 31933 18164
+25 13 1 25918 27692 29271
+26 13 1 28265 28249 28163
+27 13 1 28847 28211 28555
+28 13 1 29708 28167 28946
+29 13 1 30346 28112 29497
+30 13 1 30989 28046 29791
+31 13 1 31853 27966 30200
+32 13 1 32437 27871 30743
+33 13 1 33188 27763 31004
+34 13 1 34057 27665 31499
+35 13 1 34649 27585 31784
+36 13 1 35516 27518 32287
+37 13 1 36333 27464 32844
+38 13 1 36907 27418 33148
+39 13 1 37752 27380 33597
+40 13 1 38572 27347 33924
+41 13 1 39252 27318 34402
+42 13 1 39848 27291 34909
+43 13 1 40701 27265 35205
+44 13 1 41316 27237 35681
+45 13 1 41960 27206 36181
+46 13 1 42809 27170 36497
+47 13 1 43384 27128 36918
+48 13 1 44080 27077 37221
+49 13 1 44920 27015 37687
+50 13 1 45473 26942 38199
+51 13 1 46214 26854 38448
+52 13 1 47042 26750 38922
+53 13 1 47585 26653 39168
+54 13 1 48361 26571 39616
+55 13 1 49172 26502 40156
+56 13 1 49713 26445 40431
+57 13 1 50511 26397 40865
+58 13 1 51308 26357 41423
+59 13 1 52086 26322 41733
+60 13 1 52618 26292 42147
+61 13 1 53422 26263 42516
+62 13 1 54175 26234 42878
+63 13 1 54705 26203 43441
+0 14 1 9795 30645 18158
+1 14 1 10441 30583 18477
+2 14 1 11023 30530 18874
+3 14 1 11831 30485 19177
+4 14 1 8385 30201 20133
+5 14 1 31336 31462 18321
+6 14 1 56114 32926 15860
+7 14 1 50324 32640 16320
+8 14 1 49720 32640 16320
+9 14 1 49089 32640 16320
+10 14 1 48249 32640 16320
+11 14 1 47666 32640 16320
+12 14 1 47002 32640 16320
+13 14 1 46162 32640 16320
+14 14 1 45599 32640 16320
+15 14 1 44891 32640 16320
+16 14 1 44052 32640 16320
+17 14 1 43498 32640 16320
+18 14 1 42731 32640 16320
+19 14 1 41894 32640 16320
+20 14 1 41327 32640 16320
+21 14 1 40490 32640 16320
+22 14 1 39654 32640 16320
+23 14 1 39072 32703 16092
+24 14 1 36039 31949 18794
+25 14 1 25846 28860 29839
+26 14 1 28279 29307 28563
+27 14 1 28871 29275 28996
+28 14 1 29731 29239 29532
+29 14 1 30353 29196 29829
+30 14 1 31016 29145 30276
+31 14 1 31882 29082 30806
+32 14 1 32463 29007 31084
+33 14 1 33246 28919 31575
+34 14 1 34119 28817 31846
+35 14 1 34723 28715 32369
+36 14 1 35592 28631 32895
+37 14 1 36376 28560 33179
+38 14 1 36949 28501 33646
+39 14 1 37790 28452 33944
+40 14 1 38609 28411 34455
+41 14 1 39263 28376 34927
+42 14 1 39877 28346 35221
+43 14 1 40730 28318 35733
+44 14 1 41326 28291 36196
+45 14 1 41995 28263 36537
+46 14 1 42843 28232 36948
+47 14 1 43407 28196 37261
+48 14 1 44130 28154 37771
+49 14 1 44967 28103 38257
+50 14 1 45516 28041 38529
+51 14 1 46286 27967 39012
+52 14 1 47107 27878 39261
+53 14 1 47653 27773 39742
+54 14 1 48455 27673 40246
+55 14 1 49252 27589 40495
+56 14 1 49792 27519 40964
+57 14 1 50600 27461 41491
+58 14 1 51393 27413 41771
+59 14 1 52146 27373 42214
+60 14 1 52680 27339 42540
+61 14 1 53495 27308 42950
+62 14 1 54223 27279 43500
+63 14 1 54759 27249 43836
+0 15 1 9817 31738 18537
+1 15 1 10448 31665 18920
+2 15 1 11043 31603 19219
+3 15 1 11849 31550 19752
+4 15 1 7942 31365 20702
+5 15 1 33059 32072 18273
+6 15 1 55631 32779 15865
+7 15 1 50304 32640 16320
+8 15 1 49713 32640 16320
+9 15 1 49066 32640 16320
+10 15 1 48227 32640 16320
+11 15 1 47656 32640 16320
+12 15 1 46974 32640 16320
+13 15 1 46136 32640 16320
+14 15 1 45580 32640 16320
+15 15 1 44852 32640 16320
+16 15 1 44016 32640 16320
+17 15 1 43463 32640 16320
+18 15 1 42673 32640 16320
+19 15 1 41839 32640 16320
+20 15 1 41268 32640 16320
+21 15 1 40428 32640 16320
+22 15 1 39600 32640 16320
+23 15 1 39175 32709 15931
+24 15 1 35466 32064 19461
+25 15 1 25836 30064 30311
+26 15 1 28289 30365 29041
+27 15 1 28893 30335 29552
+28 15 1 29754 30304 29849
+29 15 1 30361 30269 30333
+30 15 1 31047 30229 30837
+31 15 1 31915 30181 31145
+32 15 1 32497 30123 31621
+33 15 1 33315 30055 31914
+34 15 1 34192 29974 32473
+35 15 1 34788 29879 32971
+36 15 1 35655 29774 33239
+37 15 1 36407 29682 33712
+38 15 1 36985 29606 33987
+39 15 1 37820 29541 34522
+40 15 1 38640 29488 34951
+41 15 1 39271 29444 35247
+42 15 1 39906 29406 35786
+43 15 1 40758 29374 36206
+44 15 1 41340 29345 36575
+45 15 1 42036 29317 36966
+46 15 1 42881 29289 37294
+47 15 1 43438 29257 37839
+48 15 1 44190 29222 38290
+49 15 1 45023 29179 38599
+50 15 1 45573 29128 39073
+51 15 1 46369 29066 39346
+52 15 1 47183 28991 39877
+53 15 1 47731 28900 40348
+54 15 1 48545 28793 40584
+55 15 1 49317 28692 41079
+56 15 1 49856 28607 41561
+57 15 1 50669 28536 41821
+58 15 1 51461 28478 42280
+59 15 1 52190 28429 42568
+60 15 1 52730 28389 43023
+61 15 1 53553 28355 43545
+62 15 1 54257 28324 43860
+63 15 1 54803 28294 44245
+0 16 1 9838 32843 18979
+1 16 1 10454 32759 19285
+2 16 1 11063 32687 19825
+3 16 1 11866 32625 20182
+4 16 1 7560 32563 21185
+5 16 1 34842 32591 18309
+6 16 1 55065 32655 15874
+7 16 1 50283 32640 16320
+8 16 1 49705 32640 16320
+9 16 1 49040 32640 16320
+10 16 1 48203 32640 16320
+11 16 1 47642 32640 16320
+12 16 1 46941 32640 16320
+13 16 1 46105 32640 16320
+14 16 1 45554 32640 16320
+15 16 1 44805 32640 16320
+16 16 1 43972 32640 16320
+17 16 1 43417 32640 16320
+18 16 1 42605 32640 16320
+19 16 1 41779 32640 16320
+20 16 1 41214 32640 16320
+21 16 1 40374 32640 16320
+22 16 1 39552 32640 16320
+23 16 1 39309 32691 15726
+24 16 1 34878 32286 20199
+25 16 1 25885 31290 30865
+26 16 1 28297 31426 29569
+27 16 1 28916 31394 29864
+28 16 1 29778 31365 30379
+29 16 1 30372 31335 30847
+30 16 1 31084 31302 31191
+31 16 1 31954 31265 31636
+32 16 1 32542 31220 31957
+33 16 1 33396 31168 32545
+34 16 1 34255 31106 33030
+35 16 1 34841 31032 33322
+36 16 1 35707 30943 33800
+37 16 1 36429 30837 34068
+38 16 1 37015 30737 34612
+39 16 1 37846 30653 34995
+40 16 1 38668 30583 35295
+41 16 1 39279 30524 35848
+42 16 1 39937 30476 36221
+43 16 1 40789 30436 36616
+44 16 1 41360 30401 36980
+45 16 1 42083 30371 37328
+46 16 1 42926 30342 37896
+47 16 1 43481 30313 38306
+48 16 1 44261 30282 38661
+49 16 1 45088 30246 39110
+50 16 1 45644 30204 39417
+51 16 1 46461 30152 39981
+52 16 1 47253 30089 40421
+53 16 1 47797 30013 40684
+54 16 1 48618 29921 41211
+55 16 1 49366 29812 41644
+56 16 1 49908 29709 41893
+57 16 1 50724 29623 42350
+58 16 1 51517 29552 42611
+59 16 1 52221 29493 43102
+60 16 1 52771 29445 43584
+61 16 1 53599 29405 43888
+62 16 1 54280 29370 44292
+63 16 1 54839 29339 44628
+0 17 1 9859 33961 19376
+1 17 1 10460 33868 19912
+2 17 1 11085 33784 20227
+3 17 1 11885 33711 20605
+4 17 1 7272 33793 21690
+5 17 1 36686 33013 18194
+6 17 1 54433 32562 15915
+7 17 1 50259 32640 16320
+8 17 1 49693 32640 16320
+9 17 1 49010 32640 16320
+10 17 1 48176 32640 16320
+11 17 1 47622 32640 16320
+12 17 1 46901 32640 16320
+13 17 1 46069 32640 16320
+14 17 1 45520 32640 16320
+15 17 1 44749 32640 16320
+16 17 1 43920 32640 16320
+17 17 1 43360 32640 16320
+18 17 1 42530 32640 16320
+19 17 1 41727 32640 16320
+20 17 1 41166 32640 16320
+21 17 1 40327 32640 16320
+22 17 1 39509 32640 16320
+23 17 1 39467 32639 15475
+24 17 1 34272 32624 21091
+25 17 1 25990 32528 31350
+26 17 1 28305 32497 29888
+27 17 1 28941 32459 30427
+28 17 1 29803 32426 30852
+29 17 1 30388 32397 31228
+30 17 1 31127 32367 31635
+31 17 1 32000 32337 31983
+32 17 1 32599 32302 32587
+33 17 1 33480 32262 33048
+34 17 1 34305 32215 33385
+35 17 1 34885 32157 33858
+36 17 1 35750 32087 34164
+37 17 1 36444 32003 34725
+38 17 1 37042 31902 35071
+39 17 1 37869 31793 35377
+40 17 1 38694 31700 35928
+41 17 1 39290 31624 36255
+42 17 1 39972 31560 36668
+43 17 1 40823 31508 37000
+44 17 1 41387 31465 37370
+45 17 1 42138 31428 37949
+46 17 1 42978 31396 38314
+47 17 1 43535 31367 38718
+48 17 1 44342 31337 39129
+49 17 1 45162 31306 39477
+50 17 1 45716 31270 40060
+51 17 1 46540 31227 40462
+52 17 1 47306 31175 40764
+53 17 1 47851 31111 41320
+54 17 1 48678 31034 41724
+55 17 1 49401 30940 41989
+56 17 1 49950 30828 42431
+57 17 1 50767 30724 42680
+58 17 1 51561 30637 43194
+59 17 1 52242 30566 43626
+60 17 1 52805 30507 43927
+61 17 1 53638 30459 44337
+62 17 1 54295 30420 44646
+63 17 1 54870 30385 45120
+0 18 1 9881 35069 20002
+1 18 1 10467 34987 20292
+2 18 1 11109 34895 20680
+3 18 1 11906 34811 20995
+4 18 1 6612 35091 22279
+5 18 1 41475 33083 17678
+6 18 1 58979 32038 15063
+7 18 1 55273 32169 15358
+8 18 1 54739 32151 15259
+9 18 1 53834 32151 15173
+10 18 1 52624 32165 15165
+11 18 1 51675 32192 15168
+12 18 1 50408 32229 15194
+13 18 1 49003 32275 15288
+14 18 1 47931 32326 15384
+15 18 1 46582 32381 15526
+16 18 1 45247 32437 15664
+17 18 1 44263 32491 15815
+18 18 1 43055 32541 15976
+19 18 1 41994 32584 16116
+20 18 1 41246 32616 16232
+21 18 1 40306 32636 16304
+22 18 1 39471 32640 16320
+23 18 1 39642 32546 15213
+24 18 1 33650 33087 21908
+25 18 1 26147 33768 31628
+26 18 1 28312 33582 30485
+27 18 1 28969 33535 30866
+28 18 1 29831 33495 31266
+29 18 1 30411 33461 31635
+30 18 1 31180 33431 32006
+31 18 1 32055 33402 32615
+32 18 1 32668 33373 33038
+33 18 1 33550 33340 33431
+34 18 1 34342 33303 33879
+35 18 1 34921 33258 34231
+36 18 1 35785 33204 34805
+37 18 1 36455 33138 35141
+38 18 1 37068 33057 35487
+39 18 1 37891 32960 36034
+40 18 1 38721 32847 36322
+41 18 1 39304 32746 36738
+42 18 1 40011 32663 37039
+43 18 1 40862 32595 37430
+44 18 1 41423 32539 38004
+45 18 1 42201 32492 38325
+46 18 1 43038 32454 38775
+47 18 1 43602 32421 39141
+48 18 1 44429 32390 39532
+49 18 1 45224 32360 40118
+50 18 1 45774 32328 40483
+51 18 1 46604 32292 40829
+52 18 1 47345 32249 41398
+53 18 1 47894 32196 41775
+54 18 1 48727 32131 42075
+55 18 1 49426 32052 42508
+56 18 1 49985 31955 42780
+57 18 1 50801 31841 43305
+58 18 1 51598 31737 43681
+59 18 1 52256 31650 43984
+60 18 1 52835 31579 44385
+61 18 1 53670 31521 44678
+62 18 1 54306 31474 45187
+63 18 1 54898 31434 45632
+0 19 1 9904 36164 20353
+1 19 1 10476 36096 20761
+2 19 1 11137 36016 21063
+3 19 1 11931 35925 21436
+4 19 1 12397 35862 22028
+5 19 1 16266 35581 21803
+6 19 1 20087 35305 21704
+7 19 1 22017 35149 21661
+8 19 1 23975 34979 21654
+9 19 1 25907 34814 21743
+10 19 1 27740 34654 21523
+11 19 1 29361 34497 21417
+12 19 1 30909 34344 21353
+13 19 1 32316 34192 21021
+14 19 1 33592 34043 20839
+15 19 1 34717 33894 20444
+16 19 1 35690 33746 20145
+17 19 1 36603 33598 19792
+18 19 1 37276 33448 19294
+19 19 1 37834 33297 18833
+20 19 1 38377 33142 18264
+21 19 1 38596 32984 17703
+22 19 1 38666 32822 17065
+23 19 1 39766 32424 14974
+24 19 1 33278 33613 22585
+25 19 1 26238 35032 32229
+26 19 1 28321 34688 30900
+27 19 1 29002 34627 31313
+28 19 1 29865 34577 31649
+29 19 1 30442 34534 32039
+30 19 1 31243 34498 32643
+31 19 1 32122 34466 33019
+32 19 1 32731 34437 33468
+33 19 1 33608 34407 33876
+34 19 1 34369 34376 34279
+35 19 1 34952 34340 34851
+36 19 1 35815 34297 35176
+37 19 1 36462 34246 35570
+38 19 1 37093 34182 36117
+39 19 1 37915 34105 36410
+40 19 1 38749 34010 36828
+41 19 1 39323 33897 37111
+42 19 1 40057 33789 37520
+43 19 1 40906 33700 38071
+44 19 1 41468 33627 38353
+45 19 1 42274 33568 38837
+46 19 1 43107 33519 39157
+47 19 1 43670 33479 39590
+48 19 1 44502 33444 40165
+49 19 1 45272 33413 40493
+50 19 1 45822 33382 40887
+51 19 1 46656 33350 41452
+52 19 1 47373 33313 41804
+53 19 1 47930 33269 42141
+54 19 1 48767 33215 42554
+55 19 1 49443 33149 42872
+56 19 1 50015 33068 43416
+57 19 1 50829 32969 43757
+58 19 1 51629 32852 44066
+59 19 1 52266 32748 44443
+60 19 1 52861 32662 44734
+61 19 1 53698 32591 45263
+62 19 1 54314 32534 45655
+63 19 1 54924 32487 46002
+0 20 1 9929 37248 20822
+1 20 1 10489 37191 21120
+2 20 1 11169 37125 21540
+3 20 1 11960 37047 22065
+4 20 1 12774 36957 22321
+5 20 1 13239 36877 22779
+6 20 1 13681 36820 23098
+7 20 1 14202 36776 23563
+8 20 1 14358 36749 24206
+9 20 1 14699 36734 24619
+10 20 1 15125 36728 25158
+11 20 1 15262 36730 25877
+12 20 1 15721 36736 26383
+13 20 1 16281 36744 27002
+14 20 1 16599 36750 27545
+15 20 1 17337 36753 28108
+16 20 1 18120 36748 28825
+17 20 1 18707 36733 29340
+18 20 1 19763 36705 29779
+19 20 1 20767 36661 30215
+20 20 1 21654 36596 30513
+21 20 1 22950 36508 30928
+22 20 1 24279 36393 31040
+23 20 1 25358 36264 31103
+24 20 1 26747 36034 30826
+25 20 1 27715 35913 30998
+26 20 1 28333 35818 31378
+27 20 1 29041 35741 31688
+28 20 1 29905 35676 32093
+29 20 1 30484 35621 32682
+30 20 1 31319 35575 33011
+31 20 1 32190 35536 33505
+32 20 1 32784 35501 33867
+33 20 1 33657 35469 34317
+34 20 1 34388 35439 34878
+35 20 1 34978 35408 35191
+36 20 1 35841 35373 35630
+37 20 1 36469 35332 36161
+38 20 1 37120 35283 36472
+39 20 1 37941 35222 36891
+40 20 1 38780 35146 37191
+41 20 1 39349 35054 37640
+42 20 1 40109 34943 38162
+43 20 1 40957 34828 38411
+44 20 1 41524 34734 38910
+45 20 1 42355 34657 39188
+46 20 1 43168 34594 39658
+47 20 1 43726 34544 40209
+48 20 1 44562 34502 40504
+49 20 1 45308 34466 40944
+50 20 1 45862 34434 41491
+51 20 1 46700 34403 41823
+52 20 1 47394 34370 42193
+53 20 1 47961 34333 42578
+54 20 1 48800 34288 42947
+55 20 1 49455 34232 43493
+56 20 1 50041 34164 43820
+57 20 1 50852 34081 44158
+58 20 1 51657 33979 44520
+59 20 1 52274 33860 44821
+60 20 1 52887 33757 45352
+61 20 1 53724 33672 45690
+62 20 1 54322 33602 46054
+63 20 1 54951 33546 46409
+0 21 1 9959 38322 21164
+1 21 1 10508 38275 21629
+2 21 1 11207 38219 22136
+3 21 1 11997 38155 22396
+4 21 1 12810 38080 22854
+5 21 1 13350 37993 23110
+6 21 1 14084 37906 23538
+7 21 1 14903 37831 24073
+8 21 1 15442 37766 24338
+9 21 1 16210 37710 24768
+10 21 1 17025 37662 25329
+11 21 1 17572 37621 25625
+12 21 1 18382 37586 26091
+13 21 1 19183 37555 26428
+14 21 1 19734 37528 26846
+15 21 1 20564 37503 27421
+16 21 1 21322 37479 27763
+17 21 1 21878 37454 28149
+18 21 1 22722 37428 28546
+19 21 1 23425 37398 28927
+20 21 1 24002 37364 29490
+21 21 1 24829 37323 29814
+22 21 1 25646 37275 30195
+23 21 1 26269 37218 30747
+24 21 1 26914 37151 31048
+25 21 1 27772 37070 31463
+26 21 1 28352 36976 31760
+27 21 1 29088 36880 32177
+28 21 1 29953 36798 32740
+29 21 1 30540 36727 33030
+30 21 1 31406 36667 33551
+31 21 1 32244 36615 33869
+32 21 1 32829 36572 34358
+33 21 1 33699 36534 34899
+34 21 1 34402 36500 35198
+35 21 1 35003 36468 35677
+36 21 1 35865 36437 36181
+37 21 1 36477 36403 36513
+38 21 1 37149 36363 36924
+39 21 1 37971 36315 37240
+40 21 1 38814 36256 37733
+41 21 1 39381 36182 38240
+42 21 1 40168 36092 38501
+43 21 1 41014 35981 39000
+44 21 1 41589 35863 39251
+45 21 1 42428 35764 39747
+46 21 1 43217 35684 40261
+47 21 1 43772 35619 40529
+48 21 1 44611 35566 41007
+49 21 1 45334 35523 41523
+50 21 1 45895 35487 41839
+51 21 1 46735 35455 42236
+52 21 1 47408 35423 42590
+53 21 1 47987 35389 43009
+54 21 1 48828 35351 43540
+55 21 1 49464 35305 43863
+56 21 1 50066 35248 44226
+57 21 1 50874 35178 44580
+58 21 1 51681 35091 44931
+59 21 1 52281 34986 45460
+60 21 1 52913 34866 45748
+61 21 1 53749 34764 46124
+62 21 1 54330 34680 46450
+63 21 1 54980 34612 46781
+0 22 1 9993 39387 21706
+1 22 1 10533 39348 22184
+2 22 1 11253 39302 22461
+3 22 1 12042 39248 22924
+4 22 1 12853 39186 23185
+5 22 1 13389 39114 23656
+6 22 1 14144 39030 24163
+7 22 1 14957 38943 24404
+8 22 1 15496 38866 24866
+9 22 1 16286 38800 25400
+10 22 1 17094 38743 25668
+11 22 1 17642 38694 26157
+12 22 1 18461 38652 26453
+13 22 1 19238 38616 26907
+14 22 1 19787 38584 27460
+15 22 1 20622 38557 27776
+16 22 1 21354 38531 28192
+17 22 1 21916 38507 28552
+18 22 1 22762 38483 28977
+19 22 1 23440 38458 29523
+20 22 1 24030 38430 29833
+21 22 1 24853 38399 30257
+22 22 1 25673 38362 30795
+23 22 1 26278 38318 31105
+24 22 1 26947 38266 31524
+25 22 1 27804 38204 31832
+26 22 1 28378 38130 32286
+27 22 1 29145 38043 32823
+28 22 1 30011 37943 33086
+29 22 1 30610 37854 33611
+30 22 1 31484 37777 33896
+31 22 1 32287 37710 34414
+32 22 1 32867 37653 34927
+33 22 1 33734 37605 35212
+34 22 1 34413 37564 35724
+35 22 1 35026 37528 36194
+36 22 1 35889 37496 36543
+37 22 1 36487 37464 36939
+38 22 1 37181 37430 37270
+39 22 1 38006 37391 37798
+40 22 1 38853 37344 38276
+41 22 1 39422 37286 38574
+42 22 1 40235 37213 39069
+43 22 1 41077 37123 39340
+44 22 1 41648 37013 39866
+45 22 1 42489 36892 40332
+46 22 1 43254 36790 40579
+47 22 1 43811 36707 41085
+48 22 1 44653 36641 41561
+49 22 1 45353 36587 41863
+50 22 1 45923 36543 42277
+51 22 1 46766 36507 42601
+52 22 1 47418 36474 43066
+53 22 1 48011 36441 43568
+54 22 1 48854 36407 43894
+55 22 1 49471 36368 44274
+56 22 1 50090 36320 44617
+57 22 1 50895 36261 45021
+58 22 1 51705 36189 45546
+59 22 1 52290 36099 45827
+60 22 1 52940 35989 46216
+61 22 1 53774 35869 46510
+62 22 1 54342 35769 46857
+63 22 1 55011 35687 47397
+0 23 1 10035 40445 22217
+1 23 1 10568 40412 22520
+2 23 1 11310 40374 22975
+3 23 1 12098 40330 23248
+4 23 1 12905 40278 23761
+5 23 1 13439 40219 24245
+6 23 1 14217 40149 24492
+7 23 1 15022 40069 24988
+8 23 1 15565 39982 25486
+9 23 1 16372 39904 25734
+10 23 1 17160 39836 26230
+11 23 1 17704 39778 26495
+12 23 1 18529 39727 26981
+13 23 1 19282 39684 27502
+14 23 1 19833 39647 27798
+15 23 1 20672 39614 28236
+16 23 1 21378 39586 28562
+17 23 1 21949 39560 29027
+18 23 1 22797 39536 29546
+19 23 1 23450 39513 29845
+20 23 1 24057 39489 30309
+21 23 1 24876 39462 30821
+22 23 1 25701 39433 31145
+23 23 1 26290 39399 31560
+24 23 1 26985 39358 31877
+25 23 1 27843 39310 32369
+26 23 1 28414 39253 32887
+27 23 1 29214 39184 33164
+28 23 1 30081 39102 33686
+29 23 1 30679 39004 33959
+30 23 1 31549 38907 34495
+31 23 1 32318 38823 34974
+32 23 1 32899 38750 35245
+33 23 1 33765 38688 35779
+34 23 1 34420 38636 36214
+35 23 1 35050 38592 36571
+36 23 1 35912 38555 36953
+37 23 1 36500 38521 37291
+38 23 1 37218 38489 37844
+39 23 1 38047 38455 38284
+40 23 1 38896 38416 38629
+41 23 1 39471 38369 39098
+42 23 1 40309 38311 39405
+43 23 1 41132 38239 39970
+44 23 1 41697 38149 40416
+45 23 1 42541 38039 40669
+46 23 1 43282 37915 41187
+47 23 1 43843 37811 41615
+48 23 1 44687 37727 41903
+49 23 1 45366 37660 42324
+50 23 1 45947 37606 42622
+51 23 1 46792 37562 43124
+52 23 1 47425 37525 43586
+53 23 1 48034 37491 43922
+54 23 1 48877 37459 44306
+55 23 1 49478 37424 44640
+56 23 1 50115 37383 45094
+57 23 1 50917 37334 45599
+58 23 1 51730 37273 45889
+59 23 1 52301 37197 46298
+60 23 1 52971 37103 46588
+61 23 1 53802 36989 46968
+62 23 1 54357 36870 47487
+63 23 1 55046 36772 47758
+0 24 1 10085 41474 22579
+1 24 1 10615 41450 23014
+2 24 1 11378 41422 23307
+3 24 1 12168 41389 23852
+4 24 1 12969 41350 24305
+5 24 1 13505 41304 24572
+6 24 1 14304 41249 25107
+7 24 1 15093 41184 25575
+8 24 1 15633 41109 25824
+9 24 1 16449 41023 26313
+10 24 1 17213 40943 26559
+11 24 1 17757 40874 27070
+12 24 1 18588 40814 27551
+13 24 1 19315 40762 27834
+14 24 1 19872 40717 28283
+15 24 1 20715 40678 28582
+16 24 1 21396 40645 29081
+17 24 1 21979 40616 29566
+18 24 1 22828 40590 29858
+19 24 1 23459 40565 30358
+20 24 1 24085 40542 30835
+21 24 1 24902 40519 31177
+22 24 1 25730 40494 31581
+23 24 1 26308 40466 31907
+24 24 1 27032 40434 32431
+25 24 1 27889 40396 32919
+26 24 1 28463 40351 33226
+27 24 1 29296 40298 33735
+28 24 1 30152 40233 34031
+29 24 1 30737 40154 34596
+30 24 1 31604 40059 35045
+31 24 1 32342 39955 35309
+32 24 1 32928 39863 35854
+33 24 1 33792 39785 36252
+34 24 1 34427 39719 36606
+35 24 1 35075 39663 36976
+36 24 1 35937 39618 37318
+37 24 1 36517 39579 37884
+38 24 1 37259 39544 38281
+39 24 1 38094 39512 38672
+40 24 1 38946 39478 39102
+41 24 1 39529 39439 39447
+42 24 1 40377 39392 40039
+43 24 1 41176 39334 40461
+44 24 1 41738 39261 40753
+45 24 1 42584 39170 41304
+46 24 1 43302 39058 41696
+47 24 1 43871 38933 41972
+48 24 1 44717 38828 42385
+49 24 1 45376 38744 42665
+50 24 1 45970 38676 43190
+51 24 1 46815 38622 43606
+52 24 1 47432 38578 43952
+53 24 1 48057 38541 44332
+54 24 1 48899 38508 44659
+55 24 1 49486 38475 45155
+56 24 1 50141 38439 45626
+57 24 1 50940 38397 45938
+58 24 1 51755 38345 46351
+59 24 1 52315 38281 46647
+60 24 1 53004 38202 47081
+61 24 1 53831 38104 47589
+62 24 1 54378 37984 47828
+63 24 1 55086 37868 48218
+0 25 1 10147 42652 23046
+1 25 1 10676 42617 23367
+2 25 1 11461 42579 23934
+3 25 1 12255 42538 24351
+4 25 1 13042 42492 24645
+5 25 1 13578 42440 25204
+6 25 1 14386 42382 25638
+7 25 1 15153 42316 25909
+8 25 1 15693 42242 26393
+9 25 1 16516 42158 26648
+10 25 1 17254 42064 27177
+11 25 1 17803 41973 27613
+12 25 1 18639 41894 27890
+13 25 1 19339 41825 28337
+14 25 1 19907 41767 28619
+15 25 1 20752 41717 29145
+16 25 1 21408 41674 29589
+17 25 1 22007 41639 29880
+18 25 1 22857 41608 30408
+19 25 1 23467 41582 30846
+20 25 1 24116 41560 31207
+21 25 1 24931 41539 31596
+22 25 1 25764 41518 31931
+23 25 1 26334 41498 32481
+24 25 1 27088 41475 32932
+25 25 1 27945 41448 33275
+26 25 1 28527 41417 33757
+27 25 1 29387 41378 34081
+28 25 1 30208 41330 34671
+29 25 1 30785 41270 35104
+30 25 1 31650 41196 35396
+31 25 1 32358 41103 35953
+32 25 1 32954 40995 36316
+33 25 1 33817 40897 36659
+34 25 1 34435 40815 37018
+35 25 1 35102 40745 37359
+36 25 1 35965 40688 37928
+37 25 1 36540 40640 38281
+38 25 1 37307 40601 38711
+39 25 1 38149 40566 39094
+40 25 1 39002 40533 39476
+41 25 1 39586 40498 40082
+42 25 1 40435 40459 40476
+43 25 1 41210 40411 40813
+44 25 1 41772 40352 41384
+45 25 1 42620 40278 41763
+46 25 1 43318 40186 42061
+47 25 1 43895 40071 42468
+48 25 1 44742 39945 42741
+49 25 1 45384 39841 43273
+50 25 1 45991 39757 43639
+51 25 1 46837 39690 43993
+52 25 1 47438 39637 44359
+53 25 1 48081 39594 44684
+54 25 1 48922 39557 45211
+55 25 1 49496 39523 45640
+56 25 1 50170 39489 45981
+57 25 1 50967 39452 46384
+58 25 1 51783 39408 46689
+59 25 1 52334 39355 47172
+60 25 1 53042 39287 47660
+61 25 1 53864 39204 47906
+62 25 1 54403 39101 48343
+63 25 1 55130 38977 48842
+0 26 1 10220 43879 23434
+1 26 1 10751 43859 24008
+2 26 1 11548 43836 24384
+3 26 1 12340 43811 24711
+4 26 1 13105 43781 25282
+5 26 1 13640 43746 25681
+6 26 1 14457 43704 25982
+7 26 1 15200 43656 26446
+8 26 1 15743 43598 26728
+9 26 1 16572 43532 27281
+10 26 1 17285 43454 27684
+11 26 1 17842 43365 27967
+12 26 1 18682 43271 28401
+13 26 1 19357 43187 28678
+14 26 1 19938 43113 29219
+15 26 1 20784 43046 29621
+16 26 1 21417 42987 29917
+17 26 1 22036 42935 30464
+18 26 1 22885 42888 30861
+19 26 1 23478 42845 31241
+20 26 1 24152 42806 31611
+21 26 1 24967 42770 31957
+22 26 1 25804 42734 32524
+23 26 1 26370 42700 32936
+24 26 1 27156 42664 33318
+25 26 1 28012 42626 33764
+26 26 1 28603 42585 34118
+27 26 1 29464 42538 34723
+28 26 1 30251 42484 35133
+29 26 1 30826 42421 35460
+30 26 1 31689 42345 36034
+31 26 1 32370 42255 36392
+32 26 1 32979 42147 36734
+33 26 1 33841 42021 37085
+34 26 1 34444 41907 37426
+35 26 1 35134 41812 37986
+36 26 1 35995 41735 38297
+37 26 1 36569 41672 38753
+38 26 1 37361 41623 39091
+39 26 1 38212 41584 39505
+40 26 1 39060 41551 40111
+41 26 1 39634 41522 40475
+42 26 1 40485 41492 40857
+43 26 1 41237 41457 41435
+44 26 1 41802 41414 41799
+45 26 1 42651 41359 42128
+46 26 1 43329 41287 42535
+47 26 1 43916 41195 42845
+48 26 1 44764 41079 43378
+49 26 1 45390 40953 43696
+50 26 1 46012 40850 44051
+51 26 1 46857 40768 44397
+52 26 1 47446 40702 44724
+53 26 1 48106 40650 45268
+54 26 1 48945 40607 45649
+55 26 1 49509 40571 46023
+56 26 1 50202 40537 46403
+57 26 1 50997 40503 46723
+58 26 1 51814 40464 47245
+59 26 1 52357 40418 47701
+60 26 1 53084 40362 47966
+61 26 1 53901 40291 48454
+62 26 1 54435 40202 48936
+63 26 1 55180 40094 49166
+0 27 1 10287 44889 24071
+1 27 1 10817 44868 24409
+2 27 1 11623 44846 24774
+3 27 1 12412 44823 25345
+4 27 1 13154 44797 25711
+5 27 1 13693 44769 26044
+6 27 1 14517 44736 26479
+7 27 1 15235 44699 26797
+8 27 1 15786 44655 27361
+9 27 1 16620 44605 27737
+10 27 1 17307 44548 28045
+11 27 1 17877 44481 28468
+12 27 1 18719 44405 28762
+13 27 1 19369 44322 29308
+14 27 1 19967 44248 29666
+15 27 1 20814 44184 29974
+16 27 1 21426 44129 30529
+17 27 1 22067 44082 30885
+18 27 1 22915 44042 31283
+19 27 1 23493 44008 31631
+20 27 1 24195 43979 31990
+21 27 1 25012 43954 32568
+22 27 1 25853 43932 32940
+23 27 1 26420 43911 33359
+24 27 1 27239 43891 33766
+25 27 1 28089 43869 34151
+26 27 1 28670 43846 34760
+27 27 1 29529 43818 35144
+28 27 1 30284 43784 35507
+29 27 1 30861 43741 36086
+30 27 1 31722 43688 36441
+31 27 1 32379 43620 36795
+32 27 1 33004 43535 37160
+33 27 1 33865 43429 37521
+34 27 1 34456 43300 38064
+35 27 1 35169 43180 38341
+36 27 1 36031 43077 38804
+37 27 1 36605 42988 39103
+38 27 1 37422 42913 39542
+39 27 1 38282 42848 40138
+40 27 1 39107 42791 40470
+41 27 1 39676 42739 40895
+42 27 1 40527 42689 41467
+43 27 1 41258 42636 41815
+44 27 1 41828 42578 42176
+45 27 1 42678 42511 42571
+46 27 1 43338 42431 42925
+47 27 1 43937 42335 43470
+48 27 1 44785 42220 43778
+49 27 1 45396 42080 44133
+50 27 1 46034 41941 44456
+51 27 1 46878 41830 44787
+52 27 1 47456 41743 45333
+53 27 1 48133 41676 45665
+54 27 1 48970 41624 46068
+55 27 1 49526 41583 46418
+56 27 1 50237 41550 46757
+57 27 1 51032 41520 47305
+58 27 1 51847 41490 47722
+59 27 1 52386 41455 48013
+60 27 1 53131 41412 48541
+61 27 1 53942 41357 48996
+62 27 1 54474 41286 49253
+63 27 1 55236 41196 49725
+0 28 1 10340 45933 24430
+1 28 1 10874 45912 24835
+2 28 1 11684 45891 25394
+3 28 1 12474 45869 25733
+4 28 1 13191 45847 26098
+5 28 1 13738 45822 26500
+6 28 1 14568 45794 26857
+7 28 1 15260 45763 27419
+8 28 1 15823 45727 27774
+9 28 1 16661 45685 28108
+10 28 1 17322 45637 28514
+11 28 1 17908 45582 28844
+12 28 1 18751 45518 29399
+13 28 1 19378 45445 29726
+14 28 1 19997 45364 30054
+15 28 1 20843 45287 30606
+16 28 1 21436 45219 30924
+17 28 1 22102 45160 31336
+18 28 1 22949 45108 31661
+19 28 1 23516 45063 32037
+20 28 1 24248 45024 32615
+21 28 1 25068 44990 32949
+22 28 1 25912 44960 33402
+23 28 1 26486 44933 33769
+24 28 1 27334 44908 34186
+25 28 1 28153 44884 34790
+26 28 1 28725 44860 35146
+27 28 1 29584 44834 35545
+28 28 1 30307 44805 36120
+29 28 1 30891 44771 36469
+30 28 1 31752 44730 36837
+31 28 1 32387 44679 37205
+32 28 1 33030 44615 37603
+33 28 1 33891 44536 38146
+34 28 1 34473 44439 38415
+35 28 1 35210 44322 38870
+36 28 1 36071 44218 39141
+37 28 1 36649 44133 39597
+38 28 1 37492 44065 40170
+39 28 1 38346 44011 40473
+40 28 1 39146 43969 40935
+41 28 1 39712 43934 41492
+42 28 1 40564 43904 41821
+43 28 1 41273 43874 42213
+44 28 1 41851 43840 42586
+45 28 1 42701 43798 42986
+46 28 1 43344 43745 43525
+47 28 1 43957 43675 43839
+48 28 1 44805 43585 44211
+49 28 1 45402 43470 44536
+50 28 1 46057 43327 44883
+51 28 1 46900 43190 45413
+52 28 1 47468 43076 45700
+53 28 1 48163 42982 46123
+54 28 1 48997 42904 46438
+55 28 1 49547 42839 46798
+56 28 1 50275 42781 47359
+57 28 1 51071 42729 47733
+58 28 1 51884 42678 48056
+59 28 1 52421 42625 48611
+60 28 1 53182 42566 49030
+61 28 1 53988 42497 49327
+62 28 1 54519 42416 49791
+63 28 1 55297 42319 50042
+0 29 1 10380 46978 24898
+1 29 1 10921 46955 25434
+2 29 1 11734 46934 25755
+3 29 1 12526 46913 26144
+4 29 1 13218 46892 26513
+5 29 1 13776 46870 26912
+6 29 1 14611 46846 27461
+7 29 1 15278 46819 27800
+8 29 1 15855 46789 28158
+9 29 1 16696 46754 28543
+10 29 1 17333 46714 28914
+11 29 1 17937 46668 29466
+12 29 1 18782 46614 29775
+13 29 1 19387 46553 30144
+14 29 1 20029 46483 30692
+15 29 1 20874 46403 30982
+16 29 1 21451 46325 31403
+17 29 1 22144 46255 31705
+18 29 1 22989 46194 32101
+19 29 1 23549 46140 32670
+20 29 1 24312 46094 32971
+21 29 1 25140 46053 33451
+22 29 1 25984 46018 33781
+23 29 1 26565 45986 34228
+24 29 1 27416 45958 34819
+25 29 1 28202 45933 35147
+26 29 1 28771 45908 35582
+27 29 1 29629 45883 36143
+28 29 1 30323 45857 36485
+29 29 1 30919 45827 36867
+30 29 1 31778 45793 37230
+31 29 1 32394 45750 37662
+32 29 1 33059 45698 38194
+33 29 1 33920 45633 38481
+34 29 1 34495 45551 38937
+35 29 1 35258 45450 39208
+36 29 1 36118 45330 39676
+37 29 1 36703 45225 40217
+38 29 1 37558 45138 40494
+39 29 1 38401 45067 40984
+40 29 1 39177 45009 41519
+41 29 1 39743 44962 41828
+42 29 1 40596 44923 42247
+43 29 1 41285 44888 42592
+44 29 1 41871 44853 43034
+45 29 1 42722 44816 43556
+46 29 1 43350 44771 43878
+47 29 1 43976 44717 44261
+48 29 1 44824 44648 44593
+49 29 1 45410 44560 44987
+50 29 1 46081 44451 45510
+51 29 1 46923 44321 45764
+52 29 1 47483 44207 46191
+53 29 1 48196 44119 46473
+54 29 1 49027 44050 46854
+55 29 1 49572 43998 47413
+56 29 1 50318 43957 47743
+57 29 1 51115 43924 48101
+58 29 1 51926 43894 48668
+59 29 1 52461 43863 49048
+60 29 1 53239 43827 49392
+61 29 1 54038 43783 49832
+62 29 1 54571 43725 50115
+63 29 1 55363 43651 50650
+0 30 1 10409 48025 25467
+1 30 1 10962 48000 25779
+2 30 1 11775 47977 26184
+3 30 1 12570 47956 26525
+4 30 1 13237 47935 26966
+5 30 1 13810 47915 27491
+6 30 1 14648 47893 27821
+7 30 1 15290 47869 28198
+8 30 1 15886 47843 28561
+9 30 1 16728 47814 28974
+10 30 1 17342 47780 29512
+11 30 1 17968 47741 29808
+12 30 1 18812 47697 30219
+13 30 1 19398 47645 30760
+14 30 1 20065 47586 31045
+15 30 1 20909 47518 31479
+16 30 1 21473 47440 31766
+17 30 1 22196 47360 32185
+18 30 1 23038 47288 32736
+19 30 1 23596 47226 33009
+20 30 1 24390 47170 33508
+21 30 1 25229 47122 33806
+22 30 1 26063 47080 34280
+23 30 1 26634 47043 34852
+24 30 1 27485 47010 35155
+25 30 1 28238 46982 35621
+26 30 1 28809 46955 36162
+27 30 1 29667 46930 36496
+28 30 1 30334 46904 36891
+29 30 1 30945 46877 37244
+30 30 1 31804 46847 37707
+31 30 1 32404 46811 38220
+32 30 1 33092 46767 38528
+33 30 1 33952 46713 38976
+34 30 1 34524 46644 39265
+35 30 1 35313 46559 39767
+36 30 1 36172 46453 40283
+37 30 1 36762 46332 40541
+38 30 1 37616 46229 41051
+39 30 1 38449 46144 41556
+40 30 1 39202 46075 41844
+41 30 1 39771 46019 42285
+42 30 1 40623 45974 42600
+43 30 1 41294 45935 43078
+44 30 1 41891 45900 43574
+45 30 1 42742 45865 43905
+46 30 1 43355 45826 44293
+47 30 1 43997 45780 44627
+48 30 1 44844 45721 45065
+49 30 1 45420 45648 45580
+50 30 1 46108 45555 45845
+51 30 1 46948 45439 46277
+52 30 1 47502 45308 46532
+53 30 1 48232 45198 46931
+54 30 1 49060 45111 47474
+55 30 1 49602 45042 47762
+56 30 1 50365 44988 48153
+57 30 1 51164 44944 48720
+58 30 1 51971 44906 49060
+59 30 1 52508 44872 49451
+60 30 1 53301 44837 49855
+61 30 1 54090 44797 50177
+62 30 1 54622 44749 50735
+63 30 1 55422 44690 51144
+0 31 1 10429 49074 25810
+1 31 1 10997 49046 26220
+2 31 1 11809 49021 26540
+3 31 1 12607 48999 27019
+4 31 1 13249 48978 27511
+5 31 1 13841 48957 27843
+6 31 1 14681 48937 28229
+7 31 1 15300 48915 28574
+8 31 1 15917 48892 29028
+9 31 1 16758 48867 29543
+10 31 1 17352 48838 29832
+11 31 1 18002 48805 30284
+12 31 1 18844 48767 30805
+13 31 1 19415 48724 31096
+14 31 1 20110 48674 31539
+15 31 1 20950 48616 31827
+16 31 1 21506 48549 32283
+17 31 1 22259 48473 32813
+18 31 1 23097 48392 33068
+19 31 1 23659 48318 33574
+20 31 1 24485 48254 33849
+21 31 1 25325 48197 34346
+22 31 1 26126 48147 34890
+23 31 1 26690 48104 35173
+24 31 1 27543 48066 35666
+25 31 1 28264 48032 36183
+26 31 1 28843 48002 36508
+27 31 1 29700 47975 36915
+28 31 1 30342 47949 37254
+29 31 1 30972 47923 37746
+30 31 1 31830 47896 38232
+31 31 1 32417 47864 38564
+32 31 1 33130 47826 38995
+33 31 1 33989 47780 39303
+34 31 1 34562 47723 39833
+35 31 1 35376 47650 40334
+36 31 1 36232 47560 40608
+37 31 1 36814 47448 41140
+38 31 1 37664 47328 41610
+39 31 1 38491 47228 41878
+40 31 1 39221 47146 42333
+41 31 1 39795 47080 42619
+42 31 1 40647 47027 43125
+43 31 1 41301 46983 43590
+44 31 1 41910 46946 43927
+45 31 1 42761 46911 44314
+46 31 1 43362 46875 44647
+47 31 1 44018 46834 45124
+48 31 1 44864 46785 45618
+49 31 1 45432 46722 45906
+50 31 1 46138 46643 46343
+51 31 1 46975 46544 46610
+52 31 1 47524 46420 47037
+53 31 1 48272 46292 47548
+54 31 1 49095 46190 47800
+55 31 1 49638 46108 48220
+56 31 1 50417 46044 48773
+57 31 1 51219 45993 49075
+58 31 1 52020 45951 49508
+59 31 1 52559 45915 49869
+60 31 1 53360 45881 50231
+61 31 1 54135 45844 50798
+62 31 1 54666 45802 51176
+63 31 1 55472 45751 51482
+0 32 1 10443 50127 26253
+1 32 1 11029 50095 26563
+2 32 1 11838 50067 27074
+3 32 1 12640 50042 27527
+4 32 1 13259 50020 27868
+5 32 1 13872 49999 28255
+6 32 1 14712 49979 28588
+7 32 1 15309 49958 29080
+8 32 1 15950 49937 29563
+9 32 1 16790 49914 29853
+10 32 1 17365 49889 30342
+11 32 1 18042 49861 30836
+12 32 1 18882 49829 31140
+13 32 1 19440 49792 31584
+14 32 1 20164 49749 31875
+15 32 1 21000 49700 32369
+16 32 1 21553 49643 32881
+17 32 1 22337 49577 33142
+18 32 1 23171 49501 33651
+19 32 1 23740 49419 33911
+20 32 1 24578 49344 34427
+21 32 1 25405 49278 34938
+22 32 1 26173 49220 35205
+23 32 1 26737 49169 35721
+24 32 1 27590 49124 36208
+25 32 1 28281 49085 36527
+26 32 1 28872 49051 36940
+27 32 1 29729 49021 37266
+28 32 1 30350 48993 37782
+29 32 1 31001 48967 38239
+30 32 1 31859 48940 38592
+31 32 1 32436 48911 39004
+32 32 1 33174 48878 39328
+33 32 1 34033 48838 39880
+34 32 1 34609 48789 40358
+35 32 1 35449 48728 40655
+36 32 1 36286 48651 41221
+37 32 1 36859 48554 41678
+38 32 1 37706 48435 41937
+39 32 1 38527 48319 42393
+40 32 1 39236 48223 42657
+41 32 1 39817 48146 43181
+42 32 1 40669 48083 43611
+43 32 1 41307 48033 43950
+44 32 1 41929 47992 44334
+45 32 1 42779 47955 44663
+46 32 1 43369 47920 45171
+47 32 1 44042 47883 45635
+48 32 1 44886 47840 45953
+49 32 1 45448 47786 46382
+50 32 1 46170 47719 46667
+51 32 1 47004 47633 47139
+52 32 1 47551 47526 47636
+53 32 1 48315 47394 47865
+54 32 1 49135 47275 48307
+55 32 1 49678 47180 48833
+56 32 1 50473 47105 49101
+57 32 1 51278 47045 49566
+58 32 1 52067 46998 49883
+59 32 1 52604 46958 50283
+60 32 1 53412 46923 50847
+61 32 1 54171 46888 51193
+62 32 1 54704 46850 51541
+63 32 1 55516 46805 51965
+0 33 1 10453 51183 26597
+1 33 1 11060 51147 27133
+2 33 1 11865 51115 27542
+3 33 1 12672 51087 27900
+4 33 1 13268 51063 28278
+5 33 1 13905 51040 28607
+6 33 1 14744 51019 29130
+7 33 1 15321 50999 29577
+8 33 1 15988 50979 29874
+9 33 1 16825 50958 30396
+10 33 1 17386 50936 30856
+11 33 1 18091 50911 31180
+12 33 1 18926 50883 31618
+13 33 1 19477 50851 31916
+14 33 1 20231 50814 32444
+15 33 1 21062 50772 32931
+16 33 1 21618 50722 33210
+17 33 1 22432 50665 33718
+18 33 1 23254 50600 33987
+19 33 1 23815 50524 34523
+20 33 1 24652 50440 34997
+21 33 1 25472 50365 35255
+22 33 1 26207 50297 35787
+23 33 1 26776 50238 36241
+24 33 1 27629 50187 36555
+25 33 1 28293 50142 36970
+26 33 1 28900 50102 37284
+27 33 1 29756 50068 37820
+28 33 1 30359 50037 38245
+29 33 1 31033 50009 38618
+30 33 1 31890 49982 39009
+31 33 1 32461 49954 39348
+32 33 1 33226 49924 39916
+33 33 1 34083 49889 40366
+34 33 1 34666 49847 40687
+35 33 1 35520 49794 41276
+36 33 1 36330 49727 41721
+37 33 1 36898 49644 42003
+38 33 1 37741 49540 42465
+39 33 1 38559 49416 42720
+40 33 1 39247 49306 43251
+41 33 1 39837 49216 43645
+42 33 1 40690 49144 43980
+43 33 1 41313 49085 44360
+44 33 1 41948 49038 44682
+45 33 1 42798 48999 45213
+46 33 1 43378 48963 45642
+47 33 1 44067 48928 45990
+48 33 1 44910 48889 46400
+49 33 1 45466 48842 46705
+50 33 1 46205 48784 47215
+51 33 1 47036 48710 47695
+52 33 1 47583 48617 47942
+53 33 1 48363 48500 48417
+54 33 1 49178 48368 48906
+55 33 1 49724 48258 49147
+56 33 1 50530 48170 49629
+57 33 1 51332 48101 49905
+58 33 1 52107 48046 50341
+59 33 1 52643 48002 50888
+60 33 1 53456 47965 51204
+61 33 1 54201 47930 51592
+62 33 1 54737 47894 51980
+63 33 1 55554 47854 52324
+0 34 1 10462 52242 27195
+1 34 1 11093 52201 27561
+2 34 1 11893 52165 27941
+3 34 1 12704 52134 28300
+4 34 1 13281 52107 28635
+5 34 1 13943 52082 29181
+6 34 1 14779 52060 29588
+7 34 1 15340 52039 29901
+8 34 1 16035 52019 30448
+9 34 1 16868 51999 30871
+10 34 1 17418 51978 31222
+11 34 1 18152 51956 31644
+12 34 1 18982 51931 31956
+13 34 1 19529 51903 32511
+14 34 1 20314 51871 32968
+15 34 1 21139 51834 33272
+16 34 1 21701 51791 33770
+17 34 1 22531 51742 34052
+18 34 1 23320 51684 34611
+19 34 1 23877 51618 35059
+20 34 1 24712 51540 35324
+21 34 1 25526 51456 35865
+22 34 1 26231 51379 36285
+23 34 1 26810 51312 36595
+24 34 1 27663 51252 37006
+25 34 1 28302 51200 37314
+26 34 1 28929 51155 37863
+27 34 1 29784 51116 38256
+28 34 1 30372 51081 38645
+29 34 1 31071 51051 39016
+30 34 1 31927 51022 39367
+31 34 1 32495 50995 39946
+32 34 1 33287 50966 40365
+33 34 1 34142 50934 40711
+34 34 1 34729 50897 41313
+35 34 1 35582 50851 41741
+36 34 1 36365 50793 42053
+37 34 1 36932 50721 42515
+38 34 1 37770 50630 42798
+39 34 1 38587 50517 43339
+40 34 1 39256 50394 43698
+41 34 1 39856 50291 44024
+42 34 1 40709 50207 44396
+43 34 1 41318 50140 44712
+44 34 1 41969 50086 45256
+45 34 1 42817 50042 45647
+46 34 1 43389 50005 46023
+47 34 1 44095 49970 46409
+48 34 1 44936 49933 46733
+49 34 1 45489 49892 47271
+50 34 1 46244 49841 47726
+51 34 1 47072 49777 47997
+52 34 1 47619 49696 48513
+53 34 1 48414 49594 48987
+54 34 1 49224 49466 49219
+55 34 1 49770 49341 49701
+56 34 1 50580 49240 49944
+57 34 1 51379 49160 50408
+58 34 1 52140 49097 50929
+59 34 1 52677 49047 51219
+60 34 1 53495 49006 51639
+61 34 1 54226 48970 51988
+62 34 1 54765 48936 52375
+63 34 1 55586 48898 52927
+0 35 1 10474 53305 27586
+1 35 1 11130 53258 27991
+2 35 1 11926 53218 28326
+3 35 1 12740 53183 28675
+4 35 1 13299 53152 29234
+5 35 1 13990 53125 29601
+6 35 1 14820 53101 29936
+7 35 1 15369 53079 30500
+8 35 1 16092 53058 30885
+9 35 1 16919 53038 31266
+10 35 1 17464 53018 31667
+11 35 1 18228 52997 31998
+12 35 1 19050 52975 32573
+13 35 1 19601 52950 32999
+14 35 1 20415 52921 33332
+15 35 1 21223 52889 33807
+16 35 1 21777 52851 34110
+17 35 1 22612 52808 34683
+18 35 1 23369 52757 35104
+19 35 1 23927 52698 35391
+20 35 1 24759 52630 35949
+21 35 1 25571 52550 36339
+22 35 1 26247 52465 36648
+23 35 1 26840 52388 37050
+24 35 1 27694 52321 37357
+25 35 1 28311 52262 37913
+26 35 1 28959 52210 38274
+27 35 1 29813 52165 38676
+28 35 1 30390 52127 39027
+29 35 1 31116 52093 39390
+30 35 1 31970 52062 39975
+31 35 1 32539 52034 40362
+32 35 1 33358 52006 40732
+33 35 1 34206 51976 41341
+34 35 1 34783 51942 41747
+35 35 1 35636 51901 42092
+36 35 1 36393 51851 42542
+37 35 1 36961 51787 42857
+38 35 1 37796 51708 43415
+39 35 1 38611 51608 43766
+40 35 1 39263 51485 44084
+41 35 1 39875 51369 44447
+42 35 1 40727 51274 44758
+43 35 1 41325 51198 45306
+44 35 1 41991 51136 45659
+45 35 1 42838 51087 46056
+46 35 1 43404 51046 46416
+47 35 1 44126 51010 46757
+48 35 1 44964 50975 47314
+49 35 1 45515 50937 47738
+50 35 1 46286 50891 48037
+51 35 1 47110 50835 48583
+52 35 1 47660 50765 49038
+53 35 1 48469 50675 49298
+54 35 1 49267 50562 49782
+55 35 1 49810 50429 50008
+56 35 1 50623 50314 50491
+57 35 1 51420 50223 50977
+58 35 1 52167 50151 51243
+59 35 1 52706 50094 51687
+60 35 1 53528 50049 51997
+61 35 1 54245 50010 52425
+62 35 1 54790 49975 52963
+63 35 1 55615 49940 53276
+0 36 1 10492 54371 28051
+1 36 1 11175 54319 28358
+2 36 1 11967 54273 28730
+3 36 1 12782 54234 29292
+4 36 1 13328 54199 29620
+5 36 1 14047 54169 29983
+6 36 1 14871 54142 30554
+7 36 1 15412 54118 30902
+8 36 1 16164 54097 31315
+9 36 1 16984 54076 31690
+10 36 1 17528 54056 32047
+11 36 1 18321 54036 32633
+12 36 1 19135 54015 33028
+13 36 1 19688 53992 33388
+14 36 1 20512 53966 33835
+15 36 1 21288 53938 34161
+16 36 1 21840 53904 34742
+17 36 1 22679 53866 35134
+18 36 1 23404 53821 35448
+19 36 1 23969 53768 36016
+20 36 1 24797 53707 36389
+21 36 1 25609 53636 36711
+22 36 1 26258 53552 37103
+23 36 1 26870 53467 37415
+24 36 1 27722 53391 37970
+25 36 1 28321 53325 38302
+26 36 1 28993 53267 38713
+27 36 1 29846 53216 39044
+28 36 1 30415 53173 39421
+29 36 1 31168 53135 40005
+30 36 1 32020 53102 40362
+31 36 1 32594 53072 40754
+32 36 1 33438 53043 41364
+33 36 1 34261 53014 41746
+34 36 1 34830 52983 42124
+35 36 1 35682 52946 42553
+36 36 1 36414 52901 42900
+37 36 1 36987 52845 43467
+38 36 1 37818 52775 43816
+39 36 1 38634 52686 44149
+40 36 1 39269 52576 44514
+41 36 1 39894 52450 44825
+42 36 1 40746 52344 45366
+43 36 1 41333 52257 45684
+44 36 1 42015 52188 46093
+45 36 1 42861 52132 46427
+46 36 1 43421 52087 46783
+47 36 1 44160 52049 47350
+48 36 1 44995 52014 47740
+49 36 1 45546 51978 48067
+50 36 1 46333 51937 48634
+51 36 1 47153 51887 49064
+52 36 1 47705 51825 49360
+53 36 1 48520 51746 49840
+54 36 1 49303 51646 50088
+55 36 1 49845 51521 50593
+56 36 1 50661 51393 51036
+57 36 1 51456 51289 51284
+58 36 1 52189 51207 51738
+59 36 1 52732 51143 52015
+60 36 1 53557 51092 52477
+61 36 1 54261 51050 52991
+62 36 1 54812 51014 53284
+63 36 1 55640 50979 53700
+0 37 1 10520 55439 28400
+1 37 1 11231 55381 28801
+2 37 1 12021 55330 29355
+3 37 1 12833 55286 29648
+4 37 1 13372 55248 30043
+5 37 1 14119 55214 30612
+6 37 1 14934 55184 30928
+7 37 1 15473 55158 31370
+8 37 1 16254 55135 31718
+9 37 1 17065 55113 32104
+10 37 1 17613 55093 32693
+11 37 1 18427 55073 33054
+12 37 1 19214 55052 33440
+13 37 1 19761 55031 33855
+14 37 1 20591 55008 34210
+15 37 1 21336 54981 34790
+16 37 1 21891 54952 35155
+17 37 1 22733 54917 35498
+18 37 1 23428 54877 36067
+19 37 1 24005 54830 36425
+20 37 1 24828 54775 36765
+21 37 1 25642 54710 37151
+22 37 1 26267 54634 37487
+23 37 1 26900 54547 38037
+24 37 1 27752 54463 38341
+25 37 1 28335 54389 38758
+26 37 1 29033 54324 39071
+27 37 1 29885 54268 39460
+28 37 1 30449 54219 40039
+29 37 1 31230 54177 40367
+30 37 1 32080 54141 40782
+31 37 1 32661 54109 41386
+32 37 1 33510 54079 41743
+33 37 1 34304 54051 42153
+34 37 1 34870 54021 42555
+35 37 1 35722 53987 42935
+36 37 1 36430 53946 43501
+37 37 1 37011 53896 43847
+38 37 1 37837 53833 44195
+39 37 1 38655 53754 44568
+40 37 1 39274 53656 44908
+41 37 1 39915 53533 45441
+42 37 1 40765 53415 45728
+43 37 1 41343 53319 46138
+44 37 1 42042 53241 46448
+45 37 1 42886 53179 46817
+46 37 1 43442 53129 47385
+47 37 1 44197 53088 47740
+48 37 1 45029 53051 48095
+49 37 1 45581 53016 48673
+50 37 1 46383 52978 49073
+51 37 1 47197 52933 49410
+52 37 1 47747 52878 49871
+53 37 1 48565 52808 50151
+54 37 1 49334 52719 50688
+55 37 1 49876 52607 51109
+56 37 1 50693 52474 51346
+57 37 1 51488 52359 51798
+58 37 1 52207 52267 52049
+59 37 1 52755 52194 52537
+60 37 1 53583 52136 53019
+61 37 1 54274 52091 53295
+62 37 1 54831 52052 53748
+63 37 1 55661 52017 54263
+0 38 1 10562 56509 28890
+1 38 1 11301 56445 29426
+2 38 1 12091 56389 29691
+3 38 1 12898 56340 30119
+4 38 1 13433 56297 30676
+5 38 1 14208 56260 30965
+6 38 1 15014 56227 31432
+7 38 1 15556 56199 31753
+8 38 1 16362 56173 32171
+9 38 1 17151 56150 32748
+10 38 1 17694 56129 33080
+11 38 1 18517 56108 33488
+12 38 1 19272 56088 33872
+13 38 1 19821 56068 34257
+14 38 1 20656 56046 34829
+15 38 1 21370 56021 35169
+16 38 1 21934 55994 35544
+17 38 1 22778 55963 36107
+18 38 1 23444 55926 36451
+19 38 1 24037 55884 36808
+20 38 1 24855 55834 37185
+21 38 1 25672 55775 37551
+22 38 1 26276 55706 38102
+23 38 1 26933 55625 38392
+24 38 1 27784 55535 38812
+25 38 1 28355 55453 39108
+26 38 1 29081 55382 39511
+27 38 1 29930 55320 40080
+28 38 1 30495 55266 40381
+29 38 1 31304 55220 40817
+30 38 1 32149 55180 41410
+31 38 1 32724 55146 41743
+32 38 1 33572 55115 42182
+33 38 1 34339 55085 42554
+34 38 1 34904 55056 42964
+35 38 1 35756 55024 43525
+36 38 1 36443 54987 43867
+37 38 1 37032 54942 44229
+38 38 1 37856 54885 44602
+39 38 1 38674 54814 44974
+40 38 1 39280 54725 45512
+41 38 1 39937 54613 45790
+42 38 1 40786 54487 46194
+43 38 1 41356 54381 46484
+44 38 1 42071 54295 46863
+45 38 1 42913 54226 47424
+46 38 1 43467 54171 47745
+47 38 1 44238 54126 48124
+48 38 1 45067 54088 48705
+49 38 1 45622 54053 49074
+50 38 1 46437 54016 49451
+51 38 1 47237 53975 49885
+52 38 1 47783 53926 50198
+53 38 1 48605 53863 50756
+54 38 1 49360 53783 51162
+55 38 1 49903 53683 51422
+56 38 1 50721 53558 51870
+57 38 1 51516 53430 52105
+58 38 1 52222 53328 52607
+59 38 1 52775 53246 53052
+60 38 1 53606 53182 53316
+61 38 1 54284 53131 53798
+62 38 1 54848 53090 54284
+63 38 1 55681 53054 54604
+0 39 1 10623 57581 29507
+1 39 1 11390 57511 29752
+2 39 1 12181 57449 30213
+3 39 1 12980 57395 30748
+4 39 1 13517 57348 31017
+5 39 1 14317 57306 31501
+6 39 1 15103 57270 31795
+7 39 1 15642 57239 32242
+8 39 1 16457 57211 32797
+9 39 1 17217 57186 33107
+10 39 1 17760 57164 33533
+11 39 1 18589 57143 33887
+12 39 1 19314 57122 34304
+13 39 1 19869 57102 34860
+14 39 1 20709 57081 35179
+15 39 1 21392 57058 35586
+16 39 1 21970 57033 36137
+17 39 1 22816 57004 36472
+18 39 1 23455 56971 36844
+19 39 1 24067 56932 37209
+20 39 1 24882 56886 37606
+21 39 1 25703 56832 38151
+22 39 1 26288 56769 38440
+23 39 1 26972 56694 38870
+24 39 1 27821 56606 39156
+25 39 1 28384 56517 39574
+26 39 1 29139 56439 40127
+27 39 1 29984 56371 40406
+28 39 1 30553 56313 40861
+29 39 1 31389 56262 41440
+30 39 1 32210 56219 41750
+31 39 1 32776 56181 42212
+32 39 1 33625 56149 42555
+33 39 1 34365 56119 42993
+34 39 1 34934 56089 43542
+35 39 1 35786 56059 43879
+36 39 1 36452 56024 44254
+37 39 1 37053 55982 44621
+38 39 1 37873 55931 45024
+39 39 1 38694 55867 45559
+40 39 1 39288 55785 45847
+41 39 1 39960 55684 46257
+42 39 1 40808 55559 46537
+43 39 1 41372 55444 46924
+44 39 1 42104 55349 47470
+45 39 1 42943 55274 47759
+46 39 1 43497 55213 48160
+47 39 1 44284 55164 48735
+48 39 1 45108 55124 49073
+49 39 1 45666 55088 49488
+50 39 1 46486 55052 49888
+51 39 1 47271 55014 50235
+52 39 1 47816 54969 50805
+53 39 1 48641 54912 51193
+54 39 1 49381 54840 51482
+55 39 1 49927 54750 51931
+56 39 1 50746 54636 52183
+57 39 1 51541 54504 52692
+58 39 1 52234 54391 53096
+59 39 1 52793 54300 53351
+60 39 1 53626 54229 53854
+61 39 1 54292 54173 54306
+62 39 1 54864 54128 54622
+63 39 1 55698 54090 55013
+0 40 1 10706 58654 29834
+1 40 1 11498 58577 30324
+2 40 1 12291 58510 30827
+3 40 1 13069 58451 31080
+4 40 1 13604 58399 31569
+5 40 1 14415 58353 31840
+6 40 1 15172 58314 32314
+7 40 1 15711 58279 32842
+8 40 1 16535 58249 33137
+9 40 1 17264 58222 33575
+10 40 1 17814 58198 33903
+11 40 1 18648 58176 34351
+12 40 1 19343 58155 34886
+13 40 1 19910 58135 35189
+14 40 1 20752 58114 35628
+15 40 1 21407 58092 36159
+16 40 1 22002 58069 36491
+17 40 1 22849 58042 36873
+18 40 1 23464 58011 37226
+19 40 1 24100 57975 37654
+20 40 1 24910 57933 38187
+21 40 1 25735 57883 38479
+22 40 1 26307 57824 38916
+23 40 1 27018 57754 39205
+24 40 1 27864 57672 39648
+25 40 1 28423 57580 40182
+26 40 1 29207 57495 40442
+27 40 1 30049 57422 40915
+28 40 1 30624 57358 41475
+29 40 1 31467 57304 41765
+30 40 1 32259 57257 42244
+31 40 1 32821 57217 42561
+32 40 1 33670 57182 43023
+33 40 1 34385 57151 43557
+34 40 1 34961 57121 43887
+35 40 1 35813 57091 44275
+36 40 1 36459 57058 44633
+37 40 1 37074 57020 45064
+38 40 1 37891 56972 45589
+39 40 1 38714 56913 45889
+40 40 1 39297 56839 46302
+41 40 1 39987 56746 46595
+42 40 1 40833 56629 47001
+43 40 1 41392 56506 47526
+44 40 1 42141 56403 47787
+45 40 1 42977 56321 48205
+46 40 1 43531 56255 48768
+47 40 1 44333 56202 49076
+48 40 1 45153 56159 49523
+49 40 1 45707 56121 49887
+50 40 1 46530 56086 50268
+51 40 1 47299 56050 50841
+52 40 1 47845 56008 51209
+53 40 1 48673 55956 51531
+54 40 1 49399 55891 51968
+55 40 1 49949 55809 52252
+56 40 1 50767 55706 52785
+57 40 1 51563 55578 53155
+58 40 1 52244 55454 53405
+59 40 1 52809 55355 53919
+60 40 1 53644 55277 54333
+61 40 1 54298 55215 54646
+62 40 1 54879 55166 55042
+63 40 1 55714 55126 55359
+0 41 1 10795 59722 30440
+1 41 1 11598 59643 30904
+2 41 1 12388 59570 31150
+3 41 1 13137 59506 31633
+4 41 1 13674 59450 31890
+5 41 1 14494 59400 32389
+6 41 1 15221 59357 32883
+7 41 1 15767 59320 33172
+8 41 1 16598 59287 33613
+9 41 1 17296 59258 33922
+10 41 1 17858 59232 34399
+11 41 1 18696 59209 34907
+12 41 1 19361 59187 35199
+13 41 1 19945 59167 35668
+14 41 1 20789 59146 36176
+15 41 1 21418 59125 36508
+16 41 1 22034 59102 36897
+17 41 1 22881 59076 37240
+18 41 1 23474 59047 37698
+19 41 1 24136 59014 38213
+20 41 1 24944 58974 38512
+21 41 1 25772 58928 38953
+22 41 1 26334 58873 39244
+23 41 1 27074 58807 39717
+24 41 1 27916 58730 40240
+25 41 1 28476 58639 40492
+26 41 1 29289 58550 40980
+27 41 1 30123 58471 41517
+28 41 1 30690 58403 41790
+29 41 1 31534 58344 42280
+30 41 1 32297 58294 42573
+31 41 1 32859 58251 43057
+32 41 1 33709 58214 43572
+33 41 1 34399 58182 43896
+34 41 1 34985 58152 44295
+35 41 1 35837 58122 44640
+36 41 1 36465 58090 45098
+37 41 1 37095 58054 45608
+38 41 1 37909 58010 45920
+39 41 1 38735 57955 46332
+40 41 1 39309 57886 46636
+41 41 1 40016 57800 47074
+42 41 1 40860 57692 47591
+43 41 1 41416 57566 47831
+44 41 1 42182 57456 48262
+45 41 1 43014 57368 48806
+46 41 1 43571 57296 49087
+47 41 1 44387 57239 49560
+48 41 1 45193 57193 49888
+49 41 1 45743 57154 50299
+50 41 1 46569 57119 50868
+51 41 1 47324 57084 51216
+52 41 1 47870 57044 51570
+53 41 1 48701 56997 51988
+54 41 1 49414 56937 52306
+55 41 1 49968 56862 52854
+56 41 1 50786 56767 53212
+57 41 1 51583 56649 53480
+58 41 1 52251 56518 53996
+59 41 1 52825 56411 54371
+60 41 1 53661 56325 54681
+61 41 1 54304 56258 55074
+62 41 1 54892 56204 55376
+63 41 1 55729 56161 55873
+0 42 1 10867 60783 30972
+1 42 1 11676 60708 31227
+2 42 1 12465 60630 31694
+3 42 1 13186 60561 31947
+4 42 1 13730 60500 32466
+5 42 1 14558 60447 32921
+6 42 1 15255 60400 33212
+7 42 1 15813 60359 33649
+8 42 1 16649 60324 33945
+9 42 1 17317 60293 34449
+10 42 1 17895 60265 34924
+11 42 1 18736 60241 35213
+12 42 1 19373 60218 35709
+13 42 1 19978 60197 36188
+14 42 1 20822 60176 36527
+15 42 1 21427 60155 36918
+16 42 1 22068 60133 37252
+17 42 1 22913 60109 37740
+18 42 1 23489 60081 38232
+19 42 1 24178 60049 38542
+20 42 1 24986 60012 38983
+21 42 1 25816 59968 39277
+22 42 1 26372 59916 39778
+23 42 1 27142 59855 40286
+24 42 1 27979 59781 40545
+25 42 1 28545 59695 41055
+26 42 1 29379 59601 41566
+27 42 1 30185 59517 41826
+28 42 1 30745 59445 42319
+29 42 1 31590 59383 42594
+30 42 1 32325 59329 43096
+31 42 1 32892 59284 43590
+32 42 1 33742 59245 43907
+33 42 1 34410 59211 44314
+34 42 1 35008 59181 44647
+35 42 1 35859 59151 45128
+36 42 1 36472 59120 45619
+37 42 1 37118 59086 45945
+38 42 1 37930 59044 46352
+39 42 1 38758 58993 46664
+40 42 1 39324 58929 47130
+41 42 1 40049 58848 47640
+42 42 1 40890 58747 47887
+43 42 1 41445 58624 48334
+44 42 1 42227 58508 48853
+45 42 1 43056 58413 49110
+46 42 1 43615 58337 49599
+47 42 1 44438 58276 49896
+48 42 1 45228 58227 50333
+49 42 1 45775 58186 50892
+50 42 1 46604 58150 51219
+51 42 1 47344 58115 51603
+52 42 1 47893 58078 51996
+53 42 1 48726 58033 52349
+54 42 1 49426 57979 52903
+55 42 1 49986 57910 53249
+56 42 1 50802 57822 53551
+57 42 1 51601 57713 54082
+58 42 1 52258 57582 54424
+59 42 1 52839 57466 54728
+60 42 1 53676 57374 55112
+61 42 1 54308 57300 55404
+62 42 1 54906 57242 55920
+63 42 1 55742 57196 56346
+0 43 1 10924 61837 31301
+1 43 1 11737 61767 31752
+2 43 1 12528 61688 32012
+3 43 1 13218 61614 32544
+4 43 1 13776 61549 32959
+5 43 1 14609 61492 33259
+6 43 1 15277 61442 33682
+7 43 1 15852 61398 33975
+8 43 1 16690 61360 34499
+9 43 1 17331 61327 34940
+10 43 1 17929 61298 35231
+11 43 1 18771 61272 35751
+12 43 1 19383 61248 36198
+13 43 1 20012 61226 36548
+14 43 1 20855 61205 36935
+15 43 1 21439 61184 37266
+16 43 1 22107 61162 37779
+17 43 1 22950 61139 38246
+18 43 1 23512 61112 38571
+19 43 1 24230 61082 39007
+20 43 1 25038 61046 39307
+21 43 1 25869 61005 39832
+22 43 1 26424 60955 40323
+23 43 1 27223 60896 40593
+24 43 1 28054 60826 41128
+25 43 1 28620 60744 41619
+26 43 1 29457 60649 41873
+27 43 1 30233 60561 42363
+28 43 1 30792 60485 42624
+29 43 1 31637 60419 43140
+30 43 1 32346 60363 43610
+31 43 1 32921 60316 43923
+32 43 1 33771 60275 44334
+33 43 1 34418 60240 44656
+34 43 1 35030 60209 45156
+35 43 1 35881 60179 45626
+36 43 1 36479 60148 45965
+37 43 1 37143 60115 46365
+38 43 1 37953 60076 46685
+39 43 1 38783 60027 47175
+40 43 1 39344 59967 47672
+41 43 1 40086 59891 47930
+42 43 1 40924 59796 48407
+43 43 1 41479 59678 48909
+44 43 1 42277 59556 49148
+45 43 1 43101 59457 49643
+46 43 1 43658 59376 49913
+47 43 1 44483 59311 50372
+48 43 1 45257 59259 50915
+49 43 1 45805 59217 51223
+50 43 1 46635 59180 51634
+51 43 1 47362 59146 51999
+52 43 1 47914 59109 52385
+53 43 1 48749 59068 52938
+54 43 1 49436 59016 53270
+55 43 1 50002 58952 53607
+56 43 1 50817 58871 54149
+57 43 1 51617 58770 54483
+58 43 1 52263 58644 54789
+59 43 1 52852 58521 55160
+60 43 1 53690 58422 55446
+61 43 1 54312 58343 55972
+62 43 1 54919 58280 56363
+63 43 1 55755 58231 56709
+0 44 1 10971 62885 31795
+1 44 1 11783 62819 32082
+2 44 1 12578 62744 32624
+3 44 1 13239 62665 32997
+4 44 1 13815 62596 33311
+5 44 1 14652 62535 33715
+6 44 1 15291 62482 34012
+7 44 1 15887 62436 34552
+8 44 1 16727 62395 34956
+9 44 1 17341 62360 35256
+10 44 1 17963 62329 35794
+11 44 1 18804 62301 36207
+12 44 1 19394 62277 36573
+13 44 1 20049 62254 36951
+14 44 1 20890 62233 37284
+15 44 1 21458 62211 37818
+16 44 1 22154 62190 38257
+17 44 1 22992 62167 38601
+18 44 1 23546 62141 39029
+19 44 1 24293 62112 39337
+20 44 1 25105 62078 39883
+21 44 1 25934 62038 40354
+22 44 1 26493 61990 40637
+23 44 1 27318 61933 41192
+24 44 1 28126 61866 41665
+25 44 1 28683 61786 41927
+26 44 1 29523 61692 42411
+27 44 1 30269 61601 42664
+28 44 1 30831 61522 43190
+29 44 1 31677 61453 43635
+30 44 1 32361 61395 43944
+31 44 1 32947 61346 44357
+32 44 1 33797 61303 44668
+33 44 1 34425 61267 45186
+34 44 1 35053 61235 45632
+35 44 1 35903 61205 45982
+36 44 1 36488 61175 46375
+37 44 1 37170 61142 46700
+38 44 1 37980 61105 47211
+39 44 1 38811 61058 47691
+40 44 1 39368 61001 47962
+41 44 1 40128 60929 48465
+42 44 1 40962 60839 48959
+43 44 1 41518 60727 49201
+44 44 1 42333 60602 49693
+45 44 1 43144 60498 49942
+46 44 1 43697 60413 50419
+47 44 1 44524 60345 50942
+48 44 1 45282 60291 51230
+49 44 1 45831 60247 51664
+50 44 1 46663 60209 52001
+51 44 1 47376 60174 52418
+52 44 1 47933 60139 52962
+53 44 1 48769 60099 53282
+54 44 1 49444 60051 53652
+55 44 1 50017 59991 54196
+56 44 1 50831 59916 54527
+57 44 1 51632 59821 54851
+58 44 1 52267 59702 55219
+59 44 1 52865 59574 55504
+60 44 1 53703 59468 56031
+61 44 1 54316 59384 56387
+62 44 1 54931 59318 56739
+63 44 1 55768 59265 57105
+0 45 0 0 0 0
+1 45 0 0 0 0
+2 45 0 0 0 0
+3 45 1 13253 63710 33370
+4 45 1 13850 63628 33748
+5 45 1 14689 63556 34057
+6 45 1 15301 63494 34606
+7 45 1 15921 63440 34973
+8 45 1 16761 63394 35288
+9 45 1 17352 63355 35838
+10 45 1 18000 63321 36218
+11 45 1 18839 63292 36603
+12 45 1 19410 63267 36967
+13 45 1 20094 63245 37306
+14 45 1 20931 63225 37858
+15 45 1 21486 63206 38269
+16 45 1 22212 63187 38632
+17 45 1 23045 63168 39048
+18 45 1 23594 63146 39368
+19 45 1 24371 63121 39932
+20 45 1 25188 63092 40380
+21 45 1 26012 63057 40680
+22 45 1 26573 63014 41250
+23 45 1 27404 62962 41700
+24 45 1 28181 62899 41975
+25 45 1 28736 62823 42456
+26 45 1 29578 62731 42713
+27 45 1 30296 62637 43245
+28 45 1 30864 62555 43665
+29 45 1 31712 62485 43971
+30 45 1 32372 62425 44382
+31 45 1 32971 62373 44686
+32 45 1 33821 62330 45216
+33 45 1 34432 62293 45640
+34 45 1 35077 62260 46000
+35 45 1 35926 62230 46383
+36 45 1 36500 62200 46713
+37 45 1 37201 62168 47241
+38 45 1 38011 62131 47702
+39 45 1 38843 62087 47986
+40 45 1 39397 62032 48511
+41 45 1 40175 61963 48992
+42 45 1 41005 61876 49251
+43 45 1 41564 61769 49746
+44 45 1 42387 61644 49984
+45 45 1 43182 61536 50473
+46 45 1 43731 61448 50973
+47 45 1 44561 61378 51245
+48 45 1 45304 61321 51695
+49 45 1 45854 61275 52006
+50 45 1 46689 61237 52449
+51 45 1 47388 61202 52981
+52 45 1 47950 61167 53288
+53 45 1 48788 61129 53689
+54 45 1 49451 61083 54229
+55 45 1 50031 61027 54557
+56 45 1 50843 60955 54898
+57 45 1 51646 60866 55268
+58 45 1 52271 60754 55578
+59 45 1 52877 60624 56099
+60 45 1 53715 60514 56421
+61 45 1 54319 60425 56777
+62 45 1 54944 60354 57129
+63 45 1 55780 60299 57447
+0 46 0 0 0 0
+1 46 0 0 0 0
+2 46 0 0 0 0
+3 46 0 0 0 0
+4 46 0 0 0 0
+5 46 0 0 0 0
+6 46 0 0 0 0
+7 46 0 0 0 0
+8 46 0 0 0 0
+9 46 0 0 0 0
+10 46 0 0 0 0
+11 46 0 0 0 0
+12 46 0 0 0 0
+13 46 0 0 0 0
+14 46 0 0 0 0
+15 46 0 0 0 0
+16 46 0 0 0 0
+17 46 0 0 0 0
+18 46 0 0 0 0
+19 46 0 0 0 0
+20 46 0 0 0 0
+21 46 0 0 0 0
+22 46 0 0 0 0
+23 46 0 0 0 0
+24 46 0 0 0 0
+25 46 0 0 0 0
+26 46 0 0 0 0
+27 46 1 30315 63659 43698
+28 46 1 30893 63566 44004
+29 46 1 31741 63486 44409
+30 46 1 32380 63419 44709
+31 46 1 32995 63363 45249
+32 46 1 33844 63317 45649
+33 46 1 34440 63279 46019
+34 46 1 35104 63248 46392
+35 46 1 35950 63220 46726
+36 46 1 36516 63194 47268
+37 46 1 37237 63167 47708
+38 46 1 38047 63136 48006
+39 46 1 38878 63098 48548
+40 46 1 39432 63050 49012
+41 46 1 40228 62987 49293
+42 46 1 41052 62907 49787
+43 46 1 41611 62805 50037
+44 46 1 42436 62681 50537
+45 46 1 43214 62571 51011
+46 46 1 43762 62481 51268
+47 46 1 44593 62408 51729
+48 46 1 45322 62350 52016
+49 46 1 45875 62302 52482
+50 46 1 46711 62263 52997
+51 46 1 47398 62228 53292
+52 46 1 47966 62194 53722
+53 46 1 48805 62156 54251
+54 46 1 49456 62113 54579
+55 46 1 50044 62059 54934
+56 46 1 50855 61991 55302
+57 46 1 51659 61906 55643
+58 46 1 52275 61801 56172
+59 46 1 52890 61673 56469
+60 46 1 53727 61557 56823
+61 46 1 54323 61464 57161
+62 46 1 54957 61390 57479
+63 46 1 55792 61331 58015
+0 47 0 0 0 0
+1 47 0 0 0 0
+2 47 0 0 0 0
+3 47 0 0 0 0
+4 47 0 0 0 0
+5 47 0 0 0 0
+6 47 0 0 0 0
+7 47 0 0 0 0
+8 47 0 0 0 0
+9 47 0 0 0 0
+10 47 0 0 0 0
+11 47 0 0 0 0
+12 47 0 0 0 0
+13 47 0 0 0 0
+14 47 0 0 0 0
+15 47 0 0 0 0
+16 47 0 0 0 0
+17 47 0 0 0 0
+18 47 0 0 0 0
+19 47 0 0 0 0
+20 47 0 0 0 0
+21 47 0 0 0 0
+22 47 0 0 0 0
+23 47 0 0 0 0
+24 47 0 0 0 0
+25 47 0 0 0 0
+26 47 0 0 0 0
+27 47 0 0 0 0
+28 47 0 0 0 0
+29 47 0 0 0 0
+30 47 0 0 0 0
+31 47 0 0 0 0
+32 47 0 0 0 0
+33 47 0 0 0 0
+34 47 0 0 0 0
+35 47 0 0 0 0
+36 47 0 0 0 0
+37 47 0 0 0 0
+38 47 0 0 0 0
+39 47 0 0 0 0
+40 47 0 0 0 0
+41 47 0 0 0 0
+42 47 0 0 0 0
+43 47 0 0 0 0
+44 47 1 42480 63710 51056
+45 47 1 43241 63584 51301
+46 47 1 43789 63483 51766
+47 47 1 44623 63402 52033
+48 47 1 45336 63339 52518
+49 47 1 45895 63290 53014
+50 47 1 46732 63252 53298
+51 47 1 47406 63219 53752
+52 47 1 47981 63190 54267
+53 47 1 48820 63159 54594
+54 47 1 49461 63122 54961
+55 47 1 50057 63076 55323
+56 47 1 50866 63016 55696
+57 47 1 51672 62939 56227
+58 47 1 52278 62841 56523
+59 47 1 52902 62717 56879
+60 47 1 53739 62598 57201
+61 47 1 54327 62501 57522
+62 47 1 54970 62424 58060
+63 47 1 55804 62363 58414
diff --git a/tests/golden/stitch_rect_bic.txt b/tests/golden/stitch_rect_bic.txt
new file mode 100644
index 0000000..ca546ea
--- /dev/null
+++ b/tests/golden/stitch_rect_bic.txt
@@ -0,0 +1,3072 @@
+0 0 1 8544 12745 10252
+1 0 1 9428 12758 10679
+2 0 1 10316 12771 11349
+3 0 1 10922 12783 11709
+4 0 1 11790 12795 12208
+5 0 1 12632 12807 12734
+6 0 1 13268 12820 13109
+7 0 1 13950 12832 13510
+8 0 1 14834 12845 13898
+9 0 1 15431 12858 14536
+10 0 1 16293 12871 14913
+11 0 1 17176 12884 15445
+12 0 1 17777 12897 15796
+13 0 1 18658 12911 16338
+14 0 1 19362 12924 16845
+15 0 1 19975 12937 17162
+16 0 1 20846 12950 17755
+17 0 1 21448 12963 18139
+18 0 1 22172 12975 18608
+19 0 1 23051 12987 18952
+20 0 1 23650 12999 19445
+21 0 1 24529 13009 20074
+22 0 1 25406 13020 20400
+23 0 1 26192 13029 20923
+24 0 1 26778 13038 21236
+25 0 1 27647 13046 21819
+26 0 1 28302 13052 22241
+27 0 1 28941 13058 22643
+28 0 1 29809 13063 23013
+29 0 1 30397 13067 23428
+30 0 1 31155 13070 24041
+31 0 1 32032 13072 24360
+32 0 1 32640 13072 24893
+33 0 1 33523 13072 25486
+34 0 1 34330 13070 25799
+35 0 1 34913 13067 26304
+36 0 1 35783 13063 26614
+37 0 1 36456 13058 27197
+38 0 1 37080 13052 27596
+39 0 1 37911 13046 28010
+40 0 1 38757 13038 28364
+41 0 1 39343 13029 28788
+42 0 1 40129 13020 29401
+43 0 1 41014 13009 29726
+44 0 1 41632 12999 30262
+45 0 1 42516 12987 30838
+46 0 1 43290 12975 31157
+47 0 1 43879 12963 31628
+48 0 1 44751 12950 31947
+49 0 1 45393 12937 32521
+50 0 1 46053 12924 32916
+51 0 1 46929 12911 33350
+52 0 1 47519 12897 33706
+53 0 1 48336 12884 34150
+54 0 1 49237 12871 34793
+55 0 1 49856 12858 35140
+56 0 1 50736 12845 35645
+57 0 1 51585 12832 36188
+58 0 1 52267 12820 36536
+59 0 1 52903 12807 36944
+60 0 1 53783 12795 37295
+61 0 1 54381 12783 37887
+62 0 1 55187 12771 38311
+63 0 1 56106 12758 38770
+0 1 1 8539 13817 10607
+1 1 1 9418 13831 11278
+2 1 1 10311 13845 11668
+3 1 1 10918 13859 12178
+4 1 1 11787 13874 12735
+5 1 1 13244 14170 13128
+6 1 1 10179 12509 13337
+7 1 1 8248 11269 13613
+8 1 1 9921 11531 14313
+9 1 1 10826 11619 14751
+10 1 1 12068 11718 15340
+11 1 1 13345 11826 15773
+12 1 1 14264 11941 16395
+13 1 1 15523 12063 16961
+14 1 1 16555 12190 17332
+15 1 1 17461 12320 17957
+16 1 1 18662 12452 18379
+17 1 1 19525 12584 18839
+18 1 1 20511 12715 19206
+19 1 1 21654 12844 19703
+20 1 1 22454 12969 20319
+21 1 1 23549 13089 20659
+22 1 1 24616 13203 21210
+23 1 1 25542 13279 21533
+24 1 1 26407 13641 22029
+25 1 1 27717 14297 22247
+26 1 1 28302 14204 22658
+27 1 1 28941 14212 23053
+28 1 1 29809 14218 23453
+29 1 1 30397 14223 24058
+30 1 1 31154 14226 24369
+31 1 1 32032 14228 24885
+32 1 1 32640 14229 25481
+33 1 1 33523 14228 25797
+34 1 1 34330 14226 26337
+35 1 1 34914 14223 26640
+36 1 1 35784 14218 27233
+37 1 1 36456 14212 27643
+38 1 1 37081 14204 28030
+39 1 1 37912 14196 28404
+40 1 1 38758 14186 28812
+41 1 1 39343 14175 29413
+42 1 1 40131 14163 29722
+43 1 1 41016 14150 30248
+44 1 1 41635 14136 30842
+45 1 1 42519 14122 31153
+46 1 1 43292 14106 31664
+47 1 1 43881 14091 31969
+48 1 1 44753 14074 32549
+49 1 1 45394 14057 32946
+50 1 1 46055 14040 33360
+51 1 1 46931 14023 33712
+52 1 1 47521 14006 34141
+53 1 1 48341 13989 34762
+54 1 1 49243 13971 35104
+55 1 1 49860 13954 35636
+56 1 1 50740 13938 36196
+57 1 1 51588 13921 36529
+58 1 1 52268 13905 36962
+59 1 1 52905 13889 37299
+60 1 1 53786 13874 37882
+61 1 1 54384 13859 38277
+62 1 1 55194 13845 38743
+63 1 1 56114 13831 39152
+0 2 1 8534 14900 11212
+1 2 1 9408 14918 11600
+2 2 1 10306 14936 12132
+3 2 1 10914 14955 12730
+4 2 1 11784 14974 13076
+5 2 1 9348 13483 13345
+6 2 1 30466 22856 14891
+7 2 1 47408 30732 16120
+8 2 1 44937 29929 16083
+9 2 1 45095 30235 16146
+10 2 1 44989 30528 16235
+11 2 1 44780 30808 16276
+12 2 1 44731 31075 16336
+13 2 1 44387 31330 16367
+14 2 1 44128 31571 16376
+15 2 1 43901 31799 16392
+16 2 1 43385 32014 16386
+17 2 1 43067 32217 16375
+18 2 1 42593 32406 16354
+19 2 1 41919 32582 16330
+20 2 1 41481 32744 16299
+21 2 1 40718 32889 16264
+22 2 1 39915 33016 16225
+23 2 1 39543 33746 15969
+24 2 1 35291 27886 18003
+25 2 1 25840 12769 23565
+26 2 1 28302 15384 23107
+27 2 1 28940 15391 23492
+28 2 1 29809 15397 24091
+29 2 1 30396 15401 24402
+30 2 1 31154 15404 24899
+31 2 1 32032 15406 25480
+32 2 1 32640 15406 25796
+33 2 1 33523 15406 26359
+34 2 1 34330 15404 26669
+35 2 1 34914 15401 27272
+36 2 1 35784 15397 27698
+37 2 1 36456 15391 28060
+38 2 1 37081 15384 28462
+39 2 1 37913 15376 28858
+40 2 1 38759 15366 29450
+41 2 1 39344 15355 29752
+42 2 1 40134 15342 30261
+43 2 1 41019 15328 30844
+44 2 1 41637 15312 31159
+45 2 1 42522 15294 31703
+46 2 1 43293 15274 32003
+47 2 1 43882 15254 32593
+48 2 1 44755 15234 32999
+49 2 1 45394 15213 33384
+50 2 1 46057 15191 33751
+51 2 1 46933 15169 34162
+52 2 1 47524 15147 34765
+53 2 1 48347 15124 35080
+54 2 1 49248 15102 35625
+55 2 1 49864 15080 36205
+56 2 1 50743 15058 36523
+57 2 1 51591 15036 36997
+58 2 1 52268 15015 37316
+59 2 1 52908 14994 37898
+60 2 1 53789 14974 38277
+61 2 1 54387 14955 38736
+62 2 1 55202 14936 39095
+63 2 1 56122 14918 39591
+0 3 1 8529 16007 11550
+1 3 1 9399 16030 12064
+2 3 1 10300 16054 12710
+3 3 1 10910 16079 13051
+4 3 1 11781 16105 13555
+5 3 1 8528 14350 13718
+6 3 1 34309 25204 15418
+7 3 1 54355 34034 16498
+8 3 1 50307 32695 16325
+9 3 1 49657 32668 16322
+10 3 1 48771 32651 16320
+11 3 1 47864 32642 16320
+12 3 1 47258 32640 16320
+13 3 1 46375 32640 16320
+14 3 1 45665 32640 16320
+15 3 1 45055 32640 16320
+16 3 1 44183 32640 16320
+17 3 1 43579 32640 16320
+18 3 1 42860 32640 16320
+19 3 1 41981 32640 16320
+20 3 1 41383 32640 16320
+21 3 1 40505 32640 16320
+22 3 1 39626 32640 16320
+23 3 1 39193 33192 16091
+24 3 1 35047 27848 18296
+25 3 1 25901 14185 24050
+26 3 1 28302 16535 23522
+27 3 1 28940 16540 24116
+28 3 1 29808 16544 24432
+29 3 1 30396 16547 24913
+30 3 1 31153 16550 25479
+31 3 1 32032 16551 25792
+32 3 1 32640 16551 26345
+33 3 1 33524 16551 26672
+34 3 1 34331 16550 27289
+35 3 1 34914 16547 27731
+36 3 1 35785 16544 28078
+37 3 1 36457 16540 28508
+38 3 1 37082 16535 28895
+39 3 1 37913 16529 29481
+40 3 1 38760 16522 29786
+41 3 1 39345 16514 30280
+42 3 1 40136 16505 30846
+43 3 1 41022 16494 31159
+44 3 1 41640 16482 31721
+45 3 1 42524 16469 32033
+46 3 1 43294 16454 32638
+47 3 1 43884 16438 33060
+48 3 1 44756 16420 33419
+49 3 1 45394 16400 33816
+50 3 1 46059 16379 34212
+51 3 1 46936 16355 34802
+52 3 1 47526 16328 35102
+53 3 1 48352 16300 35624
+54 3 1 49254 16272 36215
+55 3 1 49868 16243 36528
+56 3 1 50747 16215 37045
+57 3 1 51594 16187 37349
+58 3 1 52269 16159 37936
+59 3 1 52911 16132 38313
+60 3 1 53792 16105 38747
+61 3 1 54390 16079 39087
+62 3 1 55209 16054 39558
+63 3 1 56130 16030 40196
+0 4 1 8525 17149 12016
+1 4 1 9389 17180 12665
+2 4 1 10294 17212 13020
+3 4 1 10906 17245 13580
+4 4 1 11778 17279 13909
+5 4 1 8633 15705 14396
+6 4 1 33944 25588 15674
+7 4 1 54227 33868 16442
+8 4 1 50204 32640 16320
+9 4 1 49608 32640 16320
+10 4 1 48758 32640 16320
+11 4 1 47866 32640 16320
+12 4 1 47261 32640 16320
+13 4 1 46378 32640 16320
+14 4 1 45666 32640 16320
+15 4 1 45056 32640 16320
+16 4 1 44185 32640 16320
+17 4 1 43580 32640 16320
+18 4 1 42862 32640 16320
+19 4 1 41983 32640 16320
+20 4 1 41385 32640 16320
+21 4 1 40508 32640 16320
+22 4 1 39628 32640 16320
+23 4 1 39189 33145 16084
+24 4 1 35066 28206 18357
+25 4 1 25899 15467 24519
+26 4 1 28302 17655 24133
+27 4 1 28939 17658 24456
+28 4 1 29808 17661 24923
+29 4 1 30396 17663 25476
+30 4 1 31153 17665 25797
+31 4 1 32031 17666 26311
+32 4 1 32640 17666 26649
+33 4 1 33524 17666 27280
+34 4 1 34331 17665 27737
+35 4 1 34915 17663 28084
+36 4 1 35785 17661 28533
+37 4 1 36457 17658 28918
+38 4 1 37083 17655 29501
+39 4 1 37914 17650 29812
+40 4 1 38761 17645 30294
+41 4 1 39346 17639 30843
+42 4 1 40138 17633 31161
+43 4 1 41024 17625 31697
+44 4 1 41643 17617 32033
+45 4 1 42527 17607 32654
+46 4 1 43295 17597 33093
+47 4 1 43886 17585 33440
+48 4 1 44758 17573 33867
+49 4 1 45395 17558 34256
+50 4 1 46061 17543 34839
+51 4 1 46938 17526 35140
+52 4 1 47529 17507 35647
+53 4 1 48358 17486 36218
+54 4 1 49259 17463 36540
+55 4 1 49872 17438 37091
+56 4 1 50750 17411 37397
+57 4 1 51597 17381 37994
+58 4 1 52270 17348 38380
+59 4 1 52914 17313 38777
+60 4 1 53795 17279 39127
+61 4 1 54393 17245 39572
+62 4 1 55217 17212 40180
+63 4 1 56137 17180 40510
+0 5 1 8520 18340 12644
+1 5 1 9380 18380 12986
+2 5 1 10289 18417 13588
+3 5 1 10901 18450 13924
+4 5 1 11775 18481 14555
+5 5 1 8697 17050 14896
+6 5 1 33734 26058 15788
+7 5 1 54298 33798 16418
+8 5 1 50208 32640 16320
+9 5 1 49611 32640 16320
+10 5 1 48765 32640 16320
+11 5 1 47870 32640 16320
+12 5 1 47264 32640 16320
+13 5 1 46381 32640 16320
+14 5 1 45667 32640 16320
+15 5 1 45058 32640 16320
+16 5 1 44186 32640 16320
+17 5 1 43581 32640 16320
+18 5 1 42864 32640 16320
+19 5 1 41985 32640 16320
+20 5 1 41388 32640 16320
+21 5 1 40512 32640 16320
+22 5 1 39631 32640 16320
+23 5 1 39184 33101 16068
+24 5 1 35085 28554 18529
+25 5 1 25897 16721 25166
+26 5 1 28301 18749 24471
+27 5 1 28939 18752 24929
+28 5 1 29807 18754 25473
+29 5 1 30396 18756 25806
+30 5 1 31153 18757 26278
+31 5 1 32031 18757 26617
+32 5 1 32640 18758 27246
+33 5 1 33524 18757 27715
+34 5 1 34331 18757 28081
+35 5 1 34915 18756 28539
+36 5 1 35786 18754 28928
+37 5 1 36457 18752 29511
+38 5 1 37083 18749 29829
+39 5 1 37915 18746 30303
+40 5 1 38762 18743 30838
+41 5 1 39347 18739 31169
+42 5 1 40141 18734 31661
+43 5 1 41027 18729 32004
+44 5 1 41645 18723 32641
+45 5 1 42530 18716 33095
+46 5 1 43296 18709 33448
+47 5 1 43887 18700 33893
+48 5 1 44759 18691 34283
+49 5 1 45395 18681 34862
+50 5 1 46063 18671 35169
+51 5 1 46940 18659 35662
+52 5 1 47532 18645 36212
+53 5 1 48363 18631 36535
+54 5 1 49264 18615 37091
+55 5 1 49876 18597 37420
+56 5 1 50753 18578 38035
+57 5 1 51600 18557 38443
+58 5 1 52271 18534 38813
+59 5 1 52916 18509 39195
+60 5 1 53798 18481 39623
+61 5 1 54396 18450 40209
+62 5 1 55224 18417 40514
+63 5 1 56145 18380 41102
+0 6 1 8516 19540 12979
+1 6 1 9371 19567 13550
+2 6 1 10283 19592 13907
+3 6 1 10897 19614 14553
+4 6 1 11771 19634 14998
+5 6 1 8761 18334 15335
+6 6 1 33524 26517 16062
+7 6 1 54368 33728 16376
+8 6 1 50211 32640 16320
+9 6 1 49615 32640 16320
+10 6 1 48771 32640 16320
+11 6 1 47874 32640 16320
+12 6 1 47268 32640 16320
+13 6 1 46384 32640 16320
+14 6 1 45668 32640 16320
+15 6 1 45059 32640 16320
+16 6 1 44188 32640 16320
+17 6 1 43581 32640 16320
+18 6 1 42866 32640 16320
+19 6 1 41987 32640 16320
+20 6 1 41390 32640 16320
+21 6 1 40515 32640 16320
+22 6 1 39633 32640 16320
+23 6 1 39180 33059 16058
+24 6 1 35104 28886 18649
+25 6 1 25894 17928 25591
+26 6 1 28301 19803 24930
+27 6 1 28938 19805 25470
+28 6 1 29807 19806 25815
+29 6 1 30395 19807 26251
+30 6 1 31152 19808 26596
+31 6 1 32031 19809 27204
+32 6 1 32640 19809 27666
+33 6 1 33524 19809 28068
+34 6 1 34331 19808 28523
+35 6 1 34915 19807 28925
+36 6 1 35786 19806 29514
+37 6 1 36457 19805 29837
+38 6 1 37084 19803 30308
+39 6 1 37915 19802 30834
+40 6 1 38763 19799 31179
+41 6 1 39348 19797 31629
+42 6 1 40143 19794 31975
+43 6 1 41030 19790 32602
+44 6 1 41648 19787 33065
+45 6 1 42532 19782 33446
+46 6 1 43298 19778 33896
+47 6 1 43889 19773 34294
+48 6 1 44761 19767 34873
+49 6 1 45396 19761 35187
+50 6 1 46066 19754 35672
+51 6 1 46943 19747 36203
+52 6 1 47535 19739 36538
+53 6 1 48369 19729 37049
+54 6 1 49269 19719 37406
+55 6 1 49880 19709 38041
+56 6 1 50757 19696 38469
+57 6 1 51603 19683 38830
+58 6 1 52271 19668 39241
+59 6 1 52919 19652 39660
+60 6 1 53801 19634 40233
+61 6 1 54400 19614 40533
+62 6 1 55232 19592 41098
+63 6 1 56152 19567 41662
+0 7 1 8511 20743 13505
+1 7 1 9363 20767 13855
+2 7 1 10276 20788 14517
+3 7 1 10892 20808 14995
+4 7 1 11768 20826 15367
+5 7 1 8826 19667 15820
+6 7 1 33314 27014 16185
+7 7 1 54439 33648 16350
+8 7 1 50214 32640 16320
+9 7 1 49618 32640 16320
+10 7 1 48777 32640 16320
+11 7 1 47879 32640 16320
+12 7 1 47271 32640 16320
+13 7 1 46387 32640 16320
+14 7 1 45668 32640 16320
+15 7 1 45061 32640 16320
+16 7 1 44189 32640 16320
+17 7 1 43582 32640 16320
+18 7 1 42868 32640 16320
+19 7 1 41989 32640 16320
+20 7 1 41393 32640 16320
+21 7 1 40518 32640 16320
+22 7 1 39635 32640 16320
+23 7 1 39175 33014 16051
+24 7 1 35123 29258 18719
+25 7 1 25892 19311 26100
+26 7 1 28301 21013 25468
+27 7 1 28938 21015 25820
+28 7 1 29806 21017 26233
+29 7 1 30395 21018 26583
+30 7 1 31152 21019 27174
+31 7 1 32031 21020 27613
+32 7 1 32640 21020 28041
+33 7 1 33525 21020 28487
+34 7 1 34332 21019 28908
+35 7 1 34916 21018 29508
+36 7 1 35787 21017 29835
+37 7 1 36457 21015 30310
+38 7 1 37084 21013 30831
+39 7 1 37916 21010 31185
+40 7 1 38764 21007 31604
+41 7 1 39349 21003 31956
+42 7 1 40145 20999 32564
+43 7 1 41033 20994 33010
+44 7 1 41650 20989 33432
+45 7 1 42535 20984 33876
+46 7 1 43299 20978 34291
+47 7 1 43890 20972 34876
+48 7 1 44762 20965 35195
+49 7 1 45396 20957 35678
+50 7 1 46068 20949 36195
+51 7 1 46945 20940 36545
+52 7 1 47537 20931 37006
+53 7 1 48375 20921 37366
+54 7 1 49274 20910 38016
+55 7 1 49884 20899 38458
+56 7 1 50760 20886 38835
+57 7 1 51605 20873 39262
+58 7 1 52272 20858 39681
+59 7 1 52922 20843 40246
+60 7 1 53804 20826 40548
+61 7 1 54403 20808 41091
+62 7 1 55240 20788 41627
+63 7 1 56159 20767 41989
+0 8 1 8507 22100 13819
+1 8 1 9354 22113 14445
+2 8 1 10270 22124 14952
+3 8 1 10888 22135 15348
+4 8 1 11765 22144 15799
+5 8 1 8891 21127 16293
+6 8 1 33104 27579 16509
+7 8 1 54509 33555 16298
+8 8 1 50217 32640 16320
+9 8 1 49621 32640 16320
+10 8 1 48783 32640 16320
+11 8 1 47884 32640 16320
+12 8 1 47275 32640 16320
+13 8 1 46390 32640 16320
+14 8 1 45669 32640 16320
+15 8 1 45062 32640 16320
+16 8 1 44191 32640 16320
+17 8 1 43583 32640 16320
+18 8 1 42870 32640 16320
+19 8 1 41991 32640 16320
+20 8 1 41395 32640 16320
+21 8 1 40522 32640 16320
+22 8 1 39638 32640 16320
+23 8 1 39171 32969 16037
+24 8 1 35142 29633 18893
+25 8 1 25890 20719 26695
+26 8 1 28301 22241 25818
+27 8 1 28937 22242 26226
+28 8 1 29806 22243 26576
+29 8 1 30395 22244 27156
+30 8 1 31152 22244 27575
+31 8 1 32031 22245 28014
+32 8 1 32640 22245 28433
+33 8 1 33525 22245 28879
+34 8 1 34332 22244 29494
+35 8 1 34916 22244 29824
+36 8 1 35787 22243 30308
+37 8 1 36457 22242 30832
+38 8 1 37085 22241 31188
+39 8 1 37916 22239 31592
+40 8 1 38765 22237 31945
+41 8 1 39350 22235 32540
+42 8 1 40148 22233 32958
+43 8 1 41035 22231 33405
+44 8 1 41653 22228 33836
+45 8 1 42537 22225 34275
+46 8 1 43300 22222 34871
+47 8 1 43892 22219 35193
+48 8 1 44764 22215 35681
+49 8 1 45397 22211 36191
+50 8 1 46070 22207 36552
+51 8 1 46948 22203 36973
+52 8 1 47540 22198 37335
+53 8 1 48381 22193 37967
+54 8 1 49278 22188 38414
+55 8 1 49888 22182 38830
+56 8 1 50763 22175 39258
+57 8 1 51608 22169 39689
+58 8 1 52273 22161 40251
+59 8 1 52925 22153 40558
+60 8 1 53808 22144 41086
+61 8 1 54407 22135 41593
+62 8 1 55248 22124 41964
+63 8 1 56166 22113 42489
+0 9 1 8503 23150 14388
+1 9 1 9345 23159 14875
+2 9 1 10264 23167 15311
+3 9 1 10883 23175 15773
+4 9 1 11761 23183 16212
+5 9 1 8957 22282 16885
+6 9 1 32894 28027 16675
+7 9 1 54579 33481 16263
+8 9 1 50220 32640 16320
+9 9 1 49624 32640 16320
+10 9 1 48789 32640 16320
+11 9 1 47888 32640 16320
+12 9 1 47278 32640 16320
+13 9 1 46393 32640 16320
+14 9 1 45670 32640 16320
+15 9 1 45064 32640 16320
+16 9 1 44192 32640 16320
+17 9 1 43583 32640 16320
+18 9 1 42872 32640 16320
+19 9 1 41994 32640 16320
+20 9 1 41397 32640 16320
+21 9 1 40525 32640 16320
+22 9 1 39640 32640 16320
+23 9 1 39166 32931 16031
+24 9 1 35161 29949 18970
+25 9 1 25887 21911 27126
+26 9 1 28301 23282 26231
+27 9 1 28937 23283 26575
+28 9 1 29806 23285 27148
+29 9 1 30395 23286 27556
+30 9 1 31151 23287 27999
+31 9 1 32030 23287 28381
+32 9 1 32640 23287 28836
+33 9 1 33525 23287 29470
+34 9 1 34332 23287 29804
+35 9 1 34917 23286 30302
+36 9 1 35788 23285 30835
+37 9 1 36457 23283 31185
+38 9 1 37085 23282 31594
+39 9 1 37917 23280 31943
+40 9 1 38766 23278 32528
+41 9 1 39351 23275 32926
+42 9 1 40150 23272 33383
+43 9 1 41038 23269 33780
+44 9 1 41655 23266 34246
+45 9 1 42540 23262 34858
+46 9 1 43301 23259 35182
+47 9 1 43893 23254 35681
+48 9 1 44765 23250 36192
+49 9 1 45397 23246 36554
+50 9 1 46072 23241 36956
+51 9 1 46950 23236 37318
+52 9 1 47543 23231 37930
+53 9 1 48387 23226 38348
+54 9 1 49283 23221 38813
+55 9 1 49892 23215 39231
+56 9 1 50766 23209 39683
+57 9 1 51611 23203 40252
+58 9 1 52274 23196 40562
+59 9 1 52928 23190 41084
+60 9 1 53811 23183 41568
+61 9 1 54411 23175 41952
+62 9 1 55257 23167 42419
+63 9 1 56172 23159 42834
+0 10 1 8499 24225 14819
+1 10 1 9337 24232 15257
+2 10 1 10257 24240 15715
+3 10 1 10878 24247 16176
+4 10 1 11758 24254 16780
+5 10 1 9024 23471 17248
+6 10 1 32683 28505 16868
+7 10 1 54649 33400 16227
+8 10 1 50223 32640 16320
+9 10 1 49627 32640 16320
+10 10 1 48795 32640 16320
+11 10 1 47893 32640 16320
+12 10 1 47282 32640 16320
+13 10 1 46396 32640 16320
+14 10 1 45671 32640 16320
+15 10 1 45066 32640 16320
+16 10 1 44194 32640 16320
+17 10 1 43584 32640 16320
+18 10 1 42874 32640 16320
+19 10 1 41996 32640 16320
+20 10 1 41400 32640 16320
+21 10 1 40528 32640 16320
+22 10 1 39642 32640 16320
+23 10 1 39162 32893 16021
+24 10 1 35180 30274 19097
+25 10 1 25885 23150 27609
+26 10 1 28301 24364 26578
+27 10 1 28936 24366 27151
+28 10 1 29805 24368 27555
+29 10 1 30394 24369 27994
+30 10 1 31151 24370 28353
+31 10 1 32030 24371 28798
+32 10 1 32640 24371 29434
+33 10 1 33525 24371 29776
+34 10 1 34332 24370 30293
+35 10 1 34917 24369 30841
+36 10 1 35788 24368 31178
+37 10 1 36458 24366 31609
+38 10 1 37086 24364 31947
+39 10 1 37918 24362 32530
+40 10 1 38767 24359 32916
+41 10 1 39352 24356 33374
+42 10 1 40152 24352 33735
+43 10 1 41041 24349 34206
+44 10 1 41658 24345 34838
+45 10 1 42542 24341 35164
+46 10 1 43302 24336 35680
+47 10 1 43895 24331 36200
+48 10 1 44767 24326 36553
+49 10 1 45398 24321 36958
+50 10 1 46074 24316 37313
+51 10 1 46952 24310 37913
+52 10 1 47546 24304 38301
+53 10 1 48393 24298 38784
+54 10 1 49288 24292 39186
+55 10 1 49896 24286 39667
+56 10 1 50770 24280 40249
+57 10 1 51614 24274 40560
+58 10 1 52274 24267 41085
+59 10 1 52931 24260 41558
+60 10 1 53814 24254 41949
+61 10 1 54415 24247 42364
+62 10 1 55265 24240 42775
+63 10 1 56179 24232 43444
+0 11 1 8495 25300 15223
+1 11 1 9329 25308 15635
+2 11 1 10250 25316 16117
+3 11 1 10873 25324 16757
+4 11 1 11754 25331 17085
+5 11 1 9091 24665 17797
+6 11 1 32473 28998 17199
+7 11 1 54718 33314 16170
+8 11 1 50226 32640 16320
+9 11 1 49630 32640 16320
+10 11 1 48801 32640 16320
+11 11 1 47898 32640 16320
+12 11 1 47285 32640 16320
+13 11 1 46399 32640 16320
+14 11 1 45672 32640 16320
+15 11 1 45067 32640 16320
+16 11 1 44195 32640 16320
+17 11 1 43585 32640 16320
+18 11 1 42876 32640 16320
+19 11 1 41998 32640 16320
+20 11 1 41402 32640 16320
+21 11 1 40532 32640 16320
+22 11 1 39645 32640 16320
+23 11 1 39158 32856 16016
+24 11 1 35199 30601 19164
+25 11 1 25883 24414 28025
+26 11 1 28300 25468 27164
+27 11 1 28936 25470 27573
+28 11 1 29805 25473 27995
+29 11 1 30394 25474 28352
+30 11 1 31150 25475 28784
+31 11 1 32030 25476 29406
+32 11 1 32640 25476 29741
+33 11 1 33526 25476 30279
+34 11 1 34333 25475 30847
+35 11 1 34917 25474 31170
+36 11 1 35789 25473 31636
+37 11 1 36458 25470 31960
+38 11 1 37087 25468 32544
+39 11 1 37918 25465 32928
+40 11 1 38768 25461 33375
+41 11 1 39353 25457 33720
+42 11 1 40155 25453 34178
+43 11 1 41044 25449 34808
+44 11 1 41660 25443 35141
+45 11 1 42545 25438 35677
+46 11 1 43303 25432 36212
+47 11 1 43896 25426 36550
+48 11 1 44769 25420 36979
+49 11 1 45398 25414 37321
+50 11 1 46076 25407 37914
+51 11 1 46955 25400 38284
+52 11 1 47549 25393 38767
+53 11 1 48399 25385 39131
+54 11 1 49292 25378 39640
+55 11 1 49900 25370 40242
+56 11 1 50773 25363 40554
+57 11 1 51617 25355 41090
+58 11 1 52275 25347 41563
+59 11 1 52934 25339 41950
+60 11 1 53818 25331 42338
+61 11 1 54419 25324 42738
+62 11 1 55274 25316 43387
+63 11 1 56185 25308 43786
+0 12 1 8492 26389 15589
+1 12 1 9321 26398 16044
+2 12 1 10243 26408 16712
+3 12 1 10868 26418 17059
+4 12 1 11751 26428 17608
+5 12 1 9158 25877 18335
+6 12 1 32262 29514 17331
+7 12 1 54787 33223 16137
+8 12 1 50228 32640 16320
+9 12 1 49633 32640 16320
+10 12 1 48807 32640 16320
+11 12 1 47903 32640 16320
+12 12 1 47289 32640 16320
+13 12 1 46402 32640 16320
+14 12 1 45673 32640 16320
+15 12 1 45069 32640 16320
+16 12 1 44197 32640 16320
+17 12 1 43585 32640 16320
+18 12 1 42878 32640 16320
+19 12 1 42000 32640 16320
+20 12 1 41404 32640 16320
+21 12 1 40535 32640 16320
+22 12 1 39647 32640 16320
+23 12 1 39153 32819 16006
+24 12 1 35218 30932 19288
+25 12 1 25881 25712 28672
+26 12 1 28300 26601 27607
+27 12 1 28935 26604 28006
+28 12 1 29804 26607 28377
+29 12 1 30394 26609 28793
+30 12 1 31150 26611 29404
+31 12 1 32030 26611 29722
+32 12 1 32640 26612 30261
+33 12 1 33526 26611 30851
+34 12 1 34333 26611 31166
+35 12 1 34918 26609 31671
+36 12 1 35789 26607 31983
+37 12 1 36458 26604 32573
+38 12 1 37087 26601 32962
+39 12 1 37919 26597 33386
+40 12 1 38769 26593 33735
+41 12 1 39354 26588 34178
+42 12 1 40157 26583 34792
+43 12 1 41047 26577 35116
+44 12 1 41663 26571 35674
+45 12 1 42547 26564 36228
+46 12 1 43304 26557 36550
+47 12 1 43898 26549 37016
+48 12 1 44770 26542 37342
+49 12 1 45399 26533 37934
+50 12 1 46079 26525 38298
+51 12 1 46958 26516 38767
+52 12 1 47552 26507 39104
+53 12 1 48405 26497 39605
+54 12 1 49297 26488 40230
+55 12 1 49903 26478 40546
+56 12 1 50776 26468 41100
+57 12 1 51619 26458 41584
+58 12 1 52276 26448 41953
+59 12 1 52937 26438 42344
+60 12 1 53821 26428 42728
+61 12 1 54423 26418 43354
+62 12 1 55282 26408 43720
+63 12 1 56191 26398 44259
+0 13 1 8488 27505 16009
+1 13 1 9313 27518 16656
+2 13 1 10236 27531 17016
+3 13 1 10863 27544 17589
+4 13 1 11747 27557 18135
+5 13 1 9225 27120 18726
+6 13 1 32051 30059 17641
+7 13 1 54855 33124 16083
+8 13 1 50231 32640 16320
+9 13 1 49636 32640 16320
+10 13 1 48813 32640 16320
+11 13 1 47908 32640 16320
+12 13 1 47292 32640 16320
+13 13 1 46405 32640 16320
+14 13 1 45674 32640 16320
+15 13 1 45070 32640 16320
+16 13 1 44198 32640 16320
+17 13 1 43586 32640 16320
+18 13 1 42880 32640 16320
+19 13 1 42002 32640 16320
+20 13 1 41407 32640 16320
+21 13 1 40538 32640 16320
+22 13 1 39650 32640 16320
+23 13 1 39149 32782 15995
+24 13 1 35237 31271 19452
+25 13 1 25879 27054 29225
+26 13 1 28300 27773 28027
+27 13 1 28935 27777 28426
+28 13 1 29804 27780 28826
+29 13 1 30394 27783 29428
+30 13 1 31150 27784 29738
+31 13 1 32030 27786 30259
+32 13 1 32640 27786 30852
+33 13 1 33526 27786 31169
+34 13 1 34333 27784 31710
+35 13 1 34918 27783 32016
+36 13 1 35790 27780 32615
+37 13 1 36458 27777 33016
+38 13 1 37088 27773 33411
+39 13 1 37920 27769 33780
+40 13 1 38770 27763 34207
+41 13 1 39356 27757 34806
+42 13 1 40160 27751 35113
+43 13 1 41050 27744 35670
+44 13 1 41665 27736 36245
+45 13 1 42550 27728 36558
+46 13 1 43305 27719 37063
+47 13 1 43899 27710 37377
+48 13 1 44772 27700 37973
+49 13 1 45399 27690 38341
+50 13 1 46081 27679 38783
+51 13 1 46960 27668 39118
+52 13 1 47555 27657 39598
+53 13 1 48411 27645 40213
+54 13 1 49301 27633 40539
+55 13 1 49907 27621 41115
+56 13 1 50779 27608 41617
+57 13 1 51622 27596 41962
+58 13 1 52276 27583 42381
+59 13 1 52940 27570 42745
+60 13 1 53825 27557 43356
+61 13 1 54427 27544 43692
+62 13 1 55291 27531 44239
+63 13 1 56197 27518 44590
+0 14 1 8485 28663 16640
+1 14 1 9305 28680 16977
+2 14 1 10229 28696 17558
+3 14 1 10858 28713 18148
+4 14 1 11743 28730 18483
+5 14 1 9293 28408 19255
+6 14 1 31840 30640 17790
+7 14 1 54924 33017 16048
+8 14 1 50234 32640 16320
+9 14 1 49639 32640 16320
+10 14 1 48818 32640 16320
+11 14 1 47913 32640 16320
+12 14 1 47296 32640 16320
+13 14 1 46408 32640 16320
+14 14 1 45675 32640 16320
+15 14 1 45072 32640 16320
+16 14 1 44200 32640 16320
+17 14 1 43587 32640 16320
+18 14 1 42882 32640 16320
+19 14 1 42004 32640 16320
+20 14 1 41409 32640 16320
+21 14 1 40542 32640 16320
+22 14 1 39652 32640 16320
+23 14 1 39145 32745 15991
+24 14 1 35256 31610 19509
+25 14 1 25877 28411 29706
+26 14 1 28300 28956 28482
+27 14 1 28934 28958 28868
+28 14 1 29804 28961 29463
+29 14 1 30393 28963 29772
+30 14 1 31149 28964 30274
+31 14 1 32029 28965 30850
+32 14 1 32640 28965 31169
+33 14 1 33526 28965 31730
+34 14 1 34333 28964 32045
+35 14 1 34918 28963 32654
+36 14 1 35790 28961 33071
+37 14 1 36458 28958 33440
+38 14 1 37088 28956 33837
+39 14 1 37920 28952 34250
+40 14 1 38770 28948 34838
+41 14 1 39357 28944 35140
+42 14 1 40162 28939 35677
+43 14 1 41052 28933 36251
+44 14 1 41668 28927 36572
+45 14 1 42552 28920 37109
+46 14 1 43306 28912 37423
+47 14 1 43901 28904 38026
+48 14 1 44773 28895 38406
+49 14 1 45400 28885 38815
+50 14 1 46083 28875 39169
+51 14 1 46963 28863 39629
+52 14 1 47558 28851 40223
+53 14 1 48417 28838 40538
+54 14 1 49306 28824 41136
+55 14 1 49911 28809 41660
+56 14 1 50781 28794 41985
+57 14 1 51625 28778 42443
+58 14 1 52277 28762 42789
+59 14 1 52943 28746 43392
+60 14 1 53828 28730 43716
+61 14 1 54431 28713 44236
+62 14 1 55300 28696 44565
+63 14 1 56203 28680 45154
+0 15 1 8482 29875 16993
+1 15 1 9297 29893 17532
+2 15 1 10221 29910 18145
+3 15 1 10853 29926 18485
+4 15 1 11739 29940 19020
+5 15 1 9362 29726 19687
+6 15 1 31629 31247 18152
+7 15 1 54991 32904 15984
+8 15 1 50237 32640 16320
+9 15 1 49642 32640 16320
+10 15 1 48824 32640 16320
+11 15 1 47918 32640 16320
+12 15 1 47300 32640 16320
+13 15 1 46411 32640 16320
+14 15 1 45676 32640 16320
+15 15 1 45073 32640 16320
+16 15 1 44201 32640 16320
+17 15 1 43587 32640 16320
+18 15 1 42884 32640 16320
+19 15 1 42006 32640 16320
+20 15 1 41411 32640 16320
+21 15 1 40545 32640 16320
+22 15 1 39654 32640 16320
+23 15 1 39141 32711 15982
+24 15 1 35276 31934 19655
+25 15 1 25875 29723 30220
+26 15 1 28300 30099 28897
+27 15 1 28934 30101 29487
+28 15 1 29803 30102 29800
+29 15 1 30393 30104 30286
+30 15 1 31149 30104 30845
+31 15 1 32029 30105 31164
+32 15 1 32640 30105 31711
+33 15 1 33527 30105 32046
+34 15 1 34334 30104 32667
+35 15 1 34919 30104 33100
+36 15 1 35790 30102 33455
+37 15 1 36458 30101 33877
+38 15 1 37089 30099 34281
+39 15 1 37921 30097 34861
+40 15 1 38771 30094 35163
+41 15 1 39358 30091 35683
+42 15 1 40164 30088 36237
+43 15 1 41055 30084 36567
+44 15 1 41670 30080 37118
+45 15 1 42555 30075 37445
+46 15 1 43307 30070 38059
+47 15 1 43902 30064 38456
+48 15 1 44775 30058 38836
+49 15 1 45400 30052 39218
+50 15 1 46085 30045 39663
+51 15 1 46965 30037 40240
+52 15 1 47562 30029 40544
+53 15 1 48424 30021 41133
+54 15 1 49310 30011 41682
+55 15 1 49914 30001 42008
+56 15 1 50784 29991 42504
+57 15 1 51627 29979 42845
+58 15 1 52278 29967 43447
+59 15 1 52946 29954 43779
+60 15 1 53832 29940 44257
+61 15 1 54436 29926 44580
+62 15 1 55310 29910 45156
+63 15 1 56209 29893 45730
+0 16 1 8478 31067 17526
+1 16 1 9290 31079 18113
+2 16 1 10213 31089 18463
+3 16 1 10848 31099 19029
+4 16 1 11735 31108 19373
+5 16 1 9430 30991 20344
+6 16 1 31418 31842 18443
+7 16 1 55058 32792 15924
+8 16 1 50240 32640 16320
+9 16 1 49644 32640 16320
+10 16 1 48829 32640 16320
+11 16 1 47924 32640 16320
+12 16 1 47304 32640 16320
+13 16 1 46414 32640 16320
+14 16 1 45677 32640 16320
+15 16 1 45075 32640 16320
+16 16 1 44202 32640 16320
+17 16 1 43588 32640 16320
+18 16 1 42886 32640 16320
+19 16 1 42008 32640 16320
+20 16 1 41413 32640 16320
+21 16 1 40549 32640 16320
+22 16 1 39657 32640 16320
+23 16 1 39137 32679 15979
+24 16 1 35295 32245 19704
+25 16 1 25873 30999 30689
+26 16 1 28300 31210 29501
+27 16 1 28933 31212 29821
+28 16 1 29803 31213 30295
+29 16 1 30393 31214 30839
+30 16 1 31148 31214 31167
+31 16 1 32029 31214 31672
+32 16 1 32640 31215 32018
+33 16 1 33527 31214 32656
+34 16 1 34334 31214 33102
+35 16 1 34919 31214 33459
+36 16 1 35791 31213 33898
+37 16 1 36459 31212 34299
+38 16 1 37089 31210 34875
+39 16 1 37922 31209 35182
+40 16 1 38772 31207 35687
+41 16 1 39359 31205 36221
+42 16 1 40167 31203 36556
+43 16 1 41058 31201 37087
+44 16 1 41673 31198 37438
+45 16 1 42557 31195 38066
+46 16 1 43308 31192 38478
+47 16 1 43904 31188 38847
+48 16 1 44776 31184 39251
+49 16 1 45401 31180 39685
+50 16 1 46088 31175 40250
+51 16 1 46968 31171 40552
+52 16 1 47565 31165 41117
+53 16 1 48430 31160 41656
+54 16 1 49314 31154 42009
+55 16 1 49918 31147 42526
+56 16 1 50787 31141 42875
+57 16 1 51630 31133 43481
+58 16 1 52278 31126 43831
+59 16 1 52949 31117 44265
+60 16 1 53836 31108 44604
+61 16 1 54440 31099 45150
+62 16 1 55319 31089 45718
+63 16 1 56214 31079 46065
+0 17 1 8475 32209 18091
+1 17 1 9283 32216 18434
+2 17 1 10205 32222 18984
+3 17 1 10842 32228 19355
+4 17 1 11731 32234 19995
+5 17 1 9499 32207 20773
+6 17 1 31207 32429 18617
+7 17 1 55125 32680 15885
+8 17 1 50242 32640 16320
+9 17 1 49647 32640 16320
+10 17 1 48835 32640 16320
+11 17 1 47929 32640 16320
+12 17 1 47307 32640 16320
+13 17 1 46418 32640 16320
+14 17 1 45678 32640 16320
+15 17 1 45077 32640 16320
+16 17 1 44204 32640 16320
+17 17 1 43588 32640 16320
+18 17 1 42888 32640 16320
+19 17 1 42010 32640 16320
+20 17 1 41415 32640 16320
+21 17 1 40552 32640 16320
+22 17 1 39659 32640 16320
+23 17 1 39132 32649 15969
+24 17 1 35314 32546 19856
+25 17 1 25871 32247 31332
+26 17 1 28299 32298 29833
+27 17 1 28933 32299 30300
+28 17 1 29802 32300 30833
+29 17 1 30393 32300 31175
+30 17 1 31148 32301 31637
+31 17 1 32029 32301 31984
+32 17 1 32640 32301 32618
+33 17 1 33527 32301 33075
+34 17 1 34334 32301 33456
+35 17 1 34919 32300 33900
+36 17 1 35791 32300 34306
+37 17 1 36459 32299 34881
+38 17 1 37090 32298 35194
+39 17 1 37922 32297 35690
+40 17 1 38773 32296 36207
+41 17 1 39360 32295 36555
+42 17 1 40169 32294 37039
+43 17 1 41061 32292 37405
+44 17 1 41675 32290 38050
+45 17 1 42560 32288 38472
+46 17 1 43309 32286 38849
+47 17 1 43905 32284 39266
+48 17 1 44778 32281 39698
+49 17 1 45402 32279 40255
+50 17 1 46090 32276 40559
+51 17 1 46971 32273 41105
+52 17 1 47568 32269 41617
+53 17 1 48437 32266 41987
+54 17 1 49318 32262 42508
+55 17 1 49921 32258 42877
+56 17 1 50790 32254 43493
+57 17 1 51633 32249 43861
+58 17 1 52279 32244 44265
+59 17 1 52952 32239 44624
+60 17 1 53839 32234 45146
+61 17 1 54445 32228 45683
+62 17 1 55328 32222 46046
+63 17 1 56220 32216 46588
+0 18 1 8472 33314 18428
+1 18 1 9275 33318 18915
+2 18 1 10197 33322 19299
+3 18 1 10837 33325 19965
+4 18 1 11727 33329 20383
+5 18 1 9567 33387 21177
+6 18 1 30995 33014 18913
+7 18 1 55191 32567 15829
+8 18 1 50245 32640 16320
+9 18 1 49650 32640 16320
+10 18 1 48840 32640 16320
+11 18 1 47935 32640 16320
+12 18 1 47311 32640 16320
+13 18 1 46421 32640 16320
+14 18 1 45679 32640 16320
+15 18 1 45078 32640 16320
+16 18 1 44205 32640 16320
+17 18 1 43589 32640 16320
+18 18 1 42890 32640 16320
+19 18 1 42012 32640 16320
+20 18 1 41418 32640 16320
+21 18 1 40556 32640 16320
+22 18 1 39662 32640 16320
+23 18 1 39128 32621 15965
+24 18 1 35333 32839 19950
+25 18 1 25869 33478 31750
+26 18 1 28299 33371 30302
+27 18 1 28932 33372 30830
+28 18 1 29802 33372 31182
+29 18 1 30393 33373 31609
+30 18 1 31148 33373 31960
+31 18 1 32029 33373 32575
+32 18 1 32640 33373 33022
+33 18 1 33527 33373 33442
+34 18 1 34334 33373 33881
+35 18 1 34920 33373 34302
+36 18 1 35792 33372 34882
+37 18 1 36459 33372 35198
+38 18 1 37091 33371 35691
+39 18 1 37923 33371 36197
+40 18 1 38774 33370 36558
+41 18 1 39361 33369 36998
+42 18 1 40172 33368 37364
+43 18 1 41064 33367 38009
+44 18 1 41678 33366 38440
+45 18 1 42562 33364 38846
+46 18 1 43310 33363 39263
+47 18 1 43907 33361 39702
+48 18 1 44779 33360 40257
+49 18 1 45402 33358 40563
+50 18 1 46092 33356 41097
+51 18 1 46974 33354 41586
+52 18 1 47572 33352 41967
+53 18 1 48443 33349 42453
+54 18 1 49322 33347 42854
+55 18 1 49924 33344 43487
+56 18 1 50793 33342 43866
+57 18 1 51635 33339 44264
+58 18 1 52280 33336 44637
+59 18 1 52955 33332 45143
+60 18 1 53843 33329 45655
+61 18 1 54450 33325 46025
+62 18 1 55338 33322 46536
+63 18 1 56225 33318 46932
+0 19 1 8470 34396 18865
+1 19 1 9268 34399 19239
+2 19 1 10189 34402 19899
+3 19 1 10831 34404 20341
+4 19 1 11723 34407 20768
+5 19 1 9293 34576 21660
+6 19 1 32852 33424 18792
+7 19 1 60183 32016 14948
+8 19 1 54512 32241 15507
+9 19 1 53808 32238 15444
+10 19 1 52837 32235 15363
+11 19 1 51739 32232 15315
+12 19 1 50986 32229 15234
+13 19 1 49896 32226 15162
+14 19 1 48986 32224 15113
+15 19 1 48253 32221 15048
+16 19 1 47175 32219 14997
+17 19 1 46414 32217 14914
+18 19 1 45551 32215 14866
+19 19 1 44457 32214 14795
+20 19 1 43719 32212 14749
+21 19 1 42643 32211 14670
+22 19 1 41522 32209 14591
+23 19 1 40811 32150 14138
+24 19 1 36474 32811 18667
+25 19 1 25636 34766 32540
+26 19 1 28299 34438 30829
+27 19 1 28932 34439 31185
+28 19 1 29802 34439 31593
+29 19 1 30392 34439 31947
+30 19 1 31147 34439 32545
+31 19 1 32028 34440 32966
+32 19 1 32640 34440 33416
+33 19 1 33528 34440 33844
+34 19 1 34335 34439 34286
+35 19 1 34920 34439 34877
+36 19 1 35792 34439 35195
+37 19 1 36459 34439 35692
+38 19 1 37091 34438 36193
+39 19 1 37924 34438 36561
+40 19 1 38775 34437 36971
+41 19 1 39362 34436 37337
+42 19 1 40174 34435 37963
+43 19 1 41067 34435 38384
+44 19 1 41680 34434 38835
+45 19 1 42564 34433 39243
+46 19 1 43311 34431 39698
+47 19 1 43908 34430 40257
+48 19 1 44781 34429 40565
+49 19 1 45403 34428 41094
+50 19 1 46095 34426 41566
+51 19 1 46976 34425 41958
+52 19 1 47575 34423 42392
+53 19 1 48450 34421 42806
+54 19 1 49326 34419 43461
+55 19 1 49928 34418 43846
+56 19 1 50795 34416 44264
+57 19 1 51638 34413 44640
+58 19 1 52281 34411 45142
+59 19 1 52959 34409 45638
+60 19 1 53847 34407 46018
+61 19 1 54455 34404 46468
+62 19 1 55348 34402 46881
+63 19 1 56230 34399 47544
+0 20 1 8467 35472 19206
+1 20 1 9261 35474 19833
+2 20 1 10182 35476 20262
+3 20 1 10825 35478 20747
+4 20 1 11718 35480 21147
+5 20 1 11343 35564 21829
+6 20 1 20621 34995 21024
+7 20 1 31378 34297 19720
+8 20 1 29632 34422 20193
+9 20 1 29623 34432 20463
+10 20 1 29662 34442 20882
+11 20 1 29732 34450 21060
+12 20 1 29766 34459 21443
+13 20 1 29860 34467 21804
+14 20 1 29939 34475 22000
+15 20 1 29998 34482 22317
+16 20 1 30122 34489 22515
+17 20 1 30204 34496 22938
+18 20 1 30310 34502 23137
+19 20 1 30463 34507 23487
+20 20 1 30562 34512 23670
+21 20 1 30726 34517 24028
+22 20 1 30905 34522 24435
+23 20 1 31175 34501 24459
+24 20 1 30077 34792 26456
+25 20 1 26953 35651 31600
+26 20 1 28299 35508 31182
+27 20 1 28931 35508 31591
+28 20 1 29801 35508 31942
+29 20 1 30392 35509 32529
+30 20 1 31147 35509 32930
+31 20 1 32028 35509 33390
+32 20 1 32640 35509 33789
+33 20 1 33528 35509 34258
+34 20 1 34335 35509 34865
+35 20 1 34921 35509 35185
+36 20 1 35793 35508 35692
+37 20 1 36459 35508 36196
+38 20 1 37092 35508 36562
+39 20 1 37924 35507 36960
+40 20 1 38776 35507 37324
+41 20 1 39363 35506 37934
+42 20 1 40177 35505 38327
+43 20 1 41070 35504 38812
+44 20 1 41682 35504 39208
+45 20 1 42567 35503 39685
+46 20 1 43312 35502 40255
+47 20 1 43909 35500 40563
+48 20 1 44782 35499 41094
+49 20 1 45403 35498 41559
+50 20 1 46097 35497 41955
+51 20 1 46979 35495 42353
+52 20 1 47579 35494 42758
+53 20 1 48457 35492 43414
+54 20 1 49330 35491 43804
+55 20 1 49931 35489 44265
+56 20 1 50798 35488 44632
+57 20 1 51640 35486 45141
+58 20 1 52281 35484 45633
+59 20 1 52962 35482 46016
+60 20 1 53851 35480 46424
+61 20 1 54460 35478 46826
+62 20 1 55357 35476 47494
+63 20 1 56235 35474 47888
+0 21 1 8464 36554 19799
+1 21 1 9254 36556 20197
+2 21 1 10174 36558 20705
+3 21 1 10819 36560 21103
+4 21 1 11714 36562 21616
+5 21 1 12770 36552 22137
+6 21 1 12041 36639 22707
+7 21 1 10988 36746 23396
+8 21 1 12317 36728 23744
+9 21 1 13022 36728 24375
+10 21 1 13957 36728 24788
+11 21 1 15005 36728 25293
+12 21 1 15737 36728 25952
+13 21 1 16765 36728 26332
+14 21 1 17628 36728 26827
+15 21 1 18320 36728 27180
+16 21 1 19318 36728 27759
+17 21 1 20028 36728 28210
+18 21 1 20820 36728 28663
+19 21 1 21818 36728 29050
+20 21 1 22493 36728 29509
+21 21 1 23464 36728 30167
+22 21 1 24476 36728 30555
+23 21 1 25379 36731 31114
+24 21 1 26232 36691 31439
+25 21 1 27738 36568 31053
+26 21 1 28299 36589 31603
+27 21 1 28931 36590 31944
+28 21 1 29801 36590 32527
+29 21 1 30392 36590 32917
+30 21 1 31147 36590 33377
+31 21 1 32028 36591 33741
+32 21 1 32640 36591 34218
+33 21 1 33528 36591 34846
+34 21 1 34335 36590 35169
+35 21 1 34921 36590 35691
+36 21 1 35793 36590 36205
+37 21 1 36459 36590 36560
+38 21 1 37092 36589 36967
+39 21 1 37925 36589 37323
+40 21 1 38777 36588 37923
+41 21 1 39364 36588 38295
+42 21 1 40179 36587 38787
+43 21 1 41072 36586 39161
+44 21 1 41685 36585 39664
+45 21 1 42569 36584 40251
+46 21 1 43313 36583 40559
+47 21 1 43911 36582 41100
+48 21 1 44784 36581 41567
+49 21 1 45404 36580 41955
+50 21 1 46100 36579 42339
+51 21 1 46982 36577 42735
+52 21 1 47583 36576 43369
+53 21 1 48464 36574 43746
+54 21 1 49334 36573 44261
+55 21 1 49934 36571 44616
+56 21 1 50801 36570 45141
+57 21 1 51643 36568 45640
+58 21 1 52282 36566 46015
+59 21 1 52965 36564 46409
+60 21 1 53855 36562 46799
+61 21 1 54465 36560 47439
+62 21 1 55366 36558 47832
+63 21 1 56240 36556 48340
+0 22 1 8462 37660 20177
+1 22 1 9248 37662 20671
+2 22 1 10166 37664 21039
+3 22 1 10813 37666 21583
+4 22 1 11709 37668 22170
+5 22 1 12566 37669 22510
+6 22 1 13252 37671 22957
+7 22 1 13890 37673 23302
+8 22 1 14770 37674 23905
+9 22 1 15366 37676 24275
+10 22 1 16164 37677 24721
+11 22 1 17068 37679 25365
+12 22 1 17698 37680 25714
+13 22 1 18590 37682 26235
+14 22 1 19341 37683 26558
+15 22 1 19940 37684 27124
+16 22 1 20814 37685 27564
+17 22 1 21434 37686 27967
+18 22 1 22127 37687 28337
+19 22 1 23006 37688 28752
+20 22 1 23599 37689 29379
+21 22 1 24456 37690 29723
+22 22 1 25353 37690 30256
+23 22 1 26169 37691 30836
+24 22 1 26757 37692 31163
+25 22 1 27630 37692 31628
+26 22 1 28299 37693 31955
+27 22 1 28930 37693 32539
+28 22 1 29800 37693 32926
+29 22 1 30392 37693 33376
+30 22 1 31146 37694 33722
+31 22 1 32028 37694 34186
+32 22 1 32640 37694 34817
+33 22 1 33528 37694 35148
+34 22 1 34335 37694 35689
+35 22 1 34921 37693 36220
+36 22 1 35794 37693 36558
+37 22 1 36459 37693 36991
+38 22 1 37093 37693 37334
+39 22 1 37926 37692 37930
+40 22 1 38778 37692 38291
+41 22 1 39366 37691 38778
+42 22 1 40182 37690 39120
+43 22 1 41075 37690 39635
+44 22 1 41687 37689 40244
+45 22 1 42571 37688 40554
+46 22 1 43314 37687 41109
+47 22 1 43912 37686 41587
+48 22 1 44785 37685 41958
+49 22 1 45405 37684 42351
+50 22 1 46102 37683 42734
+51 22 1 46985 37682 43353
+52 22 1 47586 37680 43699
+53 22 1 48470 37679 44246
+54 22 1 49337 37677 44594
+55 22 1 49938 37676 45141
+56 22 1 50803 37674 45658
+57 22 1 51645 37673 46015
+58 22 1 52283 37671 46424
+59 22 1 52969 37669 46796
+60 22 1 53859 37668 47418
+61 22 1 54471 37666 47778
+62 22 1 55376 37664 48301
+63 22 1 56244 37662 48921
+0 23 1 8459 38804 20662
+1 23 1 9241 38806 21008
+2 23 1 10159 38807 21535
+3 23 1 10807 38809 22164
+4 23 1 11705 38810 22495
+5 23 1 12563 38811 22994
+6 23 1 13251 38812 23321
+7 23 1 13887 38813 23923
+8 23 1 14767 38815 24291
+9 23 1 15364 38816 24714
+10 23 1 16159 38817 25339
+11 23 1 17063 38818 25673
+12 23 1 17694 38819 26243
+13 23 1 18587 38820 26560
+14 23 1 19340 38820 27141
+15 23 1 19939 38821 27599
+16 23 1 20812 38822 27975
+17 23 1 21433 38823 28366
+18 23 1 22125 38823 28762
+19 23 1 23004 38824 29377
+20 23 1 23597 38825 29701
+21 23 1 24452 38825 30229
+22 23 1 25350 38826 30835
+23 23 1 26168 38826 31153
+24 23 1 26756 38827 31662
+25 23 1 27629 38827 31975
+26 23 1 28298 38827 32565
+27 23 1 28930 38828 32958
+28 23 1 29800 38828 33385
+29 23 1 30391 38828 33735
+30 23 1 31146 38828 34183
+31 23 1 32028 38828 34799
+32 23 1 32640 38828 35124
+33 23 1 33529 38828 35687
+34 23 1 34336 38828 36238
+35 23 1 34922 38828 36560
+36 23 1 35794 38828 37030
+37 23 1 36460 38828 37358
+38 23 1 37093 38827 37955
+39 23 1 37927 38827 38315
+40 23 1 38779 38827 38783
+41 23 1 39367 38826 39114
+42 23 1 40185 38826 39613
+43 23 1 41078 38825 40232
+44 23 1 41689 38825 40550
+45 23 1 42574 38824 41123
+46 23 1 43315 38823 41618
+47 23 1 43914 38823 41967
+48 23 1 44787 38822 42388
+49 23 1 45405 38821 42754
+50 23 1 46104 38820 43364
+51 23 1 46988 38820 43695
+52 23 1 47590 38819 44231
+53 23 1 48477 38818 44569
+54 23 1 49341 38817 45141
+55 23 1 49941 38816 45682
+56 23 1 50806 38815 46019
+57 23 1 51648 38813 46464
+58 23 1 52284 38812 46817
+59 23 1 52972 38811 47429
+60 23 1 53863 38810 47771
+61 23 1 54476 38809 48266
+62 23 1 55385 38807 48905
+63 23 1 56249 38806 49243
+0 24 1 8457 40003 21036
+1 24 1 9235 40003 21524
+2 24 1 10151 40003 22144
+3 24 1 10801 40003 22485
+4 24 1 11700 40003 23038
+5 24 1 12559 40003 23355
+6 24 1 13250 40003 23964
+7 24 1 13885 40003 24342
+8 24 1 14764 40003 24743
+9 24 1 15361 40003 25353
+10 24 1 16154 40003 25668
+11 24 1 17058 40003 26236
+12 24 1 17689 40003 26565
+13 24 1 18583 40003 27166
+14 24 1 19339 40003 27647
+15 24 1 19937 40003 27996
+16 24 1 20811 40003 28421
+17 24 1 21433 40003 28799
+18 24 1 22123 40003 29404
+19 24 1 23002 40003 29720
+20 24 1 23595 40003 30226
+21 24 1 24449 40003 30828
+22 24 1 25348 40003 31151
+23 24 1 26167 40003 31699
+24 24 1 26755 40003 32006
+25 24 1 27629 40003 32606
+26 24 1 28298 40003 33011
+27 24 1 28929 40003 33409
+28 24 1 29800 40003 33779
+29 24 1 30391 40003 34210
+30 24 1 31145 40003 34812
+31 24 1 32027 40003 35121
+32 24 1 32640 40003 35684
+33 24 1 33529 40003 36256
+34 24 1 34336 40003 36571
+35 24 1 34922 40003 37079
+36 24 1 35795 40003 37398
+37 24 1 36460 40003 37999
+38 24 1 37094 40003 38366
+39 24 1 37927 40003 38805
+40 24 1 38780 40003 39145
+41 24 1 39368 40003 39625
+42 24 1 40187 40003 40226
+43 24 1 41081 40003 40549
+44 24 1 41691 40003 41142
+45 24 1 42576 40003 41657
+46 24 1 43315 40003 41987
+47 24 1 43915 40003 42445
+48 24 1 44788 40003 42797
+49 24 1 45406 40003 43402
+50 24 1 46107 40003 43730
+51 24 1 46991 40003 44238
+52 24 1 47594 40003 44560
+53 24 1 48483 40003 45141
+54 24 1 49344 40003 45709
+55 24 1 49944 40003 46033
+56 24 1 50808 40003 46522
+57 24 1 51650 40003 46861
+58 24 1 52285 40003 47470
+59 24 1 52976 40003 47808
+60 24 1 53867 40003 48276
+61 24 1 54482 40003 48893
+62 24 1 55393 40003 49233
+63 24 1 56253 40003 49801
+0 25 1 8455 41218 21548
+1 25 1 9228 41217 22144
+2 25 1 10144 41215 22471
+3 25 1 10795 41214 23056
+4 25 1 11695 41213 23386
+5 25 1 12556 41211 24007
+6 25 1 13250 41210 24404
+7 25 1 13882 41209 24789
+8 25 1 14762 41207 25388
+9 25 1 15359 41206 25703
+10 25 1 16149 41205 26230
+11 25 1 17052 41204 26557
+12 25 1 17685 41203 27179
+13 25 1 18580 41202 27687
+14 25 1 19338 41201 28020
+15 25 1 19935 41200 28482
+16 25 1 20809 41199 28847
+17 25 1 21432 41198 29447
+18 25 1 22121 41198 29764
+19 25 1 23000 41197 30249
+20 25 1 23593 41196 30831
+21 25 1 24446 41196 31149
+22 25 1 25345 41195 31718
+23 25 1 26166 41195 32035
+24 25 1 26754 41194 32647
+25 25 1 27628 41194 33069
+26 25 1 28298 41194 33438
+27 25 1 28929 41193 33836
+28 25 1 29799 41193 34252
+29 25 1 30391 41193 34842
+30 25 1 31145 41193 35144
+31 25 1 32027 41193 35687
+32 25 1 32640 41193 36260
+33 25 1 33529 41193 36583
+34 25 1 34336 41193 37120
+35 25 1 34922 41193 37439
+36 25 1 35795 41193 38046
+37 25 1 36460 41193 38427
+38 25 1 37095 41194 38833
+39 25 1 37928 41194 39195
+40 25 1 38781 41194 39657
+41 25 1 39369 41195 40242
+42 25 1 40190 41195 40550
+43 25 1 41083 41196 41150
+44 25 1 41694 41196 41686
+45 25 1 42578 41197 42010
+46 25 1 43316 41198 42502
+47 25 1 43917 41198 42846
+48 25 1 44790 41199 43450
+49 25 1 45406 41200 43788
+50 25 1 46109 41201 44256
+51 25 1 46994 41202 44583
+52 25 1 47598 41203 45138
+53 25 1 48489 41204 45718
+54 25 1 49348 41205 46049
+55 25 1 49947 41206 46574
+56 25 1 50811 41207 46909
+57 25 1 51653 41209 47521
+58 25 1 52285 41210 47866
+59 25 1 52979 41211 48312
+60 25 1 53872 41213 48907
+61 25 1 54488 41214 49229
+62 25 1 55402 41215 49815
+63 25 1 56257 41217 50151
+0 26 1 8453 42432 22144
+1 26 1 9222 42429 22471
+2 26 1 10137 42426 23012
+3 26 1 10789 42424 23373
+4 26 1 11690 42421 24016
+5 26 1 12552 42419 24437
+6 26 1 13249 42417 24817
+7 26 1 13880 42414 25411
+8 26 1 14759 42412 25741
+9 26 1 15357 42410 26216
+10 26 1 16144 42408 26546
+11 26 1 17047 42406 27149
+12 26 1 17681 42404 27682
+13 26 1 18576 42402 28025
+14 26 1 19336 42401 28515
+15 26 1 19934 42399 28877
+16 26 1 20808 42398 29475
+17 26 1 21432 42396 29799
+18 26 1 22120 42395 30266
+19 26 1 22998 42394 30831
+20 26 1 23591 42392 31149
+21 26 1 24442 42391 31693
+22 26 1 25343 42390 32033
+23 26 1 26165 42390 32659
+24 26 1 26753 42389 33099
+25 26 1 27627 42388 33453
+26 26 1 28298 42387 33878
+27 26 1 28928 42387 34283
+28 26 1 29799 42386 34863
+29 26 1 30391 42386 35166
+30 26 1 31145 42386 35691
+31 26 1 32027 42386 36242
+32 26 1 32640 42386 36575
+33 26 1 33529 42386 37123
+34 26 1 34336 42386 37455
+35 26 1 34923 42386 38071
+36 26 1 35796 42386 38468
+37 26 1 36460 42387 38848
+38 26 1 37095 42387 39234
+39 26 1 37929 42388 39682
+40 26 1 38782 42389 40251
+41 26 1 39370 42390 40554
+42 26 1 40192 42390 41132
+43 26 1 41086 42391 41675
+44 26 1 41696 42392 42015
+45 26 1 42580 42394 42526
+46 26 1 43317 42395 42874
+47 26 1 43918 42396 43480
+48 26 1 44791 42398 43833
+49 26 1 45407 42399 44262
+50 26 1 46112 42401 44606
+51 26 1 46997 42402 45136
+52 26 1 47602 42404 45691
+53 26 1 48496 42406 46041
+54 26 1 49351 42408 46584
+55 26 1 49950 42410 46930
+56 26 1 50813 42412 47548
+57 26 1 51655 42414 47905
+58 26 1 52286 42417 48338
+59 26 1 52983 42419 48915
+60 26 1 53876 42421 49240
+61 26 1 54494 42424 49776
+62 26 1 55411 42426 50139
+63 26 1 56261 42429 50787
+0 27 1 8450 43789 22484
+1 27 1 9216 43787 22961
+2 27 1 10130 43784 23320
+3 27 1 10782 43782 23984
+4 27 1 11685 43780 24433
+5 27 1 12548 43778 24823
+6 27 1 13248 43776 25422
+7 27 1 13878 43774 25770
+8 27 1 14757 43772 26200
+9 27 1 15355 43770 26543
+10 27 1 16139 43768 27116
+11 27 1 17042 43767 27633
+12 27 1 17676 43765 28011
+13 27 1 18572 43763 28513
+14 27 1 19335 43762 28885
+15 27 1 19932 43760 29489
+16 27 1 20806 43759 29822
+17 27 1 21431 43758 30279
+18 27 1 22118 43756 30828
+19 27 1 22996 43755 31159
+20 27 1 23589 43754 31654
+21 27 1 24439 43753 31999
+22 27 1 25340 43752 32642
+23 27 1 26163 43751 33097
+24 27 1 26752 43751 33457
+25 27 1 27626 43750 33899
+26 27 1 28298 43750 34301
+27 27 1 28928 43749 34877
+28 27 1 29798 43749 35184
+29 27 1 30391 43748 35693
+30 27 1 31144 43748 36223
+31 27 1 32027 43748 36563
+32 27 1 32640 43748 37089
+33 27 1 33530 43748 37444
+34 27 1 34337 43748 38073
+35 27 1 34923 43748 38483
+36 27 1 35796 43749 38854
+37 27 1 36460 43749 39259
+38 27 1 37096 43750 39698
+39 27 1 37929 43750 40256
+40 27 1 38783 43751 40559
+41 27 1 39372 43751 41117
+42 27 1 40195 43752 41635
+43 27 1 41089 43753 42001
+44 27 1 41698 43754 42517
+45 27 1 42583 43755 42879
+46 27 1 43318 43756 43492
+47 27 1 43919 43758 43860
+48 27 1 44793 43759 44262
+49 27 1 45408 43760 44624
+50 27 1 46115 43762 45135
+51 27 1 47000 43763 45665
+52 27 1 47606 43765 46019
+53 27 1 48502 43767 46552
+54 27 1 49355 43768 46922
+55 27 1 49953 43770 47553
+56 27 1 50816 43772 47924
+57 27 1 51657 43774 48351
+58 27 1 52287 43776 48918
+59 27 1 52987 43778 49256
+60 27 1 53881 43780 49735
+61 27 1 54500 43782 50093
+62 27 1 55419 43784 50758
+63 27 1 56265 43787 51216
+0 28 1 8448 44840 22923
+1 28 1 9210 44838 23282
+2 28 1 10123 44836 23915
+3 28 1 10775 44833 24387
+4 28 1 11680 44831 24803
+5 28 1 12544 44829 25419
+6 28 1 13247 44827 25781
+7 28 1 13875 44825 26189
+8 28 1 14755 44823 26545
+9 28 1 15353 44822 27094
+10 28 1 16135 44820 27583
+11 28 1 17037 44818 27977
+12 28 1 17672 44817 28474
+13 28 1 18568 44815 28870
+14 28 1 19334 44814 29487
+15 28 1 19930 44812 29831
+16 28 1 20805 44811 30284
+17 28 1 21431 44810 30826
+18 28 1 22116 44809 31170
+19 28 1 22995 44807 31619
+20 28 1 23587 44806 31967
+21 28 1 24436 44806 32596
+22 28 1 25337 44805 33063
+23 28 1 26162 44804 33451
+24 28 1 26751 44803 33898
+25 28 1 27625 44803 34307
+26 28 1 28298 44802 34883
+27 28 1 28927 44802 35195
+28 28 1 29798 44801 35694
+29 28 1 30390 44801 36208
+30 28 1 31144 44801 36560
+31 28 1 32026 44801 37038
+32 28 1 32640 44801 37408
+33 28 1 33530 44801 38054
+34 28 1 34337 44801 38472
+35 28 1 34923 44801 38855
+36 28 1 35796 44801 39269
+37 28 1 36460 44802 39707
+38 28 1 37096 44802 40258
+39 28 1 37930 44803 40563
+40 28 1 38784 44803 41106
+41 28 1 39373 44804 41601
+42 28 1 40198 44805 41976
+43 28 1 41091 44806 42476
+44 28 1 41700 44806 42862
+45 28 1 42585 44807 43488
+46 28 1 43319 44809 43866
+47 28 1 43921 44810 44261
+48 28 1 44794 44811 44636
+49 28 1 45409 44812 45134
+50 28 1 46117 44814 45646
+51 28 1 47003 44815 46010
+52 28 1 47610 44817 46491
+53 28 1 48508 44818 46886
+54 28 1 49358 44820 47536
+55 28 1 49956 44822 47920
+56 28 1 50818 44823 48353
+57 28 1 51660 44825 48919
+58 28 1 52288 44827 49269
+59 28 1 52991 44829 49703
+60 28 1 53885 44831 50060
+61 28 1 54506 44833 50698
+62 28 1 55427 44836 51170
+63 28 1 56269 44838 51591
+0 29 1 8447 45918 23264
+1 29 1 9204 45915 23867
+2 29 1 10117 45912 24308
+3 29 1 10769 45910 24753
+4 29 1 11675 45907 25398
+5 29 1 12541 45905 25767
+6 29 1 13246 45903 26186
+7 29 1 13873 45901 26546
+8 29 1 14752 45899 27081
+9 29 1 15351 45897 27550
+10 29 1 16130 45895 27953
+11 29 1 17032 45893 28402
+12 29 1 17668 45891 28831
+13 29 1 18565 45890 29468
+14 29 1 19332 45888 29821
+15 29 1 19929 45887 30283
+16 29 1 20803 45885 30825
+17 29 1 21430 45884 31178
+18 29 1 22114 45883 31595
+19 29 1 22993 45882 31948
+20 29 1 23585 45880 32555
+21 29 1 24433 45879 33000
+22 29 1 25335 45879 33434
+23 29 1 26161 45878 33874
+24 29 1 26750 45877 34300
+25 29 1 27624 45876 34882
+26 29 1 28297 45876 35199
+27 29 1 28927 45875 35695
+28 29 1 29798 45875 36198
+29 29 1 30390 45875 36562
+30 29 1 31144 45875 36997
+31 29 1 32026 45874 37366
+32 29 1 32640 45874 38011
+33 29 1 33530 45874 38436
+34 29 1 34337 45875 38851
+35 29 1 34924 45875 39262
+36 29 1 35797 45875 39708
+37 29 1 36460 45875 40259
+38 29 1 37097 45876 40566
+39 29 1 37931 45876 41100
+40 29 1 38785 45877 41576
+41 29 1 39374 45878 41963
+42 29 1 40200 45879 42415
+43 29 1 41094 45879 42824
+44 29 1 41703 45880 43467
+45 29 1 42587 45882 43849
+46 29 1 43320 45883 44261
+47 29 1 43922 45884 44638
+48 29 1 44796 45885 45133
+49 29 1 45409 45887 45634
+50 29 1 46120 45888 46008
+51 29 1 47006 45890 46441
+52 29 1 47614 45891 46831
+53 29 1 48514 45893 47496
+54 29 1 49361 45895 47893
+55 29 1 49959 45897 48343
+56 29 1 50820 45899 48918
+57 29 1 51662 45901 49272
+58 29 1 52289 45903 49686
+59 29 1 52994 45905 50044
+60 29 1 53890 45907 50653
+61 29 1 54513 45910 51095
+62 29 1 55435 45912 51569
+63 29 1 56273 45915 51992
+0 30 1 8445 47001 23845
+1 30 1 9199 46997 24260
+2 30 1 10110 46993 24683
+3 30 1 10762 46989 25352
+4 30 1 11670 46986 25727
+5 30 1 12537 46983 26190
+6 30 1 13245 46980 26542
+7 30 1 13870 46977 27077
+8 30 1 14750 46974 27537
+9 30 1 15349 46971 27943
+10 30 1 16126 46969 28345
+11 30 1 17027 46966 28770
+12 30 1 17663 46964 29430
+13 30 1 18561 46962 29794
+14 30 1 19331 46960 30273
+15 30 1 19927 46958 30825
+16 30 1 20802 46956 31177
+17 30 1 21430 46955 31586
+18 30 1 22112 46953 31938
+19 30 1 22991 46952 32529
+20 30 1 23583 46950 32947
+21 30 1 24429 46949 33401
+22 30 1 25332 46948 33828
+23 30 1 26160 46947 34280
+24 30 1 26749 46946 34876
+25 30 1 27624 46945 35194
+26 30 1 28297 46945 35696
+27 30 1 28926 46944 36194
+28 30 1 29797 46944 36565
+29 30 1 30390 46943 36970
+30 30 1 31143 46943 37340
+31 30 1 32026 46943 37965
+32 30 1 32640 46943 38378
+33 30 1 33530 46943 38839
+34 30 1 34337 46943 39239
+35 30 1 34924 46943 39702
+36 30 1 35797 46944 40259
+37 30 1 36460 46944 40566
+38 30 1 37098 46945 41098
+39 30 1 37931 46945 41562
+40 30 1 38786 46946 41958
+41 30 1 39375 46947 42368
+42 30 1 40203 46948 42773
+43 30 1 41096 46949 43429
+44 30 1 41705 46950 43814
+45 30 1 42589 46952 44261
+46 30 1 43320 46953 44631
+47 30 1 43924 46955 45133
+48 30 1 44797 46956 45631
+49 30 1 45410 46958 46008
+50 30 1 46123 46960 46411
+51 30 1 47009 46962 46796
+52 30 1 47619 46964 47440
+53 30 1 48520 46966 47845
+54 30 1 49364 46969 48319
+55 30 1 49962 46971 48913
+56 30 1 50823 46974 49263
+57 30 1 51665 46977 49686
+58 30 1 52290 46980 50038
+59 30 1 52998 46983 50630
+60 30 1 53894 46986 51042
+61 30 1 54519 46989 51529
+62 30 1 55443 46993 51945
+63 30 1 56277 46997 52448
+0 31 1 8443 48106 24253
+1 31 1 9193 48100 24649
+2 31 1 10104 48093 25293
+3 31 1 10755 48088 25668
+4 31 1 11664 48082 26194
+5 31 1 12533 48077 26534
+6 31 1 13244 48072 27080
+7 31 1 13868 48067 27545
+8 31 1 14747 48063 27940
+9 31 1 15348 48058 28321
+10 31 1 16121 48055 28732
+11 31 1 17022 48051 29375
+12 31 1 17658 48047 29749
+13 31 1 18557 48044 30254
+14 31 1 19330 48041 30825
+15 31 1 19925 48038 31168
+16 31 1 20800 48036 31593
+17 31 1 21429 48033 31938
+18 31 1 22111 48031 32520
+19 31 1 22989 48029 32919
+20 31 1 23582 48027 33380
+21 31 1 24426 48026 33766
+22 31 1 25329 48024 34246
+23 31 1 26159 48023 34862
+24 31 1 26748 48021 35182
+25 31 1 27623 48020 35696
+26 31 1 28297 48019 36199
+27 31 1 28926 48018 36565
+28 31 1 29797 48018 36962
+29 31 1 30390 48017 37328
+30 31 1 31143 48017 37938
+31 31 1 32026 48017 38323
+32 31 1 32640 48017 38815
+33 31 1 33531 48017 39203
+34 31 1 34338 48017 39688
+35 31 1 34925 48017 40258
+36 31 1 35798 48018 40564
+37 31 1 36461 48018 41100
+38 31 1 37098 48019 41561
+39 31 1 37932 48020 41957
+40 31 1 38787 48021 42343
+41 31 1 39376 48023 42742
+42 31 1 40206 48024 43381
+43 31 1 41099 48026 43762
+44 31 1 41707 48027 44258
+45 31 1 42591 48029 44616
+46 31 1 43321 48031 45132
+47 31 1 43925 48033 45637
+48 31 1 44799 48036 46007
+49 31 1 45411 48038 46404
+50 31 1 46125 48041 46780
+51 31 1 47012 48044 47405
+52 31 1 47623 48047 47785
+53 31 1 48526 48051 48282
+54 31 1 49367 48055 48902
+55 31 1 49965 48058 49245
+56 31 1 50825 48063 49700
+57 31 1 51667 48067 50040
+58 31 1 52291 48072 50626
+59 31 1 53002 48077 51023
+60 31 1 53899 48082 51504
+61 31 1 54526 48088 51881
+62 31 1 55451 48093 52416
+63 31 1 56280 48100 53018
+0 32 1 8441 49252 24656
+1 32 1 9187 49241 25277
+2 32 1 10097 49230 25615
+3 32 1 10747 49220 26185
+4 32 1 11659 49211 26521
+5 32 1 12529 49202 27089
+6 32 1 13243 49194 27571
+7 32 1 13866 49187 27943
+8 32 1 14745 49179 28334
+9 32 1 15346 49173 28725
+10 32 1 16116 49166 29348
+11 32 1 17017 49160 29696
+12 32 1 17654 49155 30225
+13 32 1 18553 49150 30823
+14 32 1 19328 49145 31153
+15 32 1 19923 49140 31617
+16 32 1 20798 49136 31946
+17 32 1 21429 49132 32528
+18 32 1 22109 49129 32918
+19 32 1 22987 49126 33373
+20 32 1 23580 49123 33727
+21 32 1 24423 49120 34201
+22 32 1 25326 49117 34838
+23 32 1 26157 49115 35164
+24 32 1 26747 49113 35696
+25 32 1 27622 49112 36211
+26 32 1 28297 49110 36564
+27 32 1 28925 49109 36974
+28 32 1 29796 49108 37330
+29 32 1 30389 49107 37931
+30 32 1 31142 49107 38296
+31 32 1 32026 49106 38792
+32 32 1 32640 49106 39156
+33 32 1 33531 49106 39668
+34 32 1 34338 49107 40255
+35 32 1 34925 49107 40561
+36 32 1 35798 49108 41108
+37 32 1 36461 49109 41574
+38 32 1 37099 49110 41958
+39 32 1 37933 49112 42342
+40 32 1 38788 49113 42732
+41 32 1 39378 49115 43355
+42 32 1 40209 49117 43709
+43 32 1 41102 49120 44247
+44 32 1 41709 49123 44595
+45 32 1 42593 49126 45130
+46 32 1 43322 49129 45651
+47 32 1 43926 49132 46005
+48 32 1 44800 49136 46419
+49 32 1 45411 49140 46782
+50 32 1 46128 49145 47396
+51 32 1 47015 49150 47754
+52 32 1 47627 49155 48237
+53 32 1 48532 49160 48881
+54 32 1 49370 49166 49220
+55 32 1 49968 49173 49723
+56 32 1 50827 49179 50049
+57 32 1 51669 49187 50639
+58 32 1 52292 49194 51037
+59 32 1 53006 49202 51498
+60 32 1 53904 49211 51850
+61 32 1 54533 49220 52370
+62 32 1 55459 49230 53009
+63 32 1 56283 49241 53332
+0 33 1 8440 50456 25310
+1 33 1 9182 50437 25630
+2 33 1 10091 50420 26171
+3 33 1 10740 50404 26507
+4 33 1 11653 50390 27103
+5 33 1 12525 50375 27613
+6 33 1 13242 50362 27956
+7 33 1 13863 50350 28382
+8 33 1 14743 50338 28749
+9 33 1 15344 50328 29359
+10 33 1 16112 50317 29686
+11 33 1 17013 50308 30194
+12 33 1 17649 50299 30815
+13 33 1 18549 50291 31139
+14 33 1 19327 50283 31653
+15 33 1 19922 50276 31965
+16 33 1 20797 50270 32553
+17 33 1 21428 50263 32945
+18 33 1 22107 50258 33379
+19 33 1 22986 50253 33726
+20 33 1 23578 50248 34183
+21 33 1 24420 50244 34808
+22 33 1 25324 50240 35142
+23 33 1 26156 50236 35696
+24 33 1 26746 50233 36230
+25 33 1 27621 50231 36564
+26 33 1 28297 50228 37006
+27 33 1 28925 50227 37346
+28 33 1 29796 50225 37944
+29 33 1 30389 50224 38299
+30 33 1 31142 50223 38787
+31 33 1 32025 50222 39122
+32 33 1 32640 50222 39640
+33 33 1 33531 50222 40249
+34 33 1 34338 50223 40558
+35 33 1 34925 50224 41121
+36 33 1 35799 50225 41599
+37 33 1 36461 50227 41964
+38 33 1 37099 50228 42365
+39 33 1 37934 50231 42742
+40 33 1 38789 50233 43355
+41 33 1 39379 50236 43690
+42 33 1 40211 50240 44228
+43 33 1 41104 50244 44569
+44 33 1 41711 50248 45129
+45 33 1 42596 50253 45670
+46 33 1 43323 50258 46006
+47 33 1 43928 50263 46454
+48 33 1 44801 50270 46801
+49 33 1 45412 50276 47410
+50 33 1 46131 50283 47759
+51 33 1 47019 50291 48222
+52 33 1 47632 50299 48853
+53 33 1 48537 50308 49195
+54 33 1 49373 50317 49749
+55 33 1 49970 50328 50066
+56 33 1 50829 50338 50667
+57 33 1 51672 50350 51078
+58 33 1 52293 50362 51508
+59 33 1 53010 50375 51866
+60 33 1 53909 50390 52357
+61 33 1 54540 50404 52989
+62 33 1 55466 50420 53334
+63 33 1 56287 50437 53951
+0 34 1 8438 51679 25694
+1 34 1 9177 51663 26180
+2 34 1 10085 51647 26506
+3 34 1 10733 51631 27109
+4 34 1 11647 51614 27656
+5 34 1 12520 51599 27984
+6 34 1 13241 51583 28457
+7 34 1 13861 51567 28806
+8 34 1 14740 51552 29407
+9 34 1 15342 51537 29727
+10 34 1 16108 51522 30207
+11 34 1 17008 51508 30808
+12 34 1 17644 51494 31134
+13 34 1 18545 51482 31692
+14 34 1 19325 51470 31999
+15 34 1 19920 51459 32596
+16 34 1 20795 51449 33001
+17 34 1 21428 51439 33401
+18 34 1 22106 51431 33764
+19 34 1 22984 51423 34200
+20 34 1 23576 51416 34807
+21 34 1 24417 51409 35123
+22 34 1 25321 51403 35697
+23 34 1 26155 51398 36253
+24 34 1 26744 51393 36571
+25 34 1 27620 51389 37053
+26 34 1 28296 51386 37379
+27 34 1 28925 51383 37978
+28 34 1 29796 51380 38334
+29 34 1 30389 51378 38799
+30 34 1 31142 51377 39127
+31 34 1 32025 51376 39627
+32 34 1 32640 51376 40239
+33 34 1 33531 51376 40558
+34 34 1 34338 51377 41139
+35 34 1 34926 51378 41635
+36 34 1 35799 51380 41978
+37 34 1 36461 51383 42411
+38 34 1 37100 51386 42772
+39 34 1 37934 51389 43379
+40 34 1 38791 51393 43708
+41 34 1 39380 51398 44228
+42 34 1 40214 51403 44550
+43 34 1 41107 51409 45126
+44 34 1 41714 51416 45691
+45 34 1 42598 51423 46015
+46 34 1 43323 51431 46504
+47 34 1 43929 51439 46838
+48 34 1 44803 51449 47447
+49 34 1 45413 51459 47798
+50 34 1 46134 51470 48242
+51 34 1 47022 51482 48856
+52 34 1 47636 51494 49180
+53 34 1 48543 51508 49769
+54 34 1 49376 51522 50092
+55 34 1 49973 51537 50708
+56 34 1 50832 51552 51141
+57 34 1 51674 51567 51536
+58 34 1 52294 51583 51920
+59 34 1 53015 51599 52383
+60 34 1 53914 51614 52992
+61 34 1 54547 51631 53327
+62 34 1 55474 51647 53967
+63 34 1 56290 51663 54475
+0 35 1 8437 52842 26176
+1 35 1 9171 52829 26518
+2 35 1 10079 52817 27083
+3 35 1 10725 52805 27641
+4 35 1 11641 52793 27991
+5 35 1 12516 52780 28501
+6 35 1 13240 52768 28851
+7 35 1 13859 52756 29453
+8 35 1 14738 52744 29780
+9 35 1 15341 52732 30240
+10 35 1 16103 52721 30818
+11 35 1 17003 52709 31134
+12 35 1 17640 52698 31706
+13 35 1 18541 52688 32030
+14 35 1 19323 52677 32643
+15 35 1 19918 52667 33067
+16 35 1 20794 52657 33436
+17 35 1 21427 52648 33829
+18 35 1 22104 52639 34247
+19 35 1 22982 52631 34839
+20 35 1 23574 52623 35142
+21 35 1 24414 52616 35699
+22 35 1 25318 52609 36274
+23 35 1 26153 52603 36591
+24 35 1 26743 52597 37110
+25 35 1 27619 52592 37429
+26 35 1 28296 52588 38032
+27 35 1 28924 52584 38397
+28 35 1 29795 52581 38828
+29 35 1 30389 52579 39170
+30 35 1 31141 52577 39652
+31 35 1 32025 52576 40244
+32 35 1 32640 52576 40562
+33 35 1 33532 52576 41163
+34 35 1 34339 52577 41678
+35 35 1 34926 52579 42005
+36 35 1 35800 52581 42474
+37 35 1 36461 52584 42823
+38 35 1 37101 52588 43428
+39 35 1 37935 52592 43760
+40 35 1 38792 52597 44246
+41 35 1 39382 52603 44568
+42 35 1 40217 52609 45126
+43 35 1 41109 52616 45708
+44 35 1 41716 52623 46032
+45 35 1 42600 52631 46556
+46 35 1 43324 52639 46886
+47 35 1 43931 52648 47498
+48 35 1 44804 52657 47856
+49 35 1 45414 52667 48282
+50 35 1 46136 52677 48880
+51 35 1 47025 52688 49195
+52 35 1 47641 52698 49758
+53 35 1 48549 52709 50100
+54 35 1 49379 52721 50732
+55 35 1 49976 52732 51188
+56 35 1 50834 52744 51555
+57 35 1 51676 52756 51970
+58 35 1 52295 52768 52408
+59 35 1 53019 52780 52997
+60 35 1 53919 52793 53314
+61 35 1 54555 52805 53937
+62 35 1 55481 52817 54476
+63 35 1 56293 52829 54836
+0 36 1 8435 53959 26531
+1 36 1 9166 53950 27066
+2 36 1 10073 53940 27589
+3 36 1 10718 53931 27968
+4 36 1 11635 53922 28494
+5 36 1 12512 53913 28863
+6 36 1 13239 53904 29475
+7 36 1 13856 53895 29815
+8 36 1 14736 53886 30262
+9 36 1 15339 53877 30822
+10 36 1 16099 53868 31145
+11 36 1 16999 53859 31669
+12 36 1 17635 53851 32019
+13 36 1 18537 53842 32654
+14 36 1 19322 53834 33100
+15 36 1 19916 53826 33454
+16 36 1 20792 53819 33878
+17 36 1 21427 53812 34284
+18 36 1 22102 53805 34864
+19 36 1 22980 53798 35166
+20 36 1 23572 53792 35701
+21 36 1 24410 53786 36258
+22 36 1 25315 53781 36594
+23 36 1 26152 53776 37135
+24 36 1 26742 53772 37462
+25 36 1 27618 53768 38072
+26 36 1 28296 53764 38453
+27 36 1 28924 53761 38850
+28 36 1 29795 53759 39217
+29 36 1 30389 53757 39681
+30 36 1 31141 53756 40255
+31 36 1 32025 53755 40561
+32 36 1 32640 53754 41160
+33 36 1 33532 53755 41694
+34 36 1 34339 53756 42022
+35 36 1 34926 53757 42518
+36 36 1 35800 53759 42864
+37 36 1 36461 53761 43467
+38 36 1 37101 53764 43812
+39 36 1 37936 53768 44258
+40 36 1 38793 53772 44594
+41 36 1 39383 53776 45128
+42 36 1 40220 53781 45691
+43 36 1 41112 53786 46031
+44 36 1 41718 53792 46575
+45 36 1 42602 53798 46911
+46 36 1 43325 53805 47528
+47 36 1 43932 53812 47897
+48 36 1 44806 53819 48312
+49 36 1 45415 53826 48896
+50 36 1 46139 53834 49218
+51 36 1 47029 53842 49732
+52 36 1 47646 53851 50073
+53 36 1 48554 53859 50721
+54 36 1 49382 53868 51198
+55 36 1 49979 53877 51560
+56 36 1 50836 53886 52000
+57 36 1 51679 53895 52424
+58 36 1 52296 53904 53000
+59 36 1 53023 53913 53311
+60 36 1 53924 53922 53897
+61 36 1 54563 53931 54428
+62 36 1 55488 53940 54822
+63 36 1 56296 53950 55310
+0 37 1 8434 55048 27056
+1 37 1 9161 55040 27551
+2 37 1 10067 55033 27938
+3 37 1 10710 55026 28431
+4 37 1 11629 55019 28841
+5 37 1 12507 55011 29473
+6 37 1 13237 55004 29827
+7 37 1 13854 54997 30273
+8 37 1 14733 54990 30823
+9 37 1 15338 54984 31162
+10 37 1 16095 54977 31629
+11 37 1 16994 54970 31978
+12 37 1 17630 54964 32625
+13 37 1 18533 54957 33090
+14 37 1 19320 54951 33458
+15 37 1 19915 54945 33901
+16 37 1 20791 54940 34306
+17 37 1 21427 54934 34880
+18 37 1 22100 54929 35185
+19 37 1 22979 54924 35703
+20 37 1 23571 54919 36235
+21 37 1 24407 54914 36578
+22 37 1 25313 54910 37117
+23 37 1 26151 54907 37465
+24 37 1 26741 54903 38087
+25 37 1 27618 54900 38484
+26 37 1 28296 54897 38861
+27 37 1 28923 54895 39251
+28 37 1 29794 54893 39702
+29 37 1 30388 54892 40261
+30 37 1 31141 54891 40563
+31 37 1 32025 54890 41140
+32 37 1 32640 54890 41670
+33 37 1 33532 54890 42021
+34 37 1 34339 54891 42530
+35 37 1 34927 54892 42883
+36 37 1 35800 54893 43490
+37 37 1 36461 54895 43849
+38 37 1 37102 54897 44262
+39 37 1 37937 54900 44615
+40 37 1 38794 54903 45129
+41 37 1 39384 54907 45669
+42 37 1 40222 54910 46013
+43 37 1 41114 54914 46555
+44 37 1 41720 54919 46909
+45 37 1 42604 54924 47537
+46 37 1 43325 54929 47917
+47 37 1 43933 54934 48328
+48 37 1 44807 54940 48905
+49 37 1 45416 54945 49240
+50 37 1 46142 54951 49705
+51 37 1 47032 54957 50047
+52 37 1 47651 54964 50676
+53 37 1 48560 54970 51171
+54 37 1 49384 54977 51554
+55 37 1 49981 54984 52008
+56 37 1 50838 54990 52429
+57 37 1 51681 54997 53002
+58 37 1 52298 55004 53312
+59 37 1 53028 55011 53870
+60 37 1 53930 55019 54370
+61 37 1 54570 55026 54786
+62 37 1 55495 55033 55279
+63 37 1 56299 55040 55705
+0 38 1 8432 56127 27533
+1 38 1 9156 56119 27927
+2 38 1 10062 56112 28355
+3 38 1 10702 56105 28780
+4 38 1 11623 56098 29444
+5 38 1 12503 56092 29813
+6 38 1 13236 56085 30273
+7 38 1 13851 56079 30822
+8 38 1 14731 56073 31174
+9 38 1 15336 56067 31598
+10 38 1 16091 56062 31952
+11 38 1 16990 56056 32571
+12 38 1 17625 56051 33039
+13 38 1 18528 56046 33450
+14 38 1 19319 56041 33895
+15 38 1 19913 56036 34312
+16 38 1 20789 56032 34886
+17 38 1 21426 56027 35197
+18 38 1 22099 56023 35704
+19 38 1 22977 56019 36215
+20 38 1 23569 56015 36571
+21 38 1 24404 56012 37062
+22 38 1 25310 56009 37439
+23 38 1 26149 56006 38078
+24 38 1 26740 56003 38486
+25 38 1 27617 56001 38865
+26 38 1 28296 55999 39269
+27 38 1 28923 55997 39715
+28 38 1 29794 55996 40263
+29 38 1 30388 55994 40566
+30 38 1 31140 55994 41125
+31 38 1 32024 55993 41627
+32 38 1 32640 55993 42001
+33 38 1 33532 55993 42509
+34 38 1 34339 55994 42881
+35 38 1 34927 55994 43496
+36 38 1 35801 55996 43866
+37 38 1 36462 55997 44262
+38 38 1 37102 55999 44631
+39 38 1 37937 56001 45131
+40 38 1 38795 56003 45650
+41 38 1 39386 56006 46004
+42 38 1 40225 56009 46501
+43 38 1 41116 56012 46882
+44 38 1 41722 56015 47525
+45 38 1 42606 56019 47916
+46 38 1 43326 56023 48333
+47 38 1 43935 56027 48909
+48 38 1 44809 56032 49256
+49 38 1 45416 56036 49684
+50 38 1 46145 56041 50033
+51 38 1 47036 56046 50634
+52 38 1 47656 56051 51110
+53 38 1 48565 56056 51535
+54 38 1 49387 56062 51990
+55 38 1 49984 56067 52423
+56 38 1 50840 56073 53001
+57 38 1 51684 56079 53314
+58 38 1 52299 56085 53855
+59 38 1 53032 56092 54331
+60 38 1 53935 56098 54749
+61 38 1 54578 56105 55212
+62 38 1 55502 56112 55670
+63 38 1 56301 56119 56292
+0 39 1 8431 57215 27925
+1 39 1 9151 57205 28316
+2 39 1 10056 57195 28725
+3 39 1 10694 57186 29384
+4 39 1 11616 57177 29772
+5 39 1 12498 57170 30259
+6 39 1 13235 57162 30822
+7 39 1 13849 57155 31175
+8 39 1 14729 57149 31585
+9 39 1 15335 57143 31939
+10 39 1 16087 57137 32536
+11 39 1 16985 57131 32967
+12 39 1 17620 57126 33424
+13 39 1 18524 57121 33861
+14 39 1 19317 57117 34303
+15 39 1 19911 57112 34886
+16 39 1 20787 57108 35201
+17 39 1 21426 57104 35705
+18 39 1 22097 57100 36201
+19 39 1 22975 57097 36571
+20 39 1 23567 57094 37011
+21 39 1 24401 57091 37390
+22 39 1 25307 57088 38044
+23 39 1 26148 57085 38459
+24 39 1 26739 57083 38865
+25 39 1 27616 57081 39270
+26 39 1 28296 57079 39721
+27 39 1 28922 57078 40263
+28 39 1 29794 57077 40569
+29 39 1 30388 57076 41115
+30 39 1 31140 57075 41593
+31 39 1 32024 57075 41979
+32 39 1 32640 57075 42457
+33 39 1 33533 57075 42857
+34 39 1 34340 57075 43486
+35 39 1 34927 57076 43863
+36 39 1 35801 57077 44262
+37 39 1 36462 57078 44638
+38 39 1 37103 57079 45132
+39 39 1 37938 57081 45637
+40 39 1 38796 57083 46003
+41 39 1 39387 57085 46452
+42 39 1 40228 57088 46833
+43 39 1 41119 57091 47491
+44 39 1 41724 57094 47893
+45 39 1 42608 57097 48325
+46 39 1 43327 57100 48908
+47 39 1 43936 57104 49261
+48 39 1 44810 57108 49673
+49 39 1 45417 57112 50026
+50 39 1 46148 57117 50607
+51 39 1 47039 57121 51054
+52 39 1 47661 57126 51500
+53 39 1 48570 57131 51948
+54 39 1 49390 57137 52403
+55 39 1 49987 57143 52996
+56 39 1 50842 57149 53311
+57 39 1 51686 57155 53847
+58 39 1 52300 57162 54314
+59 39 1 53037 57170 54731
+60 39 1 53941 57177 55143
+61 39 1 54586 57186 55612
+62 39 1 55509 57195 56268
+63 39 1 56304 57205 56628
+0 40 1 8430 58331 28324
+1 40 1 9147 58315 28709
+2 40 1 10051 58300 29339
+3 40 1 10687 58286 29708
+4 40 1 11610 58273 30232
+5 40 1 12494 58262 30822
+6 40 1 13234 58251 31164
+7 40 1 13847 58241 31595
+8 40 1 14726 58233 31938
+9 40 1 15333 58224 32523
+10 40 1 16083 58217 32925
+11 40 1 16981 58210 33392
+12 40 1 17615 58203 33800
+13 40 1 18520 58197 34277
+14 40 1 19315 58191 34878
+15 40 1 19909 58186 35195
+16 40 1 20786 58181 35707
+17 40 1 21425 58177 36197
+18 40 1 22095 58173 36573
+19 40 1 22974 58169 36977
+20 40 1 23566 58166 37356
+21 40 1 24398 58163 37991
+22 40 1 25304 58160 38406
+23 40 1 26147 58157 38859
+24 40 1 26738 58155 39254
+25 40 1 27615 58153 39720
+26 40 1 28295 58151 40264
+27 40 1 28922 58150 40570
+28 40 1 29793 58148 41110
+29 40 1 30388 58147 41571
+30 40 1 31139 58147 41970
+31 40 1 32024 58146 42397
+32 40 1 32640 58146 42812
+33 40 1 33533 58146 43460
+34 40 1 34340 58147 43838
+35 40 1 34928 58147 44264
+36 40 1 35802 58148 44636
+37 40 1 36462 58150 45133
+38 40 1 37104 58151 45631
+39 40 1 37939 58153 46004
+40 40 1 38797 58155 46417
+41 40 1 39388 58157 46796
+42 40 1 40231 58160 47438
+43 40 1 41121 58163 47849
+44 40 1 41726 58166 48303
+45 40 1 42610 58169 48902
+46 40 1 43327 58173 49253
+47 40 1 43938 58177 49673
+48 40 1 44812 58181 50024
+49 40 1 45418 58186 50594
+50 40 1 46151 58191 51021
+51 40 1 47043 58197 51471
+52 40 1 47666 58203 51883
+53 40 1 48576 58210 52368
+54 40 1 49392 58217 52984
+55 40 1 49989 58224 53304
+56 40 1 50844 58233 53845
+57 40 1 51688 58241 54315
+58 40 1 52301 58251 54724
+59 40 1 53041 58262 55109
+60 40 1 53946 58273 55555
+61 40 1 54593 58286 56225
+62 40 1 55515 58300 56590
+63 40 1 56306 58315 57100
+0 41 1 8429 59494 28734
+1 41 1 9142 59468 29343
+2 41 1 10045 59444 29677
+3 41 1 10680 59423 30192
+4 41 1 11603 59403 30816
+5 41 1 12489 59385 31146
+6 41 1 13232 59369 31626
+7 41 1 13844 59354 31950
+8 41 1 14724 59340 32533
+9 41 1 15332 59328 32919
+10 41 1 16079 59316 33381
+11 41 1 16977 59306 33741
+12 41 1 17609 59296 34235
+13 41 1 18515 59288 34862
+14 41 1 19313 59280 35183
+15 41 1 19907 59272 35711
+16 41 1 20784 59266 36206
+17 41 1 21425 59259 36575
+18 41 1 22094 59254 36968
+19 41 1 22972 59249 37341
+20 41 1 23564 59244 37958
+21 41 1 24395 59240 38341
+22 41 1 25301 59236 38842
+23 41 1 26145 59233 39224
+24 41 1 26737 59230 39712
+25 41 1 27614 59228 40266
+26 41 1 28295 59225 40571
+27 41 1 28921 59224 41112
+28 41 1 29793 59222 41563
+29 41 1 30387 59221 41967
+30 41 1 31139 59220 42358
+31 41 1 32024 59220 42768
+32 41 1 32640 59220 43417
+33 41 1 33533 59220 43796
+34 41 1 34340 59220 44265
+35 41 1 34928 59221 44626
+36 41 1 35802 59222 45133
+37 41 1 36462 59224 45633
+38 41 1 37104 59225 46005
+39 41 1 37940 59228 46402
+40 41 1 38798 59230 46777
+41 41 1 39390 59233 47401
+42 41 1 40234 59236 47790
+43 41 1 41124 59240 48269
+44 41 1 41728 59244 48888
+45 41 1 42612 59249 49233
+46 41 1 43328 59254 49683
+47 41 1 43939 59259 50024
+48 41 1 44813 59266 50592
+49 41 1 45419 59272 51011
+50 41 1 46154 59280 51458
+51 41 1 47046 59288 51833
+52 41 1 47671 59296 52318
+53 41 1 48581 59306 52963
+54 41 1 49395 59316 53291
+55 41 1 49992 59328 53846
+56 41 1 50846 59340 54332
+57 41 1 51691 59354 54722
+58 41 1 52303 59369 55110
+59 41 1 53046 59385 55534
+60 41 1 53952 59403 56181
+61 41 1 54601 59423 56544
+62 41 1 55522 59444 57114
+63 41 1 56309 59468 57444
+0 42 1 8428 60722 29399
+1 42 1 9137 60684 29716
+2 42 1 10040 60648 30193
+3 42 1 10672 60615 30803
+4 42 1 11596 60585 31135
+5 42 1 12484 60557 31672
+6 42 1 13231 60532 31979
+7 42 1 13842 60509 32567
+8 42 1 14722 60488 32953
+9 42 1 15331 60469 33389
+10 42 1 16075 60451 33731
+11 42 1 16972 60435 34203
+12 42 1 17604 60420 34836
+13 42 1 18511 60407 35166
+14 42 1 19311 60395 35717
+15 42 1 19906 60384 36226
+16 42 1 20783 60374 36577
+17 42 1 21424 60365 36987
+18 42 1 22092 60356 37345
+19 42 1 22970 60349 37951
+20 42 1 23562 60342 38307
+21 42 1 24392 60336 38817
+22 42 1 25298 60331 39182
+23 42 1 26144 60326 39699
+24 42 1 26736 60322 40268
+25 42 1 27613 60318 40571
+26 42 1 28295 60315 41122
+27 42 1 28921 60312 41572
+28 42 1 29792 60310 41969
+29 42 1 30387 60309 42345
+30 42 1 31139 60307 42747
+31 42 1 32024 60307 43378
+32 42 1 32640 60307 43742
+33 42 1 33533 60307 44262
+34 42 1 34340 60307 44609
+35 42 1 34928 60309 45135
+36 42 1 35803 60310 45644
+37 42 1 36462 60312 46005
+38 42 1 37105 60315 46408
+39 42 1 37940 60318 46774
+40 42 1 38799 60322 47386
+41 42 1 39391 60326 47753
+42 42 1 40237 60331 48223
+43 42 1 41126 60336 48866
+44 42 1 41730 60342 49206
+45 42 1 42614 60349 49701
+46 42 1 43329 60356 50028
+47 42 1 43940 60365 50602
+48 42 1 44815 60374 51024
+49 42 1 45420 60384 51457
+50 42 1 46157 60395 51818
+51 42 1 47050 60407 52283
+52 42 1 47676 60420 52929
+53 42 1 48586 60435 53272
+54 42 1 49397 60451 53848
+55 42 1 49994 60469 54360
+56 42 1 50848 60488 54726
+57 42 1 51693 60509 55143
+58 42 1 52304 60532 55544
+59 42 1 53051 60557 56176
+60 42 1 53958 60585 56514
+61 42 1 54608 60615 57114
+62 42 1 55528 60648 57456
+63 42 1 56311 60684 58085
+0 43 1 8426 61932 29782
+1 43 1 9133 61904 30236
+2 43 1 10035 61876 30818
+3 43 1 10665 61846 31137
+4 43 1 11590 61816 31713
+5 43 1 12480 61786 32027
+6 43 1 13229 61754 32629
+7 43 1 13840 61723 33027
+8 43 1 14720 61692 33420
+9 43 1 15329 61663 33776
+10 43 1 16071 61637 34220
+11 43 1 16968 61613 34826
+12 43 1 17599 61591 35149
+13 43 1 18506 61571 35728
+14 43 1 19310 61552 36258
+15 43 1 19904 61536 36586
+16 43 1 20781 61521 37032
+17 43 1 21424 61507 37372
+18 43 1 22091 61494 37971
+19 43 1 22969 61483 38313
+20 43 1 23561 61473 38812
+21 43 1 24389 61464 39144
+22 43 1 25295 61456 39681
+23 43 1 26142 61448 40271
+24 43 1 26735 61442 40575
+25 43 1 27612 61437 41140
+26 43 1 28295 61432 41598
+27 43 1 28920 61428 41976
+28 43 1 29792 61425 42361
+29 43 1 30387 61423 42750
+30 43 1 31138 61421 43367
+31 43 1 32023 61420 43704
+32 43 1 32640 61420 44249
+33 43 1 33534 61420 44589
+34 43 1 34340 61421 45137
+35 43 1 34929 61423 45662
+36 43 1 35803 61425 46005
+37 43 1 36462 61428 46435
+38 43 1 37105 61432 46787
+39 43 1 37941 61437 47393
+40 43 1 38800 61442 47747
+41 43 1 39393 61448 48202
+42 43 1 40240 61456 48835
+43 43 1 41128 61464 49176
+44 43 1 41732 61473 49719
+45 43 1 42616 61483 50037
+46 43 1 43329 61494 50623
+47 43 1 43942 61507 51057
+48 43 1 44816 61521 51466
+49 43 1 45421 61536 51838
+50 43 1 46160 61552 52283
+51 43 1 47054 61571 52911
+52 43 1 47681 61591 53250
+53 43 1 48591 61613 53851
+54 43 1 49399 61637 54393
+55 43 1 49997 61663 54740
+56 43 1 50850 61692 55201
+57 43 1 51695 61723 55583
+58 43 1 52306 61754 56207
+59 43 1 53055 61786 56537
+60 43 1 53964 61816 57099
+61 43 1 54615 61846 57455
+62 43 1 55535 61876 58103
+63 43 1 56314 61904 58577
+0 44 1 8425 63069 30264
+1 44 1 9128 63051 30824
+2 44 1 10030 63032 31148
+3 44 1 10659 63013 31685
+4 44 1 11582 62992 32038
+5 44 1 12475 62971 32667
+6 44 1 13228 62949 33093
+7 44 1 13837 62927 33457
+8 44 1 14717 62904 33849
+9 44 1 15328 62881 34273
+10 44 1 16067 62858 34858
+11 44 1 16964 62834 35161
+12 44 1 17593 62811 35740
+13 44 1 18502 62788 36296
+14 44 1 19308 62765 36612
+15 44 1 19902 62742 37101
+16 44 1 20779 62720 37425
+17 44 1 21424 62700 38022
+18 44 1 22089 62681 38362
+19 44 1 22967 62665 38830
+20 44 1 23559 62650 39152
+21 44 1 24386 62636 39670
+22 44 1 25292 62624 40274
+23 44 1 26141 62614 40585
+24 44 1 26734 62605 41168
+25 44 1 27611 62597 41640
+26 44 1 28295 62590 41993
+27 44 1 28920 62584 42405
+28 44 1 29792 62579 42778
+29 44 1 30387 62576 43385
+30 44 1 31138 62573 43707
+31 44 1 32023 62572 44241
+32 44 1 32640 62571 44568
+33 44 1 33534 62572 45141
+34 44 1 34341 62573 45686
+35 44 1 34929 62576 46012
+36 44 1 35804 62579 46480
+37 44 1 36462 62584 46817
+38 44 1 37106 62590 47422
+39 44 1 37942 62597 47773
+40 44 1 38801 62605 48212
+41 44 1 39394 62614 48829
+42 44 1 40243 62624 49153
+43 44 1 41131 62636 49734
+44 44 1 41734 62650 50051
+45 44 1 42618 62665 50654
+46 44 1 43330 62681 51108
+47 44 1 43943 62700 51488
+48 44 1 44818 62720 51889
+49 44 1 45422 62742 52315
+50 44 1 46164 62765 52929
+51 44 1 47057 62788 53250
+52 44 1 47687 62811 53847
+53 44 1 48596 62834 54420
+54 44 1 49402 62858 54757
+55 44 1 49999 62881 55257
+56 44 1 50852 62904 55624
+57 44 1 51698 62927 56238
+58 44 1 52307 62949 56574
+59 44 1 53060 62971 57080
+60 44 1 53970 62992 57424
+61 44 1 54622 63013 58079
+62 44 1 55541 63032 58583
+63 44 1 56316 63051 58959
+0 45 0 0 0 0
+1 45 0 0 0 0
+2 45 0 0 0 0
+3 45 0 0 0 0
+4 45 0 0 0 0
+5 45 0 0 0 0
+6 45 0 0 0 0
+7 45 0 0 0 0
+8 45 0 0 0 0
+9 45 0 0 0 0
+10 45 0 0 0 0
+11 45 0 0 0 0
+12 45 0 0 0 0
+13 45 0 0 0 0
+14 45 0 0 0 0
+15 45 0 0 0 0
+16 45 0 0 0 0
+17 45 0 0 0 0
+18 45 0 0 0 0
+19 45 0 0 0 0
+20 45 0 0 0 0
+21 45 0 0 0 0
+22 45 0 0 0 0
+23 45 0 0 0 0
+24 45 0 0 0 0
+25 45 0 0 0 0
+26 45 0 0 0 0
+27 45 0 0 0 0
+28 45 0 0 0 0
+29 45 0 0 0 0
+30 45 0 0 0 0
+31 45 0 0 0 0
+32 45 0 0 0 0
+33 45 0 0 0 0
+34 45 0 0 0 0
+35 45 0 0 0 0
+36 45 0 0 0 0
+37 45 0 0 0 0
+38 45 0 0 0 0
+39 45 0 0 0 0
+40 45 0 0 0 0
+41 45 0 0 0 0
+42 45 0 0 0 0
+43 45 0 0 0 0
+44 45 0 0 0 0
+45 45 0 0 0 0
+46 45 0 0 0 0
+47 45 0 0 0 0
+48 45 0 0 0 0
+49 45 0 0 0 0
+50 45 0 0 0 0
+51 45 0 0 0 0
+52 45 0 0 0 0
+53 45 0 0 0 0
+54 45 0 0 0 0
+55 45 0 0 0 0
+56 45 0 0 0 0
+57 45 0 0 0 0
+58 45 0 0 0 0
+59 45 0 0 0 0
+60 45 0 0 0 0
+61 45 0 0 0 0
+62 45 0 0 0 0
+63 45 0 0 0 0
+0 46 0 0 0 0
+1 46 0 0 0 0
+2 46 0 0 0 0
+3 46 0 0 0 0
+4 46 0 0 0 0
+5 46 0 0 0 0
+6 46 0 0 0 0
+7 46 0 0 0 0
+8 46 0 0 0 0
+9 46 0 0 0 0
+10 46 0 0 0 0
+11 46 0 0 0 0
+12 46 0 0 0 0
+13 46 0 0 0 0
+14 46 0 0 0 0
+15 46 0 0 0 0
+16 46 0 0 0 0
+17 46 0 0 0 0
+18 46 0 0 0 0
+19 46 0 0 0 0
+20 46 0 0 0 0
+21 46 0 0 0 0
+22 46 0 0 0 0
+23 46 0 0 0 0
+24 46 0 0 0 0
+25 46 0 0 0 0
+26 46 0 0 0 0
+27 46 0 0 0 0
+28 46 0 0 0 0
+29 46 0 0 0 0
+30 46 0 0 0 0
+31 46 0 0 0 0
+32 46 0 0 0 0
+33 46 0 0 0 0
+34 46 0 0 0 0
+35 46 0 0 0 0
+36 46 0 0 0 0
+37 46 0 0 0 0
+38 46 0 0 0 0
+39 46 0 0 0 0
+40 46 0 0 0 0
+41 46 0 0 0 0
+42 46 0 0 0 0
+43 46 0 0 0 0
+44 46 0 0 0 0
+45 46 0 0 0 0
+46 46 0 0 0 0
+47 46 0 0 0 0
+48 46 0 0 0 0
+49 46 0 0 0 0
+50 46 0 0 0 0
+51 46 0 0 0 0
+52 46 0 0 0 0
+53 46 0 0 0 0
+54 46 0 0 0 0
+55 46 0 0 0 0
+56 46 0 0 0 0
+57 46 0 0 0 0
+58 46 0 0 0 0
+59 46 0 0 0 0
+60 46 0 0 0 0
+61 46 0 0 0 0
+62 46 0 0 0 0
+63 46 0 0 0 0
+0 47 0 0 0 0
+1 47 0 0 0 0
+2 47 0 0 0 0
+3 47 0 0 0 0
+4 47 0 0 0 0
+5 47 0 0 0 0
+6 47 0 0 0 0
+7 47 0 0 0 0
+8 47 0 0 0 0
+9 47 0 0 0 0
+10 47 0 0 0 0
+11 47 0 0 0 0
+12 47 0 0 0 0
+13 47 0 0 0 0
+14 47 0 0 0 0
+15 47 0 0 0 0
+16 47 0 0 0 0
+17 47 0 0 0 0
+18 47 0 0 0 0
+19 47 0 0 0 0
+20 47 0 0 0 0
+21 47 0 0 0 0
+22 47 0 0 0 0
+23 47 0 0 0 0
+24 47 0 0 0 0
+25 47 0 0 0 0
+26 47 0 0 0 0
+27 47 0 0 0 0
+28 47 0 0 0 0
+29 47 0 0 0 0
+30 47 0 0 0 0
+31 47 0 0 0 0
+32 47 0 0 0 0
+33 47 0 0 0 0
+34 47 0 0 0 0
+35 47 0 0 0 0
+36 47 0 0 0 0
+37 47 0 0 0 0
+38 47 0 0 0 0
+39 47 0 0 0 0
+40 47 0 0 0 0
+41 47 0 0 0 0
+42 47 0 0 0 0
+43 47 0 0 0 0
+44 47 0 0 0 0
+45 47 0 0 0 0
+46 47 0 0 0 0
+47 47 0 0 0 0
+48 47 0 0 0 0
+49 47 0 0 0 0
+50 47 0 0 0 0
+51 47 0 0 0 0
+52 47 0 0 0 0
+53 47 0 0 0 0
+54 47 0 0 0 0
+55 47 0 0 0 0
+56 47 0 0 0 0
+57 47 0 0 0 0
+58 47 0 0 0 0
+59 47 0 0 0 0
+60 47 0 0 0 0
+61 47 0 0 0 0
+62 47 0 0 0 0
+63 47 0 0 0 0
diff --git a/tests/golden/stitch_rect_near.txt b/tests/golden/stitch_rect_near.txt
new file mode 100644
index 0000000..94e9465
--- /dev/null
+++ b/tests/golden/stitch_rect_near.txt
@@ -0,0 +1,3072 @@
+0 0 1 8670 12240 10200
+1 0 1 9435 13260 10965
+2 0 1 10200 13260 11475
+3 0 1 10710 13260 11730
+4 0 1 11475 13260 12240
+5 0 1 12240 13260 12750
+6 0 1 13515 13260 13515
+7 0 1 14280 13260 13770
+8 0 1 15045 13260 14280
+9 0 1 15555 13260 14790
+10 0 1 16320 13260 15045
+11 0 1 17085 13260 15555
+12 0 1 17595 13260 15810
+13 0 1 18360 13260 16320
+14 0 1 19125 13260 16830
+15 0 1 19635 13260 17085
+16 0 1 21165 13260 18105
+17 0 1 21675 13260 18360
+18 0 1 22440 13260 18870
+19 0 1 23205 13260 19125
+20 0 1 23715 13260 19635
+21 0 1 24480 13260 20145
+22 0 1 25245 13260 20400
+23 0 1 26010 13260 20910
+24 0 1 26520 13260 21165
+25 0 1 27285 13260 21675
+26 0 1 28050 13260 22185
+27 0 1 29325 13260 22950
+28 0 1 30090 13260 23205
+29 0 1 30600 13260 23715
+30 0 1 31365 13260 24225
+31 0 1 32130 13260 24480
+32 0 1 32640 13260 24990
+33 0 1 33405 13260 25500
+34 0 1 34170 13260 25755
+35 0 1 34680 13260 26265
+36 0 1 35445 13260 26520
+37 0 1 36210 13260 27030
+38 0 1 37485 13260 27795
+39 0 1 38250 13260 28305
+40 0 1 39015 13260 28560
+41 0 1 39525 13260 29070
+42 0 1 40290 13260 29580
+43 0 1 41055 13260 29835
+44 0 1 41565 13260 30345
+45 0 1 42330 13260 30855
+46 0 1 43095 13260 31110
+47 0 1 43605 13260 31620
+48 0 1 44370 13260 31875
+49 0 1 45645 13260 32895
+50 0 1 46410 13260 33150
+51 0 1 47175 13260 33660
+52 0 1 47685 13260 33915
+53 0 1 48450 13260 34425
+54 0 1 49215 13260 34935
+55 0 1 49725 13260 35190
+56 0 1 50490 13260 35700
+57 0 1 51255 13260 36210
+58 0 1 52020 13260 36465
+59 0 1 53295 13260 37230
+60 0 1 54060 13260 37740
+61 0 1 54570 13260 38250
+62 0 1 55335 13260 38505
+63 0 1 56100 13260 39015
+0 1 1 8670 14280 10965
+1 1 1 9435 14280 11475
+2 1 1 10200 14280 11730
+3 1 1 10710 14280 12240
+4 1 1 11475 14280 12750
+5 1 1 12240 14280 13005
+6 1 1 13515 14280 13770
+7 1 1 14280 14280 14280
+8 1 1 15045 14280 14790
+9 1 1 15555 14280 15045
+10 1 1 16320 14280 15555
+11 1 1 17085 14280 15810
+12 1 1 17595 14280 16320
+13 1 1 18360 14280 16830
+14 1 1 19125 14280 17085
+15 1 1 19635 14280 17595
+16 1 1 21165 14280 18360
+17 1 1 21675 14280 18870
+18 1 1 22440 14280 19125
+19 1 1 23205 14280 19635
+20 1 1 23715 14280 20145
+21 1 1 24480 14280 20400
+22 1 1 25245 14280 20910
+23 1 1 26010 14280 21165
+24 1 1 26520 14280 21675
+25 1 1 27285 14280 22185
+26 1 1 28050 14280 22440
+27 1 1 29325 14280 23205
+28 1 1 30090 14280 23715
+29 1 1 30600 14280 24225
+30 1 1 31365 14280 24480
+31 1 1 32130 14280 24990
+32 1 1 32640 14280 25500
+33 1 1 33405 14280 25755
+34 1 1 34170 14280 26265
+35 1 1 34680 14280 26520
+36 1 1 35445 14280 27030
+37 1 1 36210 14280 27540
+38 1 1 37485 14280 28305
+39 1 1 38250 14280 28560
+40 1 1 39015 14280 29070
+41 1 1 39525 14280 29580
+42 1 1 40290 14280 29835
+43 1 1 41055 14280 30345
+44 1 1 41565 14280 30855
+45 1 1 42330 14280 31110
+46 1 1 43095 14280 31620
+47 1 1 43605 14280 31875
+48 1 1 44370 14280 32385
+49 1 1 45645 14280 33150
+50 1 1 46410 14280 33660
+51 1 1 47175 14280 33915
+52 1 1 47685 14280 34425
+53 1 1 48450 14280 34935
+54 1 1 49215 14280 35190
+55 1 1 49725 14280 35700
+56 1 1 50490 14280 36210
+57 1 1 51255 14280 36465
+58 1 1 52020 14280 36975
+59 1 1 53295 14280 37740
+60 1 1 54060 14280 38250
+61 1 1 54570 14280 38505
+62 1 1 55335 14280 39015
+63 1 1 56100 14280 39270
+0 2 1 8670 15300 11475
+1 2 1 9435 15300 11730
+2 2 1 10200 15300 12240
+3 2 1 10710 15300 12750
+4 2 1 11475 15300 13005
+5 2 1 12240 15300 13515
+6 2 1 51510 32640 16320
+7 2 1 50745 32640 16320
+8 2 1 49980 32640 16320
+9 2 1 49470 32640 16320
+10 2 1 48705 32640 16320
+11 2 1 47940 32640 16320
+12 2 1 47430 32640 16320
+13 2 1 46665 32640 16320
+14 2 1 45900 32640 16320
+15 2 1 45390 32640 16320
+16 2 1 43860 32640 16320
+17 2 1 43350 32640 16320
+18 2 1 42585 32640 16320
+19 2 1 41820 32640 16320
+20 2 1 41310 32640 16320
+21 2 1 40545 32640 16320
+22 2 1 39780 32640 16320
+23 2 1 39015 32640 16320
+24 2 1 38505 32640 16320
+25 2 1 27285 15300 22440
+26 2 1 28050 15300 22950
+27 2 1 29325 15300 23715
+28 2 1 30090 15300 24225
+29 2 1 30600 15300 24480
+30 2 1 31365 15300 24990
+31 2 1 32130 15300 25500
+32 2 1 32640 15300 25755
+33 2 1 33405 15300 26265
+34 2 1 34170 15300 26520
+35 2 1 34680 15300 27030
+36 2 1 35445 15300 27540
+37 2 1 36210 15300 27795
+38 2 1 37485 15300 28560
+39 2 1 38250 15300 29070
+40 2 1 39015 15300 29580
+41 2 1 39525 15300 29835
+42 2 1 40290 15300 30345
+43 2 1 41055 15300 30855
+44 2 1 41565 15300 31110
+45 2 1 42330 15300 31620
+46 2 1 43095 15300 31875
+47 2 1 43605 15300 32385
+48 2 1 44370 15300 32895
+49 2 1 45645 15300 33660
+50 2 1 46410 15300 33915
+51 2 1 47175 15300 34425
+52 2 1 47685 15300 34935
+53 2 1 48450 15300 35190
+54 2 1 49215 15300 35700
+55 2 1 49725 15300 36210
+56 2 1 50490 15300 36465
+57 2 1 51255 15300 36975
+58 2 1 52020 15300 37230
+59 2 1 53295 15300 38250
+60 2 1 54060 15300 38505
+61 2 1 54570 15300 39015
+62 2 1 55335 15300 39270
+63 2 1 56100 15300 39780
+0 3 1 8670 16320 11730
+1 3 1 9435 16320 12240
+2 3 1 10200 16320 12750
+3 3 1 10710 16320 13005
+4 3 1 11475 16320 13515
+5 3 1 12240 16320 13770
+6 3 1 51510 32640 16320
+7 3 1 50745 32640 16320
+8 3 1 49980 32640 16320
+9 3 1 49470 32640 16320
+10 3 1 48705 32640 16320
+11 3 1 47940 32640 16320
+12 3 1 47430 32640 16320
+13 3 1 46665 32640 16320
+14 3 1 45900 32640 16320
+15 3 1 45390 32640 16320
+16 3 1 43860 32640 16320
+17 3 1 43350 32640 16320
+18 3 1 42585 32640 16320
+19 3 1 41820 32640 16320
+20 3 1 41310 32640 16320
+21 3 1 40545 32640 16320
+22 3 1 39780 32640 16320
+23 3 1 39015 32640 16320
+24 3 1 38505 32640 16320
+25 3 1 27285 16320 22950
+26 3 1 28050 16320 23205
+27 3 1 29325 16320 24225
+28 3 1 30090 16320 24480
+29 3 1 30600 16320 24990
+30 3 1 31365 16320 25500
+31 3 1 32130 16320 25755
+32 3 1 32640 16320 26265
+33 3 1 33405 16320 26520
+34 3 1 34170 16320 27030
+35 3 1 34680 16320 27540
+36 3 1 35445 16320 27795
+37 3 1 36210 16320 28305
+38 3 1 37485 16320 29070
+39 3 1 38250 16320 29580
+40 3 1 39015 16320 29835
+41 3 1 39525 16320 30345
+42 3 1 40290 16320 30855
+43 3 1 41055 16320 31110
+44 3 1 41565 16320 31620
+45 3 1 42330 16320 31875
+46 3 1 43095 16320 32385
+47 3 1 43605 16320 32895
+48 3 1 44370 16320 33150
+49 3 1 45645 16320 33915
+50 3 1 46410 16320 34425
+51 3 1 47175 16320 34935
+52 3 1 47685 16320 35190
+53 3 1 48450 16320 35700
+54 3 1 49215 16320 36210
+55 3 1 49725 16320 36465
+56 3 1 50490 16320 36975
+57 3 1 51255 16320 37230
+58 3 1 52020 16320 37740
+59 3 1 53295 16320 38505
+60 3 1 54060 16320 39015
+61 3 1 54570 16320 39270
+62 3 1 55335 16320 39780
+63 3 1 56100 16320 40290
+0 4 1 8670 17340 12240
+1 4 1 9435 17340 12750
+2 4 1 10200 17340 13005
+3 4 1 10710 17340 13515
+4 4 1 11475 17340 13770
+5 4 1 12240 17340 14280
+6 4 1 51510 32640 16320
+7 4 1 50745 32640 16320
+8 4 1 49980 32640 16320
+9 4 1 49470 32640 16320
+10 4 1 48705 32640 16320
+11 4 1 47940 32640 16320
+12 4 1 47430 32640 16320
+13 4 1 46665 32640 16320
+14 4 1 45900 32640 16320
+15 4 1 45390 32640 16320
+16 4 1 43860 32640 16320
+17 4 1 43350 32640 16320
+18 4 1 42585 32640 16320
+19 4 1 41820 32640 16320
+20 4 1 41310 32640 16320
+21 4 1 40545 32640 16320
+22 4 1 39780 32640 16320
+23 4 1 39015 32640 16320
+24 4 1 38505 32640 16320
+25 4 1 27285 17340 23205
+26 4 1 28050 17340 23715
+27 4 1 29325 17340 24480
+28 4 1 30090 17340 24990
+29 4 1 30600 17340 25500
+30 4 1 31365 17340 25755
+31 4 1 32130 17340 26265
+32 4 1 32640 17340 26520
+33 4 1 33405 17340 27030
+34 4 1 34170 17340 27540
+35 4 1 34680 17340 27795
+36 4 1 35445 17340 28305
+37 4 1 36210 17340 28560
+38 4 1 37485 17340 29580
+39 4 1 38250 17340 29835
+40 4 1 39015 17340 30345
+41 4 1 39525 17340 30855
+42 4 1 40290 17340 31110
+43 4 1 41055 17340 31620
+44 4 1 41565 17340 31875
+45 4 1 42330 17340 32385
+46 4 1 43095 17340 32895
+47 4 1 43605 17340 33150
+48 4 1 44370 17340 33660
+49 4 1 45645 17340 34425
+50 4 1 46410 17340 34935
+51 4 1 47175 17340 35190
+52 4 1 47685 17340 35700
+53 4 1 48450 17340 36210
+54 4 1 49215 17340 36465
+55 4 1 49725 17340 36975
+56 4 1 50490 17340 37230
+57 4 1 51255 17340 37740
+58 4 1 52020 17340 38250
+59 4 1 53295 17340 39015
+60 4 1 54060 17340 39270
+61 4 1 54570 17340 39780
+62 4 1 55335 17340 40290
+63 4 1 56100 17340 40545
+0 5 1 8670 18360 12750
+1 5 1 9435 18360 13005
+2 5 1 10200 18360 13515
+3 5 1 10710 18360 13770
+4 5 1 11475 18360 14280
+5 5 1 12240 18360 14790
+6 5 1 51510 32640 16320
+7 5 1 50745 32640 16320
+8 5 1 49980 32640 16320
+9 5 1 49470 32640 16320
+10 5 1 48705 32640 16320
+11 5 1 47940 32640 16320
+12 5 1 47430 32640 16320
+13 5 1 46665 32640 16320
+14 5 1 45900 32640 16320
+15 5 1 45390 32640 16320
+16 5 1 43860 32640 16320
+17 5 1 43350 32640 16320
+18 5 1 42585 32640 16320
+19 5 1 41820 32640 16320
+20 5 1 41310 32640 16320
+21 5 1 40545 32640 16320
+22 5 1 39780 32640 16320
+23 5 1 39015 32640 16320
+24 5 1 38505 32640 16320
+25 5 1 27285 18360 23715
+26 5 1 28050 18360 24225
+27 5 1 29325 18360 24990
+28 5 1 30090 18360 25500
+29 5 1 30600 18360 25755
+30 5 1 31365 18360 26265
+31 5 1 32130 18360 26520
+32 5 1 32640 18360 27030
+33 5 1 33405 18360 27540
+34 5 1 34170 18360 27795
+35 5 1 34680 18360 28305
+36 5 1 35445 18360 28560
+37 5 1 36210 18360 29070
+38 5 1 37485 18360 29835
+39 5 1 38250 18360 30345
+40 5 1 39015 18360 30855
+41 5 1 39525 18360 31110
+42 5 1 40290 18360 31620
+43 5 1 41055 18360 31875
+44 5 1 41565 18360 32385
+45 5 1 42330 18360 32895
+46 5 1 43095 18360 33150
+47 5 1 43605 18360 33660
+48 5 1 44370 18360 33915
+49 5 1 45645 18360 34935
+50 5 1 46410 18360 35190
+51 5 1 47175 18360 35700
+52 5 1 47685 18360 36210
+53 5 1 48450 18360 36465
+54 5 1 49215 18360 36975
+55 5 1 49725 18360 37230
+56 5 1 50490 18360 37740
+57 5 1 51255 18360 38250
+58 5 1 52020 18360 38505
+59 5 1 53295 18360 39270
+60 5 1 54060 18360 39780
+61 5 1 54570 18360 40290
+62 5 1 55335 18360 40545
+63 5 1 56100 18360 41055
+0 6 1 8670 19380 13005
+1 6 1 9435 19380 13515
+2 6 1 10200 19380 13770
+3 6 1 10710 19380 14280
+4 6 1 11475 19380 14790
+5 6 1 12240 19380 15045
+6 6 1 51510 32640 16320
+7 6 1 50745 32640 16320
+8 6 1 49980 32640 16320
+9 6 1 49470 32640 16320
+10 6 1 48705 32640 16320
+11 6 1 47940 32640 16320
+12 6 1 47430 32640 16320
+13 6 1 46665 32640 16320
+14 6 1 45900 32640 16320
+15 6 1 45390 32640 16320
+16 6 1 43860 32640 16320
+17 6 1 43350 32640 16320
+18 6 1 42585 32640 16320
+19 6 1 41820 32640 16320
+20 6 1 41310 32640 16320
+21 6 1 40545 32640 16320
+22 6 1 39780 32640 16320
+23 6 1 39015 32640 16320
+24 6 1 38505 32640 16320
+25 6 1 27285 19380 24225
+26 6 1 28050 19380 24480
+27 6 1 29325 19380 25500
+28 6 1 30090 19380 25755
+29 6 1 30600 19380 26265
+30 6 1 31365 19380 26520
+31 6 1 32130 19380 27030
+32 6 1 32640 19380 27540
+33 6 1 33405 19380 27795
+34 6 1 34170 19380 28305
+35 6 1 34680 19380 28560
+36 6 1 35445 19380 29070
+37 6 1 36210 19380 29580
+38 6 1 37485 19380 30345
+39 6 1 38250 19380 30855
+40 6 1 39015 19380 31110
+41 6 1 39525 19380 31620
+42 6 1 40290 19380 31875
+43 6 1 41055 19380 32385
+44 6 1 41565 19380 32895
+45 6 1 42330 19380 33150
+46 6 1 43095 19380 33660
+47 6 1 43605 19380 33915
+48 6 1 44370 19380 34425
+49 6 1 45645 19380 35190
+50 6 1 46410 19380 35700
+51 6 1 47175 19380 36210
+52 6 1 47685 19380 36465
+53 6 1 48450 19380 36975
+54 6 1 49215 19380 37230
+55 6 1 49725 19380 37740
+56 6 1 50490 19380 38250
+57 6 1 51255 19380 38505
+58 6 1 52020 19380 39015
+59 6 1 53295 19380 39780
+60 6 1 54060 19380 40290
+61 6 1 54570 19380 40545
+62 6 1 55335 19380 41055
+63 6 1 56100 19380 41565
+0 7 1 8670 20400 13515
+1 7 1 9435 20400 13770
+2 7 1 10200 20400 14280
+3 7 1 10710 20400 14790
+4 7 1 11475 20400 15045
+5 7 1 12240 20400 15555
+6 7 1 51510 32640 16320
+7 7 1 50745 32640 16320
+8 7 1 49980 32640 16320
+9 7 1 49470 32640 16320
+10 7 1 48705 32640 16320
+11 7 1 47940 32640 16320
+12 7 1 47430 32640 16320
+13 7 1 46665 32640 16320
+14 7 1 45900 32640 16320
+15 7 1 45390 32640 16320
+16 7 1 43860 32640 16320
+17 7 1 43350 32640 16320
+18 7 1 42585 32640 16320
+19 7 1 41820 32640 16320
+20 7 1 41310 32640 16320
+21 7 1 40545 32640 16320
+22 7 1 39780 32640 16320
+23 7 1 39015 32640 16320
+24 7 1 38505 32640 16320
+25 7 1 27285 20400 24480
+26 7 1 28050 20400 24990
+27 7 1 29325 20400 25755
+28 7 1 30090 20400 26265
+29 7 1 30600 20400 26520
+30 7 1 31365 20400 27030
+31 7 1 32130 20400 27540
+32 7 1 32640 20400 27795
+33 7 1 33405 20400 28305
+34 7 1 34170 20400 28560
+35 7 1 34680 20400 29070
+36 7 1 35445 20400 29580
+37 7 1 36210 20400 29835
+38 7 1 37485 20400 30855
+39 7 1 38250 20400 31110
+40 7 1 39015 20400 31620
+41 7 1 39525 20400 31875
+42 7 1 40290 20400 32385
+43 7 1 41055 20400 32895
+44 7 1 41565 20400 33150
+45 7 1 42330 20400 33660
+46 7 1 43095 20400 33915
+47 7 1 43605 20400 34425
+48 7 1 44370 20400 34935
+49 7 1 45645 20400 35700
+50 7 1 46410 20400 36210
+51 7 1 47175 20400 36465
+52 7 1 47685 20400 36975
+53 7 1 48450 20400 37230
+54 7 1 49215 20400 37740
+55 7 1 49725 20400 38250
+56 7 1 50490 20400 38505
+57 7 1 51255 20400 39015
+58 7 1 52020 20400 39270
+59 7 1 53295 20400 40290
+60 7 1 54060 20400 40545
+61 7 1 54570 20400 41055
+62 7 1 55335 20400 41565
+63 7 1 56100 20400 41820
+0 8 1 8670 21675 13770
+1 8 1 9435 21675 14280
+2 8 1 10200 21675 14790
+3 8 1 10710 21675 15045
+4 8 1 11475 21675 15555
+5 8 1 12240 21675 15810
+6 8 1 51510 32640 16320
+7 8 1 50745 32640 16320
+8 8 1 49980 32640 16320
+9 8 1 49470 32640 16320
+10 8 1 48705 32640 16320
+11 8 1 47940 32640 16320
+12 8 1 47430 32640 16320
+13 8 1 46665 32640 16320
+14 8 1 45900 32640 16320
+15 8 1 45390 32640 16320
+16 8 1 43860 32640 16320
+17 8 1 43350 32640 16320
+18 8 1 42585 32640 16320
+19 8 1 41820 32640 16320
+20 8 1 41310 32640 16320
+21 8 1 40545 32640 16320
+22 8 1 39780 32640 16320
+23 8 1 39015 32640 16320
+24 8 1 38505 32640 16320
+25 8 1 27285 22695 25500
+26 8 1 28050 22695 25755
+27 8 1 29325 22695 26520
+28 8 1 30090 22695 27030
+29 8 1 30600 22695 27540
+30 8 1 31365 22695 27795
+31 8 1 32130 22695 28305
+32 8 1 32640 22695 28560
+33 8 1 33405 22695 29070
+34 8 1 34170 22695 29580
+35 8 1 34680 22695 29835
+36 8 1 35445 22695 30345
+37 8 1 36210 22695 30855
+38 8 1 37485 22695 31620
+39 8 1 38250 22695 31875
+40 8 1 39015 22695 32385
+41 8 1 39525 22695 32895
+42 8 1 40290 22695 33150
+43 8 1 41055 22695 33660
+44 8 1 41565 22695 33915
+45 8 1 42330 22695 34425
+46 8 1 43095 22695 34935
+47 8 1 43605 22695 35190
+48 8 1 44370 21675 35190
+49 8 1 45645 21675 36210
+50 8 1 46410 21675 36465
+51 8 1 47175 21675 36975
+52 8 1 47685 21675 37230
+53 8 1 48450 21675 37740
+54 8 1 49215 21675 38250
+55 8 1 49725 21675 38505
+56 8 1 50490 21675 39015
+57 8 1 51255 21675 39270
+58 8 1 52020 21675 39780
+59 8 1 53295 21675 40545
+60 8 1 54060 21675 41055
+61 8 1 54570 21675 41565
+62 8 1 55335 21675 41820
+63 8 1 56100 21675 42330
+0 9 1 8670 22695 14280
+1 9 1 9435 22695 14790
+2 9 1 10200 22695 15045
+3 9 1 10710 22695 15555
+4 9 1 11475 22695 15810
+5 9 1 12240 22695 16320
+6 9 1 51510 32640 16320
+7 9 1 50745 32640 16320
+8 9 1 49980 32640 16320
+9 9 1 49470 32640 16320
+10 9 1 48705 32640 16320
+11 9 1 47940 32640 16320
+12 9 1 47430 32640 16320
+13 9 1 46665 32640 16320
+14 9 1 45900 32640 16320
+15 9 1 45390 32640 16320
+16 9 1 43860 32640 16320
+17 9 1 43350 32640 16320
+18 9 1 42585 32640 16320
+19 9 1 41820 32640 16320
+20 9 1 41310 32640 16320
+21 9 1 40545 32640 16320
+22 9 1 39780 32640 16320
+23 9 1 39015 32640 16320
+24 9 1 38505 32640 16320
+25 9 1 27285 23715 25755
+26 9 1 28050 23715 26265
+27 9 1 29325 23715 27030
+28 9 1 30090 23715 27540
+29 9 1 30600 23715 27795
+30 9 1 31365 23715 28305
+31 9 1 32130 23715 28560
+32 9 1 32640 23715 29070
+33 9 1 33405 23715 29580
+34 9 1 34170 23715 29835
+35 9 1 34680 23715 30345
+36 9 1 35445 23715 30855
+37 9 1 36210 23715 31110
+38 9 1 37485 23715 31875
+39 9 1 38250 23715 32385
+40 9 1 39015 23715 32895
+41 9 1 39525 23715 33150
+42 9 1 40290 23715 33660
+43 9 1 41055 23715 33915
+44 9 1 41565 23715 34425
+45 9 1 42330 23715 34935
+46 9 1 43095 23715 35190
+47 9 1 43605 23715 35700
+48 9 1 44370 23715 36210
+49 9 1 45645 23715 36975
+50 9 1 46410 23715 37230
+51 9 1 47175 23715 37740
+52 9 1 47685 23715 38250
+53 9 1 48450 23715 38505
+54 9 1 49215 23715 39015
+55 9 1 49725 23715 39270
+56 9 1 50490 23715 39780
+57 9 1 51255 22695 39780
+58 9 1 52020 22695 40290
+59 9 1 53295 22695 41055
+60 9 1 54060 22695 41565
+61 9 1 54570 22695 41820
+62 9 1 55335 22695 42330
+63 9 1 56100 22695 42585
+0 10 1 8670 23715 14790
+1 10 1 9435 24735 15555
+2 10 1 10200 24735 15810
+3 10 1 10710 24735 16320
+4 10 1 11475 24735 16830
+5 10 1 12240 24735 17085
+6 10 1 51510 32640 16320
+7 10 1 50745 32640 16320
+8 10 1 49980 32640 16320
+9 10 1 49470 32640 16320
+10 10 1 48705 32640 16320
+11 10 1 47940 32640 16320
+12 10 1 47430 32640 16320
+13 10 1 46665 32640 16320
+14 10 1 45900 32640 16320
+15 10 1 45390 32640 16320
+16 10 1 43860 32640 16320
+17 10 1 43350 32640 16320
+18 10 1 42585 32640 16320
+19 10 1 41820 32640 16320
+20 10 1 41310 32640 16320
+21 10 1 40545 32640 16320
+22 10 1 39780 32640 16320
+23 10 1 39015 32640 16320
+24 10 1 38505 32640 16320
+25 10 1 27285 24735 26265
+26 10 1 28050 24735 26520
+27 10 1 29325 24735 27540
+28 10 1 30090 24735 27795
+29 10 1 30600 24735 28305
+30 10 1 31365 24735 28560
+31 10 1 32130 24735 29070
+32 10 1 32640 24735 29580
+33 10 1 33405 24735 29835
+34 10 1 34170 24735 30345
+35 10 1 34680 24735 30855
+36 10 1 35445 24735 31110
+37 10 1 36210 24735 31620
+38 10 1 37485 24735 32385
+39 10 1 38250 24735 32895
+40 10 1 39015 24735 33150
+41 10 1 39525 24735 33660
+42 10 1 40290 24735 33915
+43 10 1 41055 24735 34425
+44 10 1 41565 24735 34935
+45 10 1 42330 24735 35190
+46 10 1 43095 24735 35700
+47 10 1 43605 24735 36210
+48 10 1 44370 24735 36465
+49 10 1 45645 24735 37230
+50 10 1 46410 24735 37740
+51 10 1 47175 24735 38250
+52 10 1 47685 24735 38505
+53 10 1 48450 24735 39015
+54 10 1 49215 24735 39270
+55 10 1 49725 24735 39780
+56 10 1 50490 24735 40290
+57 10 1 51255 24735 40545
+58 10 1 52020 24735 41055
+59 10 1 53295 24735 41820
+60 10 1 54060 24735 42330
+61 10 1 54570 24735 42585
+62 10 1 55335 24735 43095
+63 10 1 56100 24735 43605
+0 11 1 8670 25755 15555
+1 11 1 9435 25755 15810
+2 11 1 10200 25755 16320
+3 11 1 10710 25755 16830
+4 11 1 11475 25755 17085
+5 11 1 12240 25755 17595
+6 11 1 51510 32640 16320
+7 11 1 50745 32640 16320
+8 11 1 49980 32640 16320
+9 11 1 49470 32640 16320
+10 11 1 48705 32640 16320
+11 11 1 47940 32640 16320
+12 11 1 47430 32640 16320
+13 11 1 46665 32640 16320
+14 11 1 45900 32640 16320
+15 11 1 45390 32640 16320
+16 11 1 43860 32640 16320
+17 11 1 43350 32640 16320
+18 11 1 42585 32640 16320
+19 11 1 41820 32640 16320
+20 11 1 41310 32640 16320
+21 11 1 40545 32640 16320
+22 11 1 39780 32640 16320
+23 11 1 39015 32640 16320
+24 11 1 38505 32640 16320
+25 11 1 27285 25755 26520
+26 11 1 28050 25755 27030
+27 11 1 29325 25755 27795
+28 11 1 30090 25755 28305
+29 11 1 30600 25755 28560
+30 11 1 31365 25755 29070
+31 11 1 32130 25755 29580
+32 11 1 32640 25755 29835
+33 11 1 33405 25755 30345
+34 11 1 34170 25755 30855
+35 11 1 34680 25755 31110
+36 11 1 35445 25755 31620
+37 11 1 36210 25755 31875
+38 11 1 37485 25755 32895
+39 11 1 38250 25755 33150
+40 11 1 39015 25755 33660
+41 11 1 39525 25755 33915
+42 11 1 40290 25755 34425
+43 11 1 41055 25755 34935
+44 11 1 41565 25755 35190
+45 11 1 42330 25755 35700
+46 11 1 43095 25755 36210
+47 11 1 43605 25755 36465
+48 11 1 44370 25755 36975
+49 11 1 45645 25755 37740
+50 11 1 46410 25755 38250
+51 11 1 47175 25755 38505
+52 11 1 47685 25755 39015
+53 11 1 48450 25755 39270
+54 11 1 49215 25755 39780
+55 11 1 49725 25755 40290
+56 11 1 50490 25755 40545
+57 11 1 51255 25755 41055
+58 11 1 52020 25755 41565
+59 11 1 53295 25755 42330
+60 11 1 54060 25755 42585
+61 11 1 54570 25755 43095
+62 11 1 55335 25755 43605
+63 11 1 56100 25755 43860
+0 12 1 8670 26775 15810
+1 12 1 9435 26775 16320
+2 12 1 10200 26775 16830
+3 12 1 10710 26775 17085
+4 12 1 11475 26775 17595
+5 12 1 12240 26775 18105
+6 12 1 13005 26775 18360
+7 12 1 50745 32640 16320
+8 12 1 49980 32640 16320
+9 12 1 49470 32640 16320
+10 12 1 48705 32640 16320
+11 12 1 47940 32640 16320
+12 12 1 47430 32640 16320
+13 12 1 46665 32640 16320
+14 12 1 45900 32640 16320
+15 12 1 45390 32640 16320
+16 12 1 43860 32640 16320
+17 12 1 43350 32640 16320
+18 12 1 42585 32640 16320
+19 12 1 41820 32640 16320
+20 12 1 41310 32640 16320
+21 12 1 40545 32640 16320
+22 12 1 39780 32640 16320
+23 12 1 39015 32640 16320
+24 12 1 38505 32640 16320
+25 12 1 27285 26775 27030
+26 12 1 28050 26775 27540
+27 12 1 29325 26775 28305
+28 12 1 30090 26775 28560
+29 12 1 30600 26775 29070
+30 12 1 31365 26775 29580
+31 12 1 32130 26775 29835
+32 12 1 32640 26775 30345
+33 12 1 33405 26775 30855
+34 12 1 34170 26775 31110
+35 12 1 34680 26775 31620
+36 12 1 35445 26775 31875
+37 12 1 36210 26775 32385
+38 12 1 37485 26775 33150
+39 12 1 38250 26775 33660
+40 12 1 39015 26775 33915
+41 12 1 39525 26775 34425
+42 12 1 40290 26775 34935
+43 12 1 41055 26775 35190
+44 12 1 41565 26775 35700
+45 12 1 42330 26775 36210
+46 12 1 43095 26775 36465
+47 12 1 43605 26775 36975
+48 12 1 44370 26775 37230
+49 12 1 45645 26775 38250
+50 12 1 46410 26775 38505
+51 12 1 47175 26775 39015
+52 12 1 47685 26775 39270
+53 12 1 48450 26775 39780
+54 12 1 49215 26775 40290
+55 12 1 49725 26775 40545
+56 12 1 50490 26775 41055
+57 12 1 51255 26775 41565
+58 12 1 52530 26775 42330
+59 12 1 53295 26775 42585
+60 12 1 54060 26775 43095
+61 12 1 54570 26775 43605
+62 12 1 55335 26775 43860
+63 12 1 56100 26775 44370
+0 13 1 8670 27795 16320
+1 13 1 9435 27795 16830
+2 13 1 10200 27795 17085
+3 13 1 10710 27795 17595
+4 13 1 11475 27795 18105
+5 13 1 12240 27795 18360
+6 13 1 13005 27795 18870
+7 13 1 50745 32640 16320
+8 13 1 49980 32640 16320
+9 13 1 49470 32640 16320
+10 13 1 48705 32640 16320
+11 13 1 47940 32640 16320
+12 13 1 47430 32640 16320
+13 13 1 46665 32640 16320
+14 13 1 45900 32640 16320
+15 13 1 45390 32640 16320
+16 13 1 43860 32640 16320
+17 13 1 43350 32640 16320
+18 13 1 42585 32640 16320
+19 13 1 41820 32640 16320
+20 13 1 41310 32640 16320
+21 13 1 40545 32640 16320
+22 13 1 39780 32640 16320
+23 13 1 39015 32640 16320
+24 13 1 38505 32640 16320
+25 13 1 27285 27795 27540
+26 13 1 28050 27795 27795
+27 13 1 29325 27795 28560
+28 13 1 30090 27795 29070
+29 13 1 30600 27795 29580
+30 13 1 31365 27795 29835
+31 13 1 32130 27795 30345
+32 13 1 32640 27795 30855
+33 13 1 33405 27795 31110
+34 13 1 34170 27795 31620
+35 13 1 34680 27795 31875
+36 13 1 35445 27795 32385
+37 13 1 36210 27795 32895
+38 13 1 37485 27795 33660
+39 13 1 38250 27795 33915
+40 13 1 39015 27795 34425
+41 13 1 39525 27795 34935
+42 13 1 40290 27795 35190
+43 13 1 41055 27795 35700
+44 13 1 41565 27795 36210
+45 13 1 42330 27795 36465
+46 13 1 43095 27795 36975
+47 13 1 43605 27795 37230
+48 13 1 44370 27795 37740
+49 13 1 45645 27795 38505
+50 13 1 46410 27795 39015
+51 13 1 47175 27795 39270
+52 13 1 47685 27795 39780
+53 13 1 48450 27795 40290
+54 13 1 49215 27795 40545
+55 13 1 49725 27795 41055
+56 13 1 50490 27795 41565
+57 13 1 51255 27795 41820
+58 13 1 52530 27795 42585
+59 13 1 53295 27795 43095
+60 13 1 54060 27795 43605
+61 13 1 54570 27795 43860
+62 13 1 55335 27795 44370
+63 13 1 56100 27795 44625
+0 14 1 8670 28815 16830
+1 14 1 9435 28815 17085
+2 14 1 10200 28815 17595
+3 14 1 10710 28815 18105
+4 14 1 11475 28815 18360
+5 14 1 12240 28815 18870
+6 14 1 13005 28815 19125
+7 14 1 50745 32640 16320
+8 14 1 49980 32640 16320
+9 14 1 49470 32640 16320
+10 14 1 48705 32640 16320
+11 14 1 47940 32640 16320
+12 14 1 47430 32640 16320
+13 14 1 46665 32640 16320
+14 14 1 45900 32640 16320
+15 14 1 45390 32640 16320
+16 14 1 43860 32640 16320
+17 14 1 43350 32640 16320
+18 14 1 42585 32640 16320
+19 14 1 41820 32640 16320
+20 14 1 41310 32640 16320
+21 14 1 40545 32640 16320
+22 14 1 39780 32640 16320
+23 14 1 39015 32640 16320
+24 14 1 38505 32640 16320
+25 14 1 27285 28815 27795
+26 14 1 28050 28815 28305
+27 14 1 29325 28815 29070
+28 14 1 30090 28815 29580
+29 14 1 30600 28815 29835
+30 14 1 31365 28815 30345
+31 14 1 32130 28815 30855
+32 14 1 32640 28815 31110
+33 14 1 33405 28815 31620
+34 14 1 34170 28815 31875
+35 14 1 34680 28815 32385
+36 14 1 35445 28815 32895
+37 14 1 36210 28815 33150
+38 14 1 37485 28815 33915
+39 14 1 38250 28815 34425
+40 14 1 39015 28815 34935
+41 14 1 39525 28815 35190
+42 14 1 40290 28815 35700
+43 14 1 41055 28815 36210
+44 14 1 41565 28815 36465
+45 14 1 42330 28815 36975
+46 14 1 43095 28815 37230
+47 14 1 43605 28815 37740
+48 14 1 44370 28815 38250
+49 14 1 45645 28815 39015
+50 14 1 46410 28815 39270
+51 14 1 47175 28815 39780
+52 14 1 47685 28815 40290
+53 14 1 48450 28815 40545
+54 14 1 49215 28815 41055
+55 14 1 49725 28815 41565
+56 14 1 50490 28815 41820
+57 14 1 51255 28815 42330
+58 14 1 52530 28815 43095
+59 14 1 53295 28815 43605
+60 14 1 54060 28815 43860
+61 14 1 54570 28815 44370
+62 14 1 55335 28815 44625
+63 14 1 56100 28815 45135
+0 15 1 8670 29835 17085
+1 15 1 9435 29835 17595
+2 15 1 10200 29835 18105
+3 15 1 10710 29835 18360
+4 15 1 11475 29835 18870
+5 15 1 12240 29835 19125
+6 15 1 13005 29835 19635
+7 15 1 50745 32640 16320
+8 15 1 49980 32640 16320
+9 15 1 49470 32640 16320
+10 15 1 48705 32640 16320
+11 15 1 47940 32640 16320
+12 15 1 47430 32640 16320
+13 15 1 46665 32640 16320
+14 15 1 45900 32640 16320
+15 15 1 45390 32640 16320
+16 15 1 43860 32640 16320
+17 15 1 43350 32640 16320
+18 15 1 42585 32640 16320
+19 15 1 41820 32640 16320
+20 15 1 41310 32640 16320
+21 15 1 40545 32640 16320
+22 15 1 39780 32640 16320
+23 15 1 39015 32640 16320
+24 15 1 38505 32640 16320
+25 15 1 27285 29835 28305
+26 15 1 28050 29835 28560
+27 15 1 29325 29835 29580
+28 15 1 30090 29835 29835
+29 15 1 30600 29835 30345
+30 15 1 31365 29835 30855
+31 15 1 32130 29835 31110
+32 15 1 32640 29835 31620
+33 15 1 33405 29835 31875
+34 15 1 34170 29835 32385
+35 15 1 34680 29835 32895
+36 15 1 35445 29835 33150
+37 15 1 36210 29835 33660
+38 15 1 37485 29835 34425
+39 15 1 38250 29835 34935
+40 15 1 39015 29835 35190
+41 15 1 39525 29835 35700
+42 15 1 40290 29835 36210
+43 15 1 41055 29835 36465
+44 15 1 41565 29835 36975
+45 15 1 42330 29835 37230
+46 15 1 43095 29835 37740
+47 15 1 43605 29835 38250
+48 15 1 44370 29835 38505
+49 15 1 45645 29835 39270
+50 15 1 46410 29835 39780
+51 15 1 47175 29835 40290
+52 15 1 47685 29835 40545
+53 15 1 48450 29835 41055
+54 15 1 49215 29835 41565
+55 15 1 49725 29835 41820
+56 15 1 50490 29835 42330
+57 15 1 51255 29835 42585
+58 15 1 52530 29835 43605
+59 15 1 53295 29835 43860
+60 15 1 54060 29835 44370
+61 15 1 54570 29835 44625
+62 15 1 55335 29835 45135
+63 15 1 56100 29835 45645
+0 16 1 8670 30855 17595
+1 16 1 9435 30855 18105
+2 16 1 10200 30855 18360
+3 16 1 10710 30855 18870
+4 16 1 11475 30855 19125
+5 16 1 12240 30855 19635
+6 16 1 13005 30855 20145
+7 16 1 50745 32640 16320
+8 16 1 49980 32640 16320
+9 16 1 49470 32640 16320
+10 16 1 48705 32640 16320
+11 16 1 47940 32640 16320
+12 16 1 47430 32640 16320
+13 16 1 46665 32640 16320
+14 16 1 45900 32640 16320
+15 16 1 45390 32640 16320
+16 16 1 43860 32640 16320
+17 16 1 43350 32640 16320
+18 16 1 42585 32640 16320
+19 16 1 41820 32640 16320
+20 16 1 41310 32640 16320
+21 16 1 40545 32640 16320
+22 16 1 39780 32640 16320
+23 16 1 39015 32640 16320
+24 16 1 38505 32640 16320
+25 16 1 27285 30855 28560
+26 16 1 28050 30855 29070
+27 16 1 29325 30855 29835
+28 16 1 30090 30855 30345
+29 16 1 30600 30855 30855
+30 16 1 31365 30855 31110
+31 16 1 32130 30855 31620
+32 16 1 32640 30855 31875
+33 16 1 33405 30855 32385
+34 16 1 34170 30855 32895
+35 16 1 34680 30855 33150
+36 16 1 35445 30855 33660
+37 16 1 36210 30855 33915
+38 16 1 37485 30855 34935
+39 16 1 38250 30855 35190
+40 16 1 39015 30855 35700
+41 16 1 39525 30855 36210
+42 16 1 40290 30855 36465
+43 16 1 41055 30855 36975
+44 16 1 41565 30855 37230
+45 16 1 42330 30855 37740
+46 16 1 43095 30855 38250
+47 16 1 43605 30855 38505
+48 16 1 44370 30855 39015
+49 16 1 45645 30855 39780
+50 16 1 46410 30855 40290
+51 16 1 47175 30855 40545
+52 16 1 47685 30855 41055
+53 16 1 48450 30855 41565
+54 16 1 49215 30855 41820
+55 16 1 49725 30855 42330
+56 16 1 50490 30855 42585
+57 16 1 51255 30855 43095
+58 16 1 52530 30855 43860
+59 16 1 53295 30855 44370
+60 16 1 54060 30855 44625
+61 16 1 54570 30855 45135
+62 16 1 55335 30855 45645
+63 16 1 56100 30855 45900
+0 17 1 8670 31875 18105
+1 17 1 9435 31875 18360
+2 17 1 10200 31875 18870
+3 17 1 10710 31875 19125
+4 17 1 11475 31875 19635
+5 17 1 12240 31875 20145
+6 17 1 13005 31875 20400
+7 17 1 50745 32640 16320
+8 17 1 49980 32640 16320
+9 17 1 49470 32640 16320
+10 17 1 48705 32640 16320
+11 17 1 47940 32640 16320
+12 17 1 47430 32640 16320
+13 17 1 46665 32640 16320
+14 17 1 45900 32640 16320
+15 17 1 45390 32640 16320
+16 17 1 43860 32640 16320
+17 17 1 43350 32640 16320
+18 17 1 42585 32640 16320
+19 17 1 41820 32640 16320
+20 17 1 41310 32640 16320
+21 17 1 40545 32640 16320
+22 17 1 39780 32640 16320
+23 17 1 39015 32640 16320
+24 17 1 38505 32640 16320
+25 17 1 27285 31875 29070
+26 17 1 28050 31875 29580
+27 17 1 29325 31875 30345
+28 17 1 30090 31875 30855
+29 17 1 30600 31875 31110
+30 17 1 31365 31875 31620
+31 17 1 32130 31875 31875
+32 17 1 32640 31875 32385
+33 17 1 33405 31875 32895
+34 17 1 34170 31875 33150
+35 17 1 34680 31875 33660
+36 17 1 35445 31875 33915
+37 17 1 36210 31875 34425
+38 17 1 37485 31875 35190
+39 17 1 38250 31875 35700
+40 17 1 39015 31875 36210
+41 17 1 39525 31875 36465
+42 17 1 40290 31875 36975
+43 17 1 41055 31875 37230
+44 17 1 41565 31875 37740
+45 17 1 42330 31875 38250
+46 17 1 43095 31875 38505
+47 17 1 43605 31875 39015
+48 17 1 44370 31875 39270
+49 17 1 45645 31875 40290
+50 17 1 46410 31875 40545
+51 17 1 47175 31875 41055
+52 17 1 47685 31875 41565
+53 17 1 48450 31875 41820
+54 17 1 49215 31875 42330
+55 17 1 49725 31875 42585
+56 17 1 50490 31875 43095
+57 17 1 51255 31875 43605
+58 17 1 52530 31875 44370
+59 17 1 53295 31875 44625
+60 17 1 54060 31875 45135
+61 17 1 54570 31875 45645
+62 17 1 55335 31875 45900
+63 17 1 56100 31875 46410
+0 18 1 8670 32895 18360
+1 18 1 9435 32895 18870
+2 18 1 10200 32895 19125
+3 18 1 10710 32895 19635
+4 18 1 11475 32895 20145
+5 18 1 12240 32895 20400
+6 18 1 13005 32895 20910
+7 18 1 50745 32640 16320
+8 18 1 49980 32640 16320
+9 18 1 49470 32640 16320
+10 18 1 48705 32640 16320
+11 18 1 47940 32640 16320
+12 18 1 47430 32640 16320
+13 18 1 46665 32640 16320
+14 18 1 45900 32640 16320
+15 18 1 45390 32640 16320
+16 18 1 43860 32640 16320
+17 18 1 43350 32640 16320
+18 18 1 42585 32640 16320
+19 18 1 41820 32640 16320
+20 18 1 41310 32640 16320
+21 18 1 40545 32640 16320
+22 18 1 39780 32640 16320
+23 18 1 39015 32640 16320
+24 18 1 38505 32640 16320
+25 18 1 27285 32895 29580
+26 18 1 28050 32895 29835
+27 18 1 29325 32895 30855
+28 18 1 30090 32895 31110
+29 18 1 30600 32895 31620
+30 18 1 31365 32895 31875
+31 18 1 32130 32895 32385
+32 18 1 32640 32895 32895
+33 18 1 33405 32895 33150
+34 18 1 34170 32895 33660
+35 18 1 34680 32895 33915
+36 18 1 35445 32895 34425
+37 18 1 36210 32895 34935
+38 18 1 37485 32895 35700
+39 18 1 38250 32895 36210
+40 18 1 39015 32895 36465
+41 18 1 39525 32895 36975
+42 18 1 40290 32895 37230
+43 18 1 41055 32895 37740
+44 18 1 41565 32895 38250
+45 18 1 42330 32895 38505
+46 18 1 43095 32895 39015
+47 18 1 43605 32895 39270
+48 18 1 44370 32895 39780
+49 18 1 45645 32895 40545
+50 18 1 46410 32895 41055
+51 18 1 47175 32895 41565
+52 18 1 47685 32895 41820
+53 18 1 48450 32895 42330
+54 18 1 49215 32895 42585
+55 18 1 49725 32895 43095
+56 18 1 50490 32895 43605
+57 18 1 51255 32895 43860
+58 18 1 52530 32895 44625
+59 18 1 53295 32895 45135
+60 18 1 54060 32895 45645
+61 18 1 54570 32895 45900
+62 18 1 55335 32895 46410
+63 18 1 56100 32895 46665
+0 19 1 8670 33915 18870
+1 19 1 9435 33915 19125
+2 19 1 10200 33915 19635
+3 19 1 10710 33915 20145
+4 19 1 11475 33915 20400
+5 19 1 12240 33915 20910
+6 19 1 13005 33915 21165
+7 19 1 50745 32640 16320
+8 19 1 49980 32640 16320
+9 19 1 49470 32640 16320
+10 19 1 48705 32640 16320
+11 19 1 47940 32640 16320
+12 19 1 47430 32640 16320
+13 19 1 46665 32640 16320
+14 19 1 45900 32640 16320
+15 19 1 45390 32640 16320
+16 19 1 43860 32640 16320
+17 19 1 43350 32640 16320
+18 19 1 42585 32640 16320
+19 19 1 41820 32640 16320
+20 19 1 41310 32640 16320
+21 19 1 40545 32640 16320
+22 19 1 39780 32640 16320
+23 19 1 39015 32640 16320
+24 19 1 38505 32640 16320
+25 19 1 27285 34935 30345
+26 19 1 28050 34935 30855
+27 19 1 29325 34935 31620
+28 19 1 30090 34935 31875
+29 19 1 30600 34935 32385
+30 19 1 31365 34935 32895
+31 19 1 32130 34935 33150
+32 19 1 32640 34935 33660
+33 19 1 33405 34935 33915
+34 19 1 34170 34935 34425
+35 19 1 34680 34935 34935
+36 19 1 35445 34935 35190
+37 19 1 36210 34935 35700
+38 19 1 37485 34935 36465
+39 19 1 38250 34935 36975
+40 19 1 39015 34935 37230
+41 19 1 39525 34935 37740
+42 19 1 40290 34935 38250
+43 19 1 41055 34935 38505
+44 19 1 41565 34935 39015
+45 19 1 42330 34935 39270
+46 19 1 43095 34935 39780
+47 19 1 43605 34935 40290
+48 19 1 44370 34935 40545
+49 19 1 45645 34935 41565
+50 19 1 46410 34935 41820
+51 19 1 47175 33915 41820
+52 19 1 47685 33915 42330
+53 19 1 48450 33915 42585
+54 19 1 49215 33915 43095
+55 19 1 49725 33915 43605
+56 19 1 50490 33915 43860
+57 19 1 51255 33915 44370
+58 19 1 52530 33915 45135
+59 19 1 53295 33915 45645
+60 19 1 54060 33915 45900
+61 19 1 54570 33915 46410
+62 19 1 55335 33915 46665
+63 19 1 56100 33915 47175
+0 20 1 8670 35955 19635
+1 20 1 9435 35955 20145
+2 20 1 10200 35955 20400
+3 20 1 10710 35955 20910
+4 20 1 11475 35955 21165
+5 20 1 12240 35955 21675
+6 20 1 13005 35955 22185
+7 20 1 14280 35955 22950
+8 20 1 15045 35955 23205
+9 20 1 15555 35955 23715
+10 20 1 16320 35955 24225
+11 20 1 17085 35955 24480
+12 20 1 17595 35955 24990
+13 20 1 18360 35955 25500
+14 20 1 19125 35955 25755
+15 20 1 19635 35955 26265
+16 20 1 21165 35955 27030
+17 20 1 21675 35955 27540
+18 20 1 22440 35955 27795
+19 20 1 23205 35955 28305
+20 20 1 23715 35955 28560
+21 20 1 24480 35955 29070
+22 20 1 25245 35955 29580
+23 20 1 26010 35955 29835
+24 20 1 26520 35955 30345
+25 20 1 27285 35955 30855
+26 20 1 28050 35955 31110
+27 20 1 29325 35955 31875
+28 20 1 30090 35955 32385
+29 20 1 30600 35955 32895
+30 20 1 31365 35955 33150
+31 20 1 32130 35955 33660
+32 20 1 32640 35955 33915
+33 20 1 33405 35955 34425
+34 20 1 34170 35955 34935
+35 20 1 34680 35955 35190
+36 20 1 35445 35955 35700
+37 20 1 36210 35955 36210
+38 20 1 37485 35955 36975
+39 20 1 38250 35955 37230
+40 20 1 39015 35955 37740
+41 20 1 39525 35955 38250
+42 20 1 40290 35955 38505
+43 20 1 41055 35955 39015
+44 20 1 41565 35955 39270
+45 20 1 42330 35955 39780
+46 20 1 43095 35955 40290
+47 20 1 43605 35955 40545
+48 20 1 44370 35955 41055
+49 20 1 45645 35955 41820
+50 20 1 46410 35955 42330
+51 20 1 47175 35955 42585
+52 20 1 47685 35955 43095
+53 20 1 48450 35955 43605
+54 20 1 49215 35955 43860
+55 20 1 49725 35955 44370
+56 20 1 50490 35955 44625
+57 20 1 51255 35955 45135
+58 20 1 52530 35955 45900
+59 20 1 53295 35955 46410
+60 20 1 54060 35955 46665
+61 20 1 54570 35955 47175
+62 20 1 55335 35955 47685
+63 20 1 56100 35955 47940
+0 21 1 8670 36975 20145
+1 21 1 9435 36975 20400
+2 21 1 10200 36975 20910
+3 21 1 10710 36975 21165
+4 21 1 11475 36975 21675
+5 21 1 12240 36975 22185
+6 21 1 13005 36975 22440
+7 21 1 14280 36975 23205
+8 21 1 15045 36975 23715
+9 21 1 15555 36975 24225
+10 21 1 16320 36975 24480
+11 21 1 17085 36975 24990
+12 21 1 17595 36975 25500
+13 21 1 18360 36975 25755
+14 21 1 19125 36975 26265
+15 21 1 19635 36975 26520
+16 21 1 21165 36975 27540
+17 21 1 21675 36975 27795
+18 21 1 22440 36975 28305
+19 21 1 23205 36975 28560
+20 21 1 23715 36975 29070
+21 21 1 24480 36975 29580
+22 21 1 25245 36975 29835
+23 21 1 26010 36975 30345
+24 21 1 26520 36975 30855
+25 21 1 27285 36975 31110
+26 21 1 28050 36975 31620
+27 21 1 29325 36975 32385
+28 21 1 30090 36975 32895
+29 21 1 30600 36975 33150
+30 21 1 31365 36975 33660
+31 21 1 32130 36975 33915
+32 21 1 32640 36975 34425
+33 21 1 33405 36975 34935
+34 21 1 34170 36975 35190
+35 21 1 34680 36975 35700
+36 21 1 35445 36975 36210
+37 21 1 36210 36975 36465
+38 21 1 37485 36975 37230
+39 21 1 38250 36975 37740
+40 21 1 39015 36975 38250
+41 21 1 39525 36975 38505
+42 21 1 40290 36975 39015
+43 21 1 41055 36975 39270
+44 21 1 41565 36975 39780
+45 21 1 42330 36975 40290
+46 21 1 43095 36975 40545
+47 21 1 43605 36975 41055
+48 21 1 44370 36975 41565
+49 21 1 45645 36975 42330
+50 21 1 46410 36975 42585
+51 21 1 47175 36975 43095
+52 21 1 47685 36975 43605
+53 21 1 48450 36975 43860
+54 21 1 49215 36975 44370
+55 21 1 49725 36975 44625
+56 21 1 50490 36975 45135
+57 21 1 51255 36975 45645
+58 21 1 52530 36975 46410
+59 21 1 53295 36975 46665
+60 21 1 54060 36975 47175
+61 21 1 54570 36975 47685
+62 21 1 55335 36975 47940
+63 21 1 56100 36975 48450
+0 22 1 8670 37995 20400
+1 22 1 9435 37995 20910
+2 22 1 10200 37995 21165
+3 22 1 10710 37995 21675
+4 22 1 11475 37995 22185
+5 22 1 12240 37995 22440
+6 22 1 13005 37995 22950
+7 22 1 14280 37995 23715
+8 22 1 15045 37995 24225
+9 22 1 15555 37995 24480
+10 22 1 16320 37995 24990
+11 22 1 17085 37995 25500
+12 22 1 17595 37995 25755
+13 22 1 18360 37995 26265
+14 22 1 19125 37995 26520
+15 22 1 19635 37995 27030
+16 22 1 20400 37995 27540
+17 22 1 21675 37995 28305
+18 22 1 22440 37995 28560
+19 22 1 23205 37995 29070
+20 22 1 23715 37995 29580
+21 22 1 24480 37995 29835
+22 22 1 25245 37995 30345
+23 22 1 26010 37995 30855
+24 22 1 26520 37995 31110
+25 22 1 27285 37995 31620
+26 22 1 28050 37995 31875
+27 22 1 29325 37995 32895
+28 22 1 30090 37995 33150
+29 22 1 30600 37995 33660
+30 22 1 31365 37995 33915
+31 22 1 32130 37995 34425
+32 22 1 32640 37995 34935
+33 22 1 33405 37995 35190
+34 22 1 34170 37995 35700
+35 22 1 34680 37995 36210
+36 22 1 35445 37995 36465
+37 22 1 36210 37995 36975
+38 22 1 37485 37995 37740
+39 22 1 38250 37995 38250
+40 22 1 39015 37995 38505
+41 22 1 39525 37995 39015
+42 22 1 40290 37995 39270
+43 22 1 41055 37995 39780
+44 22 1 41565 37995 40290
+45 22 1 42330 37995 40545
+46 22 1 43095 37995 41055
+47 22 1 43605 37995 41565
+48 22 1 45135 37995 42330
+49 22 1 45645 37995 42585
+50 22 1 46410 37995 43095
+51 22 1 47175 37995 43605
+52 22 1 47685 37995 43860
+53 22 1 48450 37995 44370
+54 22 1 49215 37995 44625
+55 22 1 49725 37995 45135
+56 22 1 50490 37995 45645
+57 22 1 51255 37995 45900
+58 22 1 52530 37995 46665
+59 22 1 53295 37995 47175
+60 22 1 54060 37995 47685
+61 22 1 54570 37995 47940
+62 22 1 55335 37995 48450
+63 22 1 56100 37995 48960
+0 23 1 8670 39015 20910
+1 23 1 9435 39015 21165
+2 23 1 10200 39015 21675
+3 23 1 10710 39015 22185
+4 23 1 11475 39015 22440
+5 23 1 12240 39015 22950
+6 23 1 13005 39015 23205
+7 23 1 14280 39015 24225
+8 23 1 15045 39015 24480
+9 23 1 15555 39015 24990
+10 23 1 16320 39015 25500
+11 23 1 17085 39015 25755
+12 23 1 17595 39015 26265
+13 23 1 18360 39015 26520
+14 23 1 19125 39015 27030
+15 23 1 19635 39015 27540
+16 23 1 20400 39015 27795
+17 23 1 21675 39015 28560
+18 23 1 22440 39015 29070
+19 23 1 23205 39015 29580
+20 23 1 23715 39015 29835
+21 23 1 24480 39015 30345
+22 23 1 25245 39015 30855
+23 23 1 26010 39015 31110
+24 23 1 26520 39015 31620
+25 23 1 27285 39015 31875
+26 23 1 28050 39015 32385
+27 23 1 29325 39015 33150
+28 23 1 30090 39015 33660
+29 23 1 30600 39015 33915
+30 23 1 31365 39015 34425
+31 23 1 32130 39015 34935
+32 23 1 32640 39015 35190
+33 23 1 33405 39015 35700
+34 23 1 34170 39015 36210
+35 23 1 34680 39015 36465
+36 23 1 35445 39015 36975
+37 23 1 36210 39015 37230
+38 23 1 37485 39015 38250
+39 23 1 38250 39015 38505
+40 23 1 39015 39015 39015
+41 23 1 39525 39015 39270
+42 23 1 40290 39015 39780
+43 23 1 41055 39015 40290
+44 23 1 41565 39015 40545
+45 23 1 42330 39015 41055
+46 23 1 43095 39015 41565
+47 23 1 43605 39015 41820
+48 23 1 45135 39015 42585
+49 23 1 45645 39015 43095
+50 23 1 46410 39015 43605
+51 23 1 47175 39015 43860
+52 23 1 47685 39015 44370
+53 23 1 48450 39015 44625
+54 23 1 49215 39015 45135
+55 23 1 49725 39015 45645
+56 23 1 50490 39015 45900
+57 23 1 51255 39015 46410
+58 23 1 52530 39015 47175
+59 23 1 53295 39015 47685
+60 23 1 54060 39015 47940
+61 23 1 54570 39015 48450
+62 23 1 55335 39015 48960
+63 23 1 56100 39015 49215
+0 24 1 8670 40035 21165
+1 24 1 9435 40035 21675
+2 24 1 10200 40035 22185
+3 24 1 10710 40035 22440
+4 24 1 11475 40035 22950
+5 24 1 12240 40035 23205
+6 24 1 13005 40035 23715
+7 24 1 14280 40035 24480
+8 24 1 15045 40035 24990
+9 24 1 15555 40035 25500
+10 24 1 16320 40035 25755
+11 24 1 17085 40035 26265
+12 24 1 17595 40035 26520
+13 24 1 18360 40035 27030
+14 24 1 19125 40035 27540
+15 24 1 19635 40035 27795
+16 24 1 20400 40035 28305
+17 24 1 21675 40035 29070
+18 24 1 22440 40035 29580
+19 24 1 23205 40035 29835
+20 24 1 23715 40035 30345
+21 24 1 24480 40035 30855
+22 24 1 25245 40035 31110
+23 24 1 26010 40035 31620
+24 24 1 26520 40035 31875
+25 24 1 27285 40035 32385
+26 24 1 28050 40035 32895
+27 24 1 29325 40035 33660
+28 24 1 30090 40035 33915
+29 24 1 30600 40035 34425
+30 24 1 31365 40035 34935
+31 24 1 32130 40035 35190
+32 24 1 32640 40035 35700
+33 24 1 33405 40035 36210
+34 24 1 34170 40035 36465
+35 24 1 34680 40035 36975
+36 24 1 35445 40035 37230
+37 24 1 36210 40035 37740
+38 24 1 37485 40035 38505
+39 24 1 38250 40035 39015
+40 24 1 39015 40035 39270
+41 24 1 39525 40035 39780
+42 24 1 40290 40035 40290
+43 24 1 41055 40035 40545
+44 24 1 41565 40035 41055
+45 24 1 42330 40035 41565
+46 24 1 43095 40035 41820
+47 24 1 43605 40035 42330
+48 24 1 45135 40035 43095
+49 24 1 45645 40035 43605
+50 24 1 46410 40035 43860
+51 24 1 47175 40035 44370
+52 24 1 47685 40035 44625
+53 24 1 48450 40035 45135
+54 24 1 49215 40035 45645
+55 24 1 49725 40035 45900
+56 24 1 50490 40035 46410
+57 24 1 51255 40035 46665
+58 24 1 52530 40035 47685
+59 24 1 53295 40035 47940
+60 24 1 54060 40035 48450
+61 24 1 54570 40035 48960
+62 24 1 55335 40035 49215
+63 24 1 56100 40035 49725
+0 25 1 8670 41055 21675
+1 25 1 9435 41055 22185
+2 25 1 10200 41055 22440
+3 25 1 10710 41055 22950
+4 25 1 11475 41055 23205
+5 25 1 12240 41055 23715
+6 25 1 13005 41055 24225
+7 25 1 14280 41055 24990
+8 25 1 15045 41055 25500
+9 25 1 15555 41055 25755
+10 25 1 16320 41055 26265
+11 25 1 17085 41055 26520
+12 25 1 17595 41055 27030
+13 25 1 18360 41055 27540
+14 25 1 19125 41055 27795
+15 25 1 19635 41055 28305
+16 25 1 20400 41055 28560
+17 25 1 21675 41055 29580
+18 25 1 22440 41055 29835
+19 25 1 23205 41055 30345
+20 25 1 23715 41055 30855
+21 25 1 24480 41055 31110
+22 25 1 25245 41055 31620
+23 25 1 26010 41055 31875
+24 25 1 26520 41055 32385
+25 25 1 27285 41055 32895
+26 25 1 28050 41055 33150
+27 25 1 29325 41055 33915
+28 25 1 30090 41055 34425
+29 25 1 30600 41055 34935
+30 25 1 31365 41055 35190
+31 25 1 32130 41055 35700
+32 25 1 32640 41055 36210
+33 25 1 33405 41055 36465
+34 25 1 34170 41055 36975
+35 25 1 34680 41055 37230
+36 25 1 35445 41055 37740
+37 25 1 36210 41055 38250
+38 25 1 37485 41055 39015
+39 25 1 38250 41055 39270
+40 25 1 39015 41055 39780
+41 25 1 39525 41055 40290
+42 25 1 40290 41055 40545
+43 25 1 41055 41055 41055
+44 25 1 41565 41055 41565
+45 25 1 42330 41055 41820
+46 25 1 43095 41055 42330
+47 25 1 43605 41055 42585
+48 25 1 45135 41055 43605
+49 25 1 45645 41055 43860
+50 25 1 46410 41055 44370
+51 25 1 47175 41055 44625
+52 25 1 47685 41055 45135
+53 25 1 48450 41055 45645
+54 25 1 49215 41055 45900
+55 25 1 49725 41055 46410
+56 25 1 50490 41055 46665
+57 25 1 51255 41055 47175
+58 25 1 52530 41055 47940
+59 25 1 53295 41055 48450
+60 25 1 54060 41055 48960
+61 25 1 54570 41055 49215
+62 25 1 55335 41055 49725
+63 25 1 56100 41055 49980
+0 26 1 8670 42075 22185
+1 26 1 9435 42075 22440
+2 26 1 10200 42075 22950
+3 26 1 10710 42075 23205
+4 26 1 11475 42075 23715
+5 26 1 12240 42075 24225
+6 26 1 13005 42075 24480
+7 26 1 14280 42075 25500
+8 26 1 15045 42075 25755
+9 26 1 15555 42075 26265
+10 26 1 16320 42075 26520
+11 26 1 17085 42075 27030
+12 26 1 17595 42075 27540
+13 26 1 18360 42075 27795
+14 26 1 19125 42075 28305
+15 26 1 19635 42075 28560
+16 26 1 20400 42075 29070
+17 26 1 21675 42075 29835
+18 26 1 22440 42075 30345
+19 26 1 23205 42075 30855
+20 26 1 23715 42075 31110
+21 26 1 24480 42075 31620
+22 26 1 25245 42075 31875
+23 26 1 26010 42075 32385
+24 26 1 26520 42075 32895
+25 26 1 27285 42075 33150
+26 26 1 28050 42075 33660
+27 26 1 29325 42075 34425
+28 26 1 30090 42075 34935
+29 26 1 30600 42075 35190
+30 26 1 31365 42075 35700
+31 26 1 32130 42075 36210
+32 26 1 32640 42075 36465
+33 26 1 33405 42075 36975
+34 26 1 34170 42075 37230
+35 26 1 34680 42075 37740
+36 26 1 35445 42075 38250
+37 26 1 36210 42075 38505
+38 26 1 37485 42075 39270
+39 26 1 38250 42075 39780
+40 26 1 39015 42075 40290
+41 26 1 39525 42075 40545
+42 26 1 40290 42075 41055
+43 26 1 41055 42075 41565
+44 26 1 41565 42075 41820
+45 26 1 42330 42075 42330
+46 26 1 43095 42075 42585
+47 26 1 43605 42075 43095
+48 26 1 45135 42075 43860
+49 26 1 45645 42075 44370
+50 26 1 46410 42075 44625
+51 26 1 47175 42075 45135
+52 26 1 47685 42075 45645
+53 26 1 48450 42075 45900
+54 26 1 49215 42075 46410
+55 26 1 49725 42075 46665
+56 26 1 50490 42075 47175
+57 26 1 51255 42075 47685
+58 26 1 52530 42075 48450
+59 26 1 53295 42075 48960
+60 26 1 54060 42075 49215
+61 26 1 54570 42075 49725
+62 26 1 55335 42075 49980
+63 26 1 56100 42075 50490
+0 27 1 8670 43350 22440
+1 27 1 9435 43350 22950
+2 27 1 10200 43350 23205
+3 27 1 10710 43350 23715
+4 27 1 11475 43350 24225
+5 27 1 12240 43350 24480
+6 27 1 13005 43350 24990
+7 27 1 14280 43350 25755
+8 27 1 15045 43350 26265
+9 27 1 15555 43350 26520
+10 27 1 16320 43350 27030
+11 27 1 17085 43350 27540
+12 27 1 17595 43350 27795
+13 27 1 18360 43350 28305
+14 27 1 19125 43350 28560
+15 27 1 19635 43350 29070
+16 27 1 20400 43350 29580
+17 27 1 21675 43350 30345
+18 27 1 22440 43350 30855
+19 27 1 23205 43350 31110
+20 27 1 23715 43350 31620
+21 27 1 24480 43350 31875
+22 27 1 25245 43350 32385
+23 27 1 26010 43350 32895
+24 27 1 26520 43350 33150
+25 27 1 27285 43350 33660
+26 27 1 28050 43350 33915
+27 27 1 29325 43350 34935
+28 27 1 30090 43350 35190
+29 27 1 30600 43350 35700
+30 27 1 31365 43350 36210
+31 27 1 32130 43350 36465
+32 27 1 32640 43350 36975
+33 27 1 33405 43350 37230
+34 27 1 34170 43350 37740
+35 27 1 34680 43350 38250
+36 27 1 35445 43350 38505
+37 27 1 36210 43350 39015
+38 27 1 37485 43350 39780
+39 27 1 38250 43350 40290
+40 27 1 39015 43350 40545
+41 27 1 39525 43350 41055
+42 27 1 40290 43350 41565
+43 27 1 41055 43350 41820
+44 27 1 41565 43350 42330
+45 27 1 42330 43350 42585
+46 27 1 43095 43350 43095
+47 27 1 43605 43350 43605
+48 27 1 45135 43350 44370
+49 27 1 45645 43350 44625
+50 27 1 46410 43350 45135
+51 27 1 47175 43350 45645
+52 27 1 47685 43350 45900
+53 27 1 48450 43350 46410
+54 27 1 49215 43350 46665
+55 27 1 49725 43350 47175
+56 27 1 50490 43350 47685
+57 27 1 51255 43350 47940
+58 27 1 52530 43350 48960
+59 27 1 53295 43350 49215
+60 27 1 54060 43350 49725
+61 27 1 54570 43350 49980
+62 27 1 55335 43350 50490
+63 27 1 56100 43350 51000
+0 28 1 8670 44370 22950
+1 28 1 9435 44370 23205
+2 28 1 10200 44370 23715
+3 28 1 10710 44370 24225
+4 28 1 11475 44370 24480
+5 28 1 12240 44370 24990
+6 28 1 13005 44370 25500
+7 28 1 14280 44370 26265
+8 28 1 15045 44370 26520
+9 28 1 15555 44370 27030
+10 28 1 16320 44370 27540
+11 28 1 17085 44370 27795
+12 28 1 17595 44370 28305
+13 28 1 18360 44370 28560
+14 28 1 19125 44370 29070
+15 28 1 19635 44370 29580
+16 28 1 20400 44370 29835
+17 28 1 21675 44370 30855
+18 28 1 22440 44370 31110
+19 28 1 23205 44370 31620
+20 28 1 23715 44370 31875
+21 28 1 24480 44370 32385
+22 28 1 25245 44370 32895
+23 28 1 26010 44370 33150
+24 28 1 26520 44370 33660
+25 28 1 27285 44370 33915
+26 28 1 28050 44370 34425
+27 28 1 29325 44370 35190
+28 28 1 30090 44370 35700
+29 28 1 30600 44370 36210
+30 28 1 31365 44370 36465
+31 28 1 32130 44370 36975
+32 28 1 32640 44370 37230
+33 28 1 33405 44370 37740
+34 28 1 34170 44370 38250
+35 28 1 34680 44370 38505
+36 28 1 35445 44370 39015
+37 28 1 36210 44370 39270
+38 28 1 37485 44370 40290
+39 28 1 38250 44370 40545
+40 28 1 39015 44370 41055
+41 28 1 39525 44370 41565
+42 28 1 40290 44370 41820
+43 28 1 41055 44370 42330
+44 28 1 41565 44370 42585
+45 28 1 42330 44370 43095
+46 28 1 43095 44370 43605
+47 28 1 43605 44370 43860
+48 28 1 45135 44370 44625
+49 28 1 45645 44370 45135
+50 28 1 46410 44370 45645
+51 28 1 47175 44370 45900
+52 28 1 47685 44370 46410
+53 28 1 48450 44370 46665
+54 28 1 49215 44370 47175
+55 28 1 49725 44370 47685
+56 28 1 50490 44370 47940
+57 28 1 51255 44370 48450
+58 28 1 52530 44370 49215
+59 28 1 53295 44370 49725
+60 28 1 54060 44370 49980
+61 28 1 54570 44370 50490
+62 28 1 55335 44370 51000
+63 28 1 56100 44370 51255
+0 29 1 8670 46410 23715
+1 29 1 9435 46410 24225
+2 29 1 10200 46410 24480
+3 29 1 10710 46410 24990
+4 29 1 11475 46410 25500
+5 29 1 12240 46410 25755
+6 29 1 13005 46410 26265
+7 29 1 14280 46410 27030
+8 29 1 15045 45390 27030
+9 29 1 15555 45390 27540
+10 29 1 16320 45390 27795
+11 29 1 17085 45390 28305
+12 29 1 17595 45390 28560
+13 29 1 18360 45390 29070
+14 29 1 19125 45390 29580
+15 29 1 19635 45390 29835
+16 29 1 20400 45390 30345
+17 29 1 21675 45390 31110
+18 29 1 22440 45390 31620
+19 29 1 23205 45390 31875
+20 29 1 23715 45390 32385
+21 29 1 24480 45390 32895
+22 29 1 25245 45390 33150
+23 29 1 26010 45390 33660
+24 29 1 26520 45390 33915
+25 29 1 27285 45390 34425
+26 29 1 28050 45390 34935
+27 29 1 29325 45390 35700
+28 29 1 30090 45390 36210
+29 29 1 30600 45390 36465
+30 29 1 31365 45390 36975
+31 29 1 32130 45390 37230
+32 29 1 32640 45390 37740
+33 29 1 33405 45390 38250
+34 29 1 34170 45390 38505
+35 29 1 34680 45390 39015
+36 29 1 35445 45390 39270
+37 29 1 36210 45390 39780
+38 29 1 37485 45390 40545
+39 29 1 38250 45390 41055
+40 29 1 39015 45390 41565
+41 29 1 39525 45390 41820
+42 29 1 40290 45390 42330
+43 29 1 41055 45390 42585
+44 29 1 41565 45390 43095
+45 29 1 42330 45390 43605
+46 29 1 43095 45390 43860
+47 29 1 43605 45390 44370
+48 29 1 45135 45390 45135
+49 29 1 45645 45390 45645
+50 29 1 46410 45390 45900
+51 29 1 47175 45390 46410
+52 29 1 47685 45390 46665
+53 29 1 48450 45390 47175
+54 29 1 49215 45390 47685
+55 29 1 49725 45390 47940
+56 29 1 50490 45390 48450
+57 29 1 51255 46410 49215
+58 29 1 52530 46410 49980
+59 29 1 53295 46410 50490
+60 29 1 54060 46410 51000
+61 29 1 54570 46410 51255
+62 29 1 55335 46410 51765
+63 29 1 56100 46410 52020
+0 30 1 8670 47430 24225
+1 30 1 9435 47430 24480
+2 30 1 10200 47430 24990
+3 30 1 10710 47430 25500
+4 30 1 11475 47430 25755
+5 30 1 12240 47430 26265
+6 30 1 13005 47430 26520
+7 30 1 14280 47430 27540
+8 30 1 15045 47430 27795
+9 30 1 15555 47430 28305
+10 30 1 16320 47430 28560
+11 30 1 17085 47430 29070
+12 30 1 17595 47430 29580
+13 30 1 18360 47430 29835
+14 30 1 19125 47430 30345
+15 30 1 19635 47430 30855
+16 30 1 20400 47430 31110
+17 30 1 21675 47430 31875
+18 30 1 22440 47430 32385
+19 30 1 23205 47430 32895
+20 30 1 23715 47430 33150
+21 30 1 24480 47430 33660
+22 30 1 25245 47430 33915
+23 30 1 26010 47430 34425
+24 30 1 26520 47430 34935
+25 30 1 27285 47430 35190
+26 30 1 28050 47430 35700
+27 30 1 29325 47430 36465
+28 30 1 30090 47430 36975
+29 30 1 30600 47430 37230
+30 30 1 31365 47430 37740
+31 30 1 32130 47430 38250
+32 30 1 32640 47430 38505
+33 30 1 33405 47430 39015
+34 30 1 34170 47430 39270
+35 30 1 34680 47430 39780
+36 30 1 35445 47430 40290
+37 30 1 36210 47430 40545
+38 30 1 37485 47430 41565
+39 30 1 38250 47430 41820
+40 30 1 39015 47430 42330
+41 30 1 39525 47430 42585
+42 30 1 40290 47430 43095
+43 30 1 41055 47430 43605
+44 30 1 41565 47430 43860
+45 30 1 42330 47430 44370
+46 30 1 43095 47430 44625
+47 30 1 43605 47430 45135
+48 30 1 45135 47430 45900
+49 30 1 45645 47430 46410
+50 30 1 46410 47430 46665
+51 30 1 47175 47430 47175
+52 30 1 47685 47430 47685
+53 30 1 48450 47430 47940
+54 30 1 49215 47430 48450
+55 30 1 49725 47430 48960
+56 30 1 50490 47430 49215
+57 30 1 51255 47430 49725
+58 30 1 52530 47430 50490
+59 30 1 53295 47430 51000
+60 30 1 54060 47430 51255
+61 30 1 54570 47430 51765
+62 30 1 55335 47430 52020
+63 30 1 56100 47430 52530
+0 31 1 8670 48450 24480
+1 31 1 9435 48450 24990
+2 31 1 10200 48450 25500
+3 31 1 10710 48450 25755
+4 31 1 11475 48450 26265
+5 31 1 12240 48450 26520
+6 31 1 13005 48450 27030
+7 31 1 14280 48450 27795
+8 31 1 15045 48450 28305
+9 31 1 15555 48450 28560
+10 31 1 16320 48450 29070
+11 31 1 17085 48450 29580
+12 31 1 17595 48450 29835
+13 31 1 18360 48450 30345
+14 31 1 19125 48450 30855
+15 31 1 19635 48450 31110
+16 31 1 20400 48450 31620
+17 31 1 21675 48450 32385
+18 31 1 22440 48450 32895
+19 31 1 23205 48450 33150
+20 31 1 23715 48450 33660
+21 31 1 24480 48450 33915
+22 31 1 25245 48450 34425
+23 31 1 26010 48450 34935
+24 31 1 26520 48450 35190
+25 31 1 27285 48450 35700
+26 31 1 28050 48450 36210
+27 31 1 29325 48450 36975
+28 31 1 30090 48450 37230
+29 31 1 30600 48450 37740
+30 31 1 31365 48450 38250
+31 31 1 32130 48450 38505
+32 31 1 32640 48450 39015
+33 31 1 33405 48450 39270
+34 31 1 34170 48450 39780
+35 31 1 34680 48450 40290
+36 31 1 35445 48450 40545
+37 31 1 36210 48450 41055
+38 31 1 37485 48450 41820
+39 31 1 38250 48450 42330
+40 31 1 39015 48450 42585
+41 31 1 39525 48450 43095
+42 31 1 40290 48450 43605
+43 31 1 41055 48450 43860
+44 31 1 41565 48450 44370
+45 31 1 42330 48450 44625
+46 31 1 43095 48450 45135
+47 31 1 43605 48450 45645
+48 31 1 45135 48450 46410
+49 31 1 45645 48450 46665
+50 31 1 46410 48450 47175
+51 31 1 47175 48450 47685
+52 31 1 47685 48450 47940
+53 31 1 48450 48450 48450
+54 31 1 49215 48450 48960
+55 31 1 49725 48450 49215
+56 31 1 50490 48450 49725
+57 31 1 51255 48450 49980
+58 31 1 52530 48450 51000
+59 31 1 53295 48450 51255
+60 31 1 54060 48450 51765
+61 31 1 54570 48450 52020
+62 31 1 55335 48450 52530
+63 31 1 56100 48450 53040
+0 32 1 8670 49470 24990
+1 32 1 9435 49470 25500
+2 32 1 10200 49470 25755
+3 32 1 10710 49470 26265
+4 32 1 11475 49470 26520
+5 32 1 12240 49470 27030
+6 32 1 13005 49470 27540
+7 32 1 13515 49470 27795
+8 32 1 15045 49470 28560
+9 32 1 15555 49470 29070
+10 32 1 16320 49470 29580
+11 32 1 17085 49470 29835
+12 32 1 17595 49470 30345
+13 32 1 18360 49470 30855
+14 32 1 19125 49470 31110
+15 32 1 19635 49470 31620
+16 32 1 20400 49470 31875
+17 32 1 21675 49470 32895
+18 32 1 22440 49470 33150
+19 32 1 23205 49470 33660
+20 32 1 23715 49470 33915
+21 32 1 24480 49470 34425
+22 32 1 25245 49470 34935
+23 32 1 26010 49470 35190
+24 32 1 26520 49470 35700
+25 32 1 27285 49470 36210
+26 32 1 28050 49470 36465
+27 32 1 29325 49470 37230
+28 32 1 30090 49470 37740
+29 32 1 30600 49470 38250
+30 32 1 31365 49470 38505
+31 32 1 32130 49470 39015
+32 32 1 32640 49470 39270
+33 32 1 33405 49470 39780
+34 32 1 34170 49470 40290
+35 32 1 34680 49470 40545
+36 32 1 35445 49470 41055
+37 32 1 36210 49470 41565
+38 32 1 37485 49470 42330
+39 32 1 38250 49470 42585
+40 32 1 39015 49470 43095
+41 32 1 39525 49470 43605
+42 32 1 40290 49470 43860
+43 32 1 41055 49470 44370
+44 32 1 41565 49470 44625
+45 32 1 42330 49470 45135
+46 32 1 43095 49470 45645
+47 32 1 43605 49470 45900
+48 32 1 45135 49470 46665
+49 32 1 45645 49470 47175
+50 32 1 46410 49470 47685
+51 32 1 47175 49470 47940
+52 32 1 47685 49470 48450
+53 32 1 48450 49470 48960
+54 32 1 49215 49470 49215
+55 32 1 49725 49470 49725
+56 32 1 50490 49470 49980
+57 32 1 52020 49470 51000
+58 32 1 52530 49470 51255
+59 32 1 53295 49470 51765
+60 32 1 54060 49470 52020
+61 32 1 54570 49470 52530
+62 32 1 55335 49470 53040
+63 32 1 56100 49470 53295
+0 33 1 8670 50490 25500
+1 33 1 9435 50490 25755
+2 33 1 10200 50490 26265
+3 33 1 10710 50490 26520
+4 33 1 11475 50490 27030
+5 33 1 12240 50490 27540
+6 33 1 13005 50490 27795
+7 33 1 13515 50490 28305
+8 33 1 15045 50490 29070
+9 33 1 15555 50490 29580
+10 33 1 16320 50490 29835
+11 33 1 17085 50490 30345
+12 33 1 17595 50490 30855
+13 33 1 18360 50490 31110
+14 33 1 19125 50490 31620
+15 33 1 19635 50490 31875
+16 33 1 20400 50490 32385
+17 33 1 21675 50490 33150
+18 33 1 22440 50490 33660
+19 33 1 23205 50490 33915
+20 33 1 23715 50490 34425
+21 33 1 24480 50490 34935
+22 33 1 25245 50490 35190
+23 33 1 26010 50490 35700
+24 33 1 26520 50490 36210
+25 33 1 27285 50490 36465
+26 33 1 28050 50490 36975
+27 33 1 29325 50490 37740
+28 33 1 30090 50490 38250
+29 33 1 30600 50490 38505
+30 33 1 31365 50490 39015
+31 33 1 32130 50490 39270
+32 33 1 32640 50490 39780
+33 33 1 33405 50490 40290
+34 33 1 34170 50490 40545
+35 33 1 34680 50490 41055
+36 33 1 35445 50490 41565
+37 33 1 36210 50490 41820
+38 33 1 37485 50490 42585
+39 33 1 38250 50490 43095
+40 33 1 39015 50490 43605
+41 33 1 39525 50490 43860
+42 33 1 40290 50490 44370
+43 33 1 41055 50490 44625
+44 33 1 41565 50490 45135
+45 33 1 42330 50490 45645
+46 33 1 43095 50490 45900
+47 33 1 43605 50490 46410
+48 33 1 45135 50490 47175
+49 33 1 45645 50490 47685
+50 33 1 46410 50490 47940
+51 33 1 47175 50490 48450
+52 33 1 47685 50490 48960
+53 33 1 48450 50490 49215
+54 33 1 49215 50490 49725
+55 33 1 49725 50490 49980
+56 33 1 50490 50490 50490
+57 33 1 52020 50490 51255
+58 33 1 52530 50490 51765
+59 33 1 53295 50490 52020
+60 33 1 54060 50490 52530
+61 33 1 54570 50490 53040
+62 33 1 55335 50490 53295
+63 33 1 56100 50490 53805
+0 34 1 8670 51510 25755
+1 34 1 9435 51510 26265
+2 34 1 10200 51510 26520
+3 34 1 10710 51510 27030
+4 34 1 11475 51510 27540
+5 34 1 12240 51510 27795
+6 34 1 13005 51510 28305
+7 34 1 13515 51510 28560
+8 34 1 15045 51510 29580
+9 34 1 15555 51510 29835
+10 34 1 16320 51510 30345
+11 34 1 17085 51510 30855
+12 34 1 17595 51510 31110
+13 34 1 18360 51510 31620
+14 34 1 19125 51510 31875
+15 34 1 19635 51510 32385
+16 34 1 20400 51510 32895
+17 34 1 21675 51510 33660
+18 34 1 22440 51510 33915
+19 34 1 23205 51510 34425
+20 34 1 23715 51510 34935
+21 34 1 24480 51510 35190
+22 34 1 25245 51510 35700
+23 34 1 26010 51510 36210
+24 34 1 26520 51510 36465
+25 34 1 27285 51510 36975
+26 34 1 28050 51510 37230
+27 34 1 29325 51510 38250
+28 34 1 30090 51510 38505
+29 34 1 30600 51510 39015
+30 34 1 31365 51510 39270
+31 34 1 32130 51510 39780
+32 34 1 32640 51510 40290
+33 34 1 33405 51510 40545
+34 34 1 34170 51510 41055
+35 34 1 34680 51510 41565
+36 34 1 35445 51510 41820
+37 34 1 36210 51510 42330
+38 34 1 37485 51510 43095
+39 34 1 38250 51510 43605
+40 34 1 39015 51510 43860
+41 34 1 39525 51510 44370
+42 34 1 40290 51510 44625
+43 34 1 41055 51510 45135
+44 34 1 41565 51510 45645
+45 34 1 42330 51510 45900
+46 34 1 43095 51510 46410
+47 34 1 43605 51510 46665
+48 34 1 45135 51510 47685
+49 34 1 45645 51510 47940
+50 34 1 46410 51510 48450
+51 34 1 47175 51510 48960
+52 34 1 47685 51510 49215
+53 34 1 48450 51510 49725
+54 34 1 49215 51510 49980
+55 34 1 49725 51510 50490
+56 34 1 50490 51510 51000
+57 34 1 52020 51510 51765
+58 34 1 52530 51510 52020
+59 34 1 53295 51510 52530
+60 34 1 54060 51510 53040
+61 34 1 54570 51510 53295
+62 34 1 55335 51510 53805
+63 34 1 56100 51510 54315
+0 35 1 8670 52530 26265
+1 35 1 9435 52530 26520
+2 35 1 10200 52530 27030
+3 35 1 10710 52530 27540
+4 35 1 11475 52530 27795
+5 35 1 12240 52530 28305
+6 35 1 13005 52530 28560
+7 35 1 13515 52530 29070
+8 35 1 15045 52530 29835
+9 35 1 15555 52530 30345
+10 35 1 16320 52530 30855
+11 35 1 17085 52530 31110
+12 35 1 17595 52530 31620
+13 35 1 18360 52530 31875
+14 35 1 19125 52530 32385
+15 35 1 19635 52530 32895
+16 35 1 20400 52530 33150
+17 35 1 21675 52530 33915
+18 35 1 22440 52530 34425
+19 35 1 23205 52530 34935
+20 35 1 23715 52530 35190
+21 35 1 24480 52530 35700
+22 35 1 25245 52530 36210
+23 35 1 26010 52530 36465
+24 35 1 26520 52530 36975
+25 35 1 27285 52530 37230
+26 35 1 28050 52530 37740
+27 35 1 29325 52530 38505
+28 35 1 30090 52530 39015
+29 35 1 30600 52530 39270
+30 35 1 31365 52530 39780
+31 35 1 32130 52530 40290
+32 35 1 32640 52530 40545
+33 35 1 33405 52530 41055
+34 35 1 34170 52530 41565
+35 35 1 34680 52530 41820
+36 35 1 35445 52530 42330
+37 35 1 36210 52530 42585
+38 35 1 37485 52530 43605
+39 35 1 38250 52530 43860
+40 35 1 39015 52530 44370
+41 35 1 39525 52530 44625
+42 35 1 40290 52530 45135
+43 35 1 41055 52530 45645
+44 35 1 41565 52530 45900
+45 35 1 42330 52530 46410
+46 35 1 43095 52530 46665
+47 35 1 43605 52530 47175
+48 35 1 45135 52530 47940
+49 35 1 45645 52530 48450
+50 35 1 46410 52530 48960
+51 35 1 47175 52530 49215
+52 35 1 47685 52530 49725
+53 35 1 48450 52530 49980
+54 35 1 49215 52530 50490
+55 35 1 49725 52530 51000
+56 35 1 50490 52530 51255
+57 35 1 52020 52530 52020
+58 35 1 52530 52530 52530
+59 35 1 53295 52530 53040
+60 35 1 54060 52530 53295
+61 35 1 54570 52530 53805
+62 35 1 55335 52530 54315
+63 35 1 56100 52530 54570
+0 36 1 8670 53550 26520
+1 36 1 9435 53550 27030
+2 36 1 10200 53550 27540
+3 36 1 10710 53550 27795
+4 36 1 11475 53550 28305
+5 36 1 12240 53550 28560
+6 36 1 13005 53550 29070
+7 36 1 13515 53550 29580
+8 36 1 15045 53550 30345
+9 36 1 15555 53550 30855
+10 36 1 16320 53550 31110
+11 36 1 17085 53550 31620
+12 36 1 17595 53550 31875
+13 36 1 18360 53550 32385
+14 36 1 19125 53550 32895
+15 36 1 19635 53550 33150
+16 36 1 20400 53550 33660
+17 36 1 21675 53550 34425
+18 36 1 22440 53550 34935
+19 36 1 23205 53550 35190
+20 36 1 23715 53550 35700
+21 36 1 24480 53550 36210
+22 36 1 25245 53550 36465
+23 36 1 26010 53550 36975
+24 36 1 26520 53550 37230
+25 36 1 27285 53550 37740
+26 36 1 28050 53550 38250
+27 36 1 29325 53550 39015
+28 36 1 30090 53550 39270
+29 36 1 30600 53550 39780
+30 36 1 31365 53550 40290
+31 36 1 32130 53550 40545
+32 36 1 32640 53550 41055
+33 36 1 33405 53550 41565
+34 36 1 34170 53550 41820
+35 36 1 34680 53550 42330
+36 36 1 35445 53550 42585
+37 36 1 36210 53550 43095
+38 36 1 37485 53550 43860
+39 36 1 38250 53550 44370
+40 36 1 39015 53550 44625
+41 36 1 39525 53550 45135
+42 36 1 40290 53550 45645
+43 36 1 41055 53550 45900
+44 36 1 41565 53550 46410
+45 36 1 42330 53550 46665
+46 36 1 43095 53550 47175
+47 36 1 43605 53550 47685
+48 36 1 45135 53550 48450
+49 36 1 45645 53550 48960
+50 36 1 46410 53550 49215
+51 36 1 47175 53550 49725
+52 36 1 47685 53550 49980
+53 36 1 48450 53550 50490
+54 36 1 49215 53550 51000
+55 36 1 49725 53550 51255
+56 36 1 50490 53550 51765
+57 36 1 52020 53550 52530
+58 36 1 52530 53550 53040
+59 36 1 53295 53550 53295
+60 36 1 54060 53550 53805
+61 36 1 54570 53550 54315
+62 36 1 55335 53550 54570
+63 36 1 56100 53550 55080
+0 37 1 8670 54570 27030
+1 37 1 9435 54570 27540
+2 37 1 10200 54570 27795
+3 37 1 10710 54570 28305
+4 37 1 11475 54570 28560
+5 37 1 12240 54570 29070
+6 37 1 13005 54570 29580
+7 37 1 13515 54570 29835
+8 37 1 15045 54570 30855
+9 37 1 15555 54570 31110
+10 37 1 16320 54570 31620
+11 37 1 17085 54570 31875
+12 37 1 17595 54570 32385
+13 37 1 18360 54570 32895
+14 37 1 19125 54570 33150
+15 37 1 19635 54570 33660
+16 37 1 20400 54570 33915
+17 37 1 21675 54570 34935
+18 37 1 22440 54570 35190
+19 37 1 23205 54570 35700
+20 37 1 23715 54570 36210
+21 37 1 24480 54570 36465
+22 37 1 25245 54570 36975
+23 37 1 26010 54570 37230
+24 37 1 26520 54570 37740
+25 37 1 27285 54570 38250
+26 37 1 28050 54570 38505
+27 37 1 29325 54570 39270
+28 37 1 30090 54570 39780
+29 37 1 30600 54570 40290
+30 37 1 31365 54570 40545
+31 37 1 32130 54570 41055
+32 37 1 32640 54570 41565
+33 37 1 33405 54570 41820
+34 37 1 34170 54570 42330
+35 37 1 34680 54570 42585
+36 37 1 35445 54570 43095
+37 37 1 36210 54570 43605
+38 37 1 37485 54570 44370
+39 37 1 38250 54570 44625
+40 37 1 39015 54570 45135
+41 37 1 39525 54570 45645
+42 37 1 40290 54570 45900
+43 37 1 41055 54570 46410
+44 37 1 41565 54570 46665
+45 37 1 42330 54570 47175
+46 37 1 43095 54570 47685
+47 37 1 43605 54570 47940
+48 37 1 45135 54570 48960
+49 37 1 45645 54570 49215
+50 37 1 46410 54570 49725
+51 37 1 47175 54570 49980
+52 37 1 47685 54570 50490
+53 37 1 48450 54570 51000
+54 37 1 49215 54570 51255
+55 37 1 49725 54570 51765
+56 37 1 50490 54570 52020
+57 37 1 52020 54570 53040
+58 37 1 52530 54570 53295
+59 37 1 53295 54570 53805
+60 37 1 54060 54570 54315
+61 37 1 54570 54570 54570
+62 37 1 55335 54570 55080
+63 37 1 56100 54570 55335
+0 38 1 8670 56610 27795
+1 38 1 9435 56610 28305
+2 38 1 10200 56610 28560
+3 38 1 10710 56610 29070
+4 38 1 11475 55590 29070
+5 38 1 12240 55590 29580
+6 38 1 13005 55590 29835
+7 38 1 13515 55590 30345
+8 38 1 15045 55590 31110
+9 38 1 15555 55590 31620
+10 38 1 16320 55590 31875
+11 38 1 17085 55590 32385
+12 38 1 17595 55590 32895
+13 38 1 18360 55590 33150
+14 38 1 19125 55590 33660
+15 38 1 19635 55590 33915
+16 38 1 20400 55590 34425
+17 38 1 21675 55590 35190
+18 38 1 22440 55590 35700
+19 38 1 23205 55590 36210
+20 38 1 23715 55590 36465
+21 38 1 24480 55590 36975
+22 38 1 25245 55590 37230
+23 38 1 26010 55590 37740
+24 38 1 26520 55590 38250
+25 38 1 27285 55590 38505
+26 38 1 28050 55590 39015
+27 38 1 29325 55590 39780
+28 38 1 30090 55590 40290
+29 38 1 30600 55590 40545
+30 38 1 31365 55590 41055
+31 38 1 32130 55590 41565
+32 38 1 32640 55590 41820
+33 38 1 33405 55590 42330
+34 38 1 34170 55590 42585
+35 38 1 34680 55590 43095
+36 38 1 35445 55590 43605
+37 38 1 36210 55590 43860
+38 38 1 37485 55590 44625
+39 38 1 38250 55590 45135
+40 38 1 39015 55590 45645
+41 38 1 39525 55590 45900
+42 38 1 40290 55590 46410
+43 38 1 41055 55590 46665
+44 38 1 41565 55590 47175
+45 38 1 42330 55590 47685
+46 38 1 43095 55590 47940
+47 38 1 43605 55590 48450
+48 38 1 45135 55590 49215
+49 38 1 45645 55590 49725
+50 38 1 46410 55590 49980
+51 38 1 47175 55590 50490
+52 38 1 47685 55590 51000
+53 38 1 48450 55590 51255
+54 38 1 49215 55590 51765
+55 38 1 49725 55590 52020
+56 38 1 50490 55590 52530
+57 38 1 52020 55590 53295
+58 38 1 52530 55590 53805
+59 38 1 53295 55590 54315
+60 38 1 54060 55590 54570
+61 38 1 54570 56610 55335
+62 38 1 55335 56610 55845
+63 38 1 56100 56610 56355
+0 39 1 8670 57630 28305
+1 39 1 9435 57630 28560
+2 39 1 10200 57630 29070
+3 39 1 10710 57630 29580
+4 39 1 11475 57630 29835
+5 39 1 12240 57630 30345
+6 39 1 13005 57630 30855
+7 39 1 13515 57630 31110
+8 39 1 15045 57630 31875
+9 39 1 15555 57630 32385
+10 39 1 16320 57630 32895
+11 39 1 17085 57630 33150
+12 39 1 17595 57630 33660
+13 39 1 18360 57630 33915
+14 39 1 19125 56610 33915
+15 39 1 19635 56610 34425
+16 39 1 20400 56610 34935
+17 39 1 21675 56610 35700
+18 39 1 22440 56610 36210
+19 39 1 23205 56610 36465
+20 39 1 23715 56610 36975
+21 39 1 24480 56610 37230
+22 39 1 25245 56610 37740
+23 39 1 26010 56610 38250
+24 39 1 26520 56610 38505
+25 39 1 27285 56610 39015
+26 39 1 28050 56610 39270
+27 39 1 29325 56610 40290
+28 39 1 30090 56610 40545
+29 39 1 30600 56610 41055
+30 39 1 31365 56610 41565
+31 39 1 32130 56610 41820
+32 39 1 32640 56610 42330
+33 39 1 33405 56610 42585
+34 39 1 34170 56610 43095
+35 39 1 34680 56610 43605
+36 39 1 35445 56610 43860
+37 39 1 36210 56610 44370
+38 39 1 37485 56610 45135
+39 39 1 38250 56610 45645
+40 39 1 39015 56610 45900
+41 39 1 39525 56610 46410
+42 39 1 40290 56610 46665
+43 39 1 41055 56610 47175
+44 39 1 41565 56610 47685
+45 39 1 42330 56610 47940
+46 39 1 43095 56610 48450
+47 39 1 43605 56610 48960
+48 39 1 45135 56610 49725
+49 39 1 45645 56610 49980
+50 39 1 46410 56610 50490
+51 39 1 47175 57630 51255
+52 39 1 47685 57630 51765
+53 39 1 48450 57630 52020
+54 39 1 49215 57630 52530
+55 39 1 49725 57630 53040
+56 39 1 50490 57630 53295
+57 39 1 52020 57630 54315
+58 39 1 52530 57630 54570
+59 39 1 53295 57630 55080
+60 39 1 54060 57630 55335
+61 39 1 54570 57630 55845
+62 39 1 55335 57630 56355
+63 39 1 56100 57630 56610
+0 40 1 8670 58650 28560
+1 40 1 9435 58650 29070
+2 40 1 10200 58650 29580
+3 40 1 10710 58650 29835
+4 40 1 11475 58650 30345
+5 40 1 12240 58650 30855
+6 40 1 13005 58650 31110
+7 40 1 13515 58650 31620
+8 40 1 15045 58650 32385
+9 40 1 15555 58650 32895
+10 40 1 16320 58650 33150
+11 40 1 17085 58650 33660
+12 40 1 17595 58650 33915
+13 40 1 18360 58650 34425
+14 40 1 19125 58650 34935
+15 40 1 19635 58650 35190
+16 40 1 20400 58650 35700
+17 40 1 21675 58650 36465
+18 40 1 22440 58650 36975
+19 40 1 23205 58650 37230
+20 40 1 23715 58650 37740
+21 40 1 24480 58650 38250
+22 40 1 25245 58650 38505
+23 40 1 26010 58650 39015
+24 40 1 26520 58650 39270
+25 40 1 27285 58650 39780
+26 40 1 28050 58650 40290
+27 40 1 29325 58650 41055
+28 40 1 30090 58650 41565
+29 40 1 30600 58650 41820
+30 40 1 31365 58650 42330
+31 40 1 32130 58650 42585
+32 40 1 32640 58650 43095
+33 40 1 33405 58650 43605
+34 40 1 34170 58650 43860
+35 40 1 34680 58650 44370
+36 40 1 35445 58650 44625
+37 40 1 36210 58650 45135
+38 40 1 37485 58650 45900
+39 40 1 38250 58650 46410
+40 40 1 39015 58650 46665
+41 40 1 39525 58650 47175
+42 40 1 40290 58650 47685
+43 40 1 41055 58650 47940
+44 40 1 41565 58650 48450
+45 40 1 42330 58650 48960
+46 40 1 43095 58650 49215
+47 40 1 43605 58650 49725
+48 40 1 45135 58650 50490
+49 40 1 45645 58650 51000
+50 40 1 46410 58650 51255
+51 40 1 47175 58650 51765
+52 40 1 47685 58650 52020
+53 40 1 48450 58650 52530
+54 40 1 49215 58650 53040
+55 40 1 49725 58650 53295
+56 40 1 50490 58650 53805
+57 40 1 52020 58650 54570
+58 40 1 52530 58650 55080
+59 40 1 53295 58650 55335
+60 40 1 54060 58650 55845
+61 40 1 54570 58650 56355
+62 40 1 55335 58650 56610
+63 40 1 56100 58650 57120
+0 41 1 8670 59670 29070
+1 41 1 9435 59670 29580
+2 41 1 10200 59670 29835
+3 41 1 10710 59670 30345
+4 41 1 11475 59670 30855
+5 41 1 12240 59670 31110
+6 41 1 13005 59670 31620
+7 41 1 13515 59670 31875
+8 41 1 15045 59670 32895
+9 41 1 15555 59670 33150
+10 41 1 16320 59670 33660
+11 41 1 17085 59670 33915
+12 41 1 17595 59670 34425
+13 41 1 18360 59670 34935
+14 41 1 19125 59670 35190
+15 41 1 19635 59670 35700
+16 41 1 20400 59670 36210
+17 41 1 21675 59670 36975
+18 41 1 22440 59670 37230
+19 41 1 23205 59670 37740
+20 41 1 23715 59670 38250
+21 41 1 24480 59670 38505
+22 41 1 25245 59670 39015
+23 41 1 26010 59670 39270
+24 41 1 26520 59670 39780
+25 41 1 27285 59670 40290
+26 41 1 28050 59670 40545
+27 41 1 29325 59670 41565
+28 41 1 30090 59670 41820
+29 41 1 30600 59670 42330
+30 41 1 31365 59670 42585
+31 41 1 32130 59670 43095
+32 41 1 32640 59670 43605
+33 41 1 33405 59670 43860
+34 41 1 34170 59670 44370
+35 41 1 34680 59670 44625
+36 41 1 35445 59670 45135
+37 41 1 36210 59670 45645
+38 41 1 37485 59670 46410
+39 41 1 38250 59670 46665
+40 41 1 39015 59670 47175
+41 41 1 39525 59670 47685
+42 41 1 40290 59670 47940
+43 41 1 41055 59670 48450
+44 41 1 41565 59670 48960
+45 41 1 42330 59670 49215
+46 41 1 43095 59670 49725
+47 41 1 43605 59670 49980
+48 41 1 45135 59670 51000
+49 41 1 45645 59670 51255
+50 41 1 46410 59670 51765
+51 41 1 47175 59670 52020
+52 41 1 47685 59670 52530
+53 41 1 48450 59670 53040
+54 41 1 49215 59670 53295
+55 41 1 49725 59670 53805
+56 41 1 50490 59670 54315
+57 41 1 52020 59670 55080
+58 41 1 52530 59670 55335
+59 41 1 53295 59670 55845
+60 41 1 54060 59670 56355
+61 41 1 54570 59670 56610
+62 41 1 55335 59670 57120
+63 41 1 56100 59670 57375
+0 42 1 8670 60690 29580
+1 42 1 9435 60690 29835
+2 42 1 10200 60690 30345
+3 42 1 10710 60690 30855
+4 42 1 11475 60690 31110
+5 42 1 12240 60690 31620
+6 42 1 13005 60690 31875
+7 42 1 13515 60690 32385
+8 42 1 15045 60690 33150
+9 42 1 15555 60690 33660
+10 42 1 16320 60690 33915
+11 42 1 17085 60690 34425
+12 42 1 17595 60690 34935
+13 42 1 18360 60690 35190
+14 42 1 19125 60690 35700
+15 42 1 19635 60690 36210
+16 42 1 20400 60690 36465
+17 42 1 21675 60690 37230
+18 42 1 22440 60690 37740
+19 42 1 23205 60690 38250
+20 42 1 23715 60690 38505
+21 42 1 24480 60690 39015
+22 42 1 25245 60690 39270
+23 42 1 26010 60690 39780
+24 42 1 26520 60690 40290
+25 42 1 27285 60690 40545
+26 42 1 28050 60690 41055
+27 42 1 29325 60690 41820
+28 42 1 30090 60690 42330
+29 42 1 30600 60690 42585
+30 42 1 31365 60690 43095
+31 42 1 32130 60690 43605
+32 42 1 32640 60690 43860
+33 42 1 33405 60690 44370
+34 42 1 34170 60690 44625
+35 42 1 34680 60690 45135
+36 42 1 35445 60690 45645
+37 42 1 36210 60690 45900
+38 42 1 37485 60690 46665
+39 42 1 38250 60690 47175
+40 42 1 39015 60690 47685
+41 42 1 39525 60690 47940
+42 42 1 40290 60690 48450
+43 42 1 41055 60690 48960
+44 42 1 41565 60690 49215
+45 42 1 42330 60690 49725
+46 42 1 43095 60690 49980
+47 42 1 43605 60690 50490
+48 42 1 45135 60690 51255
+49 42 1 45645 60690 51765
+50 42 1 46410 60690 52020
+51 42 1 47175 60690 52530
+52 42 1 47685 60690 53040
+53 42 1 48450 60690 53295
+54 42 1 49215 60690 53805
+55 42 1 49725 60690 54315
+56 42 1 50490 60690 54570
+57 42 1 52020 60690 55335
+58 42 1 52530 60690 55845
+59 42 1 53295 60690 56355
+60 42 1 54060 60690 56610
+61 42 1 54570 60690 57120
+62 42 1 55335 60690 57375
+63 42 1 56100 60690 57885
+0 43 1 8670 61710 29835
+1 43 1 9435 61710 30345
+2 43 1 10200 61710 30855
+3 43 1 10710 61710 31110
+4 43 1 11475 61710 31620
+5 43 1 12240 61710 31875
+6 43 1 13005 61710 32385
+7 43 1 13515 61710 32895
+8 43 1 15045 61710 33660
+9 43 1 15555 61710 33915
+10 43 1 16320 61710 34425
+11 43 1 17085 61710 34935
+12 43 1 17595 61710 35190
+13 43 1 18360 61710 35700
+14 43 1 19125 61710 36210
+15 43 1 19635 61710 36465
+16 43 1 20400 61710 36975
+17 43 1 21675 61710 37740
+18 43 1 22440 61710 38250
+19 43 1 23205 61710 38505
+20 43 1 23715 61710 39015
+21 43 1 24480 61710 39270
+22 43 1 25245 61710 39780
+23 43 1 26010 61710 40290
+24 43 1 26520 61710 40545
+25 43 1 27285 61710 41055
+26 43 1 28050 61710 41565
+27 43 1 29325 61710 42330
+28 43 1 30090 61710 42585
+29 43 1 30600 61710 43095
+30 43 1 31365 61710 43605
+31 43 1 32130 61710 43860
+32 43 1 32640 61710 44370
+33 43 1 33405 61710 44625
+34 43 1 34170 61710 45135
+35 43 1 34680 61710 45645
+36 43 1 35445 61710 45900
+37 43 1 36210 61710 46410
+38 43 1 37485 61710 47175
+39 43 1 38250 61710 47685
+40 43 1 39015 61710 47940
+41 43 1 39525 61710 48450
+42 43 1 40290 61710 48960
+43 43 1 41055 61710 49215
+44 43 1 41565 61710 49725
+45 43 1 42330 61710 49980
+46 43 1 43095 61710 50490
+47 43 1 43605 61710 51000
+48 43 1 45135 61710 51765
+49 43 1 45645 61710 52020
+50 43 1 46410 61710 52530
+51 43 1 47175 61710 53040
+52 43 1 47685 61710 53295
+53 43 1 48450 61710 53805
+54 43 1 49215 61710 54315
+55 43 1 49725 61710 54570
+56 43 1 50490 61710 55080
+57 43 1 52020 61710 55845
+58 43 1 52530 61710 56355
+59 43 1 53295 61710 56610
+60 43 1 54060 61710 57120
+61 43 1 54570 61710 57375
+62 43 1 55335 61710 57885
+63 43 1 56100 61710 58395
+0 44 1 8670 62730 30345
+1 44 1 9435 62730 30855
+2 44 1 10200 62730 31110
+3 44 1 10710 62730 31620
+4 44 1 11475 62730 31875
+5 44 1 12240 62730 32385
+6 44 1 13005 62730 32895
+7 44 1 13515 62730 33150
+8 44 1 15045 62730 33915
+9 44 1 15555 62730 34425
+10 44 1 16320 62730 34935
+11 44 1 17085 62730 35190
+12 44 1 17595 62730 35700
+13 44 1 18360 62730 36210
+14 44 1 19125 62730 36465
+15 44 1 19635 62730 36975
+16 44 1 20400 62730 37230
+17 44 1 21675 62730 38250
+18 44 1 22440 62730 38505
+19 44 1 23205 62730 39015
+20 44 1 23715 62730 39270
+21 44 1 24480 62730 39780
+22 44 1 25245 62730 40290
+23 44 1 26010 62730 40545
+24 44 1 26520 62730 41055
+25 44 1 27285 62730 41565
+26 44 1 28050 62730 41820
+27 44 1 29325 62730 42585
+28 44 1 30090 62730 43095
+29 44 1 30600 62730 43605
+30 44 1 31365 62730 43860
+31 44 1 32130 62730 44370
+32 44 1 32640 62730 44625
+33 44 1 33405 62730 45135
+34 44 1 34170 62730 45645
+35 44 1 34680 62730 45900
+36 44 1 35445 62730 46410
+37 44 1 36210 62730 46665
+38 44 1 37485 62730 47685
+39 44 1 38250 62730 47940
+40 44 1 39015 62730 48450
+41 44 1 39525 62730 48960
+42 44 1 40290 62730 49215
+43 44 1 41055 62730 49725
+44 44 1 41565 62730 49980
+45 44 1 42330 62730 50490
+46 44 1 43095 62730 51000
+47 44 1 43605 62730 51255
+48 44 1 45135 62730 52020
+49 44 1 45645 62730 52530
+50 44 1 46410 62730 53040
+51 44 1 47175 62730 53295
+52 44 1 47685 62730 53805
+53 44 1 48450 62730 54315
+54 44 1 49215 62730 54570
+55 44 1 49725 62730 55080
+56 44 1 50490 62730 55335
+57 44 1 52020 62730 56355
+58 44 1 52530 62730 56610
+59 44 1 53295 62730 57120
+60 44 1 54060 62730 57375
+61 44 1 54570 62730 57885
+62 44 1 55335 62730 58395
+63 44 1 56100 62730 58650
+0 45 1 8670 63750 30855
+1 45 1 9435 63750 31110
+2 45 1 10200 63750 31620
+3 45 1 10710 63750 31875
+4 45 1 11475 63750 32385
+5 45 1 12240 63750 32895
+6 45 1 13005 63750 33150
+7 45 1 13515 63750 33660
+8 45 1 15045 63750 34425
+9 45 1 15555 63750 34935
+10 45 1 16320 63750 35190
+11 45 1 17085 63750 35700
+12 45 1 17595 63750 36210
+13 45 1 18360 63750 36465
+14 45 1 19125 63750 36975
+15 45 1 19635 63750 37230
+16 45 1 20400 63750 37740
+17 45 1 21675 63750 38505
+18 45 1 22440 63750 39015
+19 45 1 23205 63750 39270
+20 45 1 23715 63750 39780
+21 45 1 24480 63750 40290
+22 45 1 25245 63750 40545
+23 45 1 26010 63750 41055
+24 45 1 26520 63750 41565
+25 45 1 27285 63750 41820
+26 45 1 28050 63750 42330
+27 45 1 29325 63750 43095
+28 45 1 30090 63750 43605
+29 45 1 30600 63750 43860
+30 45 1 31365 63750 44370
+31 45 1 32130 63750 44625
+32 45 1 32640 63750 45135
+33 45 1 33405 63750 45645
+34 45 1 34170 63750 45900
+35 45 1 34680 63750 46410
+36 45 1 35445 63750 46665
+37 45 1 36210 63750 47175
+38 45 1 37485 63750 47940
+39 45 1 38250 63750 48450
+40 45 1 39015 63750 48960
+41 45 1 39525 63750 49215
+42 45 1 40290 63750 49725
+43 45 1 41055 63750 49980
+44 45 1 41565 63750 50490
+45 45 1 42330 63750 51000
+46 45 1 43095 63750 51255
+47 45 1 43605 63750 51765
+48 45 1 45135 63750 52530
+49 45 1 45645 63750 53040
+50 45 1 46410 63750 53295
+51 45 1 47175 63750 53805
+52 45 1 47685 63750 54315
+53 45 1 48450 63750 54570
+54 45 1 49215 63750 55080
+55 45 1 49725 63750 55335
+56 45 1 50490 63750 55845
+57 45 1 52020 63750 56610
+58 45 1 52530 63750 57120
+59 45 1 53295 63750 57375
+60 45 1 54060 63750 57885
+61 45 1 54570 63750 58395
+62 45 1 55335 63750 58650
+63 45 1 56100 63750 59160
+0 46 1 8670 65025 31110
+1 46 1 9435 65025 31620
+2 46 1 10200 65025 31875
+3 46 1 10710 65025 32385
+4 46 1 11475 65025 32895
+5 46 1 12240 65025 33150
+6 46 1 13005 65025 33660
+7 46 1 13515 65025 33915
+8 46 1 15045 65025 34935
+9 46 1 15555 65025 35190
+10 46 1 16320 65025 35700
+11 46 1 17085 65025 36210
+12 46 1 17595 65025 36465
+13 46 1 18360 65025 36975
+14 46 1 19125 65025 37230
+15 46 1 19635 65025 37740
+16 46 1 20400 65025 38250
+17 46 1 21675 65025 39015
+18 46 1 22440 65025 39270
+19 46 1 23205 65025 39780
+20 46 1 23715 65025 40290
+21 46 1 24480 65025 40545
+22 46 1 25245 65025 41055
+23 46 1 26010 65025 41565
+24 46 1 26520 65025 41820
+25 46 1 27285 65025 42330
+26 46 1 28050 65025 42585
+27 46 1 29325 65025 43605
+28 46 1 30090 65025 43860
+29 46 1 30600 65025 44370
+30 46 1 31365 65025 44625
+31 46 1 32130 65025 45135
+32 46 1 32640 65025 45645
+33 46 1 33405 65025 45900
+34 46 1 34170 65025 46410
+35 46 1 34680 65025 46665
+36 46 1 35445 65025 47175
+37 46 1 36210 65025 47685
+38 46 1 37485 65025 48450
+39 46 1 38250 65025 48960
+40 46 1 39015 65025 49215
+41 46 1 39525 65025 49725
+42 46 1 40290 65025 49980
+43 46 1 41055 65025 50490
+44 46 1 41565 65025 51000
+45 46 1 42330 65025 51255
+46 46 1 43095 65025 51765
+47 46 1 43605 65025 52020
+48 46 1 45135 65025 53040
+49 46 1 45645 65025 53295
+50 46 1 46410 65025 53805
+51 46 1 47175 65025 54315
+52 46 1 47685 65025 54570
+53 46 1 48450 65025 55080
+54 46 1 49215 65025 55335
+55 46 1 49725 65025 55845
+56 46 1 50490 65025 56355
+57 46 1 52020 65025 57120
+58 46 1 52530 65025 57375
+59 46 1 53295 65025 57885
+60 46 1 54060 65025 58395
+61 46 1 54570 65025 58650
+62 46 1 55335 65025 59160
+63 46 1 56100 65025 59670
+0 47 0 0 0 0
+1 47 0 0 0 0
+2 47 0 0 0 0
+3 47 0 0 0 0
+4 47 0 0 0 0
+5 47 0 0 0 0
+6 47 0 0 0 0
+7 47 0 0 0 0
+8 47 0 0 0 0
+9 47 0 0 0 0
+10 47 0 0 0 0
+11 47 0 0 0 0
+12 47 0 0 0 0
+13 47 0 0 0 0
+14 47 0 0 0 0
+15 47 0 0 0 0
+16 47 0 0 0 0
+17 47 0 0 0 0
+18 47 0 0 0 0
+19 47 0 0 0 0
+20 47 0 0 0 0
+21 47 0 0 0 0
+22 47 0 0 0 0
+23 47 0 0 0 0
+24 47 0 0 0 0
+25 47 0 0 0 0
+26 47 0 0 0 0
+27 47 0 0 0 0
+28 47 0 0 0 0
+29 47 0 0 0 0
+30 47 0 0 0 0
+31 47 0 0 0 0
+32 47 0 0 0 0
+33 47 0 0 0 0
+34 47 0 0 0 0
+35 47 0 0 0 0
+36 47 0 0 0 0
+37 47 0 0 0 0
+38 47 0 0 0 0
+39 47 0 0 0 0
+40 47 0 0 0 0
+41 47 0 0 0 0
+42 47 0 0 0 0
+43 47 0 0 0 0
+44 47 0 0 0 0
+45 47 0 0 0 0
+46 47 0 0 0 0
+47 47 0 0 0 0
+48 47 0 0 0 0
+49 47 0 0 0 0
+50 47 0 0 0 0
+51 47 0 0 0 0
+52 47 0 0 0 0
+53 47 0 0 0 0
+54 47 0 0 0 0
+55 47 0 0 0 0
+56 47 0 0 0 0
+57 47 0 0 0 0
+58 47 0 0 0 0
+59 47 0 0 0 0
+60 47 0 0 0 0
+61 47 0 0 0 0
+62 47 0 0 0 0
+63 47 0 0 0 0
diff --git a/tests/golden/test.jpg b/tests/golden/test.jpg
new file mode 100644
index 0000000..5ea9549
--- /dev/null
+++ b/tests/golden/test.jpg
Binary files differ
diff --git a/tests/harness/build/cyl_funcs.cxx b/tests/harness/build/cyl_funcs.cxx
new file mode 100644
index 0000000..5143ba5
--- /dev/null
+++ b/tests/harness/build/cyl_funcs.cxx
@@ -0,0 +1,10 @@
+double cyl_mac_x ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return (cos(c_tilt)*(m_view-c_view)-(sin(c_tilt)*(_2-_1))/(_1*_2+1))*scale ; }
+double cyl_mac_y ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return ((-sin(c_tilt)*(m_view-c_view))-(cos(c_tilt)*(_2-_1))/(_1*_2+1))*scale ; }
+double cyl_mac_x_dc_view ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { return -cos(c_tilt)*scale ; }
+double cyl_mac_x_dc_nick ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 ; _1 = pow(sec(c_nick),2) ; _2 = sin(c_tilt) ; _3 = tan(m_nick) ; _4 = tan(c_nick) ; _5 = _4*_3+1 ; return ((_1*_2*_3*(_3-_4))/pow(_5,2)+(_1*_2)/_5)*scale ; }
+double cyl_mac_x_dc_tilt ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return ((-sin(c_tilt)*(m_view-c_view))-(cos(c_tilt)*(_2-_1))/(_1*_2+1))*scale ; }
+double cyl_mac_x_dscale ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return cos(c_tilt)*(m_view-c_view)-(sin(c_tilt)*(_2-_1))/(_1*_2+1) ; }
+double cyl_mac_y_dc_view ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { return sin(c_tilt)*scale ; }
+double cyl_mac_y_dc_nick ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 ; _1 = pow(sec(c_nick),2) ; _2 = cos(c_tilt) ; _3 = tan(m_nick) ; _4 = tan(c_nick) ; _5 = _4*_3+1 ; return ((_1*_2*_3*(_3-_4))/pow(_5,2)+(_1*_2)/_5)*scale ; }
+double cyl_mac_y_dc_tilt ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return ((sin(c_tilt)*(_2-_1))/(_1*_2+1)-cos(c_tilt)*(m_view-c_view))*scale ; }
+double cyl_mac_y_dscale ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 ; _1 = tan(c_nick) ; _2 = tan(m_nick) ; return (-sin(c_tilt)*(m_view-c_view))-(cos(c_tilt)*(_2-_1))/(_1*_2+1) ; }
diff --git a/tests/harness/build/exportdump b/tests/harness/build/exportdump
new file mode 100755
index 0000000..e732af7
--- /dev/null
+++ b/tests/harness/build/exportdump
Binary files differ
diff --git a/tests/harness/build/fitdump b/tests/harness/build/fitdump
new file mode 100755
index 0000000..37383c8
--- /dev/null
+++ b/tests/harness/build/fitdump
Binary files differ
diff --git a/tests/harness/build/funcsdump b/tests/harness/build/funcsdump
new file mode 100755
index 0000000..37efb59
--- /dev/null
+++ b/tests/harness/build/funcsdump
Binary files differ
diff --git a/tests/harness/build/rect_funcs.cxx b/tests/harness/build/rect_funcs.cxx
new file mode 100644
index 0000000..95c5cc2
--- /dev/null
+++ b/tests/harness/build/rect_funcs.cxx
@@ -0,0 +1,16 @@
+double rect_mac_x ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2)+pow(_13*_11*_12-_1*_5*_14*_11,2) ; return scale*_15*(k0*sqrt(_16)+k1*_16+1) ; }
+double rect_mac_y ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = pow(x0+_1*_5*_14*_11+_13*_11*_12,2)+pow(_15,2) ; return _15*scale*(k0*sqrt(_16)+k1*_16+1) ; }
+double rect_mac_x_dc_view ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 , _20 , _21 , _22 , _23 , _24 , _25 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = cos(c_tilt) ; _15 = _6*_9-_8*_7 ; _16 = x0+_14*_5*_15*_12+_1*_12*_13 ; _17 = _14*_12*_13-_1*_5*_15*_12 ; _18 = 1/pow(_11,2) ; _19 = pow(_5,2) ; _20 = pow(_15,2) ; _21 = (-_8*_9)-_6*_7 ; _22 = _14*_5*_21*_12+_2*_1*_5*_15*_12-_4*_14*_19*_20*_18-_4*_1*_5*_15*_18*_13 ; _23 = 2*_22*_16+2*_17*((-_1*_5*_21*_12)+_2*_14*_5*_15*_12+_4*_1*_19*_20*_18-_4*_14*_5*_15*_18*_13) ; _24 = pow(_17,2)+pow(_16,2) ; _25 = sqrt(_24) ; return _22*scale*(k0*_25+k1*_24+1)+scale*_16*((k0*_23)/(2*_25)+k1*_23) ; }
+double rect_mac_x_dc_nick ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 , _20 , _21 , _22 , _23 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = cos(c_tilt) ; _15 = _6*_9-_8*_7 ; _16 = x0+_14*_5*_15*_12+_1*_12*_13 ; _17 = 1/pow(_11,2) ; _18 = _4*_3-_2*_5*_10 ; _19 = _14*_12*_13-_1*_5*_15*_12 ; _20 = (-_1*_17*_18*_13)-_14*_5*_15*_17*_18+_1 ; _21 = 2*_20*_16+2*((-_14*_17*_18*_13)+_1*_5*_15*_17*_18+_14)*_19 ; _22 = pow(_19,2)+pow(_16,2) ; _23 = sqrt(_22) ; return _20*scale*(k0*_23+k1*_22+1)+scale*_16*((k0*_21)/(2*_23)+k1*_21) ; }
+double rect_mac_x_dc_tilt ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = _13*_11*_12-_1*_5*_14*_11 ; _17 = 2*_16*_15+2*((-_13*_5*_14*_11)-_1*_11*_12)*_16 ; _18 = pow(_16,2)+pow(_15,2) ; _19 = sqrt(_18) ; return _16*scale*(k0*_19+k1*_18+1)+scale*_15*((k0*_17)/(2*_19)+k1*_17) ; }
+double rect_mac_x_dscale ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2)+pow(_13*_11*_12-_1*_5*_14*_11,2) ; return _15*(k0*sqrt(_16)+k1*_16+1) ; }
+double rect_mac_x_dk0 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; return _15*sqrt(pow(_15,2)+pow(_13*_11*_12-_1*_5*_14*_11,2))*scale ; }
+double rect_mac_x_dk1 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; return _15*(pow(_15,2)+pow(_13*_11*_12-_1*_5*_14*_11,2))*scale ; }
+double rect_mac_x_dx0 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 ; _1 = sin(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = cos(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = x0+_13*_5*_14*_11+_1*_11*_12 ; _16 = pow(_15,2)+pow(_13*_11*_12-_1*_5*_14*_11,2) ; _17 = sqrt(_16) ; return scale*(k0*_17+k1*_16+1)+scale*_15*((k0*_15)/_17+2*k1*_15) ; }
+double rect_mac_y_dc_view ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 , _20 , _21 , _22 , _23 , _24 , _25 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = sin(c_tilt) ; _15 = _6*_9-_8*_7 ; _16 = _1*_12*_13-_14*_5*_15*_12 ; _17 = 1/pow(_11,2) ; _18 = pow(_5,2) ; _19 = pow(_15,2) ; _20 = (-_8*_9)-_6*_7 ; _21 = (-_14*_5*_20*_12)+_2*_1*_5*_15*_12+_4*_14*_18*_19*_17-_4*_1*_5*_15*_17*_13 ; _22 = x0+_1*_5*_15*_12+_14*_12*_13 ; _23 = 2*(_1*_5*_20*_12+_2*_14*_5*_15*_12-_4*_1*_18*_19*_17-_4*_14*_5*_15*_17*_13)*_22+2*_16*_21 ; _24 = pow(_22,2)+pow(_16,2) ; _25 = sqrt(_24) ; return _21*scale*(k0*_25+k1*_24+1)+_16*scale*((k0*_23)/(2*_25)+k1*_23) ; }
+double rect_mac_y_dc_nick ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 , _20 , _21 , _22 , _23 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = _4*_5*_10+_2*_3 ; _12 = 1/_11 ; _13 = _2*_5*_10-_4*_3 ; _14 = sin(c_tilt) ; _15 = _6*_9-_8*_7 ; _16 = _1*_12*_13-_14*_5*_15*_12 ; _17 = 1/pow(_11,2) ; _18 = _4*_3-_2*_5*_10 ; _19 = (-_1*_17*_18*_13)+_14*_5*_15*_17*_18+_1 ; _20 = x0+_1*_5*_15*_12+_14*_12*_13 ; _21 = 2*((-_14*_17*_18*_13)-_1*_5*_15*_17*_18+_14)*_20+2*_19*_16 ; _22 = pow(_20,2)+pow(_16,2) ; _23 = sqrt(_22) ; return _19*scale*(k0*_23+k1*_22+1)+_16*scale*((k0*_21)/(2*_23)+k1*_21) ; }
+double rect_mac_y_dc_tilt ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 , _17 , _18 , _19 , _20 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = (-_1*_5*_14*_11)-_13*_11*_12 ; _17 = x0+_1*_5*_14*_11+_13*_11*_12 ; _18 = 2*_15*_17+2*_16*_15 ; _19 = pow(_17,2)+pow(_15,2) ; _20 = sqrt(_19) ; return _16*scale*(k0*_20+k1*_19+1)+_15*scale*((k0*_18)/(2*_20)+k1*_18) ; }
+double rect_mac_y_dscale ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = pow(x0+_1*_5*_14*_11+_13*_11*_12,2)+pow(_15,2) ; return _15*(k0*sqrt(_16)+k1*_16+1) ; }
+double rect_mac_y_dk0 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; return _15*scale*sqrt(pow(x0+_1*_5*_14*_11+_13*_11*_12,2)+pow(_15,2)) ; }
+double rect_mac_y_dk1 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; return _15*scale*(pow(x0+_1*_5*_14*_11+_13*_11*_12,2)+pow(_15,2)) ; }
+double rect_mac_y_dx0 ( double c_view, double c_nick, double c_tilt, double scale, double k0, double k1, double x0, double m_view, double m_nick ) { double _1 , _2 , _3 , _4 , _5 , _6 , _7 , _8 , _9 , _10 , _11 , _12 , _13 , _14 , _15 , _16 ; _1 = cos(c_tilt) ; _2 = sin(c_nick) ; _3 = sin(m_nick) ; _4 = cos(c_nick) ; _5 = cos(m_nick) ; _6 = cos(c_view) ; _7 = cos(m_view) ; _8 = sin(c_view) ; _9 = sin(m_view) ; _10 = _8*_9+_6*_7 ; _11 = 1/(_4*_5*_10+_2*_3) ; _12 = _2*_5*_10-_4*_3 ; _13 = sin(c_tilt) ; _14 = _6*_9-_8*_7 ; _15 = _1*_11*_12-_13*_5*_14*_11 ; _16 = x0+_1*_5*_14*_11+_13*_11*_12 ; return _15*((_16*k0)/sqrt(pow(_16,2)+pow(_15,2))+2*k1*_16)*scale ; }
diff --git a/tests/harness/build/stitchdump b/tests/harness/build/stitchdump
new file mode 100755
index 0000000..e63a628
--- /dev/null
+++ b/tests/harness/build/stitchdump
Binary files differ
diff --git a/tests/harness/exportdump.cxx b/tests/harness/exportdump.cxx
new file mode 100644
index 0000000..2c0e736
--- /dev/null
+++ b/tests/harness/exportdump.cxx
@@ -0,0 +1,77 @@
+// Golden dump: export-hills pipeline through the original C++ Panorama +
+// ImageMetaData (mirrors GipfelWidget::load_image + export_hills, no FLTK).
+// Usage: exportdump <img> <gipfel.dat>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <exiv2/exiv2.hpp>
+
+#include "Panorama.H"
+#include "Hill.H"
+#include "ImageMetaData.H"
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ fprintf(stderr, "usage: exportdump <img> <gipfel.dat>\n");
+ return 1;
+ }
+
+ int w = 0, h = 0;
+ {
+ auto im = Exiv2::ImageFactory::open(argv[1]);
+ im->readMetadata();
+ w = im->pixelWidth();
+ h = im->pixelHeight();
+ }
+
+ ImageMetaData md;
+ md.load_image(argv[1]);
+
+ Panorama pan;
+ pan.set_view_long(md.longitude());
+ pan.set_view_lat(md.latitude());
+ pan.set_view_height(md.height());
+ pan.set_projection((ProjectionLSQ::Projection_t) md.projection_type());
+
+ int have = 1;
+ double v;
+ v = md.direction();
+ if (std::isnan(v)) { pan.set_center_angle(0.0); have = 0; } else pan.set_center_angle(v);
+ v = md.nick();
+ if (std::isnan(v)) { pan.set_nick_angle(0.0); have = 0; } else pan.set_nick_angle(v);
+ v = md.tilt();
+ if (std::isnan(v)) { pan.set_tilt_angle(0.0); have = 0; } else pan.set_tilt_angle(v);
+ v = md.focal_length_35mm();
+ if (std::isnan(v) || v == 0.0) { pan.set_scale(35.0 * (double)(w > h ? w : h) / 35.0); have = 0; }
+ else pan.set_scale(v * (double)(w > h ? w : h) / 35.0);
+
+ md.distortion_params(&pan.parms.k0, &pan.parms.k1, &pan.parms.x0);
+ if (std::isnan(pan.parms.k0)) pan.parms.k0 = 0.0;
+ if (std::isnan(pan.parms.k1)) pan.parms.k1 = 0.0;
+ if (std::isnan(pan.parms.x0)) pan.parms.x0 = 0.0;
+
+ if (pan.load_data(argv[2])) return 1;
+ pan.set_height_dist_ratio(0.07);
+
+ if (!have) {
+ fprintf(stderr, "No gipfel info available.\n");
+ return 0;
+ }
+
+ printf("#\n# name\theight\tx\ty\tdistance\tflags\n#\n");
+ Hills *mnts = pan.get_visible_mountains();
+ for (int i = 0; i < mnts->get_num(); i++) {
+ Hill *m = mnts->get(i);
+ int _x = (int) rint(m->x) + w / 2;
+ int _y = (int) rint(m->y) + h / 2;
+
+ if (m->flags & Hill::DUPLIC || m->flags & Hill::HIDDEN)
+ continue;
+ if (_x < 0 || _x > w || _y < 0 || _y > h)
+ continue;
+
+ printf("%s\t%d\t%d\t%d\t%d\n", m->name, (int) rint(m->height), _x, _y,
+ (int) rint(pan.get_real_distance(m)));
+ }
+ return 0;
+}
diff --git a/tests/harness/fitdump.cxx b/tests/harness/fitdump.cxx
new file mode 100644
index 0000000..8ef392c
--- /dev/null
+++ b/tests/harness/fitdump.cxx
@@ -0,0 +1,83 @@
+// Golden dump: full fit through the original C++ Panorama + GSL solver.
+// Usage: fitdump <gipfel.dat> <projection 0|1>
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+
+#include "Panorama.H"
+#include "Hill.H"
+#include "ProjectionRectilinear.H"
+#include "ProjectionCylindrical.H"
+
+int main(int argc, char **argv) {
+ if (argc < 3) {
+ fprintf(stderr, "usage: fitdump <gipfel.dat> <projection>\n");
+ return 1;
+ }
+
+ Panorama pan;
+ if (pan.load_data(argv[1])) {
+ fprintf(stderr, "load failed\n");
+ return 1;
+ }
+ pan.set_projection((ProjectionLSQ::Projection_t) atoi(argv[2]));
+
+ pan.set_view_lat(47.36);
+ pan.set_view_long(11.15);
+ pan.set_view_height(2100.0);
+
+ // ground truth
+ pan.set_center_angle(20.0);
+ pan.set_nick_angle(0.0);
+ pan.set_tilt_angle(1.0);
+ pan.set_scale(3000.0);
+
+ // observed positions = projection of first 6 visible hills (snapshotted
+ // as copies so the perturbation below does not overwrite them)
+ Hills *vis = pan.get_visible_mountains();
+ Hills known;
+ int n = vis->get_num() < 6 ? vis->get_num() : 6;
+ for (int i = 0; i < n; i++) {
+ Hill *c = new Hill(*vis->get(i));
+ // NOTE: Hill's copy ctor in the original does not copy a_nick
+ // (uninitialized garbage -> LSQR stagnates). The real app always
+ // fits hills living in the main list where a_nick is set, so set it
+ // here to reproduce the real-app behavior.
+ c->a_nick = vis->get(i)->a_nick;
+ known.add(c);
+ }
+
+ // perturb the initial guess
+ pan.set_center_angle(pan.get_center_angle() + 0.5);
+ pan.set_nick_angle(0.3);
+ pan.set_tilt_angle(0.5);
+ pan.set_scale(2800.0);
+
+ int ret = pan.comp_params(&known);
+
+ ViewParams *p = &pan.parms;
+ printf("ret %d\n", ret);
+ printf("a_center %.17g\n", p->a_center);
+ printf("a_nick %.17g\n", p->a_nick);
+ printf("a_tilt %.17g\n", p->a_tilt);
+ printf("scale %.17g\n", p->scale);
+ printf("k0 %.17g\n", p->k0);
+ printf("k1 %.17g\n", p->k1);
+ printf("x0 %.17g\n", p->x0);
+
+ // final image coordinates of the known hills (bypass visibility check)
+ for (int i = 0; i < known.get_num(); i++) {
+ Hill *m = known.get(i);
+ double x = NAN, y = NAN;
+ if (atoi(argv[2]) == 1) {
+ ProjectionCylindrical pc;
+ pc.get_coordinates(m->alph, m->a_nick, &pan.parms, &x, &y);
+ } else {
+ ProjectionRectilinear pr;
+ pr.get_coordinates(m->alph, m->a_nick, &pan.parms, &x, &y);
+ }
+ printf("hill %d %.17g %.17g\n", i, x, y);
+ }
+
+ return 0;
+}
diff --git a/tests/harness/funcsdump.cxx b/tests/harness/funcsdump.cxx
new file mode 100644
index 0000000..b416f2c
--- /dev/null
+++ b/tests/harness/funcsdump.cxx
@@ -0,0 +1,60 @@
+// Golden dump of the maxima-generated projection functions (C reference).
+// The `#define` trick turns the generated class methods into free functions.
+#include <stdio.h>
+#include <math.h>
+
+static double sec(double a) { return 1.0 / cos(a); }
+
+// gen_golden.sh copies the generated files with the class scope replaced:
+// ProjectionRectilinear::rect_mac_x -> rect_mac_x, ProjectionCylindrical::rect_mac_x -> cyl_mac_x
+#include "rect_funcs.cxx"
+#include "cyl_funcs.cxx"
+
+int main(void) {
+ for (int k = 0; k < 200; k++) {
+ double kk = (double) k;
+ double c_view = -3.0 + 0.03 * kk;
+ double c_nick = 0.10 * sin(0.11 * kk);
+ double c_tilt = 0.05 * cos(0.07 * kk);
+ double scale = 2000.0 + 37.0 * fmod(kk, 19.0);
+ double k0 = 0.001 * sin(0.31 * kk);
+ double k1 = 0.0001 * cos(0.17 * kk);
+ double x0 = 50.0 * sin(0.13 * kk);
+ double m_view = c_view + 0.5 * sin(0.23 * kk);
+ double m_nick = 0.05 * cos(0.19 * kk);
+
+ printf("%.17g %.17g %.17g %.17g %.17g %.17g %.17g %.17g %.17g ",
+ rect_mac_x(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dc_view(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dc_nick(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dc_tilt(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dscale(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dk0(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dk1(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_x_dx0(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick));
+ printf("%.17g %.17g %.17g %.17g %.17g %.17g %.17g ",
+ rect_mac_y_dc_view(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dc_nick(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dc_tilt(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dscale(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dk0(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dk1(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ rect_mac_y_dx0(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick));
+
+ printf("%.17g %.17g ",
+ cyl_mac_x(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_y(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick));
+ printf("%.17g %.17g %.17g %.17g ",
+ cyl_mac_x_dc_view(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_x_dc_nick(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_x_dc_tilt(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_x_dscale(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick));
+ printf("%.17g %.17g %.17g %.17g\n",
+ cyl_mac_y_dc_view(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_y_dc_nick(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_y_dc_tilt(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick),
+ cyl_mac_y_dscale(c_view, c_nick, c_tilt, scale, k0, k1, x0, m_view, m_nick));
+ }
+ return 0;
+}
diff --git a/tests/harness/stitchdump.cxx b/tests/harness/stitchdump.cxx
new file mode 100644
index 0000000..cd015e7
--- /dev/null
+++ b/tests/harness/stitchdump.cxx
@@ -0,0 +1,102 @@
+// Golden dump: Stitch::resample pipeline through the original C++ Panorama +
+// ScanImage (no FLTK GUI; PPM input wrapped in Fl_RGB_Image).
+// Usage: stitchdump <img.ppm> <projtype> <center> <pitch> <roll> <scale>
+// <k0> <k1> <x0> <w> <h> <from> <to> <bicubic>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include <algorithm>
+
+#include <FL/Fl.H>
+#include <FL/Fl_RGB_Image.H>
+
+#include "Panorama.H"
+#include "ScanImage.H"
+
+#define MAX_VALUE 65025
+
+// minimal P6 PPM reader
+static unsigned char *read_ppm(const char *path, int *w, int *h) {
+ FILE *f = fopen(path, "rb");
+ if (!f) return NULL;
+ int c;
+ if (fgetc(f) != 'P' || fgetc(f) != '6' || (c = fgetc(f), !isspace(c))) {
+ fclose(f); return NULL;
+ }
+ int skip = 0;
+ while (!skip) {
+ c = fgetc(f);
+ if (c == '#') { while ((c = fgetc(f)) != '\n' && c != EOF); continue; }
+ if (c == ' ' || c == '\n' || c == '\t' || c == '\r') continue;
+ skip = 1;
+ ungetc(c, f);
+ }
+ (void)c;
+ if (fscanf(f, "%d %d %*d", w, h) != 2) { fclose(f); return NULL; }
+ fgetc(f); // single whitespace
+ unsigned char *buf = (unsigned char*)malloc((size_t)(*w) * (*h) * 3);
+ if (fread(buf, 1, (size_t)(*w) * (*h) * 3, f) != (size_t)(*w) * (*h) * 3) {
+ free(buf); fclose(f); return NULL;
+ }
+ fclose(f);
+ return buf;
+}
+
+int main(int argc, char **argv) {
+ if (argc < 15) {
+ fprintf(stderr, "usage: stitchdump <img.ppm> <projtype> <center> <pitch> "
+ "<roll> <scale> <k0> <k1> <x0> <w> <h> <from> <to> <bicubic>\n");
+ return 1;
+ }
+ int iw = 0, ih = 0;
+ unsigned char *px = read_ppm(argv[1], &iw, &ih);
+ if (!px) { fprintf(stderr, "cannot read %s\n", argv[1]); return 1; }
+
+ Fl_RGB_Image img(px, iw, ih, 3);
+
+ Panorama pan;
+ pan.set_projection((ProjectionLSQ::Projection_t) atoi(argv[2]));
+ pan.set_center_angle(atof(argv[3]));
+ pan.set_nick_angle(atof(argv[4]));
+ pan.set_tilt_angle(atof(argv[5]));
+ pan.set_scale(atof(argv[6]));
+ pan.parms.k0 = atof(argv[7]);
+ pan.parms.k1 = atof(argv[8]);
+ pan.parms.x0 = atof(argv[9]);
+
+ int w = atoi(argv[10]), h = atoi(argv[11]);
+ double view_start = atof(argv[12]) * M_PI / 180.0;
+ double view_end = atof(argv[13]) * M_PI / 180.0;
+ ScanImage::mode_t mode = atoi(argv[14]) ? ScanImage::BICUBIC : ScanImage::NEAREST;
+
+ double step_view = (view_end - view_start) / w;
+ int y_off = h / 2;
+ double radius = (double) w / (view_end - view_start);
+
+ for (int y = 0; y < h; y++) {
+ double a_nick = atan((double)(y_off - y) / radius);
+ for (int x = 0; x < w; x++) {
+ double a_view = view_start + x * step_view;
+ int hit = 0, r = 0, g = 0, b = 0;
+ double px2, py2;
+ if (pan.get_coordinates(a_view, a_nick, &px2, &py2) == 0) {
+ int rr, gg, bb;
+ if (ScanImage::get_pixel(&img, mode,
+ px2 + ((double) img.w()) / 2.0,
+ py2 + ((double) img.h()) / 2.0,
+ &rr, &gg, &bb) == 0) {
+ hit = 1;
+ r = std::max(std::min(rr, MAX_VALUE), 0);
+ g = std::max(std::min(gg, MAX_VALUE), 0);
+ b = std::max(std::min(bb, MAX_VALUE), 0);
+ }
+ }
+ printf("%d %d %d %d %d %d\n", x, y, hit, r, g, b);
+ }
+ }
+
+ free(px);
+ return 0;
+}