Integrations
Each integration is a separate package that depends on the core and on its host. The core never references a host.
using Nullean.TerminalImages.Spectre;
using Spectre.Console;
TerminalImage.DefaultCapabilities = await TerminalImageDetector.DetectAsync();
AnsiConsole.Write(new TerminalImage(image) { MaxWidth = 60, AltText = "[photo]" });
AnsiConsole.Write(TerminalImage.FromPng(File.ReadAllBytes("photo.png")));
- optional: probe once at start-up
TerminalImage is a Renderable. With a graphics protocol it writes one zero-width control segment that draws the image from
the cursor and puts the cursor back, then one line break per row, so Spectre's layout and Live displays count the rows. Without
one it draws half blocks as Spectre Segments with Spectre Style colours, so Spectre downgrades them to the console's colour
system. Without ANSI it writes AltText.
Every line ends with a line break, like Spectre's own widgets: the next write starts below the image.
Limit: a Panel or Table pads each line with spaces, and those spaces erase a Sixel or iTerm2 image drawn beneath them.
Kitty images are on a separate layer and survive. Put images directly on the console, or use Kitty, inside such a widget.
Spectre cannot read terminal replies, so detection runs before rendering with the standalone channel
(TerminalImageDetector.DetectAsync), not through Spectre.
using Nullean.TerminalImages.TerminalGui;
using var app = Application.Create();
app.Init();
var capabilities = await TerminalImageDetector.DetectAsync();
app.Driver!.ApplyDetection(capabilities);
var view = new TerminalImageView { Width = Dim.Fill(), Height = Dim.Fill(), Image = image };
- before the input loop runs
- replace what Terminal.Gui worked out itself
Terminal.Gui has its own Sixel and Kitty pipeline: it clips an image to its view, orders it with text, and repaints it on resize.
TerminalImageView feeds that pipeline scaled pixels and the destination cells; Terminal.Gui does the encoding. Without Sixel or
Kitty it draws half blocks into the view's cells with the view's own colours. Terminal.Gui's pipeline has no iTerm2 protocol, so
an iTerm2-only terminal gets half blocks.
ApplyDetection exists because Terminal.Gui's start-up handshake does not see through tmux or SSH, and it detects Kitty from
environment variables only. A probe can do better, and ApplyDetection hands the answer (support, cell size, palette size) to the
driver. Run detection before Terminal.Gui starts reading input: two readers on one stream steal each other's bytes.
Tested against Terminal.Gui 2.5.0.
using Nullean.TerminalImages.XenoAtom;
var terminal = Terminal.Instance;
var capabilities = await terminal.DetectImagesAsync(probe: true);
terminal.WriteImage(image, capabilities);
terminal.WritePng(File.ReadAllBytes("photo.png"), capabilities);
- before the first key read
Independent of XenoAtom.Terminal.Graphics: no SkiaSharp, and only the core XenoAtom.Terminal package. WriteImage fits the
image to the terminal's size, renders to a string, and writes it with WriteAtomic, so no other output lands in the middle of it.
DetectImagesAsync uses XenoAtom's colour level, redirection and ANSI flags.
XenoAtom does not hand terminal replies to the application, so probe: true reads /dev/tty itself and must run before XenoAtom's
input loop starts. Without it, detection uses the environment only.
Implement ITerminalQueryChannel if you own terminal input and want probing; render with TerminalImageRenderer or the encoders and
write the text however your host writes raw output. Everything a host needs is in the core: TerminalImageDetector,
ProbeReplyParser, TerminalImageRenderer, TmuxPassthrough.