| Crates.io | ratatui-image |
| lib.rs | ratatui-image |
| version | 8.0.1 |
| created_at | 2023-09-17 11:11:09.663522+00 |
| updated_at | 2025-05-30 09:53:15.802272+00 |
| description | An image widget for ratatui, supporting sixels, kitty, iterm2, and unicode-halfblocks |
| homepage | https://github.com/benjajaja/ratatui-image |
| repository | https://github.com/benjajaja/ratatui-image |
| max_upload_size | |
| id | 975034 |
| size | 6,244,551 |

ratatui is an immediate-mode TUI library. ratatui-image tackles 3 general problems when rendering images with an immediate-mode TUI:
Query the terminal for available graphics protocols
Some terminals may implement one or more graphics protocols, such as Sixels, or the iTerm2 or Kitty graphics protocols. Guess by env vars. If that fails, query the terminal with some control sequences. Fallback to "halfblocks" which uses some unicode half-block characters with fore- and background colors.
Query the terminal for the font-size in pixels.
If there is an actual graphics protocol available, it is necessary to know the font-size to be able to map the image pixels to character cell area. The image can be resized, fit, or cropped to an area. Query the terminal for the window and columns/rows sizes, and derive the font-size.
Render the image by the means of the guessed protocol.
Some protocols, like Sixels, are essentially "immediate-mode", but we still need to avoid the TUI from overwriting the image area, even with blank characters. Other protocols, like Kitty, are essentially stateful, but at least provide a way to re-render an image that has been loaded, at a different or same position. Since we have the font-size in pixels, we can precisely map the characters/cells/rows-columns that will be covered by the image and skip drawing over the image.
use ratatui::{backend::TestBackend, Terminal, Frame};
use ratatui_image::{picker::Picker, StatefulImage, protocol::StatefulProtocol};
struct App {
// We need to hold the render state.
image: StatefulProtocol,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let backend = TestBackend::new(80, 30);
let mut terminal = Terminal::new(backend)?;
// Should use Picker::from_query_stdio() to get the font size and protocol,
// but we can't put that here because that would break doctests!
let mut picker = Picker::from_fontsize((8, 12));
// Load an image with the image crate.
let dyn_img = image::ImageReader::open("./assets/Ada.png")?.decode()?;
// Create the Protocol which will be used by the widget.
let image = picker.new_resize_protocol(dyn_img);
let mut app = App { image };
// This would be your typical `loop {` in a real app:
terminal.draw(|f| ui(f, &mut app))?;
// It is recommended to handle the encoding result
app.image.last_encoding_result().unwrap()?;
Ok(())
}
fn ui(f: &mut Frame<'_>, app: &mut App) {
// The image widget.
let image = StatefulImage::default();
// Render with the protocol state.
f.render_stateful_widget(image, f.area(), &mut app.image);
}
The [picker::Picker] helper is there to do all this font-size and graphics-protocol guessing, and also to map character-cell-size to pixel size so that we can e.g. "fit" an image inside a desired columns+rows bound, and so on.
examples/async.rs). It must be rendered with
render_stateful_widget (i.e. with some mutable state).examples/demo.rs is a fully fledged demo.examples/async.rs shows how to offload resize and encoding to another thread, to avoid
blocking the UI thread.The lib also includes a binary that renders an image file, but it is focused on testing.
crossterm or termion should match your ratatui backend. termwiz is available, but not
working correctly with ratatu-image.serde for #[derive]s on [picker::ProtocolType] for convenience, because it might be
useful to save it in some user configuration.image-defaults (default) just enables image/defaults (image has default-features = false). To only support a selection of image formats and cut down dependencies, disable this
feature, add image to your crate, and enable its features/formats as desired. See
https://doc.rust-lang.org/cargo/reference/features.html#feature-unification/.Compatibility and QA:
| Terminal | Protocol | OK | Notes |
|---|---|---|---|
| Xterm | Sixel |
✔️ | Run with -ti 340 to make sure sixel support is enabled. |
| Foot | Sixel |
✔️ | Wayland. |
| Kitty | Kitty |
✔️ | Reference for the Kitty protocol (requires Kitty 0.28.0 or later). |
| Wezterm | iTerm2 |
✔️ | Also would support Sixel and Kitty, but only iTerm2 actually works bug-free. |
| Ghostty | Kitty |
✔️ | Implements Kitty with unicode placeholders. |
iTerm2 | iTerm2 | ✔️ | Reference for the iTerm2 protocol. Mac only.
Rio | iTerm2 | ✔️ | Also supports Sixel but has glitches.
mlterm | Sixel | ✔️ | Quite slow but no glitches.
Black Box | Sixel | ✔️ | Confirmed only with the flatpak version, most distro packages don't enable Sixel support.
Alacritty | Sixel | ❌ | There is a sixel fork, but it's probably never getting merged, and does not clear graphics.
Konsole | Sixel | ❌ | Not really fixed in 24.12
Contour | Sixel | ❌ | Does not clear graphics.
ctx | Sixel | ❌ | Buggy.
A basic screenshot test is run with xterm on Xvfb in the CI (or cargo make screenshot-xvfb && cargo make screenshot-diff).
Halfblocks should work in all terminals, even if the font size could not be detected, with a 4:8 pixel ratio.
| Summary | Link |
|---|---|
| Termwiz backend does not work at all | #1 |
| Sixel image rendered on the last line of terminal causes a scroll | #57 |
| Terminals may or may not take DPI scale into account | #59 (closed) |
Many more, see
and
(note that github includes a huge number of unrelated dotfile repos).
PRs and issues/discussions welcome!
There are some specific rules for a PR to be reviewed at all, please see CONTRIBUTING.md for reference.
License: MIT