NEO2MONO - Ein Programm zur Konvertierung von Neochrome Bildern

Hier ist das von allen ATARI ST Besitzern lang erwartete Konvertierungsprogramm. Gehören Sie auch zu denen, die sich die Public Domain Disketten besorgt haben, und nun die vielen, schönen Neochromebilder, die sich darauf finden, nicht anschauen können?

Seien Sie unbesorgt hier ist die Lösung: Das Programm NEO2MONO konvertiert die Farbbilder so, daß Sie auf dem Monochromemonitor dargestellt werden können. Die Farben werden dabei durch Grauwerte simuliert. Und das ganze geht in Sekundenschnelle. Vergessen Sie also irgendwelche seltsamen BASIC Programme, die zwar auch konvertieren können, aber so, daß man in der Zwischenzeit ruhig mal eine (oder auch zwei) Tassen Kaffee trinken kann. NEO2MONO erledigt ein Bild in 3,6 Sekunden - ohne Laden von Diskette - und ist damit gleichzeitig ein Beispiel dafür, daß man in C die kompliziertesten Bitfummeleien sehr effizient erledigen kann.

Bevor ich das Programm kurz erkläre einige Worte zur Bedienung: Das Programm muß die Endung TTP (TOS Takes Parameter) erhalten. Nach dem Anklicken gibt es dann zwei Möglichkeiten:

  1. man gibt einfach den Namen eines Neochromebildes an. Dieses wird dann geladen und die konvertierte Version auf dem Bildschirm dargestellt. - Treten Sie einige Meter zurück und bewundern Sie das Ergebnis.

  2. oder Sie geben vor dem Dateinamen noch -w ein. Dann verschwindet das Bild nach Beendigung des Programms zwar wie bei 1.) vom Bildschirm, aber gleichzeitig wird eine Kopie davon auf Diskette gerettet (mit der Endung mon statt neo). Dadurch haben Sie die Möglichkeit, das Bild später mit irgendwelchen anderen Programmen zu laden (z. B. in BSAIC mit bload) und zu bearbeiten.

Also, um es noch einmal ganz deutlich zu machen!

neo2mono aafall.neo

konvertiert den berühmten Neochrome Wasserfall (leider ohne Bewegung) und erzeugt nach der Konvertierung eine Datei aafall.mon die man dann weiterverarbeiten kann.

Um zu verstehen, wie das ganze funktioniert, muß man zuerst einmal wissen, wie der ATARI seinen Bildschirm organisiert hat. Uns interessieren nur zwei der drei Auflösungsstufen.

Die Auflösung von 640 * 400, die es (bis jetzt) nur schwarz-weiß gibt, weil wir das Bild auf diese Auflösung konvertieren müssen und 320 * 200, weil die Neochrome Bilder in dieser Auflösung gespeichert sind.

Die hohe Auflösung ist sehr einfach angelegt. Jedem Bit im Bildspeicher ist genau ein Punkt (Pixel) auf dem Bildschirm zugeordnet. Ist das Bit auf eins gesetzt, wird der Punkt schwarz, ist es auf Null, weiß angezeigt.

Das höchstwertige Bit im allerersten Wort (l Wort = 16 Bit) des Bildspeichers ist für den Punkt ganz links oben in der Ecke zuständig. Man kann sich nun leicht ausrechnen, daß man genau 640/16 = 40 Speicherworte für eine Bildschirmzeile benötigt. Wenn man diese Zahl mit 400 der Anzahl der Zeilen multipliziert, kommt man genau auf die 16000 Worte (oder 32000 Byte), die der Bildspeicher groß ist.

Bei der niedrigen Auflösung ist alles etwas komplizierter. In dieser Auflösungsstufe hat man 16 Farben zur Verfügung, d. h. für jeden Bildpunkt muß im Bildspeicher ein Wert zwischen 0 und 15 stehen, der die jeweilige Farbe des Punktes repräsentiert. Um Zahlenwerte in diesem Bereich darzustellen, benötigt man 4 Bit:

1510 = l * y + l * 22 + l * 2' + l = lllh

