FrameBuffer
FrameBuffer exposes a low-level, two-dimensional cell buffer for custom graphics. Use Image to display encoded images without direct cell drawing.
Availability#
| Field | Availability |
|---|---|
| Package | @opentui/core |
| Core renderable | FrameBufferRenderable |
| React | Unavailable |
| Solid | Unavailable |
| Status | Advanced |
Basic usage#
Renderable API#
import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer()
const canvas = new FrameBufferRenderable(renderer, {
id: "canvas",
width: 50,
height: 20,
})
// Draw on the frame buffer
canvas.frameBuffer.fillRect(5, 2, 20, 10, RGBA.fromHex("#FF0000"))
canvas.frameBuffer.drawText("Hello!", 8, 6, RGBA.fromHex("#FFFFFF"))
renderer.root.add(canvas)The renderable creates frameBuffer in the renderer’s Context. Native code composes it into each frame at the
renderable’s position. Call requestRender() after you draw so that the renderer paints the change.
When layout changes the renderable’s size, the renderable resizes frameBuffer. A resize clears all cells, so draw
again after it.
Destroying the renderable also destroys its current frameBuffer. You can assign another buffer to frameBuffer.
Create it with owner: renderer.nativeScene. The renderable does not destroy the buffer that you replace.
Drawing methods#
setCell#
Set a single cell’s content and colors:
canvas.frameBuffer.setCell(
x, // X position
y, // Y position
char, // One-cell scalar to display
fg, // Foreground color (RGBA)
bg, // Background color (RGBA)
attributes, // Text attributes (optional, default: 0)
)
// Example
canvas.frameBuffer.setCell(10, 5, "@", RGBA.fromHex("#FFFF00"), RGBA.fromHex("#000000"))setCell() uses only the first code point and does not reserve continuation cells. Use it for one-cell scalars. Use drawText() for wide or joined graphemes.
Draw text and block characters directly to build a compact chart:
setCellWithAlphaBlending#
Set a cell with alpha blending for transparency effects:
const semiTransparent = RGBA.fromValues(1.0, 0.0, 0.0, 0.5)
const transparent = RGBA.fromValues(0, 0, 0, 0)
canvas.frameBuffer.setCellWithAlphaBlending(10, 5, " ", transparent, semiTransparent)drawText#
Draw a string of text at a position:
canvas.frameBuffer.drawText(
text, // String to draw
x, // Starting X position
y, // Y position
fg, // Text color (RGBA)
bg, // Background color (RGBA, optional)
attributes, // Text attributes (optional, default: 0)
)
// Example
canvas.frameBuffer.drawText("Score: 100", 2, 1, RGBA.fromHex("#00FF00"))fillRect#
Fill a rectangular area with a color:
canvas.frameBuffer.fillRect(
x, // X position
y, // Y position
width, // Rectangle width
height, // Rectangle height
color, // Fill color (RGBA)
)
// Example: Draw a red rectangle
canvas.frameBuffer.fillRect(10, 5, 20, 8, RGBA.fromHex("#FF0000"))drawFrameBuffer#
Composite another buffer onto this one. The source must be an owned buffer in the same Context:
canvas.frameBuffer.drawFrameBuffer(
destX, // Destination X
destY, // Destination Y
sourceBuffer, // Source FrameBuffer (OptimizedBuffer)
sourceX, // Source X offset (optional)
sourceY, // Source Y offset (optional)
sourceWidth, // Width to copy (optional)
sourceHeight, // Height to copy (optional)
)drawImage#
Place a native image into destination cells:
const placed = canvas.frameBuffer.drawImage(
image,
x,
y,
width,
height,
pixelWidth,
pixelHeight,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
protocol,
)x, y, width, and height use terminal cells. pixelWidth and pixelHeight use terminal pixels and default to 0. Source coordinates default to the full image, and protocol defaults to "auto". Sixel requires nonzero pixel dimensions or falls back to blocks.
false means the buffer recorded no visible valid placement. A successful placement retains the native image until the buffer is cleared, resized, or destroyed. Ownership does not transfer. The caller must still dispose its NativeImage.
colorMatrix / colorMatrixUniform#
Apply native 4x4 RGBA matrix transforms for post-processing effects. Use colorMatrixUniform for full-buffer
transforms, and colorMatrix when you want to target specific cells.
import { INVERT_MATRIX, TargetChannel } from "@opentui/core"
// Full-buffer transform
canvas.frameBuffer.colorMatrixUniform(INVERT_MATRIX, 1.0, TargetChannel.Both)
// Per-cell transform with explicit mask
const cellMask = new Float32Array([10, 5, 1.0, 11, 5, 0.5])
canvas.frameBuffer.colorMatrix(INVERT_MATRIX, cellMask, 1.0, TargetChannel.FG)See Color matrix reference for matrix layout, mask format, and behavior details.
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
width |
number |
- | Initial buffer and layout width in cells (required) |
height |
number |
- | Initial buffer and layout height in rows (required) |
respectAlpha |
boolean |
false |
Always blend the buffer’s cells into the frame |
position |
string |
"relative" |
Positioning mode |
left, top, right, bottom |
number, "auto", or percentage string |
- | Position offsets |
Example: game canvas#
import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"
const renderer = await createCliRenderer()
const gameCanvas = new FrameBufferRenderable(renderer, {
id: "game",
width: 40,
height: 20,
position: "absolute",
left: 5,
top: 2,
})
// Game state
let playerX = 20
let playerY = 10
function render() {
const fb = gameCanvas.frameBuffer
const BG = RGBA.fromHex("#111111")
// Clear the canvas
fb.fillRect(0, 0, 40, 20, BG)
// Draw border
for (let x = 0; x < 40; x++) {
fb.setCell(x, 0, "-", RGBA.fromHex("#444444"), BG)
fb.setCell(x, 19, "-", RGBA.fromHex("#444444"), BG)
}
for (let y = 0; y < 20; y++) {
fb.setCell(0, y, "|", RGBA.fromHex("#444444"), BG)
fb.setCell(39, y, "|", RGBA.fromHex("#444444"), BG)
}
// Draw player
fb.setCell(playerX, playerY, "@", RGBA.fromHex("#00FF00"), BG)
// Draw score
fb.drawText("Score: 0", 2, 0, RGBA.fromHex("#FFFF00"))
}
// Handle input
renderer.keyInput.on("keypress", (key) => {
switch (key.name) {
case "up":
playerY = Math.max(1, playerY - 1)
break
case "down":
playerY = Math.min(18, playerY + 1)
break
case "left":
playerX = Math.max(1, playerX - 1)
break
case "right":
playerX = Math.min(38, playerX + 1)
break
}
render()
gameCanvas.requestRender()
})
render()
renderer.root.add(gameCanvas)Example: progress bar#
const EMPTY_BG = RGBA.fromHex("#222222")
function drawProgressBar(fb, x, y, width, progress, color) {
const filled = Math.floor(width * progress)
// Draw filled portion
for (let i = 0; i < filled; i++) {
fb.setCell(x + i, y, "█", color, EMPTY_BG)
}
// Draw empty portion
for (let i = filled; i < width; i++) {
fb.setCell(x + i, y, "░", RGBA.fromHex("#333333"), EMPTY_BG)
}
}
// Usage
drawProgressBar(canvas.frameBuffer, 5, 10, 30, 0.75, RGBA.fromHex("#00FF00"))A 20-cell bar with 14 filled cells renders as:
Related APIs#
Read the Buffer API for OptimizedBuffer ownership and drawing operations. Use NativeImage to decode and transform image data. The color matrix reference covers post-processing matrix and mask formats.