2 releases (1 stable)
1.0.0 | Sep 17, 2023 |
---|---|
0.1.1 | Aug 28, 2023 |
#18 in #sixel
56KB
1.5K
SLoC
⚠️ This crate/repository has been renamed:
ratatu-image -> ratatui-image
New links:
lib.rs
:
Image widgets for Ratatui
⚠️ THIS CRATE IS EXPERIMENTAL
Render images with graphics protocols in the terminal with Ratatui.
struct App {
image: Box<dyn FixedBackend>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let font_size = (7, 16); // Or use Picker::from_termios, or let user provide it.
let mut picker = Picker::new(
font_size,
BackendType::Sixel,
None,
)?;
let dyn_img = image::io::Reader::open("./assets/Ada.png")?.decode()?;
let image = picker.new_static_fit(dyn_img, Rect::new(0, 0, 30, 20), Resize::Fit)?;
let mut app = App { image };
let backend = TestBackend::new(80, 30);
let mut terminal = Terminal::new(backend)?;
// loop:
terminal.draw(|f| ui(f, &mut app))?;
Ok(())
}
fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
let image = FixedImage::new(app.image.as_ref());
f.render_widget(image, f.size());
}
TUIs
TUI application revolve around columns and rows of text naturally without the need of any notions of pixel sizes. Ratatui is based on "immediate rendering with intermediate buffers".
At each frame, widgets are constructed and rendered into some character buffer, and any changes from respect to the last frame are then diffed and written to the terminal screen.
Terminal graphic protocols
Some protocols allow to output image data to terminals that support it.
The Sixel protocol mechanism is, in a nutshell, just printing an escape sequence. The image will be "dumped" at the cursor position, and the implementation may add enough carriage returns to scroll the output.
Problem
Simply "dumping" an image into a Ratatui buffer is not enough. At best, the buffer diff might not overwrite any characters that are covered by the image in some instances, but the diff might change at any time due to screen/area resizing or simply other widget's contents changing. Then the graphics would inmediately get overwritten by the underlying character data.
Solution
First it is necessary to suppress the covered character cells' rendering, which is addressed in a Ratatui PR for cell skipping.
Second it is then necessary to get the image's size in columns and rows, which is done by
querying the terminal for it's pixel size and dividing by columns/rows to get the font size in
pixels. Currently this is implemented with rustix::termios
, but this is subject to change for
a Ratatui PR for getting window size.
Implementation
The images are always resized so that they fit their nearest rectangle in columns/rows. This is so that the image shall be drawn in the same "render pass" as all surrounding text, and cells under the area of the image skip the draw on the ratatui buffer level, so there is no way to "clear" previous drawn text. This would leave artifacts around the image's right and bottom borders.
Example
See the crate::picker::Picker helper and examples/demo
.
Dependencies
~8–25MB
~303K SLoC