This commit is contained in:
retoor 2026-07-08 12:09:57 +02:00
parent 87ae53d2f5
commit fe49f827ba
11 changed files with 50 additions and 35 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.log
# Compilation artifacts
nimcache/
*.o
*.so
# Test / example binaries
tests/test_termimg
examples/demo
# Editor / OS
*.swp
*.swo
*~
.DS_Store

View File

@ -119,11 +119,11 @@ import termimg
# Detect terminal capabilities once at startup # Detect terminal capabilities once at startup
let caps = detectCapabilities() let caps = detectCapabilities()
# Default rendering half-block, contain fit, 90% width # Default rendering - half-block, contain fit, 90% width
let output = renderImageRgba(pixelBytes, 640, 480, caps, defaultOptions()) let output = renderImageRgba(pixelBytes, 640, 480, caps, defaultOptions())
stdout.write(output) stdout.write(output)
# Thumbnail quarter-block, 40 cols max, dithered # Thumbnail - quarter-block, 40 cols max, dithered
let thumb = renderImageRgba(pixelBytes, 640, 480, caps, thumbnailOptions()) let thumb = renderImageRgba(pixelBytes, 640, 480, caps, thumbnailOptions())
stdout.write(thumb) stdout.write(thumb)

View File

@ -1,4 +1,4 @@
## termimg demo full visual showcase of every rendering path. ## termimg demo - full visual showcase of every rendering path.
## ##
## Usage: ## Usage:
## nim c -r --path:src examples/demo.nim ## nim c -r --path:src examples/demo.nim
@ -107,7 +107,7 @@ proc renderSideBySide(images: openArray[seq[uint8]],
when isMainModule: when isMainModule:
echo "" echo ""
echo "╭──────────────────────────────────────────────╮" echo "╭──────────────────────────────────────────────╮"
echo "│ termimg Full Demo │" echo "│ termimg - Full Demo │"
echo "╰──────────────────────────────────────────────╯" echo "╰──────────────────────────────────────────────╯"
echo "" echo ""
@ -165,7 +165,7 @@ when isMainModule:
opts.backgroundRgb, geo.columns, geo.sampleHeight) opts.backgroundRgb, geo.columns, geo.sampleHeight)
stdout.write(outStr) stdout.write(outStr)
stdout.write("\n") stdout.write("\n")
echo " ($1 $2 cols x $3 samples)" % [$density, $geo.columns, $geo.sampleHeight] echo " ($1 - $2 cols x $3 samples)" % [$density, $geo.columns, $geo.sampleHeight]
echo "" echo ""
echo "" echo ""
@ -183,7 +183,7 @@ when isMainModule:
opts.backgroundRgb, geo.columns, geo.sampleHeight, dither = true) opts.backgroundRgb, geo.columns, geo.sampleHeight, dither = true)
stdout.write(outStr) stdout.write(outStr)
stdout.write("\n") stdout.write("\n")
echo " ($1 $2 px, $3 cols x $4 samples)" % [ echo " ($1 - $2 px, $3 cols x $4 samples)" % [
$fit, $geo.pixelWidth & "x" & $geo.pixelHeight, $fit, $geo.pixelWidth & "x" & $geo.pixelHeight,
$geo.columns, $geo.sampleHeight] $geo.columns, $geo.sampleHeight]
echo "" echo ""

View File

@ -2,7 +2,7 @@
version = "1.0.0" version = "1.0.0"
author = "molodetz" author = "molodetz"
description = "Render images in the terminal kitty, sixel, iTerm2, halfblock, and quarterblock protocols" description = "Render images in the terminal - kitty, sixel, iTerm2, halfblock, and quarterblock protocols"
license = "MIT" license = "MIT"
srcDir = "src" srcDir = "src"

View File

