C-Programm, das ein ppm-Bild in ein Graustufenbild umwandelt

wantri

Mitglied
Hallo,

ich habe ein Farbbild im ppm-Format vorliegen und möchte dieses in ein Graustufenbild umwandeln. Der Text zu diesem ppm-Bild liegt im ASCII-Code vor und ich erhalte folgendes, wenn ich es im Texteditor öffne:
P3
# Created by GIMP version 2.10.0 PNM plug-in
500 539
255
187
107
74
181
101
68
189
109
76
191
111
78
184
104
69
...
Die eigentliche Datei ist natürlich viel länger. Ich habe hier nur die ersten Zeilen hineinkopiert.
Folgendes habe ich versucht, um das Bild umzuwandeln:
C:
#include <stdio.h>
#include <stdlib.h>
int main()
{

    int number1;

    int number2;

    int number3;

    FILE *in;

    in = fopen("image.ppm", "rb");

    FILE *out;

    out = fopen("newimage.ppm", "wb");

    fseek(in, 60, SEEK_SET);

    fseek(out, 60, SEEK_SET);

        while(!feof(in)) {

            fscanf(in,"%d",&number1);

            fscanf(in,"%d",&number2);

            fscanf(in,"%d",&number3);

            fprintf(out,"%d\n",(number1+number2+number3)/3);
        }

    fclose(in);

    fclose(out);


        return 0;
       
}

Dabei treten zwei Probleme auf:

1) Die Kopfzeile wird nicht in die neue Datei übernommen. Wenn ich das Programm wie oben angegeben ausführe, steht in der Textdatei:
(Diese 122 ist mehrmals eingerückt, das wird hier aber nicht angezeigt)
122
116
124
126
119
127
133
...
Ich wollte die ersten 60 Bytes unverändert lassen, da sie Teil der Kopfzeile stehen, stattdessen werden sie durch Leerzeichen ersetzt.

2) Ich habe die Kopfzeile dann manuell eingefügt und die erste Zahl(122) an der richtigen Stelle eingefügt. Ich hatte nun zwar eine ppm-Datei, allerdings füllt das neue Bild die entsprechende Fläche nicht komplett aus. Das umgewandelte Graustufenbild taucht jetzt drei mal nebeneinander im ersten Drittel des Bildes auf, während der untere Abschnitt schwarz ist.

Ich wäre sehr dankbar, wenn mir jemand bei diesen beiden Problemen weiterhelfen kann.

Vielen Dank im Voraus!
 
K

kneitzel

Gast
Also Dein Vorgehen kann ich nur schwer nachvollziehen:

Du überspringst einfach 60 Zeichen beim lesen und beim schreiben. Aber das müssen natürlich keine 60 Zeichen sein. Und wenn Du den Header beibehalten willst, dann musst Du ihn natürlich lesen und wieder schreiben.

Aber nach meinem Verständnis ist der Ansatz doch auch komplett falsch. Der Header bei Graustufe ist ja P2 und nicht P3. Und als Endung hätte ich pgm erwartet... Denn nur dann hast Du einen Wert statt eben der drei Werte.

Siehe dazu auch einfach: https://de.wikipedia.org/wiki/Portable_Anymap
 

wantri

Mitglied
Hallo,

Vielen Dank für Deine Antwort!

Ich habe es mit P2 ausprobiert, und es klappt jetzt, sofern ich die Kopfzeile manuell einfüge.
Allerdings weiß ich noch nicht, wie ich die Kopfzeile automatisch bestimme. Ich habe die ersten 60 Bytes übersprungen, da ich abgezählt habe, dass es sich hierbei um die Kopfzeilenbytes handelt.
Gibt es ein Verfahren, die Umwandlung allgemein nach der Kopfzeile zu starten?
Und wie würde sich in diesem Fall die Kopfzeile neu schreiben lassen?
 
K

kneitzel

Gast
Das ist aber nur in Deinem Beispiel 60 Bytes. Das kann aber auch mehr oder weniger sein.

Also musst Du die Kopfzeile lesen und auswerten. Der Kopf ist in dem Link im Detail beschrieben. Also wirklich den Kopf schön lesen.

Es muss erst ein "P3" kommen, dann die Breite und Höhe und die maximale Helligkeit.
Getrennt sind die Werte durch Leerzeichen, welche ein Leerzeichen, Zeilenumbruch, Line Feed, ... sein kann.
Zeilen, die mit einem "#" starten ignorierst Du generell, da dies Kommentare sind.

Und wenn Du das gelesen hast, dann kannst Du die gelesenen Werte schreiben:
P2
Breite Höhe
MaxHelligkeit
...

Auch in den Werten können natürlich noch Kommentare kommen. Mit denen musst Du also auch umgehen können.

Ich würde hier generell Zeilenweise die Werte lesen. Leere Zeilen und Zeilen, die mit einem # starten, ignoriert man.
Und dann wird die Zeile gesplittet so dass einzelne Werte kommen..

