Der ATARI ST verfügt in seiner niedrigsten Auflösung bekanntlich über 16 Farben, die aus 512 Variationen frei gewählt werden können. Dies geschieht über indirekte Farbengebung, d.h. der Wert eines auf dem Bildschirm dargestellten Pixels (zwischen 0 und 15) dient als Offset in einer Tabelle, in der die aktuelle, eingestellte Palette abgelegt wurde. Meistens haben daher verschiedene Bilder auch höchst individuelle Paletten.
Schaltet man auf ein anderes Bild um, ohne gleichzeitig die Palette zu ändern, so erscheint es stark verfremdet, da es in den falschen Farben dargestellt wird. Das mag für Künstler (Surrealisten) interessant sein, für den normalen Anwender ist es zumindesten ärgerlich.
Normalerweise ist es ja auch ohne Problem möglich, die Palette umzuschalten. Was aber, wenn man mehrere Bilder mischen möchte? Ich denke da an viele Anwendungen wie z.B. bei Überblendungen, Raytracing etc.. Abgesehen von IMAGIC - ein Programm, das ich mir leider bisher noch nicht leisten konnte - ist mir kein Programm bekannt, das dies unterstützt.
An dieser Stelle springt mein Programm helfend ein. Es lädt ein Bild, rechnet es um und speichert es dann mit den neuen Farben ab.
Zur Auswahl stehen zwei verschiedene Paletten: die Standard-Farbenpalette des Desktops und eine regenbogenähnliche. Durch einfache Änderungen des Programmes können Sie auch eigene Paletten einsetzen oder das Programm so umschreiben, daß die zu erzeugenden Farben von Diskette eingelesen werden bzw. aus anderen Bildern extrahiert werden können. Das Programm kann Bilder in dem weitverbreiteten DEGAS-Format verarbeiten.
Die Programmiersprache, die ich verwendet habe, ist Megamax-C. Sie können es leicht in andere C-Dialekte umschreiben, wenn diese Inline-Assemblerbefehle unterstützen oder auf die LineA-Grafikroutinen zugreifen können.
Zur Bedienung des Programmes ist nicht viel zu sagen: eine Fileselektorbox fragt nach dem Namen des Bildes, das dann eingelesen wird. Eine Alertbox fragt ab, welche der beiden Paletten verwendet werden soll. (Leider steht in der niedrigsten Auflösung dafür sehr wenig Platz zur Verfügung.) Danach erfolgt die Berechnung, die ca. 80 Sekunden dauert. Als letztes kann wieder in einer Fileselektorbox der Filename eingegeben werden, unter dem das neue Bild abgespeichert werden soll.
Das Programm benötigt nicht sehr viel Arbeitsspeicher - nur zur Einrichtung von zwei Arbeitsbildschirmen wird welches reserviert.
Nun zum verwendeten Algorithmus. Er versucht, mit der zur Verfügung stehenden Palette die größtmögliche Ähnlichkeit zwischen dem ursprünglichen und dem erzeugten Bild herzustellen. Dabei wählt er nicht nur für jedes Pixel die Farbe, die der ursprünglichen am ähnlichsten ist, sondern ist auch noch in der Lage, nicht vorhandene Farben durch additives Mischen der Farben von benachbarten Pixeln zu erzeugen. Wenn etwa Grün benötigt wird, dieses aber nicht in der Zielpalette vorhanden ist, werden ein blaues und ein gelbes Pixel nebeneinandergesetzt. Dadurch wird das umgesetzte Bild jedoch auch unschärfer, was leider nicht zu vermeiden ist. Auf manchen Monitoren - besonders Fernsehern - die gar nicht in der Lage sind, einzelne Pixel zu trennen, da sie schon von Natur aus unscharf sind, fällt dies nicht sehr stark ins Gewicht, aber solch ein Monitor wird dann auch kaum von ernsthaften Grafikfreaks benutzt.
Die Idee zu diesem Algorithmus lieferte mir ein Algorithmus von Floyd/ Steinberg, der kürzlich in der c’t beschrieben wurde, jedoch nur zur Darstellung von Grauwerten auf reinen Monochrombildschirmen dient.
Der Algorithmus bearbeitet das Bild Zeile für Zeile, von oben nach unten. Ein Array enthält die Werte der Pixel im ursprünglichen Bild. Für jedes einzelne Pixel geschieht dann folgendes:
Nun stimmt diese Farbe mit der ursprünglichen Farbe des Pixels i.allg. nicht überein. Also entsteht beim Zeichnen ein Fehler, der ausgeglichen werden muß, indem er bei den folgenden Pixeln Berücksichtigung findet. Der Unterschied zwischen ursprünglicher und erzeugter Farbe wird - mit umgekehrtem Vorzeichen - auf die umliegenden Pixel verteilt. Das unmittelbar rechts danebenliegende Pixel wird mit 2/8 bedacht, genauso das direkt darunterliegende. Das rechts darunterliegende Pixel erhält noch 1/8.
Zur Verteilung des Fehlers sind noch einige Anmerkungen zu machen:
Der Fehler wird nicht sofort durch 8 geteilt, sondern erst, wenn er zum Farbwert des Pixels hinzugerechnet wird. Dadurch wird zum einen eine kleine Zeitersparnis erzielt, zum anderen lassen sich Rundungsfehler vermindern, da mit einer Art Festkommaarithmetik gerechnet wird -nämlich 3 Binärstellen hinter dem Komma. Für den Fehler existieren - für jede Farbkomponente getrennt - Arrays, die die Fehler jeweils einer ganzen Zeile enthalten. Wenn ein Pixel bearbeitet wurde, enthält solch ein Array an dieser Stelle schon den Fehler für die nächste Zeile.
Wenn Sie den Algorithmus näher betrachten, stellen Sie fest, daß nur 5/8, also 62.5% des gemachten Fehlers ausgeglichen werden. Diese Werte habe ich so festgelegt, da bei höheren Werten oftmals Fehler auftraten, die ich als “Überkompensationsfehler” oder auch als “Resonanzkatastrophen” bezeichnen könnte. Dadurch, daß ein Fehler zu gut ausgeglichen wurde - d.h. die ähnlichste Farbe bei Berücksichtigung des Fehlers war der ursprünglichen Farbe viel weniger ähnlich, als ohne Kompensation - wurde ein größerer Fehler erzeugt, wodurch dann nach einigen Iterationen der ganze Mechanismus entgleiste. In Folge zog sich über den restlichen Bildschirm eine Fahne von falschen Farben. (Ein überaus interessanter Defekt, aber nicht das gewünschte Ergebnis.) Sie können auch mit eigenen Werten Experimente anstellen -ich bin an Verbesserungen des Programmes immer interessiert, am meisten aber an Vorschlägen, wie die Suche nach der ähnlichsten Farbe noch weiter optimiert werden könnte.
Ein wenig Aufmerksamkeit möchte ich noch auf die Programmierung der Routinen zum Einlesen und Abspeichern der Bilder richten. Das Programm ist - bis auf die im ROM vorhandenen Fehler z.B. für die Fileselektorbox - kaum mehr zum Absturz zu bewegen, da man noch nicht einmal auf ein nicht existentes Laufwerk zugreifen kann!
Noch einige Anregungen und Anmerkungen:
Das Programm läßt sich auch noch für andere Zwecke mißbrauchen. Durch Wahl einer geeigneten Palette kann man z.B. farbige Bilder in Graustufen umrechnen lassen (aber in der 320* *200-Auflösung!).
Zur Vorsicht möchte ich raten bei der Gestaltung eigener Paletten. Wenn Sie eine der Grundfarben - Rot, Grün und Blau - nicht genügend berücksichtigen, kann es leicht zu Overflow-Fehlern kommen, die evtl. zum Absturz führen.
Zur Zeit arbeite ich an einer Erweiterung des Programmes, um farbige Bilder möglichst exakt in ihr schwarzweißes Äquivalent umzuformen. Eine Erweiterung für die mittlere Auflösung halte ich dagegen nicht für sinnvoll. Diese wird auch kaum für grafische Zwecke verwendet - ich glaube, daß sie tatsächlich für überhaupt nichts verwendet wird.
Ich hoffe, daß ich Ihnen mit diesem Programm geholfen habe, die Grafikfähigkeiten des ATARIs voll auszuschöpfen, und wünsche Ihnen viel Erfolg bei eigenen Experimenten. hk
#include <osbind.h>
#include <stdio.h>
/*
COLCONV.C - Version Nr. 1.021 A
Dieses Programm wandelt ein Bild (in der niedrigsten Auflösung)
mit einer beliebigen Farbpalette in eins mit einer festgelegten
Palette um. Dieses eignet sich dann für Überblendungen o.ä.
Die Umwandlung erfolgt mittels eines selbst entwickelten
Algorithmus, der die Abweichungen der Farben auf die umliegenden
Punkte verteilt.
Dabei wird mit einer Genauigkeit gearbeitet, die auch kleinere
Fehler zuverlässig ausgleicht.
Hans-Detlef Siewert, Bruchbäumerweg 20 A, 4780 Lippstadt
*/
/****************************************************************/
/* Copyright by Hans-Detlef Siewert, HaDeS-Soft, April/Mai 1988 */
/****************************************************************/
int oldcolor[16], picolor[16];
char Pathname[128], Filename[128];
char *WS1, *WS2;
char *Desktop, *WrkScr1, *WrkScr2;
/*
Desktop ist der Zeiger auf den normalen Bildschirmspeicher.
Daneben werden zwei weitere Bildschirmspeicher angelegt. Der
erste enthält das ursprüngliche Bild, der zweite dann das
bearbeitete Bild.
*/
int Filehandle,Auflsg;
int rerr[321],gerr[321],berr[321]; /* Fehler für Rot, Grün und Blau */
int col[321]; /* Farben einer Bildschirmzeile */
int srpal[16],sgpal[16],sbpal[16]; /* Source-Palette - Farbanteile */
int trpal[16],tgpal[16],tbpal[16]; /* Target-Palette - Farbanteile */
/*
Das sind die Paletten, die gewählt werden können:
(Sie können hier natürlich eigene Paletten verwenden)
*/
int paletten[2][16]={ { 0x0777, 0x0700, 0x0070, 0x0770,
0x0007, 0x0707, 0x0077, 0x0555,
0x0333, 0x0733, 0x0373, 0x0773,
0x0337, 0x0737, 0x0377, 0x0000 },
{ 0x0777, 0x0004, 0x0007, 0x0047,
0x0077, 0x0074, 0x0070, 0x0470,
0x0770, 0x0740, 0x0700, 0x0400,
0x0444, 0x0040, 0x0404, 0x0000 }};
int targpal; /* Das ist die gewählte Palette */
/****
Die folgenden Variablen und Routinen sind Teile eines von mir
geschriebenen Bindings zur Benutzung der LineA-Grafikroutinen
im ROM vom Megamax-C Compiler aus.
****/
/* Pointer auf den LineA- Parameterblock */
static long LineA_PP;
lina_init()
/* Diese Routine initialisiert die LineA Routinen.*/
{ asm
{
dc.w 0xa000 /* Aufruf der 'Initialization'-Routine */
move.l A0,LineA_PP(A4) /* LineA_PP zeigt auf den Parameterblock */
}
}
lina_setp(x,y,farbe)
/* Setzen eines einzelnen Punktes (x,y) mit'farbe'*/
int x,y,farbe;
{ asm
{
move.l LineA_PP(A4),A0 /* A0 zeigt auf den Parameterblock */
move.l 0x08(A0),A1 /* A1 zeigt auf die int_in-Tabelle */
move.w farbe(A6),(A1) /* Farbwert nach int_in[0] übertragen */
move.l 0x0C(A0),A1 /* A1 zeigt jetzt aufdie pts_in-Tabelle */
move.w x(A6),(A1)+ /* x nach pts_in[0]*/
move.w y(A6),(A1) /* y nach pts_in[1]*/
dc.w 0xa001 /* Aufruf von 'Put_Pixel' */
}
}
/* Routinen zum Abspeichern und Laden von Bildern im DEGAS-Format */
int Screen_Save()
/* Speichert Auflösung, Palette und Bild-Daten im DEGAS-Format */
{
long Length, Count;
Filehandle= Fcreate (Filename,0);
if (Filehandle<=0) /* Fehler beim Anlegen */
return(0);
Length= 2L; /* Auflösung schreiben */
Count = Fwrite (Filehandle, Length, (char *)&Auflsg);
Length= 32L; /* Farbpalette abspeichern */
Count+= Fwrite (Filehandle, Length, (char *) &picolor[0]);
Length= 32000L; /* Bild-Daten abspeichern */
Count+= Fwrite (Filehandle, Length, (char *) WrkScr2);
Fclose (Filehandle);
return (Count==32034);
}
int Screen_Load()
/* Lädt Bild im DEGAS-Format in die Workscreen1 */
{
long Length, Count;
Filehandle=Fopen (Filename,0);
if (Filehandle<=0) /* Fehler beim Öffnen */
return(0);
Length= 2L; /* Auflösung lesen */
Count = Fread (Filehandle, Length, (char *)&Auflsg);
Length= 32L; /* Farbenpalette lesen: */
Count+= Fread (Filehandle, Length, (char *) &picolor[0]);
Length= 32000L; /* Bild-Daten lesen */
Count+= Fread (Filehandle, Length, (char *) WrkScr1);
Fclose (Filehandle);
return (Count==32034);
}
int Get_Memory()
/* Initialisiert Variablen und legt Arbeitsbildschirme an. */
{
register int i;
unsigned amount,size;
/* Arbeitsbildschirme: */
amount= 8064;
size=4;
WS1= (char *) calloc(amount,size);
if (!WS1) /* War genug Speicher da? */
return(0);
WS2= (char *) calloc(amount,size);
if (!WS2) /* War genug Speicher da? */
return(0);
/* Seitengrenzen einhalten: */
WrkScr1= (char *) ( (long)WS1 & 0xFFFFFF00L);
WrkScr2= (char *) ( (long)WS2 & ßxFFFFFF00L);
Pathname[0]= '\0'; /* Strings initialisieren */
Filename[0]= '\0';
for (i=0; i<16; i++) /* Alte Farben merken */
oldcolor[i]= Setcolor(i,-1);
/* Fehlertabellen löschen */
for (i=0; i<320; i++)
{ rerr[i]=0; gerr[i]=0; berr[i]=0; }
return(1);
}
Init_Path()
/* Ermittelt aktuellen Pfadnamen für die Fileselektor-Box */
{
char Path[128];
/* Aktuelles Laufwerk: */
sprintf (Pathname,"%c:",(char) 65+ Dgetdrv());
Dgetpath (Path,0); /* Aktuelles Verzeichnis */
strcat (Pathname, Path);
strcat (Pathname, "\\*.PI1");
}
int Ask_Fname()
/* Fragt mit einer Fileselektorbox nach dem Filenamen, */
/* setzt das gewünschte Laufwerk und den gewünschten Pfad */
/* und fängt nebenbei alle möglichen Fehler ab. */
{
int Button,DDrive;
long ADrives; /* Angemeldete Laufwerke */
char Pfad[128], *sptr1, *sptr2;
strcpy (Pfad, Pathname);
/* File-Selektorbox für Frage nach Filenamen:*/
fsel_input (Pfad, Filename, &Button);
if (!Button) /* Abbruch-Button betätigt? */
return(0);
if (!strlen(Filename))/* Kein File angewählt?*/
return(0);
DDrive= (int)Pfad[0]-65; /*Gewünschtes Laufwerk? */
ADrives= Drvmap(); /* Laufwerk angeschlossen? */
if (!( (1<<DDrive) & ADrives))
return(0);
Dsetdrv(DDrive); /* Laufwerk setzen */
/* Pfadnamen kürzen: */
sptr1= &Pfad[2]; /* Laufwerksbez. löschen: */
sptr2= Pfad;
while (*sptr1!='\0') *sptr2++= *sptr1++;
*(++sptr2)= '\0';
sptr1= &Pfad[ strlen(Pfad) ]; /* Wildcard löschen */
while (*sptr1 != '\\') *sptr1—-= '\0';
if (Dsetpath(Pfad)<0) /* Pfadnamen setzen */
return(0);
return(1);
}
int Read_Pic()
/* Fragt nach dem Namen eines Bildes und liest Workscreen1 ein. */
{
int i,ok;
if (!Ask_Fname()) /* Filename abfragen */
return(0);
graf_mouse(256,0L); /* Maus abschalten */
Setscreen(WrkScr1, WrkScr1, -1); /* Workscreen1 anzeigen */
ok= Screen_Load(); /* Bild laden*/
Setscreen(Desktop, Desktop, -1); /* Desktop anzeigen */
graf_mouse(257,0L); /* Maus einschalten */
if (!ok) /* Fehler beim Laden? */
return(0);
return (Auflsg==0); /* Ist es ein Lowrez-Bild?*/
}
int Write_Pic()
/* Fragt nach dem Namen und speichert Workscreen2 ab. */
{
int i,ok;
if (!Ask_Fname()) /* Filename abfragen */
return(0);
Auflsg= 0; /* niedrige Auflösung */
for (i=0; i<16; i++) /* Farbwerte holen */
picolor[i]= paletten[targpal][i];
graf_mouse(256,0L); /* Maus ausschalten */
Setscreen(WrkScr2, WrkScr2, -1); /* Workscreen2 anzeigen */
ok= Screen_Save(); /* Bild abspeichern */
Setscreen(Desktop, Desktop, -1); /* Desktop anzeigen */
graf_mouse(257,0L); /* Maus einschalten: */
return(ok);
}
Clean_up()
/* Gibt reservierten Speicher frei und restauriert die Farben */
{
register int i;
free(WS1);
free(WS2);
for (i=0; i<16; i++)
Setcolor(i, oldcolor[i]);
}
getrow(line_addr,tab_addr)
register char *line_addr, *tab_addr;
/*
Diese Assemblerroutine wandelt eine komplette Bildschirmzeile
in die einzelnen Farbwerte um. line_addr zeigt auf die Startadresse
der Bildschirmzeile, tab_addr auf die Startadresse der
Tabelle, in der die Farbwerte gespeichert werden sollen.
Die Verarbeitung geschieht wortweise. Eine äußere Schleife wird
20 mal ausgeführt, dort werden jeweils immer 4 neue Worte geholt
(eines für jedes Farbplane). Diese Worte werden dann in einer
inneren Schleife in die Werte zerlegt, dadurch erhält man 16
Farbwerte. Insgesamt werden also 20*16=320 Pixelwerte erzeugt.
*/
{ asm
{
movem.l D0-D6,-(A7) /* Register retten */
moveq #0x13,D0 /* D0 zählt die Worte pro Farbplane*/
w_loop: /* Äußere Schleife, 20* */
move.w (line_addr)+,D2 /* D2 enthält Farbplane #0 */
move.w (line_addr)+,D3 /* D3 enthält Farbplane #1 */
move.w (line_addr)+,D4 /* D4 enthält Farbplane #2 */
move.w (line_addr)+,D5 /* D5 enthält Farbplane #3 */
moveq #0x0F,D1 /* D1 zählt die Bits pro Wort */
b_loop: /* Innere Schleife, 16* */
moveq #0x00,D6 /* D6 löschen */
roxl.w #1,D5 /* höchstwertiges Bit holen */
roxl.w #1,D6 /* ... und speichern */
roxl.w #1,D4 /* zweithöchstes Bit holen*/
roxl.w #1,D6 /* ... und speichern */
roxl.w #1,D3 /* dritthöchstes Bit holen*/
roxl.w #1,D6 /* ... und speichern */
roxl.w #1,D2 /* niederwertigstes Bit holen */
roxl.w #1,D6 /* ... und speichern */
move.w D6,(tab_addr)+ /* Farbwert in Tabelle speichern */
dbf D1,b_loop /* Ende der inneren Schleife */
dbf D0,w_loop /* Ende der äußeren Schleife */
movem.l (A7)+,D0-D6 /* Alte Registerwerte holen */
}
}
int Picture_Work()
/* Bearbeitet das geladene Bild. */
{288: int i, x, y; /* Schleifen-Laufvariablen */
int r,g,b; /* Farbanteile */
int Abweichung, MinAbw;
int nr,ng,nb; /* neu gemachter Fehler */
int fr,fg,fb; /* Fehler in den Farbanteilen */
register int scol; /* Ursprüngliche Farbe */
register int *rval, *gval, *bval; /* Farben-Vergleichswerte */
register int *color; /* Zeiger auf Farbentabelle */
graf_mouse(256,0L); /* Maus ausschalten */
Setscreen(WrkScr2, WrkScr2,-1); /* Workscreen2 anzeigen */
for (i=0; i<16; i++) /* Neue Palette setzen */
Setcolor(i, paletten[targpal][i]);
/* Ausgangs- und Zielpaletten in ihre Farbanteile umrechnen */
for (i=0; i<16; i++)
{
srpal[i]= (picolorfi] & 0x0700)>>8;
sgpal[i]= (picolor[i] & 0x0070)>>4;
sbpal[i]= picolor[i] & 0x0007;
trpal[i]= (paletten[targpal][i] & 0x0700)>>8;
tgpal[i]= (paletten[targpal][i] & 0x0070)>>4;
tbpal[i]= paletten[targpal][i] & 0x0007;
}
/* Berechnungssschleife f.alle Bildschirmzeilen */
for (y=0; y<200; y++)
{
/* Farben in einer Zeile lesen */
getrow (WrkScr1 + y*160, col);
/* noch keine Abweichungen in dieser Zeile */
nr=0; ng=0; nb=0;
/* Berechnung der neuen Farben */
color= &col[0];
for (x=0; x<320; x++)
{
scol= *color;
r= srpal[scol]+ (rerr[x]>>3);
g= sgpal[scol]+ (gerr[x]>>3);
b= sbpal[scol]+ (berr[x]>>3);
MinAbw= 255;
rval= &trpal[0];
gval= &tgpal[0];
bval= &tbpal[0];
/* ähnlichste Farbe suchen */
for (i=0; i<16;i++)
{
/* Rot-Anteil */
Abweichung = (*rval - r) *(*(rval++)-r); /* Grün-Anteil */
Abweichung+= (*gval - g) *(*(gval++)-g); /* Blau-Anteil */
Abweichung+= (*bval - b) *(*(bval++)-b);
if (Abweichung<MinAbw)
{ scol=i; MinAbw= Abweichung; }
}
/* Nächster Punkt */
*(color++)= scol;
/* gemachten Fehler merken */
fr= r- trpal[scol];
fg= g- tgpal[scol];
fb= b- tbpal[scol];
/* Fehler auf nachfolg.Punkte verteilen */
rerr[x+1]+= fr<<1;
gerr[x+1]+= fg<<1;
berr[x+1]+= fb<<1;
rerr[x]= nr+ fr<<1;
gerr[x]= ng+ fg<<1;
berr[x]= nb+ fb<<1;
nr= fr;
ng= fg;
nb= fb;
}
/* Plotten der neuen Farben */
color= &col[0];
for (x=0; x<320; x++)
lina_setp(x, y, *(color++));
}
/* Neues Bild fertiggestellt, Bildschirm zurückschalten. */
for (i=0; i<16; i++) /* Alte Palette setzen */
Setcolor(i, oldcolor[i]);
Setscreen(Desktop, Desktop, -1); /* Desktop anzeigen */
graf_mouse(257,0L); /* Maus einschalten */
return(1);
}
main()
/***** Hauptprogramm *****/
{
appl_init();
lina_init(); /* Initialisierung der LineA-Routinen. */
while (Cconis())
Crawcin();
Desktop= (char *) Physbase();
if (Getrez()) /* Nicht niedrigste Auflösung */
{
form_alert(1,"[1][Geht nur|in LowRes!][ Ach was ? ]");
exit (0);
}
if (!Get_Memory())
/* Speicher anfordern hat nicht hingehauen. */
/* Entweder Sie verkleinern Ihre RAM-Disk oder*/
/* Sie verkleinern Ihr Vermögen... */
{
form_alert(1,"[1][Zu wenig|Speicher!][ Tja...]);
exit (0);
}
Init_Path();
if (!Read_Pic()) /* Einlesen des Bildes */
/* Dabei kann *alles* mögliche passieren. */
{
Clean_up();
form_alert(1,"[3][Lesefehler!][ Mist ]");
exit(0);
}
/*
Frage nach der Farbpalette, die für das umgewandelte
Bild verwendet werden soll. Leider können die Alertboxen
in der Low-Resolution nicht allzu groß sein, so
daß man sich sehr knapp fassen muß.
Palette 1 ist die Standard-Farbenpalette des Desktops.
Palette 2 ist eine regenbogenähnliche Palette.
*/
targpal= form_alert(1,"[2][Welche|Palette?][1|2]")-1;
if (!Picture_Work())
/* Bearbeitung des Bildes */
/* Solch ein Fehler darf eigentlich nicht geschehen! */
{
Setscreen(Desktop, Desktop, -1);
Clean_up();
form_alert(1,"[2][Interner Fehler!][ Pech ]");
exit(0);
}
if (!Write_Pic()) /* Bild speichern */
/* Schreibschutz, kein Platz, defekte Diskette?*/
form_alert(1,"[3][Schreibfehler!][ Schade ]");
Clean_up();
appl_exit();
}
Listing: Das Umwandlungsprogramm