## 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'u8 # 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'u8, 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'u8, 0'u8, 0'u8) # 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'u8, 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 ==="