├── .editorconfig ├── .github └── workflows │ ├── flakehub.yml │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .release-please-manifest.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── cli.ts ├── default.nix ├── flake.lock ├── flake.nix ├── justfile ├── overlay.nix ├── pkg └── deno │ ├── LICENSE │ ├── README.md │ ├── catwalk.d.ts │ ├── catwalk.js │ ├── catwalk_bg.wasm │ └── catwalk_bg.wasm.d.ts ├── release-please-config.json ├── renovate.json ├── shell.nix └── src ├── cli.rs ├── lib.rs ├── main.rs ├── mask.rs └── wasm.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # EditorConfig is awesome: https://EditorConfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | indent_size = 2 10 | indent_style = space 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | # go 16 | [*.go] 17 | indent_style = tab 18 | indent_size = 4 19 | 20 | # python 21 | [*.{ini,py,py.tpl,rst}] 22 | indent_size = 4 23 | 24 | # rust 25 | [*.rs] 26 | indent_size = 4 27 | 28 | # documentation, utils 29 | [*.{md,mdx,diff}] 30 | trim_trailing_whitespace = false 31 | 32 | # windows shell scripts 33 | [*.{cmd,bat,ps1}] 34 | end_of_line = crlf 35 | -------------------------------------------------------------------------------- /.github/workflows/flakehub.yml: -------------------------------------------------------------------------------- 1 | name: Publish to FlakeHub 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: ["**.nix", "**.rs", "Cargo.lock"] 7 | pull_request: 8 | paths: ["**.nix", "**.rs", "Cargo.lock"] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest] 16 | 17 | permissions: 18 | id-token: write 19 | contents: read 20 | 21 | runs-on: ${{ matrix.os }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: DeterminateSystems/nix-installer-action@main 27 | - uses: DeterminateSystems/magic-nix-cache-action@main 28 | 29 | - name: Setup cachix 30 | uses: cachix/cachix-action@v15 31 | with: 32 | name: catppuccin 33 | authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" 34 | 35 | - name: Flake check 36 | run: nix flake check -Lv 37 | 38 | - name: Build packages 39 | run: nix build -L . 40 | 41 | - uses: DeterminateSystems/flakehub-push@main 42 | # only push to FlakeHub once when running on main 43 | if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && matrix.os == 'ubuntu-latest' 44 | with: 45 | name: catppuccin/catwalk 46 | rolling: true 47 | visibility: public 48 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | paths: ["**.rs", "**.toml", "**.lock"] 6 | push: 7 | branches: [main] 8 | paths: ["**.rs", "**.toml", "**.lock"] 9 | 10 | jobs: 11 | rust: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: DeterminateSystems/nix-installer-action@main 17 | 18 | - name: clippy 19 | run: | 20 | nix develop --command cargo clippy 21 | nix develop --command cargo clippy --target wasm32-unknown-unknown --lib 22 | nix develop --command cargo clippy --target wasm32-unknown-unknown --lib --all-features 23 | 24 | - name: rustfmt check 25 | run: | 26 | cargo fmt --all --check 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [main] 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: googleapis/release-please-action@v4 17 | id: release 18 | outputs: 19 | release_created: ${{ steps.release.outputs.release_created }} 20 | tag_name: ${{ steps.release.outputs.tag_name }} 21 | 22 | release: 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | os: ["ubuntu-latest", "macos-latest", "windows-latest"] 27 | 28 | runs-on: ${{ matrix.os }} 29 | 30 | defaults: 31 | run: 32 | shell: bash 33 | 34 | env: 35 | EXECUTABLE: "catwalk" 36 | CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse 37 | EXE_SUFFIX: ${{ matrix.os == 'windows-latest' && '.exe' || '' }} 38 | 39 | needs: release-please 40 | if: ${{ needs.release-please.outputs.release_created }} 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: dtolnay/rust-toolchain@stable 44 | - uses: actions/cache@v4 45 | with: 46 | path: | 47 | ~/.cargo/registry 48 | ~/.cargo/git 49 | target 50 | key: ${{ runner.os }}-cargo-${{ hashFiles('./Cargo.lock') }}-release 51 | 52 | - name: Build 53 | id: build 54 | run: | 55 | cargo build --release --locked 56 | cargo test --release --locked 57 | 58 | export BINARY_NAME="${EXECUTABLE}-$(rustc --version --verbose | grep host | cut -d ' ' -f 2)${EXE_SUFFIX}" 59 | mv "target/release/${EXECUTABLE}${EXE_SUFFIX}" "./target/${BINARY_NAME}" 60 | echo "binary=target/${BINARY_NAME}" >> $GITHUB_OUTPUT 61 | 62 | - name: Publish to crates.io 63 | if: ${{ matrix.os == 'ubuntu-latest' }} 64 | env: 65 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 66 | run: cargo publish 67 | 68 | - name: Upload to release 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | run: gh release upload "${{ needs.release-please.outputs.tag_name }}" ${{ steps.build.outputs.binary }} 72 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | paths: ["**.rs", "**.toml", "**.lock"] 6 | push: 7 | branches: [main] 8 | paths: ["**.rs", "**.toml", "**.lock"] 9 | 10 | jobs: 11 | rust: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: dtolnay/rust-toolchain@stable 20 | 21 | - uses: actions/cache@v4 22 | with: 23 | path: | 24 | ~/.cargo/registry 25 | ~/.cargo/git 26 | target 27 | key: ${{ runner.os }}-cargo-${{ hashFiles('./Cargo.lock') }}-test 28 | 29 | - name: cargo test 30 | run: cargo test --all-features 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore the NPM package 2 | pkg/* 3 | # keep deno for esm 4 | !pkg/deno 5 | # cargo 6 | target/ 7 | # nix 8 | result 9 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.3.2" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.3.2](https://github.com/catppuccin/catwalk/compare/v1.3.1...v1.3.2) (2024-06-10) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * **deps:** update rust crate clap_complete to v4.5.5 ([#3](https://github.com/catppuccin/catwalk/issues/3)) ([f95b7ef](https://github.com/catppuccin/catwalk/commit/f95b7efd29cd6ab3b6ae6915d4a06fa00c29e7b1)) 9 | 10 | ## [1.3.1](https://github.com/catppuccin/toolbox/compare/catwalk-v1.3.0...catwalk-v1.3.1) (2024-05-20) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **deps:** update rust crate clap to 4.5.4 ([#189](https://github.com/catppuccin/toolbox/issues/189)) ([52d5aa4](https://github.com/catppuccin/toolbox/commit/52d5aa42b0e9a6085b22da37580912a55c442477)) 16 | * **deps:** update rust crate clap_complete to 4.5.2 ([#190](https://github.com/catppuccin/toolbox/issues/190)) ([cda4f29](https://github.com/catppuccin/toolbox/commit/cda4f2999ed187cacf14e3304ff73637119d1f27)) 17 | * **deps:** update rust crate color-eyre to 0.6.3 ([#191](https://github.com/catppuccin/toolbox/issues/191)) ([8a7c14a](https://github.com/catppuccin/toolbox/commit/8a7c14a3ee9f48ceccda36c42815279c08862916)) 18 | * **deps:** update rust crate getrandom to 0.2.14 ([#196](https://github.com/catppuccin/toolbox/issues/196)) ([fb426be](https://github.com/catppuccin/toolbox/commit/fb426be432fd48785426b9da7431978c7aa342fd)) 19 | 20 | ## [1.3.0](https://github.com/catppuccin/toolbox/compare/catwalk-v1.2.0...catwalk-v1.3.0) (2024-04-27) 21 | 22 | 23 | ### Features 24 | 25 | * binstall support ([#173](https://github.com/catppuccin/toolbox/issues/173)) ([2ae0c33](https://github.com/catppuccin/toolbox/commit/2ae0c33b9b6c577cacbeed02e6a68873194597ab)) 26 | 27 | ## [1.2.0](https://github.com/catppuccin/toolbox/compare/catwalk-v1.1.1...catwalk-v1.2.0) (2023-11-04) 28 | 29 | 30 | ### Features 31 | 32 | * add `--layout row` ([#60](https://github.com/catppuccin/toolbox/issues/60)) ([00f26b5](https://github.com/catppuccin/toolbox/commit/00f26b56b77ba1aeb12f3070371efc19a6fb67ac)), closes [#20](https://github.com/catppuccin/toolbox/issues/20) 33 | * **wasm:** add `Catwalk.version` ([6833066](https://github.com/catppuccin/toolbox/commit/6833066b0f371aa6c8ccd3a0300aec7cc728f3a1)) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * **wasm:** result image size ([6833066](https://github.com/catppuccin/toolbox/commit/6833066b0f371aa6c8ccd3a0300aec7cc728f3a1)) 39 | 40 | ## [1.1.1](https://github.com/catppuccin/toolbox/compare/catwalk-v1.1.0...catwalk-v1.1.1) (2023-11-03) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * custom antialiasing artifacts ([#57](https://github.com/catppuccin/toolbox/issues/57)) ([0f25654](https://github.com/catppuccin/toolbox/commit/0f256543b404533c3bf05cc9f9862d6ea7aa13ee)) 46 | * explicit filename extension override ([#59](https://github.com/catppuccin/toolbox/issues/59)) ([e211ba8](https://github.com/catppuccin/toolbox/commit/e211ba8e9daeb3c0199394ed149bf81799391da8)) 47 | 48 | ## [1.1.0](https://github.com/catppuccin/toolbox/compare/catwalk-v1.0.3...catwalk-v1.1.0) (2023-10-30) 49 | 50 | 51 | ### Features 52 | 53 | * make input names optional, `-C` flag for directory, `--ext` for input file type ([#55](https://github.com/catppuccin/toolbox/issues/55)) ([b2ce59e](https://github.com/catppuccin/toolbox/commit/b2ce59e5d607f77ca3135b847c851465933d05e5)) 54 | 55 | ## [1.0.3](https://github.com/catppuccin/toolbox/compare/catwalk-v1.0.2...catwalk-v1.0.3) (2023-10-28) 56 | 57 | 58 | ### Miscellaneous Chores 59 | 60 | * **catwalk:** release as 1.0.3 ([842134a](https://github.com/catppuccin/toolbox/commit/842134a483fa8a0fb1a2ca6cdad66a4f760ecafe)) 61 | 62 | ## [1.0.2](https://github.com/catppuccin/toolbox/compare/catwalk-v1.0.1...catwalk-v1.0.2) (2023-10-27) 63 | 64 | 65 | ### Miscellaneous Chores 66 | 67 | * **catwalk:** release as 1.0.2 ([78834c6](https://github.com/catppuccin/toolbox/commit/78834c684ae565fc139fc307ad61f106b626e0f5)) 68 | 69 | ## [1.0.1](https://github.com/catppuccin/toolbox/compare/catwalk-v1.0.0...catwalk-v1.0.1) (2023-10-27) 70 | 71 | 72 | ### Miscellaneous Chores 73 | 74 | * **catwalk:** release as 1.0.1 ([58ff3e0](https://github.com/catppuccin/toolbox/commit/58ff3e07cc69b51d8a6de6dd710ce1f5634f161a)) 75 | 76 | ## [1.0.0](https://github.com/catppuccin/toolbox/compare/catwalk-v0.1.4...catwalk-v1.0.0) (2023-10-27) 77 | 78 | 79 | ### Miscellaneous Chores 80 | 81 | * **catwalk:** release as 1.0.0 ([d66ec8d](https://github.com/catppuccin/toolbox/commit/d66ec8d984887c4d417166d2180bcf249e11a318)) 82 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.8" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.4" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 55 | dependencies = [ 56 | "windows-sys", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys", 67 | ] 68 | 69 | [[package]] 70 | name = "autocfg" 71 | version = "1.3.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 74 | 75 | [[package]] 76 | name = "backtrace" 77 | version = "0.3.71" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 80 | dependencies = [ 81 | "addr2line", 82 | "cc", 83 | "cfg-if", 84 | "libc", 85 | "miniz_oxide", 86 | "object", 87 | "rustc-demangle", 88 | ] 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "1.3.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 95 | 96 | [[package]] 97 | name = "bumpalo" 98 | version = "3.16.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 101 | 102 | [[package]] 103 | name = "catppuccin-catwalk" 104 | version = "1.3.2" 105 | dependencies = [ 106 | "clap", 107 | "clap_complete", 108 | "color-eyre", 109 | "getrandom", 110 | "js-sys", 111 | "ril", 112 | "thiserror", 113 | "wasm-bindgen", 114 | "web-sys", 115 | ] 116 | 117 | [[package]] 118 | name = "cc" 119 | version = "1.0.98" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 122 | 123 | [[package]] 124 | name = "cfg-if" 125 | version = "1.0.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 128 | 129 | [[package]] 130 | name = "clap" 131 | version = "4.5.18" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" 134 | dependencies = [ 135 | "clap_builder", 136 | "clap_derive", 137 | ] 138 | 139 | [[package]] 140 | name = "clap_builder" 141 | version = "4.5.18" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" 144 | dependencies = [ 145 | "anstream", 146 | "anstyle", 147 | "clap_lex", 148 | "strsim", 149 | ] 150 | 151 | [[package]] 152 | name = "clap_complete" 153 | version = "4.5.29" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "8937760c3f4c60871870b8c3ee5f9b30771f792a7045c48bcbba999d7d6b3b8e" 156 | dependencies = [ 157 | "clap", 158 | ] 159 | 160 | [[package]] 161 | name = "clap_derive" 162 | version = "4.5.18" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 165 | dependencies = [ 166 | "heck", 167 | "proc-macro2", 168 | "quote", 169 | "syn", 170 | ] 171 | 172 | [[package]] 173 | name = "clap_lex" 174 | version = "0.7.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 177 | 178 | [[package]] 179 | name = "color-eyre" 180 | version = "0.6.3" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" 183 | dependencies = [ 184 | "backtrace", 185 | "eyre", 186 | "indenter", 187 | "once_cell", 188 | "owo-colors", 189 | ] 190 | 191 | [[package]] 192 | name = "colorchoice" 193 | version = "1.0.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 196 | 197 | [[package]] 198 | name = "crc32fast" 199 | version = "1.4.2" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 202 | dependencies = [ 203 | "cfg-if", 204 | ] 205 | 206 | [[package]] 207 | name = "eyre" 208 | version = "0.6.12" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 211 | dependencies = [ 212 | "indenter", 213 | "once_cell", 214 | ] 215 | 216 | [[package]] 217 | name = "fast_image_resize" 218 | version = "2.7.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "cc789a40040e11bbe4ba31ca319406805a12fe3f8d71314bbc4bd076602ad55a" 221 | dependencies = [ 222 | "num-traits", 223 | "thiserror", 224 | ] 225 | 226 | [[package]] 227 | name = "fdeflate" 228 | version = "0.3.4" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" 231 | dependencies = [ 232 | "simd-adler32", 233 | ] 234 | 235 | [[package]] 236 | name = "flate2" 237 | version = "1.0.30" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" 240 | dependencies = [ 241 | "crc32fast", 242 | "miniz_oxide", 243 | ] 244 | 245 | [[package]] 246 | name = "getrandom" 247 | version = "0.2.15" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 250 | dependencies = [ 251 | "cfg-if", 252 | "js-sys", 253 | "libc", 254 | "wasi", 255 | "wasm-bindgen", 256 | ] 257 | 258 | [[package]] 259 | name = "gimli" 260 | version = "0.28.1" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 263 | 264 | [[package]] 265 | name = "heck" 266 | version = "0.5.0" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 269 | 270 | [[package]] 271 | name = "indenter" 272 | version = "0.3.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 275 | 276 | [[package]] 277 | name = "is_terminal_polyfill" 278 | version = "1.70.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 281 | 282 | [[package]] 283 | name = "js-sys" 284 | version = "0.3.69" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 287 | dependencies = [ 288 | "wasm-bindgen", 289 | ] 290 | 291 | [[package]] 292 | name = "libc" 293 | version = "0.2.155" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 296 | 297 | [[package]] 298 | name = "libwebp-sys2" 299 | version = "0.1.9" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "0e2ae528b6c8f543825990b24c00cfd8fe64dde126c8288f4972b18e3d558072" 302 | dependencies = [ 303 | "cc", 304 | "cfg-if", 305 | "libc", 306 | "pkg-config", 307 | "vcpkg", 308 | ] 309 | 310 | [[package]] 311 | name = "log" 312 | version = "0.4.21" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 315 | 316 | [[package]] 317 | name = "memchr" 318 | version = "2.7.2" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 321 | 322 | [[package]] 323 | name = "miniz_oxide" 324 | version = "0.7.3" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 327 | dependencies = [ 328 | "adler", 329 | "simd-adler32", 330 | ] 331 | 332 | [[package]] 333 | name = "num-traits" 334 | version = "0.2.19" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 337 | dependencies = [ 338 | "autocfg", 339 | ] 340 | 341 | [[package]] 342 | name = "object" 343 | version = "0.32.2" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 346 | dependencies = [ 347 | "memchr", 348 | ] 349 | 350 | [[package]] 351 | name = "once_cell" 352 | version = "1.19.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 355 | 356 | [[package]] 357 | name = "owo-colors" 358 | version = "3.5.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 361 | 362 | [[package]] 363 | name = "pkg-config" 364 | version = "0.3.30" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 367 | 368 | [[package]] 369 | name = "png" 370 | version = "0.17.13" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" 373 | dependencies = [ 374 | "bitflags", 375 | "crc32fast", 376 | "fdeflate", 377 | "flate2", 378 | "miniz_oxide", 379 | ] 380 | 381 | [[package]] 382 | name = "proc-macro2" 383 | version = "1.0.85" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 386 | dependencies = [ 387 | "unicode-ident", 388 | ] 389 | 390 | [[package]] 391 | name = "quote" 392 | version = "1.0.36" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 395 | dependencies = [ 396 | "proc-macro2", 397 | ] 398 | 399 | [[package]] 400 | name = "ril" 401 | version = "0.10.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "cb9d89f558ed427b172d6014c4cf3145b506d379df0676b471964dbbbe923ea1" 404 | dependencies = [ 405 | "fast_image_resize", 406 | "libwebp-sys2", 407 | "num-traits", 408 | "png", 409 | ] 410 | 411 | [[package]] 412 | name = "rustc-demangle" 413 | version = "0.1.24" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 416 | 417 | [[package]] 418 | name = "simd-adler32" 419 | version = "0.3.7" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 422 | 423 | [[package]] 424 | name = "strsim" 425 | version = "0.11.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 428 | 429 | [[package]] 430 | name = "syn" 431 | version = "2.0.66" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 434 | dependencies = [ 435 | "proc-macro2", 436 | "quote", 437 | "unicode-ident", 438 | ] 439 | 440 | [[package]] 441 | name = "thiserror" 442 | version = "1.0.63" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 445 | dependencies = [ 446 | "thiserror-impl", 447 | ] 448 | 449 | [[package]] 450 | name = "thiserror-impl" 451 | version = "1.0.63" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 454 | dependencies = [ 455 | "proc-macro2", 456 | "quote", 457 | "syn", 458 | ] 459 | 460 | [[package]] 461 | name = "unicode-ident" 462 | version = "1.0.12" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 465 | 466 | [[package]] 467 | name = "utf8parse" 468 | version = "0.2.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 471 | 472 | [[package]] 473 | name = "vcpkg" 474 | version = "0.2.15" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 477 | 478 | [[package]] 479 | name = "wasi" 480 | version = "0.11.0+wasi-snapshot-preview1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 483 | 484 | [[package]] 485 | name = "wasm-bindgen" 486 | version = "0.2.92" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 489 | dependencies = [ 490 | "cfg-if", 491 | "wasm-bindgen-macro", 492 | ] 493 | 494 | [[package]] 495 | name = "wasm-bindgen-backend" 496 | version = "0.2.92" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 499 | dependencies = [ 500 | "bumpalo", 501 | "log", 502 | "once_cell", 503 | "proc-macro2", 504 | "quote", 505 | "syn", 506 | "wasm-bindgen-shared", 507 | ] 508 | 509 | [[package]] 510 | name = "wasm-bindgen-macro" 511 | version = "0.2.92" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 514 | dependencies = [ 515 | "quote", 516 | "wasm-bindgen-macro-support", 517 | ] 518 | 519 | [[package]] 520 | name = "wasm-bindgen-macro-support" 521 | version = "0.2.92" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 524 | dependencies = [ 525 | "proc-macro2", 526 | "quote", 527 | "syn", 528 | "wasm-bindgen-backend", 529 | "wasm-bindgen-shared", 530 | ] 531 | 532 | [[package]] 533 | name = "wasm-bindgen-shared" 534 | version = "0.2.92" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 537 | 538 | [[package]] 539 | name = "web-sys" 540 | version = "0.3.69" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 543 | dependencies = [ 544 | "js-sys", 545 | "wasm-bindgen", 546 | ] 547 | 548 | [[package]] 549 | name = "windows-sys" 550 | version = "0.52.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 553 | dependencies = [ 554 | "windows-targets", 555 | ] 556 | 557 | [[package]] 558 | name = "windows-targets" 559 | version = "0.52.5" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 562 | dependencies = [ 563 | "windows_aarch64_gnullvm", 564 | "windows_aarch64_msvc", 565 | "windows_i686_gnu", 566 | "windows_i686_gnullvm", 567 | "windows_i686_msvc", 568 | "windows_x86_64_gnu", 569 | "windows_x86_64_gnullvm", 570 | "windows_x86_64_msvc", 571 | ] 572 | 573 | [[package]] 574 | name = "windows_aarch64_gnullvm" 575 | version = "0.52.5" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 578 | 579 | [[package]] 580 | name = "windows_aarch64_msvc" 581 | version = "0.52.5" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 584 | 585 | [[package]] 586 | name = "windows_i686_gnu" 587 | version = "0.52.5" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 590 | 591 | [[package]] 592 | name = "windows_i686_gnullvm" 593 | version = "0.52.5" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 596 | 597 | [[package]] 598 | name = "windows_i686_msvc" 599 | version = "0.52.5" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 602 | 603 | [[package]] 604 | name = "windows_x86_64_gnu" 605 | version = "0.52.5" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 608 | 609 | [[package]] 610 | name = "windows_x86_64_gnullvm" 611 | version = "0.52.5" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 614 | 615 | [[package]] 616 | name = "windows_x86_64_msvc" 617 | version = "0.52.5" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 620 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "catppuccin-catwalk" 3 | description = "🚶 Soothing pastel previews for the high-spirited!" 4 | authors = ["Catppuccin Org "] 5 | repository = "https://github.com/catppuccin/catwalk" 6 | license = "MIT" 7 | version = "1.3.2" 8 | edition = "2021" 9 | 10 | [profile.release] 11 | lto = true 12 | opt-level = "s" 13 | strip = true 14 | 15 | [[bin]] 16 | name = "catwalk" 17 | path = "src/main.rs" 18 | 19 | [package.metadata.binstall] 20 | pkg-url = "{ repo }/releases/download/v{ version }/catwalk-{ target }{ archive-suffix }" 21 | pkg-fmt = "bin" 22 | 23 | [lib] 24 | crate-type = ["cdylib", "rlib"] 25 | 26 | [dependencies] 27 | thiserror = "1.0.61" 28 | 29 | [target.'cfg(not(target_family = "wasm"))'.dependencies] 30 | clap = { version = "4.5.4", features = ["derive"] } 31 | clap_complete = { version = "4.5.2" } 32 | color-eyre = { version = "0.6.3", default-features = false } 33 | ril = { version = "0.10.1", default-features = false, features = [ 34 | "png", 35 | "resize", 36 | "webp", 37 | ] } 38 | 39 | [target.'cfg(target_family = "wasm")'.dependencies] 40 | js-sys = "0.3.69" 41 | ril = { version = "0.10.1", default-features = false } 42 | wasm-bindgen = "0.2.92" 43 | web-sys = { version = "0.3.69", features = ["ImageData"] } 44 | getrandom = { version = "0.2.15", features = ["js"] } 45 | 46 | [package.metadata.wasm-pack.profile.release] 47 | wasm-opt = ['-O3'] 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Catppuccin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo
3 | 4 | Catppuccin Catwalk 5 | 6 |