Da der Bildspeicher immer die gleiche Größe hat, folgt daraus, daß man damit nur noch 1/4 der hohen Auflösung, eben 320 * 200 Pixel, hat. Es bleibt jetzt noch zu klären, wo die zu einem Punkt gehörigen Bits zu finden sind. Die Lösung sieht so aus:

Immer vier aufeinanderfolgende Worte im Bildspeicher ergeben 16 Farbpunkte auf dem Bildschirm, und zwar bilden immer die vier Bits mit der gleichen Wertigkeit zusammengenommen den 4-Bitwert der benötigt wird. Für den Punkt ganz links oben bedeutet das also: Man nehme die ersten vier Worte im Bildspeicher, von jedem Wort das höchstwertige Bit, und schreibe diese Bits nebeneinander. Der Wert, der sich dann ergib, entspricht der Farbe des Punktes. (Anmerkung für Profis: Es ergibt sich natürlich nur der Index in die Farbpalette, das tut hier aber nichts zur Sache. Falls Sie das nicht ganz verstanden haben, trösten Sie sich, mir ging es anfänglich genauso. Betrachten Sie sich einfach einmal Bild l, dann wird Ihnen alles klar werden.)

Eine ähnliche Rechnung wie oben ergibt nun, daß man bei 320 Punkten a 4 Bit = 1280 Bit = 1280/16 = 80 Worte pro Bildzeile benötigt.

Die Idee ist nun, jedes Farbpixel durch einen Grauwert anzunähern. Sie werden jetzt zu Recht fragen, woher ich denn Grauwerte nehmen will, wie ja allgemein bekannt ist, bietet der Mo-nochromemonitor, wie sein Name schon sagt, nur weißestes Weiß oder schwärzestes Schwarz, und dazwischen ist nichts.

Hier kommt nun eine Methode zum Zuge, die schon in den ersten Anfängen der Computergraphik beliebt und bekannt war. Der Trick ist: Aus einer angemessenen Entfernung betrachtet, verschwimmen mehrere Pixel zu einem Fleck, der umso dunkler wird, je höher die Pixelkonzentration wird. Man nehme also z. B. eine 2*2 Matrix, fülle sie nach und nach mit Pixeln an, und schon hat man fünf Grauwertstufen zur Verfügung. Bild__2 zeigt, wie ich die Verteilung für NEO2-MONO gewählt habe. Die vier Bit Farbinformation werden also auf vier Pixel in der Grauwertmatrix abgebildet.

Jetzt haben Sie alles an Grundlagen zusammen, um das Konvertierungsprogramm verstehen zu können.

Das Programm zerfällt in drei Hauptbestandteile:

Einlesen des Farbbildes aus einer Ne-ochromedatei, Konvertieren des Bildes direkt auf den Bildschirm und, falls gewünscht, Abspeichern des konvertierten Bildes auf Diskette.

Zum Einlesen und Abspeichern ist nicht viel zu sagen. Es werden nicht die C Standardroutinen benutzt, da diese im allgemeinen zu langsam sind, sondern direkt die Schnittstellen, die das ATARI TOS anbietet. Ein Bild kann dadurch von der RAM-Disk in weniger als 2 Sekunden geladen werden. Beim Laden des Bildes wird zuerst ein Vorspann von 128 Byte überlesen. Dieser enthält globale Information zum Bild, wie zum Beispiel die Farbpalette. Ich benutzt diese Informationen allerdings nicht in meinem Programm. An dieser Stelle ergeben sich noch Verbesserungsmöglichkeiten, denen Sie gerne einmal nachgehen können.

Das Farbbild wird in einen integer Ar-ray namens buff geladen und von dort aus weiterverarbeitet. Eine andere Möglichkeit wäre, es das Bild „in situ", also direkt im Bildschirmspeicher, zu konvertieren. Bei dieser Methode müßte allerdings jedes Bit des konvertierten Bildes separat gesetzt und gelöscht werden, während meine Methode das simultane Verarbeiten von jeweils zwei nebeneinanderliegenden Bits erlaubt - daraus folgt — zusammen mit einigen anderen Dingen - mehr als doppelte Geschwindigkeit. Außerdem ist der Atari ein Rechner, bei dem man nun ja wirklich nicht mit Speicher zu geizen braucht.

