diff --git a/Cargo.lock b/Cargo.lock index 0ab7069f7382cc28822c67cbe041d7afa186b71f..7b4f82e02e796456e4c447564f0670e508011aac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "anyhow" version = "1.0.86" @@ -23,12 +29,67 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit_field" version = "0.10.2" @@ -41,6 +102,24 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitstream-io" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415f8399438eb5e4b2f73ed3152a3448b98149dda642a957ee704e1daa5cf1d8" + +[[package]] +name = "built" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "by_address" version = "1.2.1" @@ -59,6 +138,33 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "cc" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -71,12 +177,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "crc32fast" version = "1.4.2" @@ -117,19 +217,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - [[package]] name = "either" version = "1.12.0" @@ -238,24 +325,51 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "image" -version = "0.24.9" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", ] +[[package]] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" + [[package]] name = "indexmap" version = "2.2.6" @@ -266,20 +380,46 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "kmeans_colors" @@ -305,6 +445,17 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -317,9 +468,28 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] [[package]] name = "memchr" @@ -327,6 +497,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -337,6 +513,69 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -346,6 +585,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "palette" version = "0.7.6" @@ -366,9 +611,21 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "png" version = "0.17.13" @@ -397,6 +654,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "qoi" version = "0.4.1" @@ -406,6 +682,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.36" @@ -445,6 +727,56 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67376f469e7e7840d0040bbf4b9b3334005bb167f814621326e4c7ab8cd6e944" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "rayon" version = "1.10.0" @@ -466,12 +798,12 @@ dependencies = [ ] [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rgb" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "a7439be6844e40133eda024efd85bf07f59d0dd2f59b10c00dd6cfb92cc5c741" dependencies = [ - "semver", + "bytemuck", ] [[package]] @@ -495,12 +827,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" - [[package]] name = "serde" version = "1.0.203" @@ -518,7 +844,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -549,6 +875,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -569,7 +904,7 @@ name = "sprite_processor" version = "0.1.0" dependencies = [ "anyhow", - "derive_more", + "bincode", "fxhash", "image", "kmeans_colors", @@ -578,15 +913,14 @@ dependencies = [ "rayon", "serde", "serde_yaml", - "toml", "walkdir", ] [[package]] name = "syn" -version = "1.0.109" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -594,14 +928,42 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.66" +name = "system-deps" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] @@ -661,6 +1023,23 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "walkdir" version = "2.5.0" @@ -677,6 +1056,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "weezl" version = "0.1.8" @@ -774,6 +1207,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -782,3 +1221,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 02dffcd30d39e6cff42beb66a4e6c874c5f340d6..e8281bea27636516a5c3a6457b0e513b19a75890 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,16 @@ edition = "2021" [dependencies] anyhow = "1.0.86" -image = "0.24.9" -log = "0.4.20" -serde = { version = "1.0.197", features = ["derive"]} -serde_yaml = "0.9.17" -toml = "0.8.10" -rayon = "1.5.1" -walkdir = "2.3.2" +image = "0.25.1" +log = "0.4.22" +serde = { version = "1.0.203", features = ["derive"]} +serde_yaml = "0.9.33" +rayon = "1.10.0" +walkdir = "2.5.0" kmeans_colors = { version = "0.6.0", default-features = false, features = ["palette_color"] } fxhash = { version = "0.2.1" } palette = { version = "0.7.6", default-features = false, features = ["std"] } -derive_more = "0.99.17" +bincode = "1.3.3" [profile.dev.package."*"] opt-level = 3 diff --git a/source_assets/fallback/sprites/background/background.gif b/source_assets/fallback/animations/background.gif similarity index 100% rename from source_assets/fallback/sprites/background/background.gif rename to source_assets/fallback/animations/background.gif diff --git a/source_assets/fallback/sprites/diver/diver.gif b/source_assets/fallback/animations/diver.gif similarity index 100% rename from source_assets/fallback/sprites/diver/diver.gif rename to source_assets/fallback/animations/diver.gif diff --git a/source_assets/fallback/sprites/ink/ink.gif b/source_assets/fallback/animations/ink.gif similarity index 100% rename from source_assets/fallback/sprites/ink/ink.gif rename to source_assets/fallback/animations/ink.gif diff --git a/source_assets/fallback/sprites/kraken/kraken.gif b/source_assets/fallback/animations/kraken.gif similarity index 100% rename from source_assets/fallback/sprites/kraken/kraken.gif rename to source_assets/fallback/animations/kraken.gif diff --git a/source_assets/fallback/sprites/shark/shark.gif b/source_assets/fallback/animations/shark.gif similarity index 100% rename from source_assets/fallback/sprites/shark/shark.gif rename to source_assets/fallback/animations/shark.gif diff --git a/source_assets/fallback/sprites/speedboat/speedboat.gif b/source_assets/fallback/animations/speedboat.gif similarity index 100% rename from source_assets/fallback/sprites/speedboat/speedboat.gif rename to source_assets/fallback/animations/speedboat.gif diff --git a/source_assets/fallback/sprites/sub/sub_side.gif b/source_assets/fallback/animations/sub_side.gif similarity index 100% rename from source_assets/fallback/sprites/sub/sub_side.gif rename to source_assets/fallback/animations/sub_side.gif diff --git a/source_assets/fallback/sprites/sub/sub_turn.gif b/source_assets/fallback/animations/sub_turn.gif similarity index 100% rename from source_assets/fallback/sprites/sub/sub_turn.gif rename to source_assets/fallback/animations/sub_turn.gif diff --git a/source_assets/fallback/sprites/torpedo/torpedo.gif b/source_assets/fallback/animations/torpedo.gif similarity index 100% rename from source_assets/fallback/sprites/torpedo/torpedo.gif rename to source_assets/fallback/animations/torpedo.gif diff --git a/source_assets/fallback/sprites/explosions/torpedo_explosion.gif b/source_assets/fallback/animations/torpedo_explosion.gif similarity index 100% rename from source_assets/fallback/sprites/explosions/torpedo_explosion.gif rename to source_assets/fallback/animations/torpedo_explosion.gif diff --git a/source_assets/fallback/config.yml b/source_assets/fallback/config.yml index 10f3ee7fdf314644ab5ab4805ac080e67a5ebdf7..24af84d6117402ff2f3368cdc4e347abee818276 100644 --- a/source_assets/fallback/config.yml +++ b/source_assets/fallback/config.yml @@ -1,80 +1,116 @@ -theme_name: "fallback" -palette_sets: - - { name: "p1", animation: "sub_side", palettes: ["p1", "p1", "p1", "p1"] } - - { name: "p2", animation: "sub_side", palettes: ["p2", "p2", "p2", "p2"] } - - { name: "low_air", animation: "sub_side", palettes: ["low_air", "", "", ""] } - - { name: "critical_air", animation: "sub_side", palettes: ["low_air", "", "critical_air", ""] } -animations: - - { animation: "background", offset: [0, 0], max_colors: 255 } - - { animation: "diver", offset: [0, 0], max_colors: 12 } - - { animation: "kraken", offset: [6, 2], max_colors: 12 } - - { animation: "shark", offset: [14, 3], max_colors: 12 } - - { animation: "speedboat", offset: [0, 0], max_colors: 12 } - - { animation: "sub_side", offset: [9, 0], max_colors: 12 } - - { animation: "torpedo", offset: [0, 0], max_colors: 12 } - - { animation: "ink", offset: [0, 0], max_colors: 12 } - - { animation: "explosions", offset: [0, 0], max_colors: 12 } -entities: - - { - name: "background", - mappings: [ - { action: "background", sprite: "background", animation: "background" }] - } - - { - name: "player1", - mappings: [ - { action: "idle", sprite: "sub", animation: "sub_side" }, - { action: "move", sprite: "sub", animation: "sub_side" }, - { action: "fire", sprite: "sub", animation: "sub_side" }, - { action: "death", sprite: "sub", animation: "sub_side" } ] - } - - { - name: "player2", - mappings: [ - { action: "idle", sprite: "sub", animation: "sub_side" }, - { action: "move", sprite: "sub", animation: "sub_side" }, - { action: "fire", sprite: "sub", animation: "sub_side" }, - { action: "death", sprite: "sub", animation: "sub_side" }] - } - - { - name: "patrol_sub", - mappings: [ - { action: "idle", sprite: "speedboat", animation: "speedboat" }, - { action: "move", sprite: "speedboat", animation: "speedboat" }, - { action: "fire", sprite: "speedboat", animation: "speedboat" }, - { action: "death", sprite: "speedboat", animation: "speedboat" }] - } - - { - name: "shark", - mappings: [ - { action: "idle", sprite: "shark", animation: "shark"}, - { action: "move", sprite: "shark", animation: "shark"}, - { action: "fire", sprite: "shark", animation: "shark" }, - { action: "death", sprite: "shark", animation: "shark"}] - } - - { - name: "bullet", - mappings: [ - { action: "idle", sprite: "torpedo", animation: "torpedo", palette: "torpedo-default" }, - { action: "collision", sprite: "explosions", animation: "torpedo_explosion", palette: "explosions-default" } - ] - } - - { - name: "ink", - mappings: [ - { action: "idle", sprite: "ink", animation: "ink", palette: "ink-default" }, - { action: "collision", sprite: "explosions", animation: "torpedo_explosion", palette: "explosions-default" } - ] - } - - { - name: "diver", - mappings: [{ action: "idle", sprite: "diver", animation: "diver", palette: "diver-default" }] - } - - { - name: "sub", - mappings: [ - { action: "idle", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "move", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "fire", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "death", sprite: "kraken", animation: "kraken", palette: "kraken-default" }] - } +{ + name: "fallback", + required_mappings: [ + { game_entity: "player1", animation_states: ["idle", "move"] }, + { game_entity: "player2", animation_states: ["idle", "move"] }, + { game_entity: "shark", animation_states: ["move"] }, + { game_entity: "sub", animation_states: ["move"] }, + { game_entity: "ink", animation_states: ["move"] }, + { game_entity: "patrol_sub", animation_states: ["move"] }, + { game_entity: "bullet", animation_states: ["move"] }, + { game_entity: "diver", animation_states: ["move"] }, + { game_entity: "background", animation_states: ["background"] }, + { game_entity: "explosion", animation_states: ["explosion"] } + ], + animations: [ + { + source_animation: "sub_side", + offset: [ 9, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "player1", "player2" ], + animation_states: [ "idle", "move" ], + }, + }, + { + source_animation: "shark", + offset: [ 14, 3 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "shark" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "kraken", + offset: [ 6, 2 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "ink", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "ink" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "speedboat", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "patrol_sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "torpedo", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "bullet" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "diver", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "diver" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "background", + offset: [ 0, 0 ], + looping: true, + max_colors: 255, + game_entity_map: + { + game_entities: [ "background" ], + animation_states: [ "background" ], + }, + }, + { + source_animation: "torpedo_explosion", + offset: [ 0, 0 ], + looping: true, + max_colors: 15, + game_entity_map: + { + game_entities: [ "explosion" ], + animation_states: [ "explosion" ], + }, + }, + ] +} diff --git a/source_assets/fallback/sprites/shark/palette_set_0.palette.png b/source_assets/fallback/sprites/shark/palette_set_0.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/shark/palette_set_0.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/shark/palette_set_1.palette.png b/source_assets/fallback/sprites/shark/palette_set_1.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/shark/palette_set_1.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/shark/palette_set_2.palette.png b/source_assets/fallback/sprites/shark/palette_set_2.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/shark/palette_set_2.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/shark/palette_set_3.palette.png b/source_assets/fallback/sprites/shark/palette_set_3.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/shark/palette_set_3.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/sub/critical_air.palette.png b/source_assets/fallback/sprites/sub/critical_air.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/sub/critical_air.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/sub/low_air.palette.png b/source_assets/fallback/sprites/sub/low_air.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/sub/low_air.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/sub/p1.palette.png b/source_assets/fallback/sprites/sub/p1.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/sub/p1.palette.png and /dev/null differ diff --git a/source_assets/fallback/sprites/sub/p2.palette.png b/source_assets/fallback/sprites/sub/p2.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/fallback/sprites/sub/p2.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/background/background.gif b/source_assets/joe/animations/background.gif similarity index 100% rename from source_assets/joe/sprites/background/background.gif rename to source_assets/joe/animations/background.gif diff --git a/source_assets/joe/sprites/diver/diver.gif b/source_assets/joe/animations/diver.gif similarity index 100% rename from source_assets/joe/sprites/diver/diver.gif rename to source_assets/joe/animations/diver.gif diff --git a/source_assets/joe/sprites/torpedo/torpedo.gif b/source_assets/joe/animations/ink.gif similarity index 100% rename from source_assets/joe/sprites/torpedo/torpedo.gif rename to source_assets/joe/animations/ink.gif diff --git a/source_assets/joe/sprites/kraken/kraken.gif b/source_assets/joe/animations/kraken.gif similarity index 100% rename from source_assets/joe/sprites/kraken/kraken.gif rename to source_assets/joe/animations/kraken.gif diff --git a/source_assets/joe/sprites/shark/shark.gif b/source_assets/joe/animations/shark.gif similarity index 100% rename from source_assets/joe/sprites/shark/shark.gif rename to source_assets/joe/animations/shark.gif diff --git a/source_assets/joe/sprites/speedboat/speedboat.gif b/source_assets/joe/animations/speedboat.gif similarity index 100% rename from source_assets/joe/sprites/speedboat/speedboat.gif rename to source_assets/joe/animations/speedboat.gif diff --git a/source_assets/joe/sprites/sub/sub_side.gif b/source_assets/joe/animations/sub_side.gif similarity index 100% rename from source_assets/joe/sprites/sub/sub_side.gif rename to source_assets/joe/animations/sub_side.gif diff --git a/source_assets/joe/sprites/sub/sub_turn.gif b/source_assets/joe/animations/sub_turn.gif similarity index 100% rename from source_assets/joe/sprites/sub/sub_turn.gif rename to source_assets/joe/animations/sub_turn.gif diff --git a/source_assets/joe/animations/torpedo.gif b/source_assets/joe/animations/torpedo.gif new file mode 100644 index 0000000000000000000000000000000000000000..68451094d341eb3c2f5a25b85ca8665271e294c8 Binary files /dev/null and b/source_assets/joe/animations/torpedo.gif differ diff --git a/source_assets/joe/sprites/explosions/torpedo_explosion.gif b/source_assets/joe/animations/torpedo_explosion.gif similarity index 100% rename from source_assets/joe/sprites/explosions/torpedo_explosion.gif rename to source_assets/joe/animations/torpedo_explosion.gif diff --git a/source_assets/joe/config.yml b/source_assets/joe/config.yml index c2a73c3a0460ba5afc17669b5a7cfa2a66cee6b9..d37ad331b9cace36ab5973a60c02aec498bfa49a 100644 --- a/source_assets/joe/config.yml +++ b/source_assets/joe/config.yml @@ -1,72 +1,104 @@ -theme_name: "Joe" -palette_sets: - - { name: "p1", animation: "sub_side", palettes: ["p1", "p1", "p1", "p1"] } - - { name: "p2", animation: "sub_side", palettes: ["p2", "p2", "p2", "p2"] } - - { name: "low_air", animation: "sub_side", palettes: ["low_air", "", "", ""] } - - { name: "critical_air", animation: "sub_side", palettes: ["low_air", "", "critical_air", ""] } -animations: - - { animation: "background", offset: [0, 0], max_colors: 255 } - - { animation: "diver", offset: [0, 0], max_colors: 12 } - - { animation: "kraken", offset: [6, 2], max_colors: 12 } - - { animation: "shark", offset: [14, 3], max_colors: 12 } - - { animation: "speedboat", offset: [0, 0], max_colors: 12 } - - { animation: "sub_side", offset: [9, 0], max_colors: 12 } - - { animation: "torpedo", offset: [0, 0], max_colors: 12 } - - { animation: "explosions", offset: [0, 0], max_colors: 12 } -entities: - - { - name: "background", - mappings: [ - { action: "background", sprite: "background", animation: "background" }] - } - - { - name: "player1", - mappings: [ - { action: "idle", sprite: "sub", animation: "sub_side" }, - { action: "move", sprite: "sub", animation: "sub_side" }, - { action: "fire", sprite: "sub", animation: "sub_side" }, - { action: "death", sprite: "sub", animation: "sub_side" } ] - } - - { - name: "player2", - mappings: [ - { action: "idle", sprite: "sub", animation: "sub_side" }, - { action: "move", sprite: "sub", animation: "sub_side" }, - { action: "fire", sprite: "sub", animation: "sub_side" }, - { action: "death", sprite: "sub", animation: "sub_side" }] - } - - { - name: "patrol_sub", - mappings: [ - { action: "idle", sprite: "speedboat", animation: "speedboat" }, - { action: "move", sprite: "speedboat", animation: "speedboat" }, - { action: "fire", sprite: "speedboat", animation: "speedboat" }, - { action: "death", sprite: "speedboat", animation: "speedboat" }] - } - - { - name: "shark", - mappings: [ - { action: "idle", sprite: "shark", animation: "shark"}, - { action: "move", sprite: "shark", animation: "shark"}, - { action: "fire", sprite: "shark", animation: "shark" }, - { action: "death", sprite: "shark", animation: "shark"}] - } - - { - name: "bullet", - mappings: [ - { action: "idle", sprite: "torpedo", animation: "torpedo", palette: "torpedo-default" }, - { action: "collision", sprite: "explosions", animation: "torpedo_explosion", palette: "explosions-default" } - ] - } - - { - name: "diver", - mappings: [{ action: "idle", sprite: "diver", animation: "diver", palette: "diver-default" }] - } - - { - name: "sub", - mappings: [ - { action: "idle", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "move", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "fire", sprite: "kraken", animation: "kraken", palette: "kraken-default" }, - { action: "death", sprite: "kraken", animation: "kraken", palette: "kraken-default" }] - } +{ + name: "Joe", + required_mappings: [ + { game_entity: "player1", animation_states: ["idle", "move"] }, + { game_entity: "player2", animation_states: ["idle", "move"] }, + { game_entity: "shark", animation_states: ["move"] }, + { game_entity: "sub", animation_states: ["move"] }, + { game_entity: "ink", animation_states: ["move"] }, + { game_entity: "patrol_sub", animation_states: ["move"] }, + { game_entity: "bullet", animation_states: ["move"] }, + { game_entity: "diver", animation_states: ["move"] }, + { game_entity: "background", animation_states: ["background"] }, + ], + animations: [ + { + source_animation: "sub_side", + offset: [ 9, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "player1", "player2" ], + animation_states: [ "idle", "move" ], + }, + }, + { + source_animation: "shark", + offset: [ 14, 3 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "shark" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "kraken", + offset: [ 6, 2 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "ink", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "ink" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "speedboat", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "patrol_sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "torpedo", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "bullet" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "diver", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "diver" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "background", + offset: [ 0, 0 ], + looping: true, + max_colors: 255, + game_entity_map: + { + game_entities: [ "background" ], + animation_states: [ "background" ], + }, + }, + ] +} diff --git a/source_assets/joe/sprites/shark/palette_set_0.palette.png b/source_assets/joe/sprites/shark/palette_set_0.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/shark/palette_set_0.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/shark/palette_set_1.palette.png b/source_assets/joe/sprites/shark/palette_set_1.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/shark/palette_set_1.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/shark/palette_set_2.palette.png b/source_assets/joe/sprites/shark/palette_set_2.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/shark/palette_set_2.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/shark/palette_set_3.palette.png b/source_assets/joe/sprites/shark/palette_set_3.palette.png deleted file mode 100644 index 5b83903efbe0083c87fa775ce525a4fd3c847833..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/shark/palette_set_3.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/sub/critical_air.palette.png b/source_assets/joe/sprites/sub/critical_air.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/sub/critical_air.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/sub/low_air.palette.png b/source_assets/joe/sprites/sub/low_air.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/sub/low_air.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/sub/p1.palette.png b/source_assets/joe/sprites/sub/p1.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/sub/p1.palette.png and /dev/null differ diff --git a/source_assets/joe/sprites/sub/p2.palette.png b/source_assets/joe/sprites/sub/p2.palette.png deleted file mode 100644 index 87b8075313b332f2ada60fc5fcc2fca77751fc22..0000000000000000000000000000000000000000 Binary files a/source_assets/joe/sprites/sub/p2.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/background/background.gif b/source_assets/thorfinn/animations/background.gif similarity index 100% rename from source_assets/thorfinn/sprites/background/background.gif rename to source_assets/thorfinn/animations/background.gif diff --git a/source_assets/thorfinn/sprites/bird/bird.gif b/source_assets/thorfinn/animations/bird.gif similarity index 100% rename from source_assets/thorfinn/sprites/bird/bird.gif rename to source_assets/thorfinn/animations/bird.gif diff --git a/source_assets/thorfinn/sprites/cat/cat_idle.gif b/source_assets/thorfinn/animations/cat_idle.gif similarity index 100% rename from source_assets/thorfinn/sprites/cat/cat_idle.gif rename to source_assets/thorfinn/animations/cat_idle.gif diff --git a/source_assets/thorfinn/sprites/cat/cat_shooting.gif b/source_assets/thorfinn/animations/cat_shooting.gif similarity index 100% rename from source_assets/thorfinn/sprites/cat/cat_shooting.gif rename to source_assets/thorfinn/animations/cat_shooting.gif diff --git a/source_assets/thorfinn/sprites/croc/croc_spitting.gif b/source_assets/thorfinn/animations/croc_spitting.gif similarity index 100% rename from source_assets/thorfinn/sprites/croc/croc_spitting.gif rename to source_assets/thorfinn/animations/croc_spitting.gif diff --git a/source_assets/thorfinn/sprites/dog/evil_dog.gif b/source_assets/thorfinn/animations/evil_dog.gif similarity index 100% rename from source_assets/thorfinn/sprites/dog/evil_dog.gif rename to source_assets/thorfinn/animations/evil_dog.gif diff --git a/source_assets/thorfinn/sprites/mouse/mouse.gif b/source_assets/thorfinn/animations/mouse.gif similarity index 100% rename from source_assets/thorfinn/sprites/mouse/mouse.gif rename to source_assets/thorfinn/animations/mouse.gif diff --git a/source_assets/thorfinn/sprites/yarn/yarn.gif b/source_assets/thorfinn/animations/yarn.gif similarity index 100% rename from source_assets/thorfinn/sprites/yarn/yarn.gif rename to source_assets/thorfinn/animations/yarn.gif diff --git a/source_assets/thorfinn/config.yml b/source_assets/thorfinn/config.yml index 94e607ff743f9931bb6ff4efd82392f77f39b25e..2c9d93213315aab047f05072cce5ed5cd3c30e92 100644 --- a/source_assets/thorfinn/config.yml +++ b/source_assets/thorfinn/config.yml @@ -1,69 +1,93 @@ -theme_name: "thorfinn" -palette_sets: - - { name: "p1", animation: "cat_shooting", palettes: ["p1", "p1", "p1", "p1"] } - - { name: "p2", animation: "cat_shooting", palettes: ["p2", "p2", "p2", "p2"] } - - { name: "low_air", animation: "cat_shooting", palettes: ["low_air", "", "", ""] } - - { name: "critical_air", animation: "cat_shooting", palettes: ["low_air", "", "critical_air", ""] } -animations: - - { animation: "background", offset: [-53, 0] } - - { animation: "cat_idle", offset: [11, 11] } - - { animation: "cat_shooting", offset: [11, 11] } - - { animation: "bird", offset: [1, 3] } - - { animation: "croc_spitting", offset: [0, 10] } - - { animation: "evil_dog", offset: [6, 9] } - - { animation: "mouse", offset: [13, 13] } - - { animation: "yarn", offset: [7, 3] } -entities: - - { - name: "background", - mappings: [ - { action: "background", sprite: "background", animation: "background" }] - } - - { - name: "player1", - mappings: [ - { action: "idle", sprite: "cat", animation: "cat_shooting", palette: "p1" }, - { action: "move", sprite: "cat", animation: "cat_shooting", palette: "p1" }, - { action: "fire", sprite: "cat", animation: "cat_shooting", palette: "p1", sound: "fire" }, - { action: "death", sprite: "cat", animation: "cat_shooting", palette: "p1" } ] - } - - { - name: "player2", - mappings: [ - { action: "idle", sprite: "cat", animation: "cat_shooting", palette: "p2" }, - { action: "move", sprite: "cat", animation: "cat_shooting", palette: "p2" }, - { action: "fire", sprite: "cat", animation: "cat_shooting", palette: "p2", sound: "fire" }, - { action: "death", sprite: "cat", animation: "cat_shooting", palette: "p2" }] - } - - { - name: "sub", - mappings: [ - { action: "idle", sprite: "dog", animation: "evil_dog", palette: "dog-default" }, - { action: "move", sprite: "dog", animation: "evil_dog", palette: "dog-default" }, - { action: "fire", sprite: "dog", animation: "evil_dog", palette: "dog-default", sound: "fire" }, - { action: "death", sprite: "dog", animation: "evil_dog", palette: "dog-default" }] - } - - { - name: "shark", - mappings: [ - { action: "idle", sprite: "croc", animation: "croc_spitting", palette: "croc-default" }, - { action: "move", sprite: "croc", animation: "croc_spitting", palette: "croc-default" }, - { action: "fire", sprite: "croc", animation: "croc_spitting", palette: "croc-default", sound: "fire" }, - { action: "death", sprite: "croc", animation: "croc_spitting", palette: "croc-default" }] - } - - { - name: "bullet", - mappings: [{ action: "idle", sprite: "yarn", animation: "yarn", palette: "yarn-default" }] - } - - { - name: "diver", - mappings: [{ action: "idle", sprite: "mouse", animation: "mouse", palette: "mouse-default" }] - } - - { - name: "patrol_sub", - mappings: [ - { action: "idle", sprite: "bird", animation: "bird", palette: "bird-default" }, - { action: "move", sprite: "bird", animation: "bird", palette: "bird-default" }, - { action: "fire", sprite: "bird", animation: "bird", palette: "bird-default", sound: "fire" }, - { action: "death", sprite: "bird", animation: "bird", palette: "bird-default" }] - } +{ + name: "Thorfinn", + required_mappings: [ + { game_entity: "player1", animation_states: ["idle", "move"] }, + { game_entity: "player2", animation_states: ["idle", "move"] }, + { game_entity: "shark", animation_states: ["move"] }, + { game_entity: "sub", animation_states: ["move"] }, + { game_entity: "ink", animation_states: ["move"] }, + { game_entity: "patrol_sub", animation_states: ["move"] }, + { game_entity: "bullet", animation_states: ["move"] }, + { game_entity: "diver", animation_states: ["move"] }, + { game_entity: "background", animation_states: ["background"] }, + ], + animations: [ + { + source_animation: "cat_idle", + offset: [ 11, 11 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "player1", "player2" ], + animation_states: [ "idle", "move" ], + }, + }, + { + source_animation: "croc_spitting", + offset: [ 0, 10 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "shark" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "evil_dog", + offset: [ 6, 9 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "bird", + offset: [ 1, 3 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "patrol_sub" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "yarn", + offset: [ 7, 3 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "bullet" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "mouse", + offset: [ 0, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "diver" ], + animation_states: [ "move" ], + }, + }, + { + source_animation: "background", + offset: [ -53, 0 ], + looping: true, + max_colors: 12, + game_entity_map: + { + game_entities: [ "background" ], + animation_states: [ "background" ], + }, + }, + ] +} diff --git a/source_assets/thorfinn/sprites/cat/critical_air.palette.png b/source_assets/thorfinn/sprites/cat/critical_air.palette.png deleted file mode 100644 index d335822bd59d863d4510ac0c6d9f641bd1a6f43b..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/cat/critical_air.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/cat/low_air.palette.png b/source_assets/thorfinn/sprites/cat/low_air.palette.png deleted file mode 100644 index f3b61e389301ba54b2d6f33ab1a1aeeb4c17e110..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/cat/low_air.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/cat/p1.palette.png b/source_assets/thorfinn/sprites/cat/p1.palette.png deleted file mode 100644 index a093ba8e4b079b6a15e61eafd9aece2d86d44818..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/cat/p1.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/cat/p2.palette.png b/source_assets/thorfinn/sprites/cat/p2.palette.png deleted file mode 100644 index a093ba8e4b079b6a15e61eafd9aece2d86d44818..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/cat/p2.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/croc/croc_palette_0.palette.png b/source_assets/thorfinn/sprites/croc/croc_palette_0.palette.png deleted file mode 100644 index cc900273591d183b3bdab26ec5afcc5d166a2bb7..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/croc/croc_palette_0.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/croc/croc_palette_1.palette.png b/source_assets/thorfinn/sprites/croc/croc_palette_1.palette.png deleted file mode 100644 index cc900273591d183b3bdab26ec5afcc5d166a2bb7..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/croc/croc_palette_1.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/croc/croc_palette_2.palette.png b/source_assets/thorfinn/sprites/croc/croc_palette_2.palette.png deleted file mode 100644 index cc900273591d183b3bdab26ec5afcc5d166a2bb7..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/croc/croc_palette_2.palette.png and /dev/null differ diff --git a/source_assets/thorfinn/sprites/croc/croc_palette_3.palette.png b/source_assets/thorfinn/sprites/croc/croc_palette_3.palette.png deleted file mode 100644 index cc900273591d183b3bdab26ec5afcc5d166a2bb7..0000000000000000000000000000000000000000 Binary files a/source_assets/thorfinn/sprites/croc/croc_palette_3.palette.png and /dev/null differ diff --git a/src/config_parser.rs b/src/config_parser.rs deleted file mode 100644 index fea955297ee1248a7c6302b1f5877ae2c611b534..0000000000000000000000000000000000000000 --- a/src/config_parser.rs +++ /dev/null @@ -1,86 +0,0 @@ -use std::path::Path; -use serde::{Deserialize, Serialize}; -use anyhow::Result; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct PaletteSetEntry -{ - pub name: String, - pub animation: String, - pub palettes: Vec<String>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AnimationEntry -{ - pub animation: String, - pub offset: [f32; 2], - pub max_colors: Option<u8>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Mapping -{ - pub action: String, - pub sprite: Option<String>, - pub animation: Option<String>, - pub palette: Option<String>, - pub sound: Option<String>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Entity -{ - pub name: String, - pub mappings: Vec<Mapping>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ThemeConfig -{ - pub theme_name: String, - pub palette_sets: Vec<PaletteSetEntry>, - pub animations: Vec<AnimationEntry>, - pub entities: Vec<Entity>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ThemeAsset -{ - pub name: String, - pub path: String, - #[serde(rename = "type")] - pub type_name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ThemeAnimation -{ - pub entity: String, - pub entity_anim: String, - pub source: String, - pub animation: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ThemeSound -{ - pub entity: String, - pub entity_sound: String, - pub source: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ThemeFile -{ - pub name: String, - pub assets: Vec<ThemeAsset>, - pub animations: Vec<ThemeAnimation>, - pub sounds: Vec<ThemeSound>, -} - -pub fn load_theme_config(config_path: &Path) -> Result<ThemeConfig> -{ - let f = std::fs::File::open(config_path)?; - serde_yaml::from_reader::<std::fs::File, ThemeConfig>(f).map(|c| c).map_err(|e| e.into()) -} diff --git a/src/gif_processing.rs b/src/gif_processing.rs new file mode 100644 index 0000000000000000000000000000000000000000..9147ff979c03787df28f0f2b912368df068e5dcc --- /dev/null +++ b/src/gif_processing.rs @@ -0,0 +1,266 @@ +use std::path::PathBuf; +use std::io::BufReader; + + +use fxhash::FxHashMap; +use image::{AnimationDecoder, DynamicImage, GrayImage, Luma}; +use kmeans_colors::{Calculate, get_kmeans, Sort}; +use palette::{Darken, FromColor, IntoColor, Lab, Lch, Lighten, ShiftHue, Srgba}; +use palette::white_point::D65; +use rayon::prelude::*; +use anyhow::Result; +use crate::get_file_stem; + +pub type PaletteColors = Vec<Srgba<u8>>; +pub type PaletteBrightnessVariations = Vec<PaletteColors>; +pub type PaletteColorVariations = Vec<PaletteBrightnessVariations>; + +pub struct GifFrameInfo +{ + pub duration: f32, + pub frame: GrayImage, +} +pub struct ProcessedGif +{ + pub name: String, + pub source: PathBuf, + pub frames: Vec<GifFrameInfo>, + pub palette_variations: PaletteColorVariations, +} + +struct HueModification +{ + starting_shift: f32, + shift_increment: f32, + num_variations: u32, +} + +struct LightnessModification +{ + starting_lightness: f32, + lightness_increment: f32, + num_variations: u32, +} + +struct DarknessModification +{ + starting_darkness: f32, + darkness_increment: f32, + num_variations: u32, +} + +enum PaletteModification +{ + Hue(HueModification), + Lightness(LightnessModification), + Darkness(DarknessModification), +} + +fn read_gif_frames(gif_path: &PathBuf) -> Result<Vec<(DynamicImage, f32)>> +{ + let gif_file = std::fs::File::open(gif_path)?; + let gif_file_buf = BufReader::new(gif_file); + let decoder = image::codecs::gif::GifDecoder::new(gif_file_buf)?; + let frames = decoder.into_frames().collect_frames()?; + + Ok(frames.into_iter().map(|f| { + let delay = f.delay().numer_denom_ms(); + let delay = (delay.0 as f32 / delay.1 as f32) * 0.001; + (DynamicImage::ImageRgba8(f.into_buffer()), delay) + }).collect::<Vec<_>>()) +} + +fn get_gif_lab_pixels(frames: &[(DynamicImage, f32)]) -> (Vec<Lab<D65, f32>>, Vec<(usize, usize)>) +{ + let mut lab_cache: FxHashMap<[u8; 3], Lab<D65, f32>> = FxHashMap::default(); + let mut lab_pixels: Vec<Lab<D65, f32>> = Vec::new(); + let mut frame_ranges = Vec::new(); + let mut current_pixel: usize = 0; + + for (frame, _delay) in frames + { + let starting_pixel = current_pixel; + let pixels: Vec<_> = frame.to_rgba8().pixels().map(|p| Srgba::from(p.0)).collect(); + lab_pixels.extend(pixels.iter().filter(|x: &&Srgba<u8>| x.alpha == 255).map(|color| { + current_pixel += 1; + *lab_cache.entry([color.red, color.green, color.blue]) + .or_insert_with(|| color.into_linear::<_, f32>().into_color()) + })); + + frame_ranges.push((starting_pixel, current_pixel)); + } + + (lab_pixels, frame_ranges) +} + +fn get_sorted_palette(lab_pixels: &[Lab<D65, f32>], max_colors: u8, runs: u32, iterations: usize) -> (Vec<Srgba<u8>>, Vec<u8>) +{ + let result = (0..runs).into_par_iter().map(|i| { + let run_result = get_kmeans(max_colors as usize, iterations, 0.0025, false, &lab_pixels, i as u64); + run_result + }).max_by(|a, b| a.score.partial_cmp(&b.score).unwrap()).unwrap(); + + let sorted_palette = Lab::<D65, f32>::sort_indexed_colors(&result.centroids, &result.indices); + + let sorted_lab_palette = sorted_palette.iter().map(|c| c.centroid).collect::<Vec<_>>(); + let sorted_rgb_palette = sorted_palette.iter().map(|c| Srgba::from_format::<f32, u8>(c.centroid.into_color())); + let mut sorted_rgb_palette_with_alpha = vec![Srgba::<u8>::new(0, 0, 0, 0)]; + sorted_rgb_palette_with_alpha.extend(sorted_rgb_palette); + + let mut indices = Vec::new(); + Lab::<D65, f32>::get_closest_centroid(&lab_pixels, &sorted_lab_palette, &mut indices); + indices.iter_mut().for_each(|i| *i += 1); + + (sorted_rgb_palette_with_alpha, indices) +} + +fn get_palette_variations(palette: &[Srgba<u8>], modification: PaletteModification) -> Vec<Vec<Srgba<u8>>> +{ + match modification + { + PaletteModification::Hue(hue_mod) => + { + (0..hue_mod.num_variations).into_iter().map(|i| { + let hue_shift = hue_mod.starting_shift + i as f32 * hue_mod.shift_increment; + hue_shift_palette(palette, hue_shift) + }).collect() + } + PaletteModification::Lightness(lightness_mod) => + { + (0..lightness_mod.num_variations).into_iter().map(|i| { + let lightness = lightness_mod.starting_lightness + i as f32 * lightness_mod.lightness_increment; + lighten_palette(palette, lightness) + }).collect() + } + PaletteModification::Darkness(darkness_mod) => + { + (0..darkness_mod.num_variations).into_iter().map(|i| { + let darkness = darkness_mod.starting_darkness + i as f32 * darkness_mod.darkness_increment; + darken_palette(palette, darkness) + }).collect() + } + } +} + +fn hue_shift_palette(palette: &[Srgba<u8>], hue_shift: f32) -> Vec<Srgba<u8>> +{ + palette.iter().map(|color| { + if color.alpha != 255 + { + return *color; + } + + let color: Lch = color.into_format::<f32, f32>().into_color(); + let lch = color.shift_hue(hue_shift); + let srgba = Srgba::from_color(lch).into_format(); + srgba + }).collect() +} + +fn lighten_palette(palette: &[Srgba<u8>], amount: f32) -> Vec<Srgba<u8>> +{ + palette.iter().map(|color| { + if color.alpha != 255 + { + return *color; + } + + let color: Lch = color.into_format::<f32, f32>().into_color(); + let lch = color.lighten(amount); + let srgba = Srgba::from_color(lch).into_format(); + srgba + }).collect() +} + +fn darken_palette(palette: &[Srgba<u8>], amount: f32) -> Vec<Srgba<u8>> +{ + palette.iter().map(|color| { + if color.alpha != 255 + { + return *color; + } + + let color: Lch = color.into_format::<f32, f32>().into_color(); + let lch = color.darken(amount); + let srgba = Srgba::from_color(lch).into_format(); + srgba + }).collect() +} + +fn quantize_frame(frame: &DynamicImage, delay: f32, indices: &[u8], frame_indices_range: (usize, usize)) -> GifFrameInfo +{ + let pixels: Vec<_> = frame.to_rgba8().pixels().map(|p| Srgba::from(p.0)).collect(); + let (start_index, end_index) = frame_indices_range; + + let mut next_lab_pixel = 0; + + let mut indexed_image = GrayImage::new(frame.width(), frame.height()); + + for (original, index_pixel) in pixels.iter().zip(indexed_image.pixels_mut()) + { + let index = if original.alpha != 255 + { + 0 + } else { + next_lab_pixel += 1; + let calculated_index = start_index + next_lab_pixel - 1; + debug_assert!(calculated_index < end_index, "Index out of range: {} {} {}", start_index, next_lab_pixel, end_index); + indices[calculated_index] + }; + + *index_pixel = Luma([index]); + } + + GifFrameInfo { frame: indexed_image, duration: delay } +} + +fn quantize_frames(frames: Vec<(DynamicImage, f32)>, max_colors: u8) -> (Vec<GifFrameInfo>, PaletteColorVariations) +{ + let (lab_pixels, frame_indices_ranges) = get_gif_lab_pixels(&frames); + let (sorted_palette, indices) = get_sorted_palette(&lab_pixels, max_colors, 10, 10); + let hue_variations = get_palette_variations(&sorted_palette, PaletteModification::Hue(HueModification { + starting_shift: 0.0, + shift_increment: 20.0, + num_variations: 20, + })); + + let all_variations = hue_variations.iter().map(|palette| { + let mut new_palettes = Vec::new(); + // This one counts backwards because the palettes are ordered from light to dark + let lightness_variations = get_palette_variations(palette, PaletteModification::Lightness(LightnessModification { + starting_lightness: 0.2, + lightness_increment: -0.1, + num_variations: 2, + })); + + let darkness_variations = get_palette_variations(palette, PaletteModification::Darkness(DarknessModification { + starting_darkness: 0.15, + darkness_increment: 0.15, + num_variations: 2, + })); + + // Order the palettes from light to dark + new_palettes.extend(lightness_variations.into_iter()); + new_palettes.extend([palette.clone()]); + new_palettes.extend(darkness_variations.into_iter()); + + new_palettes + + }).collect::<Vec<_>>(); + + let quantized_frames = frames.par_iter().enumerate().map(|(i, (frame, delay))| + { + quantize_frame(&frame, *delay, &indices, frame_indices_ranges[i]) + }).collect::<Vec<_>>(); + + (quantized_frames, all_variations) +} + +pub fn process_gif(gif_path: &PathBuf, max_colors: u8) -> Result<ProcessedGif> +{ + let name = get_file_stem(gif_path)?; + let frames = read_gif_frames(gif_path)?; + let (frames, palette_variations) = quantize_frames(frames, max_colors); + + Ok(ProcessedGif { name, source: gif_path.clone(), frames, palette_variations }) +} diff --git a/src/main.rs b/src/main.rs index 275dea1871bc8fbec127d9cf0f7c5c55abc74234..fedade32725fbc3271d09255a126a8c809d8d9a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,332 +1,19 @@ -mod config_parser; +mod theme_config_parser; mod sprite_parser; +mod theme_parser; +mod gif_processing; use image::*; use std::path::PathBuf; use std::fs; -use std::fs::{create_dir_all, File}; -use std::io::Write; +use std::fs::{create_dir_all, }; use rayon::prelude::*; -use walkdir::*; use anyhow::{anyhow, Result}; -use fxhash::{FxHashMap}; -use image::{AnimationDecoder, DynamicImage, Rgba, RgbaImage}; -use kmeans_colors::{Calculate, get_kmeans, Sort}; -use palette::{white_point::D65, IntoColor, Lab, Srgba, ShiftHue, Lch, FromColor, Darken, Lighten}; -use crate::config_parser::{ThemeAnimation, ThemeAsset, ThemeConfig, ThemeFile, ThemeSound}; -use crate::sprite_parser::{PaletteSet, Sprite, SpriteAnimation, SpriteAnimationFrame, SpriteMaterial, SpritePalette, SpritePalettePath, SpriteParse, SpriteTexture}; - -type PaletteColors = Vec<Srgba<u8>>; -type PaletteBrightnessVariations = Vec<PaletteColors>; -type PaletteColorVariations = Vec<PaletteBrightnessVariations>; - -fn log_error<T>(result: &Result<T>) -{ - match result - { - Ok(_) => {} - Err(e) => println!("{}", e), - } -} - -// fn sprite_parse_to_yaml(sprite_parse: &SpriteParse) -> Result<String> -// { -// Ok(serde_yaml::to_string(sprite_parse)?) -// } -// -// fn sprite_parse_to_toml(sprite_parse: &SpriteParse) -> Result<String> -// { -// Ok(toml::Value::try_from(sprite_parse)?.to_string()) -// } - -fn get_file_stem(path: &PathBuf) -> Result<String> -{ - if let Some(filename) = path.file_stem() - { - if let Some(filename) = filename.to_str() - { - return Ok(filename.into()); - } - } - - Err(anyhow!("Failed to get filename from: {}", path.to_str().unwrap_or("Failed to convert path to string"))) -} - -fn get_final_path_component(path: &PathBuf) -> Result<String> -{ - if let Some(filename) = path.file_name() - { - if let Some(filename) = filename.to_str() - { - return Ok(filename.into()); - } - } - - Err(anyhow!("Failed to get filename from: {}", path.to_str().unwrap_or("Failed to convert path to string"))) -} - -fn get_theme_dirs(root: &PathBuf) -> Vec<PathBuf> -{ - fs::read_dir(root).unwrap().filter_map(|e| e.ok()).filter_map(|e| { - let path = e.path(); - if path.is_dir() { - Some(path) - } else { - None - } - }).collect() -} - -fn get_theme_gif_paths(theme_dir: &PathBuf) -> Vec<PathBuf> -{ - WalkDir::new(theme_dir).into_iter().filter_map(|e| e.ok()).filter_map(|e| { - let path = e.path(); - if path.is_file() && path.extension().unwrap_or_default() == "gif" { - Some(path.to_path_buf()) - } else { - None - } - }).collect() -} - -fn read_gif_frames(gif_path: &PathBuf) -> Result<Vec<(DynamicImage, f32)>> -{ - let gif = std::fs::File::open(gif_path)?; - let decoder = image::codecs::gif::GifDecoder::new(gif)?; - let frames = decoder.into_frames().collect_frames()?; - - Ok(frames.into_iter().map(|f| { - let delay = f.delay().numer_denom_ms(); - let delay = (delay.0 as f32 / delay.1 as f32) * 0.001; - (DynamicImage::ImageRgba8(f.into_buffer()), delay) - }).collect::<Vec<_>>()) -} - -fn get_gif_lab_pixels(frames: &[(DynamicImage, f32)]) -> (Vec<Lab<D65, f32>>, Vec<(usize, usize)>) -{ - let mut lab_cache: FxHashMap<[u8; 3], Lab<D65, f32>> = FxHashMap::default(); - let mut lab_pixels: Vec<Lab<D65, f32>> = Vec::new(); - let mut frame_ranges = Vec::new(); - let mut current_pixel: usize = 0; - - for (frame, _delay) in frames - { - let starting_pixel = current_pixel; - let pixels: Vec<_> = frame.to_rgba8().pixels().map(|p| Srgba::from(p.0)).collect(); - lab_pixels.extend(pixels.iter().filter(|x: &&Srgba<u8>| x.alpha == 255).map(|color| { - current_pixel += 1; - *lab_cache.entry([color.red, color.green, color.blue]) - .or_insert_with(|| color.into_linear::<_, f32>().into_color()) - })); - - frame_ranges.push((starting_pixel, current_pixel)); - } - - (lab_pixels, frame_ranges) -} - -fn get_sorted_palette(lab_pixels: &[Lab<D65, f32>], max_colors: u8, runs: u32, iterations: usize) -> (Vec<Srgba<u8>>, Vec<u8>) -{ - let result = (0..runs).into_par_iter().map(|i| { - let run_result = get_kmeans(max_colors as usize, iterations, 0.0025, false, &lab_pixels, i as u64); - run_result - }).max_by(|a, b| a.score.partial_cmp(&b.score).unwrap()).unwrap(); - - let sorted_palette = Lab::<D65, f32>::sort_indexed_colors(&result.centroids, &result.indices); - - let sorted_lab_palette = sorted_palette.iter().map(|c| c.centroid).collect::<Vec<_>>(); - let sorted_rgb_palette = sorted_palette.iter().map(|c| Srgba::from_format::<f32, u8>(c.centroid.into_color())); - let mut sorted_rgb_palette_with_alpha = vec![Srgba::<u8>::new(0, 0, 0, 0)]; - sorted_rgb_palette_with_alpha.extend(sorted_rgb_palette); - - let mut indices = Vec::new(); - Lab::<D65, f32>::get_closest_centroid(&lab_pixels, &sorted_lab_palette, &mut indices); - indices.iter_mut().for_each(|i| *i += 1); - - (sorted_rgb_palette_with_alpha, indices) -} - -fn write_palette_to_file(filename: &PathBuf, palette: &[Srgba<u8>]) -> Result<()> -{ - let mut image = RgbaImage::new(palette.len() as u32, 1); - image.pixels_mut().zip(palette).par_bridge().for_each(|(pixel, original)| - { - *pixel = Rgba((*original).into()); - }); - - image.save(filename)?; - Ok(()) -} - -struct HueModification -{ - starting_shift: f32, - shift_increment: f32, - num_variations: u32, -} - -struct LightnessModification -{ - starting_lightness: f32, - lightness_increment: f32, - num_variations: u32, -} - -struct DarknessModification -{ - starting_darkness: f32, - darkness_increment: f32, - num_variations: u32, -} - -enum PaletteModification -{ - Hue(HueModification), - Lightness(LightnessModification), - Darkness(DarknessModification), -} - -fn get_palette_variations(palette: &[Srgba<u8>], modification: PaletteModification) -> Vec<Vec<Srgba<u8>>> -{ - match modification - { - PaletteModification::Hue(hue_mod) => - { - (0..hue_mod.num_variations).into_iter().map(|i| { - let hue_shift = hue_mod.starting_shift + i as f32 * hue_mod.shift_increment; - hue_shift_palette(palette, hue_shift) - }).collect() - } - PaletteModification::Lightness(lightness_mod) => - { - (0..lightness_mod.num_variations).into_iter().map(|i| { - let lightness = lightness_mod.starting_lightness + i as f32 * lightness_mod.lightness_increment; - lighten_palette(palette, lightness) - }).collect() - } - PaletteModification::Darkness(darkness_mod) => - { - (0..darkness_mod.num_variations).into_iter().map(|i| { - let darkness = darkness_mod.starting_darkness + i as f32 * darkness_mod.darkness_increment; - darken_palette(palette, darkness) - }).collect() - } - } -} - -fn hue_shift_palette(palette: &[Srgba<u8>], hue_shift: f32) -> Vec<Srgba<u8>> -{ - palette.iter().map(|color| { - if color.alpha != 255 - { - return *color; - } - - let color: Lch = color.into_format::<f32, f32>().into_color(); - let lch = color.shift_hue(hue_shift); - let srgba = Srgba::from_color(lch).into_format(); - srgba - }).collect() -} - -fn lighten_palette(palette: &[Srgba<u8>], amount: f32) -> Vec<Srgba<u8>> -{ - palette.iter().map(|color| { - if color.alpha != 255 - { - return *color; - } - - let color: Lch = color.into_format::<f32, f32>().into_color(); - let lch = color.lighten(amount); - let srgba = Srgba::from_color(lch).into_format(); - srgba - }).collect() -} - -fn darken_palette(palette: &[Srgba<u8>], amount: f32) -> Vec<Srgba<u8>> -{ - palette.iter().map(|color| { - if color.alpha != 255 - { - return *color; - } - - let color: Lch = color.into_format::<f32, f32>().into_color(); - let lch = color.darken(amount); - let srgba = Srgba::from_color(lch).into_format(); - srgba - }).collect() -} - -fn quantize_frame(frame: &DynamicImage, delay: f32, indices: &[u8], frame_indices_range: (usize, usize)) -> GifFrameInfo -{ - let pixels: Vec<_> = frame.to_rgba8().pixels().map(|p| Srgba::from(p.0)).collect(); - let (start_index, end_index) = frame_indices_range; - - let mut next_lab_pixel = 0; - - let mut indexed_image = GrayImage::new(frame.width(), frame.height()); - - for (original, index_pixel) in pixels.iter().zip(indexed_image.pixels_mut()) - { - let index = if original.alpha != 255 - { - 0 - } else { - next_lab_pixel += 1; - let calculated_index = start_index + next_lab_pixel - 1; - debug_assert!(calculated_index < end_index, "Index out of range: {} {} {}", start_index, next_lab_pixel, end_index); - indices[calculated_index] - }; - - *index_pixel = Luma([index]); - } - - GifFrameInfo { frame: indexed_image, duration: delay } -} - -fn quantize_frames(frames: Vec<(DynamicImage, f32)>, max_colors: u8) -> (Vec<GifFrameInfo>, PaletteColorVariations) -{ - let (lab_pixels, frame_indices_ranges) = get_gif_lab_pixels(&frames); - let (sorted_palette, indices) = get_sorted_palette(&lab_pixels, max_colors, 10, 10); - let hue_variations = get_palette_variations(&sorted_palette, PaletteModification::Hue(HueModification { - starting_shift: 0.0, - shift_increment: 20.0, - num_variations: 20, - })); - - let all_variations = hue_variations.iter().map(|palette| { - let mut new_palettes = Vec::new(); - // This one counts backwards because the palettes are ordered from light to dark - let lightness_variations = get_palette_variations(palette, PaletteModification::Lightness(LightnessModification { - starting_lightness: 0.2, - lightness_increment: -0.1, - num_variations: 2, - })); - - let darkness_variations = get_palette_variations(palette, PaletteModification::Darkness(DarknessModification { - starting_darkness: 0.15, - darkness_increment: 0.15, - num_variations: 2, - })); - - // Order the palettes from light to dark - new_palettes.extend(lightness_variations.into_iter()); - new_palettes.extend([palette.clone()]); - new_palettes.extend(darkness_variations.into_iter()); - - new_palettes - - }).collect::<Vec<_>>(); - - let quantized_frames = frames.par_iter().enumerate().map(|(i, (frame, delay))| - { - quantize_frame(&frame, *delay, &indices, frame_indices_ranges[i]) - }).collect::<Vec<_>>(); - - (quantized_frames, all_variations) -} +use image::{Rgba, RgbaImage}; +use palette::{Srgba}; +use crate::theme_config_parser::{ThemeConfig}; +use crate::gif_processing::*; +use crate::theme_parser::{generate_theme_file, serialize_theme_file}; #[derive(Clone)] struct ImageRect @@ -362,6 +49,7 @@ struct SpriteSheet image: GrayImage, group_name: String, spacing: u16, + filename: PathBuf, } impl SpriteSheet @@ -380,6 +68,7 @@ impl SpriteSheet frame_mappings: Vec::new(), image: GrayImage::new(max_dimensions as u32, max_dimensions as u32), spacing, + filename: PathBuf::new(), } } @@ -492,12 +181,13 @@ fn generate_sprite_sheets(destination_dir: &PathBuf, group_name: &str, max_dimen // Crop all sprite sheets to size sprite_sheets.iter_mut().enumerate().for_each(|(i, sheet)|{ sheet.image = sheet.image.view(0, 0, sheet.width as u32, sheet.height as u32).to_image(); - let path = destination_dir.join(group_name); - let file_name = make_texture_name(&sheet.group_name, i as u16); + let path = destination_dir; + let file_name = format!("{}-sheet-{}.indexed", &sheet.group_name, i as u16); let file_path = path.join(format!("{}.png", file_name)); create_dir_all(&path).expect("Failed to create sprite sheet directory"); - sheet.image.save(file_path).expect("Failed to save sprite sheet image"); + sheet.image.save(&file_path).expect("Failed to save sprite sheet image"); + sheet.filename = file_path; }); sprite_sheets.iter_mut().for_each(|sprite_sheet| { @@ -516,23 +206,11 @@ fn generate_sprite_sheets(destination_dir: &PathBuf, group_name: &str, max_dimen Ok(sprite_sheets) } -struct GifFrameInfo -{ - duration: f32, - frame: GrayImage, -} -struct ProcessedGif -{ - name: String, - source: PathBuf, - frames: Vec<GifFrameInfo>, - palette_variations_index: u16, -} - -fn render_color_sprite_sheet(group_name: &str, destination_dir: &PathBuf, sprite_sheet: &SpriteSheet, palettes: &Vec<PaletteColorVariations>) -> Result<()> +fn render_color_sprite_sheet(destination_dir: &PathBuf, sprite_sheet: &SpriteSheet, palettes: &PaletteColorVariations) -> Result<PathBuf> { let mut image = RgbaImage::new(sprite_sheet.image.width(), sprite_sheet.image.height()); + let group_name = &sprite_sheet.group_name; let sprite_sheet_index = sprite_sheet.index; create_dir_all(destination_dir)?; let file_name = format!("{group_name}-sheet-{sprite_sheet_index}_default.png"); @@ -546,7 +224,7 @@ fn render_color_sprite_sheet(group_name: &str, destination_dir: &PathBuf, sprite let mut destination = image.sub_image(x as u32, y as u32, width as u32, height as u32); let source = sprite_sheet.image.view(x as u32, y as u32, width as u32, height as u32); - let palette = &palettes[frame_mapping.gif_index as usize][0][2]; + let palette = &palettes[0][2]; for (x, y, src_pixel) in source.pixels() { let index = src_pixel[0] as usize; @@ -556,439 +234,93 @@ fn render_color_sprite_sheet(group_name: &str, destination_dir: &PathBuf, sprite } } - image.save(path)?; - - Ok(()) -} - -struct ThemeSerializationConfig -{ - theme_name: String, - group_name: String, - config: ThemeConfig, - destination_images_dir: PathBuf, - destination_sprites_dir: PathBuf, - - sprite_sheets: Vec<SpriteSheet>, - palettes: Vec<PaletteColorVariations>, -} - -fn generate_theme_config(destination_theme_dir: &PathBuf, config: &ThemeConfig, sprite_file_paths: &[SpriteFile]) -> Result<()> -{ - let assets = sprite_file_paths.iter().map(|SpriteFile { name, path }| - { - let path = path.to_str().expect("Failed to convert path to string").to_string(); - - Ok(ThemeAsset - { - name: name.clone(), - path, - type_name: "sprite".to_string() - }) - }) - .inspect(log_error) - .flatten() - .collect::<Vec<_>>(); - - let animations = config.entities.iter().flat_map(|entity| - { - entity.mappings.iter().flat_map(|mapping| - { - match (&mapping.sprite, &mapping.animation) - { - (Some(sprite), Some(animation)) => Some(ThemeAnimation - { - entity: entity.name.clone(), - entity_anim: mapping.action.clone(), - source: sprite.clone(), - animation: animation.clone() - }), - _ => None - } - }) - }) - .collect::<Vec<_>>(); - - let sounds = config.entities.iter().flat_map(|entity| - { - entity.mappings.iter().flat_map(|mapping| - { - mapping.sound.as_ref().map(|sound| - { - ThemeSound - { - entity: entity.name.clone(), - entity_sound: mapping.action.clone(), - source: sound.clone() - } - }) - }) - }) - .collect::<Vec<_>>(); + image.save(&path)?; - let theme_data = ThemeFile - { - name: config.theme_name.clone(), - assets, - animations, - sounds, - }; - - let destination_theme_file_path = destination_theme_dir.join("theme.toml"); - //println!("Theme file: {:?}", theme_data); - println!("Writing theme file: {:?}", destination_theme_file_path); - let mut theme_file = File::create(destination_theme_file_path)?; - Ok(theme_file.write_all(toml::to_string(&theme_data)?.as_bytes())?) + Ok(path) } -fn process_theme(source_dir: &PathBuf, destination_dir: &PathBuf, max_dimensions: u16) -> Result<()> +fn process_theme(theme_dir: &PathBuf, destination_dir: &PathBuf, max_dimensions: u16) -> Result<()> { - let sprite_dirs = get_theme_dirs(&source_dir.join("sprites")); - let theme_config_path = source_dir.join("config.yml"); - let theme_config = config_parser::load_theme_config(&theme_config_path)?; - let theme_name = get_final_path_component(source_dir)?; - let theme_destination_dir = destination_dir.join(&theme_name); - let theme_images_destination_dir = theme_destination_dir.join("images"); - let theme_sprite_file_destination_dir = theme_destination_dir.join("sprites"); - let render_destination_dir= theme_destination_dir.join("renders"); - //dbg!(&sprite_dirs, &theme_name, &theme_images_destination_dir, &theme_sprite_file_destination_dir); - - let sprite_paths = sprite_dirs.par_iter().map(|sprite_dir| { - let gif_paths = get_theme_gif_paths(sprite_dir); - fn process_gif(gif_path: &PathBuf, max_colors: u8) -> Result<(ProcessedGif, Vec<PaletteBrightnessVariations>)> - { - let name = get_file_stem(gif_path)?; - let frames = read_gif_frames(gif_path)?; - let (frames, palette_variations) = quantize_frames(frames, max_colors); - - Ok((ProcessedGif { name, source: gif_path.clone(), frames, palette_variations_index: 0 }, palette_variations)) - } - - let anim_group_name = get_final_path_component(sprite_dir)?; - let max_colors = theme_config.animations.iter().find_map(|animation| { - if animation.animation == anim_group_name - { - animation.max_colors - } else { - None - } - }).unwrap_or(15); - - let mut process_gifs_results = gif_paths.par_iter().filter_map(|g| { - process_gif(g, max_colors).ok() - }).collect::<Vec<_>>(); - - process_gifs_results.sort_by(|a, b| b.0.frames[0].frame.width().cmp(&a.0.frames[0].frame.width())); + let theme_config_path = theme_dir.join("config.yml"); + let theme_config = load_theme_config(&theme_config_path)?; - let mut all_palette_variations = Vec::new(); - let mut processed_gifs = Vec::new(); + dbg!(&theme_config.name); - // TODO: Deduplicate palette variations and update palette indices - for (mut processed_gif, palette_variations) in process_gifs_results - { - all_palette_variations.push(palette_variations); - processed_gif.palette_variations_index = all_palette_variations.len() as u16 - 1; - processed_gifs.push(processed_gif); - } - - let group_name = get_final_path_component(sprite_dir)?; - let sprite_sheets = generate_sprite_sheets(&theme_images_destination_dir, &group_name, max_dimensions, &processed_gifs)?; - sprite_sheets.iter().for_each(|sprite_sheet| render_color_sprite_sheet(&group_name, &render_destination_dir, sprite_sheet, &all_palette_variations).expect("Failed to render sprite sheet")); - - let theme_serialization_config = ThemeSerializationConfig { - theme_name: theme_name.clone(), - group_name, - config: theme_config.clone(), - destination_images_dir: theme_images_destination_dir.clone(), - destination_sprites_dir: theme_sprite_file_destination_dir.clone(), - sprite_sheets, - palettes: all_palette_variations, - }; - - generate_sprite_files(&theme_serialization_config, &processed_gifs) - }).inspect(log_error) - .flatten() - .collect::<Vec<_>>(); + let animations_sprites = theme_config.animations.iter().map(|anim| anim.source_animation.clone()).collect::<Vec<_>>(); + dbg!(&animations_sprites); - generate_theme_config(&theme_destination_dir, &theme_config, &sprite_paths) -} + let theme_file = generate_theme_file(&theme_config, &theme_dir, &destination_dir, max_dimensions)?; -struct SpriteFile -{ - name: String, - path: PathBuf, -} + let theme_file_destination = destination_dir.join(&theme_config.name); + create_dir_all(&theme_file_destination)?; + serialize_theme_file(&theme_file, &theme_file_destination)?; -fn generate_sprite_files(theme_config: &ThemeSerializationConfig, processed_gifs: &[ProcessedGif]) -> Result<SpriteFile> -{ - //dbg!(theme_config); - //let gif_paths = processed_gifs.iter().map(|gif| gif.source.clone()).collect::<Vec<_>>(); - //let sprite_names = processed_gifs.iter().filter_map(|gif| get_file_stem(&gif.source).ok()).collect::<Vec<_>>(); - //dbg!(gif_paths, sprite_names); - - let sprite_textures = get_sprite_textures(theme_config.theme_name.as_str(), theme_config.group_name.as_str(), &theme_config.sprite_sheets); - //dbg!(&sprite_textures); - - let sprites = get_sprites(&theme_config); - //dbg!(&sprites); - - let materials = get_materials(theme_config.group_name.as_str(), &theme_config.sprite_sheets); - //dbg!(&materials); - - let (palette_files, palettes) = write_palettes(&theme_config, processed_gifs); - //dbg!(&palettes); - - let animations = get_animations(&theme_config); - //dbg!(&animations); - - let sprite_parse = SpriteParse - { - name: theme_config.group_name.clone(), - textures: sprite_textures, - sprites, - materials, - palette_files, - animations, - palettes, - }; - - //dbg!(&sprite_parse); - - let toml = toml::to_string(&sprite_parse)?; - let destination_dir = theme_config.destination_sprites_dir.join(&theme_config.group_name); - let destination = destination_dir.join(format!("{}.toml", sprite_parse.name)); - //dbg!(&destination); - - create_dir_all(destination_dir)?; - let mut file = File::create(&destination)?; - file.write_all(toml.as_bytes())?; - - Ok(SpriteFile { name: sprite_parse.name, path: destination }) -} - -fn make_material_name(group_name: &str, sprite_sheet_index: u16) -> String -{ - let sprite_name = format!("{group_name}-sheet-{sprite_sheet_index}.indexed"); - let material_name = format!("{}_material_{}", sprite_name, sprite_sheet_index); - - material_name + Ok(()) } -fn make_texture_name(group_name: &str, sprite_sheet_index: u16) -> String +fn load_theme_config(theme_config_path: &PathBuf) -> Result<ThemeConfig> { - format!("{group_name}-sheet-{sprite_sheet_index}.indexed") + theme_config_parser::load_theme_config(theme_config_path) } -fn make_sprite_name(group_name: &str, animation_index: u32, frame_index: u16) -> String +fn main() -> Result<()> { - format!("{group_name}_{animation_index}_{frame_index}") -} + // Inputs + let max_dimension = 1024; + let source_dir = PathBuf::from("./source_assets/"); + let theme_dirs = get_theme_dirs(&source_dir); + //let theme_dirs = [PathBuf::from("./source_assets/fallback")]; + theme_dirs.par_iter().map(|theme| process_theme(theme, &"./themes".into(), max_dimension)) //{ process_theme(theme, &"./themes".into(), max_dimension) }) + .inspect(log_error) + .flatten() + .for_each(|_| {}); -fn make_palette_name(group_name: &str, color_variation_index: u16, brightness_index: u16) -> String -{ - format!("{group_name}-palette-{color_variation_index}_{brightness_index}") + Ok(()) } -fn write_palettes(theme_config: &ThemeSerializationConfig, processed_gifs: &[ProcessedGif]) -> (Vec<SpritePalettePath>, Vec<SpritePalette>) +fn log_error<T>(result: &Result<T>) { - let mut palette_paths = Vec::new(); - let mut palette_index = 1; - let mut sprite_palettes = Vec::new(); - - for (gif_index, processed_gif) in processed_gifs.iter().enumerate() + match result { - let group_name = theme_config.group_name.clone(); - let default_palettes_name = format!("{}-default", processed_gif.name); - let default_palettes_path = theme_config.destination_images_dir.join(&format!("{group_name}/{default_palettes_name}.palette.png")); - let path_dir = theme_config.destination_images_dir.join(&format!("{group_name}")); - create_dir_all(&path_dir).ok(); - - // TODO: Exclude unwanted palettes somehow - palette_paths.push(SpritePalettePath - { - name: default_palettes_name, - path: default_palettes_path.to_str().expect("Failed to convert path to string").to_string(), - }); - - let color_index = processed_gif.palette_variations_index as usize; - write_palette_to_file(&default_palettes_path, &theme_config.palettes[gif_index][color_index][2]).ok(); - - palette_paths.extend(theme_config.palettes.iter().enumerate().flat_map(|(_, palette_variations)| - { - palette_variations.iter().enumerate().flat_map(|(i, brightness)| - { - let mut sprite_palette = SpritePalette - { - name: format!("palette_{i}").to_string(), - default_variation: palette_index + 2, // The middle variation is 2 after the first - variations: Vec::new() - }; - - let variations = brightness.iter().enumerate().map(|(j, brightness_palette)| - { - let name = make_palette_name(&group_name, i as u16, j as u16); - let path_dir = path_dir.join(&format!("palettes/{}", sprite_palette.name)); - create_dir_all(&path_dir).ok(); - - let path = path_dir.join(&format!("{name}.palette.png")); - - write_palette_to_file(&path, brightness_palette)?; - - sprite_palette.variations.push(palette_index); - palette_index += 1; - - Ok(SpritePalettePath - { - name, - path: path.to_str().expect("Failed to convert path to string").to_string(), - }) - }).collect::<Vec<_>>(); - - sprite_palettes.push(sprite_palette); - - variations - }).collect::<Vec<_>>() - }).inspect(log_error).flatten().collect::<Vec<_>>()); + Ok(_) => {} + Err(e) => println!("{}", e), } - - (palette_paths, sprite_palettes) } -fn get_materials(group_name: &str, sprite_sheets: &[SpriteSheet]) -> Vec<SpriteMaterial> +fn get_file_stem(path: &PathBuf) -> Result<String> { - sprite_sheets.iter().map(|sheet| { - let name = make_material_name(group_name, sheet.index as u16); - let texture_name = make_texture_name(group_name, sheet.index as u16); - SpriteMaterial + if let Some(filename) = path.file_stem() + { + if let Some(filename) = filename.to_str() { - name, - texture_name, + return Ok(filename.into()); } - }).collect() -} - -fn get_sprite_textures(theme_name: &str, animation_group_name: &str, sprite_sheets: &[SpriteSheet]) -> Vec<SpriteTexture> -{ - sprite_sheets.iter().flat_map(|sprite_sheet| sprite_sheet.frame_mappings.iter().map(|_frame_mapping| - { - let sprite_sheet_index = sprite_sheet.index; - let name = make_texture_name(animation_group_name, sprite_sheet_index as u16); - let path = format!("./themes/{theme_name}/images/{animation_group_name}/{name}.png"); + } - SpriteTexture - { - name, - texture_path: path.into(), - paletted: true, - filtered: false, - } - }).collect::<Vec<_>>()).collect::<Vec<_>>() + Err(anyhow!("Failed to get filename from: {}", path.to_str().unwrap_or("Failed to convert path to string"))) } -fn get_sprites(theme_config: &ThemeSerializationConfig) -> Vec<Sprite> +fn get_theme_dirs(root: &PathBuf) -> Vec<PathBuf> { - theme_config.sprite_sheets.iter().flat_map(|sprite_sheet| - { - sprite_sheet.frame_mappings.iter().map(|frame_mapping| - { - let sprite_sheet_index = sprite_sheet.index; - let frame_index = frame_mapping.frame_index; - let animation_name = frame_mapping.animation_name.clone(); - - let name = make_sprite_name(&theme_config.group_name.clone(), frame_mapping.gif_index, frame_index); - let material_name = make_material_name(&theme_config.group_name, sprite_sheet_index as u16); - - let total_width = sprite_sheet.width as f32; - let total_height = sprite_sheet.height as f32; - let x1 = frame_mapping.image_rect.x as f32; - let y1 = frame_mapping.image_rect.y as f32; - let x2 = x1 + frame_mapping.image_rect.width as f32; - let y2 = y1 + frame_mapping.image_rect.height as f32; - let color_map_coords = [x1 / total_width, y1 / total_height, x2 / total_width, y2 / total_height]; - - let palette_name = Some(format!("{}-default", animation_name).to_string()); //Some(format!("{theme_config.group_name}-palette-{frame_mapping.palette_variations_index}")); - - Sprite - { - name, // {group_name}_{animation index}_{frame_index} ex: sub_1_1 - material_name, // {group_name}_material_{sprite_sheet_index} ex: sub-sheet-0.indexed_material_0 - color_map_coords, // [uv coords] - palette_name, // sub_side-default - } - }).collect::<Vec<_>>() + fs::read_dir(root).unwrap().filter_map(|e| e.ok()).filter_map(|e| { + let path = e.path(); + if path.is_dir() { + Some(path) + } else { + None + } }).collect() } -fn get_animations(theme_config: &ThemeSerializationConfig) -> Vec<SpriteAnimation> +fn write_palette_to_file(filename: &PathBuf, palette: &[Srgba<u8>]) -> Result<()> { - let animation_offsets = theme_config.config.animations.iter().map(|anim| - { - (anim.animation.clone(), anim.offset) - }).collect::<FxHashMap<_, _>>(); - - let mut animations = FxHashMap::default(); - - theme_config.sprite_sheets.iter().for_each(|sprite_sheet| + let mut image = RgbaImage::new(palette.len() as u32, 1); + image.pixels_mut().zip(palette).par_bridge().for_each(|(pixel, original)| { - sprite_sheet.frame_mappings.iter().for_each(|frame_mapping| - { - let animation_group_name = theme_config.group_name.clone(); - let animation_name = frame_mapping.animation_name.clone(); - let color_map_coords = frame_mapping.uv_coords; - let duration = frame_mapping.duration; - let palette_name = Some(format!("{}-default", animation_name).to_string()); - let sprite_name = make_sprite_name(&animation_group_name, frame_mapping.gif_index, frame_mapping.frame_index); - - let animation_frame = SpriteAnimationFrame - { - color_map_coords, - duration, - palette_name, - sprite_name, - }; - - let frames = animations.entry(animation_name.clone()).or_insert_with(|| { - vec![] - }); - - frames.push(animation_frame); - }); + *pixel = Rgba((*original).into()); }); - animations.into_iter().map(|(animation_name, frames)| - { - let offset = *animation_offsets.get(&animation_name).unwrap_or(&[0.0, 0.0]); - let palette_sets = theme_config.config.palette_sets.iter().filter(|p| p.animation == animation_name).map(|p| - { - PaletteSet - { - set_name: p.name.clone(), - frame_palettes: p.palettes.clone(), - } - }).collect::<Vec<_>>(); - - SpriteAnimation - { - name: animation_name.clone(), - offset, - looping: Some(true), // What is correct here - palette_sets, - frames, - } - }).collect::<Vec<_>>() -} - -fn main() -> Result<()> -{ - // Inputs - let max_dimension = 1024; - let source_dir = PathBuf::from("./source_assets/"); - let theme_dirs = get_theme_dirs(&source_dir); - //let theme_dirs = get_theme_gif_paths(&source_dir); - theme_dirs.par_iter().map(|theme| { process_theme(theme, &"./themes".into(), max_dimension) }) - .inspect(log_error) - .flatten() - .for_each(|_| {}); - + image.save(filename)?; Ok(()) } + diff --git a/src/theme_config_parser.rs b/src/theme_config_parser.rs new file mode 100644 index 0000000000000000000000000000000000000000..08ce2e141776a4e7ff5f2c92aa1eda75271cb297 --- /dev/null +++ b/src/theme_config_parser.rs @@ -0,0 +1,91 @@ +use std::path::{Path, }; +use serde::{Deserialize, Serialize}; +use anyhow::{anyhow, Result}; +use fxhash::FxHashMap; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeGameEntityConfig +{ + pub game_entities: Vec<String>, + pub animation_states: Vec<String>, +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeAnimationConfig +{ + pub source_animation: String, + pub offset: Option<[f32; 2]>, + pub game_entity_map: ThemeGameEntityConfig, + pub looping: Option<bool>, + pub max_colors: Option<u8>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeRequiredMappingConfig +{ + pub game_entity: String, + pub animation_states: Vec<String>, +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeConfig +{ + pub name: String, + pub animations: Vec<ThemeAnimationConfig>, + pub required_mappings: Vec<ThemeRequiredMappingConfig>, +} + +fn generate_entity_mappings(theme_config: &ThemeConfig) -> Result<FxHashMap<(String, String), String>> +{ + let mappings = theme_config.animations + .iter() + .flat_map(|anim_config| { + let entity_map = &anim_config.game_entity_map; + entity_map.game_entities.iter() + .flat_map(|game_entity| { + entity_map.animation_states.iter().map(|anim_state| + ((game_entity.clone(), anim_state.clone()), anim_config.source_animation.clone())) + }) + }) + .collect::<FxHashMap<_, _>>(); + + let errors = validate_mappings(&mappings, &theme_config.required_mappings); + if !errors.is_empty() + { + return Err(anyhow!("Failed to validate mappings: {:?}", errors)); + } + + Ok(mappings) +} + +fn validate_mappings(theme_mappings: &FxHashMap<(String, String), String>, theme_requirements: &[ThemeRequiredMappingConfig]) -> Vec<String> +{ + let mut errors = Vec::new(); + + for mapping in theme_requirements + { + for animation_state in &mapping.animation_states + { + let game_entity = mapping.game_entity.clone(); + let state = animation_state.clone(); + let key = (game_entity.clone(), state.clone()); + if !theme_mappings.contains_key(&key) + { + errors.push(format!("Missing mapping - entity: {}, state: {}", game_entity, state)); + } + } + } + + errors +} + +pub fn load_theme_config(config_path: &Path) -> Result<ThemeConfig> +{ + let f = std::fs::File::open(config_path)?; + let config = serde_yaml::from_reader::<std::fs::File, ThemeConfig>(f).map(|c| c).map_err(|e| anyhow!(e))?; + + if let Err(errors) = generate_entity_mappings(&config) + { + log::warn!("{}", errors); + } + + Ok(config) +} diff --git a/src/theme_parser.rs b/src/theme_parser.rs new file mode 100644 index 0000000000000000000000000000000000000000..18afd9568d9f587f6fed38f6dac2a33f76cdd058 --- /dev/null +++ b/src/theme_parser.rs @@ -0,0 +1,245 @@ +use std::fs::create_dir_all; +use std::hash::{Hash, Hasher}; +use std::io::{Read, Write}; +use std::path::{PathBuf}; +use serde::{Deserialize, Serialize}; +use anyhow::{Error, Result}; +use fxhash::{FxHashMap, }; +use crate::{generate_sprite_sheets, log_error, render_color_sprite_sheet, write_palette_to_file}; +use crate::gif_processing::{PaletteColors, process_gif}; +use crate::theme_config_parser::ThemeConfig; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeAsset +{ + pub name: String, + pub path: String, + #[serde(rename = "type")] + pub type_name: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ThemeSound +{ + pub entity: String, + pub entity_sound: String, + pub source: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] +pub struct ThemePaletteGroup +{ + pub name: String, + pub palettes: Vec<usize>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct ThemeAnimationFrame +{ + pub image: usize, + pub duration: f32, + pub width: u16, + pub height: u16, + pub uv_coords: [f32; 4], +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct ThemeAnimation +{ + pub name: String, + pub looping: bool, + pub offset: [f32; 2], + pub frames: Vec<ThemeAnimationFrame>, + pub palette_groups_index: Vec<usize>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct ThemeFile +{ + pub name: String, + pub image_paths: Vec<PathBuf>, + pub sound_paths: Vec<PathBuf>, + pub palette_paths: Vec<PathBuf>, + + pub palette_groups: Vec<ThemePaletteGroup>, + pub animations: Vec<ThemeAnimation>, + pub game_to_theme_animation_map: FxHashMap<(String, String), usize>, // (game_entity, animation_state) -> animation index +} + +impl ThemeFile +{ + pub fn load(file_path: &PathBuf) -> Result<Self> + { + let mut file = std::fs::File::open(file_path)?; + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes)?; + dbg!("Theme size: {}", bytes.len()); + let result: Self = bincode::deserialize(&bytes)?; + Ok(result) + } +} + +fn hash_palette(palette: &PaletteColors) -> u64 +{ + let mut hasher = fxhash::FxHasher::default(); + for color in palette.iter() + { + color.into_components().hash(&mut hasher); + } + hasher.finish() +} + +pub fn serialize_theme_file(theme_file: &ThemeFile, file_path: &PathBuf) -> Result<()> +{ + let bin_theme_file_name = format!("{}.theme", theme_file.name); + let yaml_theme_file_name = format!("{}.yaml", theme_file.name); + + let encoded: Vec<u8> = bincode::serialize(&theme_file)?; + let mut file = std::fs::File::create(file_path.join(&bin_theme_file_name))?; + file.write_all(&encoded)?; + + let yaml = serde_yaml::to_string(&theme_file)?; + let mut file = std::fs::File::create(file_path.join(yaml_theme_file_name))?; + file.write_all(yaml.as_bytes())?; + + Ok(()) +} + +pub fn generate_theme_file(theme_config: &ThemeConfig, theme_dir: &PathBuf, destination_dir: &PathBuf, max_dimensions: u16) -> Result<ThemeFile> +{ + let mut theme_file = ThemeFile { + name: theme_config.name.clone(), + image_paths: vec![], // Generated sprite sheets + sound_paths: vec![], // Copied directly from the theme + palette_paths: vec![], // Generated from animations + palette_groups: vec![], // Generated from animations + animations: vec![], // Generated from gif processing + game_to_theme_animation_map: FxHashMap::default(), + }; + + let source_animations_dir = theme_dir.join("animations"); + let mut theme_gifs = Vec::new(); + let mut color_palettes = FxHashMap::default(); + let mut palette_groups_map = FxHashMap::default(); + + for anim in &theme_config.animations + { + //let theme_animation_frames = []; // Generated by loading the gif + let gif_path = source_animations_dir.join( format!("{}.gif", anim.source_animation)); + let processed_gif = process_gif(&gif_path, anim.max_colors.unwrap_or(15))?; + + let palettes_dir = destination_dir + .join(&theme_config.name) + .join("palettes"); + create_dir_all(&palettes_dir)?; + + // Each color group + let palette_groups_index = processed_gif.palette_variations.iter().enumerate() + .map(|(i, palette)| + { + let name = format!("{}_color_{i}", anim.source_animation); + + // Each brightness palette + let palettes = palette.iter().map(|colors| + { + let hash_value = hash_palette(colors); + if let Some(palette_id) = color_palettes.get(&hash_value) + { + Ok::<_, Error>(*palette_id) + } + else + { + + let name = format!("{hash_value}"); + let file_path = palettes_dir.join(format!("{}.palette.png", name)); + + write_palette_to_file(&file_path, colors)?; + + theme_file.palette_paths.push(file_path.clone()); + let index = theme_file.palette_paths.len() - 1; + color_palettes.insert(hash_value, index); + + Ok(index) + } + }).inspect(log_error) + .flatten() + .collect::<Vec<_>>(); + + if let Some(palette_group_index) = palette_groups_map.get(&name) + { + *palette_group_index + } + else + { + let index = theme_file.palette_groups.len(); + let group = ThemePaletteGroup { palettes, name: name.clone() }; + theme_file.palette_groups.push(group.clone()); + palette_groups_map.insert(name.clone(), index); + + index + } + + }).collect::<Vec<_>>(); + + let sprite_sheet_destination = destination_dir + .join(&theme_config.name) + .join("sprite_sheets"); + + theme_gifs.push(processed_gif); + let gif_index = theme_gifs.len() - 1; + + let sprite_sheets = generate_sprite_sheets(&sprite_sheet_destination, + &anim.source_animation, + max_dimensions, + &theme_gifs[gif_index..=gif_index])?; + + let render_destination = destination_dir + .join(&theme_config.name) + .join("renders"); + + let palette_variations = &theme_gifs[gif_index].palette_variations; + let sprite_sheet_image_index = sprite_sheets.iter().map(|sheet| { + render_color_sprite_sheet(&render_destination, sheet, palette_variations)?; + let image_index = theme_file.image_paths.len(); + theme_file.image_paths.push(sheet.filename.clone()); + + Ok(image_index) + }).inspect(log_error).flatten().collect::<Vec<_>>(); + + let frames = sprite_sheets.iter() + .flat_map(|sprite_sheet| { + sprite_sheet.frame_mappings.iter().map(|frame| { + ThemeAnimationFrame { + image: sprite_sheet_image_index[frame.sprite_sheet_index as usize], + width: frame.image_rect.width, + height: frame.image_rect.height, + duration: frame.duration, + uv_coords: frame.uv_coords + } + }) + }) + .collect(); + + let theme_animation = ThemeAnimation + { + name: anim.source_animation.clone(), + looping: anim.looping.unwrap_or(false), + offset: anim.offset.unwrap_or([0.0, 0.0]), + frames, + palette_groups_index // Generate from palettes that get generated during gif processing + }; + + theme_file.animations.push(theme_animation); + let anim_id = theme_file.animations.len() - 1; + + for game_entity in &anim.game_entity_map.game_entities + { + for anim_state in &anim.game_entity_map.animation_states + { + theme_file.game_to_theme_animation_map.insert((game_entity.clone(), anim_state.clone()), anim_id); + } + } + } + + Ok(theme_file) +} \ No newline at end of file