Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Add section on rendering images 📚 #141

Draft
wants to merge 4 commits into
base: ratatui-book
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/how-to/render/images.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some examples (links in discord)

Yeah - pretty much anything that gives you a grid of characters will work - just wrap it int a Widget that splats the chars to the cells. E.g. https://github.com/joshka/tui-big-text/blob/5b030f6f3c5587e25a38d8e6317d89e58b93050e/src/lib.rs#L128-L139

/// Render a single 8x8 glyph into a cell by setting the corresponding cells in the buffer.
fn render_glyph(glyph: [u8; 8], area: Rect, buf: &mut Buffer) {
    for (row, y) in glyph.iter().zip(area.top()..area.bottom()) {
        for (col, x) in (area.left()..area.right()).enumerate() {
            let cell = buf.get_mut(x, y);
            match row & (1 << col) {
                0 => cell.set_symbol(" "),
                _ => cell.set_symbol("█"),
            };
        }
    }
}

Or a nicer version of half blocks example: https://github.com/ratatui-org/ratatui/blob/66215e286740c6d7816e3ba5df73e240320ba332/examples/colors_rgb.rs#L143-L156 (from an open PR ratatui/ratatui#583)

impl Widget for RgbColors<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let colors = self.colors;
        for (xi, x) in (area.left()..area.right()).enumerate() {
            // animate the colors by shifting the x index by the frame number
            let xi = (xi + self.frame_count) % (area.width as usize);
            for (yi, y) in (area.top()..area.bottom()).enumerate() {
                let fg = colors[yi * 2][xi];
                let bg = colors[yi * 2 + 1][xi];
                buf.get_mut(x, y).set_char('▀').set_fg(fg).set_bg(bg);
            }
        }
    }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some links to relevant crates

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Rendering images

There's basically a few different ways to "draw images" in the terminal.
kdheepak marked this conversation as resolved.
Show resolved Hide resolved

1. Sixel
2. Terminal specific control sequences (ITerm2 vs Kitty vs others)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link https://sw.kovidgoyal.net/kitty/graphics-protocol/ and https://iterm2.com/documentation-images.html
I have previously seen a support page for which terminal emulators support which protocols but can't find it again unfortunately.

3. Half block cells
4. Braille
5. other unicode characters
kdheepak marked this conversation as resolved.
Show resolved Hide resolved

Sixel is a standard for drawing in the terminal but not all terminals support it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a very good one ;) From memory it's restricted to 6 bits per channel color instead of 8 bits


The terminal specific control sequences are just that, terminal specific. And these are different
for each terminal, and there's no easy way to detect which terminal emulator you are running it, so
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorta - often terminals support multiple protocols, so it's worth finding that page on compatibility.
It's worth linking https://github.com/wez/wezterm/blob/main/termwiz/src/caps/mod.rs for the commentary on detection.

you have to choose ahead of time. But this allows you to draw _anything_. ITerm2 for example
supports drawing gifs to the terminal.

Both these two methods give high resolution images because it is the terminal emulator itself that
is drawing the images, as opposes to drawing text that is a lower fidelity representation of the
kdheepak marked this conversation as resolved.
Show resolved Hide resolved
image.

The half block cells trick uses the fact that terminal fonts are typically 2 times taller than they
are wide. Here's a full block `█`. You can see a full block is 2 times taller than it is wide.
Here's a upper half block `▀` and lower half block `▄`. You can see that they only have foreground
text for either upper or lower halfs of a single "character".

Check warning on line 25 in src/how-to/render/images.md

View workflow job for this annotation

GitHub Actions / lint

"halfs" should be "halves".

Take into consideration just the lower half right now, i.e. `▄`. In terminals, you can color the
foreground and background for any individual `Cell` differently. By applying different colors to the
foreground and background for a upper half or lower half block, you get "twice the resolution" of
the image.

Braille characters have 8 dots per `Cell` that you can use to draw. This effectively makes the
resolution 8 times but you can't color dots differently, so it works well for black and white
images. See image attached:

https://en.wikipedia.org/wiki/Braille_Patterns?useskin=vector

There are other unicode characters can you can use to draw to the terminal. Check out this wiki page

https://en.wikipedia.org/wiki/Box-drawing_character?useskin=vector
Loading