In dit artikel beschrijft Andrea de technische implementatie van een torus-renderer voor de terminal. De focus ligt op twee hoofdonderdelen:
- Base64-codering: Er wordt uitgelegd hoe ruwe pixelgegevens worden omgezet naar base64 (via 6-bit woorden) om deze als tekst te kunnen versturen.
- Kitty Graphics Protocol: Andrea beschrijft de specifieke syntax (
<ESC>_G...) en de benodigde control-data (zoals breedte, hoogte en datatype) om afbeeldingen in een ondersteunde terminal weer te geven.
Door over te stappen op echte image rendering kon de auteur visuele fouten makkelijker debuggen en features toevoegen zoals kleuren en meerdere lichtbronnen. Voor de toekomst overweegt Andrea het gebruik van Metal voor Apple Silicon of het uitbreiden naar een engine die OBJ-meshes kan laden.
Torus v0.5
Base64-codering
Als je er in eerste instantie niet goed op let, zou je kunnen denken dat je ruwe pixelgegevens in platte ASCII-tekst kunt versturen. Dat is echter niet zo eenvoudig. Je moet afbeeldingen versturen die zijn gecodeerd in base64 (RGB, RGBA, PNG). Voor wie dit niet weet: de codering werkt als volgt:
- Er worden 3 bytes als input genomen.
- Deze worden gesplitst in woorden van elk 6 bits.
- Gegeven
i (een unsigned integer van 6 bits), wordt het teken map[i] geretourneerd, waarbij map een specifieke coderingsmap is. Dit gebeurt voor elk van de vier 6-bit unsigned integers.
- Als de input geen veelvoud is van 3 bytes, wordt deze indien nodig opgevuld met
= (een speciaal teken).
Wie hier meer over wil weten of het op de oude manier wil doen, kan dit nalezen in de RFC. Het is verrassend eenvoudig om te implementeren voor RGB-gegevens, en je kunt er vervolgens mee pronken dat je weet waar RFC voor staat (Request For Comment).
Het Kitty Graphics Protocol
Zodra je je base64-gecodeerde afbeelding hebt, kun je deze in brokken van maximaal 4096 bytes versturen via het Kitty Graphics Protocol. Alle graphics volgen het formaat:
<ESC>_G<control data>;<payload><ESC>\
Vertaald naar printf() betekent dit: printf("\033_G<control data>;<payload>\033\\");
Hierbij is <control data> een door komma's gescheiden reeks van sleutel-waardeparen die eindigt met een puntkomma. Enkele basisgegevens voor deze map zijn:
- Weergave in terminal:
a=T (verplicht, anders wordt er niets weergegeven).
- Type gegevens:
f=24/32/100 voor respectievelijk RGB, RGBA en PNG (verplicht).
- Breedte:
s=%n (verplicht).
- Hoogte:
v=%n (verplicht).
- Compressie:
o=z (optioneel).
- Terminal kolommen en rijen:
c=%d,r=%d (optioneel).
- Vervolg:
m=%1 (indien er nog chunks volgen).
Resultaten en reflectie
Door gebruik te maken van echte image rendering kon ik eindelijk zien wat er precies gebeurde. Er vonden namelijk vreemde dingen plaats, zoals een torus die doormidden was gesneden en inconsistent licht. Het was hierdoor veel eenvoudiger om visueel te debuggen en leuke zaken toe te voegen, zoals kleuren en een tweede lichtbron in een andere kleur.
Voor de toekomst zou het interessant kunnen zijn om Metal-code te schrijven om dit proces op Apple Silicon te versnellen; dat zou een mooie manier zijn om iets nieuws te leren (of simpelweg te lijden). Een andere optie is om er een rendering engine van te maken die daadwerkelijk OBJ-meshes kan laden, of om het project voorgoed te vergeten.
Dit is het resultaat voor nu. De code is te vinden in de repository. De fps-teller maakt het geheel nog professioneler, ook al is deze nutteloos omdat het programma pauzeert om niet boven de 33,33 fps uit te komen.
Leuk weetje: Wist je dat magenta (blauw + rood) geen echte kleur is? Het bestaat niet echt in het zichtbare spectrum; onze hersenen verzinnen deze kleur simpelweg.
Torus v0.5
Base64-codering
Als je er in eerste instantie niet goed op let, zou je kunnen denken dat je ruwe pixelgegevens in platte ASCII-tekst kunt versturen. Dat is echter niet zo eenvoudig. Je moet afbeeldingen versturen die zijn gecodeerd in base64 (RGB, RGBA, PNG). Voor wie dit niet weet: de codering werkt als volgt:
- Er worden 3 bytes als input genomen.
- Deze worden gesplitst in woorden van elk 6 bits.
- Gegeven
i (een unsigned integer van 6 bits), wordt het teken map[i] geretourneerd, waarbij map een specifieke coderingsmap is. Dit gebeurt voor elk van de vier 6-bit unsigned integers.
- Als de input geen veelvoud is van 3 bytes, wordt deze indien nodig opgevuld met
= (een speciaal teken).
Wie hier meer over wil weten of het op de oude manier wil doen, kan dit nalezen in de RFC. Het is verrassend eenvoudig om te implementeren voor RGB-gegevens, en je kunt er vervolgens mee pronken dat je weet waar RFC voor staat (Request For Comment).
Het Kitty Graphics Protocol
Zodra je je base64-gecodeerde afbeelding hebt, kun je deze in brokken van maximaal 4096 bytes versturen via het Kitty Graphics Protocol. Alle graphics volgen het formaat:
<ESC>_G<control data>;<payload><ESC>\
Vertaald naar printf() betekent dit: printf("\033_G<control data>;<payload>\033\\");
Hierbij is <control data> een door komma's gescheiden reeks van sleutel-waardeparen die eindigt met een puntkomma. Enkele basisgegevens voor deze map zijn:
- Weergave in terminal:
a=T (verplicht, anders wordt er niets weergegeven).
- Type gegevens:
f=24/32/100 voor respectievelijk RGB, RGBA en PNG (verplicht).
- Breedte:
s=%n (verplicht).
- Hoogte:
v=%n (verplicht).
- Compressie:
o=z (optioneel).
- Terminal kolommen en rijen:
c=%d,r=%d (optioneel).
- Vervolg:
m=%1 (indien er nog chunks volgen).
Resultaten en reflectie
Door gebruik te maken van echte image rendering kon ik eindelijk zien wat er precies gebeurde. Er vonden namelijk vreemde dingen plaats, zoals een torus die doormidden was gesneden en inconsistent licht. Het was hierdoor veel eenvoudiger om visueel te debuggen en leuke zaken toe te voegen, zoals kleuren en een tweede lichtbron in een andere kleur.
Voor de toekomst zou het interessant kunnen zijn om Metal-code te schrijven om dit proces op Apple Silicon te versnellen; dat zou een mooie manier zijn om iets nieuws te leren (of simpelweg te lijden). Een andere optie is om er een rendering engine van te maken die daadwerkelijk OBJ-meshes kan laden, of om het project voorgoed te vergeten.
Dit is het resultaat voor nu. De code is te vinden in de repository. De fps-teller maakt het geheel nog professioneler, ook al is deze nutteloos omdat het programma pauzeert om niet boven de 33,33 fps uit te komen.
Leuk weetje: Wist je dat magenta (blauw + rood) geen echte kleur is? Het bestaat niet echt in het zichtbare spectrum; onze hersenen verzinnen deze kleur simpelweg.