fdraw

reinhard@finalmedia.de cafeface
Tue Jul 14 11:04:46 AM CEST 2026
Public Domain

fdraw liest auf stdin fdraw befehle und gibt auf stdout binäre rgba frames aus.

fdrawterm liest auf stdin fdraw befehle und gibt auf stdout ANSI Escape Sequenzen mit Positonierung von UTF8 Lower Halfblock (0x2584) als Pixel Terminalgrafik aus.

Sourcecode

  • readme.txt
  • fdraw.c Quellcode 0c214caebd7ac6af2de7eb82cf2ffd14 fdraw Befehle als rgba frames ausgeben, sinnvoll für pipe in ffplay, ffmpeg etc.
  • fdrawterm.c Quellcode 8d10abc3d3cd1cbc329b89543f8a674a fdraw Befehle im Terminal als ANSI Grafiken anzeigen. Sehr performant und auch ohne Sixel-Support einfach remote via ssh nutzbar!
  • jsplay.tgz Demo Tarball mit Javascript Code als Framebuffer Video Display. Nutzbar im Browser (2D Canvas,WebGL,WebGPU). Stellt komprimierte gestreamte fdraw frames dar. streamed in einer unix pipe via websocketd aus der shell mittels awk und fdraw und pigz zum komprimieren live an jeden modernen browser als display. (Setzt im backend dann die fdraw binary voraus, z.B. unter /usr/bin/fdraw. natürlich auch die selbst compilierte). Anmerkung: Die Variante fdraw befehle zeilenweise an Javascript durchzugeben (statt bereits die fertige rgba Frames) wurde ebenso evaluiert, ist jedoch nicht performant genug in standard Javaascript beim zeilenweisen lesen und parsen der strings. Zudem ist dann das Datenaufkommen für einen webstream auch sehr hoch. Diese Variante streamed sehr sparsam.
  • fvoxel Weiteres Tool, um ein Voxelterrain zu erzeugen und zu durchfliegen. Steuerbar und animierbar. Auch das Voxelterrain ist zeilenbasiert animierbar. Liest separate fvoxel Befehle von stdin und erzeugt fdraw Befehle auf stdout.
  • fsplat Weiteres Tool, um eine 3D Gaussian Splatting Szene zu durchfliegen. Steuerbar und animierbar. Liest eine .splat file von stdin und Kamerapositionsbefehle von descriptor 4 und gibt fdraw Befehle auf stdout aus. performance-optimiert im djb style und CPU only!

    Binaries

  • fdraw (amd64 elf64 binary) march=silvermont mit SSE 4.2 035a8d09c3561f311d5f8f29dc46c383
  • fdrawterm (amd64 elf64 binary) march=silvermont embedded mit SSE4.2 und POPCNT aber ohne AVX 7f6166ee939190d1cb76eb9ad1975921090add065e35c2cc8c0e03a079b40877
  • fdrawterm.x86-64 (amd64 elf64 binary) march=x86-64 universell
  • fdrawterm.x86-64-v3 (amd64 elf64 binary) march=x86-64-v3 mit AVX2
  • fdrawterm.znver2 (amd64 elf64 binary) marc=znver2 mit AVX2 BMI2 FMA3 ZEN2 wie Steamdeck

    Referenz

    Befehle für fdraw sind linebased ascii char. dezimal werte.
    fdraw erwartet diese Form der Eingabe auf stdin.
    
    Das erste Zeichen einer Zeile ist immer der befehl.
    Dann folgen die Argumente
    
    
    cmd     argumente       name    description
    -----------------------------------------------------------------------------------
    c       r g b           color   Setzt die Zeichenfarbe nach R G B
    m       x y             move    Bewegt den Zeichencursor zu gewünschten Koordinaten
    r       dx dy           rect    Zeichnet ein Rechteck
    p       x y r g b       pixel   Zeichnet exakt einen Pixel an x y in Farbe r g b
    s       n               sleep   Wartet angegebene Anzahl an Microsekunden
    o       ox oy           offset  Definiert globalen Offset für Zeichenoperationen
    
    Befehle und Parameter/Argumente werden durch ein Leerzeichen /space) getrennt.
    Alle Argumente MÜSSEN postive Ganzzahlen (integer) sein.
    

    Fragen und Antworten

    Warum der Umweg über die fdraw Befehle?

    Das Prinzip der Unix-Philosophie besagt, dass Datenströme zeilenbasiert und in ASCII (menschenlesbar) übertragen werden sollten, weil Text die universellste und flexibelste Schnittstelle zwischen Programmen ist.

    Durch den zeilenbasierten Austausch über reine ASCII Texte gewinnt man diese Vorteile:

  • Universelle Kompatibilität: Jedes Werkzeug kann Text ohne spezielle Bibliotheken lesen und schreiben.
  • Menschenlesbarkeit: Fehler in Datenströmen lassen sich direkt im Terminal ohne Hex-Editor aufspüren.
  • Einfache Werkzeuge: Programme wie grep, awk oder sed arbeiten nativ mit Zeilenumbrüchen.
  • Zukunftssicherheit: Textformate überdauern proprietäre oder hardwareabhängige Binärformate.
  • Nicht zu vergessen: Das Prinzip wurde weltberühmt durch Doug McIlroy, den Erfinder der UNIX-Pipe.

    Er formulierte das Konzept 1978 im Bell System Technical Journal:

        "Expect the output of every program to become the input to another, as yet unknown, program. Don't clutter output with extraneous information. Avoid stringently columnar or binary input formats. Don't insist on interactive input."
    

    Die wichtigste Ergänzung dazu fasste Peter H. Salus 1994 als Philosophie in seinem Buch A Quarter Century of UNIX zusammen. Er formulierte die goldenen Regel:

        "Write programs to handle text streams, because that is a universal interface." (Schreibe Programme so, dass sie Textströme verarbeiten, denn das ist eine universelle Schnittstelle.)
    

    Die Intention dahinter ist recht einfach nachvollziehbar. Die Entwickler wollten Modularität statt monolithischer Software.

    Wenn ein Programm seine Daten in einem komplexen Binärformat ausgibt, bricht die Kette. Ein binäres Format zwingt das nachfolgende Programm, die genaue Datenstruktur des Vorgängers zu kennen. Standardisierter ASCII-Text entkoppelt hingegen die Programme einer Pipe.

    Das nächste Tool muss lediglich wissen, wie man Zeilen trennt. Alles bleibt menschlesbar und die Kette kann an jeder Stelle auseinandergenommen und geprüft werden. Zudem ist es dann ein Leichtes, auch zeilenbasierte Filter zu schreiben, um Ausgaben umzustruktieren, zu wandeln, zu extrahieren, wegzulassen oder aufzutrennen. Wenn du einmal die Magie dahinter verstanden hast, lässt sich das nicht mehr los. Du stellst dir dann eher die umgekehrte Frage: Warum sollte man das nicht so tun?

    Aber...

    Ja, natürlich ist das langsamer, als die Daten da binär rüberzustreamen oder direktes Memory Mapping zu betreiben.

    Wie sinnvoll ist dann das Ganze?

    Wie so oft, liegt der Sinn im Auge des Betrachters. Das hier ist ein Playground. Das Konzept hat rein akademischen, pädagogischen und spielerischen Sinn. Es gibt dir bequeme Analysefähigkeiten, du kannst bestehende Filter und beliebiges Scripting in beliebigen Sprachen verwenden. Natürlich darfst du es auch produktiv nutzen. Es wurden allerdings in den 1990er nicht umsonst Grafikkarten entwickelt, die diese Pixel-Operationen beschleunigen.

    fdraw Beispielbefehle

    Hier zum Verständnis

    Beispiele:
    
    c 140 20 0              Wechselt zur Farbe rot
    m 20 80                 Wechselt zu den Koordinaten x=20 und y=80
    r 10 10                 Zeichnet ein Quadrat 10x10 px
    r 10 40                 Zeichnet ein Rechteck 10px breit und 40px hoch
    s 50000                 Wartet 50 Millisekunden (Animationen)
    p 23 42 164 163 255     Zeichnet einen hellblauen Pixel an x=23 y=42
    o 50 100                Definiert einen globalen Offset von x=50 y=100
    
    
    Gerade das Definieren eines globalen offsets ermöglicht das Verschieben
    eines frames, ohne dessen eigene steuersequenzen verändern zu müssen.
    
    Siehe daher auch die Tools charforce, intrange etc. auf https://finalmedia.de/code/
    die wunderbar in diese Kette passen, um die Streams zu limitieren, zu formen und zu modifizieren.
    

    Hinweis: Für die nachfolgenden awk Demos wird ein awk mit math modul benötigt, z.B. gawk. einfaches busybox awk reicht nicht, solange nicht math support aktiviert ist.

    Ob in deinem awk diese mathematischen Funktionen (wie sqrt, sin, log) verfügbar sind, hängtdavon ab, wie die jeweilige Linux-Distribution die BusyBox-Binärdatei kompiliert hat. Die Option muss aktiviert sein: Option CONFIG_FEATURE_AWK_MATH, und dann wird auch die libm mitgeladen. Mit Math-Support ist die binary geringfügig größer, bietet dafür aber alle POSIX-Standard-Mathefunktionen.

    Animated Demos

  • barnsley.awk
  • barnsley.sh
  • barnsley_pure.tgz
  • Animated Torus Rotation

  • torus.awk
  • torus.sh
  • Example Pipeline to mp4 file

     export SCREEN_FPS=30
     export SCREEN_HEIGHT=240
     export SCREEN_WIDTH=320
     gawk -v width=320 -v height=240 -f torus.awk | \
     fdraw 2>/dev/null | \
     ffmpeg -f rawvideo -pixel_format rgba -video_size 320x240 -framerate 30 -i - -frames 300 -y torus.mp4 2>/dev/null
    

    Starfield Animation

  • starfield.awk
  • Raytracer Demo

  • raytracer.awk
  • rayconst.awk
  • raytracer.sh

  •  export SCREEN_FPS=30
     export SCREEN_HEIGHT=240
     export SCREEN_WIDTH=320
     gawk -v width=320 -v height=240 -f raytracer.awk | fdraw 2>/dev/null | ffmpeg -f rawvideo -pixel_format rgba -video_size 320x240 -framerate 60 -i - -y raytracer%03d.png
     gawk -v width=320 -v height=240 -f raytracer.awk | fdraw 2>/dev/null | ffmpeg -f rawvideo -pixel_format rgba -video_size 320x240 -framerate 60 -i - -y raytracer.mp4
    

    Voxel Renderer Animation

  • voxel.awk
  • zendless.awk
  •  export SCREEN_FPS=30
     export SCREEN_HEIGHT=240
     export SCREEN_WIDTH=320
     gawk -v width=320 -v height=240 -f voxel.awk | gzip -9 > voxel.precomputed
     zcat voxel.precomputed | fdraw 2>/dev/null | ffmpeg -f rawvideo -pixel_format rgba -video_size 320x240 -i - -vf "setpts=0.1*PTS,fps=30" -y voxel.mp4
    

    Fallout Mushroom Cloud

  • fallout.awk
  •  export SCREEN_FPS=30
     export SCREEN_HEIGHT=240
     export SCREEN_WIDTH=320
     gawk -f fallout.awk | fdraw | ffplay -f rawvideo -pixel_format rgba -video_size 320x240 -i -
    

    fdrawterm

    Zur Ausgabe aus einem Terminal in Halfblock Pixel, steht das Tool fdrawterm bereit. Es kann auch genutzt werden, um grafiken als ANSI ESC Sequenzen auf der Konsole zu betrachten.

     export SCREEN_WIDTH=80
     export SCREEN_HEIGHT=50
     convert example.png -resize 80x50 -compress none txt:- | awk -F'[(,):]' 'NR>1 {print "p", $1, $2, int($4), int($5), int($6)}' | fdrawterm
    
    
     convert example.png -resize 80x50 -compress none txt:- | awk -F'[(,):]' 'NR>1 {print "p", $1, $2, int($4), int($5), int($6)}' > example.txt
     (cat example.txt; echo s 40000) | fdrawterm > example.term
     cat example.term
    
    

    Weitere Beispiele und Experimente zum besseren Verständnis der Vorteile einer pipe

    
     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
    
     curl -Ls finalmedia.de/code/fdraw/torus.awk | gawk -f-  | fdrawterm
    
     curl -Ls finalmedia.de/code/fdraw/torus.awk | gawk -f-  | tr -dc "0-9a-z \n" | grep p | fdrawterm
    
     curl -Ls finalmedia.de/code/fdraw/torus.awk | gawk -f-  | grep -v p
    
     curl -Ls finalmedia.de/code/fdraw/torus.awk | sed "s/g = brightness/g = int(brightness * 0.6)/g" | gawk -f-  | fdrawterm
    
    

    Für derartige Spielereien empfehle ich zudem das coolretroterm als Terminal, aber auch allacritty oder xterm arbeitet damit.

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     curl -Ls finalmedia.de/code/fdraw/fallout.awk | gawk -f-  | fdrawterm
    

    Dabei lässt sich auch live die Anzahl der maximalen Partikel reduzieren

     export SCREEN_WIDTH=120 SCREEN_HEIGHT=80
     curl -Ls finalmedia.de/code/fdraw/fallout.awk | sed "s/1200;120;/g" | gawk -f- | fdrawterm
    

    Full Stack Demo x86_64 (Binarys)

    wget https://finalmedia.de/code/fdraw/fdraw
    wget https://finalmedia.de/code/fdraw/fdrawterm
    chmod +x fdrawterm
    chmod +x fdraw
    
    wget https://finalmedia.de/code/fvoxel/fvoxel
    chmod +x fvoxel
    curl https://finalmedia.de/code/fvoxel/map.txt.gz | zcat > map.txt
    wget https://finalmedia.de/code/fvoxel/rundflug.txt
    
    export SCREEN_WIDTH=100 SCREEN_HEIGHT=80
    
    # voxel rundflug
    cat map.txt rundflug.txt | ./fvoxel | ./fdrawterm
    
    # weitere spielereien
    wget https://finalmedia.de/code/fdraw/barnsley.awk
    timeout 30 gawk -f barnsley.awk | ./fdrawterm
    
    wget https://finalmedia.de/code/fdraw/torus.awk
    timeout 30 gawk -f torus.awk | ./fdrawterm
    
    wget https://finalmedia.de/code/fdraw/starfield.awk
    timeout 30 gawk -f starfield.awk | ./fdrawterm
    
    wget https://finalmedia.de/code/fdraw/plasma.awk
    timeout 30 gawk -f plasma.awk | ./fdrawterm
    
    wget https://finalmedia.de/code/fdraw/rayconst.awk
    gawk -f rayconst.awk | ./fdrawterm
    
    wget https://finalmedia.de/code/fdraw/fallout.awk
    timeout 30 gawk -f fallout.awk | ./fdrawterm
    
    timeout 30 od -t u1 /dev/urandom | tr -s " " | cut -d " " -f 2-6 | sed "s/^/p /g" | ./fdrawterm
    
    timeout 30 cat /dev/urandom | od -t u1 | tr -s " " | cut -d " " -f 2-4 | sed "s/^/p /g;s/$/ 12 30/g" | ./fdrawterm
    
    find / 2>/dev/null | od -t u1 | tr -s " " | cut -d " " -f 2-6 | sed "s/^/p /g" | ./fdrawterm
    
    curl -Ls https://finalmedia.de/code/fdraw/orb.gz | zcat