@ -1,4 +1,4 @@
## termimg Terminal Image Rendering for Nim ## termimg - Terminal Image Rendering for Nim
## ##
## Picture-perfect image rendering in the native terminal. Renders RGBA ## Picture-perfect image rendering in the native terminal. Renders RGBA
## pixel data using the highest-fidelity protocol the terminal supports. ## pixel data using the highest-fidelity protocol the terminal supports.

View File

@ -44,7 +44,7 @@ proc protocolsFromEnvironment(): set[Protocol] =
proc detectCapabilities*(): TerminalCapabilities = proc detectCapabilities*(): TerminalCapabilities =
## Resolve terminal capabilities: size, cell size, and supported protocols. ## Resolve terminal capabilities: size, cell size, and supported protocols.
## Always returns valid values never crashes even outside a terminal. ## Always returns valid values - never crashes even outside a terminal.
let rawCols = terminalWidth() let rawCols = terminalWidth()
let rawRows = terminalHeight() let rawRows = terminalHeight()
let columns = if rawCols > 0: rawCols else: 80 let columns = if rawCols > 0: rawCols else: 80

View File

@ -1,4 +1,4 @@
## Half-block Unicode renderer universal fallback. ## Half-block Unicode renderer - universal fallback.
## ##
## Uses ▀ (UPPER HALF BLOCK) with 24-bit foreground/background ## Uses ▀ (UPPER HALF BLOCK) with 24-bit foreground/background
## colors. Two vertical samples per character cell. Works in any ## colors. Two vertical samples per character cell. Works in any

View File

@ -1,4 +1,4 @@
## Quarter-block Unicode renderer double-density fallback. ## Quarter-block Unicode renderer - double-density fallback.
## ##
## Uses the Unicode quadrant block characters (▖▗▘▙▚▛▜▝▞▟█ and space) ## Uses the Unicode quadrant block characters (▖▗▘▙▚▛▜▝▞▟█ and space)
## to render 2×2 pixel samples per terminal cell. Each character ## to render 2×2 pixel samples per terminal cell. Each character
@ -23,22 +23,22 @@ type
# 1 = foreground colour, 0 = background colour # 1 = foreground colour, 0 = background colour
const const
QuadChars: array[16, string] = [ QuadChars: array[16, string] = [
" ", # 0000 all background " ", # 0000 - all background
"", # 0001 bottom-right only "", # 0001 - bottom-right only
"", # 0010 bottom-left only "", # 0010 - bottom-left only
"", # 0011 bottom row (both bottom quadrants) "", # 0011 - bottom row (both bottom quadrants)
"", # 0100 top-right only "", # 0100 - top-right only
"", # 0101 top-right + bottom-right (right column) "", # 0101 - top-right + bottom-right (right column)
"", # 0110 top-right + bottom-left (diagonal) "", # 0110 - top-right + bottom-left (diagonal)
"", # 0111 all except top-left "", # 0111 - all except top-left
"", # 1000 top-left only "", # 1000 - top-left only
"", # 1001 all except top-right "", # 1001 - all except top-right
"", # 1010 top-left + bottom-left (left column) "", # 1010 - top-left + bottom-left (left column)
"", # 1011 all except bottom-right "", # 1011 - all except bottom-right
"", # 1100 top row (both top quadrants) "", # 1100 - top row (both top quadrants)
"", # 1101 left column (top-left + bottom-left) "", # 1101 - left column (top-left + bottom-left)
"", # 1110 right column (top-right + bottom-right) "", # 1110 - right column (top-right + bottom-right)
"", # 1111 all foreground "", # 1111 - all foreground
] ]
proc foreground(color: Color): string = proc foreground(color: Color): string =

View File

@ -1,4 +1,4 @@
## Terminal image renderer main API and protocol selector. ## Terminal image renderer - main API and protocol selector.
## ##
## Auto-detects terminal capabilities and renders images using the ## Auto-detects terminal capabilities and renders images using the
## best available protocol. The priority order is: ## best available protocol. The priority order is:

View File

@ -1,4 +1,4 @@
## Terminal image rendering shared types and geometry calculations. ## Terminal image rendering - shared types and geometry calculations.
import std/math import std/math

