Skip to content
Snippets Groups Projects
Verified Commit 902c5f4a authored by Louis's avatar Louis :fire:
Browse files

Move subcommand defs into command files

parent 453c63da
No related branches found
No related tags found
No related merge requests found
......@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
- Moved all arguments into subcommands, for a less confusing end user experience
## [0.4.0] - 2022-08-08
### Added
- Support for GlobRef definitions in a `pipeline.toml` file
......
......@@ -25,20 +25,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.58"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "autocfg"
......@@ -76,6 +65,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
......@@ -84,26 +79,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.8"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.7"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
......@@ -114,9 +107,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.2.4"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
......@@ -138,9 +131,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
......@@ -148,9 +141,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
......@@ -159,26 +152,24 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.9"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.10"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
......@@ -219,23 +210,44 @@ checksum = "e412cd91a4ec62fcc739ea50c40babe21e3de60d69f36393cce377c7c04ead5a"
[[package]]
name = "either"
version = "1.7.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "env_logger"
version = "0.9.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
dependencies = [
"atty",
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "exr"
version = "1.4.2"
......@@ -312,15 +324,15 @@ dependencies = [
[[package]]
name = "glam"
version = "0.20.5"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f"
checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
[[package]]
name = "glob"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "half"
......@@ -342,13 +354,19 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01"
[[package]]
name = "humantime"
version = "2.1.0"
......@@ -394,11 +412,33 @@ dependencies = [
"adler32",
]
[[package]]
name = "io-lifetimes"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
dependencies = [
"hermit-abi 0.3.0",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.2"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "jpeg-decoder"
......@@ -438,9 +478,15 @@ checksum = "7efd1d698db0759e6ef11a7cd44407407399a910c774dd804c64c032da7826ff"
[[package]]
name = "libc"
version = "0.2.126"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "lock_api"
......@@ -469,9 +515,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
......@@ -494,6 +540,15 @@ dependencies = [
"getrandom",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "num-integer"
version = "0.1.45"
......@@ -537,19 +592,19 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.1"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "once_cell"
version = "1.13.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "os_str_bytes"
......@@ -615,39 +670,37 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.40"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.20"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.5.3"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
......@@ -672,11 +725,25 @@ version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "rustix"
version = "0.36.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.10"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "scoped_threadpool"
......@@ -692,18 +759,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.138"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.138"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
......@@ -712,15 +779,24 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.82"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "smallvec"
version = "1.9.0"
......@@ -744,9 +820,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.98"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
......@@ -762,26 +838,20 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.31"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
......@@ -810,18 +880,43 @@ dependencies = [
[[package]]
name = "toml"
version = "0.5.9"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f35c303ea3e062b6131be4de16debe23860b9d3f2396658f13b7af1987fb473"
dependencies = [
"indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime",
]
[[package]]
name = "unicode-ident"
version = "1.0.1"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "version_check"
......@@ -925,3 +1020,69 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
......@@ -3,19 +3,28 @@ name = "crunch"
version = "0.4.0"
edition = "2021"
license = "GPL-3"
description = "Command line asset manipulation, set up a pipeline once and run it against all of your files"
authors = [
"Louis Capitanchik <louis@microhacks.co.uk>"
]
[dependencies]
anyhow = "1.0.53"
clap = { version = "3.0.14", features = ["derive"] }
clap = { version = "4.1.4", features = ["derive"] }
env_logger = "0.10.0"
log = "0.4.17"
anyhow = "1.0.69"
thiserror = "1.0.38"
deltae = "0.3.0"
env_logger = "0.9.0"
glam = "0.20.5"
glam = "0.22.0"
image = "0.24"
lab = "0.11.0"
log = "0.4.14"
num-traits = "0.2.14"
rayon = "1.5.1"
thiserror = "1.0.30"
serde = { version = "1.0.131", features = ["derive"] }
toml = "0.5.9"
serde_json = "1.0.81"
glob = "0.3.0"
num-traits = "0.2.15"
rayon = "1.6.1"
toml = "0.7.1"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.92"
glob = "0.3.1"
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use crate::commands::{
calculate_mapping, execute_pipeline, extrude, flip, palette, remap_image, rescale, rotate,
write_palette, FlipDirection, RotateDegree,
};
// use crate::commands::{calculate_mapping, execute_pipeline, extrude, flip, palette, remap_image, rescale, rotate, write_palette, FlipDirection, RotateDegree, Rotate};
use crate::commands::{Extrude, Flip, Palette, Pipeline, Remap, Rotate, Scale};
use crate::format::PaletteFormat;
use crate::{load_image, Format};
/// Image utilities for Advent
#[derive(Parser, Debug, Clone)]
/// Crunch is a set of utilities for quickly and easily processing a batch of files, either directly
/// or by defining pipelines
#[derive(Parser, Debug, Clone, Serialize, Deserialize)]
#[clap(name = "Crunch")]
#[clap(author = "Louis Capitanchik <louis@microhacks.co.uk>")]
#[clap(version = "0.4.0")]
#[clap(version = "0.5.0-beta.1")]
#[clap(about, long_about = None)]
pub struct Args {
/// The path to the spritesheet file
in_path: String,
/// The path to the output file
out_path: String,
/// Force Crunch to read the input file as a specific format
#[clap(short, long, arg_enum)]
format: Option<Format>,
#[clap(subcommand)]
command: CrunchCommand,
}
#[inline]
pub fn u32_zero() -> u32 {
0
}
#[inline]
pub fn u32_32() -> u32 {
32
}
#[inline]
pub fn f32_one() -> f32 {
1.0
}
#[derive(Debug, Clone, Subcommand, Serialize, Deserialize)]
#[serde(tag = "command", content = "params")]
pub enum CrunchCommand {
/// Take each tile in an image and expand its borders by a given amount
#[clap(version = "0.3.0")]
Extrude {
/// The amount of horizontal padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default = "u32_zero")]
space_x: u32,
/// The amount of vertical padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default = "u32_zero")]
space_y: u32,
/// The amount of horizontal padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default = "u32_zero")]
pad_x: u32,
/// The amount of vertical padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default = "u32_zero")]
pad_y: u32,
/// The size of each tile in the spritesheet. Assumed to be square tiles
#[clap(short, long, default_value_t = 32)]
#[serde(default = "u32_32")]
tile_size: u32,
/// Use nearby pixels for padding
#[clap(short, long)]
#[serde(default)]
extrude: bool,
},
/// Create a palette file containing every distinct colour from the input image
#[clap(version = "0.3.0")]
Palette {
#[clap(short, long, arg_enum, default_value_t)]
#[serde(default)]
format: PaletteFormat,
},
/// Convert the colour space of an input image to the given palette
#[clap(version = "0.3.0")]
Remap {
/// The path to the palette file containing the output colours
palette_file: String,
},
/// Make an image larger or smaller
#[clap(version = "0.3.0")]
Scale {
/// The scale factor to use; numbers between 0-1 shrink the image; numbers > 1 enlarge
#[clap(long, default_value_t = 1.0)]
#[serde(default = "f32_one")]
factor: f32,
},
/// Apply a clockwise rotation to the image
#[clap(version = "0.3.0")]
Rotate {
/// How many 90 degree steps should this image be rotated by
#[clap(long, arg_enum)]
amount: RotateDegree,
},
/// Flip an image along one or both axis
#[clap(version = "0.3.0")]
Flip {
/// The axis along which the image should be flipped
#[clap(long, arg_enum)]
direction: FlipDirection,
},
/// Execute a pipeline file to run multiple commands on one or more images
#[clap(version = "0.3.0")]
Pipeline,
pub enum Args {
#[clap(name = "rotate")]
#[serde(alias = "rotate")]
Rotate(Rotate),
#[clap(name = "extrude")]
#[serde(alias = "extrude")]
Extrude(Extrude),
#[clap(name = "palette")]
#[serde(alias = "palette")]
Palette(Palette),
#[clap(name = "scale")]
#[serde(alias = "scale")]
Scale(Scale),
#[clap(name = "flip")]
#[serde(alias = "flip")]
Flip(Flip),
#[clap(name = "remap")]
#[serde(alias = "remap")]
Remap(Remap),
#[clap(name = "remap")]
#[serde(alias = "remap")]
Pipeline(Pipeline),
}
impl Args {
pub fn run(&self) -> anyhow::Result<()> {
match &self.command {
CrunchCommand::Extrude {
extrude: ext,
pad_x,
pad_y,
space_x,
space_y,
tile_size,
} => {
let image = load_image(&self.in_path, self.format)?;
let output = extrude(image, *tile_size, *pad_x, *pad_y, *space_x, *space_y, *ext)?;
output.save(&self.out_path).map_err(anyhow::Error::from)
match &self {
Args::Rotate(rotate) => {
let image = load_image(&rotate.input, None)?;
let output = rotate.run(&image)?;
output
.save(rotate.output.as_str())
.map_err(anyhow::Error::from)
}
CrunchCommand::Palette { format } => {
let image = load_image(&self.in_path, self.format)?;
let output = palette(&image)?;
write_palette(output, *format, &self.out_path)
Args::Extrude(extrude) => {
let image = load_image(&extrude.input, None)?;
let output = extrude.run(&image)?;
output
.save(extrude.output.as_str())
.map_err(anyhow::Error::from)
}
CrunchCommand::Remap { palette_file } => {
let image_data = load_image(&self.in_path, self.format)?;
let palette_data = load_image(&palette_file, self.format)?;
let image_palette = palette(&image_data)?;
let target_palette = palette(&palette_data)?;
let mappings = calculate_mapping(&image_palette, &target_palette);
let output = remap_image(image_data, mappings)?;
output.save(&self.out_path).map_err(anyhow::Error::from)
Args::Palette(palette) => {
let image = load_image(&palette.input, None)?;
palette.run(&image)
}
CrunchCommand::Scale { factor } => {
let image = load_image(self.in_path.clone(), self.format)?;
let output = rescale(&image, *factor)?;
output.save(&self.out_path).map_err(anyhow::Error::from)
Args::Scale(scale) => {
let image = load_image(&scale.input, None)?;
let output = scale.run(&image)?;
output
.save(scale.output.as_str())
.map_err(anyhow::Error::from)
}
CrunchCommand::Rotate { amount } => {
let image = load_image(self.in_path.clone(), self.format)?;
let output = rotate(&image, *amount)?;
output.save(&self.out_path).map_err(anyhow::Error::from)
Args::Flip(flip) => {
let image = load_image(&flip.input, None)?;
let output = flip.run(&image)?;
output
.save(flip.output.as_str())
.map_err(anyhow::Error::from)
}
CrunchCommand::Flip { direction } => {
let image = load_image(self.in_path.clone(), self.format)?;
let output = flip(&image, *direction)?;
output.save(&self.out_path).map_err(anyhow::Error::from)
Args::Remap(remap) => {
let image_data = load_image(&remap.input, None)?;
let palette_data = load_image(&remap.palette, None)?;
let image_palette = Palette::extract_from(&image_data)?;
let target_palette = Palette::extract_from(&palette_data)?;
let mappings = Palette::calculate_mapping(&image_palette, &target_palette);
let output = Remap::remap_image(image_data, mappings)?;
output.save(&remap.output).map_err(anyhow::Error::from)
}
CrunchCommand::Pipeline => execute_pipeline(&self.in_path, &self.out_path),
Args::Pipeline(pipeline) => pipeline.execute(),
}
}
}
use crate::utils::{OutputFormat, RgbaOutputFormat, SpriteData};
use clap::Parser;
use image::{GenericImage, GenericImageView, Pixel, Rgba, RgbaImage};
use num_traits::cast::ToPrimitive;
use serde::{Deserialize, Serialize};
use crate::{OutputFormat, SpriteData};
#[inline(always)]
fn tile_size() -> u32 {
32
}
pub fn extrude(
image: impl GenericImage,
tile_size: u32,
pad_x: u32,
pad_y: u32,
/// Take each tile in an image and expand its borders by a given amount. Optionally fill with
/// nearby pixels instead of empty space
#[derive(Parser, Serialize, Deserialize, Clone, Debug)]
#[clap(author, version = "0.3.0")]
pub struct Extrude {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// The amount of horizontal padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default)]
space_x: u32,
/// The amount of vertical padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default)]
space_y: u32,
_extrude: bool,
) -> anyhow::Result<OutputFormat> {
log::info!(
"Image loaded with size {} x {}",
image.width(),
image.height()
);
/// The amount of horizontal padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default)]
pad_x: u32,
/// The amount of vertical padding to add between each sprite in the image
#[clap(long, default_value_t = 0)]
#[serde(default)]
pad_y: u32,
/// The size of each tile in the spritesheet. Assumed to be square tiles
#[clap(short, long, default_value_t = 32)]
#[serde(default = "tile_size")]
tile_size: u32,
/// Use nearby pixels for padding
#[clap(short, long)]
#[serde(default)]
extrude: bool,
}
impl Extrude {
pub fn run(&self, image: &impl GenericImage) -> anyhow::Result<RgbaOutputFormat> {
log::info!(
"Image loaded with size {} x {}",
image.width(),
image.height()
);
let columns = image.width() / tile_size;
let rows = image.height() / tile_size;
log::info!("Inferred sheet contains {} columns", columns);
log::info!("Inferred sheet contains {} rows", rows);
let columns = image.width() / self.tile_size;
let rows = image.height() / self.tile_size;
log::info!("Inferred sheet contains {} columns", columns);
log::info!("Inferred sheet contains {} rows", rows);
let mut views = Vec::with_capacity((columns * rows) as usize);
for x in 0..columns {
for y in 0..rows {
let img_x = x * tile_size;
let img_y = y * tile_size;
let mut views = Vec::with_capacity((columns * rows) as usize);
for x in 0..columns {
for y in 0..rows {
let img_x = x * self.tile_size;
let img_y = y * self.tile_size;
let payload = SpriteData {
data: image.view(img_x, img_y, tile_size, tile_size),
x: img_x,
y: img_y,
tx: x,
ty: y,
width: tile_size,
height: tile_size,
};
let payload = SpriteData {
data: image.view(img_x, img_y, self.tile_size, self.tile_size),
x: img_x,
y: img_y,
tx: x,
ty: y,
width: self.tile_size,
height: self.tile_size,
};
views.push(payload);
views.push(payload);
}
}
}
let new_width = (pad_x * 2 + space_x * columns) + image.width();
let new_height = (pad_y * 2 + space_y * rows) + image.height();
let new_width = (self.pad_x * 2 + self.space_x * columns) + image.width();
let new_height = (self.pad_y * 2 + self.space_y * rows) + image.height();
log::info!(
"Using new image width {} / height {}",
new_width,
new_height
);
let mut new_image = RgbaImage::new(new_width, new_height);
let (new_image_x, new_image_y, new_image_width, new_image_height) = new_image.bounds();
for x in new_image_x..new_image_width {
for y in new_image_y..new_image_height {
new_image.put_pixel(x, y, Rgba::from([0, 0, 0, 0]));
log::info!(
"Using new image width {} / height {}",
new_width,
new_height
);
let mut new_image = RgbaImage::new(new_width, new_height);
let (new_image_x, new_image_y, new_image_width, new_image_height) = new_image.bounds();
for x in new_image_x..new_image_width {
for y in new_image_y..new_image_height {
new_image.put_pixel(x, y, Rgba::from([0, 0, 0, 0]));
}
}
}
for sprite in views.iter() {
let (img_x, img_y, width, height) = sprite.data.bounds();
for x in 0..width {
for y in 0..height {
let pix = sprite.data.get_pixel(x, y).to_rgba();
let p = Rgba::from([
pix.0[0].to_u8().unwrap(),
pix.0[1].to_u8().unwrap(),
pix.0[2].to_u8().unwrap(),
pix.0[3].to_u8().unwrap(),
]);
for sprite in views.iter() {
let (img_x, img_y, width, height) = sprite.data.bounds();
for x in 0..width {
for y in 0..height {
let pix = sprite.data.get_pixel(x, y).to_rgba();
let p = Rgba::from([
pix.0[0].to_u8().unwrap(),
pix.0[1].to_u8().unwrap(),
pix.0[2].to_u8().unwrap(),
pix.0[3].to_u8().unwrap(),
]);
new_image.put_pixel(
pad_x + (sprite.tx * space_x) + img_x + x,
pad_y + (sprite.ty * space_y) + img_y + y,
p,
);
new_image.put_pixel(
self.pad_x + (sprite.tx * self.space_x) + img_x + x,
self.pad_y + (sprite.ty * self.space_y) + img_y + y,
p,
);
}
}
}
}
Ok(new_image)
Ok(new_image)
}
}
use clap::ArgEnum;
use crate::utils::TypedOutputFormat;
use clap::{Parser, ValueEnum};
use image::{imageops, GenericImage, Pixel};
use serde::{Deserialize, Serialize};
use crate::TypedOutputFormat;
#[derive(Copy, Clone, Serialize, Deserialize, ArgEnum, Debug)]
#[derive(Copy, Clone, Serialize, Deserialize, ValueEnum, Debug)]
pub enum FlipDirection {
#[serde(rename = "vertical")]
Vertical,
......@@ -14,19 +13,37 @@ pub enum FlipDirection {
Both,
}
pub fn flip<T: GenericImage>(image: &T, dir: FlipDirection) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
use FlipDirection::*;
match dir {
Vertical => Ok(imageops::flip_vertical(image)),
Horizontal => Ok(imageops::flip_horizontal(image)),
Both => {
let mut image = imageops::flip_horizontal(image);
imageops::flip_vertical_in_place(&mut image);
Ok(image)
/// Rotate an image clockwise by the given degree
#[derive(Debug, Clone, Parser, Serialize, Deserialize)]
#[clap(author, version = "0.3.0")]
pub struct Flip {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// The axis along which the image should be flipped
#[clap(long, value_enum)]
direction: FlipDirection,
}
impl Flip {
pub fn run<T: GenericImage>(&self, image: &T) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
use FlipDirection::*;
match self.direction {
Vertical => Ok(imageops::flip_vertical(image)),
Horizontal => Ok(imageops::flip_horizontal(image)),
Both => {
let mut image = imageops::flip_horizontal(image);
imageops::flip_vertical_in_place(&mut image);
Ok(image)
}
}
}
}
......@@ -6,10 +6,10 @@ mod remap;
mod rotate;
mod scale;
pub use extrude::extrude;
pub use flip::{flip, FlipDirection};
pub use palette::{calculate_mapping, palette, write_palette, ColourMapping};
pub use pipeline::execute_pipeline;
pub use remap::remap_image;
pub use rotate::{rotate, RotateDegree};
pub use scale::rescale;
pub use extrude::Extrude;
pub use flip::Flip;
pub use palette::Palette;
pub use pipeline::Pipeline;
pub use remap::Remap;
pub use rotate::Rotate;
pub use scale::Scale;
use clap::Parser;
use std::cmp::{min, Ordering};
use std::collections::hash_map::RandomState;
use std::collections::{HashMap, HashSet};
......@@ -8,63 +9,16 @@ use std::path::Path;
use deltae::{Delta, LabValue, DE2000};
use image::{GenericImage, Pixel, Rgba};
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use crate::format::PaletteFormat;
use crate::utils::{new_image, BasicRgba};
pub type Palette = Vec<BasicRgba>;
pub fn palette(image: &impl GenericImage) -> anyhow::Result<Palette> {
let mut colours = HashSet::new();
for (_, _, pixel) in image.pixels() {
let pixel = pixel.to_rgba();
colours.insert(Rgba::from([
pixel.0[0].to_u8().unwrap(),
pixel.0[1].to_u8().unwrap(),
pixel.0[2].to_u8().unwrap(),
pixel.0[3].to_u8().unwrap(),
]));
}
Ok(colours.iter().map(BasicRgba::from).collect())
}
struct HexStringValue(String);
trait HexString {
fn as_hex_string(&self) -> HexStringValue;
}
impl HexString for Rgba<u8> {
fn as_hex_string(&self) -> HexStringValue {
HexStringValue(format!(
"{:02X}{:02X}{:02X}{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3]
))
}
}
impl<T: HexString + Clone> HexString for &T {
fn as_hex_string(&self) -> HexStringValue {
(*self).clone().as_hex_string()
}
}
impl UpperHex for HexStringValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_uppercase())
}
}
impl LowerHex for HexStringValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_lowercase())
}
}
pub type PixelPalette = Vec<BasicRgba>;
pub type ColourMapping = HashMap<BasicRgba, BasicRgba>;
pub fn write_palette<T: AsRef<Path>>(
colours: Palette,
format: PaletteFormat,
outpath: T,
) -> anyhow::Result<()> {
#[allow(clippy::redundant_clone)]
let mut sorted = colours.clone();
sorted.sort_by(|pa, pb| {
pub fn sort_by_hue(palette: &mut PixelPalette) {
palette.sort_by(|pa, pb| {
let hue_a = pa.hue();
let hue_b = pb.hue();
......@@ -78,74 +32,128 @@ pub fn write_palette<T: AsRef<Path>>(
Ordering::Equal
}
});
}
match format {
PaletteFormat::Png => {
let num_colours = sorted.len();
let image_width = min(16, num_colours);
let image_height = if num_colours % 16 > 0 {
num_colours / image_width + 1
} else {
num_colours / image_width
};
let mut out_image = new_image(image_width as u32, image_height as u32);
for (idx, colour) in sorted.iter().enumerate() {
out_image.put_pixel(
(idx % image_width) as u32,
(idx / image_width) as u32,
Rgba::from(colour),
);
}
/// Create a palette file containing every distinct colour from the input image
#[derive(Parser, Clone, Serialize, Deserialize, Debug)]
#[clap(author, version = "0.3.0")]
pub struct Palette {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// The format for the palette file. PNG will output an image with colours, while TXT will
/// output a text file with one hex value per line
#[clap(short, long, value_enum, default_value_t)]
#[serde(default)]
format: PaletteFormat,
}
out_image.save(outpath)?;
}
PaletteFormat::Txt => {
let mut file = std::fs::File::create(outpath)?;
for colour in sorted.iter() {
let line = format!("#{:X}\n", colour);
file.write_all(line.as_bytes())?;
}
}
impl Palette {
pub fn run(&self, image: &impl GenericImage) -> anyhow::Result<()> {
let palette = Palette::extract_from(image)?;
self.write_palette(palette)
}
Ok(())
}
pub fn extract_from(image: &impl GenericImage) -> anyhow::Result<PixelPalette> {
let mut colours = HashSet::new();
for (_, _, pixel) in image.pixels() {
let pixel = pixel.to_rgba();
colours.insert(Rgba::from([
pixel.0[0].to_u8().unwrap(),
pixel.0[1].to_u8().unwrap(),
pixel.0[2].to_u8().unwrap(),
pixel.0[3].to_u8().unwrap(),
]));
}
pub type ColourMapping = HashMap<BasicRgba, BasicRgba>;
Ok(colours.iter().map(BasicRgba::from).collect())
}
pub fn calculate_mapping(from: &Palette, to: &Palette) -> ColourMapping {
let colour_labs = Vec::from_iter(to.iter().map(LabValue::from));
let to_palette_vectors: HashMap<usize, &BasicRgba, RandomState> =
HashMap::from_iter(to.iter().enumerate());
let mut out_map: ColourMapping = HashMap::with_capacity(from.len());
for colour in from {
let closest = to_palette_vectors
.keys()
.fold(None, |lowest, idx| match lowest {
Some(num) => {
let current = colour_labs[*idx];
let previous: LabValue = colour_labs[num];
if colour.delta(current, DE2000) < colour.delta(previous, DE2000) {
Some(*idx)
} else {
Some(num)
}
pub fn write_palette(&self, mut colours: PixelPalette) -> anyhow::Result<()> {
sort_by_hue(&mut colours);
match self.format {
PaletteFormat::Png => {
let num_colours = colours.len();
let image_width = min(16, num_colours);
let image_height = if num_colours % 16 > 0 {
num_colours / image_width + 1
} else {
num_colours / image_width
};
let mut out_image = new_image(image_width as u32, image_height as u32);
for (idx, colour) in colours.iter().enumerate() {
out_image.put_pixel(
(idx % image_width) as u32,
(idx / image_width) as u32,
Rgba::from(colour),
);
}
None => Some(*idx),
});
match closest {
Some(idx) => match to_palette_vectors.get(&idx) {
Some(col) => {
out_map.insert(*colour, **col);
out_image.save(self.output.as_str())?;
}
PaletteFormat::Txt => {
let mut file = std::fs::File::create(self.output.as_str())?;
for colour in colours.iter() {
let line = format!("#{:X}\n", colour);
file.write_all(line.as_bytes())?;
}
None => {
println!("No matching vec for {} with col {:?}", idx, &colour);
}
}
Ok(())
}
pub fn calculate_mapping(from: &PixelPalette, to: &PixelPalette) -> ColourMapping {
let colour_labs = Vec::from_iter(to.iter().map(LabValue::from));
let to_palette_vectors: HashMap<usize, &BasicRgba, RandomState> =
HashMap::from_iter(to.iter().enumerate());
let mut out_map: ColourMapping = HashMap::with_capacity(from.len());
for colour in from {
let closest = to_palette_vectors
.keys()
.fold(None, |lowest, idx| match lowest {
Some(num) => {
let current = colour_labs[*idx];
let previous: LabValue = colour_labs[num];
if colour.delta(current, DE2000) < colour.delta(previous, DE2000) {
Some(*idx)
} else {
Some(num)
}
}
None => Some(*idx),
});
match closest {
Some(idx) => match to_palette_vectors.get(&idx) {
Some(col) => {
out_map.insert(*colour, **col);
}
None => {
log::warn!("No matching set for {} with col {:?}", idx, &colour);
out_map.insert(
*colour,
BasicRgba {
r: 0,
g: 0,
b: 0,
a: 0,
},
);
}
},
None => {
log::warn!("No closest for {:?}", &colour);
out_map.insert(
*colour,
BasicRgba {
......@@ -156,21 +164,9 @@ pub fn calculate_mapping(from: &Palette, to: &Palette) -> ColourMapping {
},
);
}
},
None => {
println!("No closest for {:?}", &colour);
out_map.insert(
*colour,
BasicRgba {
r: 0,
g: 0,
b: 0,
a: 0,
},
);
}
}
}
out_map
out_map
}
}
use clap::Parser;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
......@@ -5,10 +6,11 @@ use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::cli_args::CrunchCommand;
use crate::cli_args::Args;
use crate::commands::{Palette, Remap};
use crate::format::make_paths;
use crate::load_image;
use crate::utils::normalize_path;
use crate::{commands, load_image};
#[derive(Error, Debug)]
pub enum PipelineError {
......@@ -22,7 +24,7 @@ pub enum PipelineType {
Pipeline {
input_path: String,
output_path: String,
actions: Vec<CrunchCommand>,
actions: Vec<Args>,
},
Ref {
input_path: String,
......@@ -38,7 +40,7 @@ pub enum PipelineType {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PipelineRef {
pub actions: Vec<CrunchCommand>,
pub actions: Vec<Args>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
......@@ -47,215 +49,132 @@ pub struct PipelineFile {
pub pipelines: Vec<PipelineType>,
}
pub fn execute_pipeline<IN: ToString, OUT: ToString>(
file_path: IN,
_outpath: OUT,
) -> anyhow::Result<()> {
let path = std::env::current_dir().map(|path| path.join(file_path.to_string()))?;
let path_string = format!("{}", &path.display());
/// Rotate an image clockwise by the given degree
#[derive(Debug, Clone, Parser, Serialize, Deserialize)]
#[clap(author, version = "0.3.0")]
pub struct Pipeline {
/// The path to the pipeline definition file
#[serde(default)]
pub config: String,
}
log::debug!("Trying to read from input file: {}", &path.display());
macro_rules! result {
($value: expr) => {
match $value {
Ok(val) => val,
Err(e) => {
log::error!("{}", e);
return;
}
}
};
}
if !&path_string.ends_with(".toml") && !&path_string.ends_with(".json") {
Err(PipelineError::FormatDetection)?;
}
impl Pipeline {
pub fn execute(&self) -> anyhow::Result<()> {
let path = std::env::current_dir().map(|path| path.join(&self.config))?;
let path_string = format!("{}", &path.display());
let file_contents = std::fs::read(&path)?;
log::debug!("Trying to read from input file: {}", &path.display());
log::debug!("Found correct file type and read bytes, trying to parse");
let pipeline_data: PipelineFile = if path_string.ends_with(".toml") {
toml::from_slice(&file_contents)?
} else {
serde_json::from_slice(&file_contents)?
};
if !&path_string.ends_with(".toml") && !&path_string.ends_with(".json") {
Err(PipelineError::FormatDetection)?;
}
log::debug!("Expanding pipeline file into targets");
let base_path = PathBuf::from(path.parent().unwrap());
get_targets(base_path.clone(), &pipeline_data).for_each(
|(input_path, output_path, actions)| {
match make_paths(&output_path) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to create target directory {}; {}", &output_path, e);
return;
}
}
let file_contents = std::fs::read(&path)?;
if actions.is_empty() {
match std::fs::copy(&input_path, &output_path) {
log::debug!("Found correct file type and read bytes, trying to parse");
let pipeline_data: PipelineFile = if path_string.ends_with(".toml") {
toml::from_str(String::from_utf8(file_contents)?.as_str())?
} else {
serde_json::from_slice(&file_contents)?
};
log::debug!("Expanding pipeline file into targets");
let base_path = PathBuf::from(path.parent().unwrap());
get_targets(base_path.clone(), &pipeline_data).for_each(
|(input_path, output_path, actions)| {
match make_paths(&output_path) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to copy {} to {}; {}", input_path, output_path, e);
log::error!("Failed to create target directory {}; {}", &output_path, e);
return;
}
};
return;
}
}
let mut file = match load_image(&input_path, None) {
Ok(image) => image,
Err(e) => {
log::error!("Error loading {}; {:?}", &input_path, e);
if actions.is_empty() {
match std::fs::copy(&input_path, &output_path) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to copy {} to {}; {}", input_path, output_path, e);
}
};
return;
}
};
log::debug!(
"Loaded {}, Executing {} actions",
&input_path,
actions.len()
);
let mut count = 1;
for step in actions {
match step {
CrunchCommand::Extrude {
tile_size,
space_y,
space_x,
pad_y,
pad_x,
extrude,
} => {
file = match commands::extrude(
file, tile_size, pad_x, pad_y, space_x, space_y, extrude,
) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to extrude {} at step {}; {}",
input_path,
count,
e
);
return;
}
};
let mut file = result!(load_image(&input_path, None));
log::debug!(
"Loaded {}, Executing {} actions",
&input_path,
actions.len()
);
let mut count = 1;
for step in actions {
match step {
Args::Rotate(rotate) => {
file = result!(rotate.run(&file));
}
Args::Extrude(extrude) => {
file = result!(extrude.run(&file));
}
Args::Scale(scale) => {
file = result!(scale.run(&file));
}
Args::Flip(flip) => {
file = result!(flip.run(&file));
}
Args::Remap(remap) => {
let palette = result!(load_image(&remap.palette, None));
let image_palette = result!(Palette::extract_from(&file));
let target_palette = result!(Palette::extract_from(&palette));
let mappings =
Palette::calculate_mapping(&image_palette, &target_palette);
file = result!(Remap::remap_image(file, mappings));
}
_ => {}
}
CrunchCommand::Remap { palette_file } => {
let palette_data = match load_image(join(&base_path, &palette_file), None) {
Ok(p) => p,
Err(e) => {
log::error!(
"Failed to load palette {} at step {}; {:?}",
&palette_file,
count,
e
);
return;
}
};
let image_palette = match commands::palette(&file) {
Ok(ip) => ip,
Err(e) => {
log::error!(
"Failed to extract palette from {} at step {}; {}",
input_path,
count,
e
);
return;
}
};
let target_palette = match commands::palette(&palette_data) {
Ok(tp) => tp,
Err(e) => {
log::error!(
"Failed to extract palette from {} at step {}; {}",
&palette_file,
count,
e
);
return;
}
};
let mappings = commands::calculate_mapping(&image_palette, &target_palette);
file = match commands::remap_image(file, mappings) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to remap {} at step {}; {}",
input_path,
count,
e
);
return;
}
};
}
CrunchCommand::Scale { factor } => {
file = match commands::rescale(&file, factor) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to scale {} at step {}; {}",
input_path,
count,
e
);
return;
}
};
}
CrunchCommand::Rotate { amount } => {
file = match commands::rotate(&file, amount) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to rotate {} by {:?} step(s); {}",
input_path,
amount,
e
);
return;
}
};
}
CrunchCommand::Flip { direction } => {
file = match commands::flip(&file, direction) {
Ok(f) => f,
Err(e) => {
log::error!(
"Failed to flip {} in the following direction: {:?}; {}",
input_path,
direction,
e
);
return;
}
};
}
CrunchCommand::Palette { .. } | CrunchCommand::Pipeline => continue,
count += 1;
}
count += 1;
}
let mut outer_target_path = PathBuf::from(&output_path);
outer_target_path.pop();
if let Err(e) = std::fs::create_dir(&outer_target_path) {
match e.kind() {
std::io::ErrorKind::AlreadyExists => { /* This is fine */ }
_ => log::error!(
"Failed to create containing directory {}; {}",
outer_target_path.to_string_lossy(),
e
),
let mut outer_target_path = PathBuf::from(&output_path);
outer_target_path.pop();
if let Err(e) = std::fs::create_dir(&outer_target_path) {
match e.kind() {
std::io::ErrorKind::AlreadyExists => { /* This is fine */ }
_ => log::error!(
"Failed to create containing directory {}; {}",
outer_target_path.to_string_lossy(),
e
),
}
}
}
match file.save(&output_path) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to save to {}; {}", output_path, e);
match file.save(&output_path) {
Ok(_) => {}
Err(e) => {
log::error!("Failed to save to {}; {}", output_path, e);
}
}
}
},
);
},
);
Ok(())
Ok(())
}
}
fn join<T: AsRef<Path>>(root: &Path, rest: &T) -> String {
......@@ -266,7 +185,7 @@ fn join<T: AsRef<Path>>(root: &Path, rest: &T) -> String {
fn get_targets(
base_path: PathBuf,
pipeline_data: &PipelineFile,
) -> impl ParallelIterator<Item = (String, String, Vec<CrunchCommand>)> + '_ {
) -> impl ParallelIterator<Item = (String, String, Vec<Args>)> + '_ {
pipeline_data
.pipelines
.par_iter()
......
use crate::commands::palette::ColourMapping;
use clap::Parser;
use image::{GenericImage, Pixel, Rgba};
use num_traits::ToPrimitive;
use serde::{Deserialize, Serialize};
use crate::commands::ColourMapping;
use crate::utils::{new_image, BasicRgba};
use crate::OutputFormat;
pub fn remap_image(
image: impl GenericImage,
mappings: ColourMapping,
) -> anyhow::Result<OutputFormat> {
let mut output = new_image(image.width(), image.height());
for (x, y, pixel) in image.pixels() {
let pixel = pixel.to_rgba();
if pixel.0[3].to_u8().unwrap() == 0 {
output.put_pixel(x, y, Rgba::from(BasicRgba::transparent()));
continue;
use crate::utils::{new_image, BasicRgba, OutputFormat, TypedOutputFormat};
/// Rotate an image clockwise by the given degree
#[derive(Debug, Clone, Parser, Serialize, Deserialize)]
#[clap(author, version = "0.3.0")]
pub struct Remap {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// The path to the palette file containing the output colours
#[clap(short, long)]
pub palette: String,
}
impl Remap {
pub fn remap_image(
image: impl GenericImage,
mappings: ColourMapping,
) -> anyhow::Result<OutputFormat> {
let mut output = new_image(image.width(), image.height());
for (x, y, pixel) in image.pixels() {
let pixel = pixel.to_rgba();
if pixel.0[3].to_u8().unwrap() == 0 {
output.put_pixel(x, y, Rgba::from(BasicRgba::transparent()));
continue;
}
let data = Rgba::from([
pixel.0[0].to_u8().unwrap(),
pixel.0[1].to_u8().unwrap(),
pixel.0[2].to_u8().unwrap(),
pixel.0[3].to_u8().unwrap(),
]);
let basic = BasicRgba::from(data);
match mappings.get(&basic) {
Some(mapped_data) => output.put_pixel(
x,
y,
Rgba::from(BasicRgba {
a: basic.a,
..*mapped_data
}),
),
None => output.put_pixel(x, y, Rgba::from(BasicRgba::transparent())),
};
}
let data = Rgba::from([
pixel.0[0].to_u8().unwrap(),
pixel.0[1].to_u8().unwrap(),
pixel.0[2].to_u8().unwrap(),
pixel.0[3].to_u8().unwrap(),
]);
let basic = BasicRgba::from(data);
match mappings.get(&basic) {
Some(mapped_data) => output.put_pixel(
x,
y,
Rgba::from(BasicRgba {
a: basic.a,
..*mapped_data
}),
),
None => output.put_pixel(x, y, Rgba::from(BasicRgba::transparent())),
};
Ok(output)
}
Ok(output)
}
use clap::ArgEnum;
use crate::utils::TypedOutputFormat;
use clap::{Parser, ValueEnum};
use image::{imageops, GenericImage, Pixel};
use serde::{Deserialize, Serialize};
use crate::TypedOutputFormat;
#[derive(Copy, Clone, Serialize, Deserialize, ArgEnum, Debug)]
#[derive(Copy, Clone, Serialize, Deserialize, ValueEnum, Debug)]
pub enum RotateDegree {
#[serde(rename = "90")]
One,
......@@ -14,18 +13,33 @@ pub enum RotateDegree {
Three,
}
pub fn rotate<T: GenericImage>(
image: &T,
degree: RotateDegree,
) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
use RotateDegree::*;
match degree {
One => Ok(imageops::rotate90(image)),
Two => Ok(imageops::rotate180(image)),
Three => Ok(imageops::rotate270(image)),
/// Rotate an image clockwise by the given degree
#[derive(Debug, Clone, Parser, Serialize, Deserialize)]
#[clap(author, version = "0.3.0")]
pub struct Rotate {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// How many 90 degree steps should this image be rotated by
#[clap(short, long, value_enum)]
pub amount: RotateDegree,
}
impl Rotate {
pub fn run<T: GenericImage>(&self, image: &T) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
use RotateDegree::*;
match self.amount {
One => Ok(imageops::rotate90(image)),
Two => Ok(imageops::rotate180(image)),
Three => Ok(imageops::rotate270(image)),
}
}
}
use crate::utils::TypedOutputFormat;
use clap::Parser;
use image::imageops::FilterType;
use image::{imageops, GenericImage, Pixel};
use serde::{Deserialize, Serialize};
use crate::TypedOutputFormat;
#[inline(always)]
fn one() -> f32 {
1.0
}
/// Rotate an image clockwise by the given degree
#[derive(Debug, Clone, Parser, Serialize, Deserialize)]
#[clap(author, version = "0.3.0")]
pub struct Scale {
/// The path to the spritesheet file
#[serde(default)]
pub input: String,
/// The path to write the extruded spritesheet
#[serde(default)]
pub output: String,
/// The scale factor to use; numbers between 0-1 shrink the image; numbers > 1 enlarge
#[clap(long, default_value_t = 1.0)]
#[serde(default = "one")]
factor: f32,
}
pub fn rescale<T: GenericImage>(image: &T, factor: f32) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
let nwidth = (image.width() as f32 * factor) as u32;
let nheight = (image.height() as f32 * factor) as u32;
impl Scale {
pub fn run<T: GenericImage>(&self, image: &T) -> anyhow::Result<TypedOutputFormat<T>>
where
T::Pixel: 'static,
<T::Pixel as Pixel>::Subpixel: 'static,
{
let width = (image.width() as f32 * self.factor) as u32;
let height = (image.height() as f32 * self.factor) as u32;
Ok(imageops::resize(
image,
nwidth,
nheight,
FilterType::Nearest,
))
Ok(imageops::resize(image, width, height, FilterType::Nearest))
}
}
use std::path::Path;
use clap::ArgEnum;
use clap::ValueEnum;
use image::io::Reader;
use image::{ImageError, ImageFormat, RgbaImage};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum, Debug, Serialize, Deserialize, Default,
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize, Default,
)]
pub enum PaletteFormat {
#[serde(alias = "txt")]
Txt,
#[default]
#[serde(alias = "png")]
Png,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ArgEnum, Debug)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum Format {
Png,
Jpg,
......@@ -49,11 +51,11 @@ pub enum ImageLoadingError {
ImageError(#[from] ImageError),
}
pub fn load_image<T: AsRef<Path>>(
path: T,
pub fn load_image(
path: impl ToString,
format: Option<Format>,
) -> anyhow::Result<RgbaImage, ImageLoadingError> {
let mut image = Reader::open(path)?;
let mut image = Reader::open(path.to_string())?;
let file = match format {
Some(format) => {
image.set_format(format.as_image_format());
......
......@@ -9,26 +9,6 @@ use image::{GenericImage, Rgba, SubImage};
use crate::cli_args::Args;
use crate::format::{load_image, Format};
#[derive(Clone, Copy)]
struct SpriteData<'a, T: GenericImage> {
pub data: SubImage<&'a T>,
#[allow(dead_code)]
pub x: u32,
#[allow(dead_code)]
pub y: u32,
pub tx: u32,
pub ty: u32,
#[allow(dead_code)]
pub width: u32,
#[allow(dead_code)]
pub height: u32,
}
pub type OutputFormat = image::ImageBuffer<Rgba<u8>, Vec<u8>>;
#[allow(type_alias_bounds)]
pub type TypedOutputFormat<T: image::GenericImage> =
image::ImageBuffer<T::Pixel, Vec<<T::Pixel as image::Pixel>::Subpixel>>;
fn main() -> anyhow::Result<(), anyhow::Error> {
env_logger::Builder::from_env("LOG_LEVEL").init();
let args: Args = Args::parse();
......
......@@ -3,11 +3,30 @@ use std::path::{Component, Path, PathBuf};
use deltae::LabValue;
use glam::Vec3;
use image::{GenericImageView, Rgb, Rgba, RgbaImage};
use image::{GenericImage, GenericImageView, Rgb, Rgba, RgbaImage, SubImage};
use lab::Lab;
use serde::{Deserialize, Serialize};
use crate::OutputFormat;
#[derive(Clone, Copy)]
pub struct SpriteData<'a, T: GenericImage> {
pub data: SubImage<&'a T>,
#[allow(dead_code)]
pub x: u32,
#[allow(dead_code)]
pub y: u32,
pub tx: u32,
pub ty: u32,
#[allow(dead_code)]
pub width: u32,
#[allow(dead_code)]
pub height: u32,
}
pub type OutputFormat = image::ImageBuffer<Rgba<u8>, Vec<u8>>;
#[allow(type_alias_bounds)]
pub type TypedOutputFormat<T: GenericImage> =
image::ImageBuffer<T::Pixel, Vec<<T::Pixel as image::Pixel>::Subpixel>>;
pub type RgbaOutputFormat = TypedOutputFormat<RgbaImage>;
pub fn max_f32(a: f32, b: f32) -> f32 {
if a > b {
......@@ -244,11 +263,32 @@ impl LowerHex for BasicRgba {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Pipeline {
pub input_path: String,
pub output_path: String,
pub actions: Vec<crate::cli_args::CrunchCommand>,
pub struct HexStringValue(String);
pub trait HexString {
fn as_hex_string(&self) -> HexStringValue;
}
impl HexString for Rgba<u8> {
fn as_hex_string(&self) -> HexStringValue {
HexStringValue(format!(
"{:02X}{:02X}{:02X}{:02X}",
self.0[0], self.0[1], self.0[2], self.0[3]
))
}
}
impl<T: HexString + Clone> HexString for &T {
fn as_hex_string(&self) -> HexStringValue {
(*self).clone().as_hex_string()
}
}
impl UpperHex for HexStringValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_uppercase())
}
}
impl LowerHex for HexStringValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0.to_lowercase())
}
}
pub fn normalize_path<T: AsRef<Path>>(path: T) -> PathBuf {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment