namespace cpp

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


anwenden:asciipic

Textbild : ASCII pictures

You can't do that in horizontal mode.
— TeX Fehlermeldung

Um Bilder zu erzeugen, benötigt man keine Grafikkarte. Es genügt ein Textbildschirm. Einzelne Buchstaben des Bildschirmfensters ansprechen ist jedoch mühsam: Cursorsteuerung erfordert compiler- oder systemspezifische Befehle. (Die curses-Bibliothek trägt ihren Namen nicht grundlos: curse = Fluch.) Die Standardausgabe schreibt der Reihe nach, von links nach rechts, eine Zeile nach der anderen. Warum also nicht vorher die auszugebenden Bilder im Haupspeicher aufbereiten? Dann können Texte im Fenster manipuliert oder Kurvenverläufe grafisch dargestellt werden. Dazu muss eine einfach zu handhabende Klasse her.

Klassenschnittstelle

Die grundlegende Idee hat Andrew Koenig beschrieben [1, 2]. Ein Text oder eine Ascii-Grafik ist nicht weiter als zeilenweise angeordnete Zeichenketten, ein Vektor aus string-Objekten. Dieser kann sogar öffentlich bleiben. Es wäre viel zu schade, all die nützlichen Methoden der Standardcontainerklassen zu verbergen. Ein paar Methoden und Funktionen sollen die Handhabung erleichtern:

  • Konstruktoren für rechteckige Bilder,
  • die Abfrage von Breite und Höhe,
  • der geprüfte Zugriff auf einzelne Zeichen des Bildes,
  • das Auffüllen von Bildern mit "ausgefranstem" rechten Rand, die z.B durch
  • das Zerlegen einer Zeichenkette entstehen.
  • Horizontales bzw.
  • vertikales Aneinandersetzen,
  • Umrahmen und
  • der Ausgabeoperator werden als globale Funktionen bereitgestellt.
//: textbild.h : ASCII-Bilder - R.Richter 2005-03-20
////////////////////////////////////////////////////
// Lit.: 
// [1] Andrew Koenig, Barbara Moo:
//     Intensivkurs C++. Pearson Studium, München (2003), Abschnitt 5.8
// [2] Andrew Koenig, Barbara Moo:
//     Ruminations on C++. Addison-Wesley, Reading Ms. (1997).
 
#ifndef TEXTBILD_H
#define TEXTBILD_H 
 
#include <vector>
#include <string>
#include <iostream>
 
class Textbild
{
public:
  std::vector<std::string> zeilen;    
 
  Textbild() {}
  Textbild(unsigned breite, unsigned hoehe, char fuellzeichen = ' ');
  unsigned breite() const;
  unsigned hoehe () const;
  char  at(unsigned x, unsigned y) const;
  char& at(unsigned x, unsigned y);
  Textbild& randausgleich(char fuellzeichen); // alle Zeilen gleich lang
 
  static Textbild split(std::string text, std::string trenner = "\n");
};
 
Textbild neben  (const Textbild& links, const Textbild& rechts);
Textbild unter  (const Textbild& oben,  const Textbild& unten);
Textbild umrahme(const Textbild& bild, char zeichen);
 
std::ostream& operator<<(std::ostream& os, const Textbild& bild);
 
#endif // TEXTBILD_H
//~

Implementierung

//: textbild.cpp : ASCII-Bilder - R.Richter 2005-03-20
//////////////////////////////////////////////////////
#include <algorithm>
#include <iterator>
#include <functional>
#include "textbild.h"

Der Ausgabeoperator muss die Bildzeilen nur auf den Ausgabenstrom kopieren. Mit Standardalgorithmen ist das auch so formulierbar. Ein ostream_iterator<Elementtyp> veranlasst os«zeilen[i]«"\n"; für jede Zeile des Containers.

std::ostream& operator<<(std::ostream& os, const Textbild& bild)
{
  std::copy(bild.zeilen.begin(), bild.zeilen.end(),
            std::ostream_iterator<std::string>(os, "\n"));            
}

Ein Textbild kann als rechteckige, mit einem Zeichen gefüllte Fläche gebildet werden, deren Höhe und Breite abfragbar sind.

Textbild::Textbild(unsigned breite, unsigned hoehe, char fuellzeichen)
: zeilen(hoehe, std::string(breite, fuellzeichen))
{
}
 
unsigned Textbild::hoehe() const
{
  return zeilen.size();
}
 
unsigned Textbild::breite() const
{
  unsigned maxbreite = 0;
 
  for (unsigned i=0; i < zeilen.size(); ++i)
  {
    unsigned aktuell = zeilen[i].size();
    if (maxbreite < aktuell)
      maxbreite = aktuell;
  }
  return maxbreite;
}

Die Breite eines Textbildes wird durch die längste Zeile bestimmt. Nicht alle Textbilder bestehen nur aus gleich langen Zeilen. Daher ist die Breitenbestimmung auch aufwendiger. Aus rechtsseitig ausgefransten Bildern lassen sich jedoch Rechtecke machen, indem kürzere Zeilen aufgefüllt werden:

