## termimg test — verifies protocol detection, geometry, scaling,
## halfblock, quarterblock, dithering, and aspect-ratio preservation.
##
## Run: nim c -r --path:src tests/test_termimg.nim
import std / [ strutils , math ]
import termimg
when isMainModule :
echo " === termimg Advanced Test Suite === "
# ── Capabilities ──────────────────────────────────────────────
let caps = detectCapabilities ( )
echo " Terminal: " , caps . columns , " x " , caps . rows , " cells "
echo " Cell: " , caps . cell . width , " x " , caps . cell . height , " px "
echo " Protocols: "
for proto in [ ptKitty , ptIterm2 , ptSixel , ptQuarterBlock , ptHalfBlock ] :
echo " " , proto , if caps . supports ( proto ) : " ✓ " else : " ✗ "
# ── Build a test pattern ─────────────────────────────────────
# 32x16 RGBA: gradient bands + sharp edges to test scaling quality
const
pw = 32
ph = 16
var testPixels = newSeq [ uint8 ] ( pw * ph * 4 )
for y in 0 .. < ph :
for x in 0 .. < pw :
let off = ( y * pw + x ) * 4
let r = uint8 ( 255 * x div ( pw - 1 ) )
let g = uint8 ( 0 )
let b = uint8 ( 255 * ( pw - 1 - x ) div ( pw - 1 ) )
let brightness = uint8 ( 128 + 127 * y div ( ph - 1 ) )
testPixels [ off + 0 ] = uint8 ( ( int ( r ) * int ( brightness ) ) div 255 )
testPixels [ off + 1 ] = uint8 ( ( int ( g ) * int ( brightness ) ) div 255 )
testPixels [ off + 2 ] = uint8 ( ( int ( b ) * int ( brightness ) ) div 255 )
testPixels [ off + 3 ] = 255 'u 8
# White cross for edge-testing
let cx = pw div 2
let cy = ph div 2
for i in 0 .. < pw :
let offH = ( cy * pw + i ) * 4
testPixels [ offH + 0 ] = 255 ; testPixels [ offH + 1 ] = 255
testPixels [ offH + 2 ] = 255 ; testPixels [ offH + 3 ] = 255
for i in 0 .. < ph :
let offV = ( i * pw + cx ) * 4
testPixels [ offV + 0 ] = 255 ; testPixels [ offV + 1 ] = 255
testPixels [ offV + 2 ] = 255 ; testPixels [ offV + 3 ] = 255
# ── Test 1: Default halfblock ────────────────────────────────
echo " \n ── Test 1: Half-block (default, contain fit) ── "
block :
let opts = defaultOptions ( )
let geo = computeGeometry ( pw , ph , caps , opts )
echo " geometry: " , geo . columns , " cols x " , geo . sampleHeight ,
" samples ( " , geo . pixelWidth , " x " , geo . pixelHeight , " px) "
let outStr = renderHalfBlock (
ImageData ( width : pw , height : ph , data : testPixels ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines, " ,
( if lines . len > 0 : lines [ 0 ] . len else : 0 ) , " chars first line "
# ── Test 2: Quarter-block thumbnail ──────────────────────────
echo " \n ── Test 2: Quarter-block thumbnail ── "
block :
var opts = thumbnailOptions ( )
let geo = computeGeometry ( pw , ph , caps , opts )
echo " geometry: " , geo . columns , " cols x " , geo . sampleHeight , " samples "
let outStr = renderQuarterBlock (
ImageData ( width : pw , height : ph , data : testPixels ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight , dither = true )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines, " ,
( if lines . len > 0 : lines [ 0 ] . len else : 0 ) , " chars first line "
# ── Test 3: Full-density (bdFull) ────────────────────────────
echo " \n ── Test 3: Full-density (bdFull, 10 cols) ── "
block :
var opts = defaultOptions ( )
opts . density = bdFull
opts . maxWidth = 10
let geo = computeGeometry ( pw , ph , caps , opts )
echo " geometry: " , geo . columns , " cols x " , geo . sampleHeight , " samples "
let outStr = renderHalfBlock (
ImageData ( width : pw , height : ph , data : testPixels ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines "
# ── Test 4: Half-block with dithering ────────────────────────
echo " \n ── Test 4: Half-block with dithering ── "
block :
var opts = defaultOptions ( )
opts . dither = true
opts . maxWidth = 20
let geo = computeGeometry ( pw , ph , caps , opts )
echo " geometry: " , geo . columns , " cols x " , geo . sampleHeight , " samples "
let outStr = renderHalfBlock (
ImageData ( width : pw , height : ph , data : testPixels ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight , dither = true )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines "
# ── Test 5: Aspect ratio preservation ────────────────────────
echo " \n ── Test 5: Aspect ratio ── "
block :
let geo = computeGeometry ( 640 , 200 , caps , defaultOptions ( ) )
let ratio = float ( geo . pixelWidth ) / float ( geo . pixelHeight )
echo " 640x200 → " , geo . pixelWidth , " x " , geo . pixelHeight ,
" px (ratio " , ratio . formatFloat ( ffDecimal , 2 ) , " , expected ~3.2) "
let geo2 = computeGeometry ( 200 , 640 , caps , defaultOptions ( ) )
let ratio2 = float ( geo2 . pixelWidth ) / float ( geo2 . pixelHeight )
echo " 200x640 → " , geo2 . pixelWidth , " x " , geo2 . pixelHeight ,
" px (ratio " , ratio2 . formatFloat ( ffDecimal , 2 ) , " , expected ~0.31) "
let geo3 = computeGeometry ( 500 , 500 , caps , defaultOptions ( ) )
let ratio3 = float ( geo3 . pixelWidth ) / float ( geo3 . pixelHeight )
echo " 500x500 → " , geo3 . pixelWidth , " x " , geo3 . pixelHeight ,
" px (ratio " , ratio3 . formatFloat ( ffDecimal , 2 ) , " , expected ~1.0) "
# ── Test 6: Fit modes ────────────────────────────────────────
echo " \n ── Test 6: Fit modes ── "
block :
for fit in [ fmContain , fmStretch , fmWidth , fmOriginal , fmCellExact ] :
var opts = defaultOptions ( )
opts . fit = fit
opts . maxWidth = 30
let geo = computeGeometry ( pw , ph , caps , opts )
echo " " , fit , " : " , geo . pixelWidth , " x " , geo . pixelHeight ,
" px, " , geo . columns , " cols, " , geo . sampleHeight , " samples "
# ── Test 7: Width-constrained render ─────────────────────────
echo " \n ── Test 7: Width-constrained ── "
block :
var opts = defaultOptions ( )
opts . maxWidth = 8
opts . fit = fmWidth
let geo = computeGeometry ( pw , ph , caps , opts )
echo " 32x16 forced to 8 cols → " , geo . columns , " cols x " ,
geo . sampleHeight , " samples "
let outStr = renderHalfBlock (
ImageData ( width : pw , height : ph , data : testPixels ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines "
# ── Test 8: Bilinear scaling ────────────────────────────────
echo " \n ── Test 8: Bilinear scaling ── "
block :
let img = ImageData ( width : pw , height : ph , data : testPixels )
let scaled = bilinearResizeRgba ( img , 64 , 32 )
let expected = 64 * 32 * 4
echo " " , pw , " x " , ph , " → 64x32: " , scaled . len , " bytes (expected " , expected , " ) "
doAssert scaled . len = = expected
# ── Test 9: Scaling down ─────────────────────────────────────
echo " \n ── Test 9: Scaling down ── "
block :
let img = ImageData ( width : pw , height : ph , data : testPixels )
let scaled = bilinearResizeRgba ( img , 8 , 4 )
let expected = 8 * 4 * 4
echo " " , pw , " x " , ph , " → 8x4: " , scaled . len , " bytes (expected " , expected , " ) "
doAssert scaled . len = = expected
# ── Test 10: Rgba renderer pipeline ──────────────────────────
echo " \n ── Test 10: Full renderImageRgba pipeline ── "
block :
let opts = defaultOptions ( )
let outStr = renderImageRgba ( testPixels , pw , ph , caps , opts )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines "
# ── Test 11: thumbnailOptions preset ──────────────────────────
echo " \n ── Test 11: thumbnailOptions pipeline ── "
block :
let opts = thumbnailOptions ( )
let outStr = renderImageRgba ( testPixels , pw , ph , caps , opts )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
let lines = outStr . split ( " \n " )
echo " output: " , lines . len , " lines "
# ── Test 12: Zero dimensions ──────────────────────────────────
echo " \n ── Test 12: Zero dimensions (should not crash) ── "
block :
let out1 = renderImageRgba ( testPixels , 0 , ph , caps , defaultOptions ( ) )
doAssert out1 . len = = 0 , " zero width should return empty "
let out2 = renderImageRgba ( testPixels , pw , 0 , caps , defaultOptions ( ) )
doAssert out2 . len = = 0 , " zero height should return empty "
let out3 = renderImageRgba ( newSeq [ uint8 ] ( 0 ) , 0 , 0 , caps , defaultOptions ( ) )
doAssert out3 . len = = 0 , " zero dims empty data should return empty "
echo " all zero-dim cases returned empty string (pass) "
# ── Test 13: Short RGBA buffer ────────────────────────────────
echo " \n ── Test 13: Short RGBA buffer (should not crash) ── "
block :
let shortData = newSeq [ uint8 ] ( 4 ) # only one pixel claimed as 10x10
let outStr = renderImageRgba ( shortData , 10 , 10 , caps , defaultOptions ( ) )
doAssert outStr . len = = 0 , " short buffer should return empty (invalid input) "
echo " short buffer returned empty (safe): " , outStr . len , " chars (pass) "
# ── Test 14: Negative dimensions ──────────────────────────────
echo " \n ── Test 14: Negative dimensions (should not crash) ── "
block :
let out1 = renderImageRgba ( testPixels , - 1 , ph , caps , defaultOptions ( ) )
doAssert out1 . len = = 0
let out2 = renderImageRgba ( testPixels , pw , - 5 , caps , defaultOptions ( ) )
doAssert out2 . len = = 0
echo " all negative-dim cases returned empty string (pass) "
# ── Test 15: Non-terminal environment ─────────────────────────
echo " \n ── Test 15: Non-terminal environment (no crash) ── "
block :
let fakeCaps = TerminalCapabilities (
columns : 0 , rows : 0 ,
cell : CellSize ( width : 10 , height : 20 ) ,
protocols : { ptHalfBlock } ,
)
let geo = computeGeometry ( pw , ph , fakeCaps , defaultOptions ( ) )
doAssert geo . columns > = 1 , " geometry must have at least 1 column "
doAssert geo . sampleHeight > = 1 , " geometry must have at least 1 sample row "
echo " fake caps (0x0): " , geo . columns , " cols x " , geo . sampleHeight ,
" samples (pass) "
# ── Test 16: Empty raw bytes ──────────────────────────────────
echo " \n ── Test 16: Empty raw bytes (should not crash) ── "
block :
let outStr = renderImageRaw ( newSeq [ uint8 ] ( 0 ) , 100 , 100 , caps , defaultOptions ( ) )
echo " renderImageRaw returned " , outStr . len , " chars (no crash, pass) "
# ═══════════════════════════════════════════════════════════════
# ADVANCED VISUAL TESTS — Gallery, columns, comparison grids
# ═══════════════════════════════════════════════════════════════
# ── 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
echo " \n ═══════════════════════════════════════════════ "
echo " ADVANCED VISUAL TESTS "
echo " ═══════════════════════════════════════════════ "
let patternW = 64
let patternH = 48
let chk = checkerboard ( patternW , patternH , 8 )
let rnb = rainbowBars ( patternW , patternH )
let tgt = targetCircle ( patternW , patternH )
# ── Test 17: Gallery — three patterns side-by-side ────────────
echo " \n ── Test 17: Gallery (three patterns, same width) ── "
block :
let colW = ( caps . columns - 4 ) div 3
var opts = defaultOptions ( )
opts . maxWidth = colW
opts . fit = fmWidth
let geo = computeGeometry ( patternW , patternH , caps , opts )
# Side-by-side: render left, mid, right and concatenate each row
let outChk = renderHalfBlock (
ImageData ( width : patternW , height : patternH , data : chk ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
let outRnb = renderHalfBlock (
ImageData ( width : patternW , height : patternH , data : rnb ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
let outTgt = renderHalfBlock (
ImageData ( width : patternW , height : patternH , data : tgt ) ,
opts . backgroundRgb , geo . columns , geo . sampleHeight )
let linesChk = outChk . split ( ' \n ' )
let linesRnb = outRnb . split ( ' \n ' )
let linesTgt = outTgt . split ( ' \n ' )
for i in 0 .. < min ( min ( linesChk . len , linesRnb . len ) , linesTgt . len ) :
stdout . write ( linesChk [ i ] )
stdout . write ( " " )
stdout . write ( linesRnb [ i ] )
stdout . write ( " " )
stdout . write ( linesTgt [ i ] )
stdout . write ( " \n " )
stdout . flushFile ( )
echo " gallery: " , geo . columns , " cols each × " ,
linesChk . len , " rows (3 across) (pass) "
# ── Test 18: Column layout — same pattern at 3 densities ──────
echo " \n ── Test 18: Column layout (3 densities stacked) ── "
block :
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 " )
stdout . flushFile ( )
echo " density= $1 : $2 cols × $3 samples (pass) " % [
$ density , $ geo . columns , $ geo . sampleHeight ]
echo " column layout: 3 densities stacked (pass) "
# ── Test 19: Fit-mode comparison grid ─────────────────────────
echo " \n ── Test 19: Fit-mode comparison grid ── "
block :
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 " )
stdout . flushFile ( )
echo " $1 : $2 px ( $3 cols × $4 samples) (pass) " % [
$ fit , $ geo . pixelWidth & " x " & $ geo . pixelHeight ,
$ geo . columns , $ geo . sampleHeight ]
echo " fit-mode grid: 4 fits stacked (pass) "
# ── Test 20: Checkerboard at ultra-wide vs tall ───────────────
echo " \n ── Test 20: Extreme aspect ratios (wide vs tall) ── "
block :
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 )
stdout . write ( " \n \n " )
stdout . flushFile ( )
# Tall: swap dimensions (64x48 becomes 48x64 via the pattern? no — use a tall pattern)
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 )
stdout . write ( " \n " )
stdout . flushFile ( )
echo " wide: " , geoW . columns , " cols × " , geoW . sampleHeight , " samples "
echo " tall: " , geoT . columns , " cols × " , geoT . sampleHeight , " samples (pass) "
# ── Test 21: Pipeline with all three patterns ─────────────────
echo " \n ── Test 21: Pipeline gallery (renderImageRgba) ── "
block :
for ( name , pix ) in [ ( " checkerboard " , chk ) , ( " rainbow " , rnb ) , ( " target " , tgt ) ] :
let outStr = renderImageRgba ( pix , patternW , patternH , caps , thumbnailOptions ( ) )
stdout . write ( outStr )
stdout . write ( " \n " )
stdout . flushFile ( )
echo " $1 : $2 lines rendered (pass) " % [ name , $ ( outStr . split ( ' \n ' ) . len ) ]
# ═══════════════════════════════════════════════════════════════
# WORST-CASE / CORRUPTION TESTS
# ═══════════════════════════════════════════════════════════════
echo " \n ═══════════════════════════════════════════════ "
echo " WORST-CASE / CORRUPTION TESTS "
echo " ═══════════════════════════════════════════════ "
# ── Test 22: Off-by-one data ──────────────────────────────────
echo " \n ── Test 22: Off-by-one data (should not crash) ── "
block :
# Exactly 1 byte short (data.len == w*h*4 - 1)
let shortBy1 = newSeq [ uint8 ] ( pw * ph * 4 - 1 )
let out1 = renderImageRgba ( shortBy1 , pw , ph , caps , defaultOptions ( ) )
doAssert out1 . len = = 0 ,
" 1-byte-short buffer should return empty, got " & $ out1 . len & " chars "
# Exactly 1 byte extra (data.len == w*h*4 + 1)
let extra1 = newSeq [ uint8 ] ( pw * ph * 4 + 1 )
let out2 = renderImageRgba ( extra1 , pw , ph , caps , defaultOptions ( ) )
# Extra byte is harmless — we only read w*h*4 from front
doAssert out2 . len > 0 or caps . protocols = = { ptHalfBlock } ,
" 1-byte-extra buffer should render normally (output len= " & $ out2 . len & " ) "
echo " 1 byte short → empty (safe), 1 byte extra → renders (pass) "
# ── Test 23: Tiny images ──────────────────────────────────────
echo " \n ── Test 23: Tiny images (1x1, 10x1, 1x10) ── "
block :
# 1x1
let px1 = @ [ 128 'u 8 , 64 , 192 , 255 ]
let out1 = renderImageRgba ( px1 , 1 , 1 , caps , defaultOptions ( ) )
doAssert out1 . len > 0 or caps . protocols = = { ptHalfBlock } ,
" 1x1 pixel should produce output, got " & $ out1 . len & " chars "
# 10x1 horizontal strip
var strip = newSeq [ uint8 ] ( 10 * 1 * 4 )
for x in 0 .. < 10 :
strip [ ( x ) * 4 + 0 ] = uint8 ( x * 25 )
strip [ ( x ) * 4 + 1 ] = uint8 ( 255 - x * 25 )
strip [ ( x ) * 4 + 2 ] = 128
strip [ ( x ) * 4 + 3 ] = 255
let out2 = renderImageRgba ( strip , 10 , 1 , caps , defaultOptions ( ) )
doAssert out2 . len > 0 or caps . protocols = = { ptHalfBlock } ,
" 10x1 strip should produce output, got " & $ out2 . len & " chars "
# 1x10 vertical strip
var vstrip = newSeq [ uint8 ] ( 1 * 10 * 4 )
for y in 0 .. < 10 :
let off = y * 4
vstrip [ off + 0 ] = 64
vstrip [ off + 1 ] = uint8 ( y * 25 )
vstrip [ off + 2 ] = 192
vstrip [ off + 3 ] = 255
let out3 = renderImageRgba ( vstrip , 1 , 10 , caps , defaultOptions ( ) )
doAssert out3 . len > 0 or caps . protocols = = { ptHalfBlock } ,
" 1x10 strip should produce output, got " & $ out3 . len & " chars "
echo " 1x1, 10x1, 1x10 all rendered (pass) "
# ── Test 24: Fully transparent image ──────────────────────────
echo " \n ── Test 24: Fully transparent image (alpha=0 everywhere) ── "
block :
var transparent = newSeq [ uint8 ] ( 16 * 16 * 4 )
for y in 0 .. < 16 :
for x in 0 .. < 16 :
let off = ( y * 16 + x ) * 4
transparent [ off + 0 ] = 255
transparent [ off + 1 ] = 0
transparent [ off + 2 ] = 0
transparent [ off + 3 ] = 0 # fully transparent
let outStr = renderImageRgba ( transparent , 16 , 16 , caps , defaultOptions ( ) )
# Transparency composited over white background should produce white-ish output
doAssert outStr . len > 0 or caps . protocols = = { ptHalfBlock } ,
" transparent image should not crash, got " & $ outStr . len & " chars "
echo " transparent 16x16 rendered (pass) "
# ── Test 25: Monochrome image ─────────────────────────────────
echo " \n ── Test 25: Monochrome image (all same color) ── "
block :
var mono = newSeq [ uint8 ] ( 32 * 24 * 4 )
for i in countup ( 0 , mono . len - 1 , 4 ) :
mono [ i + 0 ] = 100 ; mono [ i + 1 ] = 150 ; mono [ i + 2 ] = 200 ; mono [ i + 3 ] = 255
let outStr = renderImageRgba ( mono , 32 , 24 , caps , defaultOptions ( ) )
let lines = outStr . split ( ' \n ' )
doAssert lines . len > = 1 , " monochrome should produce lines, got " & $ lines . len
echo " monochrome 32x24: " , lines . len , " lines (pass) "
# ── Test 26: Extreme opts values ──────────────────────────────
echo " \n ── Test 26: Extreme opts (maxWidth=0, maxWidth=-1, maxHeight=0) ── "
block :
var opts0 = defaultOptions ( )
opts0 . maxWidth = 0
let out1 = renderImageRgba ( chk , patternW , patternH , caps , opts0 )
doAssert out1 . len > = 0 , " maxWidth=0 should not crash "
var optsNeg = defaultOptions ( )
optsNeg . maxWidth = - 1
let out2 = renderImageRgba ( chk , patternW , patternH , caps , optsNeg )
doAssert out2 . len > = 0 , " maxWidth=-1 should not crash "
var optsH0 = defaultOptions ( )
optsH0 . maxHeight = 0
let out3 = renderImageRgba ( chk , patternW , patternH , caps , optsH0 )
doAssert out3 . len > = 0 , " maxHeight=0 should not crash "
echo " all extreme opts returned safely (pass) "
# ── Test 27: Empty protocol set ───────────────────────────────
echo " \n ── Test 27: Empty protocol set (selectProtocol) ── "
block :
let emptyCaps = TerminalCapabilities (
columns : 80 , rows : 24 ,
cell : CellSize ( width : 10 , height : 20 ) ,
protocols : { } , # no protocols at all
)
let selected = selectProtocol ( emptyCaps , ptHalfBlock )
doAssert selected = = ptHalfBlock ,
" selectProtocol should fall back to ptHalfBlock, got " & $ selected
let selected2 = selectProtocol ( emptyCaps , ptAuto )
doAssert selected2 = = ptHalfBlock ,
" selectProtocol(ptAuto) with empty set should fall back to ptHalfBlock, got " & $ selected2
echo " empty protocols fall back to half-block (pass) "
# ── Test 28: 1-pixel-high / 1-pixel-wide aspect extremes ─────
echo " \n ── Test 28: Extreme aspect (200x1 and 1x200) ── "
block :
var thinW = newSeq [ uint8 ] ( 200 * 1 * 4 )
for x in 0 .. < 200 :
thinW [ x * 4 + 0 ] = uint8 ( x )
thinW [ x * 4 + 1 ] = uint8 ( 255 - x )
thinW [ x * 4 + 2 ] = 128
thinW [ x * 4 + 3 ] = 255
let out1 = renderImageRgba ( thinW , 200 , 1 , caps , defaultOptions ( ) )
doAssert out1 . len > = 0 , " 200x1 image should not crash "
var thinH = newSeq [ uint8 ] ( 1 * 200 * 4 )
for y in 0 .. < 200 :
thinH [ y * 4 + 0 ] = 64
thinH [ y * 4 + 1 ] = uint8 ( y )
thinH [ y * 4 + 2 ] = 192
thinH [ y * 4 + 3 ] = 255
let out2 = renderImageRgba ( thinH , 1 , 200 , caps , defaultOptions ( ) )
doAssert out2 . len > = 0 , " 1x200 image should not crash "
echo " 200x1 and 1x200 rendered without crash (pass) "
# ── Test 29: Zero background and high-dither stress ───────────
echo " \n ── Test 29: Zero background + dither on black ── "
block :
var opts = defaultOptions ( )
opts . backgroundRgb = ( 0 'u 8 , 0 'u 8 , 0 'u 8 ) # pure black bg
opts . dither = true
opts . density = bdQuarter
opts . fit = fmContain
let outStr = renderImageRgba ( tgt , patternW , patternH , caps , opts )
doAssert outStr . len > = 0 , " zero bg + dither should not crash "
echo " zero background + dither on target circles (pass) "
# ── Test 30: Random-stress renders (20 iterations) ────────────
echo " \n ── Test 30: Random-stress (20 renders at random sizes / fit modes) ── "
block :
let sizes = [ ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 5 ) , ( 7 , 3 ) , ( 10 , 10 ) , ( 16 , 16 ) ,
( 32 , 48 ) , ( 64 , 64 ) , ( 100 , 50 ) , ( 50 , 100 ) ]
let fits = [ fmContain , fmStretch , fmWidth , fmOriginal , fmCellExact ]
let densities = [ bdFull , bdHalf , bdQuarter ]
var rng = 42 # deterministic pseudo-random
proc nextRand ( maxVal : int ) : int =
rng = ( rng * 1103515245 + 12345 ) and 0x7fffffff
result = rng mod maxVal
for iter in 0 .. < 20 :
let ( sw , sh ) = sizes [ nextRand ( sizes . len ) ]
var pixels = newSeq [ uint8 ] ( sw * sh * 4 )
for i in countup ( 0 , pixels . len - 1 , 4 ) :
pixels [ i + 0 ] = uint8 ( nextRand ( 256 ) )
pixels [ i + 1 ] = uint8 ( nextRand ( 256 ) )
pixels [ i + 2 ] = uint8 ( nextRand ( 256 ) )
pixels [ i + 3 ] = 255
var opts = defaultOptions ( )
opts . fit = fits [ nextRand ( fits . len ) ]
opts . density = densities [ nextRand ( densities . len ) ]
opts . maxWidth = 10 + nextRand ( 40 )
opts . dither = nextRand ( 2 ) = = 0
let outStr = renderImageRgba ( pixels , sw , sh , caps , opts )
if outStr . len > 0 :
discard
echo " 20 random renders completed without crash (pass) "
# ── Test 31: Tiny data + huge claimed dimensions ──────────────
echo " \n ── Test 31: Tiny buffer claimed as huge image ── "
block :
let tiny = @ [ 255 'u 8 , 0 , 0 , 255 ] # 1 pixel, claim as 1000x1000
let out1 = renderImageRgba ( tiny , 1000 , 1000 , caps , defaultOptions ( ) )
doAssert out1 . len = = 0 ,
" tiny buffer claimed as huge should return empty, got " & $ out1 . len & " chars "
let out2 = renderImageRgba ( tiny , 999999 , 999999 , caps , defaultOptions ( ) )
doAssert out2 . len = = 0 ,
" tiny buffer claimed as absurdly huge should return empty, got " & $ out2 . len & " chars "
echo " tiny data + huge/silly dimensions → empty (pass) "
echo " \n === All 31 tests passed === "