Die eigentliche Konvertierung erfolgt in der Funktion convert() innerhalb der drei verschachtelten for-Schleifen. Die Schleife mit dem Index y läuft über die Zeilen des Farbbilds, gleichzeitig werden die zwei logn Pointer ptrl und ptr2 hochgezählt. Sie zeigen jeweils auf den Anfang der zwei Grauwertzeilen, die sich aus der Konvertierung einer Farbzeile ergeben. Büd_3 zeigt, wie der erste Punkt einer Farbzeile konvertiert wird. Die Schleife mit dem Index x läuft nun jeweils innerhalb einer Farbzeile in Sprüngen von 4 Worten. Wie wir oben shcon festgestellt haben, ergeben vier aufeinanderfolgende Speicherworte 16 aufeinanderfolgende Pixel im Farbbild. Diese 16 Pixel werden in der innersten Schleife mit dem Index bit errechnet. Die Funktion testQ liefert zu jedem Punkt den Farbwert. Abhängig von diesem Farbwert werden nun jeweils zwei Bits in den long Variablen ll und 12 gesetzt. Da aus einem Farbpunkt zwei nebeneinanderliegende Bits in ll bzw. 12 werden, füllt die bit-Schleife gerade die zwei Variablen, so daß diese nach Verlassen der Schleife an die Stelle des Bildspeichers geschrieben werden können, auf die ptr bzw. ptr2 zeigen.

Das ist schon die ganze Konvertierung. Wer die Erklärung beim ersten Durchlesen schon verstanden hat, kann sich gratulieren.

Die Konvertierung ist übrigens nicht bijektiv, das heißt sie läßt sich nicht umkehren. Um eine eindeutige Abbildung zu erhalten, müßte man jeden der 16 Farbwerte in eines von 16 Graumustern verwandeln. Das bedeutet, statt einer 22 Matrix muß man eine 44 Matrix mit Pixeln füllen. Um das ganze Bild so auf den Monochromemonitor bringen zu können, müßte dieser allerdings eine Auflösung von 1380 * 800 Punkten haben.

Wer keinen C-Compilter hat oder zu faul ist, das Programm abzutippen, kann die lauffähige Version zusammen mit den Neochromebildern auf der Public Domain Diskette bestellen.

/****************************************************************************
 * NEO2MONO                                   Version:   31.03.86
 * geschrieben von:  Thomas Weinstein                    08.05.86
 *                   Koenigsberger Str. 29d
 *                   7500 Karlsruhe 1
 *
 * Konvertiert Neochromebilder (320 * 200) fuer
 * Monochrome Bildschirm (640 * 400).
 * Farben werden durch Grauabstufungen angenaehert.
 *
 ****************************************************************************/

/*
 * DEFINES
 */

#define  printline(s)                      gemdos(0x09,s);
#define  create(name,mode)                 gemdos(0x3c,name,mode)
#define  open(name,att)                    gemdos(0x3d,name,att)
#define  close(hndl)                       gemdos(0x3e,hndl)
#define  read(hndl,size,buff)    (long)    gemdos(0x3f,hndl,size,buff)
#define  write(hndl,size,buff)   (long)    gemdos(0x40,hndl,size,buff)
#define  Log_base()              (short *) xbios(0x03)
#define  Wait()                            gemdos(0x01)

#define  C_OFF       "\033f"
#define  C_ON        "\033e"
/*
 * GLOBALE VARIABLE, FUNKTIONEN
 */

long     gemdos();
long     xbios();
short    buff[16000];      /* Puffer fuer Neochrome Farbbild */
short    *logbase;         /* Bildschirm Basisadresse        */

/*
 * MAIN
 */
main(argc,argv) char **argv;
{
     short wflag = 0;

     logbase   =  Log_base();  /* hole logische Bildschirmbasis */
     printline(C_OFF);         /* Cursor ausschalten.           */

     if (argc == 3)  /* Programmname + 2 Argumente */
       if (!strcmp(argv[1],"-w")) {
                             wflag++;
                             argc--;
                             argv++;
        }

     if (argc == 2) {
         if (load_file(argv[1])) {;   /* Neochromebild laden           */
                          convert();  /* Konvertieren                  */
                          if (wflag)
                             save_file(argv[1]); /* Grauwertbild speichern */
                          Wait();
         } else
            printline("Fehler beim Einlesen aufgetreten\n");
      } else {
         printline( "USAGE: hilow [-w] \n");
         Wait();
      }
      printline(C_ON);              /* Cursor einschalten            */
} /* MAIN */