7 | 8 |

9 | 10 | 11 | 12 |

13 | 14 |   15 | 16 | Catwalk makes it easy to generate [beautiful preview images](https://raw.githubusercontent.com/catppuccin/vscode/main/assets/preview.webp) for your ports. 17 | 18 | > [!IMPORTANT] 19 | > This repository has been migrated from 20 | > [catppuccin/toolbox](https://github.com/catppuccin/toolbox/tree/main/catwalk). To view releases 21 | > prior to [v1.3.1](https://github.com/catppuccin/catwalk/releases/tag/v1.3.1), 22 | > see the [releases from catppuccin/toolbox](https://github.com/catppuccin/toolbox/releases?q=catwalk&expanded=true). 23 | 24 | ## Installation 25 | 26 | You can install Catwalk using one of the methods below: 27 | 28 | | Installation Method | Instructions | 29 | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | 30 | | crates.io | `cargo install catppuccin-catwalk` | 31 | | Source | `cargo install --git https://github.com/catppuccin/catwalk catppuccin-catwalk` | 32 | | Homebrew | `brew install catppuccin/tap/catwalk` | 33 | | Nix | `nix profile install github:catppuccin/catwalk`
`nix run github:catppuccin/catwalk -- [OPTIONS] ` | 34 | | Binaries
(Windows, MacOS & Linux) | Available from the [latest GitHub release](https://github.com/catppuccin/catwalk/releases). | 35 | 36 | ## Usage 37 | 38 | ```console 39 | $ catwalk --help 40 | 🚶Soothing pastel previews for the high-spirited! 41 | 42 | Usage: catwalk [OPTIONS] [LATTE] [FRAPPE] [MACCHIATO] [MOCHA] [COMMAND] 43 | 44 | Commands: 45 | completion Generates a completion file for the given shell 46 | help Print this message or the help of the given subcommand(s) 47 | 48 | Arguments: 49 | [LATTE] Path to Latte image [default: ./latte.webp] 50 | [FRAPPE] Path to Frappé image [default: ./frappe.webp] 51 | [MACCHIATO] Path to Macchiato image [default: ./macchiato.webp] 52 | [MOCHA] Path to Mocha image [default: ./mocha.webp] 53 | 54 | Options: 55 | -o, --output Path to output file [default: ./preview.webp] 56 | -l, --layout Layout to use [default: composite] [possible values: composite, stacked, grid, row] 57 | -r, --radius Radius of rounded corners (percentage) 58 | -g, --gap Size of gaps between pictures for the `grid` layout 59 | -C, --directory Change to before processing files [default: .] 60 | --ext File extension to use for input files, if they're not explicitly named [default: webp] [possible values: webp, png] 61 | -h, --help Print help 62 | -V, --version Print version 63 | ``` 64 | 65 | ### Examples 66 | 67 | ```console 68 | $ catwalk latte.webp frappe.webp macchiato.webp mocha.webp --output catwalk.webp 69 | ``` 70 | 71 | ```console 72 | $ catwalk latte.png frappe.png macchiato.png mocha.png --directory ./assets/ 73 | ``` 74 | 75 |   76 | 77 |

78 |

Copyright © 2021-present Catppuccin Org 79 |

80 | -------------------------------------------------------------------------------- /cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A 2 | import { Catwalk, Layout } from "./pkg/deno/catwalk.js"; 3 | import { 4 | Command, 5 | EnumType, 6 | } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts"; 7 | import { 8 | ImageMagick, 9 | initialize, 10 | MagickColor, 11 | MagickFormat, 12 | MagickImage, 13 | } from "https://deno.land/x/imagemagick_deno@0.0.26/mod.ts"; 14 | 15 | const { args, options } = await new Command() 16 | .name("catwalk") 17 | .version(Catwalk.version) 18 | .description( 19 | "A sweet program that takes in four showcase images and displays them all at once. (JS port)", 20 | ) 21 | .arguments(" ") 22 | .type( 23 | "layout", 24 | new EnumType( 25 | Object.keys(Layout) 26 | .filter((k) => isNaN(Number(k))) 27 | .map((k) => k.toLowerCase()), 28 | ), 29 | ) 30 | .option("-g, --gap ", "Gap size for `grid` Layout") 31 | .option("-l, --layout ", "Layout to use") 32 | .option("-o, --output ", "Output file", { 33 | default: "./result.webp", 34 | }) 35 | .option("-r, --radius ", "Radius of corners") 36 | .parse(Deno.args); 37 | 38 | const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); 39 | 40 | let fmt: MagickFormat; 41 | switch (options.output.split(".").pop()) { 42 | case "png": 43 | fmt = MagickFormat.Png; 44 | break; 45 | case "webp": 46 | fmt = MagickFormat.Webp; 47 | break; 48 | default: 49 | console.error("Invalid output format"); 50 | Deno.exit(1); 51 | } 52 | 53 | await initialize(); 54 | 55 | let [width, height] = [0, 0]; 56 | const [latte, frappe, macchiato, mocha] = args.map((path) => { 57 | const buffer = Deno.readFileSync(path); 58 | return ImageMagick.read(buffer, (data) => { 59 | height = data.height; 60 | width = data.width; 61 | const pixels = data.getPixels((pixel) => 62 | pixel.getArea(0, 0, width, height) 63 | ); 64 | return Uint8Array.from(pixels); 65 | }); 66 | }); 67 | 68 | const cw = Catwalk.new_from_u8_array(latte, frappe, macchiato, mocha, width) 69 | .gap(options.gap) 70 | //@ts-expect-error: i'm not dealing with this 71 | .layout(Layout[(capitalize(options.layout ?? "")) as keyof typeof Layout]) 72 | .radius(options.radius) 73 | .build_buffer(); 74 | 75 | const img = MagickImage.create( 76 | new MagickColor(0, 0, 0, 0), 77 | cw.width, 78 | cw.height, 79 | ); 80 | // needed for WASM, as it defaults to lossy 81 | img.quality = 100; 82 | img.getPixels((pixels) => pixels.setArea(0, 0, cw.width, cw.height, cw.data)); 83 | img.write(fmt, (data) => Deno.writeFile(options.output, data)); 84 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { 3 | inherit system; 4 | config = { }; 5 | overlays = [ ]; 6 | }, 7 | system ? builtins.currentSystem, 8 | }: 9 | let 10 | # re-use our overlay definition 11 | pkgs' = import ./overlay.nix pkgs null; 12 | in 13 | { 14 | catwalk = pkgs'.catppuccin-catwalk; 15 | } 16 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1717681334, 6 | "narHash": "sha256-HlvsMH8BNgdmQCwbBDmWp5/DfkEQYhXZHagJQCgbJU0=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "31f40991012489e858517ec20102f033e4653afb", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "NixOS", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | }; 5 | 6 | outputs = 7 | { self, nixpkgs, ... }: 8 | let 9 | systems = [ 10 | "aarch64-darwin" 11 | "aarch64-linux" 12 | "x86_64-darwin" 13 | "x86_64-linux" 14 | ]; 15 | 16 | forEachSystem = 17 | function: 18 | nixpkgs.lib.genAttrs systems ( 19 | system: 20 | function { 21 | inherit system; 22 | pkgs = nixpkgs.legacyPackages.${system}; 23 | } 24 | ); 25 | in 26 | { 27 | devShells = forEachSystem ( 28 | { pkgs, system }: 29 | { 30 | default = import ./shell.nix { 31 | inherit pkgs system; 32 | inherit (self.packages.${system}) catwalk; 33 | }; 34 | } 35 | ); 36 | 37 | formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt-rfc-style); 38 | 39 | packages = forEachSystem ( 40 | { pkgs, system }: 41 | let 42 | pkgs' = import ./default.nix { inherit pkgs system; }; 43 | in 44 | { 45 | inherit (pkgs') catwalk; 46 | default = self.packages.${system}.catwalk; 47 | } 48 | ); 49 | 50 | overlays.default = final: prev: import ./overlay.nix final prev; 51 | }; 52 | 53 | nixConfig = { 54 | extra-substituters = [ 55 | "https://catppuccin.cachix.org" 56 | "https://nix-community.cachix.org" 57 | ]; 58 | extra-trusted-public-keys = [ 59 | "catppuccin.cachix.org-1:noG/4HkbhJb+lUAdKrph6LaozJvAeEEZj4N732IysmU=" 60 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 61 | ]; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | [private] 2 | default: 3 | @just --choose 4 | 5 | [private] 6 | wasm-pack-build *args: 7 | wasm-pack build --scope catppuccin --release {{args}} 8 | 9 | build-wasm: 10 | @echo "Building npm package..." 11 | rm -rf ./pkg || true 12 | mkdir -p ./pkg 13 | @just wasm-pack-build \ 14 | --target nodejs \ 15 | --out-dir ./pkg \ 16 | --out-name catwalk 17 | sed -i 's/@catppuccin\/catppuccin-catwalk/@catppuccin\/catwalk/g' ./pkg/package.json 18 | @just wasm-pack-build \ 19 | --target deno \ 20 | --out-dir ./pkg/deno \ 21 | --out-name catwalk 22 | rm ./pkg/.gitignore || true 23 | rm ./pkg/deno/.gitignore || true 24 | 25 | check: 26 | cargo clippy || true 27 | cargo clippy --target wasm32-unknown-unknown --lib || true 28 | cargo clippy --target wasm32-unknown-unknown --lib --all-features || true 29 | -------------------------------------------------------------------------------- /overlay.nix: -------------------------------------------------------------------------------- 1 | final: _: { 2 | catppuccin-catwalk = final.callPackage ( 3 | { 4 | lib, 5 | rustPlatform, 6 | installShellFiles, 7 | }: 8 | rustPlatform.buildRustPackage { 9 | pname = "catppuccin-catwalk"; 10 | inherit ((lib.importTOML ./Cargo.toml).package) version; 11 | 12 | src = lib.fileset.toSource { 13 | root = ./.; 14 | fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) ( 15 | lib.fileset.unions [ 16 | ./Cargo.toml 17 | ./Cargo.lock 18 | ./src 19 | ./LICENSE 20 | ] 21 | ); 22 | }; 23 | 24 | cargoLock.lockFile = ./Cargo.lock; 25 | 26 | nativeBuildInputs = [ installShellFiles ]; 27 | 28 | postInstall = '' 29 | installShellCompletion --cmd catwalk \ 30 | --bash <("$out/bin/catwalk" completion bash) \ 31 | --zsh <("$out/bin/catwalk" completion zsh) \ 32 | --fish <("$out/bin/catwalk" completion fish) 33 | ''; 34 | 35 | meta = { 36 | homepage = "https://github.com/catppuccin/catwalk"; 37 | description = "🚶 Soothing pastel previews for the high-spirited!"; 38 | license = lib.licenses.mit; 39 | mainProgram = "catwalk"; 40 | }; 41 | } 42 | ) { }; 43 | } 44 | -------------------------------------------------------------------------------- /pkg/deno/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Catppuccin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/deno/README.md: -------------------------------------------------------------------------------- 1 |

2 | Logo
3 | 4 | Catppuccin Catwalk 5 | 6 |

7 | 8 |

9 | 10 | 11 | 12 |

13 | 14 |   15 | 16 | > [!IMPORTANT] 17 | > This repository has been migrated from 18 | > [catppuccin/toolbox](https://github.com/catppuccin/toolbox/tree/main/catwalk). To view releases 19 | > prior to [v1.3.1](https://github.com/catppuccin/catwalk/releases/tag/v1.3.1), 20 | > see the [releases from catppuccin/toolbox](https://github.com/catppuccin/toolbox/releases?q=catwalk&expanded=true). 21 | 22 | ## Installation 23 | 24 | You can install Catwalk using one of the methods below: 25 | 26 | | Installation Method | Instructions | 27 | | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | 28 | | crates.io | `cargo install catppuccin-catwalk` | 29 | | Source | `cargo install --git https://github.com/catppuccin/catwalk catppuccin-catwalk` | 30 | | Homebrew | `brew install catppuccin/tap/catwalk` | 31 | | Nix | `nix profile install github:catppuccin/catwalk`
`nix run github:catppuccin/catwalk -- [OPTIONS] ` | 32 | | Binaries
(Windows, MacOS & Linux) | Available from the [latest GitHub release](https://github.com/catppuccin/catwalk/releases). | 33 | 34 | ## Usage 35 | 36 | ```console 37 | $ catwalk --help 38 | 🚶Soothing pastel previews for the high-spirited! 39 | 40 | Usage: catwalk [OPTIONS] [LATTE] [FRAPPE] [MACCHIATO] [MOCHA] [COMMAND] 41 | 42 | Commands: 43 | completion Generates a completion file for the given shell 44 | help Print this message or the help of the given subcommand(s) 45 | 46 | Arguments: 47 | [LATTE] Path to Latte image [default: ./latte.webp] 48 | [FRAPPE] Path to Frappé image [default: ./frappe.webp] 49 | [MACCHIATO] Path to Macchiato image [default: ./macchiato.webp] 50 | [MOCHA] Path to Mocha image [default: ./mocha.webp] 51 | 52 | Options: 53 | -o, --output Path to output file [default: ./preview.webp] 54 | -l, --layout Layout to use [default: composite] [possible values: composite, stacked, grid, row] 55 | -r, --radius Radius of rounded corners (percentage) 56 | -g, --gap Size of gaps between pictures for the `grid` layout 57 | -C, --directory Change to before processing files [default: .] 58 | --ext File extension to use for input files, if they're not explicitly named [default: webp] [possible values: webp, png] 59 | -h, --help Print help 60 | -V, --version Print version 61 | ``` 62 | 63 | ### Examples 64 | 65 | ```console 66 | $ catwalk latte.webp frappe.webp macchiato.webp mocha.webp --output catwalk.webp 67 | ``` 68 | 69 | ```console 70 | $ catwalk latte.png frappe.png macchiato.png mocha.png --directory ./assets/ 71 | ``` 72 | 73 |   74 | 75 |

76 |

Copyright © 2021-present Catppuccin Org 77 |

78 | -------------------------------------------------------------------------------- /pkg/deno/catwalk.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | */ 5 | export enum Layout { 6 | Composite = 0, 7 | Stacked = 1, 8 | Grid = 2, 9 | Row = 3, 10 | } 11 | /** 12 | */ 13 | export class Catwalk { 14 | free(): void; 15 | /** 16 | * @param {number | undefined} [aa_level] 17 | * @returns {Catwalk} 18 | */ 19 | aa_level(aa_level?: number): Catwalk; 20 | /** 21 | * @param {number | undefined} [gap] 22 | * @returns {Catwalk} 23 | */ 24 | gap(gap?: number): Catwalk; 25 | /** 26 | * @param {Layout | undefined} [layout] 27 | * @returns {Catwalk} 28 | */ 29 | layout(layout?: Layout): Catwalk; 30 | /** 31 | * Sets the radius of the rounding mask. 32 | * # Errors 33 | * Returns an error if the height or width are not set (automatically inferred from the `new` method arguments) 34 | * @param {number | undefined} [radius] 35 | * @returns {Catwalk} 36 | */ 37 | radius(radius?: number): Catwalk; 38 | /** 39 | * Create a new Catwalk from 4 `web_sys::ImageData` objects 40 | * # Errors 41 | * Returns an error if the images... 42 | * - cannot be read. 43 | * - are not the same size. 44 | * @param {ImageData} latte 45 | * @param {ImageData} frappe 46 | * @param {ImageData} macchiato 47 | * @param {ImageData} mocha 48 | * @returns {Catwalk} 49 | */ 50 | static new_from_imagedata(latte: ImageData, frappe: ImageData, macchiato: ImageData, mocha: ImageData): Catwalk; 51 | /** 52 | * Create a new Catwalk from 4 `Vec`, which are in practice `Vec<[u8; 4]>` (RGBA). 53 | * # Errors 54 | * Returns an error if the images... 55 | * - cannot be read. 56 | * - are not the same size. 57 | * @param {Uint8Array} latte 58 | * @param {Uint8Array} frappe 59 | * @param {Uint8Array} macchiato 60 | * @param {Uint8Array} mocha 61 | * @param {number} width 62 | * @returns {Catwalk} 63 | */ 64 | static new_from_u8_array(latte: Uint8Array, frappe: Uint8Array, macchiato: Uint8Array, mocha: Uint8Array, width: number): Catwalk; 65 | /** 66 | * Calculate the Catwalk image & return an `ImageData` object. 67 | * # Errors 68 | * Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 69 | * @returns {ImageData} 70 | */ 71 | build_imagedata(): ImageData; 72 | /** 73 | * Calculate the Catwalk image & return a `CatwalkBuffer` object. 74 | * # Errors 75 | * Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 76 | * @returns {CatwalkBuffer} 77 | */ 78 | build_buffer(): CatwalkBuffer; 79 | /** 80 | * Returns the version of the Catwalk library. 81 | */ 82 | static readonly version: string; 83 | } 84 | /** 85 | */ 86 | export class CatwalkBuffer { 87 | free(): void; 88 | /** 89 | */ 90 | readonly data: Uint8Array; 91 | /** 92 | */ 93 | height: number; 94 | /** 95 | */ 96 | width: number; 97 | } 98 | -------------------------------------------------------------------------------- /pkg/deno/catwalk.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const heap = new Array(128).fill(undefined); 4 | 5 | heap.push(undefined, null, true, false); 6 | 7 | function getObject(idx) { return heap[idx]; } 8 | 9 | let heap_next = heap.length; 10 | 11 | function dropObject(idx) { 12 | if (idx < 132) return; 13 | heap[idx] = heap_next; 14 | heap_next = idx; 15 | } 16 | 17 | function takeObject(idx) { 18 | const ret = getObject(idx); 19 | dropObject(idx); 20 | return ret; 21 | } 22 | 23 | const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); 24 | 25 | if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; 26 | 27 | let cachedUint8Memory0 = null; 28 | 29 | function getUint8Memory0() { 30 | if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { 31 | cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); 32 | } 33 | return cachedUint8Memory0; 34 | } 35 | 36 | function getStringFromWasm0(ptr, len) { 37 | ptr = ptr >>> 0; 38 | return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); 39 | } 40 | 41 | function addHeapObject(obj) { 42 | if (heap_next === heap.length) heap.push(heap.length + 1); 43 | const idx = heap_next; 44 | heap_next = heap[idx]; 45 | 46 | heap[idx] = obj; 47 | return idx; 48 | } 49 | 50 | function isLikeNone(x) { 51 | return x === undefined || x === null; 52 | } 53 | 54 | let cachedInt32Memory0 = null; 55 | 56 | function getInt32Memory0() { 57 | if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { 58 | cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); 59 | } 60 | return cachedInt32Memory0; 61 | } 62 | 63 | function getArrayU8FromWasm0(ptr, len) { 64 | ptr = ptr >>> 0; 65 | return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); 66 | } 67 | 68 | let WASM_VECTOR_LEN = 0; 69 | 70 | function passArray8ToWasm0(arg, malloc) { 71 | const ptr = malloc(arg.length * 1, 1) >>> 0; 72 | getUint8Memory0().set(arg, ptr / 1); 73 | WASM_VECTOR_LEN = arg.length; 74 | return ptr; 75 | } 76 | 77 | let cachedUint8ClampedMemory0 = null; 78 | 79 | function getUint8ClampedMemory0() { 80 | if (cachedUint8ClampedMemory0 === null || cachedUint8ClampedMemory0.byteLength === 0) { 81 | cachedUint8ClampedMemory0 = new Uint8ClampedArray(wasm.memory.buffer); 82 | } 83 | return cachedUint8ClampedMemory0; 84 | } 85 | 86 | function getClampedArrayU8FromWasm0(ptr, len) { 87 | ptr = ptr >>> 0; 88 | return getUint8ClampedMemory0().subarray(ptr / 1, ptr / 1 + len); 89 | } 90 | 91 | function handleError(f, args) { 92 | try { 93 | return f.apply(this, args); 94 | } catch (e) { 95 | wasm.__wbindgen_export_2(addHeapObject(e)); 96 | } 97 | } 98 | /** 99 | */ 100 | export const Layout = Object.freeze({ Composite:0,"0":"Composite",Stacked:1,"1":"Stacked",Grid:2,"2":"Grid",Row:3,"3":"Row", }); 101 | 102 | const CatwalkFinalization = (typeof FinalizationRegistry === 'undefined') 103 | ? { register: () => {}, unregister: () => {} } 104 | : new FinalizationRegistry(ptr => wasm.__wbg_catwalk_free(ptr >>> 0)); 105 | /** 106 | */ 107 | export class Catwalk { 108 | 109 | static __wrap(ptr) { 110 | ptr = ptr >>> 0; 111 | const obj = Object.create(Catwalk.prototype); 112 | obj.__wbg_ptr = ptr; 113 | CatwalkFinalization.register(obj, obj.__wbg_ptr, obj); 114 | return obj; 115 | } 116 | 117 | __destroy_into_raw() { 118 | const ptr = this.__wbg_ptr; 119 | this.__wbg_ptr = 0; 120 | CatwalkFinalization.unregister(this); 121 | return ptr; 122 | } 123 | 124 | free() { 125 | const ptr = this.__destroy_into_raw(); 126 | wasm.__wbg_catwalk_free(ptr); 127 | } 128 | /** 129 | * @param {number | undefined} [aa_level] 130 | * @returns {Catwalk} 131 | */ 132 | aa_level(aa_level) { 133 | const ptr = this.__destroy_into_raw(); 134 | const ret = wasm.catwalk_aa_level(ptr, !isLikeNone(aa_level), isLikeNone(aa_level) ? 0 : aa_level); 135 | return Catwalk.__wrap(ret); 136 | } 137 | /** 138 | * @param {number | undefined} [gap] 139 | * @returns {Catwalk} 140 | */ 141 | gap(gap) { 142 | const ptr = this.__destroy_into_raw(); 143 | const ret = wasm.catwalk_gap(ptr, !isLikeNone(gap), isLikeNone(gap) ? 0 : gap); 144 | return Catwalk.__wrap(ret); 145 | } 146 | /** 147 | * @param {Layout | undefined} [layout] 148 | * @returns {Catwalk} 149 | */ 150 | layout(layout) { 151 | const ptr = this.__destroy_into_raw(); 152 | const ret = wasm.catwalk_layout(ptr, isLikeNone(layout) ? 4 : layout); 153 | return Catwalk.__wrap(ret); 154 | } 155 | /** 156 | * Sets the radius of the rounding mask. 157 | * # Errors 158 | * Returns an error if the height or width are not set (automatically inferred from the `new` method arguments) 159 | * @param {number | undefined} [radius] 160 | * @returns {Catwalk} 161 | */ 162 | radius(radius) { 163 | try { 164 | const ptr = this.__destroy_into_raw(); 165 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 166 | wasm.catwalk_radius(retptr, ptr, !isLikeNone(radius), isLikeNone(radius) ? 0 : radius); 167 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 168 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 169 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 170 | if (r2) { 171 | throw takeObject(r1); 172 | } 173 | return Catwalk.__wrap(r0); 174 | } finally { 175 | wasm.__wbindgen_add_to_stack_pointer(16); 176 | } 177 | } 178 | /** 179 | * Create a new Catwalk from 4 `web_sys::ImageData` objects 180 | * # Errors 181 | * Returns an error if the images... 182 | * - cannot be read. 183 | * - are not the same size. 184 | * @param {ImageData} latte 185 | * @param {ImageData} frappe 186 | * @param {ImageData} macchiato 187 | * @param {ImageData} mocha 188 | * @returns {Catwalk} 189 | */ 190 | static new_from_imagedata(latte, frappe, macchiato, mocha) { 191 | try { 192 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 193 | wasm.catwalk_new_from_imagedata(retptr, addHeapObject(latte), addHeapObject(frappe), addHeapObject(macchiato), addHeapObject(mocha)); 194 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 195 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 196 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 197 | if (r2) { 198 | throw takeObject(r1); 199 | } 200 | return Catwalk.__wrap(r0); 201 | } finally { 202 | wasm.__wbindgen_add_to_stack_pointer(16); 203 | } 204 | } 205 | /** 206 | * Create a new Catwalk from 4 `Vec`, which are in practice `Vec<[u8; 4]>` (RGBA). 207 | * # Errors 208 | * Returns an error if the images... 209 | * - cannot be read. 210 | * - are not the same size. 211 | * @param {Uint8Array} latte 212 | * @param {Uint8Array} frappe 213 | * @param {Uint8Array} macchiato 214 | * @param {Uint8Array} mocha 215 | * @param {number} width 216 | * @returns {Catwalk} 217 | */ 218 | static new_from_u8_array(latte, frappe, macchiato, mocha, width) { 219 | try { 220 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 221 | const ptr0 = passArray8ToWasm0(latte, wasm.__wbindgen_export_1); 222 | const len0 = WASM_VECTOR_LEN; 223 | const ptr1 = passArray8ToWasm0(frappe, wasm.__wbindgen_export_1); 224 | const len1 = WASM_VECTOR_LEN; 225 | const ptr2 = passArray8ToWasm0(macchiato, wasm.__wbindgen_export_1); 226 | const len2 = WASM_VECTOR_LEN; 227 | const ptr3 = passArray8ToWasm0(mocha, wasm.__wbindgen_export_1); 228 | const len3 = WASM_VECTOR_LEN; 229 | wasm.catwalk_new_from_u8_array(retptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, width); 230 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 231 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 232 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 233 | if (r2) { 234 | throw takeObject(r1); 235 | } 236 | return Catwalk.__wrap(r0); 237 | } finally { 238 | wasm.__wbindgen_add_to_stack_pointer(16); 239 | } 240 | } 241 | /** 242 | * Calculate the Catwalk image & return an `ImageData` object. 243 | * # Errors 244 | * Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 245 | * @returns {ImageData} 246 | */ 247 | build_imagedata() { 248 | try { 249 | const ptr = this.__destroy_into_raw(); 250 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 251 | wasm.catwalk_build_imagedata(retptr, ptr); 252 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 253 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 254 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 255 | if (r2) { 256 | throw takeObject(r1); 257 | } 258 | return takeObject(r0); 259 | } finally { 260 | wasm.__wbindgen_add_to_stack_pointer(16); 261 | } 262 | } 263 | /** 264 | * Calculate the Catwalk image & return a `CatwalkBuffer` object. 265 | * # Errors 266 | * Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 267 | * @returns {CatwalkBuffer} 268 | */ 269 | build_buffer() { 270 | try { 271 | const ptr = this.__destroy_into_raw(); 272 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 273 | wasm.catwalk_build_buffer(retptr, ptr); 274 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 275 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 276 | var r2 = getInt32Memory0()[retptr / 4 + 2]; 277 | if (r2) { 278 | throw takeObject(r1); 279 | } 280 | return CatwalkBuffer.__wrap(r0); 281 | } finally { 282 | wasm.__wbindgen_add_to_stack_pointer(16); 283 | } 284 | } 285 | /** 286 | * Returns the version of the Catwalk library. 287 | * @returns {string} 288 | */ 289 | static get version() { 290 | let deferred1_0; 291 | let deferred1_1; 292 | try { 293 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 294 | wasm.catwalk_version(retptr); 295 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 296 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 297 | deferred1_0 = r0; 298 | deferred1_1 = r1; 299 | return getStringFromWasm0(r0, r1); 300 | } finally { 301 | wasm.__wbindgen_add_to_stack_pointer(16); 302 | wasm.__wbindgen_export_0(deferred1_0, deferred1_1, 1); 303 | } 304 | } 305 | } 306 | 307 | const CatwalkBufferFinalization = (typeof FinalizationRegistry === 'undefined') 308 | ? { register: () => {}, unregister: () => {} } 309 | : new FinalizationRegistry(ptr => wasm.__wbg_catwalkbuffer_free(ptr >>> 0)); 310 | /** 311 | */ 312 | export class CatwalkBuffer { 313 | 314 | static __wrap(ptr) { 315 | ptr = ptr >>> 0; 316 | const obj = Object.create(CatwalkBuffer.prototype); 317 | obj.__wbg_ptr = ptr; 318 | CatwalkBufferFinalization.register(obj, obj.__wbg_ptr, obj); 319 | return obj; 320 | } 321 | 322 | __destroy_into_raw() { 323 | const ptr = this.__wbg_ptr; 324 | this.__wbg_ptr = 0; 325 | CatwalkBufferFinalization.unregister(this); 326 | return ptr; 327 | } 328 | 329 | free() { 330 | const ptr = this.__destroy_into_raw(); 331 | wasm.__wbg_catwalkbuffer_free(ptr); 332 | } 333 | /** 334 | * @returns {number} 335 | */ 336 | get width() { 337 | const ret = wasm.__wbg_get_catwalkbuffer_width(this.__wbg_ptr); 338 | return ret >>> 0; 339 | } 340 | /** 341 | * @param {number} arg0 342 | */ 343 | set width(arg0) { 344 | wasm.__wbg_set_catwalkbuffer_width(this.__wbg_ptr, arg0); 345 | } 346 | /** 347 | * @returns {number} 348 | */ 349 | get height() { 350 | const ret = wasm.__wbg_get_catwalkbuffer_height(this.__wbg_ptr); 351 | return ret >>> 0; 352 | } 353 | /** 354 | * @param {number} arg0 355 | */ 356 | set height(arg0) { 357 | wasm.__wbg_set_catwalkbuffer_height(this.__wbg_ptr, arg0); 358 | } 359 | /** 360 | * @returns {Uint8Array} 361 | */ 362 | get data() { 363 | try { 364 | const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); 365 | wasm.catwalkbuffer_data(retptr, this.__wbg_ptr); 366 | var r0 = getInt32Memory0()[retptr / 4 + 0]; 367 | var r1 = getInt32Memory0()[retptr / 4 + 1]; 368 | var v1 = getArrayU8FromWasm0(r0, r1).slice(); 369 | wasm.__wbindgen_export_0(r0, r1 * 1, 1); 370 | return v1; 371 | } finally { 372 | wasm.__wbindgen_add_to_stack_pointer(16); 373 | } 374 | } 375 | } 376 | 377 | const imports = { 378 | __wbindgen_placeholder__: { 379 | __wbindgen_object_drop_ref: function(arg0) { 380 | takeObject(arg0); 381 | }, 382 | __wbindgen_string_new: function(arg0, arg1) { 383 | const ret = getStringFromWasm0(arg0, arg1); 384 | return addHeapObject(ret); 385 | }, 386 | __wbg_width_ddb5e7bb9fbdd107: function(arg0) { 387 | const ret = getObject(arg0).width; 388 | return ret; 389 | }, 390 | __wbg_height_2c4b892494a113f4: function(arg0) { 391 | const ret = getObject(arg0).height; 392 | return ret; 393 | }, 394 | __wbg_data_c02d3aac6da15e9f: function(arg0, arg1) { 395 | const ret = getObject(arg1).data; 396 | const ptr1 = passArray8ToWasm0(ret, wasm.__wbindgen_export_1); 397 | const len1 = WASM_VECTOR_LEN; 398 | getInt32Memory0()[arg0 / 4 + 1] = len1; 399 | getInt32Memory0()[arg0 / 4 + 0] = ptr1; 400 | }, 401 | __wbg_newwithu8clampedarray_ae824147b27925fc: function() { return handleError(function (arg0, arg1, arg2) { 402 | const ret = new ImageData(getClampedArrayU8FromWasm0(arg0, arg1), arg2 >>> 0); 403 | return addHeapObject(ret); 404 | }, arguments) }, 405 | __wbindgen_throw: function(arg0, arg1) { 406 | throw new Error(getStringFromWasm0(arg0, arg1)); 407 | }, 408 | }, 409 | 410 | }; 411 | 412 | const wasm_url = new URL('catwalk_bg.wasm', import.meta.url); 413 | let wasmCode = ''; 414 | switch (wasm_url.protocol) { 415 | case 'file:': 416 | wasmCode = await Deno.readFile(wasm_url); 417 | break 418 | case 'https:': 419 | case 'http:': 420 | wasmCode = await (await fetch(wasm_url)).arrayBuffer(); 421 | break 422 | default: 423 | throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); 424 | } 425 | 426 | const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; 427 | const wasm = wasmInstance.exports; 428 | 429 | -------------------------------------------------------------------------------- /pkg/deno/catwalk_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catppuccin/catwalk/c88c171490a1ba7f5ee85d28ba48efb659187ec2/pkg/deno/catwalk_bg.wasm -------------------------------------------------------------------------------- /pkg/deno/catwalk_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export const memory: WebAssembly.Memory; 4 | export function __wbg_catwalk_free(a: number): void; 5 | export function catwalk_aa_level(a: number, b: number, c: number): number; 6 | export function catwalk_gap(a: number, b: number, c: number): number; 7 | export function catwalk_layout(a: number, b: number): number; 8 | export function catwalk_radius(a: number, b: number, c: number, d: number): void; 9 | export function __wbg_catwalkbuffer_free(a: number): void; 10 | export function __wbg_get_catwalkbuffer_width(a: number): number; 11 | export function __wbg_set_catwalkbuffer_width(a: number, b: number): void; 12 | export function __wbg_get_catwalkbuffer_height(a: number): number; 13 | export function __wbg_set_catwalkbuffer_height(a: number, b: number): void; 14 | export function catwalkbuffer_data(a: number, b: number): void; 15 | export function catwalk_new_from_imagedata(a: number, b: number, c: number, d: number, e: number): void; 16 | export function catwalk_new_from_u8_array(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): void; 17 | export function catwalk_build_imagedata(a: number, b: number): void; 18 | export function catwalk_build_buffer(a: number, b: number): void; 19 | export function catwalk_version(a: number): void; 20 | export function __wbindgen_add_to_stack_pointer(a: number): number; 21 | export function __wbindgen_export_0(a: number, b: number, c: number): void; 22 | export function __wbindgen_export_1(a: number, b: number): number; 23 | export function __wbindgen_export_2(a: number): void; 24 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "last-release-sha": "99dfff5013c6f3b7aa874401279d8021a94edef6", 4 | "packages": { 5 | ".": { 6 | "package-name": "", 7 | "release-type": "rust" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>catppuccin/renovate-config", 5 | "github>Omochice/renovate-config:deno" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { 3 | inherit system; 4 | config = { }; 5 | overlays = [ ]; 6 | }, 7 | system ? builtins.currentSystem, 8 | catwalk ? import ./default.nix { inherit pkgs; }, 9 | }: 10 | pkgs.mkShell { 11 | inputsFrom = [ catwalk ]; 12 | 13 | packages = with pkgs; [ 14 | clippy 15 | rustfmt 16 | rust-analyzer 17 | 18 | # wasm support 19 | llvmPackages.bintools 20 | just 21 | deno 22 | nodejs 23 | wasm-pack 24 | ]; 25 | 26 | RUST_SRC_PATH = "${pkgs.rustPlatform.rustLibSrc}"; 27 | # NOTE: this is the secret sauce for wasm32 support 28 | CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_LINKER = "lld"; 29 | } 30 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::perf, clippy::nursery, clippy::pedantic)] 2 | use catppuccin_catwalk::Layout; 3 | use clap::{Command, Parser, Subcommand, ValueEnum}; 4 | use clap_complete::{generate, Generator, Shell}; 5 | use std::path::PathBuf; 6 | 7 | #[derive(Subcommand, Clone, Debug)] 8 | pub enum Commands { 9 | #[command(about = "Generates a completion file for the given shell")] 10 | Completion { 11 | #[arg(value_enum)] 12 | shell: Shell, 13 | }, 14 | } 15 | 16 | #[derive(Copy, Clone, Debug, ValueEnum)] 17 | pub enum Extension { 18 | Webp, 19 | Png, 20 | } 21 | 22 | #[derive(Parser)] 23 | #[command(author, version, about)] 24 | pub struct Cli { 25 | /// Path to Latte image. 26 | #[arg(default_value = "./latte.webp")] 27 | pub latte: Option, 28 | /// Path to Frappé image. 29 | #[arg(default_value = "./frappe.webp")] 30 | pub frappe: Option, 31 | /// Path to Macchiato image. 32 | #[arg(default_value = "./macchiato.webp")] 33 | pub macchiato: Option, 34 | /// Path to Mocha image. 35 | #[arg(default_value = "./mocha.webp")] 36 | pub mocha: Option, 37 | /// Path to output file. 38 | #[arg(short, long, default_value = "./preview.webp")] 39 | pub output: PathBuf, 40 | /// Layout to use. 41 | #[arg(short, long, value_enum, default_value_t=Layout::Composite)] 42 | pub layout: Layout, 43 | /// Radius of rounded corners (percentage). 44 | #[arg(short, long)] 45 | pub radius: Option, 46 | /// Size of gaps between pictures for the `grid` layout. 47 | #[arg(short, long)] 48 | pub gap: Option, 49 | /// Change to before processing files. 50 | #[arg(short = 'C', long, default_value = ".")] 51 | pub directory: Option, 52 | /// File extension to use for input files, if they're not explicitly named. 53 | #[arg(long = "ext", value_enum, default_value_t = Extension::Webp)] 54 | pub extension: Extension, 55 | 56 | // Shell completion 57 | #[command(subcommand)] 58 | pub command: Option, 59 | } 60 | 61 | pub fn print_completions(gen: G, cmd: &mut Command) { 62 | generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout()); 63 | } 64 | 65 | pub fn get_cli_arguments() -> Cli { 66 | Cli::parse() 67 | } 68 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::perf, clippy::nursery, clippy::pedantic)] 2 | // ignore u32 -> f32 & vice versa for now 3 | #![allow( 4 | clippy::cast_possible_truncation, 5 | clippy::cast_precision_loss, 6 | clippy::cast_sign_loss 7 | )] 8 | 9 | mod mask; 10 | use mask::{RoundMask, TrapMask}; 11 | use ril::prelude::*; 12 | 13 | #[cfg(target_family = "wasm")] 14 | use wasm_bindgen::prelude::*; 15 | #[cfg(target_family = "wasm")] 16 | mod wasm; 17 | 18 | #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] 19 | #[cfg_attr(not(target_family = "wasm"), derive(clap::ValueEnum))] 20 | #[cfg_attr(target_family = "wasm", wasm_bindgen)] 21 | pub enum Layout { 22 | Composite, 23 | Stacked, 24 | Grid, 25 | Row, 26 | } 27 | 28 | enum GridLayouts { 29 | Grid, 30 | Row, 31 | } 32 | 33 | #[derive(thiserror::Error, Debug)] 34 | pub enum CatwalkError { 35 | #[error("Images must be the same size")] 36 | SameSize, 37 | #[error("Builder missing argument.")] 38 | MissingArgument, 39 | #[error("Failed to read image from bytes")] 40 | ReadFromBytesError, 41 | #[error("Failed to encode image data")] 42 | EncodeError, 43 | 44 | #[error("unexpected error `{0}`")] 45 | JsError(String), 46 | #[error("unknown error from JS")] 47 | UnknownJsError, 48 | } 49 | 50 | #[cfg_attr(target_family = "wasm", wasm_bindgen)] 51 | pub struct Catwalk { 52 | images: Option<[Image; 4]>, 53 | height: Option, 54 | width: Option, 55 | aa_level: u32, 56 | radius: u32, 57 | gap: u32, 58 | layout: Layout, 59 | } 60 | 61 | impl Default for Catwalk { 62 | fn default() -> Self { 63 | Self { 64 | images: None, 65 | height: None, 66 | width: None, 67 | aa_level: 15, 68 | radius: 3, 69 | gap: 50, 70 | layout: Layout::Composite, 71 | } 72 | } 73 | } 74 | 75 | #[cfg_attr(target_family = "wasm", wasm_bindgen)] 76 | impl Catwalk { 77 | /// Creates a new instance of Catwalk. 78 | /// # Errors 79 | /// Returns an error if the images are not the same size. 80 | #[cfg(not(target_family = "wasm"))] 81 | pub fn new(images: [Image; 4]) -> Result { 82 | let height = images[0].height(); 83 | let width = images[0].width(); 84 | 85 | // verify that they're all the same size 86 | if images 87 | .iter() 88 | .any(|x| x.height() != height || x.width() != width) 89 | { 90 | return Err(CatwalkError::SameSize); 91 | }; 92 | 93 | Ok(Self { 94 | images: Some(images), 95 | height: Some(height), 96 | width: Some(width), 97 | ..Default::default() 98 | }) 99 | } 100 | 101 | #[must_use] 102 | #[allow(clippy::missing_const_for_fn)] 103 | pub fn aa_level(mut self, aa_level: Option) -> Self { 104 | let Some(aa_level) = aa_level else { 105 | return self; 106 | }; 107 | self.aa_level = aa_level; 108 | self 109 | } 110 | 111 | #[allow(clippy::missing_const_for_fn)] 112 | #[must_use] 113 | pub fn gap(mut self, gap: Option) -> Self { 114 | let Some(gap) = gap else { 115 | return self; 116 | }; 117 | self.gap = gap; 118 | self 119 | } 120 | 121 | #[must_use] 122 | pub fn layout(mut self, layout: Option) -> Self { 123 | let layout = layout.unwrap_or(self.layout); 124 | self.layout = layout; 125 | self 126 | } 127 | 128 | /// Sets the radius of the rounding mask. 129 | /// # Errors 130 | /// Returns an error if the height or width are not set (automatically inferred from the `new` method arguments) 131 | #[allow(clippy::use_self)] 132 | pub fn radius(mut self, radius: Option) -> Result { 133 | let radius = radius.unwrap_or(self.radius); 134 | 135 | let height = self.height.ok_or(CatwalkError::MissingArgument)?; 136 | let width = self.width.ok_or(CatwalkError::MissingArgument)?; 137 | 138 | // radius as a percentage of the image width if height > width, vice versa 139 | let radius = if height > width { 140 | (width as f32 * (radius as f32 / 100.0)) as u32 141 | } else { 142 | (height as f32 * (radius as f32 / 100.0)) as u32 143 | }; 144 | 145 | self.radius = radius; 146 | Ok(self) 147 | } 148 | 149 | fn prepare(self) -> Result { 150 | let images = self.images.ok_or(CatwalkError::MissingArgument)?; 151 | let height = self.height.ok_or(CatwalkError::MissingArgument)?; 152 | let width = self.width.ok_or(CatwalkError::MissingArgument)?; 153 | 154 | let rounding_mask = RoundMask { 155 | radius: self.radius, 156 | aa_level: self.aa_level, 157 | }; 158 | 159 | Ok(Magic { 160 | images, 161 | height, 162 | width, 163 | rounding_mask, 164 | gap: self.gap, 165 | layout: self.layout, 166 | }) 167 | } 168 | 169 | /// Calculates the catwalk image. 170 | /// # Errors 171 | /// Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 172 | #[cfg(not(target_family = "wasm"))] 173 | pub fn build(self) -> Result, CatwalkError> { 174 | let catwalk = self.prepare()?; 175 | Ok(catwalk.result()) 176 | } 177 | } 178 | 179 | pub struct Magic { 180 | images: [Image; 4], 181 | height: u32, 182 | width: u32, 183 | rounding_mask: RoundMask, 184 | gap: u32, 185 | layout: Layout, 186 | } 187 | 188 | impl Magic { 189 | /// Creates the slants image. 190 | fn gen_composite(&self) -> Image { 191 | let w = self.width as f32; 192 | let h = self.height as f32; 193 | // Use x/y to "ground" the point later on 194 | let inverse_slope = -w / (4.0 * h); 195 | let masked: Vec> = self 196 | .images 197 | .iter() 198 | .enumerate() 199 | .rev() 200 | .map(|(i, x)| Self::gen_mask(w, i, 2, inverse_slope).mask(x)) 201 | .collect(); 202 | let mut result = Image::new(self.width, self.height, Rgba::default()) 203 | .with_overlay_mode(OverlayMode::Merge); 204 | for mask in masked.iter().as_ref() { 205 | result.paste(0, 0, mask); 206 | } 207 | self.rounding_mask.mask(&result) 208 | } 209 | 210 | /// Creates a stacked image. 211 | fn gen_stacked(&self) -> Image { 212 | let gap = self.height / 3; 213 | let padding_x = f32::floor(3.0f32.mul_add(1.0 - gap as f32, self.width as f32)) as u32; 214 | let mut result = Image::new( 215 | (self.height * 2) + (padding_x * 3) + gap, 216 | self.height * 2, 217 | Rgba::default(), 218 | ) 219 | .with_overlay_mode(OverlayMode::Merge); 220 | self.images 221 | .iter() 222 | .map(|x| self.rounding_mask.mask(x)) 223 | .enumerate() 224 | .for_each(|(i, x)| result.paste(padding_x + (gap * (i as u32)), gap * (i as u32), &x)); 225 | result 226 | } 227 | 228 | /// Creates a grid image. 229 | fn gen_grid(&self, layout: &GridLayouts) -> Image { 230 | // Round images 231 | let gap = self.gap; 232 | let rounded: Vec> = self 233 | .images 234 | .iter() 235 | .map(|x| self.rounding_mask.mask(x)) 236 | .collect(); 237 | 238 | // Create final 239 | let mut result = match layout { 240 | GridLayouts::Grid => Image::new( 241 | (self.width * 2) + (gap * 3), 242 | (self.height * 2) + (gap * 3), 243 | Rgba::transparent(), 244 | ), 245 | GridLayouts::Row => Image::new( 246 | (self.width * 4) + (gap * 5), 247 | self.height + (gap * 2), 248 | Rgba::transparent(), 249 | ), 250 | } 251 | .with_overlay_mode(OverlayMode::Merge); 252 | // calculate the top left coordinates for each image, and paste 253 | rounded.iter().enumerate().for_each(|(i, img)| { 254 | let x = match layout { 255 | GridLayouts::Row => i % 4, 256 | GridLayouts::Grid => i % 2, 257 | }; 258 | let y = match layout { 259 | GridLayouts::Row => 0, 260 | GridLayouts::Grid => i / 2, 261 | }; 262 | result.paste( 263 | gap + (self.width + gap) * x as u32, 264 | gap + (self.height + gap) * y as u32, 265 | img, 266 | ); 267 | }); 268 | result 269 | } 270 | /// Generates a mask for the given image. 271 | fn gen_mask(w: f32, index: usize, aa_level: u32, inverse_slope: f32) -> TrapMask { 272 | if index == 3 { 273 | // Full mask 274 | return TrapMask::new(None, 0.0, aa_level); 275 | } 276 | let i = index as f32; 277 | let trap_top: f32 = w * i.mul_add(2.0, 3.0) / 8.0; 278 | // Return trapezoid mask 279 | // We only need to return bottom x here; we'll use the inverse slope to make a line 280 | TrapMask::new(Some(trap_top), inverse_slope, aa_level) 281 | } 282 | 283 | // this looks a bit odd because the WASM bindings use this as well, so 284 | // `result()` isn't just an oversight. 285 | fn process(self) -> Image { 286 | match self.layout { 287 | Layout::Composite => self.gen_composite(), 288 | Layout::Stacked => self.gen_stacked(), 289 | Layout::Grid => self.gen_grid(&GridLayouts::Grid), 290 | Layout::Row => self.gen_grid(&GridLayouts::Row), 291 | } 292 | } 293 | 294 | #[cfg(not(target_family = "wasm"))] 295 | #[must_use] 296 | pub fn result(self) -> Image { 297 | self.process() 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::perf, clippy::nursery, clippy::pedantic)] 2 | mod cli; 3 | 4 | use catppuccin_catwalk::Catwalk; 5 | use clap::CommandFactory; 6 | use cli::{get_cli_arguments, print_completions, Cli, Commands, Extension}; 7 | use color_eyre::{eyre::eyre, Result}; 8 | use ril::prelude::*; 9 | use std::io::Cursor; 10 | 11 | macro_rules! open_image { 12 | ($path:expr, $args:expr) => {{ 13 | let mut rel_path = $args.directory.clone().unwrap_or_default(); 14 | let path = $path.unwrap_or_default(); 15 | rel_path.push(path.clone()); 16 | // set the `--ext` file extension unless the filenames are explicitly given 17 | if path == std::path::PathBuf::default() { 18 | match $args.extension { 19 | Extension::Webp => { 20 | rel_path.set_extension("webp"); 21 | } 22 | Extension::Png => { 23 | rel_path.set_extension("png"); 24 | } 25 | } 26 | } 27 | Image::::open(&rel_path) 28 | .map_or(Err(eyre!("Failed to open `{}`", &rel_path.display())), Ok)? 29 | }}; 30 | } 31 | 32 | fn main() -> Result<()> { 33 | color_eyre::config::HookBuilder::default() 34 | .panic_section(format!( 35 | "Consider reporting this issue to {}", 36 | env!("CARGO_PKG_REPOSITORY") 37 | )) 38 | .display_env_section(false) 39 | .install()?; 40 | 41 | let args = get_cli_arguments(); 42 | 43 | if let Some(generator) = args.command { 44 | return match generator { 45 | Commands::Completion { shell } => { 46 | let mut cmd = Cli::command(); 47 | eprintln!("Generating completion file for {generator:?}..."); 48 | print_completions(shell, &mut cmd); 49 | Ok(()) 50 | } 51 | }; 52 | } 53 | 54 | let catwalk = Catwalk::new([ 55 | open_image!(args.latte, args), 56 | open_image!(args.frappe, args), 57 | open_image!(args.macchiato, args), 58 | open_image!(args.mocha, args), 59 | ])? 60 | .gap(args.gap) 61 | .layout(Some(args.layout)) 62 | .radius(args.radius)? 63 | .build()?; 64 | 65 | let mut writebuf = Cursor::new(Vec::new()); 66 | match args.output.extension() { 67 | None => return Err(eyre!("Output file type could not be determined")), 68 | Some(os_str) => match os_str.to_str() { 69 | Some("png") => { 70 | use ril::encodings::png::PngEncoder; 71 | 72 | PngEncoder::encode_static(&catwalk, &mut writebuf)?; 73 | } 74 | Some("webp") => { 75 | use ril::encodings::webp::{WebPEncoderOptions, WebPStaticEncoder}; 76 | 77 | let opt = WebPEncoderOptions::new().with_lossless(true); 78 | let meta = EncoderMetadata::::from(&catwalk).with_config(opt); 79 | let mut encoder = WebPStaticEncoder::new(&mut writebuf, meta)?; 80 | encoder.add_frame(&catwalk)?; 81 | } 82 | _ => return Err(eyre!("Output file type not supported")), 83 | }, 84 | } 85 | 86 | let output = if args.directory.is_some() { 87 | let mut path = args.directory.clone().unwrap_or_default(); 88 | if args.output.is_absolute() { 89 | args.output 90 | } else { 91 | path.push(args.output); 92 | path 93 | } 94 | } else { 95 | args.output 96 | }; 97 | 98 | std::fs::write(output, writebuf.get_ref()).map_err(|e| eyre!("Failed to write image: {}", e)) 99 | } 100 | -------------------------------------------------------------------------------- /src/mask.rs: -------------------------------------------------------------------------------- 1 | // all of the stuff is named ...Mask here 2 | #![allow(clippy::module_name_repetitions)] 3 | 4 | use ril::prelude::*; 5 | 6 | enum MaskType { 7 | Full, 8 | Partial(f32, f32), 9 | } 10 | 11 | pub struct TrapMask { 12 | vertices: MaskType, 13 | aa_level: u32, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct RoundMask { 18 | pub radius: u32, 19 | pub aa_level: u32, 20 | } 21 | 22 | impl RoundMask { 23 | /// Applies a round mask on an object 24 | pub fn mask(&self, image: &Image) -> Image { 25 | // Save us some work 26 | if self.radius == 0 { 27 | return image.clone(); 28 | } 29 | let h = image.height(); 30 | let w = image.width(); 31 | let r = self.radius; 32 | // Inner corners 33 | let corners = [(r, r), (w - r, r), (w - r, h - r), (r, h - r)]; 34 | Image::from_fn(w, h, |x, y| { 35 | if ((x <= r) || (x >= corners[2].0)) && ((y <= r) || (y >= corners[2].1)) { 36 | // y is in corner squares 37 | let distances = corners.iter().map(|c| Self::get_dis((x, y), c.to_owned())); 38 | if distances 39 | .clone() 40 | .map(|c| c <= r.pow(2)) 41 | .collect::>() 42 | == vec![false, false, false, false] 43 | { 44 | // y is not in mask 45 | let diffs: Vec = distances.map(|dis| dis - r.pow(2)).collect(); 46 | for diff in diffs { 47 | if diff <= self.aa_level.pow(2) { 48 | // Fraction of opacity 49 | let alpha = f32::from(image.pixel(x, y).alpha()) / 255.0; 50 | let frac: f32 = alpha - (diff as f32 / self.aa_level.pow(2) as f32); 51 | return image.pixel(x, y).with_alpha((255.0 * frac) as u8); 52 | } 53 | } 54 | return Rgba::transparent(); 55 | } 56 | } 57 | *image.pixel(x, y) 58 | }) 59 | } 60 | 61 | /// Gets distance between two points 62 | const fn get_dis(p1: (u32, u32), p2: (u32, u32)) -> u32 { 63 | u32::abs_diff(p1.0, p2.0).pow(2) + u32::abs_diff(p1.1, p2.1).pow(2) //<= r.pow(2) 64 | } 65 | } 66 | 67 | impl TrapMask { 68 | /// Construct a new shape. 69 | pub fn new(vertex: Option, inverse_slope: f32, aa_level: u32) -> Self { 70 | Self { 71 | vertices: vertex.map_or(MaskType::Full, |v| MaskType::Partial(v, inverse_slope)), 72 | aa_level, 73 | } 74 | } 75 | 76 | /// Apply mask onto given image 77 | pub fn mask(&self, image: &Image) -> Image { 78 | match &self.vertices { 79 | MaskType::Full => image.clone(), 80 | MaskType::Partial(v, inverse_slope) => { 81 | let w = image.width(); 82 | let h = image.height(); 83 | Image::from_fn(w, h, |x, y| { 84 | let gpos = (x as f32) - ((y as f32) * inverse_slope); 85 | if gpos <= *v { 86 | *image.pixel(x, y) 87 | } else { 88 | // Not in mask 89 | let diff: f32 = gpos - v; 90 | if diff <= self.aa_level as f32 { 91 | // Fraction of opacity 92 | let alpha = f32::from(image.pixel(x, y).alpha()) / 255.0; 93 | let frac = alpha - (diff / (self.aa_level as f32)); 94 | return image.pixel(x, y).with_alpha((255.0 * frac) as u8); 95 | } 96 | Rgba::transparent() 97 | } 98 | }) 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | // needed for WASM 2 | #![allow(clippy::use_self, clippy::missing_const_for_fn)] 3 | 4 | use crate::{Catwalk, CatwalkError, Magic}; 5 | use ril::{Image, Rgba}; 6 | use wasm_bindgen::{prelude::*, Clamped, JsValue}; 7 | use web_sys::ImageData; 8 | 9 | impl From for JsValue { 10 | fn from(err: CatwalkError) -> Self { 11 | JsValue::from_str(&err.to_string()) 12 | } 13 | } 14 | 15 | #[wasm_bindgen] 16 | #[allow(dead_code)] 17 | pub struct CatwalkBuffer { 18 | pub width: u32, 19 | pub height: u32, 20 | data: Vec, 21 | } 22 | 23 | #[wasm_bindgen] 24 | impl CatwalkBuffer { 25 | #[wasm_bindgen(getter)] 26 | pub fn data(&self) -> Vec { 27 | self.data.clone() 28 | } 29 | } 30 | 31 | #[wasm_bindgen] 32 | impl Catwalk { 33 | /// Create a new Catwalk from 4 `web_sys::ImageData` objects 34 | /// # Errors 35 | /// Returns an error if the images... 36 | /// - cannot be read. 37 | /// - are not the same size. 38 | pub fn new_from_imagedata( 39 | latte: ImageData, 40 | frappe: ImageData, 41 | macchiato: ImageData, 42 | mocha: ImageData, 43 | ) -> Result { 44 | let images: [Image; 4] = [latte, frappe, macchiato, mocha] 45 | .into_iter() 46 | .map(|img| { 47 | let data = img.data().0; 48 | Image::::from_fn(img.width(), img.height(), |x, y| { 49 | let i: usize = (y * img.width() + x) as usize * 4; 50 | Rgba::new(data[i], data[i + 1], data[i + 2], data[i + 3]) 51 | }) 52 | }) 53 | .collect::>>() 54 | .try_into() 55 | .map_err(|_| CatwalkError::ReadFromBytesError)?; 56 | 57 | let height = images[0].height(); 58 | let width = images[0].width(); 59 | 60 | // verify that they're all the same size 61 | if images 62 | .iter() 63 | .any(|x| x.height() != height || x.width() != width) 64 | { 65 | return Err(CatwalkError::SameSize); 66 | }; 67 | 68 | Ok(Self { 69 | images: Some(images), 70 | height: Some(height), 71 | width: Some(width), 72 | ..Default::default() 73 | }) 74 | } 75 | 76 | /// Create a new Catwalk from 4 `Vec`, which are in practice `Vec<[u8; 4]>` (RGBA). 77 | /// # Errors 78 | /// Returns an error if the images... 79 | /// - cannot be read. 80 | /// - are not the same size. 81 | pub fn new_from_u8_array( 82 | latte: Vec, 83 | frappe: Vec, 84 | macchiato: Vec, 85 | mocha: Vec, 86 | width: u32, 87 | ) -> Result { 88 | let images: [Image; 4] = [latte, frappe, macchiato, mocha] 89 | .into_iter() 90 | .map(|data| { 91 | let len = data.len() as u32; 92 | let height = len / (width * 4); 93 | Image::::from_fn(width, height, |x, y| { 94 | let i: usize = (y * width + x) as usize * 4; 95 | Rgba::new(data[i], data[i + 1], data[i + 2], data[i + 3]) 96 | }) 97 | }) 98 | .collect::>>() 99 | .try_into() 100 | .map_err(|_| CatwalkError::ReadFromBytesError)?; 101 | 102 | let height = images[0].height(); 103 | let width = images[0].width(); 104 | 105 | // verify that they're all the same size 106 | if images 107 | .iter() 108 | .any(|x| x.height() != height || x.width() != width) 109 | { 110 | return Err(CatwalkError::SameSize); 111 | }; 112 | 113 | Ok(Self { 114 | images: Some(images), 115 | height: Some(height), 116 | width: Some(width), 117 | ..Default::default() 118 | }) 119 | } 120 | 121 | /// Calculate the Catwalk image & return an `ImageData` object. 122 | /// # Errors 123 | /// Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 124 | pub fn build_imagedata(self) -> Result { 125 | self.prepare()?.result() 126 | } 127 | 128 | /// Calculate the Catwalk image & return a `CatwalkBuffer` object. 129 | /// # Errors 130 | /// Returns an error if any of `self.images`, `self.height`, or `self.width` are not set. 131 | pub fn build_buffer(self) -> Result { 132 | Ok(self.prepare()?.result_buffer()) 133 | } 134 | 135 | /// Returns the version of the Catwalk library. 136 | #[must_use] 137 | #[wasm_bindgen(getter)] 138 | pub fn version() -> String { 139 | env!("CARGO_PKG_VERSION").to_string() 140 | } 141 | } 142 | 143 | impl Magic { 144 | /// Calculate the Catwalk image & return an `ImageData` object. 145 | /// # Errors 146 | /// Returns an error if the `ImageData` cannot be created. 147 | pub fn result(self) -> Result { 148 | let img = self.process(); 149 | let width = img.width(); 150 | let data = img 151 | .data 152 | .iter() 153 | .flat_map(|rgba| vec![rgba.r, rgba.g, rgba.b, rgba.a]) 154 | .collect::>(); 155 | ImageData::new_with_u8_clamped_array(Clamped(&data), width) 156 | } 157 | 158 | #[must_use] 159 | pub fn result_buffer(self) -> CatwalkBuffer { 160 | let img = self.process(); 161 | let width = img.width(); 162 | let height = img.height(); 163 | // collect a Vec from the rgba pixels 164 | let data: Vec = img 165 | .data 166 | .into_iter() 167 | .flat_map(|rgba| vec![rgba.r, rgba.g, rgba.b, rgba.a]) 168 | .collect(); 169 | CatwalkBuffer { 170 | width, 171 | height, 172 | data, 173 | } 174 | } 175 | } 176 | --------------------------------------------------------------------------------