Textbild& Textbild::randausgleich(char fuellzeichen)
{
  int b = breite();
  for (unsigned i=0; i<hoehe(); ++i)
  {
    zeilen[i].resize(b, fuellzeichen);               
  }
  return *this;
}

Der lesende / schreibende Zugriff auf Einzelzeichen mit der Methode at(x,y) stellt sicher, dass nicht hinter Feldgrenzen zugriffen wird. Falsch addressierte Breife landen (ungeöffnet) im Papierkorb.

char  Textbild::at(unsigned x, unsigned y) const
{
  if (y >= hoehe() || x >= zeilen[y].size()) return '\0';
  return zeilen[y][x];      
}
 
char& Textbild::at(unsigned x, unsigned y)
{
  static char papierkorb;    
  if (y >= hoehe() || x >= zeilen[y].size()) return papierkorb = '\0';
  return zeilen[y][x];      
}

Die statische Funktion split() zum Zerlegen von Texten kann ausgefranste Textbilder erzeugen:

Textbild Textbild::split(std::string text, std::string trenner)
{
  Textbild bild;
  if (!text.empty())
  {
    unsigned start = 0;
    unsigned end = text.find(trenner, start);
    while (end != std::string::npos)
    {
      bild.zeilen.push_back(text.substr(start,end-start));
      start = end + trenner.size();
      end = text.find(trenner, start);         
    }
    bild.zeilen.push_back(text.substr(start)); 
  }
  return bild;
}

Bilder untereinander anordnen ist (unter Nutzung eines back_inserter) etwas einfacher …

Textbild unter(const Textbild& oben, const Textbild& unten)
{
  Textbild gesamt(oben);
  std::copy(unten.zeilen.begin(), unten.zeilen.end(), 
            std::back_inserter(gesamt.zeilen));
  return gesamt;
}

… als die Bilder nebeneinander zu legen:

Textbild neben(const Textbild& links, const Textbild& rechts)
{
  Textbild gesamt;
  unsigned bl = links.breite(), i = 0, j = 0;
 
  while (i < links.hoehe() || j < rechts.hoehe())
  {
    std::string s;
    if (i < links.hoehe()) 
      s += links.zeilen[i++];
 
    if (j < rechts.hoehe()) 
    {
      s.resize(bl, ' ');
      s += rechts.zeilen[j++];
    }
    gesamt.zeilen.push_back(s);
  }
  return gesamt;
}

Umrahmungen beginnen und enden mit einer Randzeile. Die dazwischen liegenden Zeilen werden links und rechts von je einem Randzeichen und einem Leerzeichen eingeschlossen.

Textbild umrahme(const Textbild& bild, char zeichen)
{
  unsigned maxbreite = bild.breite();
  std::string randzeile(maxbreite+4, zeichen);
 
  Textbild gerahmt;
  gerahmt.zeilen.push_back(randzeile);
 
  for (unsigned i = 0; i < bild.hoehe(); ++i)
  {
    std::string s(1, zeichen);
    s += " " + bild.zeilen[i];
    s.resize(maxbreite+4, ' ');
    s[maxbreite+3] = zeichen; 
    gerahmt.zeilen.push_back(s);
  }
  gerahmt.zeilen.push_back(randzeile);
  return gerahmt;
}
//~

Demo- und Testprogramm

//: testbild.cpp : Testprogramm ASCII-Bilder - R.Richter 2005-03-20
///////////////////////////////////////////////////////////////////
#include <iostream>
#include "textbild.h"
using namespace std;
 
int main()
{
  Textbild sterne(3, 5, '*');
  Textbild punkte(5, 3, '.');
  Textbild striche(5, 4, '-');
 
  for (int i = 0; i < striche.breite(); ++i)
  {
    striche.at(i,i) = 'O'; // Zugriff auf "Pixel"       
  }
 
  std::cout << "Bilder untereinander:\n" << unter(punkte, sterne);
  std::cout << "Bilder nebeneinander:\n" << neben(punkte, sterne);
 
  string text = "B ilder\n"
                "I neinander\n"
                "L egen macht\n"
                "D ir Spass?";
 
  std::cout << "Bilder kombiniert, gerahmt:\n"
            << umrahme(unter(neben(sterne, punkte), 
                             neben(Textbild::split(text), striche)
                            ).randausgleich('~'), 
                       '#');
  return 0;
}

Übersetzen und ausführen

/*
g++ -o testbild testbild.cpp textbild.cpp
testbild

Nach dem Übersetzen erzeugt das Demoprogramm die Ausgabe:

Bilder untereinander:
.....
.....
.....
***
***
***
***
***
Bilder nebeneinander:
.....***
.....***
.....***
     ***
     ***
Bilder kombiniert, gerahmt:
#####################
# ***.....~~~~~~~~~ #
# ***.....~~~~~~~~~ #
# ***.....~~~~~~~~~ #
# ***~~~~~~~~~~~~~~ #
# ***~~~~~~~~~~~~~~ #
# B ilder     O---- #
# I neinander -O--- #
# L egen macht--O-- #
# D ir Spass? ---O- #
#####################
*/
anwenden/asciipic.txt · Zuletzt geändert: 2014-07-13 16:14 (Externe Bearbeitung)