Du musst also neben beliebigen Kommentaren auch andere Schreibweisen lesen können. Also der Header
P3 500 539 255
ist ebenso korrekt.
 

wantri

Mitglied
Also ich habe mich nochmal da drangesetzt und es klappt immer noch nicht so ganz:

C:
int main()
{
    char c[255];
    int n1;
    int n2;
    int n3;

    int number=0;
    FILE *in;
    in = fopen("image.ppm", "rb");

    
    FILE *out;
    out = fopen("newimage.pbm", "wb");

     while(!feof(in)) {
            fgets(c, 255, in);
            if(c[0]!='#'){
                    //überprüfen, ob P3-Zeile gefunden wurde und in P2 umwandeln
                if(c[0]=='P'&&c[1]=='3'){
                   fprintf(out,"%s\n","P2");
                   continue;
                }
            //Es müssen nachdem P2 noch zwei Zeilen (für Breite/Höhe und Helligkeit kommen)
            //Nur die darauffolgenden Zahlen werden durch 3 geteilt, daher die Zählvariable number
                if(number<2){
                    //fscanf(in,"%d",&n1);
                   // printf("%d\n",n1);
                    fprintf(out,"%s\n",c);
                number++;
                continue;
                }
                fscanf(in,"%d",&n1);
                fscanf(in,"%d",&n2);
                fscanf(in,"%d",&n3);
                fprintf(out,"%d\n",(n1+n2+n3)/3);
            }

            }

    fclose(in);
    fclose(out);

        return 0;
    }

Wenn ich das so mache wie oben kommt:

Code:
P2
500 539

255

120
119
125
124
121
...
Also die ersten Zeilen stimmen(abgesehen von den Leerzeilen), aber dafür sind die darauffolgenden Zahlen falsch. Diese müssten mit 122 beginnen.

Ersetze ich den einen if-Block folgendermaßen:
Code:
if(number<2){
                    fscanf(in,"%d",&n1);
                    fprintf(out,"%s\n",n1);
                number++;
                continue;
                }

erhalte ich:
C:
P2
2000330816
122
116
124
126
119
127
...
Ab 122 ist also richtig. Aber das zwischen P2 und 122 ist falsch.

Woran liegt das?
 
K

kneitzel

Gast
Also der Algorithmus ist recht abenteuerlich :)

Zum einen liest Du Zeile für Zeile ein, das ist erst einmal ok.

Kernfehler ist jetzt aber noch, dass du dann den Wert mit der ersten Zahl einliest (über das Einlesen der Zeile) und dann aber nicht den Wert der Zeile nutzt, sondern Zahlen aus dem Stream liest.

Davon unabhängig muss bei Dir zwingend das p3 am Anfang alleine in einer Zeile stehen und Breite/Höhe muss auf einer Zeile kommen. Das kann so sein, aber so muss es nicht sein.

Desweiteren hast du Probleme mit dem Algorithmus, wenn eine Zeile länger als 255 Zeichen lang sein sollte.

Wenn du Zeile für Zeile liest, dann musst du die Zeile in Werte umwandeln. Und dann kannst du die Werte nutzen...

Evtl. Ist es einfacher, wenn du eine Methode schreibst, die einen Wert aus dem Stream ermittelt statt Zeile für Zeile zu lesen.
Der Algorithmus liest Zeichen für Zeichen ein:
Am Anfang können noch Lerzeichen, Zeilenumbrüche und so kommen. Die ignorierst du.
Dann kommt ggf. eine #, dann liest Du so lange weiter, bis Du zu einem newline kommst.
Sobald ein Zeichen eines Wertes kommt (P oder 0-9), dann nimmst du das in den neuen Wert.
Der neue Wert wird so lange erweitert, bis ein Leezeichen gelesen wurde.

Wenn du sowas hast, dann sieht der Algorithmus so aus:
Wert lesen -> muss p3 sein, P2 schreiben
3 Mal:
- Wert lesen -> Wert schreiben (breite, Höhe, Max Helligkeit)

Ab dann: 3 Werte lesen, in Zahl umwandeln, zusammen addieren und durch 3 teilen -> Ergebnis schreiben.

Wichtig ist: Wert Schreiben muss Werte trennen, z.B. mit Zeilenumbrüchen oder Leerzeilen.

Ist der Algorithmus einfacher zu verstehen als dieses zeilenweise lesen?
 

mihe7

Top Contributor
Mal ein Vorschlag (Achtung: meine C-Zeiten sind lange vorbei :))
C:
void skipToEol(FILE *in) {
    int ch;
    while ((ch = fgetc(in)) != EOF && (ch != '\n')) {
        // just repeat
    }
}

int nextToken(FILE *in, char *buf) {
    int result;
    int gotInput;
    do {
        if ((result = fscanf(in, "%s", buf)) != EOF) {
            if ((gotInput = strncmp(buf, "#", 1)) == 0) {
                skipToEol(in);
            }
        }
    } while (!gotInput && result != EOF);
    return result;
}
EDIT: CamelCase angepasst.
 

Ähnliche Java Themen

Neue Themen


Oben