diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e4cddf..b7148c6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,11 +1,18 @@ -on: [push, pull_request] +on: + push: + branches: + - main + tags: + - "*" + pull_request: + name: CI jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable @@ -14,139 +21,70 @@ jobs: # make sure all code has been formatted with rustfmt - run: rustup component add rustfmt - name: check rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: -- --check --color always + run: cargo fmt -- --check --color always # run clippy to verify we have no warnings + - run: cargo fetch - run: rustup component add clippy - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --lib --tests -- -D warnings + run: cargo clippy --all-targets -- -D warnings test: name: Test strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + os: [ubuntu-20.04, windows-latest, macOS-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch + - run: cargo fetch - name: cargo test build - uses: actions-rs/cargo@v1 - with: - command: build - args: --tests --release --all-features + run: cargo build --tests --release --all-features - name: cargo test - uses: actions-rs/cargo@v1 - with: - command: test - args: --release --all-features + run: cargo test --release --all-features build: name: Build WASM - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable target: wasm32-wasi override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch + - run: cargo fetch - name: cargo build - uses: actions-rs/cargo@v1 - with: - command: build - args: --all --target wasm32-wasi + run: cargo build --target wasm32-wasi deny-check: name: cargo-deny - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: EmbarkStudios/cargo-deny-action@v1 publish-check: name: Publish Check - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch + - run: cargo fetch - name: copy README shell: bash run: | cp README.md lib cp README.md cli - name: cargo publish lib - uses: actions-rs/cargo@v1 - with: - command: publish - args: --dry-run --allow-dirty --manifest-path lib/Cargo.toml - - publish: - name: Publish - needs: [test, deny-check, publish-check] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - - name: copy README - shell: bash - run: | - cp README.md lib - cp README.md cli - # - name: cargo publish lib - # uses: actions-rs/cargo@v1 - # env: - # CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - # with: - # command: publish - # args: --allow-dirty --manifest-path lib/Cargo.toml - # - name: cargo fetch - # uses: actions-rs/cargo@v1 - # with: - # command: fetch - - name: cargo publish cli - uses: actions-rs/cargo@v1 - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - with: - command: publish - args: --allow-dirty --manifest-path cli/Cargo.toml + run: cargo publish --dry-run --allow-dirty --manifest-path lib/Cargo.toml release: name: Release @@ -154,9 +92,8 @@ jobs: if: startsWith(github.ref, 'refs/tags/') strategy: matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] include: - - os: ubuntu-latest + - os: ubuntu-20.04 rust: stable target: x86_64-unknown-linux-musl bin: texture-synthesis @@ -173,6 +110,11 @@ jobs: target: x86_64-apple-darwin bin: texture-synthesis features: --features=progress + - os: macOS-latest + rust: stable + target: aarch64-apple-darwin + bin: texture-synthesis + features: --features=progress runs-on: ${{ matrix.os }} steps: - name: Install stable toolchain @@ -182,21 +124,21 @@ jobs: override: true target: ${{ matrix.target }} - name: Install build deps - if: matrix.os == 'ubuntu-latest' + if: matrix.os == 'ubuntu-20.04' run: | sudo apt-get install -y musl-tools + - name: Workaround xcode shenanigans + if: matrix.target == 'aarch64-apple-darwin' + # https://github.com/actions/virtual-environments/issues/2557#issuecomment-769611326 + run: | + sudo xcode-select -s "/Applications/Xcode_12.3.app" + sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/* - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v2 - name: cargo fetch - uses: actions-rs/cargo@v1 - with: - command: fetch - args: --target ${{ matrix.target }} + run: cargo fetch --target ${{ matrix.target }} - name: Release build - uses: actions-rs/cargo@v1 - with: - command: build - args: --manifest-path cli/Cargo.toml --release --target ${{ matrix.target }} ${{ matrix.features }} + run: cargo build --manifest-path cli/Cargo.toml --release --target ${{ matrix.target }} ${{ matrix.features }} - name: Package shell: bash run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 53fc818..daa22e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,13 @@ 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 +- Updated dependencies ## [0.8.1] - 2020-11-17 ### Added - [PR#154](https://github.com/EmbarkStudios/texture-synthesis/pull/154) added progress notification is now supported when compiling for `wasm32`, thanks [@bnjbvr](https://github.com/bnjbvr)! + ## [0.8.0] - 2020-02-26 ### Added - Added the [09_sample_masks](lib/examples/09_sample_masks.rs) example diff --git a/Cargo.lock b/Cargo.lock index 976b374..4c36878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,11 +1,26 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "adler32" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -26,12 +41,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" @@ -40,9 +49,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bit-vec" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" @@ -52,9 +61,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bstr" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" dependencies = [ "lazy_static", "memchr", @@ -64,21 +73,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.4.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "bytemuck" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41aa2ec95ca3b5c54cf73c91acf06d24f4495d5f1b1c12506ae3483d646177ac" +checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" @@ -91,9 +100,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.61" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" [[package]] name = "cfg-if" @@ -123,12 +132,12 @@ dependencies = [ ] [[package]] -name = "cloudabi" -version = "0.0.3" +name = "cmake" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" dependencies = [ - "bitflags", + "cc", ] [[package]] @@ -139,9 +148,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "console" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" dependencies = [ "encode_unicode", "lazy_static", @@ -150,15 +159,8 @@ dependencies = [ "terminal_size", "unicode-width", "winapi", - "winapi-util", ] -[[package]] -name = "const_fn" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" - [[package]] name = "crc32fast" version = "1.2.1" @@ -170,16 +172,16 @@ dependencies = [ [[package]] name = "criterion" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8" +checksum = "ab327ed7354547cc2ef43cbe20ef68b988e70b4b593cbd66a2a61733123a3d23" dependencies = [ "atty", "cast", "clap", "criterion-plot", "csv", - "itertools", + "itertools 0.10.0", "lazy_static", "num-traits", "oorandom", @@ -201,7 +203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d" dependencies = [ "cast", - "itertools", + "itertools 0.9.0", ] [[package]] @@ -227,12 +229,11 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" dependencies = [ "cfg-if 1.0.0", - "const_fn", "crossbeam-utils", "lazy_static", "memoffset", @@ -241,21 +242,20 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" dependencies = [ - "autocfg 1.0.1", + "autocfg", "cfg-if 1.0.0", - "const_fn", "lazy_static", ] [[package]] name = "csv" -version = "1.1.3" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", @@ -296,40 +296,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "encoding_rs" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "filetime" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide 0.4.4", +] [[package]] name = "half" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "heck" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] [[package]] name = "image" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f0a8345b33b082aedec2f4d7d4a926b845cee184cbe78b703413066564431b" +checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5" dependencies = [ "bytemuck", "byteorder", @@ -352,9 +379,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a68371cf417889c9d7f98235b7102ea7c54fc59bcbd22f3dea785be9d27e40" +checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" dependencies = [ "console", "lazy_static", @@ -371,26 +398,32 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "jpeg-decoder" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3" -dependencies = [ - "byteorder", -] +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821" dependencies = [ "wasm-bindgen", ] @@ -403,17 +436,17 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.80" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -424,17 +457,17 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.3.3" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" -version = "0.5.6" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +checksum = "cc14fc54a812b4472b4113facc3e44d099fbc0ea2ce0551fa5c703f8edfbfd38" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -462,55 +495,54 @@ dependencies = [ ] [[package]] -name = "num" -version = "0.1.42" +name = "miniz_oxide" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ - "num-integer", - "num-iter", - "num-traits", + "adler", + "autocfg", ] [[package]] name = "num-integer" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -531,18 +563,23 @@ checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" [[package]] name = "oorandom" -version = "11.1.2" +version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "orbclient" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b18f57ab94fbd058e30aa57f712ec423c0bb7403f8493a6c58eef0c36d9402" +checksum = "0c976c5018e7f1db4359616d8b31ef8ae7d9649b11803c0b38fff67fd2999fc8" dependencies = [ + "libc", + "raw-window-handle", "redox_syscall", "sdl2", + "sdl2-sys", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -559,34 +596,44 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "plotters" -version = "0.2.15" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb" +checksum = "45ca0ae5f169d0917a7c7f5a9c1a3d3d9598f18f529dd2b8373ed988efea307a" dependencies = [ - "js-sys", "num-traits", + "plotters-backend", + "plotters-svg", "wasm-bindgen", "web-sys", ] +[[package]] +name = "plotters-backend" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" + +[[package]] +name = "plotters-svg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" -version = "0.16.7" +version = "0.16.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", "deflate", - "miniz_oxide", + "miniz_oxide 0.3.7", ] -[[package]] -name = "ppv-lite86" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -622,162 +669,35 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg 0.1.2", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand" -version = "0.7.3" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_core", ] [[package]] name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" [[package]] name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "7de198537002b913568a3847e53535ace266f93526caf5c360ec41d72c5787f0" dependencies = [ - "rand_core 0.3.1", + "rand_core", ] [[package]] @@ -795,7 +715,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.0.1", + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -815,26 +735,22 @@ dependencies = [ ] [[package]] -name = "rdrand" -version = "0.4.0" +name = "redox_syscall" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" dependencies = [ - "rand_core 0.3.1", + "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "regex" -version = "1.4.1" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" +checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -849,9 +765,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.20" +version = "0.6.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" +checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" [[package]] name = "rstar" @@ -901,26 +817,30 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sdl2" -version = "0.32.2" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d051a07231e303f5f719da78cb6f7394f6d5b54f733aef5b0b447804a83edd7b" +checksum = "fcbb85f4211627a7291c83434d6bbfa723e28dcaa53c7606087e3c61929e4b9c" dependencies = [ "bitflags", "lazy_static", "libc", - "num", - "rand 0.6.5", + "raw-window-handle", "sdl2-sys", ] [[package]] name = "sdl2-sys" -version = "0.32.6" +version = "0.34.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e71125077d297d57e4c1acfe8981b5bdfbf5a20e7b589abfdcb33bf1127f86" +checksum = "28d81feded049b9c14eceb4a4f6d596a98cebbd59abdba949c5552a015466d33" dependencies = [ "cfg-if 0.1.10", + "cmake", + "flate2", "libc", + "tar", + "unidiff", + "version-compare", ] [[package]] @@ -940,9 +860,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" [[package]] name = "serde_cbor" @@ -956,9 +876,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" dependencies = [ "proc-macro2", "quote", @@ -967,9 +887,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" dependencies = [ "itoa", "ryu", @@ -984,9 +904,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126d630294ec449fae0b16f964e35bf3c74f940da9dca17ee9b905f7b3112eb8" +checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" dependencies = [ "clap", "lazy_static", @@ -995,9 +915,9 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e51c492f9e23a220534971ff5afc14037289de430e3c83f9daf6a1b6ae91e8" +checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" dependencies = [ "heck", "proc-macro-error", @@ -1008,20 +928,31 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.48" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "tar" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0bcfbd6a598361fda270d82469fff3d65089dc33e175c9a131f7b4cd395f228" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "terminal_size" -version = "0.1.13" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13" +checksum = "86ca8ced750734db02076f44132d802af0b33b09942331f4459dde8636fd2406" dependencies = [ "libc", "winapi", @@ -1036,8 +967,8 @@ dependencies = [ "image", "img_hash", "num_cpus", - "rand 0.7.3", - "rand_pcg 0.2.1", + "rand", + "rand_pcg", "rstar", ] @@ -1074,9 +1005,9 @@ dependencies = [ [[package]] name = "tinytemplate" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", @@ -1084,9 +1015,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-width" @@ -1100,23 +1031,40 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unidiff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a62719acf1933bfdbeb73a657ecd9ecece70b405125267dd549e2e2edc232c" +dependencies = [ + "encoding_rs", + "lazy_static", + "regex", +] + [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "walkdir" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", "winapi", @@ -1131,19 +1079,19 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.68" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.68" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3" dependencies = [ "bumpalo", "lazy_static", @@ -1156,9 +1104,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.68" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1166,9 +1114,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.68" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d" dependencies = [ "proc-macro2", "quote", @@ -1179,15 +1127,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.68" +version = "0.2.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" +checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa" [[package]] name = "web-sys" -version = "0.3.45" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310" dependencies = [ "js-sys", "wasm-bindgen", @@ -1235,3 +1183,12 @@ dependencies = [ "maybe-uninit", "pkg-config", ] + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] diff --git a/README.md b/README.md index 4557206..f5ed0c0 100644 --- a/README.md +++ b/README.md @@ -377,7 +377,40 @@ imgs/multiexample/4.jpg imgs/multiexample/3.jpg` Also note that the normal parameters that are used with `generate` don't apply to the `repeat` subcommand and will be ignored. -### 9. Combining texture synthesis 'verbs' +### 9. Sample masks + +Sample masks allow you to specify how an example image is sampled during generation. + +#### API - [09_sample_masks](lib/examples/09_sample_masks.rs) + +```rust +use texture_synthesis as ts; + +fn main() -> Result<(), ts::Error> { + let session = ts::Session::builder() + .add_example( + ts::Example::builder(&"imgs/4.png").set_sample_method(ts::SampleMethod::Ignore), + ) + .add_example(ts::Example::builder(&"imgs/5.png").set_sample_method(ts::SampleMethod::All)) + .seed(211) + .output_size(ts::Dims::square(200)) + .build()?; + + // generate an image + let generated = session.run(None); + + // save the image to the disk + generated.save("out/09.png") +} +``` + +`cargo run --release -- --seed 211 --out-size 200 --sample-masks IGNORE ALL --out 09_sample_masks.png generate imgs/4.png imgs/5.png` + +You should get the following result with the images provided in this repo: + + + +### 10. Combining texture synthesis 'verbs' We can also combine multiple modes together. For example, multi-example guided synthesis: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7989332..0a9d98e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,7 +22,7 @@ path = "src/main.rs" bench = false [dependencies] -structopt = "0.3.16" +structopt = "0.3" texture-synthesis = { version = "0.8.0", path = "../lib" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -30,8 +30,8 @@ texture-synthesis = { version = "0.8.0", path = "../lib" } # because it only ever operates on stdout, even though we only ever print # to stderr. This is also why indicatif colors don't work if you pipe # the image output :( -atty = "0.2.13" -indicatif = "0.14.0" +atty = "0.2" +indicatif = "0.15" minifb = { version = "0.16.0", default-features = false, features = ["x11"], optional = true } [features] diff --git a/cli/src/main.rs b/cli/src/main.rs index 79c9f40..b820cce 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -418,7 +418,7 @@ fn real_main() -> Result<(), Error> { let session = sb.build()?; #[cfg(not(target_arch = "wasm32"))] - let progress: Option> = + let progress: Option> = if !args.tweaks.no_progress { let progress = progress_window::ProgressWindow::new(); diff --git a/cli/src/progress_window.rs b/cli/src/progress_window.rs index d764981..781063a 100644 --- a/cli/src/progress_window.rs +++ b/cli/src/progress_window.rs @@ -75,8 +75,8 @@ impl Drop for ProgressWindow { } } -impl texture_synthesis::GeneratorProgress for ProgressWindow { - fn update(&mut self, update: texture_synthesis::ProgressUpdate<'_>) { +impl texture_synthesis::session::GeneratorProgress for ProgressWindow { + fn update(&mut self, update: texture_synthesis::session::ProgressUpdate<'_>) { if update.total.total != self.total_len { self.total_len = update.total.total; self.total_pb.set_length(self.total_len as u64); diff --git a/deny.toml b/deny.toml index 79b9dc5..f88ab86 100644 --- a/deny.toml +++ b/deny.toml @@ -25,6 +25,10 @@ deny = [ # term is not fully maintained, and termcolor is replacing it { name = "term" }, ] +skip = [ + # criterion depends on both 0.10 and 0.9 :p + { name = "itertools", version = "=0.9.0" }, +] [licenses] unlicensed = "deny" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 14e3303..111d0ce 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -21,12 +21,12 @@ exclude = ["/imgs"] [dependencies] num_cpus = "1.13" -rand = { version = "0.7", default-features = false } # avoid bringing in OS random gen that we don't use -rand_pcg = "0.2" +rand = { version = "0.8", default-features = false } # avoid bringing in OS random gen that we don't use +rand_pcg = "0.3" rstar = "0.7" [dependencies.image] -version = "0.23" +version = "=0.23.12" # Pinned to this version as there seems to be a change that affects output default-features = false features = ["jpeg", "png", "bmp"] diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 22eaedc..ce6ca45 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -49,9 +49,6 @@ )] // END - Embark standard lints v0.3 -// crate-specific exceptions: -#![allow(unsafe_code, clippy::exit)] - //! `texture-synthesis` is a light API for Multiresolution Stochastic Texture Synthesis, //! a non-parametric example-based algorithm for image generation. //! @@ -101,13 +98,15 @@ mod utils; use utils::*; mod ms; use ms::*; -use std::path::Path; +pub mod session; mod unsync; pub use image; -pub use utils::{load_dynamic_image, ChannelMask, ImageSource}; +use std::path::Path; pub use errors::Error; +pub use session::{Session, SessionBuilder}; +pub use utils::{load_dynamic_image, ChannelMask, ImageSource}; /// Simple dimensions struct #[derive(Copy, Clone)] @@ -490,16 +489,6 @@ impl<'a> ExampleBuilder<'a> { } } -impl<'a> Into> for ExampleBuilder<'a> { - fn into(self) -> Example<'a> { - Example { - img: self.img, - guide: self.guide, - sample_method: self.sample_method, - } - } -} - /// An example to be used in texture generation pub struct Example<'a> { img: ImageSource<'a>, @@ -587,6 +576,16 @@ impl<'a> Example<'a> { } } +impl<'a> From> for Example<'a> { + fn from(eb: ExampleBuilder<'a>) -> Self { + Self { + img: eb.img, + guide: eb.guide, + sample_method: eb.sample_method, + } + } +} + impl<'a, IS> From for Example<'a> where IS: Into>, @@ -607,569 +606,12 @@ struct InpaintMask<'a> { dims: Dims, } -/// Builds a session by setting parameters and adding input images, calling -/// `build` will check all of the provided inputs to verify that texture -/// synthesis will provide valid output -#[derive(Default)] -pub struct SessionBuilder<'a> { - examples: Vec>, - target_guide: Option>, - inpaint_mask: Option>, - params: Parameters, -} - -impl<'a> SessionBuilder<'a> { - /// Creates a new `SessionBuilder`, can also be created via - /// `Session::builder()` - pub fn new() -> Self { - Self::default() - } - - /// Adds an `Example` from which a generator will synthesize a new image. - /// - /// See [`examples/01_single_example_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/01_single_example_synthesis.rs) - /// - /// # Examples - /// - /// ```no_run - /// let tex_synth = texture_synthesis::Session::builder() - /// .add_example(&"imgs/1.jpg") - /// .build().expect("failed to build session"); - /// ``` - pub fn add_example>>(mut self, example: E) -> Self { - self.examples.push(example.into()); - self - } - - /// Adds Examples from which a generator will synthesize a new image. - /// - /// See [`examples/02_multi_example_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/02_multi_example_synthesis.rs) - /// - /// # Examples - /// - /// ```no_run - /// let tex_synth = texture_synthesis::Session::builder() - /// .add_examples(&[&"imgs/1.jpg", &"imgs/2.jpg"]) - /// .build().expect("failed to build session"); - /// ``` - pub fn add_examples>, I: IntoIterator>( - mut self, - examples: I, - ) -> Self { - self.examples.extend(examples.into_iter().map(|e| e.into())); - self - } - - /// Inpaints an example. Due to how inpainting works, a size must also be - /// provided, as all examples, as well as the inpaint mask, must be the same - /// size as each other, as well as the final output image. Using - /// `resize_input` or `output_size` is ignored if this method is called. - /// - /// To prevent sampling from the example, you can specify - /// `SamplingMethod::Ignore` with `Example::set_sample_method`. - /// - /// See [`examples/05_inpaint`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/05_inpaint.rs) - /// - /// # Examples - /// - /// ```no_run - /// let tex_synth = texture_synthesis::Session::builder() - /// .add_examples(&[&"imgs/1.jpg", &"imgs/3.jpg"]) - /// .inpaint_example( - /// &"masks/inpaint.jpg", - /// // This will prevent sampling from the imgs/2.jpg, note that - /// // we *MUST* provide at least one example to source from! - /// texture_synthesis::Example::builder(&"imgs/2.jpg") - /// .set_sample_method(texture_synthesis::SampleMethod::Ignore), - /// texture_synthesis::Dims::square(400) - /// ) - /// .build().expect("failed to build session"); - /// ``` - pub fn inpaint_example>, E: Into>>( - mut self, - inpaint_mask: I, - example: E, - size: Dims, - ) -> Self { - self.inpaint_mask = Some(InpaintMask { - src: MaskOrImg::ImageSource(inpaint_mask.into()), - example_index: self.examples.len(), - dims: size, - }); - self.examples.push(example.into()); - self - } - - /// Inpaints an example, using a specific channel in the example image as - /// the inpaint mask - /// - /// # Examples - /// - /// ```no_run - /// let tex_synth = texture_synthesis::Session::builder() - /// .inpaint_example_channel( - /// // Let's use inpaint the alpha channel - /// texture_synthesis::ChannelMask::A, - /// &"imgs/bricks.png", - /// texture_synthesis::Dims::square(400) - /// ) - /// .build().expect("failed to build session"); - /// ``` - pub fn inpaint_example_channel>>( - mut self, - mask: utils::ChannelMask, - example: E, - size: Dims, - ) -> Self { - self.inpaint_mask = Some(InpaintMask { - src: MaskOrImg::Mask(mask), - example_index: self.examples.len(), - dims: size, - }); - self.examples.push(example.into()); - self - } - - /// Loads a target guide map. - /// - /// If no `Example` guide maps are provided, this will produce a style - /// transfer effect, where the Examples are styles and the target guide is - /// content. - /// - /// See [`examples/03_guided_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/03_guided_synthesis.rs), - /// or [`examples/04_style_transfer`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/04_style_transfer.rs), - pub fn load_target_guide>>(mut self, guide: I) -> Self { - self.target_guide = Some(guide.into()); - self - } - - /// Overwrite incoming images sizes - pub fn resize_input(mut self, dims: Dims) -> Self { - self.params.resize_input = Some(dims); - self - } - - /// Changes pseudo-deterministic seed. - /// - /// Global structures will stay same, if the same seed is provided, but - /// smaller details may change due to undeterministic nature of - /// multithreading. - pub fn seed(mut self, value: u64) -> Self { - self.params.seed = value; - self - } - - /// Makes the generator output tiling image. - /// - /// Default: false. - pub fn tiling_mode(mut self, is_tiling: bool) -> Self { - self.params.tiling_mode = is_tiling; - self - } - - /// How many neighboring pixels each pixel is aware of during generation. - /// - /// A larger number means more global structures are captured. - /// - /// Default: 50 - pub fn nearest_neighbors(mut self, count: u32) -> Self { - self.params.nearest_neighbors = count; - self - } - - /// The number of random locations that will be considered during a pixel - /// resolution apart from its immediate neighbors. - /// - /// If unsure, keep same as nearest neighbors. - /// - /// Default: 50 - pub fn random_sample_locations(mut self, count: u64) -> Self { - self.params.random_sample_locations = count; - self - } - - /// Forces the first `n` pixels to be randomly resolved, and prevents them - /// from being overwritten. - /// - /// Can be an enforcing factor of remixing multiple images together. - pub fn random_init(mut self, count: u64) -> Self { - self.params.random_resolve = Some(count); - self - } - - /// The distribution dispersion used for picking best candidate (controls - /// the distribution 'tail flatness'). - /// - /// Values close to 0.0 will produce 'harsh' borders between generated - /// 'chunks'. Values closer to 1.0 will produce a smoother gradient on those - /// borders. - /// - /// For futher reading, check out P.Harrison's "Image Texture Tools". - /// - /// Default: 1.0 - pub fn cauchy_dispersion(mut self, value: f32) -> Self { - self.params.cauchy_dispersion = value; - self - } - - /// Controls the trade-off between guide and example maps. - /// - /// If doing style transfer, set to about 0.8-0.6 to allow for more global - /// structures of the style. - /// - /// If you'd like the guide maps to be considered through all generation - /// stages, set to 1.0, which will prevent guide maps weight "decay" during - /// the score calculation. - /// - /// Default: 0.8 - pub fn guide_alpha(mut self, value: f32) -> Self { - self.params.guide_alpha = value; - self - } - - /// The percentage of pixels to be backtracked during each `p_stage`. - /// Range (0,1). - /// - /// Default: 0.5 - pub fn backtrack_percent(mut self, value: f32) -> Self { - self.params.backtrack_percent = value; - self - } - - /// Controls the number of backtracking stages. - /// - /// Backtracking prevents 'garbage' generation. Right now, the depth of the - /// image pyramid for multiresolution synthesis depends on this parameter as - /// well. - /// - /// Default: 5 - pub fn backtrack_stages(mut self, stages: u32) -> Self { - self.params.backtrack_stages = stages; - self - } - - /// Specify size of the generated image. - /// - /// Default: 500x500 - pub fn output_size(mut self, dims: Dims) -> Self { - self.params.output_size = dims; - self - } - - /// Controls the maximum number of threads that will be spawned at any one - /// time in parallel. - /// - /// This number is allowed to exceed the number of logical cores on the - /// system, but it should generally be kept at or below that number. - /// - /// Setting this number to `1` will result in completely deterministic - /// image generation, meaning that redoing generation with the same inputs - /// will always give you the same outputs. - /// - /// Default: The number of logical cores on this system. - pub fn max_thread_count(mut self, count: usize) -> Self { - self.params.max_thread_count = Some(count); - self - } - - /// Creates a `Session`, or returns an error if invalid parameters or input - /// images were specified. - pub fn build(mut self) -> Result { - self.check_parameters_validity()?; - self.check_images_validity()?; - - struct InpaintExample { - inpaint_mask: image::RgbaImage, - color_map: image::RgbaImage, - example_index: usize, - } - - let (inpaint, out_size, in_size) = match self.inpaint_mask { - Some(inpaint_mask) => { - let dims = inpaint_mask.dims; - let inpaint_img = match inpaint_mask.src { - MaskOrImg::ImageSource(img) => load_image(img, Some(dims))?, - MaskOrImg::Mask(mask) => { - let example_img = &mut self.examples[inpaint_mask.example_index].img; - - let dynamic_img = utils::load_dynamic_image(example_img.clone())?; - let inpaint_src = ImageSource::Image(dynamic_img.clone()); - - // Replace the example image source so we don't load it twice - *example_img = ImageSource::Image(dynamic_img); - - let inpaint_mask = load_image(inpaint_src, Some(dims))?; - - utils::apply_mask(inpaint_mask, mask) - } - }; - - let color_map = load_image( - self.examples[inpaint_mask.example_index].img.clone(), - Some(dims), - )?; - - ( - Some(InpaintExample { - inpaint_mask: inpaint_img, - color_map, - example_index: inpaint_mask.example_index, - }), - dims, - Some(dims), - ) - } - None => (None, self.params.output_size, self.params.resize_input), - }; - - let target_guide = match self.target_guide { - Some(tg) => { - let tg_img = load_image(tg, Some(out_size))?; - - let num_guides = self.examples.iter().filter(|ex| ex.guide.is_some()).count(); - let tg_img = if num_guides == 0 { - transform_to_guide_map(tg_img, None, 2.0) - } else { - tg_img - }; - - Some(ImagePyramid::new( - tg_img, - Some(self.params.backtrack_stages as u32), - )) - } - None => None, - }; - - let example_len = self.examples.len(); - - let mut examples = Vec::with_capacity(example_len); - let mut guides = if target_guide.is_some() { - Vec::with_capacity(example_len) - } else { - Vec::new() - }; - let mut methods = Vec::with_capacity(example_len); - - for example in self.examples { - let resolved = example.resolve(self.params.backtrack_stages, in_size, &target_guide)?; - - examples.push(resolved.image); - - if let Some(guide) = resolved.guide { - guides.push(guide); - } - - methods.push(resolved.method); - } - - // Initialize generator based on availability of an inpaint_mask. - let generator = match inpaint { - None => Generator::new(out_size), - Some(inpaint) => Generator::new_from_inpaint( - out_size, - inpaint.inpaint_mask, - inpaint.color_map, - inpaint.example_index, - ), - }; - - let session = Session { - examples, - guides: target_guide.map(|tg| GuidesPyramidStruct { - target_guide: tg, - example_guides: guides, - }), - sampling_methods: methods, - params: self.params, - generator, - }; - - Ok(session) - } - - fn check_parameters_validity(&self) -> Result<(), Error> { - if self.params.cauchy_dispersion < 0.0 || self.params.cauchy_dispersion > 1.0 { - return Err(Error::InvalidRange(errors::InvalidRange { - min: 0.0, - max: 1.0, - value: self.params.cauchy_dispersion, - name: "cauchy-dispersion", - })); - } - - if self.params.backtrack_percent < 0.0 || self.params.backtrack_percent > 1.0 { - return Err(Error::InvalidRange(errors::InvalidRange { - min: 0.0, - max: 1.0, - value: self.params.backtrack_percent, - name: "backtrack-percent", - })); - } - - if self.params.guide_alpha < 0.0 || self.params.guide_alpha > 1.0 { - return Err(Error::InvalidRange(errors::InvalidRange { - min: 0.0, - max: 1.0, - value: self.params.guide_alpha, - name: "guide-alpha", - })); - } - - if let Some(max_count) = self.params.max_thread_count { - if max_count == 0 { - return Err(Error::InvalidRange(errors::InvalidRange { - min: 1.0, - max: 1024.0, - value: max_count as f32, - name: "max-thread-count", - })); - } - } - - if self.params.random_sample_locations == 0 { - return Err(Error::InvalidRange(errors::InvalidRange { - min: 1.0, - max: 1024.0, - value: self.params.random_sample_locations as f32, - name: "m-rand", - })); - } - - Ok(()) - } - - fn check_images_validity(&self) -> Result<(), Error> { - // We must have at least one example image to source pixels from - let input_count = self - .examples - .iter() - .filter(|ex| !ex.sample_method.is_ignore()) - .count(); - - if input_count == 0 { - return Err(Error::NoExamples); - } - - // If we have more than one example guide, then *every* example - // needs a guide - let num_guides = self.examples.iter().filter(|ex| ex.guide.is_some()).count(); - if num_guides != 0 && self.examples.len() != num_guides { - return Err(Error::ExampleGuideMismatch( - self.examples.len() as u32, - num_guides as u32, - )); - } - - Ok(()) - } -} - struct ResolvedExample { image: ImagePyramid, guide: Option, method: SamplingMethod, } -/// Texture synthesis session. -/// -/// Calling `run()` will generate a new image and return it, consuming the -/// session in the process. You can provide a `GeneratorProgress` implementation -/// to periodically get updates with the currently generated image and the -/// number of pixels that have been resolved both in the current stage and -/// globally. -/// -/// # Example -/// ```no_run -/// let tex_synth = texture_synthesis::Session::builder() -/// .seed(10) -/// .tiling_mode(true) -/// .add_example(&"imgs/1.jpg") -/// .build().expect("failed to build session"); -/// -/// let generated_img = tex_synth.run(None); -/// generated_img.save("my_generated_img.jpg").expect("failed to save image"); -/// ``` -pub struct Session { - examples: Vec, - guides: Option, - sampling_methods: Vec, - generator: Generator, - params: Parameters, -} - -impl Session { - /// Creates a new session with default parameters. - pub fn builder<'a>() -> SessionBuilder<'a> { - SessionBuilder::default() - } - - /// Runs the generator and outputs a generated image. - pub fn run(mut self, progress: Option>) -> GeneratedImage { - // random resolve - // TODO: Instead of consuming the generator, we could instead make the - // seed and random_resolve parameters, so that you could rerun the - // generator with the same inputs - if let Some(count) = self.params.random_resolve { - let lvl = self.examples[0].pyramid.len(); - let imgs: Vec<_> = self - .examples - .iter() - .map(|a| ImageBuffer::from(&a.pyramid[lvl - 1])) //take the blurriest image - .collect(); - - self.generator - .resolve_random_batch(count as usize, &imgs, self.params.seed); - } - - // run generator - self.generator.resolve( - &self.params.to_generator_params(), - &self.examples, - progress, - &self.guides, - &self.sampling_methods, - ); - - GeneratedImage { - inner: self.generator, - } - } -} - -/// Helper struct for passing progress information to external callers -pub struct ProgressStat { - /// The current amount of work that has been done - pub current: usize, - /// The total amount of work to do - pub total: usize, -} - -/// The current state of the image generator -pub struct ProgressUpdate<'a> { - /// The currenty resolved image - pub image: &'a image::RgbaImage, - /// The total progress for the final image - pub total: ProgressStat, - /// The progress for the current stage - pub stage: ProgressStat, -} - -/// Allows the generator to update external callers with the current -/// progress of the image synthesis -pub trait GeneratorProgress { - fn update(&mut self, info: ProgressUpdate<'_>); -} - -impl GeneratorProgress for G -where - G: FnMut(ProgressUpdate<'_>) + Send, -{ - fn update(&mut self, info: ProgressUpdate<'_>) { - self(info) - } -} - #[cfg(test)] mod test { #[test] diff --git a/lib/src/ms.rs b/lib/src/ms.rs index 95c1afb..a171cc0 100644 --- a/lib/src/ms.rs +++ b/lib/src/ms.rs @@ -6,8 +6,10 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Mutex, RwLock}; use crate::{ - img_pyramid::*, unsync::*, CoordinateTransform, Dims, GeneratorProgress, ProgressStat, - SamplingMethod, + img_pyramid::*, + session::{GeneratorProgress, ProgressStat}, + unsync::*, + CoordinateTransform, Dims, SamplingMethod, }; const TILING_BOUNDARY_PERCENTAGE: f32 = 0.05; @@ -377,7 +379,7 @@ impl Generator { if unresolved.len() == 0 { None //return fail } else { - let rand_index = Pcg32::seed_from_u64(seed).gen_range(0, unresolved.len()); + let rand_index = Pcg32::seed_from_u64(seed).gen_range(0..unresolved.len()); Some(unresolved.swap_remove(rand_index)) //return success } } @@ -445,11 +447,11 @@ impl Generator { example_maps: &[ImageBuffer<'_>], seed: u64, ) { - let rand_map: u32 = Pcg32::seed_from_u64(seed).gen_range(0, example_maps.len()) as u32; + let rand_map: u32 = Pcg32::seed_from_u64(seed).gen_range(0..example_maps.len()) as u32; let rand_x: u32 = - Pcg32::seed_from_u64(seed).gen_range(0, example_maps[rand_map as usize].width as u32); + Pcg32::seed_from_u64(seed).gen_range(0..example_maps[rand_map as usize].width as u32); let rand_y: u32 = - Pcg32::seed_from_u64(seed).gen_range(0, example_maps[rand_map as usize].height as u32); + Pcg32::seed_from_u64(seed).gen_range(0..example_maps[rand_map as usize].height as u32); self.update( my_resolved_list, @@ -543,7 +545,7 @@ impl Generator { let mut rng = Pcg32::seed_from_u64(m_seed); //random candidates for _ in 0..m_rand { - let rand_map = (rng.gen_range(0, example_maps.len())) as u32; + let rand_map = (rng.gen_range(0..example_maps.len())) as u32; let dims = example_maps[rand_map as usize].dimensions(); let dims = Dims { width: dims.0, @@ -554,8 +556,8 @@ impl Generator { let mut candidate_coord; //generate a random valid candidate loop { - rand_x = rng.gen_range(0, dims.width) as i32; - rand_y = rng.gen_range(0, dims.height) as i32; + rand_x = rng.gen_range(0..dims.width) as i32; + rand_y = rng.gen_range(0..dims.height) as i32; candidate_coord = SignedCoord2D::from(rand_x, rand_y); if check_coord_validity( candidate_coord, @@ -607,18 +609,18 @@ impl Generator { let coord = CoordFlat(i as u32).to_2d(self.output_size); //get random color based on id let color: image::Rgba = image::Rgba([ - Pcg32::seed_from_u64(u64::from(patch_id.0)).gen_range(0, 255), - Pcg32::seed_from_u64(u64::from((patch_id.0) * 5 + 21)).gen_range(0, 255), - Pcg32::seed_from_u64(u64::from((patch_id.0) / 4 + 12)).gen_range(0, 255), + Pcg32::seed_from_u64(u64::from(patch_id.0)).gen_range(0..255), + Pcg32::seed_from_u64(u64::from((patch_id.0) * 5 + 21)).gen_range(0..255), + Pcg32::seed_from_u64(u64::from((patch_id.0) / 4 + 12)).gen_range(0..255), 255, ]); //write image patch_id_map.put_pixel(coord.x, coord.y, color); //get random color based on id let color: image::Rgba = image::Rgba([ - Pcg32::seed_from_u64(u64::from(map_id.0) * 200).gen_range(0, 255), - Pcg32::seed_from_u64(u64::from((map_id.0) * 5 + 341)).gen_range(0, 255), - Pcg32::seed_from_u64(u64::from((map_id.0) * 1200 - 35412)).gen_range(0, 255), + Pcg32::seed_from_u64(u64::from(map_id.0) * 200).gen_range(0..255), + Pcg32::seed_from_u64(u64::from((map_id.0) * 5 + 341)).gen_range(0..255), + Pcg32::seed_from_u64(u64::from((map_id.0) * 1200 - 35412)).gen_range(0..255), 255, ]); map_id_map.put_pixel(coord.x, coord.y, color); @@ -1079,7 +1081,7 @@ impl<'a> ProgressNotifier<'a> { .round() as u32; if pcnt != self.pcnt { - self.progress.update(crate::ProgressUpdate { + self.progress.update(crate::session::ProgressUpdate { image: color_map.as_ref(), total: ProgressStat { total: self.overall_total, diff --git a/lib/src/session.rs b/lib/src/session.rs new file mode 100644 index 0000000..3656934 --- /dev/null +++ b/lib/src/session.rs @@ -0,0 +1,558 @@ +use crate::*; + +/// Texture synthesis session. +/// +/// Calling `run()` will generate a new image and return it, consuming the +/// session in the process. You can provide a `GeneratorProgress` implementation +/// to periodically get updates with the currently generated image and the +/// number of pixels that have been resolved both in the current stage and +/// globally. +/// +/// # Example +/// ```no_run +/// let tex_synth = texture_synthesis::Session::builder() +/// .seed(10) +/// .tiling_mode(true) +/// .add_example(&"imgs/1.jpg") +/// .build().expect("failed to build session"); +/// +/// let generated_img = tex_synth.run(None); +/// generated_img.save("my_generated_img.jpg").expect("failed to save image"); +/// ``` +pub struct Session { + examples: Vec, + guides: Option, + sampling_methods: Vec, + generator: Generator, + params: Parameters, +} + +impl Session { + /// Creates a new session with default parameters. + pub fn builder<'a>() -> SessionBuilder<'a> { + SessionBuilder::default() + } + + /// Runs the generator and outputs a generated image. + pub fn run(mut self, progress: Option>) -> GeneratedImage { + // random resolve + // TODO: Instead of consuming the generator, we could instead make the + // seed and random_resolve parameters, so that you could rerun the + // generator with the same inputs + if let Some(count) = self.params.random_resolve { + let lvl = self.examples[0].pyramid.len(); + let imgs: Vec<_> = self + .examples + .iter() + .map(|a| ImageBuffer::from(&a.pyramid[lvl - 1])) //take the blurriest image + .collect(); + + self.generator + .resolve_random_batch(count as usize, &imgs, self.params.seed); + } + + // run generator + self.generator.resolve( + &self.params.to_generator_params(), + &self.examples, + progress, + &self.guides, + &self.sampling_methods, + ); + + GeneratedImage { + inner: self.generator, + } + } +} + +/// Builds a session by setting parameters and adding input images, calling +/// `build` will check all of the provided inputs to verify that texture +/// synthesis will provide valid output +#[derive(Default)] +pub struct SessionBuilder<'a> { + examples: Vec>, + target_guide: Option>, + inpaint_mask: Option>, + params: Parameters, +} + +impl<'a> SessionBuilder<'a> { + /// Creates a new `SessionBuilder`, can also be created via + /// `Session::builder()` + pub fn new() -> Self { + Self::default() + } + + /// Adds an `Example` from which a generator will synthesize a new image. + /// + /// See [`examples/01_single_example_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/01_single_example_synthesis.rs) + /// + /// # Examples + /// + /// ```no_run + /// let tex_synth = texture_synthesis::Session::builder() + /// .add_example(&"imgs/1.jpg") + /// .build().expect("failed to build session"); + /// ``` + pub fn add_example>>(mut self, example: E) -> Self { + self.examples.push(example.into()); + self + } + + /// Adds Examples from which a generator will synthesize a new image. + /// + /// See [`examples/02_multi_example_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/02_multi_example_synthesis.rs) + /// + /// # Examples + /// + /// ```no_run + /// let tex_synth = texture_synthesis::Session::builder() + /// .add_examples(&[&"imgs/1.jpg", &"imgs/2.jpg"]) + /// .build().expect("failed to build session"); + /// ``` + pub fn add_examples>, I: IntoIterator>( + mut self, + examples: I, + ) -> Self { + self.examples.extend(examples.into_iter().map(|e| e.into())); + self + } + + /// Inpaints an example. Due to how inpainting works, a size must also be + /// provided, as all examples, as well as the inpaint mask, must be the same + /// size as each other, as well as the final output image. Using + /// `resize_input` or `output_size` is ignored if this method is called. + /// + /// To prevent sampling from the example, you can specify + /// `SamplingMethod::Ignore` with `Example::set_sample_method`. + /// + /// See [`examples/05_inpaint`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/05_inpaint.rs) + /// + /// # Examples + /// + /// ```no_run + /// let tex_synth = texture_synthesis::Session::builder() + /// .add_examples(&[&"imgs/1.jpg", &"imgs/3.jpg"]) + /// .inpaint_example( + /// &"masks/inpaint.jpg", + /// // This will prevent sampling from the imgs/2.jpg, note that + /// // we *MUST* provide at least one example to source from! + /// texture_synthesis::Example::builder(&"imgs/2.jpg") + /// .set_sample_method(texture_synthesis::SampleMethod::Ignore), + /// texture_synthesis::Dims::square(400) + /// ) + /// .build().expect("failed to build session"); + /// ``` + pub fn inpaint_example>, E: Into>>( + mut self, + inpaint_mask: I, + example: E, + size: Dims, + ) -> Self { + self.inpaint_mask = Some(InpaintMask { + src: MaskOrImg::ImageSource(inpaint_mask.into()), + example_index: self.examples.len(), + dims: size, + }); + self.examples.push(example.into()); + self + } + + /// Inpaints an example, using a specific channel in the example image as + /// the inpaint mask + /// + /// # Examples + /// + /// ```no_run + /// let tex_synth = texture_synthesis::Session::builder() + /// .inpaint_example_channel( + /// // Let's use inpaint the alpha channel + /// texture_synthesis::ChannelMask::A, + /// &"imgs/bricks.png", + /// texture_synthesis::Dims::square(400) + /// ) + /// .build().expect("failed to build session"); + /// ``` + pub fn inpaint_example_channel>>( + mut self, + mask: utils::ChannelMask, + example: E, + size: Dims, + ) -> Self { + self.inpaint_mask = Some(InpaintMask { + src: MaskOrImg::Mask(mask), + example_index: self.examples.len(), + dims: size, + }); + self.examples.push(example.into()); + self + } + + /// Loads a target guide map. + /// + /// If no `Example` guide maps are provided, this will produce a style + /// transfer effect, where the Examples are styles and the target guide is + /// content. + /// + /// See [`examples/03_guided_synthesis`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/03_guided_synthesis.rs), + /// or [`examples/04_style_transfer`](https://github.com/EmbarkStudios/texture-synthesis/tree/main/lib/examples/04_style_transfer.rs), + pub fn load_target_guide>>(mut self, guide: I) -> Self { + self.target_guide = Some(guide.into()); + self + } + + /// Overwrite incoming images sizes + pub fn resize_input(mut self, dims: Dims) -> Self { + self.params.resize_input = Some(dims); + self + } + + /// Changes pseudo-deterministic seed. + /// + /// Global structures will stay same, if the same seed is provided, but + /// smaller details may change due to undeterministic nature of + /// multithreading. + pub fn seed(mut self, value: u64) -> Self { + self.params.seed = value; + self + } + + /// Makes the generator output tiling image. + /// + /// Default: false. + pub fn tiling_mode(mut self, is_tiling: bool) -> Self { + self.params.tiling_mode = is_tiling; + self + } + + /// How many neighboring pixels each pixel is aware of during generation. + /// + /// A larger number means more global structures are captured. + /// + /// Default: 50 + pub fn nearest_neighbors(mut self, count: u32) -> Self { + self.params.nearest_neighbors = count; + self + } + + /// The number of random locations that will be considered during a pixel + /// resolution apart from its immediate neighbors. + /// + /// If unsure, keep same as nearest neighbors. + /// + /// Default: 50 + pub fn random_sample_locations(mut self, count: u64) -> Self { + self.params.random_sample_locations = count; + self + } + + /// Forces the first `n` pixels to be randomly resolved, and prevents them + /// from being overwritten. + /// + /// Can be an enforcing factor of remixing multiple images together. + pub fn random_init(mut self, count: u64) -> Self { + self.params.random_resolve = Some(count); + self + } + + /// The distribution dispersion used for picking best candidate (controls + /// the distribution 'tail flatness'). + /// + /// Values close to 0.0 will produce 'harsh' borders between generated + /// 'chunks'. Values closer to 1.0 will produce a smoother gradient on those + /// borders. + /// + /// For futher reading, check out P.Harrison's "Image Texture Tools". + /// + /// Default: 1.0 + pub fn cauchy_dispersion(mut self, value: f32) -> Self { + self.params.cauchy_dispersion = value; + self + } + + /// Controls the trade-off between guide and example maps. + /// + /// If doing style transfer, set to about 0.8-0.6 to allow for more global + /// structures of the style. + /// + /// If you'd like the guide maps to be considered through all generation + /// stages, set to 1.0, which will prevent guide maps weight "decay" during + /// the score calculation. + /// + /// Default: 0.8 + pub fn guide_alpha(mut self, value: f32) -> Self { + self.params.guide_alpha = value; + self + } + + /// The percentage of pixels to be backtracked during each `p_stage`. + /// Range (0,1). + /// + /// Default: 0.5 + pub fn backtrack_percent(mut self, value: f32) -> Self { + self.params.backtrack_percent = value; + self + } + + /// Controls the number of backtracking stages. + /// + /// Backtracking prevents 'garbage' generation. Right now, the depth of the + /// image pyramid for multiresolution synthesis depends on this parameter as + /// well. + /// + /// Default: 5 + pub fn backtrack_stages(mut self, stages: u32) -> Self { + self.params.backtrack_stages = stages; + self + } + + /// Specify size of the generated image. + /// + /// Default: 500x500 + pub fn output_size(mut self, dims: Dims) -> Self { + self.params.output_size = dims; + self + } + + /// Controls the maximum number of threads that will be spawned at any one + /// time in parallel. + /// + /// This number is allowed to exceed the number of logical cores on the + /// system, but it should generally be kept at or below that number. + /// + /// Setting this number to `1` will result in completely deterministic + /// image generation, meaning that redoing generation with the same inputs + /// will always give you the same outputs. + /// + /// Default: The number of logical cores on this system. + pub fn max_thread_count(mut self, count: usize) -> Self { + self.params.max_thread_count = Some(count); + self + } + + /// Creates a `Session`, or returns an error if invalid parameters or input + /// images were specified. + pub fn build(mut self) -> Result { + self.check_parameters_validity()?; + self.check_images_validity()?; + + struct InpaintExample { + inpaint_mask: image::RgbaImage, + color_map: image::RgbaImage, + example_index: usize, + } + + let (inpaint, out_size, in_size) = match self.inpaint_mask { + Some(inpaint_mask) => { + let dims = inpaint_mask.dims; + let inpaint_img = match inpaint_mask.src { + MaskOrImg::ImageSource(img) => load_image(img, Some(dims))?, + MaskOrImg::Mask(mask) => { + let example_img = &mut self.examples[inpaint_mask.example_index].img; + + let dynamic_img = utils::load_dynamic_image(example_img.clone())?; + let inpaint_src = ImageSource::Image(dynamic_img.clone()); + + // Replace the example image source so we don't load it twice + *example_img = ImageSource::Image(dynamic_img); + + let inpaint_mask = load_image(inpaint_src, Some(dims))?; + + utils::apply_mask(inpaint_mask, mask) + } + }; + + let color_map = load_image( + self.examples[inpaint_mask.example_index].img.clone(), + Some(dims), + )?; + + ( + Some(InpaintExample { + inpaint_mask: inpaint_img, + color_map, + example_index: inpaint_mask.example_index, + }), + dims, + Some(dims), + ) + } + None => (None, self.params.output_size, self.params.resize_input), + }; + + let target_guide = match self.target_guide { + Some(tg) => { + let tg_img = load_image(tg, Some(out_size))?; + + let num_guides = self.examples.iter().filter(|ex| ex.guide.is_some()).count(); + let tg_img = if num_guides == 0 { + transform_to_guide_map(tg_img, None, 2.0) + } else { + tg_img + }; + + Some(ImagePyramid::new( + tg_img, + Some(self.params.backtrack_stages as u32), + )) + } + None => None, + }; + + let example_len = self.examples.len(); + + let mut examples = Vec::with_capacity(example_len); + let mut guides = if target_guide.is_some() { + Vec::with_capacity(example_len) + } else { + Vec::new() + }; + let mut methods = Vec::with_capacity(example_len); + + for example in self.examples { + let resolved = example.resolve(self.params.backtrack_stages, in_size, &target_guide)?; + + examples.push(resolved.image); + + if let Some(guide) = resolved.guide { + guides.push(guide); + } + + methods.push(resolved.method); + } + + // Initialize generator based on availability of an inpaint_mask. + let generator = match inpaint { + None => Generator::new(out_size), + Some(inpaint) => Generator::new_from_inpaint( + out_size, + inpaint.inpaint_mask, + inpaint.color_map, + inpaint.example_index, + ), + }; + + let session = Session { + examples, + guides: target_guide.map(|tg| GuidesPyramidStruct { + target_guide: tg, + example_guides: guides, + }), + sampling_methods: methods, + params: self.params, + generator, + }; + + Ok(session) + } + + fn check_parameters_validity(&self) -> Result<(), Error> { + if self.params.cauchy_dispersion < 0.0 || self.params.cauchy_dispersion > 1.0 { + return Err(Error::InvalidRange(errors::InvalidRange { + min: 0.0, + max: 1.0, + value: self.params.cauchy_dispersion, + name: "cauchy-dispersion", + })); + } + + if self.params.backtrack_percent < 0.0 || self.params.backtrack_percent > 1.0 { + return Err(Error::InvalidRange(errors::InvalidRange { + min: 0.0, + max: 1.0, + value: self.params.backtrack_percent, + name: "backtrack-percent", + })); + } + + if self.params.guide_alpha < 0.0 || self.params.guide_alpha > 1.0 { + return Err(Error::InvalidRange(errors::InvalidRange { + min: 0.0, + max: 1.0, + value: self.params.guide_alpha, + name: "guide-alpha", + })); + } + + if let Some(max_count) = self.params.max_thread_count { + if max_count == 0 { + return Err(Error::InvalidRange(errors::InvalidRange { + min: 1.0, + max: 1024.0, + value: max_count as f32, + name: "max-thread-count", + })); + } + } + + if self.params.random_sample_locations == 0 { + return Err(Error::InvalidRange(errors::InvalidRange { + min: 1.0, + max: 1024.0, + value: self.params.random_sample_locations as f32, + name: "m-rand", + })); + } + + Ok(()) + } + + fn check_images_validity(&self) -> Result<(), Error> { + // We must have at least one example image to source pixels from + let input_count = self + .examples + .iter() + .filter(|ex| !ex.sample_method.is_ignore()) + .count(); + + if input_count == 0 { + return Err(Error::NoExamples); + } + + // If we have more than one example guide, then *every* example + // needs a guide + let num_guides = self.examples.iter().filter(|ex| ex.guide.is_some()).count(); + if num_guides != 0 && self.examples.len() != num_guides { + return Err(Error::ExampleGuideMismatch( + self.examples.len() as u32, + num_guides as u32, + )); + } + + Ok(()) + } +} + +/// Helper struct for passing progress information to external callers +pub struct ProgressStat { + /// The current amount of work that has been done + pub current: usize, + /// The total amount of work to do + pub total: usize, +} + +/// The current state of the image generator +pub struct ProgressUpdate<'a> { + /// The currenty resolved image + pub image: &'a image::RgbaImage, + /// The total progress for the final image + pub total: ProgressStat, + /// The progress for the current stage + pub stage: ProgressStat, +} + +/// Allows the generator to update external callers with the current +/// progress of the image synthesis +pub trait GeneratorProgress { + fn update(&mut self, info: ProgressUpdate<'_>); +} + +impl GeneratorProgress for G +where + G: FnMut(ProgressUpdate<'_>) + Send, +{ + fn update(&mut self, info: ProgressUpdate<'_>) { + self(info) + } +} diff --git a/lib/src/utils.rs b/lib/src/utils.rs index f1c01ec..07425be 100644 --- a/lib/src/utils.rs +++ b/lib/src/utils.rs @@ -59,19 +59,19 @@ pub(crate) fn load_image( let img = load_dynamic_image(src)?; let img = match resize { - None => img.to_rgba(), + None => img.to_rgba8(), Some(ref size) => { use image::GenericImageView; if img.width() != size.width || img.height() != size.height { image::imageops::resize( - &img.to_rgba(), + &img.to_rgba8(), size.width, size.height, image::imageops::CatmullRom, ) } else { - img.to_rgba() + img.to_rgba8() } } }; @@ -112,7 +112,7 @@ pub(crate) fn transform_to_guide_map( } } - dyn_img.blur(blur_sigma).grayscale().to_rgba() + dyn_img.blur(blur_sigma).grayscale().to_rgba8() } pub(crate) fn get_histogram(img: &image::RgbaImage) -> Vec {