This is a viewer only at the moment see the article on how this works.
To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk
This is a preview from the server running through my markdig pipeline
Saturday, 24 January 2026
En glyph-baserad terminal-referrer med form- -- matchande algoritmer- . stödjer bilder, , animerade GIF-bilder, M SK4 videor, , YouTube-sändning- -, live subtitler med flera rendermodi inklusive Braille för maximum detaljer.
Målet är inte pixelgenreligiöshet ; det är synbarhet under extrema bandbreddsbegränsningar.
Det här är en av mina verktyg. små projekt som jag bygger på ett begränsat sätt.
Det här projektet började när jag Alex Harri's fantastiska artikel om ASCII- Rendering. Hans tillvägagångspunkt till form, -, matchning snarare än enkel ljusstyrkas kartläggning, var fascinerande. ,, och jag tänkte att jag skulle kunna bygga det i C. What started as a simple ASCII image viewer grew into a terminal graphics system with multiple rendering modes.
Den fulla källkoden finns på GitHub: https://github.com/scottgal /mostlylucidM SK4~consoleimage
ladda ner den senaste versionen: GitHub-utsläpp
NuGet paket:
mostlylucid.consoleimage - Core rendering librarymostlylucid.consoleimage.video - Videostöd (FFmpeg)mostlylucid.consoleimage.transcription - Skryfttranscription + undertitel generationmostlylucid.consoleimage.player - Dokument återspelningmostlylucid.consoleimage.player.spectre - Spectre .Konsole playback renderablermostlylucid.consoleimage.spectre - Spectre .KonsoleintegrationConsoleImage är en enda CLI som omvandlar media till glyph.
Det enklaste sättet att börja är att ladda ner den senaste release-binären från GitHub-utsläpp och se till att consoleimage är på din PATH.
consoleimage photo.jpg
consoleimage "https://youtu.be/dQw4w9WgXcQ" --subs whisper