/*
 * Laedt Datei 'name' nach 'buff'. Keine Pruefung ob gueltige Neochromedatei
 */

load_file(name)   char *name;
{
   short hndl;

       if ((hndl = open(name,0)) < 0) {
          printline("Kann Datei nicht oeffnen!\n");
          Wait();
          exit(1);
       }
       read(hndl,128L,buff);   /* Header lesen       */
       if (read(hndl,32000L,buff) < 0L) { /* Bilddatei einlesen */
         printline("Fehler beim Lesen\n");
         Wait();
         return(0);
       }
       close(hndl);            /* Datei Schliessen   */

       return(1);
}

/*
 * Schreibt konvertiertes Bild nach 
 */

save_file(name) char *name;
{
   char  *strcat(), *strchr(), *help; 
   short hndl;

   if ((help = strchr(name,'.')) == (char *) 0) {
       printline(" Kein '.' in Eingabedateiname ");
       return;
   }

   *++help = '\0';      /* Extension abschneiden   */
   strcat(name,"mon");  /* 'mon' ans Ende kopieren */

   if ((hndl = create(name,0)) < 0) {
       printline("Kann Datei nicht zum Schreiben oeffnen");
       return;
   }

   if (write(hndl,32000L,logbase) < 0) {
       printline("Kann Bild nicht schreiben");
       return;
   }
   close(hndl);
}


/*
 * Konvertiert Neochromebild in Monochrome Grauwertbild
 */

convert()
{
   register long  l1,l2,*ptr1,*ptr2;  /* Hilfsvariable fuer Grauwertbildung */
   register short *word, bit;
   register short color,x,y;          /* Laufvariablen fuer Farbbild        */

   ptr1 = (long *) logbase;           /* Zeiger auf 1. Zeile Grauwertbild   */
   ptr2 = (long *) logbase + 20;      /* Zeiger auf 2. Zeile Grauwertbild   */

   word = &buff[0];
   for (y=0; y <= 16000; y += 80) {  /* Ueber Zeilen von Farbild laufen */
      for (x=0; x < 80; x += 4) {    /* Zeile konvertieren. Immer vier  */
                                     /* 16 Bit Worte geben 16 Pixel     */
            l1 = l2 = 0L;
            for (bit = 15; bit >= 0; bit--) { /* 16 Bit Wort untersuchen   */
            color =  test(word,bit);          /* test liefert 0 - 4        */
            l1 <<= 2; l2 <<= 2;               /* Grauwert 2 Bit nach links */
              switch (color) {                /* Ergibt folgende Grauwerte:*/
                case 4:
                    l1 += 3;                  /*   XX         X = schwarz  */
                    l2 += 3;                  /*   XX         0 = weiss    */
                    break;
                case 3:
                    l1 += 3;                  /*   XX                      */
                    l2 += 2;                  /*   X0                      */
                    break;
                case 2:
                    l1 += 2;                  /*   X0                      */
                    l2 += 1;                  /*   0X                      */
                    break;
                case 1:
                    l1 += 1;                  /*   0X                      */
              }                               /*   00                      */
            }
            *ptr1++ = l1;  *ptr2++ = l2; /* In Bildspeicher schreiben */
            word += 4;                   /* Die naechsten 4 Worte     */
      }
      ptr1 += 20; ptr2 += 20;
   }
}

/*
 * liefert 0 - 4 je nachdem wieviel Farbbits fuer Pixel gesetzt sind
 */

test(word,bit)
register short *word;
short bit;
{
   register short shift;

          shift = 1 << bit;

          return (((*word    & shift) ? 1 : 0) +
                 ((*(word+1) & shift) ? 1 : 0) +
                 ((*(word+2) & shift) ? 1 : 0) +
                 ((*(word+3) & shift) ? 1 : 0));
}
/* ENDE VON HILOW.TTP */


Thomas Weinstein
Aus: ST-Computer 09 / 1986, Seite 45

Links

Copyright-Bestimmungen: siehe Über diese Seite