| 1 | <div align="center"> |
| 2 | <img src="docs/logo.svg" width="360" alt="remage"> |
| 3 | <p>A keyboard-centric image viewer for Linux. Fast, quiet and vim-shaped.</p> |
| 4 | |
| 5 | [](https://github.com/krolyxon/remage/actions/workflows/ci.yml) |
| 6 | [](https://github.com/krolyxon/remage/releases/latest) |
| 7 | [](https://github.com/krolyxon/remage/releases) |
| 8 | [](https://github.com/krolyxon/remage) |
| 9 | [](src) |
| 10 | [](LICENSE) |
| 11 | [-333333?style=flat-square&labelColor=000000)](#install) |
| 12 | [](https://wgpu.rs) |
| 13 | [](https://github.com/krolyxon/remage/commits) |
| 14 | |
| 15 | </div> |
| 16 | |
| 17 | remage aims for nsxiv's feature set with vim-style control and a Nothing OS look. It draws with the |
| 18 | GPU, so zooming, panning and scrolling stay smooth, and it uses no CPU at all while the picture |
| 19 | stands still. It can take its colours from your wallpaper through matugen or pywal, and you script |
| 20 | it with plain shell commands and an IPC socket. |
| 21 | |
| 22 | <p align="center"> |
| 23 | <img src="docs/screenshots/image.jpg" width="820" alt="remage showing a painting of a torii gate under cherry blossoms, with the filename on the left of the status bar and format, size, zoom and position on the right"> |
| 24 | </p> |
| 25 | |
| 26 | <table> |
| 27 | <tr> |
| 28 | <td width="50%"><img src="docs/screenshots/grid.jpg" alt="The thumbnail grid with three marked images and the cursor on one"></td> |
| 29 | <td width="50%"><img src="docs/screenshots/palette.jpg" alt="The command palette fuzzy-matching 'fit' over actions and their keys"></td> |
| 30 | </tr> |
| 31 | <tr> |
| 32 | <td align="center"><sub>Thumbnail grid with marks</sub></td> |
| 33 | <td align="center"><sub><code>:</code> command palette</sub></td> |
| 34 | </tr> |
| 35 | </table> |
| 36 | |
| 37 | ----- |
| 38 | |
| 39 | ## Features |
| 40 | |
| 41 | - **Fast.** The first image is on screen in about 90 ms. Neighbours are decoded and uploaded ahead |
| 42 | of time, so `n` and `p` take a single frame. Rendering happens only on demand. |
| 43 | - **Keyboard first.** Vim key notation, sequences (`gg`, `<Space>w`) and counts (`5n`, `12G`). |
| 44 | Every feature is an action you can bind, run from the `:` palette or send over IPC. Which-key |
| 45 | shows what a pending prefix can do, and `?` lists the live keymap. |
| 46 | - **Thumbnail grid.** Shares the freedesktop thumbnail cache with your file manager, so thumbnails |
| 47 | are often there already. Visual mode marks ranges. |
| 48 | - **Many formats.** JPEG, PNG/APNG, GIF, WebP, JPEG XL, AVIF, HEIC, SVG, TIFF (multi-page), camera |
| 49 | RAW, OpenEXR/HDR and more. Animations play with their own timing. ICC colour profiles and EXIF |
| 50 | orientation are applied, and images larger than the GPU's texture limit are tiled. Anything |
| 51 | else can go through an external decoder such as ImageMagick. |
| 52 | - **Scripting.** User commands replace nsxiv's key-handler: placeholders, marks as targets, inline |
| 53 | confirmation and output fed back as actions. An old key-handler works unchanged. |
| 54 | - **IPC.** `remage msg next`, `remage msg --query marked` and `remage msg --subscribe image` from |
| 55 | Hyprland binds or scripts. |
| 56 | - **Themeable.** Three colours are enough, and the rest are derived. matugen and pywal recolour |
| 57 | open windows live when the wallpaper changes. |
| 58 | - **Configurable.** One TOML file with live reload, `file:line:col` errors and "did you mean" |
| 59 | suggestions. |
| 60 | |
| 61 | ## Install |
| 62 | |
| 63 | ### Prebuilt binary |
| 64 | |
| 65 | Each [release](https://github.com/krolyxon/remage/releases/latest) has an x86_64 tarball built on |
| 66 | Arch Linux. It needs `dav1d` and `libheif` installed and suits Arch and other rolling distros; on |
| 67 | anything else, build from source. |
| 68 | |
| 69 | ```bash |
| 70 | v=0.1.0 |
| 71 | curl -LO https://github.com/krolyxon/remage/releases/download/v$v/remage-v$v-x86_64-linux.tar.gz |
| 72 | tar xf remage-v$v-x86_64-linux.tar.gz && cd remage-v$v-x86_64-linux |
| 73 | install -Dm755 remage ~/.local/bin/remage |
| 74 | install -Dm644 remage.1 ~/.local/share/man/man1/remage.1 |
| 75 | ``` |
| 76 | |
| 77 | The tarball also holds zsh/bash/fish completions, the desktop file and the example config. |
| 78 | |
| 79 | ### Arch Linux |
| 80 | |
| 81 | A `remage-git` PKGBUILD lives in [`contrib/PKGBUILD`](contrib/PKGBUILD): |
| 82 | |
| 83 | ```bash |
| 84 | git clone https://github.com/krolyxon/remage |
| 85 | cd remage/contrib |
| 86 | makepkg -si |
| 87 | ``` |
| 88 | |
| 89 | It installs the binary, the man page, zsh/bash/fish completions and a desktop file. |
| 90 | |
| 91 | ### From source |
| 92 | |
| 93 | You need stable Rust (1.96 or newer). AVIF and HEIC use two system libraries: |
| 94 | |
| 95 | ```bash |
| 96 | sudo pacman -S dav1d libheif # Arch |
| 97 | sudo apt install libdav1d-dev libheif-dev # Debian/Ubuntu (libheif ≥ 1.17) |
| 98 | |
| 99 | cargo install --locked --git https://github.com/krolyxon/remage |
| 100 | ``` |
| 101 | |
| 102 | Without those libraries, build with the pure-Rust formats only: |
| 103 | |
| 104 | ```bash |
| 105 | cargo install --locked --git https://github.com/krolyxon/remage --no-default-features --features jxl,svg,raw |
| 106 | ``` |
| 107 | |
| 108 | Shell completions and the man page are generated by the binary: |
| 109 | |
| 110 | ```bash |
| 111 | remage completions zsh > ~/.local/share/zsh/site-functions/_remage # also bash, fish, elvish |
| 112 | remage man > ~/.local/share/man/man1/remage.1 |
| 113 | ``` |
| 114 | |
| 115 | ## Usage |
| 116 | |
| 117 | ```bash |
| 118 | remage # the current directory |
| 119 | remage photo.jpg # photo.jpg and the rest of its directory |
| 120 | remage -rt ~/Pictures # a whole tree, starting in the grid |
| 121 | remage -S=3 wallpapers/ # slideshow, 3 seconds per image |
| 122 | find . -name '*.png' | remage -i -o > picked.txt # pick images, print the marked ones on exit |
| 123 | remage --dump-config # the complete commented default config |
| 124 | ``` |
| 125 | |
| 126 | `remage --help` lists every option, and `man remage` covers keys, actions and IPC. |
| 127 | |
| 128 | ## Keys |
| 129 | |
| 130 | The defaults below can all be changed. `?` shows the live keymap inside remage. |
| 131 | |
| 132 | | | Keys | |
| 133 | |---|---| |
| 134 | | **Everywhere** | `n`/`p` next/previous · `[`/`]` ±10 · `gg`/`G`/`12G` first/last/goto · `<CR>` image ⇄ grid · `m` mark · `M` invert marks · `u` unmark all · `N`/`P` next/previous mark · `f` fullscreen · `b` status bar · `i` info · `s` slideshow · `r` reload · `D` remove from queue · `:` palette · `/` search · `?` help · `q` quit | |
| 135 | | **Image** | `hjkl` pan · `HJKL` jump to edge · `+`/`-`/`=` zoom in/out/100% · `w`/`W`/`e`/`E` fit/fill/width/height · `z` centre · `<`/`>` rotate · `\|`/`_` flip · `a` pixel filter · `A` transparency background · `{`/`}` gamma · `<C-Space>` play/pause · `<C-n>`/`<C-p>` frames · wheel zooms at the cursor, drag pans | |
| 136 | | **Grid** | `hjkl` move · `<C-d>`/`<C-u>` half page · `<C-f>`/`<C-b>` page · `0`/`$` row start/end · `+`/`-` thumbnail size · `v` visual mode, then `m` to mark the range · click selects, double-click opens | |
| 137 | |
| 138 | ## Configuration |
| 139 | |
| 140 | remage reads `~/.config/remage/config.toml`. The file is merged over the built-in defaults, so put |
| 141 | in only what you change. Edits apply as soon as you save, and `remage --check-config` validates |
| 142 | the file. [`contrib/config.example.toml`](contrib/config.example.toml) is a good start. |
| 143 | |
| 144 | ```toml |
| 145 | [image] |
| 146 | fit = "down" # down | fit | fill | width | height | original |
| 147 | alpha_background = "dots" # dots | checker | solid |
| 148 | |
| 149 | [grid] |
| 150 | size = 160 |
| 151 | |
| 152 | [keys.image] |
| 153 | "<C-ScrollUp>" = "next" |
| 154 | "D" = "none" # remove a default binding |
| 155 | ``` |
| 156 | |
| 157 | ### Commands |
| 158 | |
| 159 | Commands run asynchronously and act on the marked files, or on the current one when nothing is |
| 160 | marked. Bind them as `run <name>`; unbound ones still show up in the `:` palette. |
| 161 | |
| 162 | ```toml |
| 163 | [commands.delete] |
| 164 | desc = "Delete files" |
| 165 | run = ["rm", "--", "{files}"] # array: run directly, {files} becomes one arg per file |
| 166 | confirm = "Delete {count} file(s)?" # asks inline in the status bar: y / n |
| 167 | |
| 168 | [commands.copy] |
| 169 | desc = "Copy image" |
| 170 | run = "wl-copy --type image/png < {file}" # string: run through sh, placeholders are quoted |
| 171 | |
| 172 | [commands.legacy] # an nsxiv key-handler works as it is |
| 173 | run = ["~/.config/nsxiv/exec/key-handler", "w"] |
| 174 | stdin = "files" |
| 175 | |
| 176 | [keys.global] |
| 177 | "<Space>d" = "run delete" |
| 178 | "<Space>c" = "run copy" |
| 179 | ``` |
| 180 | |
| 181 | After a command succeeds, remage re-checks the files it touched by default. Deleted files leave |
| 182 | the queue and edited ones are decoded again, so delete and rotate scripts need no extra setup. |
| 183 | With `output = "actions"`, each line the command prints runs as an action. |
| 184 | |
| 185 | ### Theming |
| 186 | |
| 187 | `nothing-dark` (the default) and `nothing-light` are built in. Any theme needs only `background`, |
| 188 | `foreground` and `accent`; the other tokens are derived. |
| 189 | |
| 190 | - **matugen:** copy [`contrib/matugen/remage.toml`](contrib/matugen/remage.toml) into your matugen |
| 191 | templates (the file says how), then add `include = ["~/.cache/matugen/remage.toml"]` to your |
| 192 | config. Every wallpaper change recolours open windows. |
| 193 | - **pywal:** set `[theme] pywal = "~/.cache/wal/colors.json"`. |
| 194 | |
| 195 | ## Scripting over IPC |
| 196 | |
| 197 | Each instance listens on `$XDG_RUNTIME_DIR/remage/<pid>.sock` and speaks newline-delimited JSON. |
| 198 | `remage msg` talks to the newest instance (or `$REMAGE_SOCKET`, or `--pid`). |
| 199 | |
| 200 | ```bash |
| 201 | remage msg next 5 # any action |
| 202 | remage msg --query marked # current | marked | queue | state |
| 203 | remage msg --subscribe image # one JSON line per change |
| 204 | |
| 205 | # Hyprland |
| 206 | bind = SUPER, bracketright, exec, remage msg next |
| 207 | ``` |
| 208 | |
| 209 | ## Formats |
| 210 | |
| 211 | | Format | Through | |
| 212 | |---|---| |
| 213 | | JPEG, PNG/APNG, GIF, WebP, BMP, ICO, TIFF, TGA, PNM, QOI, DDS, HDR, OpenEXR | `image` | |
| 214 | | JPEG XL | `jxl-oxide` | |
| 215 | | AVIF | `dav1d` (system library) | |
| 216 | | HEIC / HEIF | `libheif` (system library) | |
| 217 | | SVG / SVGZ | `resvg`, re-rendered sharp at every zoom level | |
| 218 | | Camera RAW (CR2, CR3, NEF, ARW, DNG, RAF, ORF, RW2 …) | `rawler`, embedded preview first | |
| 219 | | Anything else | `[[decoders]]`, e.g. `magick "{file}[0]" png:-` for PSD and XCF | |
| 220 | |
| 221 | ## Development |
| 222 | |
| 223 | ```bash |
| 224 | cargo run --release -- ~/Pictures |
| 225 | cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test |
| 226 | RUST_LOG=remage=debug cargo run --release # startup and first-frame timings |
| 227 | REMAGE_SNAPSHOT=/tmp/x.png remage … # also write each settled frame to a PNG |
| 228 | ``` |
| 229 | |
| 230 | `tests/fixtures/make.sh` regenerates the decoder fixtures. |
| 231 | |
| 232 | To release, bump `version` in `Cargo.toml`, add a section to [`CHANGELOG.md`](CHANGELOG.md), |
| 233 | then push a tag: `git tag v0.2.0 && git push origin v0.2.0`. The release workflow builds the |
| 234 | tarball and publishes the release with that changelog section as its notes. |
| 235 | |
| 236 | ## License |
| 237 | |
| 238 | remage is licensed under the [GNU AGPL v3](LICENSE). The embedded fonts (Space Grotesk, Space |
| 239 | Mono and Doto) are under the [SIL Open Font License](assets/fonts/OFL.txt). |