View File

@ -1,4 +1,4 @@
## termimg test verifies protocol detection, geometry, scaling, ## termimg test - verifies protocol detection, geometry, scaling,
## halfblock, quarterblock, dithering, and aspect-ratio preservation. ## halfblock, quarterblock, dithering, and aspect-ratio preservation.
## ##
## Run: nim c -r --path:src tests/test_termimg.nim ## Run: nim c -r --path:src tests/test_termimg.nim
@ -250,7 +250,7 @@ when isMainModule:
echo " renderImageRaw returned ", outStr.len, " chars (no crash, pass)" echo " renderImageRaw returned ", outStr.len, " chars (no crash, pass)"
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# ADVANCED VISUAL TESTS Gallery, columns, comparison grids # ADVANCED VISUAL TESTS - Gallery, columns, comparison grids
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
# ── Pattern generators ──────────────────────────────────────── # ── Pattern generators ────────────────────────────────────────
@ -315,7 +315,7 @@ when isMainModule:
let rnb = rainbowBars(patternW, patternH) let rnb = rainbowBars(patternW, patternH)
let tgt = targetCircle(patternW, patternH) let tgt = targetCircle(patternW, patternH)
# ── Test 17: Gallery three patterns side-by-side ──────────── # ── Test 17: Gallery - three patterns side-by-side ────────────
echo "\n── Test 17: Gallery (three patterns, same width) ──" echo "\n── Test 17: Gallery (three patterns, same width) ──"
block: block:
let colW = (caps.columns - 4) div 3 let colW = (caps.columns - 4) div 3
@ -347,7 +347,7 @@ when isMainModule:
echo " gallery: ", geo.columns, " cols each × ", echo " gallery: ", geo.columns, " cols each × ",
linesChk.len, " rows (3 across) (pass)" linesChk.len, " rows (3 across) (pass)"
# ── Test 18: Column layout same pattern at 3 densities ────── # ── Test 18: Column layout - same pattern at 3 densities ──────
echo "\n── Test 18: Column layout (3 densities stacked) ──" echo "\n── Test 18: Column layout (3 densities stacked) ──"
block: block:
let colW = caps.columns - 2 let colW = caps.columns - 2
@ -402,7 +402,7 @@ when isMainModule:
stdout.write(outW) stdout.write(outW)
stdout.write("\n\n") stdout.write("\n\n")
stdout.flushFile() stdout.flushFile()
# Tall: swap dimensions (64x48 becomes 48x64 via the pattern? no use a tall pattern) # Tall: swap dimensions (64x48 becomes 48x64 via the pattern? no - use a tall pattern)
let tallPixels = checkerboard(patternH, patternW, 6) # 48x64 let tallPixels = checkerboard(patternH, patternW, 6) # 48x64
var optsT = defaultOptions() var optsT = defaultOptions()
optsT.maxHeight = caps.rows - 4 optsT.maxHeight = caps.rows - 4
@ -446,7 +446,7 @@ when isMainModule:
# Exactly 1 byte extra (data.len == w*h*4 + 1) # Exactly 1 byte extra (data.len == w*h*4 + 1)
let extra1 = newSeq[uint8](pw * ph * 4 + 1) let extra1 = newSeq[uint8](pw * ph * 4 + 1)
let out2 = renderImageRgba(extra1, pw, ph, caps, defaultOptions()) let out2 = renderImageRgba(extra1, pw, ph, caps, defaultOptions())
# Extra byte is harmless we only read w*h*4 from front # Extra byte is harmless - we only read w*h*4 from front
doAssert out2.len > 0 or caps.protocols == {ptHalfBlock}, doAssert out2.len > 0 or caps.protocols == {ptHalfBlock},
"1-byte-extra buffer should render normally (output len=" & $out2.len & ")" "1-byte-extra buffer should render normally (output len=" & $out2.len & ")"