blog · git · desktop · images · contact & privacy · gopher


(Nahezu) quadratische Pixel im Terminal

Damalige Terminals wie das VT100 und auch die heutigen Terminalemulatoren sind primär dazu gedacht, Text anzuzeigen. Daher sind Schriftgrößen wie 8x16 oder 7x15 Pixel häufig anzutreffen. Das heißt, diese Schriften sind etwa doppelt so hoch wie breit. Das ist zwar noch nicht das optimale Textdisplay – ein „i“ sollte schmäler sein als ein „m“ –, aber es ist ein guter Kompromiss. Das Terminal ist eine Art Raster und man kann recht einfach jede Zelle adressieren. Das ähnelt dem eigentlichen Bildschirm, bloß können Terminalanwendungen ein „A“ anzeigen, indem sie einfach ein „A“ ausgeben – sie müssen nicht jeden Pixel einzeln ansprechen und keinen Font bereitstellen. Das Ergebnis ist, dass deutlich kleinere Datenmengen übertragen werden müssen, was für langsame Leitungen sehr vorteilhaft ist.

Interessant ist jetzt, mit dem Terminal Dinge zu tun, für das es eigentlich nicht gedacht ist. Zum Beispiel könnte man auf die Idee kommen, Bilder oder andere graphische Informationen anzuzeigen. Aber wie macht man das, wenn man eigentlich nur „Text“ darstellen kann?

Der vermutlich ausgefeilteste Ansatz ist ASCII Art, also das Benutzen von Zeichen, deren Form ungefähr dem anzuzeigenden Bild entsprechen (asciiworld):

asciiworld.png

Man kann das natürlich programmatisch erzeugen, das sieht dann aber nicht perfekt aus oder der Code wird recht kompliziert. Manuell erzeugte ASCII Art, die gut aussieht, ist schon eine höhere Kunst.

Ein anderer Ansatz ist, jeden Pixel einer Zelle des Terminals zuzuordnen. Am einfachsten gibt man pro Pixel ein Leerzeichen aus, das normalerweise „unsichtbar“ wäre – ändert man aber zusätzlich die Hintergrundfarbe für diese Zelle, dann wird es sichtbar. Die Farben wird man nur annährend abbilden können, weil im Terminal in der Regel nur 8, 16 oder 256 fest definierte Farben zur Verfügung stehen. Für dieses Bild sähe das dann so aus (siv2):

naive.png

Die ANSI-Escapesequenz, um eine solche Zelle anzuzeigen, wäre in etwa das:

\033[48;5;196m \033[0m

Die Hintergrundfarbe wird (im 256-Farbenmodus) auf „196“ gesetzt, dann wird ein Leerzeichen gedruckt und dann als möglicherweise optionaler Schritt alle Attribute zurückgesetzt.

Mein Font ist in diesem Falle 8x16 Pixel groß, also ist das Bild verzerrt. Es ist zu hoch. Was kann man da machen? Wenn man auf ASCII beschränkt ist, gibt es nicht viel Spielraum. Man kann zum Beispiel einfach zwei Leerzeichen pro Pixel drucken:

\033[48;5;196m  \033[0m

Dann sieht es so aus:

double-size.png

Das Seitenverhältnis ist jetzt korrekt und das Bild nicht mehr verzerrt.

Es wurde aber auch viel Platz verschwendet: Das originale Bild wurde, da mein Font 8x16 Pixel groß ist, um den Faktor 16 vergrößert.

Man kann die Auflösung verdoppeln, sofern man erweiterte Zeichensätze benutzen kann. Wir wählen in diesem Fall Unicode als UTF-8 kodiert, da diese Kombination heutzutage sehr weit verbreitet ist. In Unicode gibt es den Codepoint <U+2580>, den „upper half block“. Sieht so aus:

Dieses „Zeichen“ ist Teil der in Unicode definierten „block elements“. Was wir damit nun tun können, ist, pro Zelle im Terminal die Farbe von Hinter- und Vorgergrund zu verändern. Die Vordergrundfarbe wird sichtbar im ausgefüllten Teil des „upper half block“ und die Hintergrundfarbe im nichtausgefüllten Teil darunter. Wir können damit also aus den originalen Pixeldaten zwei Reihen pro Terminalzeile anzeigen lassen. Die Escapesequenz dafür würde so aussehen:

\033[38;5;46m\033[48;5;196m▀\033[0m

Das Terminal zeigt dann zwei „Pixel“ an, einen grünen in der oberen Hälfte und einen roten in der unteren. Das kleine GIF von oben kann mit dieser Technik so aussehen (siv3):

box-drawing.png

Der Unterschied wird wesentlich deutlicher, wenn man zwei größere Bilder vergleicht:

Schön quadratische Pixel. Fast zumindest. Wenn der Font nicht genau doppelt so hoch wie breit ist, ist das Bild immer noch verzerrt. Der Font im Bild oben hat zum Beispiel eine Größe von 7x15 Pixeln. Die meisten Fonts sind daher nahezu in Ordnung. :-)