|
| 1 | +--- |
| 2 | +date: '2026-02-03T12:00:00+01:00' |
| 3 | +draft: false |
| 4 | +title: 'Terminal Images' |
| 5 | +weight: 12 |
| 6 | +--- |
| 7 | + |
| 8 | +Æsh Readline provides support for displaying images directly in the terminal using multiple graphics protocols. This allows CLI applications to show logos, charts, screenshots, and other visual content. |
| 9 | + |
| 10 | +## Overview |
| 11 | + |
| 12 | +The terminal image API supports three major graphics protocols: |
| 13 | + |
| 14 | +| Protocol | Terminals | |
| 15 | +|----------|-----------| |
| 16 | +| **Kitty Graphics** | Kitty, Ghostty, Konsole (partial) | |
| 17 | +| **iTerm2 Inline Images** | iTerm2, WezTerm, Mintty, VSCode Terminal, Tabby, Hyper | |
| 18 | +| **Sixel Graphics** | xterm, mlterm, foot, Windows Terminal, Contour | |
| 19 | + |
| 20 | +The API automatically detects which protocol the terminal supports and generates the appropriate escape sequences. |
| 21 | + |
| 22 | +## Quick Start |
| 23 | + |
| 24 | +```java |
| 25 | +import org.aesh.terminal.image.TerminalImage; |
| 26 | +import org.aesh.terminal.image.TerminalImageBuilder; |
| 27 | +import org.aesh.terminal.tty.TerminalConnection; |
| 28 | + |
| 29 | +import java.nio.file.Files; |
| 30 | +import java.nio.file.Path; |
| 31 | + |
| 32 | +TerminalConnection conn = new TerminalConnection(); |
| 33 | + |
| 34 | +// Load image from file |
| 35 | +byte[] imageData = Files.readAllBytes(Path.of("logo.png")); |
| 36 | + |
| 37 | +// Build image with auto-detected protocol |
| 38 | +TerminalImage image = TerminalImageBuilder.builder(conn.device()) |
| 39 | + .data(imageData) |
| 40 | + .filename("logo.png") |
| 41 | + .widthCells(40) // Display width in terminal cells |
| 42 | + .build(); |
| 43 | + |
| 44 | +// Display the image |
| 45 | +if (image != null) { |
| 46 | + conn.write(image.encode()); |
| 47 | +} |
| 48 | + |
| 49 | +conn.close(); |
| 50 | +``` |
| 51 | + |
| 52 | +## Checking Image Support |
| 53 | + |
| 54 | +Before displaying images, check if the terminal supports graphics: |
| 55 | + |
| 56 | +```java |
| 57 | +import org.aesh.terminal.image.ImageProtocol; |
| 58 | + |
| 59 | +ImageProtocol protocol = conn.device().getImageProtocol(); |
| 60 | + |
| 61 | +switch (protocol) { |
| 62 | + case KITTY: |
| 63 | + System.out.println("Using Kitty graphics protocol"); |
| 64 | + break; |
| 65 | + case ITERM2: |
| 66 | + System.out.println("Using iTerm2 inline images"); |
| 67 | + break; |
| 68 | + case SIXEL: |
| 69 | + System.out.println("Using Sixel graphics"); |
| 70 | + break; |
| 71 | + case NONE: |
| 72 | + System.out.println("No image support detected"); |
| 73 | + break; |
| 74 | +} |
| 75 | + |
| 76 | +// Simple check |
| 77 | +if (conn.device().supportsImages()) { |
| 78 | + // Display image |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## TerminalImageBuilder |
| 83 | + |
| 84 | +The `TerminalImageBuilder` class provides a fluent API for creating images: |
| 85 | + |
| 86 | +### Creating a Builder |
| 87 | + |
| 88 | +```java |
| 89 | +// Auto-detect protocol from terminal device |
| 90 | +TerminalImageBuilder builder = TerminalImageBuilder.builder(conn.device()); |
| 91 | + |
| 92 | +// Or specify a protocol explicitly |
| 93 | +TerminalImageBuilder builder = TerminalImageBuilder.builder(ImageProtocol.ITERM2); |
| 94 | +``` |
| 95 | + |
| 96 | +### Setting Image Data |
| 97 | + |
| 98 | +```java |
| 99 | +// From file path |
| 100 | +builder.file(Path.of("image.png")); |
| 101 | + |
| 102 | +// From byte array |
| 103 | +builder.data(imageBytes); |
| 104 | + |
| 105 | +// Set filename (used by iTerm2 protocol) |
| 106 | +builder.filename("image.png"); |
| 107 | +``` |
| 108 | + |
| 109 | +### Setting Dimensions |
| 110 | + |
| 111 | +```java |
| 112 | +// Width/height in terminal cells |
| 113 | +builder.widthCells(40); |
| 114 | +builder.heightCells(20); |
| 115 | + |
| 116 | +// Width/height in pixels |
| 117 | +builder.widthPixels(800); |
| 118 | +builder.heightPixels(600); |
| 119 | + |
| 120 | +// Preserve aspect ratio (iTerm2 only) |
| 121 | +builder.preserveAspectRatio(true); |
| 122 | +``` |
| 123 | + |
| 124 | +### Building and Displaying |
| 125 | + |
| 126 | +```java |
| 127 | +TerminalImage image = builder.build(); |
| 128 | + |
| 129 | +if (image != null) { |
| 130 | + String escapeSequence = image.encode(); |
| 131 | + conn.write(escapeSequence); |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +## Protocol-Specific Features |
| 136 | + |
| 137 | +### iTerm2 Protocol |
| 138 | + |
| 139 | +The iTerm2 protocol is widely supported and offers several options: |
| 140 | + |
| 141 | +```java |
| 142 | +import org.aesh.terminal.image.ITermImage; |
| 143 | + |
| 144 | +ITermImage image = new ITermImage(imageData) |
| 145 | + .filename("chart.png") |
| 146 | + .widthCells(60) |
| 147 | + .heightCells(30) |
| 148 | + .preserveAspectRatio(true); |
| 149 | + |
| 150 | +conn.write(image.encode()); |
| 151 | +``` |
| 152 | + |
| 153 | +**Sizing options:** |
| 154 | +- `widthCells(int)` / `heightCells(int)` - Size in terminal cells |
| 155 | +- `widthPixels(int)` / `heightPixels(int)` - Size in pixels |
| 156 | +- `widthPercent(int)` / `heightPercent(int)` - Size as percentage of terminal |
| 157 | + |
| 158 | +**Other options:** |
| 159 | +- `preserveAspectRatio(boolean)` - Maintain image proportions |
| 160 | +- `useStTerminator(boolean)` - Use ST instead of BEL terminator |
| 161 | + |
| 162 | +### Kitty Graphics Protocol |
| 163 | + |
| 164 | +The Kitty protocol provides high-quality graphics with advanced features: |
| 165 | + |
| 166 | +```java |
| 167 | +import org.aesh.terminal.image.KittyImage; |
| 168 | + |
| 169 | +KittyImage image = new KittyImage(imageData) |
| 170 | + .widthCells(50) |
| 171 | + .heightCells(25) |
| 172 | + .zIndex(1) // Layering support |
| 173 | + .suppressResponse(true); |
| 174 | + |
| 175 | +conn.write(image.encode()); |
| 176 | +``` |
| 177 | + |
| 178 | +**Features:** |
| 179 | +- Automatic PNG conversion (Kitty requires PNG format) |
| 180 | +- Chunked transmission for large images |
| 181 | +- Z-index layering support |
| 182 | +- Response suppression to avoid input clutter |
| 183 | + |
| 184 | +**Options:** |
| 185 | +- `widthCells(int)` / `heightCells(int)` - Display dimensions |
| 186 | +- `sourceDimensions(int width, int height)` - For raw RGB/RGBA data |
| 187 | +- `zIndex(int)` - Layer ordering |
| 188 | +- `suppressResponse(boolean)` - Suppress terminal responses (default: true) |
| 189 | + |
| 190 | +### Sixel Graphics |
| 191 | + |
| 192 | +Sixel is a legacy but widely supported protocol: |
| 193 | + |
| 194 | +```java |
| 195 | +import org.aesh.terminal.image.SixelImage; |
| 196 | + |
| 197 | +SixelImage image = new SixelImage(imageData) |
| 198 | + .maxWidth(800) |
| 199 | + .maxHeight(600) |
| 200 | + .maxColors(256) // Color palette size (2-256) |
| 201 | + .useRle(true); // Run-length encoding compression |
| 202 | + |
| 203 | +conn.write(image.encode()); |
| 204 | +``` |
| 205 | + |
| 206 | +**Features:** |
| 207 | +- Automatic image scaling with aspect ratio preservation |
| 208 | +- Color quantization (reduces colors to fit palette) |
| 209 | +- RLE compression for smaller output |
| 210 | +- Works in many legacy terminals |
| 211 | + |
| 212 | +**Options:** |
| 213 | +- `maxWidth(int)` / `maxHeight(int)` - Maximum dimensions in pixels |
| 214 | +- `maxColors(int)` - Palette size (2-256, affects quality vs size) |
| 215 | +- `useRle(boolean)` - Enable run-length encoding compression |
| 216 | + |
| 217 | +## Image Format Support |
| 218 | + |
| 219 | +The API supports multiple image formats: |
| 220 | + |
| 221 | +| Format | Kitty | iTerm2 | Sixel | |
| 222 | +|--------|-------|--------|-------| |
| 223 | +| PNG | Yes (native) | Yes | Yes | |
| 224 | +| JPEG | Converted to PNG | Yes | Yes | |
| 225 | +| GIF | Converted to PNG | Yes | Yes | |
| 226 | + |
| 227 | +For Kitty protocol, non-PNG images are automatically converted to PNG. |
| 228 | + |
| 229 | +### Format Detection |
| 230 | + |
| 231 | +```java |
| 232 | +import org.aesh.terminal.image.ImageUtils; |
| 233 | + |
| 234 | +byte[] data = Files.readAllBytes(Path.of("image.jpg")); |
| 235 | + |
| 236 | +if (ImageUtils.isPng(data)) { |
| 237 | + System.out.println("PNG format"); |
| 238 | +} else if (ImageUtils.isJpeg(data)) { |
| 239 | + System.out.println("JPEG format"); |
| 240 | +} else if (ImageUtils.isGif(data)) { |
| 241 | + System.out.println("GIF format"); |
| 242 | +} |
| 243 | + |
| 244 | +// Convert any format to PNG |
| 245 | +byte[] pngData = ImageUtils.toPng(data); |
| 246 | +``` |
| 247 | + |
| 248 | +## Complete Example |
| 249 | + |
| 250 | +Here's a complete example that displays an image with fallback handling: |
| 251 | + |
| 252 | +```java |
| 253 | +import org.aesh.terminal.image.*; |
| 254 | +import org.aesh.terminal.tty.TerminalConnection; |
| 255 | +import java.nio.file.Files; |
| 256 | +import java.nio.file.Path; |
| 257 | + |
| 258 | +public class ImageDemo { |
| 259 | + public static void main(String[] args) throws Exception { |
| 260 | + TerminalConnection conn = new TerminalConnection(); |
| 261 | + |
| 262 | + // Check for image support |
| 263 | + ImageProtocol protocol = conn.device().getImageProtocol(); |
| 264 | + |
| 265 | + if (protocol == ImageProtocol.NONE) { |
| 266 | + conn.write("This terminal does not support images.\n"); |
| 267 | + conn.write("Try running in: Kitty, iTerm2, WezTerm, or VSCode\n"); |
| 268 | + conn.close(); |
| 269 | + return; |
| 270 | + } |
| 271 | + |
| 272 | + conn.write("Detected protocol: " + protocol + "\n\n"); |
| 273 | + |
| 274 | + // Load and display image |
| 275 | + Path imagePath = Path.of(args[0]); |
| 276 | + byte[] imageData = Files.readAllBytes(imagePath); |
| 277 | + |
| 278 | + TerminalImage image = TerminalImageBuilder.builder(conn.device()) |
| 279 | + .data(imageData) |
| 280 | + .filename(imagePath.getFileName().toString()) |
| 281 | + .widthCells(60) |
| 282 | + .preserveAspectRatio(true) |
| 283 | + .build(); |
| 284 | + |
| 285 | + if (image != null) { |
| 286 | + conn.write(image.encode()); |
| 287 | + conn.write("\n\nImage displayed successfully!\n"); |
| 288 | + } else { |
| 289 | + conn.write("Failed to create image.\n"); |
| 290 | + } |
| 291 | + |
| 292 | + conn.close(); |
| 293 | + } |
| 294 | +} |
| 295 | +``` |
| 296 | + |
| 297 | +## Protocol Detection |
| 298 | + |
| 299 | +The API uses environment variables and terminal type to detect the graphics protocol: |
| 300 | + |
| 301 | +### Environment Variables Checked |
| 302 | + |
| 303 | +| Variable | Protocol | |
| 304 | +|----------|----------| |
| 305 | +| `KITTY_WINDOW_ID` | Kitty | |
| 306 | +| `GHOSTTY_RESOURCES_DIR` | Kitty | |
| 307 | +| `ITERM_SESSION_ID` | iTerm2 | |
| 308 | +| `WEZTERM_PANE` | iTerm2 | |
| 309 | +| `TERM_PROGRAM=vscode` | iTerm2 | |
| 310 | + |
| 311 | +### TERM Variable Patterns |
| 312 | + |
| 313 | +| TERM contains | Protocol | |
| 314 | +|---------------|----------| |
| 315 | +| `kitty` | Kitty | |
| 316 | +| `ghostty` | Kitty | |
| 317 | +| `konsole` | Kitty | |
| 318 | +| `iterm`, `wezterm`, `mintty` | iTerm2 | |
| 319 | +| `vscode`, `tabby`, `hyper` | iTerm2 | |
| 320 | +| `mlterm`, `foot`, `contour` | Sixel | |
| 321 | + |
| 322 | +### Manual Protocol Override |
| 323 | + |
| 324 | +If automatic detection doesn't work, specify the protocol explicitly: |
| 325 | + |
| 326 | +```java |
| 327 | +TerminalImage image = TerminalImageBuilder.builder(ImageProtocol.ITERM2) |
| 328 | + .data(imageData) |
| 329 | + .widthCells(40) |
| 330 | + .build(); |
| 331 | +``` |
| 332 | + |
| 333 | +## Supported Terminals |
| 334 | + |
| 335 | +| Terminal | Protocol | Notes | |
| 336 | +|----------|----------|-------| |
| 337 | +| Kitty | Kitty | Full support | |
| 338 | +| Ghostty | Kitty | Full support | |
| 339 | +| iTerm2 | iTerm2 | Full support | |
| 340 | +| WezTerm | iTerm2 | Full support | |
| 341 | +| VSCode Terminal | iTerm2 | Full support | |
| 342 | +| Mintty | iTerm2 | Full support | |
| 343 | +| Tabby | iTerm2 | Full support | |
| 344 | +| Hyper | iTerm2 | Full support | |
| 345 | +| Konsole | Kitty | Partial support | |
| 346 | +| xterm | Sixel | Requires `-ti vt340` flag | |
| 347 | +| mlterm | Sixel | Full support | |
| 348 | +| foot | Sixel | Full support | |
| 349 | +| Windows Terminal | Sixel | Full support | |
| 350 | +| Contour | Sixel | Full support | |
| 351 | +| Alacritty | None | No image support | |
| 352 | +| Apple Terminal | None | No image support | |
| 353 | +| GNOME Terminal | None | No image support | |
| 354 | + |
| 355 | +## Performance Considerations |
| 356 | + |
| 357 | +1. **Image Size**: Large images generate large escape sequences. Consider resizing before display. |
| 358 | + |
| 359 | +2. **Sixel Colors**: Reducing `maxColors` improves performance but reduces quality. |
| 360 | + |
| 361 | +3. **PNG Conversion**: Kitty protocol requires PNG, so JPEG/GIF images are converted at runtime. |
| 362 | + |
| 363 | +4. **Chunked Transmission**: Kitty protocol sends large images in 4KB chunks to avoid buffer issues. |
| 364 | + |
| 365 | +## Troubleshooting |
| 366 | + |
| 367 | +### Image Not Displayed |
| 368 | + |
| 369 | +1. **Check terminal support**: Run the example to see detected protocol |
| 370 | +2. **Try explicit protocol**: Use `TerminalImageBuilder.builder(ImageProtocol.ITERM2)` |
| 371 | +3. **Check image format**: Ensure the image file is valid |
| 372 | + |
| 373 | +### Garbled Output |
| 374 | + |
| 375 | +1. **Terminal doesn't support the protocol**: Try a different terminal |
| 376 | +2. **Image too large**: Reduce dimensions with `widthCells()` or `maxWidth()` |
| 377 | + |
| 378 | +### Colors Look Wrong (Sixel) |
| 379 | + |
| 380 | +1. **Increase color palette**: Use `.maxColors(256)` for better quality |
| 381 | +2. **Try different terminal**: Some terminals have better Sixel support |
0 commit comments