diff --git a/Cargo.lock b/Cargo.lock index 11c7b1d73..492fef141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,7 +159,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -213,13 +213,28 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + [[package]] name = "alloc-stdlib" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ - "alloc-no-stdlib", + "alloc-no-stdlib 2.0.4", +] + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib 3.0.0", ] [[package]] @@ -283,14 +298,14 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.59.0", "wl-clipboard-rs", "x11rb", ] @@ -592,15 +607,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec 0.8.0", -] - [[package]] name = "bit-set" version = "0.10.0" @@ -611,10 +617,13 @@ dependencies = [ ] [[package]] -name = "bit-vec" -version = "0.8.0" +name = "bit-set" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec 0.10.1", +] [[package]] name = "bit-vec" @@ -622,6 +631,16 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -673,7 +692,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -690,14 +709,38 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "8.0.4" +name = "borsh" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "brotli" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" +dependencies = [ + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", + "brotli-decompressor 6.0.1", ] [[package]] @@ -706,8 +749,18 @@ version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", + "alloc-no-stdlib 2.0.4", + "alloc-stdlib 0.2.4", +] + +[[package]] +name = "brotli-decompressor" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" +dependencies = [ + "alloc-no-stdlib 3.0.0", + "alloc-stdlib 0.3.0", ] [[package]] @@ -845,7 +898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fdfe9d06de16ce49961e49084e5b79a75a9bdf157246e7c7b6328e87a7aa25d" dependencies = [ "half", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-foundation 0.3.2", "objc2-metal 0.3.2", "once_cell", @@ -922,7 +975,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", ] [[package]] @@ -932,7 +996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -1084,6 +1148,12 @@ dependencies = [ "libc", ] +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1093,6 +1163,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "craft-docpath" version = "0.1.0" @@ -1110,7 +1189,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=db19c373c8d59a545972ca340987aecfb3611a4b#db19c373c8d59a545972ca340987aecfb3611a4b" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "chrono", "dunce", @@ -1132,11 +1211,22 @@ dependencies = [ "zip 9.1.0", ] +[[package]] +name = "craft-window" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +dependencies = [ + "egui", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "wayland-client", +] + [[package]] name = "crc32fast" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if", ] @@ -1225,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest", "fiat-crypto", @@ -1318,7 +1408,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -1448,7 +1538,7 @@ dependencies = [ "image", "js-sys", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "parking_lot", @@ -1516,7 +1606,7 @@ dependencies = [ "egui", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "objc2-ui-kit 0.3.2", @@ -1647,7 +1737,7 @@ dependencies = [ "emath", "epaint_default_fonts", "font-types", - "harfrust", + "harfrust 0.12.0", "log", "nohash-hasher", "parking_lot", @@ -2408,6 +2498,7 @@ dependencies = [ "js-sys", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] @@ -2490,7 +2581,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -2599,7 +2690,20 @@ checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" dependencies = [ "bitflags 2.13.2", "bytemuck", - "read-fonts", + "read-fonts 0.41.0", + "smallvec", +] + +[[package]] +name = "harfrust" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "once_cell", + "read-fonts 0.43.3", "smallvec", ] @@ -2981,7 +3085,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e35d032bcec660647828527ff42c6f5776d2fd44b8357f9f6d9ac6dc07218e46" dependencies = [ - "brotli-decompressor", + "brotli-decompressor 5.0.3", "jxl-bitstream", "jxl-frame", "jxl-grid", @@ -3013,7 +3117,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d36c662923f47586880211f3bc7c0d83fb3a9b410d278c7bde93450748abeef3" dependencies = [ - "brotli-decompressor", + "brotli-decompressor 5.0.3", "jxl-bitstream", "jxl-color", "jxl-frame", @@ -3139,9 +3243,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libfuzzer-sys" @@ -3197,7 +3301,7 @@ dependencies = [ "lightcraft-ui-egui", "log", "muda", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "pollster 1.0.1", @@ -3289,7 +3393,7 @@ dependencies = [ "faer-core", "lightcraft-denoise-core", "lightcraft-raster", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3303,8 +3407,9 @@ dependencies = [ name = "lightcraft-denoise-core" version = "0.14.0" dependencies = [ + "dhat", "lightcraft-raster", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3320,7 +3425,7 @@ version = "0.14.0" dependencies = [ "lightcraft-color", "lightcraft-geom", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "serde", "serde_json", @@ -3333,7 +3438,7 @@ dependencies = [ "ab_glyph", "chrono", "craft-docpath", - "harfrust", + "harfrust 0.13.3", "jxl-oxide", "libc", "lightcraft-catalog", @@ -3361,7 +3466,7 @@ dependencies = [ "lightcraft-testkit", "lightcraft-tiff", "log", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "rayon", "serde", "serde_json", @@ -3400,7 +3505,7 @@ dependencies = [ name = "lightcraft-geocode" version = "0.14.0" dependencies = [ - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "serde", ] @@ -3552,7 +3657,7 @@ dependencies = [ "lightcraft-raster", "lightcraft-testkit", "lightcraft-tiff", - "miniz_oxide 0.8.9", + "miniz_oxide 0.9.1", "proptest", "rayon", "serde", @@ -3620,6 +3725,7 @@ version = "0.14.0" dependencies = [ "craft-ui", "craft-update", + "craft-window", "egui", "egui_kittest", "lightcraft-catalog", @@ -3836,18 +3942,17 @@ dependencies = [ [[package]] name = "muda" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c" +checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03" dependencies = [ "crossbeam-channel", "dpi", "keyboard-types", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", - "once_cell", "png", "thiserror 2.0.21", "windows-sys 0.61.2", @@ -4150,9 +4255,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ "objc2-encode", ] @@ -4181,7 +4286,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.2", "block2 0.6.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -4231,7 +4336,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -4242,7 +4347,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-io-surface", ] @@ -4299,7 +4404,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -4310,7 +4415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -4347,7 +4452,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -4372,7 +4477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -4417,7 +4522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -4817,7 +4922,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4838,7 +4943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -4927,16 +5032,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set 0.11.1", + "bit-vec 0.10.1", "bitflags 2.13.2", + "chacha20 0.10.2", + "core_detect", "num-traits", - "rand 0.9.5", - "rand_chacha 0.9.0", + "rand 0.10.3", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -5070,6 +5176,16 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -5108,6 +5224,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rand_distr" version = "0.5.1" @@ -5120,11 +5242,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" dependencies = [ - "rand_core 0.9.5", + "rand_core 0.10.1", ] [[package]] @@ -5213,7 +5335,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", @@ -5256,6 +5378,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "read-fonts" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + [[package]] name = "reborrow" version = "0.5.5" @@ -5322,7 +5455,7 @@ dependencies = [ "js-sys", "libc", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -5434,9 +5567,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "log", "once_cell", @@ -5713,9 +5846,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -5751,7 +5884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5825,7 +5958,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.41.0", ] [[package]] @@ -6754,9 +6887,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +checksum = "1c1c8e20c8a38fc7d65abfe74eb7280e4aa667e8eec8bc84996d2874810748d9" dependencies = [ "rustls-pki-types", ] @@ -6895,7 +7028,7 @@ dependencies = [ "naga", "naga-types", "ndk-sys", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -7066,7 +7199,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -7075,16 +7208,7 @@ 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", + "windows-targets", ] [[package]] @@ -7102,31 +7226,14 @@ 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", - "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", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -7144,96 +7251,48 @@ 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.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.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.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.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.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.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.30.13" @@ -7305,9 +7364,9 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" dependencies = [ "toml", "version_check", diff --git a/Cargo.toml b/Cargo.toml index 2bb309f91..b68c6e6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,8 @@ repository = "https://github.com/storytold/lightcraft" [workspace.dependencies] craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "25da1314d443171b4adeaad2cd9454b273f35c6f", features = ["serde"] } craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "db19c373c8d59a545972ca340987aecfb3611a4b", features = ["egui"] } +craft-window = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["egui"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] } # internal lightcraft-denoise-core = { path = "crates/denoise-core" } lightcraft-denoise = { path = "crates/denoise", default-features = false } @@ -62,7 +63,7 @@ dhat = "0.3" # opt-in heap profiling (lightcraft-cli --features dhat-heap) and h # formats quick-xml = "0.41" -miniz_oxide = "0.8" +miniz_oxide = "0.9" # codecs image = { version = "0.25", default-features = false, features = ["png", "jpeg", "tiff", "webp"] } diff --git a/README.md b/README.md index 78b3b3234..71f30d4c2 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ LightCraft is young and moving fast: **alpha**. **Where we honestly stand** (det casual use.** It's great for JPEG/DNG and most Sony / Panasonic / Fujifilm / Nikon / older-Canon raws on one machine; see [docs/raw-parity.md](docs/raw-parity.md) for your camera. - **The biggest gaps:** - - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 raws have guarded estimates from their camera JPEGs, with built-in ILCA-99M2, ILCE-7CR, ILCE-7M4, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration is missing, and other raws or rejected fits retain a neutral matrix; + - **camera colour calibration:** Sony, Nikon, Panasonic, Fujifilm and Canon CR3 raws have guarded estimates from their camera JPEGs, with built-in ILCA-99M2, ILCE-7CR, ILCE-7M4, ILCE-7RM5, X-H2S, X-T4, Nikon Z 6II, Z 8 and D5300 profiles; measured calibration is missing, and other raws or rejected fits retain a neutral matrix; - **compressed Olympus raws and unsupported CR3 variants:** these use embedded JPEG previews when present. Fujifilm lossless/lossy compressed RAF now decodes sensor data; [verification and existing-library reload instructions](docs/raf-compression.md); - **AI quality and model setup:** Subject and Sky selection are classical heuristics; Object/Describe use an optional SAM 3 model. AI Denoise supports Bayer RAW with separately installed weights, while X-Trans and diff --git a/README.zh-hans.md b/README.zh-hans.md index 2800db410..2c6ca8191 100644 --- a/README.zh-hans.md +++ b/README.zh-hans.md @@ -305,7 +305,7 @@ LightCraft 很年轻,而且进展很快。**我们如实所处的位置**( - **作为日常使用的工具,我们更接近 60–70%。** 在单台机器上处理 JPEG/DNG 以及大多数尼康/索尼/ 较老佳能的 raw 都很出色。 - **最大的缺口:** - - **相机色彩校准:** 索尼、尼康、松下、富士和佳能 CR3 的 raw 会从各自的相机 JPEG 得到受保护的估计值,内置 ILCA-99M2、ILCE-7CR、ILCE-7M4、X-H2S、X-T4、尼康 Z 6II、Z 8 和 D5300 配置文件;实测校准仍然缺失,其他 raw 或被拒绝的拟合会保留中性矩阵; + - **相机色彩校准:** 索尼、尼康、松下、富士和佳能 CR3 的 raw 会从各自的相机 JPEG 得到受保护的估计值,内置 ILCA-99M2、ILCE-7CR、ILCE-7M4、ILCE-7RM5、X-H2S、X-T4、尼康 Z 6II、Z 8 和 D5300 配置文件;实测校准仍然缺失,其他 raw 或被拒绝的拟合会保留中性矩阵; - **压缩的奥林巴斯 raw 和不受支持的 CR3 变体:** 这些在有内嵌 JPEG 预览时使用预览。富士无损/有损压缩 RAF 现在能解码传感器数据;[验证与已有图库重新加载说明](docs/raf-compression.md); - **AI 蒙版与降噪:** 主体和天空选择目前是经典启发式算法; - **屏幕上的 HDR 显示、视频,以及经典版式中的打印/画册/地图模块。** diff --git a/README.zh-hant.md b/README.zh-hant.md index 12fcfb11b..ca624ce68 100644 --- a/README.zh-hant.md +++ b/README.zh-hant.md @@ -320,7 +320,7 @@ LightCraft 還很年輕,而且進展飛快:**Alpha 階段**。**老實說, 在單機上處理 JPEG/DNG,以及大多數 Sony/Panasonic/ Fujifilm/Nikon/較舊款 Canon 的 raw 都很出色;各機型詳情見 [docs/raw-parity.md](docs/raw-parity.md)。 - **最大的缺口:** - - **相機色彩校正:** Sony、Nikon、Panasonic、Fujifilm 和 Canon CR3 的 raw 會從各自的相機 JPEG 得到受保護的估計值,內建 ILCA-99M2、ILCE-7CR、ILCE-7M4、X-H2S、X-T4、Nikon Z 6II、Z 8 和 D5300 描述檔;仍缺少實測校正,其他 raw 或被拒絕的擬合會保留中性矩陣; + - **相機色彩校正:** Sony、Nikon、Panasonic、Fujifilm 和 Canon CR3 的 raw 會從各自的相機 JPEG 得到受保護的估計值,內建 ILCA-99M2、ILCE-7CR、ILCE-7M4、ILCE-7RM5、X-H2S、X-T4、Nikon Z 6II、Z 8 和 D5300 描述檔;仍缺少實測校正,其他 raw 或被拒絕的擬合會保留中性矩陣; - **壓縮的 Olympus raw 與不受支援的 CR3 變體:** 這些在有內嵌 JPEG 預覽時會改用預覽畫面。Fujifilm 無損/有損壓縮 RAF 現在能解碼感光元件資料;[驗證與現有照片庫重新載入說明](docs/raf-compression.md); - **AI 遮色片與降噪:** 主體與天空選取目前是傳統啟發式演算法; - **網路照片庫:** 預覽只讀取 raw 的標頭 — 或者,對於 CR3 或預覽標籤位於其後的 TIFF raw,只讀取稀疏規劃器指定的範圍 — 以及其內嵌的 JPEG(56.5 MB → 5.9 MB,10 秒 → 1 秒,測得於測試用的網路共用資料夾),但*顯影後*的畫面仍需要透過網路傳輸感光元件資料;詳見 [docs/raw-preview-reads.md](docs/raw-preview-reads.md); diff --git a/assets/ATTRIBUTION.md b/assets/ATTRIBUTION.md index 4bc2e927e..e5ed8090e 100644 --- a/assets/ATTRIBUTION.md +++ b/assets/ATTRIBUTION.md @@ -22,6 +22,7 @@ covers a whole directory), or if a licence file referenced in the Licence column | `assets/camera-profiles/NIKON_Z_6_2.json` | Nikon Z 6II camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 296 usable Z 6II NEFs of my own (of 299, 2024 to 2026, Standard and Auto picture controls; NIKKOR Z 20mm f/1.8 S, Z 24-70mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_Z_8.json` | Nikon Z 8 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 261 usable Z 8 NEFs of my own (of 278, 2023 and 2026, nearly all Standard picture control, lossless 14-bit; NIKKOR Z 24-120mm f/4 S and AF-S 70-200mm f/2.8G on the FTZ) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | | `assets/camera-profiles/NIKON_D5300.json` | Nikon D5300 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Michael Anderson (@endthestart) | original work: fitted by `lightcraft-cli calibrate` to 264 usable D5300 NEFs of my own (of 273, 2014 to 2017, mostly Standard picture control, lossy 12-bit, mostly the AF-S DX 35mm f/1.8G) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-13 | none | +| `assets/camera-profiles/ILCE-7RM5.json` | Sony ILCE-7RM5 camera colour profile (matrix + hue/saturation/value table), built into LightCraft | Yule (Brian) Wang (@peaperfish) | original work: fitted by `lightcraft-cli calibrate` to 220 usable ILCE-7RM5 ARWs of my own (of 241, 2025 to 2026, Standard creative style; Sigma 24-70mm F2.8 DG DN II Art and Sony FE 200-600mm F5.6-6.3 G OSS) and their in-camera JPEGs; aggregate colour statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 | 2026-10-26 | none | | `crates/raw/src/spectral_table.rs` | Camera colour matrices (DNG ColorMatrix1/2 and ForwardMatrix1/2 for Standard Illuminant A and D65) for 52 camera models, and the model alias list | Derived by LightCraft contributors from rawtoaces-data (Academy Software Foundation): camera spectral sensitivities measured by Weta Digital (44 cameras), scitech (7) and Jonathon Irons at the National Physical Laboratory (Nikon D5100); training reflectances; CIE 1931 colour-matching functions (CIE, CC-BY-4.0, as shipped in rawtoaces-data) | https://github.com/AcademySoftwareFoundation/rawtoaces-data, tag v1.1.0, commit 5cfa53707b053594b2d0d48e8019fb56e6582d6c | Apache-2.0 (`LICENSE-APACHE`); the colour-matching functions CC-BY-4.0 | 2026-10-08 | generated by `tools/spectral_camera_table.py`: a 3×3 matrix per illuminant fitted to the spectral data (no measurement copied verbatim); aliases copied from `data/aliases.json`; no Adobe or third-party camera matrix | | `assets/camera-tone/sony-dro-off.json` | Sony global tone curve with the Dynamic Range Optimizer off (32 knots), built into LightCraft for ARWs shot with DRO on | LightCraft contributors; source photographs by raw.pixls.us contributors | original work: tone curve quantile-matched over the pooled pixel pairs of 12 CC0 raw.pixls.us ARWs (DRO off, Standard creative style; ILCE-3500, -5000, -6600, -7CR, -7M3 ×3, -7S, -9M2, ILCA-77M2, SLT-A57, NEX-F3) and their embedded camera JPEGs; the file lists each source URL and SHA-256 and how the curve was derived (see also `docs/camera-preview-colour.md`); aggregate tone statistics only, no image content; no Adobe or third-party profile data | MIT OR Apache-2.0 (sources CC0) | 2026-10-09 | none | | `crates/scenes/` (generated images) | Procedural demo photographs | LightCraft contributors | original work (generated at runtime, no source imagery) | MIT OR Apache-2.0 | 2026-09-30 | n/a | diff --git a/assets/camera-profiles/ILCE-7RM5.json b/assets/camera-profiles/ILCE-7RM5.json new file mode 100644 index 000000000..787bfb062 --- /dev/null +++ b/assets/camera-profiles/ILCE-7RM5.json @@ -0,0 +1,9031 @@ +{ + "version": 1, + "model": "ILCE-7RM5", + "files": 220, + "samples": 790553, + "matrix": [ + [ + 1.249584732786861, + -0.3300570108987344, + 0.07738524309065298 + ], + [ + -0.09371116474358132, + 1.2038743929103433, + -0.1120131503658719 + ], + [ + -0.03422832387281005, + -0.8688172281950557, + 1.937872050399747 + ] + ], + "hue_sat": { + "hue_divisions": 72, + "sat_divisions": 5, + "val_divisions": 5, + "data": [ + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.6575432, + 1.0169075, + 1.0 + ], + [ + 0.045065638, + 1.0544671, + 1.0 + ], + [ + 0.2621381, + 1.0540911, + 1.0 + ], + [ + 0.5927048, + 1.0288227, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.568119, + 1.0037564, + 1.0 + ], + [ + -0.4427149, + 1.0345306, + 1.0 + ], + [ + -0.41775778, + 1.0446787, + 1.0 + ], + [ + 0.086332865, + 1.0200883, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.272573, + 1.0058666, + 1.0 + ], + [ + -0.5253304, + 0.9976382, + 1.0 + ], + [ + -1.19747, + 1.0018761, + 1.0 + ], + [ + -0.79130363, + 0.98982924, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.8305438, + 1.013145, + 1.0 + ], + [ + -0.64092255, + 1.0028768, + 1.0 + ], + [ + -1.6476865, + 0.98830545, + 1.0 + ], + [ + -1.8518519, + 0.9801241, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9341725, + 1.02301, + 1.0 + ], + [ + -1.0185463, + 1.0157639, + 1.0 + ], + [ + -2.5776067, + 0.99842805, + 1.0 + ], + [ + -3.2994924, + 0.9785849, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.5423042, + 1.0483397, + 1.0 + ], + [ + -1.4740537, + 1.032285, + 1.0 + ], + [ + -3.7110608, + 1.0046949, + 1.0 + ], + [ + -5.396528, + 0.967783, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.6425831, + 1.0778131, + 1.0 + ], + [ + -2.4365096, + 1.0424513, + 1.0 + ], + [ + -4.570945, + 1.0063766, + 1.0 + ], + [ + -6.660486, + 0.95838326, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.14437525, + 1.0805831, + 1.0 + ], + [ + -3.1429949, + 1.0347922, + 1.0 + ], + [ + -4.7743, + 1.0044911, + 1.0 + ], + [ + -6.743695, + 0.9554359, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.7822553, + 1.099207, + 1.0 + ], + [ + -2.1562428, + 1.0492018, + 1.0 + ], + [ + -4.6814117, + 0.9976052, + 1.0 + ], + [ + -7.334759, + 0.9328078, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4186356, + 1.1193541, + 1.0 + ], + [ + -1.2648852, + 1.0724756, + 1.0 + ], + [ + -4.010651, + 1.001225, + 1.0 + ], + [ + -6.9021163, + 0.93410987, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0874636, + 1.1538868, + 1.0 + ], + [ + 0.23989654, + 1.0897437, + 1.0 + ], + [ + -2.9069233, + 1.0008082, + 1.0 + ], + [ + -6.942227, + 0.9103677, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.2319875, + 1.1964883, + 1.0 + ], + [ + 1.6361779, + 1.1026742, + 1.0 + ], + [ + -1.7622405, + 0.99407303, + 1.0 + ], + [ + -7.6075315, + 0.87470096, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.200221, + 1.2336057, + 1.0 + ], + [ + 2.4819555, + 1.1164554, + 1.0 + ], + [ + -0.67436194, + 0.9800342, + 1.0 + ], + [ + -6.7233605, + 0.8354304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7635767, + 1.258256, + 1.0 + ], + [ + 2.755172, + 1.1187295, + 1.0 + ], + [ + 0.58612347, + 0.95909715, + 1.0 + ], + [ + -3.4647129, + 0.8201187, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7450001, + 1.2654432, + 1.0 + ], + [ + 2.2866368, + 1.1127598, + 1.0 + ], + [ + 1.7782547, + 0.96788186, + 1.0 + ], + [ + 0.64800644, + 0.8437623, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4756403, + 1.2495352, + 1.0 + ], + [ + 1.8023143, + 1.1058493, + 1.0 + ], + [ + 2.4051156, + 0.97594136, + 1.0 + ], + [ + 3.1766913, + 0.8713939, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.15415932, + 1.2347906, + 1.0 + ], + [ + 1.3704019, + 1.0905327, + 1.0 + ], + [ + 2.8927534, + 0.95705473, + 1.0 + ], + [ + 4.9556255, + 0.85762197, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.45234466, + 1.2126856, + 1.0 + ], + [ + 1.3297142, + 1.0610285, + 1.0 + ], + [ + 3.5847495, + 0.92722595, + 1.0 + ], + [ + 6.0845156, + 0.84810686, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.0763642, + 1.1915482, + 1.0 + ], + [ + 1.087915, + 1.0379584, + 1.0 + ], + [ + 3.107092, + 0.9336573, + 1.0 + ], + [ + 4.6096416, + 0.87201273, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.8927168, + 1.1797738, + 1.0 + ], + [ + 0.3604307, + 1.0387832, + 1.0 + ], + [ + 1.8885725, + 0.9472845, + 1.0 + ], + [ + 2.5527375, + 0.8916758, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.8154569, + 1.1746649, + 1.0 + ], + [ + -0.75731444, + 1.0539931, + 1.0 + ], + [ + 0.15679865, + 0.99418485, + 1.0 + ], + [ + 0.26365867, + 0.97036415, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.6557531, + 1.1808895, + 1.0 + ], + [ + -2.146421, + 1.0872095, + 1.0 + ], + [ + -1.216422, + 1.0318732, + 1.0 + ], + [ + -0.14712709, + 1.0002339, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.503687, + 1.1711676, + 1.0 + ], + [ + -2.6333165, + 1.0882001, + 1.0 + ], + [ + -0.9562701, + 1.0260637, + 1.0 + ], + [ + -0.035742544, + 1.0006093, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.3313165, + 1.1779693, + 1.0 + ], + [ + -3.511713, + 1.0934967, + 1.0 + ], + [ + -0.77989405, + 1.0125425, + 1.0 + ], + [ + -0.15130846, + 0.9985428, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.7568846, + 1.1773652, + 1.0 + ], + [ + -4.3852744, + 1.0850232, + 1.0 + ], + [ + -2.9159627, + 0.9900688, + 1.0 + ], + [ + -2.1400974, + 0.9775968, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.7892175, + 1.1440657, + 1.0 + ], + [ + -4.395312, + 1.0594687, + 1.0 + ], + [ + -3.1968632, + 0.9905179, + 1.0 + ], + [ + -1.8385077, + 0.98149014, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.615582, + 1.1392046, + 1.0 + ], + [ + -4.00538, + 1.0335472, + 1.0 + ], + [ + -0.9200221, + 0.99787354, + 1.0 + ], + [ + -0.11120706, + 0.9992187, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.523595, + 1.1210346, + 1.0 + ], + [ + -4.8383403, + 1.0227709, + 1.0 + ], + [ + -1.0064363, + 0.9959147, + 1.0 + ], + [ + -0.049839716, + 0.99975395, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.567221, + 1.109696, + 1.0 + ], + [ + -3.7778788, + 1.0227439, + 1.0 + ], + [ + -0.3094657, + 0.99754596, + 1.0 + ], + [ + -0.01160631, + 0.9998476, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.9242926, + 1.1014805, + 1.0 + ], + [ + -2.6735833, + 1.0197709, + 1.0 + ], + [ + -0.10607915, + 0.9988134, + 1.0 + ], + [ + -0.0016143449, + 0.99994075, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.489351, + 1.0988349, + 1.0 + ], + [ + -2.0020316, + 1.0113986, + 1.0 + ], + [ + -0.060160246, + 0.9993989, + 1.0 + ], + [ + -0.00045636468, + 0.99998945, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.7909346, + 1.0950327, + 1.0 + ], + [ + -3.1690855, + 1.0192778, + 1.0 + ], + [ + -0.20098883, + 1.0008932, + 1.0 + ], + [ + -0.0010797405, + 1.0000042, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.812833, + 1.0921811, + 1.0 + ], + [ + -3.6212065, + 1.016884, + 1.0 + ], + [ + -0.2872315, + 1.000208, + 1.0 + ], + [ + -0.0022921215, + 0.9999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.7691603, + 1.0748369, + 1.0 + ], + [ + -2.2901673, + 1.0061964, + 1.0 + ], + [ + -0.2540361, + 0.9997461, + 1.0 + ], + [ + -0.002999882, + 0.9999967, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.285355, + 1.0752903, + 1.0 + ], + [ + -2.8977985, + 1.008037, + 1.0 + ], + [ + -0.389354, + 1.0001613, + 1.0 + ], + [ + -0.0048222253, + 1.0000017, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.331701, + 1.0774896, + 1.0 + ], + [ + -3.3062727, + 1.0165391, + 1.0 + ], + [ + -0.24283074, + 1.0005523, + 1.0 + ], + [ + -0.0019910862, + 1.0000027, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.720209, + 1.0690521, + 1.0 + ], + [ + -4.8786044, + 1.0175062, + 1.0 + ], + [ + -0.48560837, + 1.0014203, + 1.0 + ], + [ + -0.0033854088, + 1.0000086, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.556501, + 1.0456746, + 1.0 + ], + [ + -4.985604, + 1.0003452, + 1.0 + ], + [ + -0.41515103, + 0.9995448, + 1.0 + ], + [ + -0.003054782, + 0.9999918, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.729045, + 1.0217354, + 1.0 + ], + [ + -3.521123, + 0.98780435, + 1.0 + ], + [ + -0.30965137, + 0.99440813, + 1.0 + ], + [ + -0.008598875, + 0.9997742, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.221271, + 0.98520076, + 1.0 + ], + [ + -4.486184, + 0.97454756, + 1.0 + ], + [ + -0.41762093, + 0.9899926, + 1.0 + ], + [ + -0.03436083, + 0.9983804, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.70429, + 0.9197576, + 1.0 + ], + [ + -6.0378523, + 0.93986017, + 1.0 + ], + [ + -0.41832632, + 0.99216044, + 1.0 + ], + [ + -0.01641741, + 0.99925196, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.093513, + 0.8699881, + 1.0 + ], + [ + -6.8281274, + 0.9147011, + 1.0 + ], + [ + -0.30128792, + 0.99418074, + 1.0 + ], + [ + -0.0026745356, + 0.9999154, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.529678, + 0.84677386, + 1.0 + ], + [ + -7.347991, + 0.897593, + 1.0 + ], + [ + -0.39531615, + 0.9915446, + 1.0 + ], + [ + -0.000654911, + 0.9999173, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.297008, + 0.8246169, + 1.0 + ], + [ + -6.991395, + 0.8822463, + 1.0 + ], + [ + -0.8904233, + 0.98326087, + 1.0 + ], + [ + -0.005210178, + 0.99982196, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.1014814, + 0.870403, + 1.0 + ], + [ + -4.2737536, + 0.9510795, + 1.0 + ], + [ + -1.0143899, + 0.9928547, + 1.0 + ], + [ + 0.0069134138, + 0.9990259, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.2389834, + 1.0089513, + 1.0 + ], + [ + 0.59233487, + 1.0836766, + 1.0 + ], + [ + 2.109882, + 1.0953512, + 1.0 + ], + [ + 0.57811993, + 1.0165061, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.42605868, + 1.0736833, + 1.0 + ], + [ + 2.6112878, + 1.1127571, + 1.0 + ], + [ + 3.734751, + 1.1181016, + 1.0 + ], + [ + 2.895584, + 1.0769328, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.7645093, + 1.0585829, + 1.0 + ], + [ + 2.7074714, + 1.1086032, + 1.0 + ], + [ + 4.612825, + 1.121013, + 1.0 + ], + [ + 4.2159166, + 1.090578, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.317804, + 1.0096856, + 1.0 + ], + [ + 2.410078, + 1.0813844, + 1.0 + ], + [ + 4.022777, + 1.0866787, + 1.0 + ], + [ + 1.5960958, + 1.0285214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7581048, + 0.94621634, + 1.0 + ], + [ + 3.344702, + 1.031301, + 1.0 + ], + [ + 1.6271458, + 1.0360217, + 1.0 + ], + [ + 0.045802835, + 1.0040928, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.2102604, + 0.8877613, + 1.0 + ], + [ + 2.5063229, + 0.9831511, + 1.0 + ], + [ + 1.0589613, + 1.0087296, + 1.0 + ], + [ + 0.044849876, + 1.0006486, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8181234, + 0.84908885, + 1.0 + ], + [ + 1.2979459, + 0.9558577, + 1.0 + ], + [ + 0.95269287, + 0.9990587, + 1.0 + ], + [ + 0.13956869, + 0.99899775, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.4218108, + 0.8245256, + 1.0 + ], + [ + 0.980773, + 0.95178086, + 1.0 + ], + [ + 0.44153172, + 1.004565, + 1.0 + ], + [ + 0.083737634, + 0.9998738, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3112286, + 0.81245995, + 1.0 + ], + [ + 1.2433338, + 0.94633913, + 1.0 + ], + [ + -0.45147318, + 1.0128739, + 1.0 + ], + [ + -0.040671885, + 1.0012027, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.0959775, + 0.82676107, + 1.0 + ], + [ + 0.19708328, + 0.9494545, + 1.0 + ], + [ + -0.71126884, + 1.0130774, + 1.0 + ], + [ + -0.066266306, + 1.0014489, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0682087, + 0.85580033, + 1.0 + ], + [ + 0.33310115, + 0.9754608, + 1.0 + ], + [ + -0.038120847, + 1.0069447, + 1.0 + ], + [ + -0.0039369226, + 1.0006703, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4665134, + 0.8869471, + 1.0 + ], + [ + 1.394241, + 0.9861353, + 1.0 + ], + [ + 0.39206934, + 1.0029631, + 1.0 + ], + [ + 0.012310251, + 1.0002207, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.840249, + 0.885747, + 1.0 + ], + [ + 2.030714, + 0.97694564, + 1.0 + ], + [ + 0.48313063, + 0.9986679, + 1.0 + ], + [ + 0.015092909, + 1.0000398, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.364709, + 0.93935746, + 1.0 + ], + [ + 3.27353, + 1.0063933, + 1.0 + ], + [ + 0.47039714, + 1.0006171, + 1.0 + ], + [ + 0.0095254835, + 0.9999786, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.708454, + 0.9936273, + 1.0 + ], + [ + 3.0144715, + 1.0396764, + 1.0 + ], + [ + 0.76019174, + 1.0015196, + 1.0 + ], + [ + 0.031606715, + 0.9997963, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.081264, + 1.007644, + 1.0 + ], + [ + 0.9569218, + 1.0373055, + 1.0 + ], + [ + -0.29632938, + 1.0081192, + 1.0 + ], + [ + -0.020595783, + 1.0002273, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4333196, + 0.9922466, + 1.0 + ], + [ + 0.8624292, + 1.0051231, + 1.0 + ], + [ + -0.124487296, + 1.0007266, + 1.0 + ], + [ + 0.0014794656, + 1.0001725, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.0025764, + 0.9966404, + 1.0 + ], + [ + 0.32528183, + 1.0222802, + 1.0 + ], + [ + -1.4467791, + 1.0029583, + 1.0 + ], + [ + -0.09876483, + 1.0001086, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.9974155, + 0.9894437, + 1.0 + ], + [ + 2.0329673, + 1.0394019, + 1.0 + ], + [ + -0.49002603, + 1.0067779, + 1.0 + ], + [ + -0.091952406, + 0.9999813, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.90883, + 0.9736301, + 1.0 + ], + [ + 2.7153323, + 1.0357952, + 1.0 + ], + [ + 0.37018985, + 1.0083603, + 1.0 + ], + [ + -0.0096038, + 1.0001452, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.1170373, + 0.98292226, + 1.0 + ], + [ + 1.2927599, + 1.035257, + 1.0 + ], + [ + 0.3150436, + 1.0090348, + 1.0 + ], + [ + 0.0004185312, + 1.0000962, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3353724, + 0.9857392, + 1.0 + ], + [ + 0.9401049, + 1.0468453, + 1.0 + ], + [ + -0.13837458, + 1.0182233, + 1.0 + ], + [ + -0.024829892, + 1.0004286, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.5100236, + 1.0068982, + 1.0 + ], + [ + 0.93617123, + 1.0617714, + 1.0 + ], + [ + 0.047028802, + 1.0331767, + 1.0 + ], + [ + 0.0046950956, + 1.0010607, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.6971107, + 1.0113055, + 1.0 + ], + [ + -0.20543085, + 1.0561123, + 1.0 + ], + [ + -0.47802702, + 1.0383303, + 1.0 + ], + [ + 0.07525184, + 1.0021627, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8135715, + 1.0148535, + 1.0 + ], + [ + 0.23203266, + 1.0550131, + 1.0 + ], + [ + -0.023389658, + 1.04285, + 1.0 + ], + [ + 0.19465668, + 1.002981, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.6685414, + 1.0124575, + 1.0 + ], + [ + 0.59984475, + 1.0448725, + 1.0 + ], + [ + 0.69587445, + 1.0372711, + 1.0 + ], + [ + 0.60554105, + 1.005815, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5002728, + 1.0161368, + 1.0 + ], + [ + 0.56248856, + 1.058378, + 1.0 + ], + [ + 0.9347412, + 1.0511781, + 1.0 + ], + [ + 0.8927735, + 1.0201077, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8510935, + 1.0004079, + 1.0 + ], + [ + -0.13690601, + 1.0438355, + 1.0 + ], + [ + 0.5490022, + 1.0381058, + 1.0 + ], + [ + 1.4847147, + 1.0113307, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2298164, + 0.98695815, + 1.0 + ], + [ + -0.5338396, + 1.008648, + 1.0 + ], + [ + -0.1529312, + 1.0192995, + 1.0 + ], + [ + 1.3007274, + 0.98695505, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.9663947, + 0.9861699, + 1.0 + ], + [ + 0.3281501, + 0.9828017, + 1.0 + ], + [ + -0.9316485, + 0.9753089, + 1.0 + ], + [ + -0.45177975, + 0.9492595, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8109272, + 0.98814505, + 1.0 + ], + [ + -0.06094338, + 0.99206847, + 1.0 + ], + [ + -1.5175383, + 0.982531, + 1.0 + ], + [ + -1.9885272, + 0.954517, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.5236363, + 0.9898073, + 1.0 + ], + [ + -0.5354513, + 1.0057786, + 1.0 + ], + [ + -2.319607, + 1.0002798, + 1.0 + ], + [ + -3.4267511, + 0.97033256, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6655283, + 1.0100801, + 1.0 + ], + [ + -1.0105478, + 1.0113044, + 1.0 + ], + [ + -3.3712995, + 0.9907962, + 1.0 + ], + [ + -5.3614125, + 0.957974, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.55430204, + 1.0288806, + 1.0 + ], + [ + -2.2823086, + 1.0114845, + 1.0 + ], + [ + -4.29412, + 0.9850471, + 1.0 + ], + [ + -6.70813, + 0.94654834, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.28653958, + 1.029249, + 1.0 + ], + [ + -2.9026222, + 1.0021783, + 1.0 + ], + [ + -4.3481326, + 0.98601437, + 1.0 + ], + [ + -6.742446, + 0.94504565, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.840411, + 1.0304451, + 1.0 + ], + [ + -1.7543678, + 1.0166836, + 1.0 + ], + [ + -4.263933, + 0.98349094, + 1.0 + ], + [ + -7.782672, + 0.9144162, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9039959, + 1.0615926, + 1.0 + ], + [ + -0.6004897, + 1.0442605, + 1.0 + ], + [ + -3.2590501, + 0.9852762, + 1.0 + ], + [ + -6.0829635, + 0.92023736, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.113609, + 1.1155086, + 1.0 + ], + [ + 0.7005239, + 1.0668454, + 1.0 + ], + [ + -1.9628628, + 0.9854875, + 1.0 + ], + [ + -5.3814754, + 0.90371615, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.0877285, + 1.1595732, + 1.0 + ], + [ + 1.9560493, + 1.0802444, + 1.0 + ], + [ + -0.77529097, + 0.9800677, + 1.0 + ], + [ + -5.565922, + 0.8722543, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.288913, + 1.2051771, + 1.0 + ], + [ + 2.74089, + 1.1004237, + 1.0 + ], + [ + 0.012938409, + 0.9697918, + 1.0 + ], + [ + -5.393885, + 0.8341304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8245485, + 1.2282706, + 1.0 + ], + [ + 2.9895375, + 1.1039253, + 1.0 + ], + [ + 1.2145354, + 0.95314676, + 1.0 + ], + [ + -2.3480604, + 0.8217049, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0588024, + 1.2314723, + 1.0 + ], + [ + 2.7796125, + 1.0972726, + 1.0 + ], + [ + 2.4223437, + 0.9613367, + 1.0 + ], + [ + 1.6759906, + 0.8447568, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.8357061, + 1.210052, + 1.0 + ], + [ + 2.2408845, + 1.0848253, + 1.0 + ], + [ + 2.808005, + 0.9623734, + 1.0 + ], + [ + 3.4482584, + 0.8608864, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.69474894, + 1.1947756, + 1.0 + ], + [ + 1.6912163, + 1.0640128, + 1.0 + ], + [ + 3.070553, + 0.93303436, + 1.0 + ], + [ + 5.026965, + 0.83831453, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.02924209, + 1.1594467, + 1.0 + ], + [ + 1.437672, + 1.0297697, + 1.0 + ], + [ + 3.5909321, + 0.90887827, + 1.0 + ], + [ + 5.7714977, + 0.8360717, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.73282367, + 1.1322978, + 1.0 + ], + [ + 1.0370998, + 1.0152173, + 1.0 + ], + [ + 2.9553826, + 0.93621993, + 1.0 + ], + [ + 4.670117, + 0.86914015, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.619349, + 1.1252851, + 1.0 + ], + [ + 0.15239729, + 1.0244952, + 1.0 + ], + [ + 1.6071638, + 0.960652, + 1.0 + ], + [ + 2.7668586, + 0.8957443, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.4471934, + 1.1201713, + 1.0 + ], + [ + -0.9853024, + 1.0402389, + 1.0 + ], + [ + -0.10679894, + 1.0003766, + 1.0 + ], + [ + 0.38707855, + 0.9576168, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.6268017, + 1.1333272, + 1.0 + ], + [ + -2.5076745, + 1.071064, + 1.0 + ], + [ + -1.8380454, + 1.0375947, + 1.0 + ], + [ + -0.49257776, + 1.0004728, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.8864617, + 1.1463132, + 1.0 + ], + [ + -3.6842506, + 1.0934086, + 1.0 + ], + [ + -2.3747447, + 1.0497583, + 1.0 + ], + [ + -0.121673696, + 1.0016263, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.1770673, + 1.1691642, + 1.0 + ], + [ + -4.8939047, + 1.1140888, + 1.0 + ], + [ + -2.1122801, + 1.0347022, + 1.0 + ], + [ + -0.2781079, + 0.9971531, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.824204, + 1.1899806, + 1.0 + ], + [ + -6.7190065, + 1.1285441, + 1.0 + ], + [ + -5.1226296, + 0.9854947, + 1.0 + ], + [ + -4.016408, + 0.9494332, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.926484, + 1.1482611, + 1.0 + ], + [ + -7.6249037, + 1.0958742, + 1.0 + ], + [ + -5.6792645, + 0.9760871, + 1.0 + ], + [ + -4.0723734, + 0.94808024, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.670957, + 1.1153084, + 1.0 + ], + [ + -8.038458, + 1.0422579, + 1.0 + ], + [ + -5.8509502, + 0.9850574, + 1.0 + ], + [ + -0.8264564, + 0.9961184, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.342434, + 1.0856272, + 1.0 + ], + [ + -9.040556, + 1.0213751, + 1.0 + ], + [ + -7.0447855, + 0.9760523, + 1.0 + ], + [ + -0.8563134, + 0.9967798, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.15017, + 1.0738698, + 1.0 + ], + [ + -8.293659, + 1.0015079, + 1.0 + ], + [ + -3.465691, + 0.95468867, + 1.0 + ], + [ + -0.26872262, + 0.996449, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.361571, + 1.0671077, + 1.0 + ], + [ + -8.069062, + 0.9878739, + 1.0 + ], + [ + -1.8393602, + 0.9616174, + 1.0 + ], + [ + -0.062451784, + 0.99806386, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.813561, + 1.0733019, + 1.0 + ], + [ + -6.277449, + 0.9938862, + 1.0 + ], + [ + -0.91688836, + 0.98827964, + 1.0 + ], + [ + -0.015721286, + 0.99975455, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.033608, + 1.0607992, + 1.0 + ], + [ + -5.8924174, + 1.0059938, + 1.0 + ], + [ + -0.4630321, + 0.99918526, + 1.0 + ], + [ + -0.0028961068, + 0.9999799, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.24634, + 1.0676687, + 1.0 + ], + [ + -8.926855, + 1.0089554, + 1.0 + ], + [ + -1.2582607, + 0.998966, + 1.0 + ], + [ + -0.01142765, + 0.9999851, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.128018, + 1.0785146, + 1.0 + ], + [ + -8.119614, + 1.0094862, + 1.0 + ], + [ + -1.2649353, + 0.99898714, + 1.0 + ], + [ + -0.015170822, + 0.9999889, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.451852, + 1.0737165, + 1.0 + ], + [ + -6.9139895, + 1.0113754, + 1.0 + ], + [ + -1.088725, + 1.0001466, + 1.0 + ], + [ + -0.013777146, + 1.0000031, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -12.380149, + 1.0823865, + 1.0 + ], + [ + -6.0622687, + 1.0224757, + 1.0 + ], + [ + -0.49693757, + 1.0009257, + 1.0 + ], + [ + -0.004322603, + 1.0000051, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.628408, + 1.0830122, + 1.0 + ], + [ + -7.603063, + 1.0293987, + 1.0 + ], + [ + -0.7915703, + 1.0023602, + 1.0 + ], + [ + -0.005485728, + 1.0000141, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.070874, + 1.025278, + 1.0 + ], + [ + -8.004763, + 0.9920342, + 1.0 + ], + [ + -0.6927761, + 0.99896514, + 1.0 + ], + [ + -0.0051751113, + 0.99998105, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.791659, + 0.9914543, + 1.0 + ], + [ + -7.6757627, + 0.9670585, + 1.0 + ], + [ + -0.94827396, + 0.98121995, + 1.0 + ], + [ + -0.042875327, + 0.99850494, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.504122, + 0.918028, + 1.0 + ], + [ + -9.548977, + 0.89464694, + 1.0 + ], + [ + -2.6915827, + 0.9125134, + 1.0 + ], + [ + -0.46877646, + 0.9763951, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.834205, + 0.86167675, + 1.0 + ], + [ + -11.029793, + 0.8641353, + 1.0 + ], + [ + -2.304663, + 0.940338, + 1.0 + ], + [ + -0.22860166, + 0.9885806, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -15.163353, + 0.80446744, + 1.0 + ], + [ + -10.703714, + 0.82697487, + 1.0 + ], + [ + -1.8142037, + 0.948325, + 1.0 + ], + [ + -0.0477379, + 0.998123, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.573051, + 0.76530474, + 1.0 + ], + [ + -10.532188, + 0.808058, + 1.0 + ], + [ + -1.7215453, + 0.94441307, + 1.0 + ], + [ + -0.0065259486, + 0.99902344, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -11.712678, + 0.78053474, + 1.0 + ], + [ + -9.12928, + 0.8284735, + 1.0 + ], + [ + -3.0647676, + 0.9377219, + 1.0 + ], + [ + -0.02522077, + 0.9987442, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.4553695, + 0.88699067, + 1.0 + ], + [ + -4.486537, + 0.9664551, + 1.0 + ], + [ + -1.3492911, + 1.0004994, + 1.0 + ], + [ + 0.20210887, + 0.98605, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.5626588, + 1.0187794, + 1.0 + ], + [ + -0.16849272, + 1.0859944, + 1.0 + ], + [ + 1.1092399, + 1.1023259, + 1.0 + ], + [ + 1.3777262, + 1.0548018, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.040617283, + 1.066993, + 1.0 + ], + [ + 1.53646, + 1.1038263, + 1.0 + ], + [ + 2.3726995, + 1.106449, + 1.0 + ], + [ + 2.8319159, + 1.0958794, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8091955, + 1.042359, + 1.0 + ], + [ + 2.302053, + 1.0939299, + 1.0 + ], + [ + 3.1255443, + 1.0935813, + 1.0 + ], + [ + 3.7047086, + 1.0906761, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4341065, + 0.9560042, + 1.0 + ], + [ + 2.3559563, + 1.0608075, + 1.0 + ], + [ + 3.3579242, + 1.0881857, + 1.0 + ], + [ + 3.2111607, + 1.0706342, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0547562, + 0.8983519, + 1.0 + ], + [ + 3.383354, + 0.9991967, + 1.0 + ], + [ + 1.8556306, + 1.0425386, + 1.0 + ], + [ + 0.0885949, + 1.0138919, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7682338, + 0.8603291, + 1.0 + ], + [ + 3.0132825, + 0.9588983, + 1.0 + ], + [ + 1.9995759, + 1.0111612, + 1.0 + ], + [ + 0.47482064, + 1.006286, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6232177, + 0.8233142, + 1.0 + ], + [ + 2.0865932, + 0.9309424, + 1.0 + ], + [ + 2.7574844, + 0.97783035, + 1.0 + ], + [ + 0.95219344, + 0.99299604, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.6943194, + 0.79197764, + 1.0 + ], + [ + 1.8879877, + 0.920102, + 1.0 + ], + [ + 1.2647737, + 0.9986511, + 1.0 + ], + [ + 0.3897333, + 1.0005867, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.7574468, + 0.78670424, + 1.0 + ], + [ + 2.1780686, + 0.9121051, + 1.0 + ], + [ + -0.5525949, + 1.0213124, + 1.0 + ], + [ + -0.2346502, + 1.0064204, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.4999485, + 0.7841847, + 1.0 + ], + [ + 1.9435306, + 0.9184122, + 1.0 + ], + [ + -1.5851839, + 1.0305399, + 1.0 + ], + [ + -0.36774108, + 1.0069425, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.909163, + 0.80230415, + 1.0 + ], + [ + 2.1358707, + 0.935365, + 1.0 + ], + [ + -0.84193116, + 1.0221872, + 1.0 + ], + [ + -0.14351588, + 1.0037925, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.5533886, + 0.8221586, + 1.0 + ], + [ + 3.3628576, + 0.95415354, + 1.0 + ], + [ + 0.8394116, + 1.0065193, + 1.0 + ], + [ + 0.021697784, + 1.000693, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 8.034471, + 0.84136575, + 1.0 + ], + [ + 4.6422033, + 0.9475814, + 1.0 + ], + [ + 1.4912386, + 0.99571365, + 1.0 + ], + [ + 0.018330399, + 1.0000721, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.851116, + 0.9006105, + 1.0 + ], + [ + 5.2049785, + 0.9920339, + 1.0 + ], + [ + 1.2569566, + 0.99717367, + 1.0 + ], + [ + 0.0052652257, + 0.9997678, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.6648865, + 0.9650344, + 1.0 + ], + [ + 4.729117, + 1.0339372, + 1.0 + ], + [ + 2.091848, + 0.99583614, + 1.0 + ], + [ + 0.10322704, + 0.99907774, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.6680355, + 0.9877355, + 1.0 + ], + [ + 3.6761901, + 1.0451121, + 1.0 + ], + [ + -0.04351923, + 1.0153469, + 1.0 + ], + [ + -0.03283479, + 1.0004294, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.463548, + 0.96174765, + 1.0 + ], + [ + 2.6405559, + 1.0113807, + 1.0 + ], + [ + -0.0145218745, + 1.0030446, + 1.0 + ], + [ + 0.02793165, + 1.0006396, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.7405667, + 0.97143346, + 1.0 + ], + [ + 1.3641241, + 1.0326787, + 1.0 + ], + [ + -3.4862273, + 1.0015736, + 1.0 + ], + [ + -0.47250292, + 0.99943143, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 8.19507, + 0.96321476, + 1.0 + ], + [ + 3.4350495, + 1.058396, + 1.0 + ], + [ + -3.5258145, + 1.0094136, + 1.0 + ], + [ + -1.0841179, + 0.9979366, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.8372226, + 0.95261025, + 1.0 + ], + [ + 4.014715, + 1.0542111, + 1.0 + ], + [ + 0.26240677, + 1.0264678, + 1.0 + ], + [ + -0.21519896, + 1.0007783, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.1908545, + 0.95756376, + 1.0 + ], + [ + 1.8382027, + 1.0398521, + 1.0 + ], + [ + 0.32465455, + 1.0176383, + 1.0 + ], + [ + -0.07286978, + 0.99991214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.5853324, + 0.9502199, + 1.0 + ], + [ + 0.9021832, + 1.0475446, + 1.0 + ], + [ + -0.38347927, + 1.0296501, + 1.0 + ], + [ + -0.10875285, + 1.0017142, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.5904784, + 0.95001745, + 1.0 + ], + [ + 0.3187001, + 1.0552145, + 1.0 + ], + [ + -0.065457314, + 1.0441794, + 1.0 + ], + [ + 0.1682706, + 1.0044458, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.727416, + 0.9608402, + 1.0 + ], + [ + -0.4404374, + 1.0511845, + 1.0 + ], + [ + 0.13549781, + 1.0374392, + 1.0 + ], + [ + 1.0298581, + 1.0050887, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 7.1869535, + 0.96325994, + 1.0 + ], + [ + 0.21250498, + 1.0488186, + 1.0 + ], + [ + 0.5558834, + 1.0353485, + 1.0 + ], + [ + 1.3571175, + 1.0007496, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.2386184, + 0.97497666, + 1.0 + ], + [ + 0.37397635, + 1.0394269, + 1.0 + ], + [ + 0.7773281, + 1.034625, + 1.0 + ], + [ + 1.4875422, + 1.0045793, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.65055, + 0.98630524, + 1.0 + ], + [ + 0.3505162, + 1.0497316, + 1.0 + ], + [ + 1.0327646, + 1.0434405, + 1.0 + ], + [ + 1.5126252, + 1.0201807, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1305985, + 0.95614517, + 1.0 + ], + [ + -0.6686687, + 0.96170896, + 1.0 + ], + [ + 0.31405145, + 0.94526744, + 1.0 + ], + [ + 0.7571128, + 0.93466496, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7789507, + 0.96007, + 1.0 + ], + [ + 0.025442632, + 0.9322739, + 1.0 + ], + [ + 1.002043, + 0.92365426, + 1.0 + ], + [ + 1.4359783, + 0.9207567, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5217903, + 0.9649529, + 1.0 + ], + [ + 1.3698361, + 0.93013364, + 1.0 + ], + [ + 0.81097263, + 0.8966511, + 1.0 + ], + [ + 1.3134359, + 0.9056901, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.4688148, + 0.94516605, + 1.0 + ], + [ + 1.1258384, + 0.9285592, + 1.0 + ], + [ + -0.03493635, + 0.9379082, + 1.0 + ], + [ + -0.34919712, + 0.92182875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0855563, + 0.9416833, + 1.0 + ], + [ + 0.61865765, + 0.95953137, + 1.0 + ], + [ + -0.53801084, + 0.98488677, + 1.0 + ], + [ + -1.3929236, + 0.9545244, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.280524, + 0.9401836, + 1.0 + ], + [ + 0.5206633, + 0.91849095, + 1.0 + ], + [ + -0.5855471, + 0.9109686, + 1.0 + ], + [ + -1.857449, + 0.9108761, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.1066036, + 0.91177595, + 1.0 + ], + [ + -0.2379211, + 0.90215147, + 1.0 + ], + [ + -0.662678, + 0.8674564, + 1.0 + ], + [ + -1.6655282, + 0.8560494, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3806509, + 0.91369975, + 1.0 + ], + [ + -1.1977674, + 0.9291132, + 1.0 + ], + [ + -2.1955984, + 0.9157609, + 1.0 + ], + [ + -3.2753534, + 0.8795514, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.27487, + 0.8657369, + 1.0 + ], + [ + 0.84525156, + 0.8752064, + 1.0 + ], + [ + -1.1533172, + 0.88577944, + 1.0 + ], + [ + -4.5261264, + 0.8575033, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.6447818, + 0.8502878, + 1.0 + ], + [ + 1.9611719, + 0.87647694, + 1.0 + ], + [ + -0.25773066, + 0.88421917, + 1.0 + ], + [ + -2.0909984, + 0.84323335, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3915353, + 0.95410204, + 1.0 + ], + [ + 1.9980977, + 0.9531656, + 1.0 + ], + [ + 0.5931035, + 0.88896406, + 1.0 + ], + [ + -1.3361316, + 0.8371896, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.3264365, + 1.0299196, + 1.0 + ], + [ + 2.5364761, + 0.97348475, + 1.0 + ], + [ + 1.3221674, + 0.88718134, + 1.0 + ], + [ + -0.9853643, + 0.8285287, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1308208, + 1.0718769, + 1.0 + ], + [ + 2.790149, + 0.9912555, + 1.0 + ], + [ + 1.4749019, + 0.8944228, + 1.0 + ], + [ + -1.1555754, + 0.8163407, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2958386, + 1.088511, + 1.0 + ], + [ + 2.6916971, + 0.9975709, + 1.0 + ], + [ + 1.9740455, + 0.88601756, + 1.0 + ], + [ + 0.36857194, + 0.80993265, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.5474374, + 1.0788504, + 1.0 + ], + [ + 2.225414, + 0.97936237, + 1.0 + ], + [ + 1.8757105, + 0.88728917, + 1.0 + ], + [ + 1.5990429, + 0.8291341, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.2438161, + 1.036749, + 1.0 + ], + [ + 1.3613421, + 0.9619165, + 1.0 + ], + [ + 1.4527313, + 0.8871177, + 1.0 + ], + [ + 2.0332718, + 0.8303382, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.09551922, + 1.0317862, + 1.0 + ], + [ + 0.43391225, + 0.9460805, + 1.0 + ], + [ + 1.4488848, + 0.8737923, + 1.0 + ], + [ + 3.4753993, + 0.81625134, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.7019963, + 1.0077614, + 1.0 + ], + [ + -0.051761873, + 0.9282602, + 1.0 + ], + [ + 1.9012142, + 0.8732041, + 1.0 + ], + [ + 4.1987333, + 0.82554394, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.6378045, + 1.0135831, + 1.0 + ], + [ + -0.38506448, + 0.9445609, + 1.0 + ], + [ + 1.5109704, + 0.91002667, + 1.0 + ], + [ + 3.479174, + 0.8619353, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.4618201, + 1.033089, + 1.0 + ], + [ + -1.2937772, + 0.97869325, + 1.0 + ], + [ + 0.10574721, + 0.9512708, + 1.0 + ], + [ + 1.488456, + 0.92858106, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.1203666, + 1.0350168, + 1.0 + ], + [ + -2.2538068, + 0.99723995, + 1.0 + ], + [ + -1.1310667, + 0.97431004, + 1.0 + ], + [ + 0.084608465, + 0.98049074, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.3735642, + 1.0520235, + 1.0 + ], + [ + -3.6140828, + 1.0261108, + 1.0 + ], + [ + -1.7979896, + 1.003871, + 1.0 + ], + [ + -0.05716326, + 0.9986102, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.2478585, + 1.0614483, + 1.0 + ], + [ + -4.203233, + 1.0365448, + 1.0 + ], + [ + -1.1785761, + 1.0052958, + 1.0 + ], + [ + -0.012518613, + 0.999905, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -5.7618976, + 1.0709513, + 1.0 + ], + [ + -4.2346697, + 1.041181, + 1.0 + ], + [ + -0.55711687, + 1.0027059, + 1.0 + ], + [ + -0.012013823, + 0.99982893, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -7.164432, + 1.0977043, + 1.0 + ], + [ + -4.544374, + 1.0551589, + 1.0 + ], + [ + -0.9486156, + 1.0006344, + 1.0 + ], + [ + -0.2880987, + 0.99641603, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.89083, + 1.0878369, + 1.0 + ], + [ + -5.120464, + 1.0464156, + 1.0 + ], + [ + -0.8068461, + 0.99902976, + 1.0 + ], + [ + -0.2889861, + 0.9960283, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.618337, + 1.0371728, + 1.0 + ], + [ + -5.1906323, + 1.007005, + 1.0 + ], + [ + -2.135625, + 0.9941443, + 1.0 + ], + [ + -0.21111596, + 0.99932176, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.985677, + 1.0003744, + 1.0 + ], + [ + -5.3432465, + 0.98975044, + 1.0 + ], + [ + -2.6925411, + 0.99021024, + 1.0 + ], + [ + -0.2637423, + 0.99901223, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.557486, + 1.0012228, + 1.0 + ], + [ + -5.126186, + 0.97562605, + 1.0 + ], + [ + -1.487278, + 0.9767831, + 1.0 + ], + [ + -0.09417072, + 0.99855137, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.93245, + 0.9848428, + 1.0 + ], + [ + -5.005292, + 0.95344836, + 1.0 + ], + [ + -1.0024476, + 0.97530013, + 1.0 + ], + [ + -0.03428691, + 0.9989181, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -10.067035, + 0.99813294, + 1.0 + ], + [ + -3.760256, + 0.9666209, + 1.0 + ], + [ + -0.43569535, + 0.9912011, + 1.0 + ], + [ + -0.007490502, + 0.99986094, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.267269, + 0.975714, + 1.0 + ], + [ + -3.3425367, + 0.97395366, + 1.0 + ], + [ + -0.16193047, + 0.99658626, + 1.0 + ], + [ + -0.00091553305, + 0.9999671, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.993388, + 1.0000288, + 1.0 + ], + [ + -4.422481, + 0.985607, + 1.0 + ], + [ + -0.32398883, + 0.9973332, + 1.0 + ], + [ + -0.0023061063, + 0.99997354, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.084186, + 0.9982832, + 1.0 + ], + [ + -2.7218752, + 0.9938026, + 1.0 + ], + [ + -0.17255202, + 0.9993347, + 1.0 + ], + [ + -0.0015168872, + 0.99999547, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.26013, + 0.9777609, + 1.0 + ], + [ + -2.3467531, + 0.98704755, + 1.0 + ], + [ + -0.09092708, + 0.9993146, + 1.0 + ], + [ + -0.00067042705, + 0.9999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.507622, + 0.9864571, + 1.0 + ], + [ + -1.6202773, + 0.9965017, + 1.0 + ], + [ + -0.03161555, + 0.999892, + 1.0 + ], + [ + -0.00015287292, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.677804, + 0.9922909, + 1.0 + ], + [ + -1.5864677, + 1.0017024, + 1.0 + ], + [ + -0.029533267, + 1.0000547, + 1.0 + ], + [ + -0.00012411954, + 1.0000002, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.914085, + 0.95310205, + 1.0 + ], + [ + -2.334698, + 0.99508375, + 1.0 + ], + [ + -0.04895491, + 0.99995136, + 1.0 + ], + [ + -0.00017408784, + 0.9999993, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.237178, + 0.8746589, + 1.0 + ], + [ + -3.9431214, + 0.96841735, + 1.0 + ], + [ + -0.13730685, + 0.99794483, + 1.0 + ], + [ + -0.0053191665, + 0.999769, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -14.975474, + 0.80612254, + 1.0 + ], + [ + -5.441822, + 0.93426883, + 1.0 + ], + [ + -0.5847607, + 0.97942346, + 1.0 + ], + [ + -0.0973578, + 0.99491835, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -16.96701, + 0.7174082, + 1.0 + ], + [ + -7.501978, + 0.8796432, + 1.0 + ], + [ + -0.55444115, + 0.9846425, + 1.0 + ], + [ + -0.0493976, + 0.9975046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -19.691572, + 0.6349826, + 1.0 + ], + [ + -11.398152, + 0.7708063, + 1.0 + ], + [ + -0.78708935, + 0.97114944, + 1.0 + ], + [ + -0.013223002, + 0.99905497, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -18.446426, + 0.61982644, + 1.0 + ], + [ + -14.164779, + 0.6866288, + 1.0 + ], + [ + -1.6122752, + 0.95558673, + 1.0 + ], + [ + -0.004578955, + 0.9995476, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.488046, + 0.67327017, + 1.0 + ], + [ + -11.041826, + 0.6985784, + 1.0 + ], + [ + -3.797718, + 0.8836202, + 1.0 + ], + [ + -0.021354746, + 0.9991143, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.215271, + 0.79168403, + 1.0 + ], + [ + -5.9288893, + 0.82115835, + 1.0 + ], + [ + -3.627646, + 0.8675918, + 1.0 + ], + [ + -0.10416387, + 0.9819024, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -3.4996793, + 0.90832794, + 1.0 + ], + [ + -1.8338926, + 0.95480883, + 1.0 + ], + [ + -1.2572361, + 0.96503127, + 1.0 + ], + [ + -0.7744646, + 0.9684802, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.1851404, + 0.9754471, + 1.0 + ], + [ + -0.38070896, + 0.9967761, + 1.0 + ], + [ + -0.20595744, + 0.99183595, + 1.0 + ], + [ + -0.04291819, + 0.991432, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.24601784, + 0.9362298, + 1.0 + ], + [ + 0.4131334, + 1.0015087, + 1.0 + ], + [ + 0.6097395, + 1.0106862, + 1.0 + ], + [ + 0.80979943, + 1.0157323, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.15171635, + 0.7862158, + 1.0 + ], + [ + 0.88354856, + 0.9524625, + 1.0 + ], + [ + 1.2969238, + 1.0249856, + 1.0 + ], + [ + 1.3480076, + 1.0304265, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.2697306, + 0.74219435, + 1.0 + ], + [ + 2.2566214, + 0.8504037, + 1.0 + ], + [ + 0.43116477, + 0.98642737, + 1.0 + ], + [ + -0.62197345, + 0.9856124, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.9409392, + 0.7197661, + 1.0 + ], + [ + 2.657014, + 0.82564926, + 1.0 + ], + [ + 0.75268286, + 0.9767613, + 1.0 + ], + [ + 0.055744026, + 0.9941756, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8314111, + 0.7361832, + 1.0 + ], + [ + 2.2688785, + 0.83848625, + 1.0 + ], + [ + 0.86895645, + 0.979231, + 1.0 + ], + [ + 0.0529236, + 1.0007346, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.6000655, + 0.7524115, + 1.0 + ], + [ + 2.0968463, + 0.888389, + 1.0 + ], + [ + 0.18316315, + 0.9902795, + 1.0 + ], + [ + -0.048968393, + 0.99920696, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.5598903, + 0.763862, + 1.0 + ], + [ + 1.1714306, + 0.8932834, + 1.0 + ], + [ + -0.57951224, + 0.985895, + 1.0 + ], + [ + -0.13511501, + 0.99782085, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.1605225, + 0.7730435, + 1.0 + ], + [ + 1.3596042, + 0.9008872, + 1.0 + ], + [ + -0.47168514, + 0.99376947, + 1.0 + ], + [ + -0.07464302, + 1.000353, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.752158, + 0.79429775, + 1.0 + ], + [ + 0.96804696, + 0.9385464, + 1.0 + ], + [ + -1.3032031, + 0.99664253, + 1.0 + ], + [ + -0.2467647, + 0.99934804, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.692927, + 0.817162, + 1.0 + ], + [ + 1.2399592, + 0.9601324, + 1.0 + ], + [ + -0.2092135, + 0.99869674, + 1.0 + ], + [ + -0.04914082, + 0.9998154, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.3077126, + 0.8646981, + 1.0 + ], + [ + 1.169301, + 0.9719851, + 1.0 + ], + [ + -0.14287929, + 0.9983894, + 1.0 + ], + [ + -0.070898615, + 0.9997087, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.741663, + 0.89205533, + 1.0 + ], + [ + 0.64960855, + 0.9830752, + 1.0 + ], + [ + -0.11191467, + 0.9987406, + 1.0 + ], + [ + -0.047313914, + 0.99977046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.5162182, + 0.94403803, + 1.0 + ], + [ + 0.07143193, + 0.9925508, + 1.0 + ], + [ + -0.72076577, + 0.9948075, + 1.0 + ], + [ + -0.1948217, + 0.9987798, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.585983, + 0.96052504, + 1.0 + ], + [ + 1.0876106, + 0.9904767, + 1.0 + ], + [ + -0.27299738, + 0.99780345, + 1.0 + ], + [ + -0.080156155, + 0.99953175, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.779256, + 0.9310572, + 1.0 + ], + [ + 0.7266878, + 0.986474, + 1.0 + ], + [ + 0.035173222, + 0.99929446, + 1.0 + ], + [ + 0.0016433608, + 1.0000198, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.0365615, + 0.94872975, + 1.0 + ], + [ + 0.30465418, + 0.9817023, + 1.0 + ], + [ + -0.34721652, + 0.99478245, + 1.0 + ], + [ + -0.061744135, + 0.99972767, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.8961987, + 0.92921335, + 1.0 + ], + [ + 0.23940657, + 0.9766385, + 1.0 + ], + [ + -1.319209, + 0.99436516, + 1.0 + ], + [ + -0.2552591, + 0.9993648, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.820354, + 0.9000567, + 1.0 + ], + [ + 0.93661356, + 0.9792745, + 1.0 + ], + [ + -1.2972772, + 1.0013816, + 1.0 + ], + [ + -0.2005057, + 1.0001276, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.8547032, + 0.8771825, + 1.0 + ], + [ + -0.40298477, + 0.92672235, + 1.0 + ], + [ + -0.76510406, + 0.983101, + 1.0 + ], + [ + -0.10773284, + 0.9988855, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.1437955, + 0.87208176, + 1.0 + ], + [ + -0.35798803, + 0.9345046, + 1.0 + ], + [ + -0.6719991, + 0.9889946, + 1.0 + ], + [ + -0.12287665, + 0.9989714, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.7768846, + 0.90311426, + 1.0 + ], + [ + -0.6762936, + 0.95233446, + 1.0 + ], + [ + -1.8836145, + 0.9920913, + 1.0 + ], + [ + -0.860416, + 0.9946867, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8961434, + 0.8824976, + 1.0 + ], + [ + -0.33391425, + 0.92908406, + 1.0 + ], + [ + -0.3221192, + 0.9918024, + 1.0 + ], + [ + -0.09044509, + 0.9966171, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 6.8632536, + 0.90186435, + 1.0 + ], + [ + 0.58749425, + 0.9437153, + 1.0 + ], + [ + 0.48553416, + 0.98712, + 1.0 + ], + [ + 0.19696854, + 0.99386424, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.8013225, + 0.9050866, + 1.0 + ], + [ + -0.49256903, + 0.9264555, + 1.0 + ], + [ + -1.7974619, + 0.9501046, + 1.0 + ], + [ + -1.5920177, + 0.97445244, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.4498076, + 0.92861384, + 1.0 + ], + [ + -0.7343135, + 0.9649644, + 1.0 + ], + [ + -1.2369624, + 0.9541305, + 1.0 + ], + [ + -1.1794292, + 0.9540083, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.9042714, + 0.9180204, + 1.0 + ], + [ + -2.219778, + 0.86444235, + 1.0 + ], + [ + -2.4816806, + 0.823798, + 1.0 + ], + [ + -1.5569929, + 0.8375031, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.83755153, + 0.91707826, + 1.0 + ], + [ + 0.22283489, + 0.86605746, + 1.0 + ], + [ + 0.4647223, + 0.8345799, + 1.0 + ], + [ + 0.72768146, + 0.84671277, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.523536, + 0.9087665, + 1.0 + ], + [ + 1.2768013, + 0.85424995, + 1.0 + ], + [ + 2.1177902, + 0.8432673, + 1.0 + ], + [ + 2.3680518, + 0.8740304, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2746108, + 0.81840515, + 1.0 + ], + [ + 2.7676985, + 0.7900528, + 1.0 + ], + [ + 1.9201541, + 0.8856915, + 1.0 + ], + [ + 1.8866074, + 0.9185119, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.6828327, + 0.8136951, + 1.0 + ], + [ + 2.175628, + 0.84009886, + 1.0 + ], + [ + 2.0939028, + 0.8847502, + 1.0 + ], + [ + 2.5929403, + 0.8947538, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.768817, + 0.7967659, + 1.0 + ], + [ + 3.2244751, + 0.7584971, + 1.0 + ], + [ + 3.6050847, + 0.7831417, + 1.0 + ], + [ + 4.268032, + 0.8045191, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.3562844, + 0.7890957, + 1.0 + ], + [ + 3.7188308, + 0.7199401, + 1.0 + ], + [ + 5.9207273, + 0.6976366, + 1.0 + ], + [ + 7.6120353, + 0.70215327, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.0832772, + 0.7600011, + 1.0 + ], + [ + 2.2862017, + 0.76166797, + 1.0 + ], + [ + 3.6557348, + 0.74157053, + 1.0 + ], + [ + 5.3892546, + 0.731114, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.7183886, + 0.6808121, + 1.0 + ], + [ + 3.3387003, + 0.70804834, + 1.0 + ], + [ + 3.3728793, + 0.7371294, + 1.0 + ], + [ + 4.041886, + 0.73437375, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.624325, + 0.6674578, + 1.0 + ], + [ + 4.5378666, + 0.6732078, + 1.0 + ], + [ + 4.398033, + 0.71216196, + 1.0 + ], + [ + 5.5895276, + 0.7182609, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 5.306699, + 0.747718, + 1.0 + ], + [ + 4.879655, + 0.70281416, + 1.0 + ], + [ + 5.1314225, + 0.7001976, + 1.0 + ], + [ + 5.486214, + 0.72053, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 4.146373, + 0.78986615, + 1.0 + ], + [ + 3.90747, + 0.7194833, + 1.0 + ], + [ + 4.436473, + 0.7135913, + 1.0 + ], + [ + 3.9732492, + 0.7439657, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.051203, + 0.8220685, + 1.0 + ], + [ + 2.5377185, + 0.73653644, + 1.0 + ], + [ + 3.6690881, + 0.73991066, + 1.0 + ], + [ + 2.8405402, + 0.76611686, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.0629168, + 0.88988215, + 1.0 + ], + [ + 1.5405259, + 0.80136687, + 1.0 + ], + [ + 2.3380709, + 0.755409, + 1.0 + ], + [ + 1.6457901, + 0.78002536, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.9272873, + 0.8686442, + 1.0 + ], + [ + 0.6143116, + 0.7969848, + 1.0 + ], + [ + 0.5216986, + 0.7725941, + 1.0 + ], + [ + 0.27696848, + 0.79700214, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.18347983, + 0.7820147, + 1.0 + ], + [ + -0.2920065, + 0.78137934, + 1.0 + ], + [ + -0.7294265, + 0.8024376, + 1.0 + ], + [ + -0.6821398, + 0.81814516, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.23992704, + 0.7879301, + 1.0 + ], + [ + -0.88038236, + 0.8075125, + 1.0 + ], + [ + -0.9809185, + 0.8284354, + 1.0 + ], + [ + -0.07751103, + 0.8428534, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.80605406, + 0.8354792, + 1.0 + ], + [ + -1.559409, + 0.80948555, + 1.0 + ], + [ + -0.7388032, + 0.83500856, + 1.0 + ], + [ + 0.69942343, + 0.88329875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.6294653, + 0.85172844, + 1.0 + ], + [ + -1.7645487, + 0.8297184, + 1.0 + ], + [ + -0.7100293, + 0.8840055, + 1.0 + ], + [ + 0.2274858, + 0.9601816, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.2752452, + 0.8992305, + 1.0 + ], + [ + -2.2524478, + 0.9024475, + 1.0 + ], + [ + -0.6900573, + 0.96735644, + 1.0 + ], + [ + -0.01443623, + 0.99743444, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.3342621, + 0.9171061, + 1.0 + ], + [ + -2.169138, + 0.9233468, + 1.0 + ], + [ + -0.38522747, + 0.9775049, + 1.0 + ], + [ + 0.0013864042, + 0.9985635, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.9786882, + 0.9581507, + 1.0 + ], + [ + -1.0984265, + 0.9696264, + 1.0 + ], + [ + -0.10668975, + 0.9950265, + 1.0 + ], + [ + -0.0001844623, + 0.99983454, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.8451796, + 0.9728855, + 1.0 + ], + [ + -0.75453144, + 0.9875004, + 1.0 + ], + [ + -0.03869543, + 0.998888, + 1.0 + ], + [ + -0.00016324317, + 0.99999046, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.4833093, + 0.9710839, + 1.0 + ], + [ + -0.4503776, + 0.99415845, + 1.0 + ], + [ + -0.014969094, + 0.9998039, + 1.0 + ], + [ + -0.00005460353, + 0.99999875, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.155806, + 0.9922136, + 1.0 + ], + [ + -0.24781401, + 0.9983759, + 1.0 + ], + [ + -0.009547606, + 0.9999699, + 1.0 + ], + [ + -0.00066759216, + 0.9999967, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.4516653, + 0.99805427, + 1.0 + ], + [ + -0.20918414, + 0.999779, + 1.0 + ], + [ + -0.006043913, + 0.9999931, + 1.0 + ], + [ + -0.00048581572, + 0.99999654, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.9392998, + 0.98668915, + 1.0 + ], + [ + -0.14349174, + 0.99852246, + 1.0 + ], + [ + -0.01090069, + 0.99995995, + 1.0 + ], + [ + -0.00083713053, + 0.99999756, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.1864663, + 0.9834442, + 1.0 + ], + [ + -0.18628179, + 0.99777365, + 1.0 + ], + [ + -0.014109625, + 0.99992806, + 1.0 + ], + [ + -0.001064983, + 0.9999959, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.0547693, + 0.98770183, + 1.0 + ], + [ + -0.17180885, + 0.9951577, + 1.0 + ], + [ + -0.014593571, + 0.99926215, + 1.0 + ], + [ + -0.00052392326, + 0.99998504, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.3548598, + 0.97179633, + 1.0 + ], + [ + -0.51838243, + 0.95328885, + 1.0 + ], + [ + -0.06852023, + 0.99208575, + 1.0 + ], + [ + -0.00085176574, + 0.99991536, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.3932482, + 0.9842496, + 1.0 + ], + [ + -0.21709155, + 0.9691371, + 1.0 + ], + [ + -0.014957193, + 0.99505883, + 1.0 + ], + [ + -0.00015723599, + 0.99995285, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.95742047, + 0.9800242, + 1.0 + ], + [ + -0.09572666, + 0.9719048, + 1.0 + ], + [ + -0.0004223666, + 0.9954867, + 1.0 + ], + [ + 0.000010266345, + 0.9999555, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8219641, + 0.97176534, + 1.0 + ], + [ + -0.10358055, + 0.9572014, + 1.0 + ], + [ + -0.0028769998, + 0.99260867, + 1.0 + ], + [ + -0.0000138675605, + 0.9999241, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.81078154, + 0.9853913, + 1.0 + ], + [ + -0.07060721, + 0.99701566, + 1.0 + ], + [ + -0.0011506415, + 0.99966466, + 1.0 + ], + [ + -0.0000053977897, + 0.99999666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8799515, + 0.9880938, + 1.0 + ], + [ + -0.09139433, + 0.99895227, + 1.0 + ], + [ + -0.0011206032, + 0.99998325, + 1.0 + ], + [ + -0.0000023958048, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -1.203021, + 0.98468393, + 1.0 + ], + [ + -0.09533536, + 0.9993307, + 1.0 + ], + [ + -0.0006138411, + 0.9999971, + 1.0 + ], + [ + -4.9110037e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.98682314, + 0.98741007, + 1.0 + ], + [ + -0.07803176, + 0.99942106, + 1.0 + ], + [ + -0.0005139681, + 0.9999974, + 1.0 + ], + [ + -3.048391e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.0533085, + 0.95174795, + 1.0 + ], + [ + -0.15526569, + 0.9967907, + 1.0 + ], + [ + -0.0011854534, + 0.9999825, + 1.0 + ], + [ + -0.0000012432189, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.3160176, + 0.8893779, + 1.0 + ], + [ + -0.37086096, + 0.99144685, + 1.0 + ], + [ + -0.0031809295, + 0.9999518, + 1.0 + ], + [ + -0.000015129414, + 0.9999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.65379, + 0.80011606, + 1.0 + ], + [ + -0.97619027, + 0.98192537, + 1.0 + ], + [ + -0.011185538, + 0.9998816, + 1.0 + ], + [ + -0.00027599925, + 0.999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.900999, + 0.6643516, + 1.0 + ], + [ + -2.2590377, + 0.93988913, + 1.0 + ], + [ + -0.021305703, + 0.99952126, + 1.0 + ], + [ + -0.00016454396, + 0.9999922, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -18.50311, + 0.5672969, + 1.0 + ], + [ + -6.324091, + 0.8226143, + 1.0 + ], + [ + -0.09798097, + 0.99709153, + 1.0 + ], + [ + -0.0001432513, + 0.99998575, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -17.64413, + 0.5391097, + 1.0 + ], + [ + -10.467655, + 0.66710144, + 1.0 + ], + [ + -0.2899716, + 0.9890324, + 1.0 + ], + [ + -0.00047773414, + 0.9999807, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -13.92279, + 0.5472615, + 1.0 + ], + [ + -10.895535, + 0.59090734, + 1.0 + ], + [ + -2.0764418, + 0.9041057, + 1.0 + ], + [ + -0.006843486, + 0.9997131, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.823085, + 0.59954053, + 1.0 + ], + [ + -8.007232, + 0.64031166, + 1.0 + ], + [ + -5.934222, + 0.7216933, + 1.0 + ], + [ + -0.21140227, + 0.98925734, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.0854692, + 0.68398213, + 1.0 + ], + [ + -4.9988737, + 0.7475099, + 1.0 + ], + [ + -4.5387487, + 0.783282, + 1.0 + ], + [ + -3.1189833, + 0.85521823, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.5944576, + 0.7649848, + 1.0 + ], + [ + -4.2297106, + 0.8085376, + 1.0 + ], + [ + -4.0575027, + 0.8316775, + 1.0 + ], + [ + -3.6946223, + 0.857067, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.07587, + 0.76617646, + 1.0 + ], + [ + -3.7157166, + 0.84208333, + 1.0 + ], + [ + -3.0963829, + 0.88380915, + 1.0 + ], + [ + -2.4804156, + 0.9129343, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.87624, + 0.6232938, + 1.0 + ], + [ + -2.6126328, + 0.7971155, + 1.0 + ], + [ + -1.7279347, + 0.92270774, + 1.0 + ], + [ + -1.2290946, + 0.95807064, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.7994735, + 0.57053953, + 1.0 + ], + [ + -0.84233916, + 0.6636242, + 1.0 + ], + [ + -2.5714352, + 0.87382954, + 1.0 + ], + [ + -3.015385, + 0.8965249, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.3739244, + 0.61843336, + 1.0 + ], + [ + 0.4184842, + 0.76101744, + 1.0 + ], + [ + -0.6304034, + 0.94809633, + 1.0 + ], + [ + -0.7494771, + 0.9597989, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.97610384, + 0.7327941, + 1.0 + ], + [ + -0.014398668, + 0.87566924, + 1.0 + ], + [ + -0.052118707, + 0.9924021, + 1.0 + ], + [ + 0.008521295, + 0.99918205, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.5996346, + 0.8233891, + 1.0 + ], + [ + 0.3935372, + 0.9229845, + 1.0 + ], + [ + -0.09391027, + 0.99190533, + 1.0 + ], + [ + -0.03780514, + 0.998876, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.9291275, + 0.9039172, + 1.0 + ], + [ + 0.74847955, + 0.9787055, + 1.0 + ], + [ + -0.16242525, + 0.9931886, + 1.0 + ], + [ + -0.072900325, + 0.9978617, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.3570204, + 0.94836956, + 1.0 + ], + [ + 0.2503319, + 0.99067676, + 1.0 + ], + [ + -0.06907828, + 0.9989688, + 1.0 + ], + [ + -0.017280824, + 0.9998503, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.26804498, + 0.9617563, + 1.0 + ], + [ + -0.30944353, + 0.9933201, + 1.0 + ], + [ + -0.83434904, + 0.9959094, + 1.0 + ], + [ + -0.16132122, + 0.999225, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.45194325, + 0.97071046, + 1.0 + ], + [ + -0.040852666, + 0.99767536, + 1.0 + ], + [ + -0.18153699, + 0.9991148, + 1.0 + ], + [ + -0.03368746, + 0.99983805, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.58231443, + 0.987187, + 1.0 + ], + [ + -0.11279778, + 0.9996504, + 1.0 + ], + [ + -0.007223121, + 0.999977, + 1.0 + ], + [ + -0.0013776413, + 0.9999941, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.27319077, + 0.9664841, + 1.0 + ], + [ + -0.21705957, + 0.9958635, + 1.0 + ], + [ + -0.017606068, + 0.99986523, + 1.0 + ], + [ + -0.0026193615, + 0.9999856, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.21634439, + 0.9787581, + 1.0 + ], + [ + -0.2651225, + 0.9962405, + 1.0 + ], + [ + -0.19124968, + 0.99883384, + 1.0 + ], + [ + -0.042182684, + 0.99975127, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.008343396, + 0.96074563, + 1.0 + ], + [ + -0.06034358, + 0.99365026, + 1.0 + ], + [ + -0.07409725, + 0.99946153, + 1.0 + ], + [ + -0.0167451, + 0.99990094, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.21916604, + 0.97861654, + 1.0 + ], + [ + 0.011656337, + 0.9968962, + 1.0 + ], + [ + 0.000026073394, + 0.9999265, + 1.0 + ], + [ + -0.00011303921, + 0.9999988, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8516588, + 0.983058, + 1.0 + ], + [ + 0.05317016, + 0.9960106, + 1.0 + ], + [ + 0.0021729777, + 0.9996684, + 1.0 + ], + [ + -0.00008834622, + 0.9999944, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.33492264, + 0.97982824, + 1.0 + ], + [ + -0.023441888, + 0.9941379, + 1.0 + ], + [ + -0.012700134, + 0.9997447, + 1.0 + ], + [ + -0.0015705364, + 0.9999959, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.98577476, + 0.9558412, + 1.0 + ], + [ + 0.11146073, + 0.9861902, + 1.0 + ], + [ + -0.039014447, + 0.9995021, + 1.0 + ], + [ + -0.004039938, + 0.9999989, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 1.4198768, + 0.92014015, + 1.0 + ], + [ + 0.017510591, + 0.95569116, + 1.0 + ], + [ + -0.07619311, + 0.99643916, + 1.0 + ], + [ + -0.0026185187, + 0.99994844, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.02523843, + 0.90478045, + 1.0 + ], + [ + -1.0690092, + 0.92566043, + 1.0 + ], + [ + -0.27379805, + 0.99030024, + 1.0 + ], + [ + -0.012234708, + 0.9998211, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.112236924, + 0.94679946, + 1.0 + ], + [ + -0.619657, + 0.96057886, + 1.0 + ], + [ + -0.9257425, + 0.9906025, + 1.0 + ], + [ + -0.29495418, + 0.99792683, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.21400073, + 0.91529566, + 1.0 + ], + [ + -0.7458604, + 0.93239814, + 1.0 + ], + [ + -1.0380262, + 0.98454165, + 1.0 + ], + [ + -0.59706265, + 0.99349016, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.4457225, + 0.91300154, + 1.0 + ], + [ + -1.336635, + 0.9316419, + 1.0 + ], + [ + -2.2979734, + 0.9655022, + 1.0 + ], + [ + -1.344974, + 0.9845655, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.25027204, + 0.9015076, + 1.0 + ], + [ + -3.4001799, + 0.8766961, + 1.0 + ], + [ + -6.167441, + 0.87958527, + 1.0 + ], + [ + -3.0434742, + 0.9592666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8902801, + 0.87916595, + 1.0 + ], + [ + -3.0949912, + 0.86760527, + 1.0 + ], + [ + -5.8532324, + 0.81513554, + 1.0 + ], + [ + -4.114237, + 0.86317617, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.026975641, + 0.99708, + 1.0 + ], + [ + -0.64516276, + 0.9682286, + 1.0 + ], + [ + -2.8310719, + 0.86257124, + 1.0 + ], + [ + -1.7226968, + 0.90401584, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.082811065, + 0.9958276, + 1.0 + ], + [ + 0.029035931, + 0.98919505, + 1.0 + ], + [ + -0.10363939, + 0.9313849, + 1.0 + ], + [ + 0.010599485, + 0.93757856, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.30115137, + 0.995358, + 1.0 + ], + [ + 0.13033178, + 0.99306947, + 1.0 + ], + [ + 0.6179642, + 0.9668538, + 1.0 + ], + [ + 0.8925493, + 0.96109754, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.46534216, + 0.9799625, + 1.0 + ], + [ + 0.38193727, + 0.97773105, + 1.0 + ], + [ + 0.24684222, + 0.9894079, + 1.0 + ], + [ + 0.37070882, + 0.987069, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.28666112, + 0.9715252, + 1.0 + ], + [ + 0.6129641, + 0.9612684, + 1.0 + ], + [ + 1.5519472, + 0.9354917, + 1.0 + ], + [ + 2.0365298, + 0.9501482, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.5976872, + 0.95618755, + 1.0 + ], + [ + 2.4345472, + 0.8642554, + 1.0 + ], + [ + 4.334619, + 0.8086935, + 1.0 + ], + [ + 4.563015, + 0.845538, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.8365076, + 0.92973554, + 1.0 + ], + [ + 4.732671, + 0.7514679, + 1.0 + ], + [ + 8.749074, + 0.67412585, + 1.0 + ], + [ + 9.21404, + 0.70109785, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.420841, + 0.77831614, + 1.0 + ], + [ + 3.1613727, + 0.7190518, + 1.0 + ], + [ + 5.003848, + 0.7066909, + 1.0 + ], + [ + 6.8984137, + 0.7076504, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 3.2887714, + 0.6649921, + 1.0 + ], + [ + 3.2296984, + 0.6861676, + 1.0 + ], + [ + 3.542693, + 0.7290553, + 1.0 + ], + [ + 3.7580287, + 0.7677797, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 2.8353093, + 0.7206155, + 1.0 + ], + [ + 3.213582, + 0.7210075, + 1.0 + ], + [ + 3.050782, + 0.7906282, + 1.0 + ], + [ + 2.910971, + 0.86843896, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.73087895, + 0.9480793, + 1.0 + ], + [ + 1.3393304, + 0.90520597, + 1.0 + ], + [ + 1.8712325, + 0.8813981, + 1.0 + ], + [ + 1.7535455, + 0.9178119, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.2656817, + 0.9784777, + 1.0 + ], + [ + 0.6957168, + 0.93848366, + 1.0 + ], + [ + 1.0034746, + 0.92618793, + 1.0 + ], + [ + 0.6805362, + 0.95591986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.07101682, + 0.9880643, + 1.0 + ], + [ + 0.3550513, + 0.96534646, + 1.0 + ], + [ + 0.7367733, + 0.952579, + 1.0 + ], + [ + 0.41733772, + 0.96733385, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.021202909, + 0.99511874, + 1.0 + ], + [ + 0.03845068, + 0.98727, + 1.0 + ], + [ + 0.13032648, + 0.9788461, + 1.0 + ], + [ + 0.08247721, + 0.9886881, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.019193187, + 0.99454707, + 1.0 + ], + [ + -0.01639945, + 0.9824369, + 1.0 + ], + [ + -0.034814365, + 0.97299594, + 1.0 + ], + [ + 0.000092838956, + 0.9879655, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0020077226, + 0.98501676, + 1.0 + ], + [ + -0.01932977, + 0.98477453, + 1.0 + ], + [ + -0.08714962, + 0.9838237, + 1.0 + ], + [ + -0.05911569, + 0.99138635, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.028955147, + 0.9820292, + 1.0 + ], + [ + -0.0068421336, + 0.9893989, + 1.0 + ], + [ + -0.066570766, + 0.99078333, + 1.0 + ], + [ + -0.044429842, + 0.995392, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.032754235, + 0.99281675, + 1.0 + ], + [ + -0.024249062, + 0.9940026, + 1.0 + ], + [ + -0.016773215, + 0.99763656, + 1.0 + ], + [ + -0.00368944, + 0.99956965, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.013820456, + 0.9963605, + 1.0 + ], + [ + -0.023061087, + 0.99682546, + 1.0 + ], + [ + -0.006765129, + 0.99912953, + 1.0 + ], + [ + -0.00025614, + 0.99994946, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.03831513, + 0.9965622, + 1.0 + ], + [ + -0.026358912, + 0.9984241, + 1.0 + ], + [ + -0.0031044614, + 0.99983025, + 1.0 + ], + [ + -0.00004853841, + 0.99999666, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.042360652, + 0.99709636, + 1.0 + ], + [ + -0.02741898, + 0.99857044, + 1.0 + ], + [ + -0.0024811549, + 0.99984425, + 1.0 + ], + [ + -0.0000068021504, + 0.99999624, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.015694104, + 0.99947315, + 1.0 + ], + [ + -0.004300875, + 0.9996656, + 1.0 + ], + [ + -0.00022572257, + 0.99996036, + 1.0 + ], + [ + -2.7103656e-8, + 0.9999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.031804975, + 0.9993172, + 1.0 + ], + [ + -0.0036146475, + 0.9998796, + 1.0 + ], + [ + -0.00006266851, + 0.9999905, + 1.0 + ], + [ + -2.0947002e-7, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.03432048, + 0.99876255, + 1.0 + ], + [ + -0.0027313528, + 0.999917, + 1.0 + ], + [ + -0.00002901146, + 0.99999934, + 1.0 + ], + [ + -5.272942e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.010446178, + 0.9998674, + 1.0 + ], + [ + -0.0009102314, + 0.9999884, + 1.0 + ], + [ + -0.000010674661, + 0.9999999, + 1.0 + ], + [ + -6.7580764e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0119908275, + 0.9999285, + 1.0 + ], + [ + -0.00078701315, + 0.9999947, + 1.0 + ], + [ + -0.0000061383644, + 0.99999994, + 1.0 + ], + [ + -3.791176e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.005170042, + 0.99979734, + 1.0 + ], + [ + -0.0004141604, + 0.9999835, + 1.0 + ], + [ + -0.0000034080406, + 0.9999999, + 1.0 + ], + [ + -4.582497e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.00895596, + 0.9998542, + 1.0 + ], + [ + -0.000709496, + 0.99998856, + 1.0 + ], + [ + -0.0000056245453, + 0.99999994, + 1.0 + ], + [ + -5.8625062e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0146880755, + 0.9997208, + 1.0 + ], + [ + -0.0018232282, + 0.9998647, + 1.0 + ], + [ + -0.00015326608, + 0.9999809, + 1.0 + ], + [ + -0.0000014805827, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.022968672, + 0.99913424, + 1.0 + ], + [ + -0.014809832, + 0.99827707, + 1.0 + ], + [ + -0.0020878739, + 0.9997331, + 1.0 + ], + [ + -0.000020184694, + 0.99999744, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.011431702, + 0.99970055, + 1.0 + ], + [ + -0.0039257053, + 0.999323, + 1.0 + ], + [ + -0.00048654518, + 0.9998965, + 1.0 + ], + [ + -0.000004697737, + 0.999999, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0041034487, + 0.99944717, + 1.0 + ], + [ + -0.00006548014, + 0.99873835, + 1.0 + ], + [ + 0.000027589553, + 0.9997964, + 1.0 + ], + [ + 2.7555294e-7, + 0.999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.009381899, + 0.9987364, + 1.0 + ], + [ + -0.00043260615, + 0.9972713, + 1.0 + ], + [ + 0.000017650782, + 0.9995557, + 1.0 + ], + [ + 2.0419672e-7, + 0.99999547, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.008616836, + 0.99971557, + 1.0 + ], + [ + -0.00045231776, + 0.9998694, + 1.0 + ], + [ + -9.984535e-7, + 0.9999807, + 1.0 + ], + [ + 7.740104e-9, + 0.9999998, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0056563327, + 0.9998474, + 1.0 + ], + [ + -0.00033137825, + 0.99999225, + 1.0 + ], + [ + -0.0000016325387, + 1.0, + 1.0 + ], + [ + -1.0257498e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.014827684, + 0.99960244, + 1.0 + ], + [ + -0.0007938461, + 0.99998146, + 1.0 + ], + [ + -0.00000293517, + 0.99999994, + 1.0 + ], + [ + -8.377253e-10, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.011590649, + 0.9997409, + 1.0 + ], + [ + -0.00065725995, + 0.9999863, + 1.0 + ], + [ + -0.0000025172164, + 0.99999994, + 1.0 + ], + [ + -7.089178e-10, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.11034412, + 0.99728817, + 1.0 + ], + [ + -0.006188652, + 0.9998468, + 1.0 + ], + [ + -0.000022063721, + 0.99999946, + 1.0 + ], + [ + -5.30563e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.11615519, + 0.9962155, + 1.0 + ], + [ + -0.006629864, + 0.9997772, + 1.0 + ], + [ + -0.000026530815, + 0.99999917, + 1.0 + ], + [ + -9.810042e-9, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.2699671, + 0.992111, + 1.0 + ], + [ + -0.01619867, + 0.99955577, + 1.0 + ], + [ + -0.0000889456, + 0.99999845, + 1.0 + ], + [ + -7.3219084e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.84938484, + 0.9749287, + 1.0 + ], + [ + -0.0546363, + 0.99835104, + 1.0 + ], + [ + -0.00042785166, + 0.99998784, + 1.0 + ], + [ + -6.3949227e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -2.9408712, + 0.91166395, + 1.0 + ], + [ + -0.28487697, + 0.99084896, + 1.0 + ], + [ + -0.0041404446, + 0.99987274, + 1.0 + ], + [ + -0.00000840042, + 0.99999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -6.1285825, + 0.7972419, + 1.0 + ], + [ + -0.82483685, + 0.96542543, + 1.0 + ], + [ + -0.007825383, + 0.99962455, + 1.0 + ], + [ + -0.0000082233155, + 0.9999996, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.914042, + 0.6662763, + 1.0 + ], + [ + -3.2296083, + 0.84786505, + 1.0 + ], + [ + -0.09797766, + 0.9948829, + 1.0 + ], + [ + -0.000322063, + 0.999986, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.49761, + 0.5991851, + 1.0 + ], + [ + -8.209785, + 0.6332046, + 1.0 + ], + [ + -2.2356362, + 0.88977855, + 1.0 + ], + [ + -0.03691413, + 0.9986572, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -8.16435, + 0.6105177, + 1.0 + ], + [ + -8.482127, + 0.6289409, + 1.0 + ], + [ + -8.195307, + 0.6653821, + 1.0 + ], + [ + -1.5681657, + 0.93180954, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.227832, + 0.6095772, + 1.0 + ], + [ + -9.688233, + 0.62955517, + 1.0 + ], + [ + -9.770227, + 0.6534116, + 1.0 + ], + [ + -7.684848, + 0.7312607, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -9.040249, + 0.61375934, + 1.0 + ], + [ + -9.414042, + 0.64601433, + 1.0 + ], + [ + -9.124609, + 0.6859369, + 1.0 + ], + [ + -6.8619404, + 0.76940507, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -4.1956196, + 0.65328926, + 1.0 + ], + [ + -6.4505258, + 0.6780245, + 1.0 + ], + [ + -6.7652173, + 0.777878, + 1.0 + ], + [ + -5.9136004, + 0.9036394, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.8208507, + 0.76508826, + 1.0 + ], + [ + -1.3313887, + 0.8808544, + 1.0 + ], + [ + -9.083516, + 0.86273944, + 1.0 + ], + [ + -10.492065, + 0.8505087, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.051133215, + 0.93782914, + 1.0 + ], + [ + -0.07260832, + 0.9849166, + 1.0 + ], + [ + -0.9264943, + 0.9826398, + 1.0 + ], + [ + -1.1677287, + 0.97983795, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.07751575, + 0.97048557, + 1.0 + ], + [ + -0.043188777, + 0.9823848, + 1.0 + ], + [ + -0.002534433, + 0.99882054, + 1.0 + ], + [ + -0.00013920956, + 0.9999524, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.07916742, + 0.97018796, + 1.0 + ], + [ + -0.031966496, + 0.97699416, + 1.0 + ], + [ + -0.0035643438, + 0.99836385, + 1.0 + ], + [ + -0.00040987533, + 0.99998116, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.01877379, + 0.9972593, + 1.0 + ], + [ + 0.01205297, + 0.9989108, + 1.0 + ], + [ + -0.0013474375, + 0.99986976, + 1.0 + ], + [ + -0.0007788474, + 0.9999769, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.00081947824, + 0.99965537, + 1.0 + ], + [ + 0.00408295, + 0.9999879, + 1.0 + ], + [ + -0.0004688936, + 0.9999954, + 1.0 + ], + [ + -0.00016395445, + 0.9999985, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0022904403, + 0.9998439, + 1.0 + ], + [ + -0.0030316855, + 0.9999691, + 1.0 + ], + [ + -0.008458131, + 0.9999591, + 1.0 + ], + [ + -0.0015517423, + 0.99999255, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.008486173, + 0.9998458, + 1.0 + ], + [ + 0.00014019103, + 0.99998724, + 1.0 + ], + [ + -0.0017433885, + 0.99999154, + 1.0 + ], + [ + -0.00032057063, + 0.99999845, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.005678298, + 0.9997919, + 1.0 + ], + [ + -0.0006329326, + 0.99999183, + 1.0 + ], + [ + -0.000014824767, + 1.0, + 1.0 + ], + [ + -0.0000014804741, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.024680974, + 0.9990245, + 1.0 + ], + [ + 0.0004288364, + 0.9999345, + 1.0 + ], + [ + -0.000051545532, + 0.99999934, + 1.0 + ], + [ + -0.000005476217, + 0.99999994, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0061284457, + 0.9994695, + 1.0 + ], + [ + -0.0008772137, + 0.9999514, + 1.0 + ], + [ + -0.0005471564, + 0.9999964, + 1.0 + ], + [ + -0.00011416538, + 0.99999934, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.019823374, + 0.99714184, + 1.0 + ], + [ + 0.0006233722, + 0.99973315, + 1.0 + ], + [ + -0.00020987287, + 0.99999684, + 1.0 + ], + [ + -0.000045174842, + 0.99999976, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0055233804, + 0.99932915, + 1.0 + ], + [ + 0.00019314369, + 0.999932, + 1.0 + ], + [ + -0.000004102488, + 0.9999994, + 1.0 + ], + [ + -3.2671397e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.017069608, + 0.99980605, + 1.0 + ], + [ + 0.0011378126, + 0.9999736, + 1.0 + ], + [ + 0.0000045061006, + 0.9999994, + 1.0 + ], + [ + 4.9558377e-8, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0025704305, + 0.9998648, + 1.0 + ], + [ + -0.00016181398, + 0.9999653, + 1.0 + ], + [ + -0.000009765289, + 0.9999992, + 1.0 + ], + [ + -2.9514266e-7, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.011738374, + 0.99957114, + 1.0 + ], + [ + 0.0013190353, + 0.99990016, + 1.0 + ], + [ + 9.2287684e-7, + 0.99999744, + 1.0 + ], + [ + -0.0000011511228, + 1.0, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.04013104, + 0.99884504, + 1.0 + ], + [ + 0.00018569271, + 0.99945897, + 1.0 + ], + [ + -0.0010192711, + 0.9999544, + 1.0 + ], + [ + -0.000013688198, + 0.9999996, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.02411725, + 0.99782336, + 1.0 + ], + [ + -0.09172129, + 0.9966743, + 1.0 + ], + [ + -0.017824568, + 0.9994446, + 1.0 + ], + [ + -0.00024212734, + 0.9999933, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.0085046105, + 0.9991983, + 1.0 + ], + [ + -0.032236904, + 0.9988923, + 1.0 + ], + [ + -0.03258688, + 0.9995004, + 1.0 + ], + [ + -0.022160465, + 0.99972326, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.047657337, + 0.9974346, + 1.0 + ], + [ + -0.17287616, + 0.9967666, + 1.0 + ], + [ + -1.7228909, + 0.97827595, + 1.0 + ], + [ + -1.5301245, + 0.9807228, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.1092619, + 0.9939699, + 1.0 + ], + [ + -0.28823772, + 0.9942406, + 1.0 + ], + [ + -2.4151208, + 0.9694442, + 1.0 + ], + [ + -2.120977, + 0.973369, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.046760842, + 0.99341387, + 1.0 + ], + [ + -0.46058732, + 0.9879337, + 1.0 + ], + [ + -0.9992375, + 0.97709274, + 1.0 + ], + [ + -0.27791965, + 0.9942543, + 1.0 + ], + [ + 0.0, + 1.0, + 1.0 + ], + [ + -0.055317637, + 0.988727, + 1.0 + ], + [ + -0.7323104, + 0.971684, + 1.0 + ], + [ + -2.933847, + 0.90179116, + 1.0 + ], + [ + -1.3042065, + 0.95250934, + 1.0 + ] + ], + "srgb_value": true + } +} \ No newline at end of file diff --git a/crates/denoise-core/Cargo.toml b/crates/denoise-core/Cargo.toml index b65f90ae4..d0a0b10c1 100644 --- a/crates/denoise-core/Cargo.toml +++ b/crates/denoise-core/Cargo.toml @@ -24,3 +24,4 @@ web-time = { workspace = true } [dev-dependencies] proptest = { workspace = true } +dhat = { workspace = true } diff --git a/crates/denoise-core/src/product.rs b/crates/denoise-core/src/product.rs index 5a607c8f4..c33b1d4c2 100644 --- a/crates/denoise-core/src/product.rs +++ b/crates/denoise-core/src/product.rs @@ -9,7 +9,7 @@ //! compressed length and CRC-32, then the strips' bytes. `key` says what the product was made from (the photo's //! content, the model, this code's version, the settings it depends on); a file with another key is stale. -use std::io::{Read, Write}; +use std::io::{Read, Seek, Write}; use std::path::Path; use lightcraft_raster::Rgb32f; @@ -216,6 +216,10 @@ fn read_start(r: &mut impl Read) -> Result { r.read_exact(&mut e)?; let l = u32::from_le_bytes([e[0], e[1], e[2], e[3]]) as usize; let c = u32::from_le_bytes([e[4], e[5], e[6], e[7]]); + // a strip never compresses to nothing (it has at least one row, so at least one deflated pixel) + if l == 0 { + return Err(ProductError::Format("a strip is empty".into())); + } // a strip never compresses to much more than its raw size if l > 12 * h.strip * h.width + 4096 { return Err(ProductError::Format("a strip is larger than it can be".into())); @@ -305,7 +309,6 @@ pub fn read_window(path: &Path, key: &str, window: Option, factor: usize } // the picture rows and columns the blocks cover let (x_end, y_end) = if whole { (w.x + w.width, w.y + w.height) } else { (w.x + ow * factor, w.y + oh * factor) }; - let mut out = Rgb32f { width: ow, height: oh, data: allocate(ow * oh, [0.0; 3])? }; let lut = f16_table(); let value = |raw: &[u8], c: usize, n: usize, i: usize| -> f32 { let (lo, hi) = (raw.get(2 * c * n + i).copied().unwrap_or(0), raw.get((2 * c + 1) * n + i).copied().unwrap_or(0)); @@ -318,6 +321,21 @@ pub fn read_window(path: &Path, key: &str, window: Option, factor: usize let strips: Vec = (0..table.len()).filter(|&s| wanted(s)).collect(); // skip the strips before the first one wanted let skipped: usize = table.iter().take(strips.first().copied().unwrap_or(0)).map(|e| e.0).sum(); + // The output is reserved only after the wanted strips' compressed bytes are present and match their checksums: + // a malformed cache whose strips are empty, damaged or short are rejected here, before the (large) result is + // allocated, not after. Reading them twice is cheap next to decoding them, and the fold below re-seeks here. + let strips_at = f.stream_position()?; + f.seek_relative(i64::try_from(skipped).map_err(|_| ProductError::Format("the file is too large".into()))?)?; + for &s in &strips { + let Some(&(len, expected)) = table.get(s) else { return Err(ProductError::Format("a strip is missing".into())) }; + let mut packed = allocate(len, 0u8)?; + f.read_exact(&mut packed)?; + if crc(&packed) != expected { + return Err(ProductError::Format(format!("strip {s} is damaged"))); + } + } + let mut out = Rgb32f { width: ow, height: oh, data: allocate(ow * oh, [0.0; 3])? }; + f.seek(std::io::SeekFrom::Start(strips_at))?; f.seek_relative(i64::try_from(skipped).map_err(|_| ProductError::Format("the file is too large".into()))?)?; for group in strips.chunks(GROUP) { let mut packed = Vec::with_capacity(group.len()); @@ -422,6 +440,21 @@ mod tests { let _ = std::fs::remove_file(p); } + #[test] + fn an_empty_strip_entry_is_rejected_by_the_header() { + let p = temp("empty-strip"); + let header = serde_json::to_vec(&Header { width: 16, height: 1, strip: STRIP_ROWS, key: "k".into() }).unwrap(); + let mut bytes = Vec::from(MAGIC.as_slice()); + bytes.extend_from_slice(&[VERSION, 0, 0, 0]); + bytes.extend_from_slice(&(header.len() as u32).to_le_bytes()); + bytes.extend_from_slice(&header); + // one strip with a zero-length entry: it has a row, so its deflated bytes are never empty + bytes.extend_from_slice(&[0; 8]); + std::fs::write(&p, bytes).unwrap(); + assert!(matches!(read_header_at(&p), Err(ProductError::Format(why)) if why.contains("empty"))); + let _ = std::fs::remove_file(p); + } + fn temp(name: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!("lc-denoise-{name}-{}.lcdn", std::process::id())) } diff --git a/crates/denoise-core/tests/product_heap.rs b/crates/denoise-core/tests/product_heap.rs new file mode 100644 index 000000000..40c170861 --- /dev/null +++ b/crates/denoise-core/tests/product_heap.rs @@ -0,0 +1,50 @@ +//! Peak heap of a `read_window` that must reject a product's strips (`crates/denoise-core/src/product.rs`), measured +//! with dhat's testing mode. The result is allocated only after the wanted strips' checksums are verified, so a cache +//! whose compressed bytes are damaged or absent fails without first reserving the (multi-megabyte) output. One test +//! per binary (dhat's profiler is global). + +use lightcraft_denoise_core::product::read_window; + +#[global_allocator] +static ALLOC: dhat::Alloc = dhat::Alloc; + +fn temp(name: &str) -> std::path::PathBuf { + std::env::temp_dir().join(format!("lc-denoise-{name}-{}.lcdn", std::process::id())) +} + +/// A 2000 × 1024 product of key `k` (a 24 MB result): its 8 strips each claim 8 bytes that do not match their +/// checksum, and the file is exactly the length its header and table declare. A 24 MB peak means the output was +/// reserved; the cap here bounds the header, the table and the strips' own bytes. +fn damaged_product() -> Vec { + let header = br#"{"width":2000,"height":1024,"strip":128,"key":"k"}"#; + const STRIP_LEN: usize = 8; + let mut bytes = Vec::from(b"LCDN".as_slice()); + bytes.extend_from_slice(&[1, 0, 0, 0]); + bytes.extend_from_slice(&(header.len() as u32).to_le_bytes()); + bytes.extend_from_slice(header); + let expected_len = 12 + header.len() + 8 * 8 + 8 * STRIP_LEN; + for _ in 0..8 { + // 8 claimed bytes whose recorded checksum does not match theirs (0xAA bytes do not have this CRC) + bytes.extend_from_slice(&(STRIP_LEN as u32).to_le_bytes()); + bytes.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD]); + } + for _ in 0..8 { + bytes.extend_from_slice(&[0xAAu8; STRIP_LEN]); + } + assert_eq!(bytes.len(), expected_len, "the crafted file lies about its length"); + bytes +} + +#[test] +fn a_damaged_product_is_rejected_before_its_output_is_allocated() { + let bytes = damaged_product(); + let p = temp("damaged-product-heap"); + std::fs::write(&p, &bytes).unwrap(); + let _profiler = dhat::Profiler::builder().testing().build(); + let why = read_window(&p, "k", None, 1, None).err().unwrap().to_string(); + let peak = dhat::HeapStats::get().max_bytes; + let _ = std::fs::remove_file(p); + assert!(why.contains("damaged"), "{why}"); + eprintln!("peak heap {peak} bytes"); + assert!(peak < 4 << 20, "peak heap {peak} bytes: the output was reserved before the strips were checked"); +} diff --git a/crates/denoise/src/reference.rs b/crates/denoise/src/reference.rs index ef58b5b9c..488e9a7b7 100644 --- a/crates/denoise/src/reference.rs +++ b/crates/denoise/src/reference.rs @@ -4,6 +4,12 @@ use crate::net::{Net, NetError, Op, TensorId}; use crate::run::{Error, TileRunner}; +/// The tensors' size cap: the same one the real runner's buffers share (`crates/denoise/src/cpu.rs`). A hostile `Net` +/// only ever has to satisfy the final shape: a `ConvT2` doubles the sides and a `MaxPool` halves them, so a small +/// accepted graph can zoom its intermediates far out and back. Every tensor is counted here, and a run whose layers +/// would retain more than one real tile's whole working set is refused before the memory behind it is reserved. +const MAX_VALUES: usize = 128 * 1024 * 1024; + /// A tensor in `[channels][side][side]` order. struct T { channels: usize, @@ -11,12 +17,36 @@ struct T { data: Vec, } +/// A tensor's plain float count, in bounds. +fn count(channels: usize, side: usize) -> Result { + channels + .checked_mul(side) + .and_then(|n| n.checked_mul(side)) + .filter(|&n| n > 0 && n <= MAX_VALUES) + .ok_or_else(|| NetError::Invalid("a tensor has no size or is too large".into())) +} + +/// A zeroed tensor, keeping the memory the run keeps alive in bounds (`tensors` retains every layer's result). +fn tensor(channels: usize, side: usize, total: &mut usize) -> Result, NetError> { + let n = count(channels, side)?; + *total = (*total).saturating_add(n); + if *total > MAX_VALUES { + return Err(NetError::Invalid("the tile needs too much working memory".into())); + } + let mut data = Vec::new(); + data.try_reserve_exact(n).map_err(|_| NetError::Invalid("not enough memory for a reference tile".into()))?; + data.resize(n, 0.0); + Ok(data) +} + /// Run `net` on `input` (`in_channels` planes of `tile × tile`). pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> { if !net.fits_tile(tile) || input.len() != net.in_channels * tile * tile { return Err(NetError::Invalid("the tile does not fit the network".into())); } - let mut tensors: Vec = vec![T { channels: net.in_channels, side: tile, data: input.to_vec() }]; + let input = input.to_vec(); + let mut total = input.len(); + let mut tensors = vec![T { channels: net.in_channels, side: tile, data: input }]; let get = |tensors: &[T], id: TensorId| -> Result { if id < tensors.len() { Ok(id) } else { Err(NetError::Invalid("a layer reads a tensor that does not exist yet".into())) } }; @@ -27,7 +57,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let b = c.src2.map(|s| get(&tensors, s)).transpose()?; let side = tensors[a].side; let pad = (c.k - 1) / 2; - let mut data = vec![0f32; c.cout * side * side]; + let mut data = tensor(c.cout, side, &mut total)?; // the input channels in order: the first source's, then the second's let planes: Vec<&[f32]> = std::iter::once(&tensors[a]).chain(b.map(|b| &tensors[b])).flat_map(|t| t.data.chunks(side * side)).collect(); @@ -56,7 +86,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let a = get(&tensors, c.src)?; let side = tensors[a].side; let out_side = 2 * side; - let mut data = vec![0f32; c.cout * out_side * out_side]; + let mut data = tensor(c.cout, out_side, &mut total)?; for co in 0..c.cout { for y in 0..side { for x in 0..side { @@ -77,7 +107,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> Op::MaxPool2 { src } => { let a = get(&tensors, *src)?; let (channels, side) = (tensors[a].channels, tensors[a].side / 2); - let mut data = vec![0f32; channels * side * side]; + let mut data = tensor(channels, side, &mut total)?; for ch in 0..channels { for y in 0..side { for x in 0..side { @@ -92,7 +122,7 @@ pub fn run(net: &Net, tile: usize, input: &[f32]) -> Result, NetError> let a = get(&tensors, *src)?; let (channels, side) = (tensors[a].channels / 4, tensors[a].side); let out_side = 2 * side; - let mut data = vec![0f32; channels * out_side * out_side]; + let mut data = tensor(channels, out_side, &mut total)?; for ch in 0..channels { for y in 0..side { for x in 0..side { @@ -131,3 +161,39 @@ impl TileRunner for ReferenceRunner { run(&self.net, self.tile, input).map_err(|e| Error::Runtime(e.to_string())) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::net::ConvT2; + + fn convt(src: TensorId, cin: usize, cout: usize) -> Op { + Op::ConvT2(ConvT2 { src, cin, cout, weight: vec![0.1; cin * cout * 4], bias: vec![0.0; cout], leaky: None }) + } + + /// A graph that fits the final-shape check while zooming its intermediates out and back: 256 `ConvT2`s double + /// the sides (256 tiers), 255 `MaxPool`s halve them back to the layer above the input. + fn hostile() -> Net { + let mut ops = Vec::new(); + let mut last = 0usize; + for _ in 0..256 { + ops.push(convt(last, 1, 1)); + last += 1; + } + for _ in 0..255 { + ops.push(Op::MaxPool2 { src: last }); + last += 1; + } + Net::new(1, ops).unwrap() + } + + /// The zoomed-out layers' memory was never reserved: either one tensor or the retained whole (the two bounds + /// share the cap) says why the run stops. + #[test] + fn a_graph_that_zooms_its_intermediates_out_and_back_is_refused_before_their_memory_is_reserved() { + assert!(matches!( + run(&hostile(), 1, &[1.0]), + Err(NetError::Invalid(why)) if why.contains("too large") || why.contains("too much working memory") + )); + } +} diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 9caa8cdb0..0b203f795 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -39,7 +39,7 @@ thiserror = { workspace = true } log = { workspace = true } ab_glyph = { workspace = true } # Apply the font’s vertical substitutions and origins to Japanese watermarks. -harfrust = { version = "0.12", default-features = false, features = ["std"] } +harfrust = { version = "0.13", default-features = false, features = ["std"] } unicode-segmentation = "1.13" lightcraft-segment = { workspace = true, optional = true } diff --git a/crates/engine/src/camera_profiles.rs b/crates/engine/src/camera_profiles.rs index c7b6c437b..9e6d2094e 100644 --- a/crates/engine/src/camera_profiles.rs +++ b/crates/engine/src/camera_profiles.rs @@ -28,6 +28,7 @@ pub const BUNDLED: &[(&str, &str)] = &[ ("ILCA-99M2", include_str!("../../../assets/camera-profiles/ILCA-99M2.json")), ("ILCE-7CR", include_str!("../../../assets/camera-profiles/ILCE-7CR.json")), ("ILCE-7M4", include_str!("../../../assets/camera-profiles/ILCE-7M4.json")), + ("ILCE-7RM5", include_str!("../../../assets/camera-profiles/ILCE-7RM5.json")), ("NIKON D5300", include_str!("../../../assets/camera-profiles/NIKON_D5300.json")), ("NIKON Z 6_2", include_str!("../../../assets/camera-profiles/NIKON_Z_6_2.json")), ("NIKON Z 8", include_str!("../../../assets/camera-profiles/NIKON_Z_8.json")), diff --git a/crates/merge/src/lib.rs b/crates/merge/src/lib.rs index b5ccd4bb8..ca7f91821 100644 --- a/crates/merge/src/lib.rs +++ b/crates/merge/src/lib.rs @@ -34,6 +34,8 @@ pub use pano::{PanoOptions, PanoResult, Projection, stitch}; pub enum MergeError { #[error("{0} photo(s) selected; at least {1} are needed")] TooFew(usize, usize), + #[error("{0} photo(s) selected; a stitch of more than {1} is refused: matching every pair would take far too long")] + TooMany(usize, usize), #[error("{0}")] Mismatch(String), #[error("could not read a photo: {0}")] diff --git a/crates/merge/src/pano/camera.rs b/crates/merge/src/pano/camera.rs index 7625170c3..4589f88db 100644 --- a/crates/merge/src/pano/camera.rs +++ b/crates/merge/src/pano/camera.rs @@ -61,10 +61,12 @@ pub struct PairMatch { /// Match every pair of images. `feats[k]` are in work coordinates scaled by `scales[k]`. pub fn match_pairs(feats: &[Features], scales: &[f64]) -> Vec { let n = feats.len(); - let pairs: Vec<(usize, usize)> = (0..n).flat_map(|i| (i + 1..n).map(move |j| (i, j))).collect(); - let mut out: Vec = pairs - .par_iter() - .filter_map(|&(i, j)| { + // the pairs are generated lazily: materialising all n(n−1)/2 up front is quadratic memory, including + // when every comparison comes up empty (no usable features) + let mut out: Vec = (0..n) + .into_par_iter() + .flat_map_iter(|i| ((i + 1)..n).map(move |j| (i, j))) + .filter_map(|(i, j)| { let m = features::match_features(&feats[i], &feats[j], 0.8); if m.len() < 12 { return None; diff --git a/crates/merge/src/pano/mod.rs b/crates/merge/src/pano/mod.rs index 5ce439681..ba7e1c63f 100644 --- a/crates/merge/src/pano/mod.rs +++ b/crates/merge/src/pano/mod.rs @@ -53,6 +53,11 @@ impl Default for PanoOptions { /// The largest panorama, in megapixels (Lightroom's limit too); a bigger one is scaled down. pub const MAX_MEGAPIXELS: f64 = 512.0; +/// Most frames one stitch accepts: pair matching compares every pair of frames (n(n−1)/2 runs), so an +/// unbounded frame count is quadratic CPU as well as quadratic memory while the frames load. A panorama +/// with more than [`MAX_FRAMES`] shots is refused with an error, not attempted. +pub const MAX_FRAMES: usize = 256; + /// The longest edge of a panorama, in pixels (Lightroom's limit too); a longer one is scaled down. pub const MAX_EDGE: f64 = 65_000.0; @@ -224,6 +229,9 @@ pub fn stitch(mut frames: Vec, opts: &PanoOptions, progress: &Progress) - if n < 2 { return Err(MergeError::TooFew(n, 2)); } + if n > MAX_FRAMES { + return Err(MergeError::TooMany(n, MAX_FRAMES)); + } check(progress, 0.01, "Finding features")?; let (cams, rms_before, rms) = register(&frames, progress)?; let used: Vec = (0..n).filter(|&i| cams[i].is_some()).collect(); diff --git a/crates/merge/tests/it/pano.rs b/crates/merge/tests/it/pano.rs index fdb736524..aeecde64c 100644 --- a/crates/merge/tests/it/pano.rs +++ b/crates/merge/tests/it/pano.rs @@ -246,6 +246,18 @@ fn unrelated_photos_do_not_stitch() { assert!(stitch(frames, &PanoOptions::default(), &no_progress).is_err()); } +/// Pair matching compares every pair of frames before anything is known about them: an unbounded +/// photo count is quadratic CPU and memory (issue #1519). More than `MAX_FRAMES` is refused at once, +/// before any frame is worked on. +#[test] +fn a_stitch_of_more_than_the_frame_cap_is_refused_up_front() { + let frames: Vec = (0..lightcraft_merge::pano::MAX_FRAMES + 1) + .map(|i| Frame::from_linear_rec2020(Rgb32f::from_fn(8, 8, |_, _| [(i % 255) as f32 / 255.0, 0.0, 0.0]), None)) + .collect(); + let err = stitch(frames, &PanoOptions::default(), &no_progress).unwrap_err(); + assert!(err.to_string().contains("refused"), "the frame cap errors, it does not begin the quadratic match: {err}"); +} + /// Issue #1455: a panorama came out at 40 MP whatever its photos' resolution. It now keeps their /// own resolution unless a size is asked for (up to Lightroom's 512 MP); a size scales it down, /// and a NaN one (from an agent) is no size at all, not a 0.1 MP thumbnail. diff --git a/crates/meta/src/exif.rs b/crates/meta/src/exif.rs index f24bb5f2f..adcba0f80 100644 --- a/crates/meta/src/exif.rs +++ b/crates/meta/src/exif.rs @@ -1,7 +1,7 @@ //! Exif / TIFF tags → [`Metadata`] (CIPA DC-008 Exif 2.32, TIFF 6.0, DNG 1.7 tag semantics). use crate::{DateTime, Flash, Gps, Metadata, Orientation}; -use lightcraft_tiff::{Ifd, Tiff, TiffError, tags as t}; +use lightcraft_tiff::{Ifd, ParseOptions, Tiff, TiffError, tags as t}; /// Strip an `Exif\0\0` (or `Exif\0\xff`) prefix as found in JPEG APP1 and some WebP `EXIF` chunks. pub fn strip_exif_header(b: &[u8]) -> &[u8] { @@ -16,8 +16,16 @@ pub fn read_exif(bytes: &[u8]) -> Metadata { /// Like [`read_exif`] but reports a structurally invalid TIFF header / IFD0. pub fn try_read_exif(bytes: &[u8]) -> Result { + try_read_exif_with(bytes, &ParseOptions::default()) +} + +/// [`try_read_exif`] with the caller's parser options (its value allocation budget in particular). +/// A decoder working from a sparse buffer — only selected ranges of the logical file are actually +/// loaded, the rest is zeros — must not size its allocations by the logical file length, which +/// unrelated bytes of the file can inflate at no cost to the attacker. +pub fn try_read_exif_with(bytes: &[u8], opts: &ParseOptions) -> Result { let data = strip_exif_header(bytes); - let tiff = Tiff::parse(data)?; + let tiff = Tiff::parse_with(data, opts)?; let mut m = from_tiff(&tiff); // Panasonic writes no LensModel, only the name in its maker note (and in a RW2 that note is not in the raw's own Exif) if m.lens_model.is_none() { diff --git a/crates/meta/src/focus_read.rs b/crates/meta/src/focus_read.rs index 5a5d349d1..65adbee75 100644 --- a/crates/meta/src/focus_read.rs +++ b/crates/meta/src/focus_read.rs @@ -171,7 +171,15 @@ fn tiff(r: &mut Reader, head: Vec) -> io::Result> { loaded.insert(0, head.len() as u64); for _ in 0..MAX_ROUNDS { match plan(&buf, &loaded) { - Step::Ready => return Ok(crate::try_read_exif(&buf).ok().and_then(|m| m.focus)), + Step::Ready => { + // The decoder's allocation budget follows the bytes actually fetched, not the logical + // file length: only the loaded ranges are data, and an unrelated entry that claims a + // huge value must be cut by what leaving it cost, not by what the buffer claims to hold. + // The same 8 × / + 1 MiB shape the general TIFF reader uses, over `reader.bytes` instead. + let opts = + lightcraft_tiff::ParseOptions { value_budget: Some(r.bytes.saturating_mul(8).saturating_add(1 << 20)), ..Default::default() }; + return Ok(crate::try_read_exif_with(&buf, &opts).ok().and_then(|m| m.focus)); + } Step::Nothing => return Ok(None), Step::Need(ranges) => { for (a, b) in ranges { diff --git a/crates/meta/src/lib.rs b/crates/meta/src/lib.rs index baebbfe0f..46c7a1043 100644 --- a/crates/meta/src/lib.rs +++ b/crates/meta/src/lib.rs @@ -27,7 +27,7 @@ mod xmp_merge; pub use container::{Embedded, embedded, jpeg_segments, png_chunks, webp_chunks}; pub use datetime::DateTime; -pub use exif::{from_tiff, read_exif, strip_exif_header, try_read_exif, write_exif}; +pub use exif::{from_tiff, read_exif, strip_exif_header, try_read_exif, try_read_exif_with, write_exif}; pub use focus::FocusPoint; pub use gpx::{GpxError, Match, TrackPoint, Tracklog, parse_gpx}; pub use iptc::parse_iptc; diff --git a/crates/meta/src/xmp.rs b/crates/meta/src/xmp.rs index 2dbf59482..ac0a8808a 100644 --- a/crates/meta/src/xmp.rs +++ b/crates/meta/src/xmp.rs @@ -154,6 +154,11 @@ const MAX_NODES: usize = 200_000; // Expanding a long namespace URI on many short names must not amplify a small packet without bound. pub(crate) const MAX_OPAQUE_NAME_BYTES: usize = 8 * 1024 * 1024; +/// Most text a converter's flattened property keys may add up to. Keys repeat their whole ancestor +/// path (`parent/field`), so a document with a long nested name and a wide child fan-out otherwise +/// retains its prefix once per property: node limits alone cannot bound that duplication. +const MAX_KEY_BYTES: usize = 1 << 20; + /// Names canonicalised by namespace URI (see [`NAMESPACES`]); conflicting foreign prefixes are opaque. pub(crate) fn canonical_name(qname: &str, scopes: &[Vec<(String, String)>], opaque_budget: &mut usize) -> Result { canonical(qname, scopes, opaque_budget) @@ -332,18 +337,29 @@ fn is_rdf_meta_attr(k: &str) -> bool { matches!(k, "rdf:about" | "rdf:ID" | "rdf:nodeID" | "xml:lang" | "rdf:parseType" | "rdf:resource" | "rdf:datatype") || k.starts_with("xmlns") } -fn collect_description(d: &Node, prefix: &str, props: &mut BTreeMap>) { +fn collect_description(d: &Node, prefix: &str, props: &mut BTreeMap>, budget: &mut usize) { for (k, v) in &d.attrs { - if !is_rdf_meta_attr(k) { + if !is_rdf_meta_attr(k) && take_key_bytes(&format!("{prefix}{k}"), budget) { props.entry(format!("{prefix}{k}")).or_default().push(v.clone()); } } for c in &d.children { - collect_property(c, prefix, props); + collect_property(c, prefix, props, budget); } } -fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap>) { +/// Charge one flattened key's length to the key budget; `false` when the budget is spent (the +/// property is then left out, like other elements a limit has cut). +fn take_key_bytes(key: &str, budget: &mut usize) -> bool { + let n = key.len(); + if *budget < n { + return false; + } + *budget -= n; + true +} + +fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap>, budget: &mut usize) { let key = format!("{prefix}{}", p.name); if let Some(arr) = p.children.iter().find(|c| matches!(c.name.as_str(), "rdf:Seq" | "rdf:Bag" | "rdf:Alt")) { let mut items: Vec<(bool, String)> = arr @@ -357,29 +373,36 @@ fn collect_property(p: &Node, prefix: &str, props: &mut BTreeMap = p.attrs.iter().filter(|(k, _)| !is_rdf_meta_attr(k)).collect(); if !field_attrs.is_empty() && p.children.is_empty() && p.text.trim().is_empty() { for (k, v) in field_attrs { - props.entry(format!("{struct_prefix}{k}")).or_default().push(v.clone()); + let full = format!("{struct_prefix}{k}"); + if take_key_bytes(&full, budget) { + props.entry(full).or_default().push(v.clone()); + } } return; } let v = p.attr("rdf:resource").map(str::to_string).unwrap_or_else(|| p.text.trim().to_string()); - props.entry(key).or_default().push(v); + if take_key_bytes(&key, budget) { + props.entry(key).or_default().push(v); + } } fn find_descriptions<'a>(n: &'a Node, out: &mut Vec<&'a Node>) { @@ -497,8 +520,10 @@ pub fn parse_xmp(s: &str) -> Result { find_descriptions(&tree, &mut descs); let mut props = BTreeMap::new(); let mut values = BTreeMap::new(); + // One budget for the whole document: several descriptions cannot each spend a fresh one. + let mut key_budget = MAX_KEY_BYTES; for d in descs { - collect_description(d, "", &mut props); + collect_description(d, "", &mut props, &mut key_budget); if let XmpValue::Struct(m) = node_struct(d) { values.extend(m); } @@ -1171,4 +1196,32 @@ mod tests { assert_eq!(entity("#x41"), Some('A')); assert_eq!(entity("#9"), Some('\t')); } + + /// Flattened keys repeat their whole ancestor path, so a long nested name with a wide child + /// fan-out would otherwise retain its prefix once per leaf: the cumulative key budget stops it + /// (a 65 KB document with 3000 leaves must not retain ~2.4 MB of key text). + #[test] + fn flattened_keys_stay_within_the_cumulative_budget() { + let level = "n".repeat(200); + let mut doc = String::from( + "", + ); + for _ in 0..4 { + doc.push_str(&format!("")); + } + // each leaf has a name of its own, so each one adds a key + for i in 0..3000 { + doc.push_str(&format!("")); + } + doc.push_str(""); + for _ in 0..4 { + doc.push_str(&format!("")); + } + doc.push_str(""); + let d = parse_xmp(&doc).unwrap(); + let total: usize = d.properties.keys().map(String::len).sum(); + assert!(total <= MAX_KEY_BYTES, "flattened-key text {total} is over the {MAX_KEY_BYTES} budget"); + assert!(d.properties.len() < 3000, "keys stop being added once the budget is spent"); + assert!(!d.properties.is_empty(), "properties parsed before the budget are kept"); + } } diff --git a/crates/raw/src/dngwrite.rs b/crates/raw/src/dngwrite.rs index d7ba42776..fae6015b6 100644 --- a/crates/raw/src/dngwrite.rs +++ b/crates/raw/src/dngwrite.rs @@ -94,6 +94,17 @@ pub const SOFTWARE: &str = "LightCraft DNG 2"; /// (3.9 s vs 0.55 s on 4 cores) to save 3 % of the file (266 vs 275 MB); 5–12 % on raw and rendered DNGs. const DEFLATE_LEVEL: u8 = 1; +/// Largest Deflate tile edge the writer pads to (`tile` is caller-supplied in [`DngCompression::Deflate`]): +/// square-tile buffers grow with the square, so an unclamped edge lets a request pad a tiny valid image into +/// a multi-gigabyte buffer and billions of edge-replicate iterations. 2048² samples (≤ 24 MB of f32) is far +/// above the 256 default and every real tile this writer makes. +const MAX_DEFLATE_TILE: u32 = 2048; + +/// `tile` aligned down to a multiple of 16 and clamped to [`MAX_DEFLATE_TILE`]. +fn deflate_tile(tile: u32) -> usize { + (tile.max(16) & !15).min(MAX_DEFLATE_TILE) as usize +} + /// IEEE 754 binary32 → binary16 (round to nearest even; overflow → ±inf, NaN kept). pub fn f32_to_f16(v: f32) -> u16 { let b = v.to_bits(); @@ -318,7 +329,7 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { match (&raw.data, opts.compression) { (RawData::F32(v), DngCompression::Deflate { tile, half }) => { - let tile = (tile.max(16) & !15) as usize; + let tile = deflate_tile(tile); let bp = if half { 2 } else { 4 }; ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![(bp * 8) as u16; cpp])); ifd.set(t::SAMPLE_FORMAT, Value::Short(vec![3; cpp])); @@ -417,7 +428,7 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { ifd.set_image(ImageData::Strips { rows_per_strip: rows as u32, strips }); } DngCompression::Deflate { tile, .. } => { - let tile = (tile.max(16) & !15) as usize; + let tile = deflate_tile(tile); ifd.set(t::BITS_PER_SAMPLE, Value::Short(vec![16; cpp])); ifd.set(t::COMPRESSION, Value::Short(vec![compression::ADOBE_DEFLATE])); ifd.set(t::PREDICTOR, Value::Short(vec![2])); @@ -546,3 +557,45 @@ pub fn write_rendered_dng( let order = ByteOrder::Little; write_dng(&raw, &DngWriteOptions { compression: DngCompression::Deflate { tile: 256, half: true }, order, xmp }) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{BlackLevel, OpcodeLists, RawData, RawFormat, RawImage, Rect}; + + /// Deflate tiles of `tile × tile` pad into a square buffer that grows with the square: a + /// caller-supplied edge of `u32::MAX` would make the writer pad an 8 × 8 image into billions of + /// edge-replicate iterations and a multi-gigabyte buffer. The clamp keeps the bounded shape — same + /// file as requesting the cap exactly, and a DNG either way. + #[test] + fn a_hostile_deflate_tile_edge_is_clamped() { + let (w, h) = (8usize, 8usize); + let full = Rect::new(0, 0, w, h); + let raw = RawImage { + format: RawFormat::Dng, + width: w, + height: h, + cpp: 1, + data: RawData::U16((0..64).map(|i| i as u16 * 129).collect()), + cfa: None, + bits: 12, + black: BlackLevel::uniform(0.0), + white: vec![4095.0], + active_area: full, + crop: full, + orientation: lightcraft_geom::Orientation::Normal, + color: crate::ColorData::default(), + wb_multipliers: None, + linearized: false, + pixel_aspect: 1.0, + opcodes: OpcodeLists::default(), + metadata: Default::default(), + lens_metadata: Default::default(), + }; + let opts = |tile: u32| DngWriteOptions { compression: DngCompression::Deflate { tile, half: false }, ..DngWriteOptions::default() }; + let hostile = write_dng(&raw, &opts(u32::MAX)).expect("the clamped tile writes"); + assert_eq!(hostile, write_dng(&raw, &opts(2048)).expect("the cap itself writes"), "u32::MAX is clamped to the cap"); + assert_ne!(hostile, write_dng(&raw, &opts(256)).expect("a normal tile writes"), "the clamp is not a no-op"); + assert_eq!(crate::probe(&hostile), Some(RawFormat::Dng), "the clamped output is still a DNG"); + } +} diff --git a/crates/raw/src/opcodes.rs b/crates/raw/src/opcodes.rs index 6cf224984..d5a3ffd1e 100644 --- a/crates/raw/src/opcodes.rs +++ b/crates/raw/src/opcodes.rs @@ -656,18 +656,38 @@ fn fix_pixels(buf: &mut [f32], w: usize, h: usize, cpp: usize, cfa: Option<&Cfa> } } +/// Most full-image passes of one `OpcodeList3`: every warp record is a full-image allocation and resample +/// pass and every bad-pixel record a full flatten-and-rebuild, and a hostile record may repeat either +/// thousands of times. Real DNGs carry at most a couple; passes past the cap are skipped. +const MAX_LIST3_PASSES: usize = 8; + /// Apply `OpcodeList3` (on the demosaiced RGB image, values normalised to [0, 1]). pub fn apply_list3(list: &[Opcode], img: &mut Rgb32f) { if list.is_empty() { return; } let (w, h) = (img.width, img.height); + let mut passes = 0usize; for op in applied_ops(list) { match op { - Opcode::WarpRectilinear { planes, center } => *img = warp_rectilinear(img, planes, *center), - Opcode::WarpRectilinear2 { planes, center, reciprocal, .. } => *img = warp_rectilinear2(img, planes, *center, *reciprocal), + Opcode::WarpRectilinear { planes, center } => { + if passes < MAX_LIST3_PASSES { + passes += 1; + *img = warp_rectilinear(img, planes, *center); + } + } + Opcode::WarpRectilinear2 { planes, center, reciprocal, .. } => { + if passes < MAX_LIST3_PASSES { + passes += 1; + *img = warp_rectilinear2(img, planes, *center, *reciprocal); + } + } Opcode::WarpFisheye { .. } | Opcode::TrimBounds { .. } | Opcode::Unknown { .. } => {} other => { + if passes >= MAX_LIST3_PASSES { + continue; + } + passes += 1; let mut flat: Vec = img.data.iter().flat_map(|p| *p).collect(); apply_list(std::slice::from_ref(other), &mut flat, w, h, 3, None, 1.0); for (d, c) in img.data.iter_mut().zip(flat.as_chunks::<3>().0) { @@ -765,6 +785,27 @@ pub(crate) mod tests { a.iter().flat_map(|v| v.to_be_bytes()).collect() } + /// Warp and bad-pixel records each cost a full-image pass, and a hostile `OpcodeList3` repeats them + /// thousands of times ([`MAX_LIST3_PASSES`] bounds the multiplier); identity warps below the cap must + /// still apply, exactly, so the same image comes out. + #[test] + fn list3_pass_cap_skips_repeated_records() { + let mut img = Rgb32f::new(8, 6); + for (i, px) in img.data.iter_mut().enumerate() { + *px = [i as f32 / 2.0, (i % 8) as f32, (i / 8) as f32]; + } + let orig: Vec<[f32; 3]> = img.data.clone(); + let list: Vec = + (0..MAX_LIST3_PASSES * 5).map(|_| Opcode::WarpRectilinear { planes: vec![[1.0, 0.0, 0.0, 0.0, 0.0, 0.0]], center: [0.5, 0.5] }).collect(); + apply_list3(&list, &mut img); + assert_eq!(img.data, orig, "identity warps change nothing, and the cap skips the repeated rest"); + + // a real warp below the cap is still applied + let one = [Opcode::WarpRectilinear { planes: vec![[2.0, 0.0, 0.0, 0.0, 0.0, 0.0]], center: [0.5, 0.5] }]; + apply_list3(&one, &mut img); + assert_ne!(img.data, orig, "a genuine warp record is not skipped"); + } + #[test] fn parse_and_apply_area_ops() { let (w, h) = (4usize, 3usize); diff --git a/crates/raw/src/preview.rs b/crates/raw/src/preview.rs index 95f9bdcaa..4bcf6963e 100644 --- a/crates/raw/src/preview.rs +++ b/crates/raw/src/preview.rs @@ -103,8 +103,6 @@ fn jpeg_frame(b: &[u8]) -> Option<(u32, u32, u8)> { let be16 = |k: usize| Some(u32::from(u16::from_be_bytes([at(k)?, at(k + 1)?]))); Some((be16(7)?, be16(5)?, at(9)?)) } - -/// Whether `b` is a JPEG XL file: a bare codestream or the ISO-BMFF container's signature box. fn is_jxl(b: &[u8]) -> bool { b.starts_with(&[0xff, 0x0a]) || b.starts_with(&[0, 0, 0, 0x0c, b'J', b'X', b'L', b' ', 0x0d, 0x0a, 0x87, 0x0a]) } @@ -128,20 +126,17 @@ fn candidates<'a>(data: &'a [u8], ifd: &Ifd, base: u64, out: &mut Vec<&'a [u8]>) } if matches!(ifd.u16(t::COMPRESSION), Some(6) | Some(7) | Some(34892)) && let Ok(info) = ifd.image() + && let Some(chunk) = info.single_chunk(data.len() as u64) + && let Some(s) = chunk_bytes(data, &chunk) { - let chunks = info.chunks(data.len() as u64); - if chunks.len() == 1 - && let Some(s) = chunk_bytes(data, &chunks[0]) - { - out.push(s); - } + out.push(s); } // a rendered (RGB or grey, never CFA/LinearRaw/mask) JPEG XL preview in one chunk if ifd.u16(t::COMPRESSION) == Some(t::compression::JPEG_XL) && matches!(ifd.u16(t::PHOTOMETRIC), Some(t::photometric::BLACK_IS_ZERO | t::photometric::RGB)) && let Ok(info) = ifd.image() - && let [chunk] = info.chunks(data.len() as u64).as_slice() - && let Some(s) = chunk_bytes(data, chunk) + && let Some(chunk) = info.single_chunk(data.len() as u64) + && let Some(s) = chunk_bytes(data, &chunk) && is_jxl(s) { out.push(s); @@ -397,8 +392,14 @@ fn cr3_preview(bytes: &[u8]) -> Option<&[u8]> { /// Canon CR3 (ISO base media file): the `PRVW` box (Laurent Clévy's CR3 notes; layout confirmed on a CC0 /// sample) is `u32 size, "PRVW", u32 0, u16 ?, u16 width, u16 height, u16 ?, u32 jpeg length, JPEG`; the smaller /// `THMB` box has the same shape. Returns the larger valid one. +/// Marker-walk budget of the CR3 fallback: candidates can overlap (each claiming the rest of the file) and +/// [`is_dct_jpeg`] walks each marker by marker, so walking every candidate would be quadratic in file size. +/// The `THMB`/`PRVW` previews this finds are far below the cap together; once it is spent the scan stops. +const CR3_MAX_WALK: usize = 8 << 20; + fn cr3_preview_boxes(bytes: &[u8]) -> Option<&[u8]> { let mut best: Option<&[u8]> = None; + let mut walked = 0usize; for tag in [b"PRVW", b"THMB"] { let mut from = 0; while let Some(i) = bytes.get(from..).and_then(|s| s.windows(4).position(|w| w == tag)).map(|p| p + from) { @@ -411,6 +412,11 @@ fn cr3_preview_boxes(bytes: &[u8]) -> Option<&[u8]> { continue; } let jpeg = &bytes[j..j + len]; + // One that doesn't fit the budget left is skipped; a smaller one later may still fit. + if walked + jpeg.len() > CR3_MAX_WALK { + continue; + } + walked += jpeg.len(); if is_dct_jpeg(jpeg) { if best.is_none_or(|b| b.len() < jpeg.len()) { best = Some(jpeg); @@ -440,6 +446,52 @@ mod tests { j } + /// Preview extraction only serves single-chunk JPEG-compressed IFDs and asks the `single_chunk` first: + /// a TIFF whose strip array declares 100 000 one-row strips and no StripByteCounts must not build their + /// descriptor vector, and of course finds no preview in it. + #[test] + fn a_multi_strip_jpeg_ifd_costs_no_descriptor_vector() { + let mut ifd = IfdBuilder::new(); + ifd.set(t::COMPRESSION, Value::Short(vec![7])); + ifd.set(t::IMAGE_WIDTH, Value::Long(vec![16])); + ifd.set(t::IMAGE_LENGTH, Value::Long(vec![100_000])); + ifd.set(t::ROWS_PER_STRIP, Value::Short(vec![1])); + ifd.set(t::STRIP_OFFSETS, Value::Byte((0..100_000).map(|i| (i % 255 + 1) as u8).collect())); + let file = TiffWriter::default().write(&[ifd]).unwrap(); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || tx.send(embedded_preview(&file))); + assert_eq!(rx.recv_timeout(std::time::Duration::from_secs(2)), Ok(None)); + } + + /// CR3 candidates claim overlapping extents: each block's first bytes double as a 40-byte jump marker so + /// a walk jogs past the next header and reaches the end of the file, making every one of the 9000 + /// candidates walk its whole suffix before the marker-walk budget — quadratic without it. Nothing here + /// is a JPEG (no frame header), so the answer is `None` either way. + #[test] + fn cr3_preview_boxes_stop_walking_when_the_budget_is_spent() { + // one 256-byte block per candidate, all marker-aligned so walks chain through later blocks: + // 0..4, size (itself a jump marker of 40 bytes); 4..8, "PRVW"; 20..24, the claimed length + // (to the file end); 24..28, an SOI plus a marker jumping to the fill at 40; 64..256, a fill of + // 4-byte markers (`ff 01 00 02`). + let blocks = 9000usize; + let total = blocks * 256; + let mut f = Vec::with_capacity(total); + for k in 0..blocks { + let base = k * 256; + f.extend_from_slice(&[0xff, 0x01, 0x00, 0x26, b'P', b'R', b'V', b'W', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + f.extend_from_slice(&u32::try_from(total - base - 24).unwrap().to_be_bytes()); + f.extend_from_slice(&[0xff, 0xd8, 0x00, 0x24, 0xff, 0x01, 0x00, 0x26, 0, 0, 0, 0, 0, 0, 0, 0]); + f.resize(base + 64, 0); // 40..64 filler: walks jump over it, so its bytes are free + for _ in 0..48 { + f.extend_from_slice(&[0xff, 0x01, 0x00, 0x02]); + } + } + assert_eq!(f.len(), total); + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || tx.send(cr3_preview_boxes(&f).map(|s| s.len()))); + assert_eq!(rx.recv_timeout(std::time::Duration::from_secs(2)), Ok(None), "no PRVW candidate is a JPEG"); + } + /// A minimal RAF: the fixed header (JPEG offset/length at 84/88), the JPEG, then a raw tail the /// caller must not have to read. `jpeg_len` is written as declared, so a test can claim more than /// the JPEG holds the way a padded container does. diff --git a/crates/raw/src/sparse.rs b/crates/raw/src/sparse.rs index d67a92b40..8a404a25e 100644 --- a/crates/raw/src/sparse.rs +++ b/crates/raw/src/sparse.rs @@ -35,6 +35,12 @@ const MAX_ENTRIES: u64 = 8192; const MAX_BOXES: usize = 4096; /// Child IFDs listed by one `SubIFDs` entry. const MAX_CHILDREN: u64 = 4096; +/// Pending child-IFD offsets the walker may queue at once: no more than it can ever visit +/// (the same [`MAX_IFDS`] the seen set is held to, deduplicated as they are popped), so an alley +/// of hostile `SubIFDs` entries can't grow the queue into the tens of millions of pointers. +/// The plan never guesses: the walk refuses and the plan reports the whole file when it would go +/// past the cap. +const MAX_PENDING: usize = MAX_IFDS; /// The RAF raw container's pointer to its raw IFD. const RAF_RAW_IFD: u16 = 0xf000; @@ -308,6 +314,13 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result = Vec::new(); while let Some(off) = stack.pop() { if off == 0 || off >= len || seen.len() >= MAX_IFDS || !seen.insert(off) { continue; @@ -329,7 +342,7 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result Result inline { - meta.insert(at(start), at(value_end)); + spans.push((at(start), at(value_end))); } let child = matches!(tag, t::SUB_IFDS | t::EXIF_IFD | t::GPS_IFD | t::INTEROP_IFD) || extra.contains(&tag); let locates = child || matches!(tag, t::STRIP_OFFSETS | t::STRIP_BYTE_COUNTS | t::TILE_OFFSETS | t::TILE_BYTE_COUNTS); @@ -359,14 +372,24 @@ fn walk_tiff(w: &mut Want, origin: u64, data: &[u8], extra: &[u16]) -> Result= len || !queued.insert(v) { + continue; + } + if stack.len() >= MAX_PENDING { + return Err("too many child IFDs queued to visit"); + } stack.push(v); } } } - if complete && let Some(next) = offset_at(table_end) { + if complete && let Some(next) = offset_at(table_end).filter(|&next| next != 0 && next < len && queued.insert(next)) { + if stack.len() >= MAX_PENDING { + return Err("too many child IFDs queued to visit"); + } stack.push(next); } } + meta.extend(spans); Ok(meta) } @@ -585,8 +608,74 @@ mod tests { assert_eq!(run(&file, 16, 0).0, HeaderPlan::Whole("an ARW6 header probe reads its tiles")); } - /// A TIFF with a million one-byte strips listed in descending order and all byte counts 0: the plan is - /// built in one sort and merge, not one pass over all strips (or all holes) per strip. + /// An IFD whose `SubIFDs` entries would queue 33 million child pointers (8192 entries of + /// 4096 values apiece, all pointing at the same array) is refused at the walker's pending + /// cap instead of growing the queue — and the plan reports the whole file, never guessing. + #[test] + fn a_subifds_alley_is_refused_at_the_pending_cap() { + // First read everything, so the walk reaches the directory table itself. + let n = 8192u32; + let len = 8 + 2 + (n * 12 + 4 + 4 * 4096) as usize; + let mut tiff = b"II*\0\x08\0\0\0".to_vec(); + tiff.extend_from_slice(&(n as u16).to_le_bytes()); + let mut entry = |tag: u16, typ: u16, count: u32, value: u32| { + tiff.extend_from_slice(&tag.to_le_bytes()); + tiff.extend_from_slice(&typ.to_le_bytes()); + tiff.extend_from_slice(&count.to_le_bytes()); + tiff.extend_from_slice(&value.to_le_bytes()); + }; + // header (8) + count field (2) + the {n} numbers and pointer (4), then the value array + let values_at = (8 + 2u64 + u64::from(n) * 12 + 4) as u32; + for _ in 0..n { + entry(t::SUB_IFDS, 4, MAX_CHILDREN as u32, values_at); + } + tiff.extend_from_slice(&0u32.to_le_bytes()); + tiff.extend((1..=4096u32).flat_map(u32::to_le_bytes)); + let mut loaded = Ranges::new(); + loaded.insert(0, len as u64); + let mut w = Want::new(&tiff, &loaded); + assert_eq!(walk_tiff(&mut w, 0, &tiff, &[]), Err("too many child IFDs queued to visit")); + } + + /// A TIFF with many out-of-line values across 512 directories is planned in one sort and + /// merge, not one pass over all accumulated ranges per value (CodeRabbit #52: before, each + /// `Ranges::insert` copied every range found before it — the ~million disjoint values of the + /// fixture below ask for more copying than the timeout allows). + #[test] + fn many_out_of_line_values_plan_fast() { + let (ifds, values) = (512usize, 2048usize); + let ifd_len = 2 + values * 12 + 4; + let values_at = 8 + ifds * ifd_len; + let len = values_at + values * 8; + let mut tiff = vec![0u8; len]; + tiff[..8].copy_from_slice(b"II*\0\x08\0\0\0"); + for i in 0..ifds { + let at = 8 + i * ifd_len; + // {values} out-of-line strings (count 8 > the 4 inline bytes), then the next-IFD + // pointer; every value is a disjoint 8-byte range for the merge to handle. + tiff[at..at + 2].copy_from_slice(&(values as u16).to_le_bytes()); + for k in 0..values { + let e = at + 2 + k * 12; + tiff[e..e + 2].copy_from_slice(&t::MAKE.to_le_bytes()); + tiff[e + 2..e + 4].copy_from_slice(&2u16.to_le_bytes()); // ASCII: 8 bytes > inline — a span per entry + tiff[e + 4..e + 8].copy_from_slice(&8u32.to_le_bytes()); + tiff[e + 8..e + 12].copy_from_slice(&(values_at as u32 + (k * 8) as u32).to_le_bytes()); + } + let next = if i == ifds - 1 { 0u32 } else { (at + ifd_len) as u32 }; + tiff[at + ifd_len - 4..at + ifd_len].copy_from_slice(&next.to_le_bytes()); + } + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || { + let mut loaded = Ranges::new(); + loaded.insert(0, len as u64); + let _ = tx.send(header_plan(&tiff, &loaded)); + }); + rx.recv_timeout(std::time::Duration::from_secs(20)).expect("header_plan took longer than 20 s"); + } + + /// A TIFF with a million one-byte strips listed in descending order and all byte counts 0: + /// the plan is built in one sort and merge, not one pass over all strips (or all holes) per + /// strip. #[test] fn many_strips_without_byte_counts_plan_fast() { let n = 1_000_000u32; diff --git a/crates/raw/src/tiffraw.rs b/crates/raw/src/tiffraw.rs index a781151b4..57878b881 100644 --- a/crates/raw/src/tiffraw.rs +++ b/crates/raw/src/tiffraw.rs @@ -105,48 +105,93 @@ pub fn read_image(data: &[u8], info: &ImageInfo, order: ByteOrder, packing: Pack let chunks = info.chunks(data.len() as u64); let planar = info.planar == 2 && cpp > 1; let ccpp = if planar { 1 } else { cpp }; - let decoded: Vec> = - chunks.par_iter().map(|c| decode_chunk(data, info, c, order, packing, ccpp, bits, float).map(|p| (*c, p))).collect(); + // The chunks are decoded and copied into the output in groups whose combined nominal size stays within + // a few times the output buffer's: everything decoded at once would retain the whole image twice, and + // a hostile tile table's chosen padding could stretch that much further (issue #1517). One group + // still decodes in parallel; the grid order groups make, the copies stay disjoint. + // The tile grid decides how much is decoded, whatever the file's bytes: the nominal sizes of all + // the chunks stay within a few times the image (edge tiles pad it) before any is decoded. + let nominal = chunks.iter().fold(0usize, |sum, c| sum.saturating_add(chunk_samples(c, ccpp))); + if nominal > total.saturating_mul(4).saturating_add(1 << 24) { + return Err(RawError::Corrupt("the tile grid is much larger than the image".into())); + } + let group_samples = (total / 4).max(1 << 20); let mut out_u16 = if float { Vec::new() } else { vec![0u16; total] }; let mut out_f32 = if float { vec![0f32; total] } else { Vec::new() }; - for r in decoded { - // a chunk that cannot be decoded is not a partial image: the file stays preview-only - let (c, px) = r?; - let (cw, ch) = (c.width as usize, c.height as usize); - let (x0, y0) = (c.x as usize, c.y as usize); - if x0 >= w || y0 >= h { - continue; + let mut decode_from = 0usize; + while decode_from < chunks.len() { + let mut to = decode_from; + let mut taken = 0usize; + for (k, c) in chunks[decode_from..].iter().enumerate() { + let n = chunk_samples(c, ccpp); + if taken > 0 && taken.saturating_add(n) > group_samples { + break; + } + taken = taken.saturating_add(n); + to = decode_from + k + 1; } - let copy_w = cw.min(w - x0); - let copy_h = ch.min(h - y0); - for y in 0..copy_h { - for x in 0..copy_w { - for s in 0..ccpp { - let src = (y * cw + x) * ccpp + s; - let dst_s = if planar { c.plane as usize } else { s }; - if dst_s >= cpp { - continue; - } - let dst = ((y0 + y) * w + x0 + x) * cpp + dst_s; - match &px { - ChunkPx::U16(v) => { - if let (Some(o), Some(&s)) = (out_u16.get_mut(dst), v.get(src)) { - *o = s; - } + let decoded: Vec> = + chunks[decode_from..to].par_iter().map(|c| decode_chunk(data, info, c, order, packing, ccpp, bits, float).map(|p| (*c, p))).collect(); + for r in decoded { + // a chunk that cannot be decoded is not a partial image: the file stays preview-only + let (c, px) = r?; + let (cw, ch) = (c.width as usize, c.height as usize); + let (x0, y0) = (c.x as usize, c.y as usize); + if x0 >= w || y0 >= h { + continue; + } + let copy_w = cw.min(w - x0); + let copy_h = ch.min(h - y0); + for y in 0..copy_h { + for x in 0..copy_w { + for s in 0..ccpp { + let src = (y * cw + x) * ccpp + s; + let dst_s = if planar { c.plane as usize } else { s }; + if dst_s >= cpp { + continue; } - ChunkPx::F32(v) => { - if let (Some(o), Some(&s)) = (out_f32.get_mut(dst), v.get(src)) { - *o = s; + let dst = ((y0 + y) * w + x0 + x) * cpp + dst_s; + match &px { + ChunkPx::U16(v) => { + if let (Some(o), Some(&s)) = (out_u16.get_mut(dst), v.get(src)) { + *o = s; + } + } + ChunkPx::F32(v) => { + if let (Some(o), Some(&s)) = (out_f32.get_mut(dst), v.get(src)) { + *o = s; + } } } } } } } + decode_from = to; } Ok(if float { RawData::F32(out_f32) } else { RawData::U16(out_u16) }) } +/// Bytes that `n` samples take in a row of an uncompressed or inflated chunk, laid out as +/// [`unpack_chunk`] reads them: whole bytes or 16-bit words, or 32-bit words for `Words32Msb`. +fn packed_row_bytes(n: u64, bits: u32, packing: Packing, float: bool) -> u64 { + if float { + return n.saturating_mul(u64::from(bits.div_ceil(8))); + } + let bit_len = n.saturating_mul(u64::from(bits)); + match (bits, packing) { + (8, _) => n, + (16, _) | (_, Packing::Word16) => n.saturating_mul(2), + (_, Packing::Msb | Packing::Lsb) => bit_len.div_ceil(8), + (_, Packing::Words32Msb) => bit_len.div_ceil(32).saturating_mul(4), + } +} + +/// A chunk's nominal decoded size in samples (per plane, as the decoder builds it). +fn chunk_samples(c: &Chunk, ccpp: usize) -> usize { + (c.width as usize).saturating_mul(c.height as usize).saturating_mul(ccpp) +} + #[allow(clippy::too_many_arguments)] fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, packing: Packing, cpp: usize, bits: u32, float: bool) -> Result { let src = chunk_bytes(data, c).ok_or_else(|| RawError::Corrupt("chunk offset past end of file".into()))?; @@ -179,9 +224,13 @@ fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, pack } comp::LOSSY_JPEG => { // lossy DNG: each chunk is a baseline (DCT) JPEG of 8-bit samples - let (px, jw, jh, jc) = lossy_jpeg(src, cpp)?; - let mut v = vec![0u16; n]; - for y in 0..ch.min(jh) { + let (px, jw, jh, jc) = lossy_jpeg(src, cpp, MAX_JPEG_SAMPLES)?; + // the buffer holds the rows the decoded JPEG covers (the caller zero-fills the tile's + // remainder and reads only rows it fills): a tile-many-larger decoded JPEG does not + // get the nominal tile's full buffer (issue #1517) + let rows = ch.min(jh); + let mut v = vec![0u16; rows.max(1).saturating_mul(cw).saturating_mul(cpp)]; + for y in 0..rows { for x in 0..cw.min(jw) { for k in 0..cpp { v[(y * cw + x) * cpp + k] = px[(y * jw + x) * jc + k.min(jc - 1)] as u16; @@ -191,13 +240,28 @@ fn decode_chunk(data: &[u8], info: &ImageInfo, c: &Chunk, order: ByteOrder, pack Ok(ChunkPx::U16(v)) } comp::ADOBE_DEFLATE | comp::DEFLATE => { - let bytes_per = if float { bits.div_ceil(8) as usize } else { 0 }; // row bytes in u64: a wrapping `cw · cpp · …` in usize could come out small on 32-bit targets and slip past the gate - let row_bytes = if float { cw as u64 * cpp as u64 * bytes_per as u64 } else { (cw as u64 * cpp as u64 * bits as u64).div_ceil(8) }; + let row_bytes = packed_row_bytes(cw as u64 * cpp as u64, bits, packing, float); // the gate caps at MAX_SAMPLES (≤ 2^30), so the `usize` cast below is lossless on 32-bit targets too let inflate_len = crate::checked_pixel_count(row_bytes, ch as u64).ok_or_else(|| RawError::Corrupt("bad TIFF chunk dimensions".into()))?; + // Deflate compresses at most about 1:1032, so a source this far under half of its + // nominal length cannot reconstruct the chunk: refused before the nominal buffer is + // inflated and predicted, which padding-only work would make gigabytes. + if (src.len() as u64).saturating_mul(2048) < inflate_len { + return Err(RawError::Corrupt(format!("a deflated chunk of {inflate_len} bytes is packed into only {} bytes", src.len()))); + } let raw = inflate(src, inflate_len as usize)?; + // The stream has to reach the last row inside the image (edge strips may stop there); + // it is laid out in nominal rows, so shorter is a truncated chunk, not a picture. + let last = packed_row_bytes(vw as u64 * cpp as u64, bits, packing, float); + let visible = row_bytes.saturating_mul(vh.saturating_sub(1) as u64).saturating_add(last); + if (raw.len() as u64) < visible { + return Err(RawError::Corrupt(format!( + "a deflated chunk holds {} bytes, fewer than the {visible} bytes its in-image samples span", + raw.len() + ))); + } unpack_chunk(&raw, order, packing, cw, ch, cpp, bits, float, info.predictor) } comp::JPEG_XL => jxl_chunk(src, cw, ch, vw, vh, cpp, bits, float), @@ -228,19 +292,31 @@ fn jxl_chunk_check(src: &[u8], cw: usize, ch: usize, vw: usize, vh: usize, cpp: jxl_chunk(src, cw, ch, vw, vh, cpp, bits, float).map(|_| ()) } +/// The most samples a tiled JPEG chunk or a white-balance preview may decode to: far more than +/// any real lossy tile or preview carries, and small enough that a JPEG header alone cannot size +/// a multi-gigabyte allocation. +pub(crate) const MAX_JPEG_SAMPLES: usize = 1 << 26; + /// Decode one baseline JPEG chunk to 8-bit samples: (samples, width, height, channels). -pub(crate) fn lossy_jpeg(src: &[u8], cpp: usize) -> Result<(Vec, usize, usize, usize)> { +pub(crate) fn lossy_jpeg(src: &[u8], cpp: usize, max_samples: usize) -> Result<(Vec, usize, usize, usize)> { use zune_core::bytestream::ZCursor; use zune_core::colorspace::ColorSpace; use zune_core::options::DecoderOptions; let cs = if cpp == 1 { ColorSpace::Luma } else { ColorSpace::RGB }; let opts = DecoderOptions::default().set_max_width(1 << 16).set_max_height(1 << 16).set_strict_mode(false).jpeg_set_out_colorspace(cs); let mut d = zune_jpeg::JpegDecoder::new_with_options(ZCursor::new(src), opts); - let px = d.decode().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; + // The header is read first: the JPEG's own dimensions, not the enclosing tile's, size every + // allocation `decode` makes, and they may claim billions of pixels. + d.decode_headers().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; let info = d.info().ok_or_else(|| RawError::Corrupt("lossy JPEG tile without a header".into()))?; let (w, h) = (info.width as usize, info.height as usize); let c = if cpp == 1 { 1 } else { 3 }; - if w == 0 || h == 0 || px.len() < w * h * c { + let out = w as u64 * h as u64 * c as u64; + if w == 0 || h == 0 || out > max_samples as u64 { + return Err(RawError::Corrupt(format!("a lossy JPEG of {w} × {h} × {c} would exceed the {max_samples} output samples allowed"))); + } + let px = d.decode().map_err(|e| RawError::Corrupt(format!("lossy JPEG tile: {e}")))?; + if px.len() < w * h * c { return Err(RawError::Corrupt("lossy JPEG tile: short output".into())); } Ok((px, w, h, c)) @@ -471,4 +547,97 @@ mod lossy_tests { // half of the visible rows are missing assert!(decode(8, 4).is_err()); } + + /// A lossy JPEG whose header alone claims more samples than any real tile or preview (here + /// 40000 × 40000 × 3) is refused from the header, before the decoder allocates its output + /// from the JPEG's own dimensions. + #[test] + fn an_oversized_lossy_jpeg_header_is_refused_before_decoding() { + // A real 8 × 8 baseline JPEG, whose frame header then claims other dimensions: the decoder + // parses the header, and the size check is what refuses it. + let px = vec![128u8; 8 * 8 * 3]; + let mut j = Vec::new(); + jpeg_encoder::Encoder::new(&mut j, 90).encode(&px, 8, 8, jpeg_encoder::ColorType::Rgb).unwrap(); + // SOF0: marker, length, precision, then the height and the width + let sof = j.windows(2).position(|w| w == [0xff, 0xc0]).expect("a baseline frame header"); + let claim = |j: &mut Vec, size: u16| { + j[sof + 5..sof + 7].copy_from_slice(&size.to_be_bytes()); + j[sof + 7..sof + 9].copy_from_slice(&size.to_be_bytes()); + }; + claim(&mut j, 40_000); + let RawError::Corrupt(refused) = lossy_jpeg(&j, 3, MAX_JPEG_SAMPLES).unwrap_err() else { panic!("a corrupt-data error") }; + assert!(refused.contains("would exceed"), "{refused}"); + // a header inside the budget passes the size check (and then fails to decode only for lack of data) + claim(&mut j, 1024); + match lossy_jpeg(&j, 3, MAX_JPEG_SAMPLES) { + Ok(_) => {} + Err(RawError::Corrupt(m)) => assert!(!m.contains("would exceed"), "the smaller header was refused for its size: {m}"), + Err(other) => panic!("unexpected error: {other:?}"), + } + } + + /// A lossy tile whose JPEG decodes far less than the nominal tile is fine: the rows the JPEG + /// covers hold its pixels, the rest of the output stays zero (and the tile's intermediate buffer + /// is sized by the JPEG, not by the nominal tile). + #[test] + fn a_small_lossy_jpeg_in_a_large_nominal_tile_decodes_with_zeros_below() { + let px: Vec = (0..8 * 8).flat_map(|_| [30u8, 90, 150]).collect(); + let mut j = Vec::new(); + jpeg_encoder::Encoder::new(&mut j, 100).encode(&px, 8, 8, jpeg_encoder::ColorType::Rgb).unwrap(); + let info = ImageInfo { + width: 16, + height: 16, + bits_per_sample: vec![8, 8, 8], + samples_per_pixel: 3, + compression: comp::LOSSY_JPEG, + photometric: 34892, + planar: 1, + predictor: 1, + sample_format: 1, + new_subfile_type: 0, + layout: Layout::Tiles { tile_width: 16, tile_height: 16 }, + offsets: vec![0], + byte_counts: vec![j.len() as u64], + }; + let RawData::U16(v) = read_image(&j, &info, ByteOrder::Little, Packing::Msb).unwrap() else { panic!("integer samples") }; + assert_eq!(v.len(), 16 * 16 * 3, "the output is the full nominal image"); + let px = |x: usize, y: usize| &v[(y * 16 + x) * 3..(y * 16 + x) * 3 + 3]; + assert!((px(3, 3)[0] as i32 - 30).abs() <= 3 && (px(3, 3)[2] as i32 - 150).abs() <= 3, "covered rows: {:?}", px(3, 3)); + assert!(px(3, 12).iter().all(|&s| s == 0), "rows below the JPEG stay zero: {:?}", px(3, 12)); + } + + /// A deflated tile is refused when its packed bytes are too few for deflate to have produced + /// the nominal chunk, or when the stream stops short of the rows inside the image; a fully + /// covered flat tile still decodes. + #[test] + fn a_short_or_undercovering_deflated_tile_is_refused() { + let info = |byte_counts: u64| ImageInfo { + width: 512, + height: 512, + bits_per_sample: vec![16], + samples_per_pixel: 1, + compression: comp::ADOBE_DEFLATE, + photometric: 32803, + planar: 1, + predictor: 1, + sample_format: 1, + new_subfile_type: 0, + layout: Layout::Tiles { tile_width: 512, tile_height: 512 }, + offsets: vec![0], + byte_counts: vec![byte_counts], + }; + // a complete flat tile: 512 KiB of zeros compresses to a few hundred bytes (well within + // deflate's packing bound) and inflates to every nominal byte the tile claims + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 512 * 512 * 2], 6); + assert_eq!(read_image(&zlib, &info(zlib.len() as u64), ByteOrder::Little, Packing::Msb), Ok(RawData::U16(vec![0; 512 * 512]))); + // the same 1 KiB packed, but the stream claims a whole tile: a few hundred bytes cannot + // reconstruct 512 KiB from a decoder that compresses no further than about 1:1032 + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 1024], 6); + assert!(matches!(read_image(&zlib, &info(512 * 512 * 2), ByteOrder::Little, Packing::Msb), Err(RawError::Corrupt(_))), "{zlib:?}"); + // 500 KiB of zeros compress to a stream within the packing bound and inflating to more + // bytes than the in-image samples span, but 24 KiB short of the whole nominal tile: + // refused as truncated rather than filled with the predictor's padding + let zlib = miniz_oxide::deflate::compress_to_vec_zlib(&vec![0u8; 500 * 1000], 6); + assert!(matches!(read_image(&zlib, &info(zlib.len() as u64), ByteOrder::Little, Packing::Msb), Err(RawError::Corrupt(_)))); + } } diff --git a/crates/raw/src/vendor/mef.rs b/crates/raw/src/vendor/mef.rs index 207bc8871..d5c5d83b4 100644 --- a/crates/raw/src/vendor/mef.rs +++ b/crates/raw/src/vendor/mef.rs @@ -89,6 +89,13 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { // the white balance is measured from the samples, so the header probe reads them too let chunks = info.chunks(bytes.len() as u64); let src = chunks.first().and_then(|c| chunk_bytes(bytes, c)).ok_or_else(|| RawError::Corrupt("Mef strip outside file".into()))?; + // 12-bit MSB packing takes 3 bytes per 2 samples; a strip that cannot hold the declared + // samples is a truncated file, not a picture — unpacking it would run past the real bytes + // through every output sample (a short strip asks for the whole allocation and more). + let need = n.div_ceil(2).saturating_mul(3); + if src.len() < need { + return Err(RawError::Corrupt(format!("a Mef strip of {} bytes cannot hold the declared {n} samples", src.len()))); + } let mut d = vec![0u16; n]; crate::unpack::unpack_msb(src, 12, &mut d); let white = super::white_from_data(&d, 12); @@ -138,6 +145,10 @@ mod tests { } fn sample(w: u32, h: u32, px: &[u16]) -> Vec { + sample_strip(w, h, &pack(px)) + } + + fn sample_strip(w: u32, h: u32, strip: &[u8]) -> Vec { let mut raw = IfdBuilder::new(); raw.set(t::NEW_SUBFILE_TYPE, Value::Long(vec![0])); raw.set(t::IMAGE_WIDTH, Value::Long(vec![w])); @@ -148,7 +159,7 @@ mod tests { raw.set(t::ORIENTATION, Value::Short(vec![6])); raw.set(t::CFA_REPEAT_PATTERN_DIM, Value::Short(vec![2, 2])); raw.set(t::CFA_PATTERN_EP, Value::Byte(vec![0, 1, 1, 2])); - raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![pack(px)] }); + raw.set_image(ImageData::Strips { rows_per_strip: h, strips: vec![strip.to_vec()] }); let mut ifd0 = IfdBuilder::new(); ifd0.set(t::MAKE, Value::Ascii("Mamiya-OP Co.,Ltd.".into())); ifd0.set(t::MODEL, Value::Ascii("MAMIYA ZD".into())); @@ -156,6 +167,52 @@ mod tests { TiffWriter::new(ByteOrder::Big, false).write(&[ifd0]).unwrap() } + /// A strip that cannot hold the declared samples is a truncated file, refused as corrupt + /// instead of unpacked: `unpack_msb` would otherwise run `n` samples through the few real + /// bytes, filling the rest from nothing. + #[test] + fn a_strip_too_short_for_the_declared_samples_is_refused() { + let px: Vec = (0..16 * 6).map(|i| (i * 41 % 4096) as u16).collect(); + let f = sample(16, 6, &px); + assert!(crate::decode(&f).is_ok()); + // A truncated file shaped like a real one: IFD tables first, strip last — only its first + // 20 packed bytes remain, while the IFD still declares the full 1.5 B/sample length. The + // probe accepts the file on the declared length; before the byte gate below, `unpack_msb` + // ran the dimension-sized allocation through those 20 bytes, filling it from nothing. + let u32be = |b: &[u8], a: usize| u32::from_be_bytes([b[a], b[a + 1], b[a + 2], b[a + 3]]); + let full = pack(&px); + let at = f.windows(full.len()).position(|w| w == full).expect("strip bytes in the file"); + let after_strip = at + full.len(); + let moved = after_strip - (at + 20); + let mut g = f[..8].to_vec(); + g.extend(std::iter::repeat_n(0u8, at + 20 - 8)); + g.extend_from_slice(&f[after_strip..]); + // repoint the first-IFD pointer and ifd0's MAKE / MODEL / SubIfds offsets (the tables now + // sit `moved` earlier); the raw sub-IFD's strip offset and byte count are patched below + let poke = |b: &mut [u8], a: usize| { + let v = u32::from_be_bytes([b[a], b[a + 1], b[a + 2], b[a + 3]]); + if v as usize >= after_strip { + b[a..a + 4].copy_from_slice(&(v - moved as u32).to_be_bytes()); + } + }; + let ifd0_at = u32be(&g, 4) as usize - moved; + g[4..8].copy_from_slice(&(ifd0_at as u32).to_be_bytes()); + let n = u16::from_be_bytes([g[ifd0_at], g[ifd0_at + 1]]) as usize; + for i in 0..n { + poke(&mut g, ifd0_at + 2 + i * 12 + 8); + } + // the raw sub-IFD: entries follow its count; tag 273 (StripOffsets) is the seventh, held + // inline — repoint it at the strip's new home, and append the truncated strip for real + let sub_at = u32be(&g, ifd0_at + 2 + 2 * 12 + 8) as usize; + let e_offsets = sub_at + 2 + 6 * 12 + 8; + let strip_at = g.len() as u32; + g[e_offsets..e_offsets + 4].copy_from_slice(&strip_at.to_be_bytes()); + g.extend_from_slice(&full[..20]); + assert_eq!(crate::probe(&g), Some(RawFormat::Mef)); + let r = crate::decode(&g); + assert!(matches!(r, Err(RawError::Corrupt(_))), "{r:?}"); + } + #[test] fn decodes_packed_12_bit() { let px: Vec = (0..16 * 6).map(|i| (i * 41 % 4096) as u16).collect(); diff --git a/crates/raw/src/wb_estimate.rs b/crates/raw/src/wb_estimate.rs index 17a86a2a5..13cb9f8da 100644 --- a/crates/raw/src/wb_estimate.rs +++ b/crates/raw/src/wb_estimate.rs @@ -66,7 +66,7 @@ fn median(v: &mut [f64]) -> Option { fn from_preview(bytes: &[u8], f: &Frame) -> Option<[f32; 3]> { let jpeg = crate::preview::embedded_preview(bytes)?; - let (px, pw, ph, ch) = crate::tiffraw::lossy_jpeg(&jpeg, 3).ok()?; + let (px, pw, ph, ch) = crate::tiffraw::lossy_jpeg(&jpeg, 3, crate::tiffraw::MAX_JPEG_SAMPLES).ok()?; if ch != 3 { return None; } diff --git a/crates/tiff/src/image.rs b/crates/tiff/src/image.rs index ca3ff8b69..16e24f367 100644 --- a/crates/tiff/src/image.rs +++ b/crates/tiff/src/image.rs @@ -118,14 +118,34 @@ impl ImageInfo { } } - /// All chunks that have an offset. Missing byte counts are inferred as "to the next offset / end of data" - /// using `data_len`. - pub fn chunks(&self, data_len: u64) -> Vec { + /// The chunk count the image declares (offsets present, cut to the layout's plane capacity) with the + /// geometry `chunks` needs to place each one. No descriptors are built, so a pathological strip array + /// costs it a length check. + fn chunk_grid(&self) -> (usize, u32, usize) { let (across, down) = self.grid(); let per_plane = across as usize * down as usize; let planes = if self.planar == 2 { self.samples_per_pixel as usize } else { 1 }; + (self.offsets.len().min(per_plane.saturating_mul(planes)), across, per_plane) + } + + fn declared_chunk_count(&self) -> usize { + self.chunk_grid().0 + } + + /// The one chunk when the image has exactly one, else `None` — and no descriptor vector beyond it. Preview + /// extraction only serves single-chunk images, so it asks here before serving a hostile multi-strip layout. + pub fn single_chunk(&self, data_len: u64) -> Option { + if self.declared_chunk_count() != 1 { + return None; + } + self.chunks(data_len).into_iter().next() + } + + /// All chunks that have an offset. Missing byte counts are inferred as "to the next offset / end of data" + /// using `data_len`. + pub fn chunks(&self, data_len: u64) -> Vec { + let (n, across, per_plane) = self.chunk_grid(); let (cw, ch) = self.chunk_size(); - let n = self.offsets.len().min(per_plane.saturating_mul(planes)); let mut out = Vec::with_capacity(n); // the distinct offsets in order, for chunks without a byte count (sorted once, not scanned per chunk) let mut sorted: Vec = Vec::new(); @@ -192,6 +212,18 @@ mod tests { } } + /// One offset is one chunk, and `single_chunk` refuses to build descriptors when the image declares more. + #[test] + fn single_chunk_is_the_only_chunk() { + let one = strips(vec![50], vec![10]); + assert_eq!(one.single_chunk(100).map(|c| (c.index, c.offset, c.len)), one.chunks(100).first().map(|c| (c.index, c.offset, c.len))); + // the inferred length matches too, and never falls back past the data + let inferred = strips(vec![70], vec![]); + assert_eq!(inferred.single_chunk(100).map(|c| c.len), Some(30)); + assert!(strips(vec![50, 10], vec![10, 5]).single_chunk(100).is_none(), "a multi-chunk image has no single chunk"); + assert!(strips(Vec::new(), Vec::new()).single_chunk(100).is_none()); + } + #[test] fn missing_byte_counts_run_to_the_next_larger_offset() { let info = strips(vec![50, 10, 30, 10, 70], vec![0, 0, 5]); diff --git a/crates/tiff/src/makernote.rs b/crates/tiff/src/makernote.rs index 7ed5e1997..288b366f6 100644 --- a/crates/tiff/src/makernote.rs +++ b/crates/tiff/src/makernote.rs @@ -55,7 +55,11 @@ pub fn parse_makernote(data: &[u8], offset: u64, len: u64, order: ByteOrder, mak Some(b"MM") => Some(ByteOrder::Big), _ => None, }; - let opts = ParseOptions { max_ifds: 64, max_depth: 3, ..Default::default() }; + // A fixed value budget: a maker note is at most tens of kilobytes of values in any honest file, and + // computing the budget from the buffer's length multiplies the file length (a hostile entry in a + // sparse header-read buffer, where the buffer holds the file's logical size but only a few + // kilobytes are loaded, would decode allocations no actual data backs). + let opts = ParseOptions { max_ifds: 64, max_depth: 3, value_budget: Some(1 << 20), ..Default::default() }; let try_parse = |kind: MakerNoteKind, ifd_at: u64, base: u64, ord: ByteOrder| -> Option { let (ifd, _) = parse_ifd_at(data, ifd_at, ord, base, false, &opts).ok()?; (!ifd.entries.is_empty()).then_some(MakerNote { kind, order: ord, base, ifd }) @@ -192,6 +196,35 @@ mod tests { assert_eq!(m.ifd.u64s(2).unwrap(), vec![9, 0]); } + /// A maker note's value budget is fixed, not computed from the length of the buffer it sits in: + /// an entry claiming more than the note budget (here 2 MB) is dropped, a fitting one is read. In a + /// sparse header read the buffer holds the file's logical size while only kilobytes were loaded, + /// so a length-scaled budget would let an unrelated entry decode megabytes of zeros (see the focus + /// reader, try_read_exif_with). + #[test] + fn a_note_entry_past_the_fixed_value_budget_is_dropped() { + let order = ByteOrder::Little; + let mut note = b"SONY DSC \0\0\0".to_vec(); + // the Sony IFD directly after the 12-byte header, one fitting BYTE entry and a decoy + // BYTE[2 MB] entry pointing into the file's own (zero-filled) body + order.put_u16(&mut note, 2); + order.put_u16(&mut note, 0x201b); + order.put_u16(&mut note, 1); + order.put_u32(&mut note, 1); + note.extend([3, 0, 0, 0]); + order.put_u16(&mut note, 0x9900); + order.put_u16(&mut note, 1); + order.put_u32(&mut note, 2 << 20); + order.put_u32(&mut note, 200); + order.put_u32(&mut note, 0); + let mut bytes = note.clone(); + bytes.resize(200 + (2 << 20), 0); // the decoy's value range is inside the real bytes + let m = parse_makernote(&bytes, 0, note.len() as u64, order, "SONY").unwrap(); + assert_eq!(m.kind, MakerNoteKind::Sony); + assert!(m.ifd.get(0x201b).is_some(), "the fitting entry is read"); + assert!(m.ifd.get(0x9900).is_none(), "the decoy is dropped by the value budget"); + } + #[test] fn garbage_note_is_none() { let (bytes, off, len) = with_note(ByteOrder::Little, vec![0xff; 40]); diff --git a/crates/tiff/src/reader.rs b/crates/tiff/src/reader.rs index bd728ca9c..70c66c5a7 100644 --- a/crates/tiff/src/reader.rs +++ b/crates/tiff/src/reader.rs @@ -338,6 +338,25 @@ pub fn decode(o: ByteOrder, typ: FieldType, b: &[u8]) -> Value { #[cfg(test)] mod tests { use super::*; + use crate::Value; + use crate::tags as t; + use crate::writer::{IfdBuilder, TiffWriter}; + + /// A value budget the caller sets bounds what is decoded: an entry claiming more value bytes than + /// the budget allows is dropped, entries that fit are read (the sparse header-read path, where the + /// buffer holds the file's logical size but only a few kilobytes were ever loaded, sizes its + /// budget from what was fetched, not from whatever length the file claims). + #[test] + fn the_value_budget_drops_over_budget_entries_and_keeps_the_rest() { + let mut ifd0 = IfdBuilder::new(); + ifd0.set(t::MAKE, Value::Ascii("Keep".into())); + ifd0.set(0x9000, Value::Byte(vec![0; 2 << 20])); + let file = TiffWriter { order: ByteOrder::Little, bigtiff: false }.write(&[ifd0]).unwrap(); + let opts = ParseOptions { value_budget: Some(1 << 20), ..Default::default() }; + let tiff = Tiff::parse_with(&file, &opts).unwrap(); + assert_eq!(tiff.ifds[0].string(t::MAKE), Some("Keep".to_string()), "a fitting entry is read"); + assert!(tiff.ifds[0].get(0x9000).is_none(), "the over-budget entry is dropped"); + } /// A BigTIFF whose first IFD, at 80, declares `declared` entries but holds one, followed by a next-IFD pointer /// `next`; a valid one-entry IFD sits at 16. diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 8dab2b967..8e7b77f42 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] craft-ui = { workspace = true } +craft-window = { workspace = true } lightcraft-geom = { workspace = true } lightcraft-meta = { workspace = true } lightcraft-color = { workspace = true } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index be1a9e879..e53989fca 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -49,7 +49,6 @@ pub mod sync; pub mod tasks; pub mod text_field; pub mod theme; -pub mod titlebar; pub mod updates_ui; pub mod widgets; pub mod window_place; @@ -395,6 +394,8 @@ pub struct LightcraftApp { pub perf: Perf, /// macOS: the host draws the traffic lights over our top bar. pub integrated_titlebar: bool, + /// The `movable` flag last given to the window (macOS, `craft_window::title_bar::sync_movable`). + pub window_movable: Option, /// The host installed a native menu bar (no in-window menus then). pub native_menu: bool, /// Shortcuts the native menu bar currently handles (`Cmd+Z`, `G`…): the egui shortcut handler @@ -611,6 +612,7 @@ impl LightcraftApp { perf: Perf::default(), caches: Caches::default(), integrated_titlebar: false, + window_movable: None, native_menu: false, native_shortcuts: Default::default(), recording_shortcut: None, @@ -2674,6 +2676,10 @@ impl LightcraftApp { panels::keyword_list::drag_feedback(self, &ctx); panels::toast(self, &ctx); drop_overlay::show(self, &ctx); + // AppKit moves the window on a drag anywhere in the title-bar band unless the window isn't + // movable: it stops being movable while the pointer is on a bar control or a panel over it + #[cfg(target_os = "macos")] + craft_window::title_bar::sync_movable(&ctx, &mut self.window_movable); if notice_blocks { // Keep the real press/release state for the next pass: an ordinary click spans host // frames, and resetting it above must only hide it from this pass's dialogs. diff --git a/crates/ui-egui/src/panels/topbar.rs b/crates/ui-egui/src/panels/topbar.rs index c1c645a1d..2ba807dcc 100644 --- a/crates/ui-egui/src/panels/topbar.rs +++ b/crates/ui-egui/src/panels/topbar.rs @@ -9,40 +9,11 @@ use crate::theme::Tokens; use crate::widgets::{icon_button, register}; /// The bar doubles as the window's title bar: dragging its empty space moves the window and a -/// double-click zooms or restores it. Registered before the bar's widgets so they win the click. -fn window_handle(app: &LightcraftApp, ui: &mut egui::Ui, bar: Rect, search_rect: Rect) { - use crate::titlebar::{Gesture, WindowState, command_for}; +/// double-click acts as the system setting says. Made before the bar's widgets so they win the click. +fn window_handle(ui: &mut egui::Ui, bar: Rect) { register(ui, "region:titlebar", bar); - let handle = egui::Id::unique("titlebar-handle"); - let resp = ui.interact(bar, handle, Sense::click_and_drag()); + let resp = craft_window::title_bar::empty_area(ui, bar); crate::widgets::announce(&resp, crate::i18n::tr("Title bar")); - // a press that began on a button or the search field is theirs: dragging off it must not move the window - // (while pressing, egui hovers every widget under the pointer; once the drag starts, only the dragged one) - let ctx = ui.ctx().clone(); - let memo = egui::Id::unique("titlebar-press-on-widget"); - if resp.is_pointer_button_down_on() && ctx.input(|i| i.pointer.primary_pressed()) { - let hovered = ctx.interaction_snapshot(|s| s.hovered.clone()); - let on_widget = - hovered.iter().any(|id| *id != handle && ctx.read_response(*id).is_some_and(|r| r.sense.senses_click() || r.sense.senses_drag())); - ctx.data_mut(|d| d.insert_temp(memo, on_widget)); - } - let on_widget = ctx.data(|d| d.get_temp::(memo)).unwrap_or(false); - // The search field is drawn after this handle. Its hit-test response may not exist - // yet when the press is recorded; the press origin still identifies text selection. - let on_search = ui.input(|i| i.pointer.press_origin().is_some_and(|p| search_rect.contains(p))); - let gesture = if resp.double_clicked_by(egui::PointerButton::Primary) && !on_search { - Some(Gesture::DoubleClicked) - } else if resp.drag_started_by(egui::PointerButton::Primary) && !on_widget && !on_search { - Some(Gesture::DragStarted) - } else { - None - }; - if let Some(g) = gesture { - let window = WindowState { maximized: ui.input(|i| i.viewport().maximized).unwrap_or(false), fullscreen: app.window_is_fullscreen }; - if let Some(cmd) = command_for(g, window) { - ui.ctx().send_viewport_cmd(cmd); - } - } } /// Draw the top bar: the titlebar drag area, the in-window menus when there is no native menu @@ -78,7 +49,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { let search_left = bar.center().x - sw / 2.0; let sr = Rect::from_center_size(pos2(bar.center().x, full.center().y), vec2(sw, 28.0)); if app.integrated_titlebar { - window_handle(app, ui, bar, sr); + window_handle(ui, bar); } ui.horizontal_centered(|ui| { ui.spacing_mut().item_spacing.x = 4.0; diff --git a/crates/ui-egui/src/tests_titlebar.rs b/crates/ui-egui/src/tests_titlebar.rs index 36598190c..8d40f212e 100644 --- a/crates/ui-egui/src/tests_titlebar.rs +++ b/crates/ui-egui/src/tests_titlebar.rs @@ -169,3 +169,25 @@ fn full_screen_moves_the_left_buttons_into_the_traffic_lights_space() { assert!(!h.app.window_is_fullscreen); assert_eq!(widget(&h, "icon:sidebar").left(), windowed, "leaving full screen restores the room for the traffic lights"); } + +// Scenario: a panel floating over the bar keeps a drag on it from moving the window (macOS's +// title-bar band would otherwise take the drag); a bar control does too +#[test] +fn a_panel_floating_over_the_bar_keeps_the_window_from_moving() { + let mut h = integrated(); + let bar = widget(&h, "region:titlebar"); + let empty = empty_spot(&h); + let hover = |h: &mut Headless, p: egui::Pos2| { + h.events.push(egui::Event::PointerMoved(p)); + play(h); + craft_window::title_bar::window_movable(&h.view.ctx) + }; + assert!(hover(&mut h, empty), "the empty bar moves the window"); + let help = widget(&h, "icon:help").center(); + assert!(!hover(&mut h, help), "a bar control"); + let params = json!({"operation": "float", "panel": "tools", "rect": [bar.left(), bar.top() + 4.0, 330.0, 500.0]}); + let r = h.request("engine.execute", json!({"command": "ui.dock", "params": params}), T); + assert_eq!(r["ok"], true, "{r}"); + play(&mut h); + assert!(!hover(&mut h, empty), "the floating panel covers the empty bar there"); +} diff --git a/crates/ui-egui/src/titlebar.rs b/crates/ui-egui/src/titlebar.rs deleted file mode 100644 index 40c8975cd..000000000 --- a/crates/ui-egui/src/titlebar.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! The integrated title bar's window gestures (macOS: the native title bar is hidden and the top -//! bar takes its place, so dragging and double-click-to-zoom have to be done here). -//! -//! Pure policy, no egui widgets: [`command_for`] maps a gesture on the bar's empty space and the -//! window's current state to the viewport command the host should run. - -use egui::ViewportCommand; - -/// What the user did on the bar's empty space. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Gesture { - /// A primary-button drag began: the window follows the pointer. - DragStarted, - /// A primary-button double-click: zoom the window, or restore it when already zoomed. - DoubleClicked, -} - -/// The window as the host reports it. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct WindowState { - /// Zoomed (macOS) / maximized (elsewhere). - pub maximized: bool, - /// In the OS's full-screen mode. - pub fullscreen: bool, -} - -/// The viewport command for `gesture`, if any. Full screen has no title bar to drag or zoom. -pub fn command_for(gesture: Gesture, window: WindowState) -> Option { - if window.fullscreen { - return None; - } - match gesture { - Gesture::DragStarted => Some(ViewportCommand::StartDrag), - Gesture::DoubleClicked => Some(ViewportCommand::Maximized(!window.maximized)), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const NORMAL: WindowState = WindowState { maximized: false, fullscreen: false }; - const ZOOMED: WindowState = WindowState { maximized: true, fullscreen: false }; - const FULL: WindowState = WindowState { maximized: false, fullscreen: true }; - - // Scenario: dragging the title bar moves the window - #[test] - fn given_a_normal_window_when_dragging_then_the_window_follows_the_pointer() { - assert_eq!(command_for(Gesture::DragStarted, NORMAL), Some(ViewportCommand::StartDrag)); - } - - // Scenario: a zoomed window can still be dragged (macOS lets you pull it free) - #[test] - fn given_a_zoomed_window_when_dragging_then_the_window_follows_the_pointer() { - assert_eq!(command_for(Gesture::DragStarted, ZOOMED), Some(ViewportCommand::StartDrag)); - } - - // Scenario: double-click zooms - #[test] - fn given_a_normal_window_when_double_clicking_then_it_zooms() { - assert_eq!(command_for(Gesture::DoubleClicked, NORMAL), Some(ViewportCommand::Maximized(true))); - } - - // Scenario: double-click again restores - #[test] - fn given_a_zoomed_window_when_double_clicking_then_it_is_restored() { - assert_eq!(command_for(Gesture::DoubleClicked, ZOOMED), Some(ViewportCommand::Maximized(false))); - } - - // Scenario: full screen ignores both - #[test] - fn given_a_full_screen_window_then_neither_gesture_does_anything() { - assert_eq!(command_for(Gesture::DragStarted, FULL), None); - assert_eq!(command_for(Gesture::DoubleClicked, FULL), None); - } -} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index c0ca62e33..7814fd2c2 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (KEYC-METADATA, LRC-LIB-KEYWORDS: ⌘⌥⇧K Set Keyword Shortcut and ⇧K; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (KEYC-METADATA, LRC-LIB-KEYWORDS: ⌘⌥⇧K Set Keyword Shortcut and ⇧K; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -485,7 +485,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-BEHAV-LEARN | Tutorials | OOS | 🚫 | | | | LR-BEHAV-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew`, `docs/whats-new.md` | Help ▸ What's New: release highlights | | LR-BEHAV-AI-EA | Early-access badges | P2 | ⬜ | | | -| LR-BEHAV-TITLEBAR | Title bar drag and double-click zoom (macOS) | P2 | 🟡 | `crates/ui-egui/src/titlebar.rs`, `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/tests_titlebar.rs` | the native title bar is hidden on macOS, so the top bar's empty space (including the traffic-light margin) moves the window and a double-click zooms or restores it; buttons and the search field keep their clicks; ignored in full screen, where the traffic lights are gone and the bar's left buttons move into their space (#1763). Zoom is winit's `Maximized`: it does not follow System Settings › “Double-click a window's title bar to” (Fill / Minimize). a press that starts on a button or the search field never moves the window. Known gap: egui-winit reads the zoomed state only at start-up and after our own `Maximized` command, so a window zoomed another way (green button, Window ▸ Zoom, snapping) makes the first double-click a no-op before the toggle catches up. Checked by hand on a real macOS window (drag, zoom, restore) | +| LR-BEHAV-TITLEBAR | Title bar drag and double-click zoom (macOS) | P2 | 🟡 | `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/tests_titlebar.rs` | the native title bar is hidden on macOS, so the top bar's empty space (including the traffic-light margin) moves the window and a double-click does what System Settings › “Double-click a window's title bar to” says (zoom or restore, minimize, nothing; craft-window's `title_bar`); buttons and the search field keep their clicks; ignored in full screen, where the traffic lights are gone and the bar's left buttons move into their space (#1763). A press that starts on a button, the search field or a panel floating over the bar never moves the window (AppKit's `movable` is off while the pointer is there). Known gap: egui-winit reads the zoomed state only at start-up and after our own `Maximized` command, so a window zoomed another way (green button, Window ▸ Zoom, snapping) makes the first double-click a no-op before the toggle catches up. Checked by hand on a real macOS window (drag, zoom, restore) | ## Y. Menus @@ -800,6 +800,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| | 2026-10-25 | minor | KEYC-METADATA, LRC-LIB-KEYWORDS: ⌘⌥⇧K / Ctrl+Alt+Shift+K (`dialog.keywordShortcut`, Set Keyword Shortcut) and ⇧K (`keyword.toggleShortcut`) add and remove the keyword shortcut's keywords; Keyword Shortcut leaves LRC-LIB-KEYWORDS' Not yet list; status counts unchanged (KEYC-METADATA stays 🟡) | +| 2026-10-25 | patch | LR-BEHAV-TITLEBAR: the top bar's title-bar gestures come from craft-window's `title_bar`: a double-click follows the system setting, and a press on a button, the search field or a panel floating over the bar never moves the window; status counts unchanged | | 2026-10-24 | minor | KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 (`keyword.nextSet`, `keyword.previousSet`) go to the next / previous keyword set; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | minor | KEYC-METADATA: ⌘K / Ctrl+K (`panel.addKeywords`, Add Keywords) shows the Keywords panel, opens Keywording and puts the cursor in the Add keyword box; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | patch | LR-LIB-RATING, LR-LIB-DELETE: a slider drag still open when a rating, flag or label takes the photo out of the view, or when the photo is deleted, ends as its own undo step before the selection moves on, as moving to the next photo does; LR-BEHAV-LOCALIZE lists all 18 languages; status unchanged | diff --git a/docs/showcase/__pycache__/run.cpython-314.pyc b/docs/showcase/__pycache__/run.cpython-314.pyc new file mode 100644 index 000000000..de0f9a97e Binary files /dev/null and b/docs/showcase/__pycache__/run.cpython-314.pyc differ diff --git a/docs/showcase/run.py b/docs/showcase/run.py index 828f8decc..05de581af 100644 --- a/docs/showcase/run.py +++ b/docs/showcase/run.py @@ -14,8 +14,7 @@ def _jsonl_path(arg): if not path.is_file(): sys.exit(f"not a readable file: {arg}") try: - with path.open("rb"): - pass + path.open("rb").close() except OSError: sys.exit(f"not a readable file: {arg}") return path diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 0f1b1f77d..c68c7dca2 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -14,4 +14,4 @@ serde_json = { workspace = true } sha2 = { workspace = true } # precompressed web bundle (`cargo xtask web`); pure Rust flate2 = "1" -brotli = { version = "8", default-features = false, features = ["std"] } +brotli = { version = "9", default-features = false, features = ["std"] }