ConsoleImage har tyst utvecklats till en full terminal-mediaplayer.
Abhängerheterna drars ner vid första användning och kastars lokalt, så nya egenskaper fungerar utan manuella installering:
| Komponent | Först laddade-down-brytare ♫ ♫ | ♫ Cache-position ♫ |
|---|---|---|
FFmpeg ~/.local/share/consoleimage/ffmpeg/ |
||
yt ~/.local/share/consoleimage/ytdlp/ |
||
| Skratt Runtime | Först --subs whisper |
~/.local/share/consoleimage/whisper/runtimes/ |
| Diktatormodeller ♫ ♫ | ♫ Först utsändning ♫ ~/.local/share/consoleimage/whisper/ |
På Windows, finns kaserna direkt under %LOCALAPPDATA%\consoleimage\.
På macOS, finns kaser som finns under din användare-lokala appdatafolder M SK2 typiskt ~/Library/Application Support/consoleimage/).
Använda -y / --yes till automatiskt-kräv alla nedläppningar.
YouTube-URLs spelar direkt i terminaln via yt-dlp, som körs genom samma renderningskanal som lokala filerM SK2 Det här betyder att samma moder, breddar , och utgångsformat gäller oavsett om du tittar på live eller sparar en klipp har en yt-version-dlp install, KonsoleImage kan peka på denM SK2
Subtitärer kommer från tre källor:
bakgrunden i 15-sekundliga chunks , stannar före playback .vtt så att återspelning är omedelbart. Det stödjer
flera modellstorlekar, språkskillnad , och hostade utgångspunkter så att du kan byta hastighet för precision eller avlagra arbetet
Transcript- endast utgång supporteras också , som omvandlar ConsoleImage till en undertitelgenerator som kan mata andra verktyg utan att göra video.
Han pekar på en folder och den blir en bild-slideshow med tangentbordskontroller framsteg.
Det som började som ett helgprojekt för att implementera Alex Harris algoritm blev något mer ambitiöst.
Skoopkrippen var verklig, men varje tillsats kändes naturlig och användbar.
ConsoleImage erbjuder tre olika sätt att rendera bilder i terminaln.
flowchart LR
A[Source Image] --> B{Choose Mode}
B --> C[ASCII]
B --> D[ColorBlocks]
B --> E[Braille]
C --> F[Shape-matched characters]
D --> G[Unicode half-blocks]
E --> H[2×4 dot patterns]
style A stroke:#4a9eff
style B stroke:#ff6600
style C stroke:#00aa00
style D stroke:#00aa00
style E stroke:#00aa00
| Modus | Kommando | Resolution | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Braille | consoleimage photo.jpg |
8× pixels per cell (2×4 prickar FAULT - Max. detaljer | |||||||||
| ASCII | consoleimage photo.jpg -a |
1× | ( | form | - | matcht | ) | МSK5 | bredaste kompatibilitet | ||
| ColorBlocks | consoleimage photo.jpg -b |
2× vertikalt | ( | halva | - | blocker | ) | МSK5 | Foton ♫ , | hög trovärdighet ♫ |
Braille är standardinställningen. Använd -a för ASCII eller -b för ColorBlocks.
Vissa terminaler supporterar inline-grafiska protokoll (iTerm2, KittyM SK2 Sixel ). ConsoleImage kan rikta sig mot dessa former för pixel-precis rendering medan man håller samma rutor och verktyg
Här är samma animerad GIF rendererad i varje Modus:
| ASCII ♫ ♫ | ♫ Braille ♫ | ||
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Formen - matchade tecken | m2×4 punktpatroner | kompakta , utan färg M | Unicode halva M SK7 blocker ♫ ♫ (▀▄) ♫ |
ASCII utgången visar formen-matching på jobbet - diagonala kanten används / och \, användande av kurvar ( och ), och olika tätheter använder tecken som @, #, *, och .. Den monochroma brailleversionen är 3× mindre än färgmodi medan den behåller full punktlösning
Resolutionens fördel blir tydlig med den klassiska Amiga-skakande bollen - en animation som kräver glada kurvar och rena diagonalerM SK1
| Braille (Monochrom ) | Brail MISK4färg МСK5 |
|---|---|
![]() |
![]() |
| 8× lösning |
Båda använder samma 2×4 punktnät (8 pixels per karaktercell). Den monochroma versionen utelämnar helt ANSI färgkoderM SK3 och producerar mycket mindre utgång
För snabb previews, SSH-sessioner , eller bandbredd-begränsade miljöerM SK3 monochrome braille MSC4--mono) tappar färgen helt
consoleimage animation.gif --mono -w 120
Resultaten är: 3-5x mindre som färgmodi medan man bevarar fullupplösningen. Den där boingball-animationen ovanför ? Monochromversionen är 259 KB jämfört med ♫ 863 KB för färgbraillen . För texten M SK5 tung innehåll eller linjer bättre eftersom det inte finns någon färgljud som tävlar med formens information.
Den traditionella ASCII konsten använder ljusstyrkas kartläggning - mörkare pixels får tätare tecken som @ eller #, ljusare pixels får sparse som . eller . Det här fungerar, men ignorerar den faktiska form av tecken.
Alex Harri's tillvägagångspunkt är smartare / eller \, inte bara någon liklig ljusstyrka
När du behandlar varje tecken som en liten 2D-formad | , | rendering blir närmast |- | grannsuche i ett lågt |
Varje tecken analyseras med hjälp av 6-punktsamplingsnät i ett 3×2 staggerat mönster
[0] [1] [2] ← Top row (staggered vertically)
[3] [4] [5] ← Bottom row
De vänstra cirklarna är nedlagda och de högra cirklarna upphöjda för att minimera luckor samtidigt som man avoiderar överlappande.
Här är den ASCII-outputen igen som referens medan vi pratar om exemplarnätet.

