Ascii PPM File für OpenGL einlesen
-
Hallo!
Ich muss eine 512x512 texture Datei im ascii ppm format für opengl einlesen.
Ich habe jetzt schon eine Weile gegoogled, aber immer nur Programme gefunden, die das fürs Binär format machen.Also, was muss ich da anders machen?
Wie hätte OpenGL die Datei gerne? (in was für einem Array etc)Sollte ich am besten ifstream fürs einlesen verwenden?
Danke,
Zuhala
-
Das PPM-Format ist sehr simpel: http://en.wikipedia.org/wiki/Portable_pixmap#PPM_example
OpenGL kommt mit relativ unterschiedlichen Texturen klar. Vermutlich ist es als 8Bit RGB am einfachsten zu speichern und besten für OpenGL.
(also uint8_t pixel[ width * height * 3 ]; und r = 0, g = 1, b = 2 Offset)
-
Hallo rüdiger,
danke fuer deine antwort.
Also wenn ich das richtig verstehe ist in einem uint8_t array
jedes Element 8 bit gross?Aus "width * height * 3" schliesse ich, dass du fuer jeden Pixel
3 mal 8 bit speichern willst - fuer rot, gruen und blau.Aber was genau bedeutet das mit dem Offset?
Und was fange ich mit der Information "max Color" an?Wie ich das ganze dann OpenGL praesentiere kriege ich glaube ich raus,
wenn OpenGL dieses Format mag.
-
can be deleted
-
Hi noch einmal!
Ich habe nun rausgefunden, dass die ppm-Dateien doch binär sind und
habe das ganze mal versucht mit ifstream zu lösen.
Hier mein bisheriger Ansatz:void read_texture_file(char* texture){ int number; string word=" "; char* comment; int width, height, maxval; ifstream input("../data/face.ppm"); if(!input) cerr << "Texture file not found!" << endl; input >> word; if(word.compare("P6")!=0) cerr << "Texture file is not in the correct format!" << endl; input >> word; while(word.compare("#")==0){ input.getline(comment,70); //"discard" the line - max 70chars by spec. input >> word; } width = atoi(word.c_str()); input >> height; input >> maxval; texture = (char*) malloc( sizeof(char) * width * height * 3 ); input.read(texture,width*height*3); }
-
Offset heißt, dass du data[Position + R] ist das Byte für Rot und data[Position + G] für Grün und entsprechend für B. Also die Reihenfolge ist R -> G -> B.
Max Color sagt dir, den Wertebereich. 255 ist eben ein Byte/Farbe.
-
Meine Fragen:
1. Kann ich das als char* array speichern? Wie viel Platz bräuchte ich dann?
2. wie viele charakter müsste ich dann mit read lesen?
3. ist meine while schleife eine gute möglichekeit kommentare auszublenden?
4. ich müsste dann theoretisch die while schleife nach jedem einlesen ausführen, hm?Bin für Hilfe - speziell mit Erläuterung - sehr dankbar!
ZuhalaPS. Irgendwie wollte mich das System nicht beide Teile zusammen absenden lassen - wegen Spam?!
-
P6 ist ein Binärformat. Wie das genau ausschaut steht auch im Wikipedia-Artikel. Erst eben P6 und dann breite und höhe und max color und danach die Farbdaten eben im Binärformat (das OpenGL so verstehen sollte).
1. Nimm kein char, sondern std::uint8_t (aus <cstdint>) oder unsigned char, wenn dein Compiler das nicht hat.
2. Halt den Header und dann width*height*3 (wenn max color == 255)
3. Nein. Kommen da überhaupt Kommentare drin vor? Wenn du wissen willst, ob das erste Zeichen ein "#" ist, dann machword[0] == '#'. Aber dein comment ist ja ein wilder Pointer und da willst du 70 Zeichen reinlesen? Wie soll das funktionieren?Außerdem ist sizeof(char) immer 1! Und benutz kein malloc in C++!
-
Hallo Rüdiger!
1. ok, dann nehme ich unsigned char.
3. Kommentare kommen jetzt in meiner speziellen Datei nicht vor, aber allgemein sicherlich (zB im Wikipedia Beispiel).
Wenn ich einfach auf word[0]=='#' überprüfe reicht das ja nicht, ich muss ja die Ganze Zeile wegschmeißen, die ein Kommentar ist.
Ich dachte das könnte ich tun, in dem ich die gesamte Zeile einlese (der Read-Pointer müsste doch dann weiterwandern).Statt malloc sollte ich wohl new verwenden?
Hier wie es gerade ausschaut:
void read_texture_file(unsigned char* texture){ int number; string word=" "; char* comment = (char *) malloc(70); int maxval; ifstream input("../data/face.ppm"); if(!input) cerr << "Texture file not found!" << endl; input >> word; if(word.compare("P6")!=0) cerr << "Texture file is not in the correct format!" << endl; input >> word; while(word.compare("#")==0){ input.getline(comment,70); //"discard" the line - max 70chars by spec. input >> word; } width = atoi(word.c_str()); input >> height; input >> maxval; //texture = (unsigned char*) malloc( sizeof(unsigned char) * width * height * 3 ); texture = new unsigned char(width*height*3); //? input.read(texture,width*height*3); }Problem:
- input.read erwartet als 1.Argument "char*" und mein Compiler meckert nun über eine ungültige Umwandlung von unsigned char* in char*Zuhala
-
Die ganze Funktion ist schon zum Scheitern verurteilt und erzeugt riesige Speicherlecks, da du per new[] Speicher anforderst und einer lokalen Variable zuweist. Diese Variable geht nach Verlassen der Funktion verloren und mit ihr der Zeiger auf den dynamisch erzeugten Speicherbereich, sodass du niemals delete[] aufrufen kannst, um ihn wieder freizugeben. Ausserdem erzeugst du einen 70 Byte großen Puffer per alloc, der genausogut auf dem Stack angelegt werden könnte. Diesen Puffer gibst du ebenfalls nie wieder frei, erzeugst damit also ein zweites Speicherleck.
Du könntest die Textur in einer eigenen Klasse kapseln, die Stream Operatoren überladen und eine Schnittstelle für den Datenzugriff anbieten:#include <vector> #include <iostream> #include <algorithm> class Texture { unsigned int Width_; unsigned int Height_; std::vector<unsigned char> Data_; public: Texture() : Width_( 0 ), Height_( 0 ) { } unsigned int width() const { return Width_; } unsigned int height() const { return Height_; } void resize( unsigned int Width, unsigned int Height ) { if( Width != width() || Height != height() ) { // Puffergröße und Dimensionen anpassen Width_ = Width; Height_= Height; Data_.resize( Width * Height * 3); } } unsigned char* data() const { if( false == Data_.empty() ) { // Adresse des ersten Elements zurückgeben return &Data_.front(); } // Textur ist leer return 0; } void swap( Texture& op ) { // Elementweise vertauschen std::swap( Width_, op.Width_ ); std::swap( Height_, op.Height_ ); std::swap( Data_, op.Data_ ); } void read( std::istream& is ) { Texture Local; // Daten in Local einlesen, ggf. exception werfen oder Status Bits des istream Objektes setzen // Inhalt mit Local tauschen swap( Local ); } void write( std::ostream& is ) const { // Textur in Stream schreiben } }; std::istream& operator<<( istream& is, Texture& op ) { op.read( is ); return is; } std::ostream operator>>( ostream& os, const Texture& op ) { op.write( os ); return os; } int main() { std::ifstream is( "c:/texture-in.ppm" ); std::ofstream os( "c:/texture-out.ppm" ); Texture t; try { is >> t; // Textur aus Datei lesen os << t; // Textur in Datei schreiben } catch( std::exception& excp ) { std::cout << excp.what() << std::endl; } }Diese Lösung hat den Vorteil, dass du ohne eigene Doofheit keine Speicherlecks erzeugen kannst, da std::vector und die C++ Kopiersemantiken sämtliche Speicherverwaltung abnehmen. Ausserdem bietet sie typsicheres Stream IO.
Wenn das deinen Code zu viel aufbläht (z.B. weil du noch einige andere Klassen hast, für die du ähnliche Funktionen/Wrapper bereitstellen müsstest) kannst du deine Funktion so umbauen, dass sie einen Zeiger auf einen dynamisch erzeugten Texturpuffer zurückgibt.
unsigned char* read_texture( /* div. Parameter*/ ) { ... unsigned char* Buffer = new[/*Size*/]; ... return Buffer; }Diese Lösung halte ich für gefährlich, da es keinen expliziten Besitzer für den dynamisch erzeugten Speicherblock gibt und somit auch nicht klar ist, wer für dessen Zerstörung verantwortlich ist. Inwieweit das GUI Framework das Besitztum übernimmt muss dann vielleicht noch geklärt werden (und disqualifiziert gegebenenfalls meinen Ansatz).
Möglich sind ebenfalls noch die Rückgabe als array/shared_array aus boost/TR1 oder einen std::vector.
Edit:
Hat jemand eine Ahnung, warum der zweite cpp Abschnitt nicht richtig formatiert wird?