termimg is a Go library for displaying images in terminal emulators. It provides a unified API that automatically detects terminal capabilities and selects the optimal image display protocol. The library uses cell coordinates (not pixel coordinates) for positioning and can fit images into cell boundaries while preserving aspect ratio.
- Status: Experimental (API may change)
- Positioning: Uses cell coordinates with origin at upper-left terminal corner
- Modularity: Highly modular design with exchangeable components
- Protocol Support: Multiple drawing methods with automatic best-fit selection
- TUI Integration: Compatible with common TUI frameworks via TTY multiplexing (incomplete)
- Import Path:
github.com/srlehn/termimg - Purpose: Public API and configuration
Draw(img image.Image, bounds image.Rectangle) error- Draw image to terminalDrawFile(imgFile string, bounds image.Rectangle) error- Draw image from fileDrawBytes(imgBytes []byte, bounds image.Rectangle) error- Draw image from bytesNewImage(img image.Image) *term.Image- Create image instanceNewImageBytes(imgBytes []byte) *term.Image- Create from bytes (requires decoder registration)NewImageFileName(imgfile string) *term.Image- Create from file pathQuery(qs string, p term.Parser) (string, error)- Send terminal queryTerminal() (*term.Terminal, error)- Get terminal instanceCleanUp() error- Resource cleanup
var DefaultConfig = term.Options{
term.SetPTYName(ttyDefault),
term.SetTTYProvider(ttyProvider, false),
term.SetQuerier(querier, true),
term.SetWindowProvider(wm.SetImpl(wmImplementation), true),
term.SetResizer(resizer),
}ttyDefault-internal.DefaultTTYDevice()ttyProvider-gotty.Newquerier-qdefault.NewQuerier()wmImplementation-wmimpl.Impl()resizer-&rdefault.Resizer{}
NewTerminal(opts ...Option) (*Terminal, error)- Create terminal with optionsDraw(img image.Image, bounds image.Rectangle) error- Draw imageQuery(qs string, p Parser) (string, error)- Send terminal queryNewCanvas(bounds image.Rectangle) (*Canvas, error)- Create drawing canvasSizeInCells() (width, height uint, err error)- Get terminal size in cellsSizeInPixels() (width, height uint, err error)- Get terminal size in pixelsCellSize() (width, height float64, _ error)- Get cell dimensions
NewImage(img image.Image) *Image- Create from Go imageNewImageBytes(imgBytes []byte) *Image- Create from byte dataNewImageFilename(imgFile string) *Image- Create from file pathFit(bounds image.Rectangle, rsz Resizer, sv Surveyor) error- Resize to fit boundsDecode() error- Decode image dataSaveAsFile(t *Terminal, fileExt string, enc ImageEncoder) (rm func() error, err error)- Save to temp file
Draw(img image.Image) error- Draw image to canvasSetImage(img image.Image) error- Set canvas imageScreenshot() (image.Image, error)- Take screenshotVideo(ctx context.Context, vid <-chan image.Image, frameDur time.Duration) error- Play video
SetPTYName(ptyName string) Option- Set PTY device nameSetTTYProvider[T TTY, F func(ptyName string) (T, error)](ttyProv F, enforce bool) Option- Set TTY providerSetQuerier(qu Querier, enforce bool) Option- Set terminal querierSetResizer(rsz Resizer) Option- Set image resizerSetWindowProvider(wProv wm.WindowProvider, enforce bool) Option- Set window providerSetDrawers(drs []Drawer) Option- Set available drawersTUIMode Option- Enable TUI modeCLIMode Option- Enable CLI mode
Drawer Interface
type Drawer interface {
Name() string
New() Drawer
IsApplicable(DrawerCheckerInput) (bool, environ.Properties)
Draw(img image.Image, bounds image.Rectangle, term *Terminal) error
Prepare(ctx context.Context, img image.Image, bounds image.Rectangle, term *Terminal) (drawFn func() error, _ error)
}TTY Interface
type TTY interface {
io.ReadWriteCloser
TTYDevName() string
// Optional: ResizeEvents(), SizePixel(), ReadRune()
}Querier Interface
type Querier interface {
Query(string, TTY, Parser) (string, error)
}Resizer Interface
type Resizer interface {
Resize(img image.Image, size image.Point) (image.Image, error)
}New(ttyPath string) (*TTYMultiplexer, error)- Create multiplexerNewWithOpener(ttyPath string, opener func(string) (*os.File, error)) (*TTYMultiplexer, error)- Create with custom openerNewReader(prefix string) *MultiplexedTTY- Create readerNewReaderWithID(id string) *MultiplexedTTY- Create reader with IDCreatePTYSlave() (*PTYSlaveWrapper, error)- Create PTY slaveClose() error- Shutdown multiplexer
Read(p []byte) (n int, err error)- Read from bufferWrite(p []byte) (n int, err error)- Write to original TTYAvailable() int- Available bytes in bufferSizePixel() (cw int, ch int, pw int, ph int, e error)- Terminal size info
Read(p []byte) (n int, err error)- Blocking read with bufferingAvailable() int- Available bytesClose() error- Close reader
type Window interface {
WindowFind() error
WindowName() string
WindowClass() string
WindowID() uint64
Screenshot() (image.Image, error)
Close() error
}
type Connection interface {
Windows() ([]Window, error)
DisplayImage(img image.Image, windowName string)
Resources() (environ.Properties, error)
Close() error
}NewConn(env environ.Properties) (Connection, error)- Create connectionCreateWindow(name, class, instance string) Window- Create windowSetImpl(impl Implementation) WindowProvider- Set implementation
sixel/- Sixel graphics protocolkitty/- Kitty terminal graphics protocoliterm2/- iTerm2 inline imagesw3mimgdisplay/- w3m image displayurxvt/- urxvt pixbuf extensionterminology/- Terminology image protocolframebuffer/- Direct framebuffer accessx11/- X11 window systemdomterm/- DomTerm HTML renderinggeneric/,generic2/- Generic fallbacksgdiplus/- Windows GDI+sane/- Sane choice of image protocolsall/- All available drawers
creacktty/- Uses github.com/creack/ptygotty/- Default TTY implementationbubbleteatty/- Bubble Tea integrationtcelltty/- tcell integrationttyhook/- TTY hooking systemttymux/- TTY multiplexing (documented above)dumbtty/- Simple TTY implementationuroottty/- u-root integrationbagabastty/- Custom TTY implementationcontdtty/- Container TTYpkgterm/- Package terminal
qdefault/-NewQuerier() term.Querier- Default querier implementation
rdefault/- Default resizer implementationgift/- Uses github.com/disintegration/giftimaging/- Uses github.com/kovidgoyal/imagingbild/- Uses github.com/anthonynsimon/bildcaire/- Uses github.com/esimov/cairenfnt/- Uses github.com/nfnt/resizerez/- Uses github.com/bamiaux/rez
termuiimg/- termui integrationNewImage(tm *term.Terminal, img image.Image, bounds image.Rectangle) (*Image, error)Draw(buf *termui.Buffer)- Render to termui buffer
bubbleteaimg/- Bubble Tea integrationtcellimg/- tcell integrationtviewimg/- tview integration
- Terminal-specific implementations for detection and configuration
- Files:
alacritty.go,kitty.go,xterm.go,konsole.go,foot.go,mintty.go, etc.
mux/- Terminal multiplexer handlingpty/- Pseudo-terminal utilitiesvideo/ffmpeg/- FFmpeg video processingenv/- Environment detectioncmd/- Command-line tools and examples
The library uses Go's init() function pattern where implementation packages register themselves:
- Import side-effect packages (e.g.,
_ "github.com/srlehn/termimg/drawers/all") - Packages register implementations during init()
- Main package provides unified access through registered implementations
- Runtime selection based on terminal capabilities and environment
- Terminal Detection - Identify terminal type and capabilities
- Drawer Selection - Choose optimal image display protocol
- Image Processing - Resize and encode image as needed
- Protocol Execution - Send image data using selected drawer
- TTY Multiplexing - Handle concurrent terminal access if needed
The library is developing a TTY multiplexing system to enable concurrent access to /dev/tty by both termimg and TUI libraries.
This addresses the fundamental problem where multiple libraries cannot simultaneously read from the same TTY device.
Three Approaches Being Explored:
-
TTY Multiplexer (
tty/ttymux/)- Buffer-based multiplexer with
TTYMultiplexer,MultiplexedTTY,BufferedReader - Single reader goroutine distributes input to multiple buffered channels
- Ensures no input loss through comprehensive buffering
- Status: Partially implemented
- Buffer-based multiplexer with
-
TTY Hook System (
tty/ttyhook/)- Function hooking using
github.com/agiledragon/gohook - Intercepts
os.Open("/dev/tty")calls with monkey patching - Returns PTY slaves while termimg keeps real TTY access
- Status: Experimental design phase
- Function hooking using
-
PTY Mirror System
- Creates PTY pairs dynamically when
Mirror()called - Fan-out approach: real TTY → copy goroutine → multiple PTY masters
- Each library gets independent PTY slave for reading
- Status: Planning phase
- Creates PTY pairs dynamically when
Challenge: TUI libraries (termui, bubbletea, tcell) and termimg both need to read from /dev/tty simultaneously for:
- Terminal capability queries (termimg)
- User input handling (TUI library)
- Resize event monitoring
- Mouse/keyboard event processing
Current Limitation: Only one process can effectively read from /dev/tty - others will miss input or block.
Multiplexing Solution: Create isolated input streams for each library while preserving all input data.
- Strategy Pattern - Multiple drawer implementations for different terminals
- Factory Pattern - Terminal creation with configurable providers
- Registry Pattern - Dynamic registration of implementations
- Adapter Pattern - Unified interface for different TTY types
- Observer Pattern - Terminal resize event handling
- Command Pattern - Terminal query/response system
- Multiplexer Pattern - TTY input fan-out to multiple consumers (in development)