// Staggered sampling positions (3x2 grid as per Harri's article)
private static readonly (float X, float Y)[] InternalSamplingPositions =
[
(0.17f, 0.30f), // Top-left (lowered)
(0.50f, 0.25f), // Top-center
(0.83f, 0.20f), // Top-right (raised)
(0.17f, 0.80f), // Bottom-left (lowered)
(0.50f, 0.75f), // Bottom-center
(0.83f, 0.70f) // Bottom-right (raised)
];
När rendereren initialiserar, renderar den varje ASCII skriftipe till ett litet bild och tar prover på regionerna
private void GenerateVectors(string characterSet, string? fontFamily, int cellSize)
{
var font = GetFont(fontFamily, cellSize);
foreach (var c in characterSet.Distinct())
{
var vector = RenderCharacterVector(c, font, cellSize);
_vectors[c] = vector;
}
// Normalise vectors for comparable magnitudes
NormalizeVectors();
}
Resultatet är en söktavla som kartlägger varje tecken till dess formsignatur.
För att hitta den bästa karaktären för varje bildcell behöver man söka 6-dimensionell utrymme. Ett naivt linjärt sök skulle vara långsamt , så vi använder K-D träd för den snabbaste närmaste
flowchart TD
A[Image Cell] --> B[Sample 6 regions]
B --> C[Create shape vector]
C --> D[K-D tree lookup]
D --> E[Nearest character match]
style A stroke:#4a9eff
style C stroke:#ff6600
style D stroke:#00aa00
style E stroke:#00aa00
Den K-D träd erbjuder O(log n) sökningar istället för O (nM SK3 och resultaten kastars med kvantifierade vektorer för ännu snabbare upprepade sökningar
Rödform matchning kan se platt ut. Algorithmen använder två typer av kontrastupphöjning
Global Contrast - En kraftfunktion som trycker ned lägre värden mot noll
value = (value / max)^power × max
riktningskonst - 10 externa sampling cirklar detekterar kanter där innehållet möter tomma utrymme
// 10 external sampling positions for directional contrast
private static readonly (float X, float Y)[] ExternalSamplingPositions =
[
(0.17f, -0.10f), // Above top-left
(0.50f, -0.10f), // Above top-center
(0.83f, -0.10f), // Above top-right
(-0.15f, 0.30f), // Left of top-left
(1.15f, 0.20f), // Right of top-right
(-0.15f, 0.80f), // Left of bottom-left
(1.15f, 0.70f), // Right of bottom-right
(0.17f, 1.10f), // Below bottom-left
(0.50f, 1.10f), // Below bottom-center
(0.83f, 1.10f) // Below bottom-right
];
Renderaren innehåller flera performance tricks:
Vector128/Vector256/Vector512 för avståndsberäkningar// Pre-computed sin/cos lookup tables (major performance optimisation)
private static readonly (float Cos, float Sin)[] InnerRingAngles = PrecomputeAngles(6, 0);
private static readonly (float Cos, float Sin)[] MiddleRingAngles = PrecomputeAngles(12, MathF.PI / 12);
private static readonly (float Cos, float Sin)[] OuterRingAngles = PrecomputeAngles(18, 0);
Braille-modiet var den största tekniska förändringen. 8 pixels in i varje cell , och ConsoleImage behandlar de prickarna som den temporella signalen, inte bara en statisk bitmap, utan en sorts glyph, -, superupplöst, ", där rörelse och ram stabilitet, från - till -, gör det uppfattade detaljerat märkligt högre än det rårutnätet skulle antyda.
Brailletecknande (U+2800 - UM SK3FFMSC4 koder ett ♫ 8-dot-mönster i en ♫2×4 rutnät ♫
┌─────┬─────┐
│ 1 │ 4 │ ← Row 0
├─────┼─────┤
│ 2 │ 5 │ ← Row 1
├─────┼─────┤
│ 3 │ 6 │ ← Row 2
├─────┼─────┤
│ 7 │ 8 │ ← Row 3
└─────┴─────┘
Col0 Col1
Varje punkt motsvarar en bit:
// Dot bit positions in braille character
// Pattern: 1 4
// 2 5
// 3 6
// 7 8
// Listed in 2×4 scan order: 1,4,2,5,3,6,7,8.
private static readonly int[] DotBits = { 0x01, 0x08, 0x02, 0x10, 0x04, 0x20, 0x40, 0x80 };
Karakterkoden är helt enkelt 0x2800 + (bit pattern). En tom braillecell är ⠀ (UM SK1 en full block är ⣿ (U+28FFM SK2
flowchart LR
A[Source Image] --> B[Resize to W×2, H×4]
B --> C[Grayscale conversion]
C --> D[Otsu threshold + temporal stabilisation]
D --> E[Atkinson dithering]
E --> F[Build braille codes]
F --> G[Hybrid colours + perceptual compensation]
G --> H[Terminal output]
style A stroke:#4a9eff
style D stroke:#ff6600
style E stroke:#ff6600
style G stroke:#00aa00
Eftersom varje braille-cell koder ett 2×4 punktnätverk , så fungerar rendereren på en inre bild som är 2× bredare och 4× längre än måltecknens dimensioner
Att omvandla till braille kräver binära beslut - var och en punkt är på eller av . En fast tröskel ♫ ♫ som ( ♫ ljushet ♫ ) ♫ misslyckas med bilder som är främst ljusa eller mörka ♫
Otsu's metod hittar den optimala tröskeln genom att maximera skillnaden mellan förgrund och bakgrund
Det anpassar sig automatiskt till varje bild - mörka bilder får låga tröskeln , klara bilder får höga trösklar BrailleRenderer källa.)
I praktiken är naiva Otsu- tröskeln otillräcklig för animation.
Binärisk tröskel skapar hårda kanten. Fördämpning diffuserar kvantifieringsfeln till grannskapande pixels, och skapar illusionen av mellantoner
Vi använder Atkinsons fördämpning (utvecklad av Bill Atkinson för den ursprungliga Macintosh ) istället för den vanligare Floyd-Steinberg algoritm:
X 1 1
1 1 1
1
(each "1" receives 1/8 of the error)
Varför Atkinson fungerar bättre för braille:
| Aspect | FloydM SK2Steinberg | Atkinson | |||||
|---|---|---|---|---|---|---|---|
| Misslyckanden diffuserade | |||||||
| Spread-mönster | МSK2 | pixels | |||||
| resultat | mjukare graderingar ♫ | ♫ högre kontrast ♫ | |||||
| Som bäst för | Foton | Linjestil | МSK3 | text |
Atkinson avvisar medvetet 25% av felet , och producerar skarpare kanter | - | kritiskt för små prickar
private static void ApplyAtkinsonDithering(Span<short> buffer, int width, int height, byte threshold)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int idx = y * width + x;
short oldPixel = buffer[idx];
byte newPixel = oldPixel > threshold ? (byte)255 : (byte)0;
buffer[idx] = newPixel;
short error = (short)(oldPixel - newPixel);
short diffuse = (short)(error / 8); // 1/8 of error
// Diffuse to 6 neighbours (only 6/8 = 75% of error)
if (x + 1 < width)
buffer[idx + 1] += diffuse;
if (x + 2 < width)
buffer[idx + 2] += diffuse;
if (y + 1 < height)
{
if (x > 0)
buffer[idx + width - 1] += diffuse;
buffer[idx + width] += diffuse;
if (x + 1 < width)
buffer[idx + width + 1] += diffuse;
}
if (y + 2 < height)
buffer[idx + width * 2] += diffuse;
}
}
}
Terminalfärger innebär en utmaning: vi kan bara sätta en förgrundfärg per tecken, men braille representerar 8 olika källpixel.
Om man jämför alla 8-pixelfärger, får man ett "solerat" utseende. ♫ --färger blandar sig i dumma gråa ♫ bara exempel på färger från pixels där prickar är tända:
flowchart TD
A[2×4 Pixel Block] --> B{Which pixels are lit?}
B -->|Dots 1,4,7| C[Sample only those 3 pixels]
B -->|Dots 2,3,5,6,8| D[Sample only those 5 pixels]
C --> E[Average lit pixel colours]
D --> E
E --> F[Apply colour boost]
F --> G[Set as foreground colour]
style A stroke:#4a9eff
style B stroke:#ff6600
style E stroke:#00aa00
style F stroke:#00aa00
Detta gör att färgen som visas matchar det som användaren faktiskt ser - de tända prickarna .
Brailletecknar är ihärdligt sparse - en skrift med bara 2-3 prickar upplyst ser dyster ut än en solid block. Utan kompensationM SK3 braille-utgången ser objektivt ut ♫ " korrekt ♫" ♫ men visuellt tråkigt ♫
Att återställa uppfattad krom istället för att överdriva färgen:
Standarduppfoster (tunbar):
// Apply gamma correction and boost saturation/brightness for braille
(r, g, b) = BoostBrailleColor(r, g, b, _options.Gamma);
För en 100×50 karakter utgång
| Modus ♫ | ♫ Effektiva Pixel ♫ | |||||
|---|---|---|---|---|---|---|
| ASCII ♫ ♫ | ♫ | |||||
| ColorBlocks | 100 | 3 | 4 | 5 | 6 | |
| Braille |
Braille erbjuder 8x lösningen ASCII och 4x lösningen av färgklokar.
Den bästa demonstrationen av braillen's lösningsförmåga är med glada gradienter och fin detaljM SK1 Denna landskapsfotografie visar hur braille fångar subtila tonella variationer som skulle förloras i ASCII

