ratatui-image

repository·master·Indexed 18 days ago

https://github.com/ratatui/ratatui-image

An image widget for the Ratatui TUI framework supporting Sixels, Kitty, iTerm2, and unicode-halfblocks. It provides stateless Image and stateful StatefulImage widgets, handling terminal capability queries, font size detection, and image pixel mapping to character cells. Includes a CLI utility for viewing images in the terminal and support for various backends like crossterm and termion.

Tokens
7.9K
Snippets
26
Records
34
Agent score
62%

What's inside ratatui-image

  1. Choose between Image and StatefulImage widgets

    master

    Depending on your performance and UI requirements, choose one of the following widgets:

    Image widget

    • Behavior: Has a fixed size in rows/columns. If the image exceeds the pixel area of the cells, it is scaled down proportionally to "fit" once during the creation of the Protocol.
    • Pros: It is stateless (immediate-mode), meaning it never blocks the rendering thread/task.
    • Clipping: Use Image::allow_clipping to control what happens when the image does not fit the render area.

    StatefulImage widget

    • Behavior: Adapts to its render area at render-time. It can be configured to fit, crop, or scale to the available area.
    • Pros: Highly flexible for dynamic layouts.
    • Cons: It is stateful (requires render_stateful_widget) and the resizing/encoding process is blocking.
    • Best Practice: Always offload StatefulImage operations to another thread or async task (e.g., using thread::ThreadProtocol) to keep the UI responsive.
  2. Quick start with ratatui-image

    master

    To use ratatui-image, you need to:

    1. Use a Picker to determine the terminal's font size and supported graphics protocol.
    2. Load an image using the image crate.
    3. Calculate the target size in character cells based on the Picker's font size.
    4. Use picker.new_protocol() to transform the image data into a Protocol object (e.g., Sixels, Kitty, iTerm2, or ASCII/halfblocks).
    5. Render the image using the Image widget within your Ratatui terminal.draw loop.

    Note: For production apps, it is recommended to use thread::ThreadProtocol to perform image resizing and encoding in a background thread to avoid blocking the UI.

    use ratatui::{backend::TestBackend, layout::Size, Terminal, Frame};
    use ratatui_image::{Image, picker::Picker, protocol::Protocol, Resize};
    
    struct App {
        // We need to hold the image data somewhere.
        image: Protocol,
    }
    
    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::halfblocks();
    
        // Load an image with the image crate.
        let dyn_img = image::ImageReader::open("./assets/Ada.png")?.decode()?;
    
        let font_size = picker.font_size();
        let size = Size::new(
            dyn_img.width().div_ceil(font_size.width as u32) as u16,
            dyn_img.height().div_ceil(font_size.height as u32) as u16,
        );
    
        // Create the Protocol once, or in other words, transform the image data to Sixels, Kitty,
        // iTerm2 base64 PNG data, or some kind of ASCII-art.
        let image = picker.new_protocol(dyn_img, size, Resize::Fit(None))?;
    
        let mut app = App { image };
    
        // This would be your typical `loop {` in a real app:
        terminal.draw(|f| {
            let image = Image::new(&app.image);
            // Rendering the transformed data is now cheap.
            f.render_widget(image, f.area());
        });
    
        Ok()
    }
  3. Overview of Ratatui-image

    master
    Ratatui-image provides image widgets for the ratatui TUI framework. It supports multiple graphics protocols to allow rendering images directly within a terminal interface. It is designed to work with immediate-mode TUIs, unlike some alternatives that rely on stateful external window drawing.
  4. How SlicedProtocol handles different terminal protocols

    master

    The SlicedProtocol enum encapsulates different rendering strategies depending on the terminal's capabilities:

    VariantDescription
    Sliced(Vec<Protocol>)A generic list of image slices (rows). Primarily used for Iterm2 protocols.
    Kitty(Kitty)Optimized for the Kitty graphics protocol, utilizing its unicode-placeholder mechanism.
    Sixel(SlicedSixel)Optimized for Sixel by stripping 'bands' at render-time to allow efficient vertical clipping.
    Halfblocks(Halfblocks)Renders the full image (often using Chafa for ASCII/Unicode art) and then renders only the relevant rows.
  5. Compare Image and StatefulImage widgets

    master

    Choosing between the two primary widgets depends on your performance and responsiveness requirements:

    • Image (Stateless):

      • Has a fixed size in rows/columns.
      • Does not react to area resizes.
      • The Protocol is resized once at creation time.
      • Advantage: It is stateless in the Ratatui sense, meaning it is extremely fast and never blocks the rendering thread.
      • Clipping: If the image is larger than the render area, it won't render unless .allow_clipping(true) is called.
    • StatefulImage (Stateful):

      • Adapts to the render area at render-time.
      • Can be configured to Fit, Crop, or Scale to the available area.
      • Warning: Resizing and encoding are blocking operations. If used in a reactive UI without offloading to a background thread (e.g., using thread::ThreadProtocol), it will block the UI thread.
      • Requires using f.render_stateful_widget with a mutable state parameter.
  6. Configure ratatui-image features

    master

    Backend Features

    • crossterm (default): Use if your Ratatui backend is crossterm.
    • termion: Use if your Ratatui backend is termion.
    • termwiz: Available but not currently working correctly with ratatui-image.

    Chafa Library (for rendering without image protocols)

    Note: These features are mutually exclusive. Enable only one at a time.

    • chafa-dyn (default): Dynamically link against libchafa.so. Requires libchafa at runtime.
    • chafa-static: Statically link against libchafa.a. The library is embedded in the binary.
    • To avoid Chafa entirely: Use --no-default-features --features image-defaults,crossterm.

    Other Features

    • image-defaults (default): Enables default image formats. To reduce dependencies, disable this and manually enable specific formats in your Cargo.toml.
    • serde: Enables #[derive] for picker::ProtocolType to allow saving protocol information in user configurations.
    • tokio: Enables tokio's UnboundedSender when using thread::ThreadProtocol.
  7. Detect terminal graphics capabilities with Picker

    master

    The Picker struct is used to detect which graphics protocols (Kitty, Sixel, Iterm2, or Halfblocks) and terminal capabilities (like font size or background color) are supported by the user's terminal.

    To automatically detect capabilities by querying stdio, use Picker::from_query_stdio().

    WARNING: This method writes to and reads from stdio momentarily. It should be called after entering the terminal's alternate screen but before reading terminal events to avoid interfering with application logic.

    If you want to bypass capability detection and use a fallback that works in almost any terminal, use Picker::halfblocks().

    use ratatui_image::picker::Picker;
    
    // Automatically detect capabilities via stdio
    let mut picker = Picker::from_query_stdio().unwrap();
    
    // Or use a guaranteed fallback
    let mut picker = Picker::halfblocks();
  8. Configure Chafa features for Halfblocks

    master

    The Halfblocks protocol can be enhanced using chafa for higher-quality rendering. This is controlled via Cargo features. Note that chafa-static and chafa-dyn are mutually exclusive.

    • chafa-static: Statically links libchafa.a at compile time.
    • chafa-dyn: Dynamically links to chafa via pkg-config at compile time.

    If neither feature is enabled, the protocol falls back to a primitive half-block implementation.

    // In your Cargo.toml, enable one of the following:
    // ratatui-image = { version = "...", features = ["chafa-static"] }
    // OR
    // ratatui-image = { version = "...", features = ["chafa-dyn"] }
  9. Terminal protocol compatibility matrix

    master

    The following table describes which graphics protocols are supported by various terminals:

    TerminalProtocolStatus
    XtermSixelOK (Run with -ti 340 to ensure support)
    FootSixelOK
    KittyKittyOK (Requires Kitty 0.28.0+)
    WeztermiTerm2OK (iTerm2 protocol is the most stable here)
    GhosttyKittyOK
    iTerm2iTerm2OK (Mac only)
    RioiTerm2OK
    mltermSixelOK
    Black BoxSixelOK (Confirmed with flatpak)
    BobcatiTerm2OK (Falls back to Sixel if TERM_PROGRAM is not set)
    AlacrittySixelNot supported
    KonsoleSixelNot supported
    ContourSixelNot supported
    ctxSixelNot supported
    WarpiTerm2Not supported

    Note: halfblocks (using unicode half-block characters) should work in all terminals, even if the font size cannot be detected, using a 4:8 pixel ratio.

  10. Initialize image state with Picker

    master

    When using ratatui-image in your own application, you can use a Picker to manage how an image is rendered and resized according to the terminal's capabilities.

    To create a new resize protocol for an image, use picker.new_resize_protocol(image_source) where image_source is a DynamicImage from the image crate. This returns a StatefulProtocol which must be passed to render_stateful_widget to display the image.

    Example workflow:

    1. Initialize a Picker (e.g., Picker::halfblocks()).
    2. Load an image using the image crate.
    3. Create the state: let image_state = picker.new_resize_protocol(image_source.clone());.
    4. Render using f.render_stateful_widget(StatefulImage::default(), area, &mut image_state);.
    let picker = Picker::halfblocks().unwrap();
    let image_source = image::ImageReader::open(&filename)?.decode()?;
    let image_state = picker.new_resize_protocol(image_source.clone());
    
    // In your draw loop:
    let image = StatefulImage::default();
    f.render_stateful_widget(image, area, &mut image_state);
  11. Create a SlicedProtocol

    master

    A SlicedProtocol contains the specialized data required to render an image in slices, optimized for different terminal protocols (Kitty, Sixel, Halfblocks, or generic Iterm2-style slices).

    • new: Creates a SlicedProtocol using the natural size of the image based on the Picker's font size.
    • new_with_resize: Creates a SlicedProtocol with a specific target Size and a Resize strategy (e.g., Resize::Fit(None)).
    ```rust
    // Using natural size
    let sliced = SlicedProtocol::new(&picker, dyn_img, None)?;
    
    // Using specific size and resize strategy
    let sliced = SlicedProtocol::new_with_resize(
        &picker, 
        dyn_img, 
        Size::new(40, 20), 
        Resize::Fit(None)
    )?;