## termimg demo - full visual showcase of every rendering path.
##
## Usage:
## nim c -r --path:src examples/demo.nim
##
## Shows: scanner pattern, rainbow gradient, target circles,
## gallery layout (3 across), column layout (3 densities stacked),
## fit-mode comparison grid, and individual renders for each protocol.
import std/[strutils, math]
import termimg
# ── Pattern generators ──────────────────────────────────────────
proc checkerboard(w, h, csize: int): seq[uint8] =
result = newSeq[uint8](w * h * 4)
for y in 0 ..< h:
for x in 0 ..< w:
let off = (y * w + x) * 4
let bright = if ((x div csize) + (y div csize)) mod 2 == 0: 220 else: 40
result[off + 0] = uint8(bright)
result[off + 1] = uint8(bright)
result[off + 2] = uint8(bright)
result[off + 3] = 255
proc rainbowBars(w, h: int): seq[uint8] =
result = newSeq[uint8](w * h * 4)
for y in 0 ..< h:
for x in 0 ..< w:
let off = (y * w + x) * 4
let band = float(x) / float(w) * 6.0
let iband = int(band)
let frac = band - float(iband)
var r, g, b: float
case iband
of 0: (r, g, b) = (1.0, frac, 0.0)
of 1: (r, g, b) = (1.0 - frac, 1.0, 0.0)
of 2: (r, g, b) = (0.0, 1.0, frac)
of 3: (r, g, b) = (0.0, 1.0 - frac, 1.0)
of 4: (r, g, b) = (frac, 0.0, 1.0)
else: (r, g, b) = (1.0, 0.0, 1.0 - frac)
let ymul = 0.6 + 0.4 * float(y) / float(h)
result[off + 0] = uint8(clamp(int(r * 255.0 * ymul), 0, 255))
result[off + 1] = uint8(clamp(int(g * 255.0 * ymul), 0, 255))
result[off + 2] = uint8(clamp(int(b * 255.0 * ymul), 0, 255))
result[off + 3] = 255
proc targetCircle(w, h: int): seq[uint8] =
result = newSeq[uint8](w * h * 4)
let cx = float(w) / 2.0
let cy = float(h) / 2.0
let maxR = min(cx, cy)
for y in 0 ..< h:
for x in 0 ..< w:
let off = (y * w + x) * 4
let dx = float(x) - cx
let dy = float(y) - cy
let r = sqrt(dx * dx + dy * dy)
let ring = int(r / (maxR / 5.0)) mod 2
if ring == 0:
result[off + 0] = 200; result[off + 1] = 50; result[off + 2] = 50
else:
result[off + 0] = 255; result[off + 1] = 255; result[off + 2] = 255
result[off + 3] = 255
proc warmGlow(w, h: int): seq[uint8] =
result = newSeq[uint8](w * h * 4)
let cx = float(w) / 2.0
let cy = float(h) / 2.0
# maxR intentionally omitted: warmGlow normalizes by cx,cy directly
for y in 0 ..< h:
for x in 0 ..< w:
let off = (y * w + x) * 4
let dx = (float(x) - cx) / cx
let dy = (float(y) - cy) / cy
let dist = sqrt(dx * dx + dy * dy)
let glow = clamp(int(255.0 * exp(-dist * 2.5)), 0, 255)
result[off + 0] = uint8(glow)
result[off + 1] = uint8(glow * 3 div 5)
result[off + 2] = uint8(glow div 3)
result[off + 3] = 255
# ── Side-by-side render helper ─────────────────────────────────
proc renderSideBySide(images: openArray[seq[uint8]],
widths: openArray[int],
heights: openArray[int],
caps: TerminalCapabilities,
opts: RenderOptions,
gap: int = 2): string =
## Renders N images at the same geometry and stitches them row-by-row.
let geo = computeGeometry(widths[0], heights[0], caps, opts)
var parts = newSeq[(seq[string], int)]() # (lines, frames_consumed)
for i in 0 ..< images.len:
let outStr = renderHalfBlock(
ImageData(width: widths[i], height: heights[i], data: images[i]),
opts.backgroundRgb, geo.columns, geo.sampleHeight)
parts.add (outStr.split('\n'), 0)
let maxRows = min(min(parts[0][0].len, parts[1][0].len),
(if parts.len > 2: parts[2][0].len else: parts[1][0].len))
for row in 0 ..< maxRows:
for i in 0 ..< parts.len:
if row < parts[i][0].len:
result.add parts[i][0][row]
if i < parts.len - 1:
result.add spaces(gap)
result.add "\n"
when isMainModule:
echo ""
echo "╭──────────────────────────────────────────────╮"
echo "│ termimg - Full Demo │"
echo "╰──────────────────────────────────────────────╯"
echo ""
let caps = detectCapabilities()
echo "Terminal: ", caps.columns, "x", caps.rows, " cells"
echo "Cell: ", caps.cell.width, "x", caps.cell.height, " px"
echo ""
echo "Detected protocols:"
for proto in [ptKitty, ptIterm2, ptSixel, ptQuarterBlock, ptHalfBlock]:
let mark = if caps.supports(proto): "" else: ""
echo " ", ($proto).align(14), mark
echo ""
# ── Pattern generation ──────────────────────────────────────────
const patternW = 64
const patternH = 48
let chk = checkerboard(patternW, patternH, 8)
let rnb = rainbowBars(patternW, patternH)
let tgt = targetCircle(patternW, patternH)
let glow = warmGlow(patternW, patternH)
# ── 1. Gallery: 3 patterns side-by-side ─────────────────────────
echo "── Gallery: 3 patterns side-by-side ──"
var optsGal = defaultOptions()
optsGal.maxWidth = (caps.columns - 4) div 3
optsGal.fit = fmWidth
stdout.write(renderSideBySide([chk, rnb, tgt], [patternW, patternW, patternW],
[patternH, patternH, patternH], caps, optsGal))
echo ""
# ── 2. All four patterns in a column (thumbnail pipeline) ──────
echo "── All four patterns (thumbnail pipeline) ──"
let thumbOpts = thumbnailOptions()
for (name, pix) in [("Checkerboard", chk), ("Rainbow bars", rnb),
("Target circles", tgt), ("Warm glow", glow)]:
echo " ", name, ":"
let outStr = renderImageRgba(pix, patternW, patternH, caps, thumbOpts)
stdout.write(outStr)
stdout.write("\n")
echo ""
# ── 3. Column layout: rainbow at 3 densities for comparison ────
echo "── Column layout: rainbow at 3 block densities ──"
let colW = caps.columns - 2
for density in [bdFull, bdHalf, bdQuarter]:
var opts = defaultOptions()
opts.density = density
opts.maxWidth = colW
opts.fit = fmWidth
let geo = computeGeometry(patternW, patternH, caps, opts)
let outStr = renderHalfBlock(
ImageData(width: patternW, height: patternH, data: rnb),
opts.backgroundRgb, geo.columns, geo.sampleHeight)
stdout.write(outStr)
stdout.write("\n")
echo " ($1 - $2 cols x $3 samples)" % [$density, $geo.columns, $geo.sampleHeight]
echo ""
echo ""
# ── 4. Fit-mode comparison grid (quarter-block, dithered) ──────
echo "── Fit-mode grid: target circles at 4 fit modes ──"
let halfW = caps.columns div 2 - 1
for fit in [fmContain, fmWidth, fmOriginal, fmStretch]:
var opts = defaultOptions()
opts.fit = fit
opts.maxWidth = halfW
opts.density = bdQuarter
let geo = computeGeometry(patternW, patternH, caps, opts)
let outStr = renderQuarterBlock(
ImageData(width: patternW, height: patternH, data: tgt),
opts.backgroundRgb, geo.columns, geo.sampleHeight, dither = true)
stdout.write(outStr)
stdout.write("\n")
echo " ($1 - $2 px, $3 cols x $4 samples)" % [
$fit, $geo.pixelWidth & "x" & $geo.pixelHeight,
$geo.columns, $geo.sampleHeight]
echo ""
echo ""
# ── 5. Individual renders per protocol ──────────────────────────
echo "── Default render (half-block, contain fit) ──"
let out1 = renderImageRgba(chk, patternW, patternH, caps, defaultOptions())
stdout.write(out1)
stdout.write("\n")
echo ""
echo "── Quarter-block thumbnail (40 cols, dithered) ──"
let out2 = renderImageRgba(rnb, patternW, patternH, caps, thumbnailOptions())
stdout.write(out2)
stdout.write("\n")
echo ""
# ── 6. Extreme aspect-ratio demo ────────────────────────────────
echo "── Wide vs tall: checkerboard at extreme ratios ──"
var optsW = defaultOptions()
optsW.maxWidth = caps.columns - 2
optsW.fit = fmWidth
let geoW = computeGeometry(patternW, patternH, caps, optsW)
let outW = renderHalfBlock(
ImageData(width: patternW, height: patternH, data: chk),
optsW.backgroundRgb, geoW.columns, geoW.sampleHeight)
stdout.write(outW)
echo " (wide: $1 cols x $2 samples)" % [$geoW.columns, $geoW.sampleHeight]
echo ""
let tallPixels = checkerboard(patternH, patternW, 6) # 48x64
var optsT = defaultOptions()
optsT.maxHeight = caps.rows - 4
optsT.fit = fmContain
let geoT = computeGeometry(patternH, patternW, caps, optsT)
let outT = renderHalfBlock(
ImageData(width: patternH, height: patternW, data: tallPixels),
optsT.backgroundRgb, geoT.columns, geoT.sampleHeight)
stdout.write(outT)
echo " (tall: $1 cols x $2 samples)" % [$geoT.columns, $geoT.sampleHeight]
echo ""
echo "╭──────────────────────────────────────────────╮"
echo "│ Demo complete. All renders done. │"
echo "╰──────────────────────────────────────────────╯"