Vid det här laget insåg jag att jag inte tittade på """, "" ASCII art "", eller """, längre. Det var synliga bilder.
Titta på hur skyns gradienter och markytan fortfarande är synliga även vid terminalupplösningen.. Med standard ASCII, skulle de bli blockerade och förlora sina smidiga övergångar. . Punktnätet 2×4 ger tillräckligt med utrymme för att ögat integrerar mönstret som ett kontinuerligt bildmaterial snarare än diskreta tecken.
Det finns också en -M flagga för en Matrix digital regn-overlay effect, eftersom det inte är så . Det kombinerar fallande katakanaströmmar över ditt källbild
Animation i terminaler är svår - naiva tillvägagångssätt orsakar synligt flicker . KonsolenImage använder flera tekniker
DECSET 2026 Synkroniserad utgång - en terminalfunktion som låter dig " ansluta ♫ " ♫ en hel bild på en gång ♫
// Start synchronised output
Console.Write("\x1b[?2026h");
// Write entire frame
Console.Write(frameContent);
// End synchronised output
Console.Write("\x1b[?2026l");
Delta- Rendering - Bara uppdaterade ändrade celler
public (string output, CellData[,] cells) RenderWithDelta(
Image<Rgba32> image,
CellData[,]? previousCells,
int colorThreshold = 8)
{
var cells = RenderToCells(image);
var height = cells.GetLength(0);
var width = cells.GetLength(1);
// First frame or dimension change - full redraw
if (previousCells == null)
return (RenderCellsToString(cells), cells);
// Delta render - only output changed cells
var sb = new StringBuilder();
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
var current = cells[y, x];
var previous = previousCells[y, x];
// Skip if cell hasn't changed
if (current.IsSimilar(previous, colorThreshold))
continue;
// Position cursor and output
sb.Append($"\x1b[{y + 1};{x + 1}H");
sb.Append(current.ToAnsi());
}
}
return (sb.ToString(), cells);
}
Det här typically minskar utgången med 70-90% för videoinhoud där de flesta av bilderna är statiska.
För att flytta innehållet , små per- bildfärgförändringar kan skina M SK2 KonsolenImage innehåller en dejitterpass som glätter färgförändringar över ramen, ökad upplevnadsstabilitet , speciellt i braille och färg
Videofiler och YouTube-URLs hanteras via FFmpeg och yt-dlp (automatiskt -downloaded on first useM SK3 You can start at offsetsMSC4 trim durationer, byta rendermodit , lägga till subtitler, och exportera till GIF- eller dokumentformat med samma renderningskärl
Videospelningskontroller:
| nyckeln | handlingen |
|---|---|
Space |
pausa/ avsluta |
Q / Esc |
Gå ut |
ConsoleImage kan spara avlägsnade resultat till självbevarade dokument som spelas tillbaka utan original källan. .cidz
format använder GZip kompression med delta-kodering (often ~7:1 mot rå JSON), och långa filmer kan strömmas som NDJSON
så ramar skrivs stegvis istället för att vara bufferade i minnet.
Det finns också en lightweight-spelare-package (ConsoleImage.Player) som kan spela upp dokumente utan ImageSharp eller FFmpeg
som gör animerade CLI-logor möjliga utan att byta stora beroenden
Det finns en full C# API för att rendera bilder som ger tillbaka CLI, så att du kan bygga in algoritmerna i dina egna verktyg utan att implementera pipelinen igen.
Spectre.Konsoleintegration är tillgänglig för både live rendering och dokumentlagning. renderabler inuti layouts. README- och NuGet-paketsidorna täcker API: s yta i detalj
Om du vill ha hela tekniska detaljerna, så är detta de vanliga dokumenten.
ConsoleImage består av en MCP (Model Kontext Protocol) server som omvandlar den till en ♫ "visuell sond | " för AI arbetsflöden ♫. ♫ Istället för att bara vara ♪ " ♫ AI-integration eftersom trenden ♫
Eftersom ConsoleImage kan göra uppfattande, meningsfulla förutseningar, kan en AI resonera om videoinhoud iterativt. Sampling, jämförelser och raffinering istället för att gå blind vid hela filmer.
Med MCP-integration
Medan jag skrev den här artikeln, använde jag ConsoleImage som ett MCP-verktyg för att hitta bra exempel på klipp.
→ extract_frames (low-res preview at various timestamps)
→ compare_render_modes (ASCII vs braille at promising scenes)
→ render_to_gif (final export of selected clips)
Här är en liten braille-version vid 30 stora bokstäver | - | tillräckligt för att märka komposition och rörelse
⣿⣿⣿⣿⣿⣿⣿⣿⡿⢃⠌⡱⢈⠱⣈⠱⣈⠱⢈⠛⢿⠿⠟⢛⠛⢛⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠇⡌⠒⡄⢃⢲⣾⣷⠶⡉⠤⡀⢆⠢⢉⠢⡘⢄⢂⠉⣺⣽⢿⣞⣷⣻⣞⣷⣻⣞⣷⣿
⣿⣿⣿⣿⣿⣿⣿⣿⠐⡈⢅⠢⡁⣾⣿⡏⠤⠑⡂⢅⠢⣕⢨⣔⠡⠌⣂⠱⠘⠯⠿⣾⣽⣳⠯⣝⡗⢿⣞⣿
Det låter mig snabbt scanna innehållet för att identifiera intressanta bilder snarare än att göra allt i fullupplösning.
Setup är en enda MCP-server-inskrywing; se MCP README för detaljer.
Nyckelverktyg: render_image, render_to_gif, extract_frames, och compare_render_modes.
flowchart TB
subgraph Core["ConsoleImage.Core (NuGet)"]
AR[AsciiRenderer]
BR[BrailleRenderer]
CB[ColorBlockRenderer]
CM[CharacterMap]
AP[AnimationPlayer]
DOC[Document Format]
end
subgraph Video["ConsoleImage.Video.Core"]
FF[FFmpegService]
VP[VideoPlayer]
end
subgraph CLI["ConsoleImage CLI"]
MAIN[Unified CLI]
end
subgraph MCP["ConsoleImage.Mcp"]
TOOLS[AI Tools]
end
MAIN --> Core
MAIN --> Video
TOOLS --> Core
style Core stroke:#4a9eff
style Video stroke:#ff6600
style CLI stroke:#00aa00
style MCP stroke:#9933ff
Det som började som en snabb implementering av Alex Harris ASCII-renderingalgoritm, blev ett terminalgrafiksystem.
Nyckelinlärningar
Koden är offentligt. (Olicenctionslös. GitHub.
Ett annat "-" -verktyg är lucidVIEW - en snabb , liten Avalonia klient som ger Markdown fint utan att använda WebView
Mer om det i 2.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.